APK加固之静态脱壳机编写入门
目录:
0x00APK加固简介与静态脱壳机的编写思路
1.大家都知道Android中的程序反编译比较简单,辛苦开发出一个APK轻易被人反编译了,所以现在就有很多APK加固的第三方平台,比如爱加密和梆梆加固等。
2.一般的加固保护通常能够提供如下保护:加密、防逆向、防篡改、反调试、反窃取等功能,编写静态脱壳机须要信息有加密后的原始DEX数据、解密算法、解密密钥、要想获得这些信息我们首先要解决的问题是过反调试、动态分析解密流程、获取密钥,获得原始DEX数据存放位置、分析解密算法。
0x01壳简单分析
1.整体来看一下加固前APK包和加固后的APK包结构相关变化,如图1所示。
图1
图1所示加固后的APK增加了librsprotect.so、librsprotect_x86.so、rsprotect.dat文件,发生变化的有AndroidManifest.xml、classes.dex文件。
2.反编译加固后APK,APK中的AndroidManifest.xml文件的入口被修改,如图2所示。
图2
3.入口类中主要会调用librsprotect.so中的3个函数,如图3所示。
private native void initialize(Context paramContext);
private native Application makeApplication(String paramString);
private native void applicationOnCreate();
图3
0x02 SO文件脱壳
1.既然主要是调用librsprotect.so中的函数,我们将librsprotect.so放到IDA Pro中分析,发现代码都是乱码 图4所示,说明被加密了。
图4
2.一般加壳的SO的壳代码都在INIT段或INIT_ARRAY段,我们先看下被加壳以后的SO信息,用readelf -a命令查看,图5所示
图5
可以看到INIT值为0x2ea91,到 IDA中看看该地址的内容,就是壳的入口了,明显是UPX的加壳,图6所示,有人会问你为什么会知道是UPX的壳,“只是因为在人群中多看了你一眼,再也没能忘掉你容颜!(^_^)”。
图6
3.尝试用upx -d脱壳,因为这样脱方便、干净、省事、提示图7所示的信息。
图7
查看UPX源码后发现可能是没有找到UPX!的标志,用16进制工具打开librsprotect.so发现标志被改成了RSP!,将其改成UPX!后再将尝试,出现 图8所示的信息。
图8
出现这种错误可能是做变形处理了或者是版本不对,通过分析librsprotect.so的壳代码好像没有变形处理,所以决定重新编一个3.92版本的来试试,编译好后脱壳成功,如图9所示。
图9
将脱壳后的so放到IDA Pro中分析,代码正常,图10所示,SO脱壳完成。
图10
0x03 反调试分析
1.如何使用IDA调试android的SO模块,网上教程也太多太多了,这里不多说,将脱壳后的librsprotect.so替换掉原始有壳的SO后(也可不用替换没影响,这里只是为了测试)签名安装进行动态分析。
2.通过动态调试该加壳程序,它用到的反调试方法是首先试探性读取/proc/pid/status获取进程状态去判断是否有调试器,如果发现被调试就kill掉本进程,如图11所示
图11
3.通过读取/proc/net/tcp查看正在运行应用的本地端口号是否有android_server端口,如果有就创建一个反调试线程,如图12所示,每隔几秒检查一次,过反调试就很简单了直接把返回值改成假就成了。
图12 android_server运行后端口
图13
0x04解密流程分析
1. 根据算法中的常量值猜测该算法为MD5,如图14所示
图14
2.获取包名并计算MD5值 图15所示,将该值做为密钥。
com.droider.crackme0201
F2 E8 F0 62 85 17 9C 3C 99 F5 67 9F A6 27 FC 55
图15
2.打开并读取/data/data/com.droider.crackme0201/files/.rsdata/rsprotect.dat数据,该文件是从APK包中的assets文件夹中拷贝过来的,判断前4字节是"RSFL"是否与so中的相同,不同则退出,rsprotect.dat前0x1000字节存放原始DEX大小与循环解密的次数,每次解密0x1000字节,根据密钥初始化流程发现解密算法为RC4,图16示(也可以看IDB)。
图16
0x05脱壳机编写
1.通过分析,已经知道了壳的数据、密钥、算法、解密过程, 现在来写脱壳机。
必要步骤如下:
1。解包获得rsprotect.da数据。
2.XML解析获得包名。
3.MD5计算获得密钥。
4.RC4解密rsprotect.dat中的数据。
代码流程:
1 #include"stdafx.h" 2 #include<afxwin.h> 3 #include<stdio.h> 4 #include<windows.h> 5 #include<process.h> 6 #include<assert.h> 7 #include<string> 8 #include<iostream> 9 #include"CMarkup.h" 10 #include"md5.h" 11 #include"rc4.h" 12 #include<string> 13 usingnamespacestd; 14 BOOLGetPackName(char* pathXml, charoutPackName[256]) 15 { 16 CMarkupxml; 17 boolflag; 18 CStringpackName; 19 CStringAppandroidname; 20 MCD_STRmyapkName; 21 MCD_STRattribName; 22 char* strXML = "\\AndroidManifest.xml"; 23 strcat(pathXml,strXML); 24 flag = xml.Load((MCD_STR)pathXml); 25 if ( FALSE == flag) 26 { 27 printf("获得包名失败...\n"); 28 returnFALSE; 29 } 30 flag = xml.FindElem((MCD_STR)"manifest"); 31 //获取包名......... 32 for(intattribIndex=0;;attribIndex++) 33 { 34 attribName=xml.GetAttribName(attribIndex); 35 if (attribName.GetLength()!=0) //方法若返回empty string,即表示属性结束,结束循环 36 { 37 MCD_STRattribVal = xml.GetAttrib(attribName);//否则读取属性值,以子元素加入dxml 38 //------判断是否为包名... 39 packName = attribName.GetString(); 40 if ( 0 == strcmp(packName.GetString(), "package")) 41 { 42 myapkName = attribVal; 43 strcpy(outPackName, attribVal.GetString()); 44 returnTRUE; 45 } 46 } 47 else 48 break; 49 } 50 returnFALSE; 51 } 52 voidStrToHex(BYTE *pbDest, BYTE *pbSrc, intnLen) 53 { 54 charh1,h2; 55 BYTEs1,s2; 56 inti; 57 for (i=0; i<nLen; i++) 58 { 59 h1 = pbSrc[2*i]; 60 h2 = pbSrc[2*i+1]; 61 s1 = toupper(h1) - 0x30; 62 if (s1> 9) 63 s1 -= 7; 64 s2 = toupper(h2) - 0x30; 65 if (s2> 9) 66 s2 -= 7; 67 pbDest[i] = s1*16 + s2; 68 } 69 } 70 int_tmain(intargc, _TCHAR* argv[]) 71 { 72 charstrAPK[512] = {0}; 73 charapkd[256] = ""; 74 charFileDirectory[512] = {0}; 75 chardexDirectory[512] = {0}; 76 charPackName[256] = {0}; 77 BOOLret = FALSE; 78 structrc4_staterc4_test; 79 FILE *fp; 80 DWORDfileSize = 0; 81 BYTE *ptr = NULL; 82 DWORDDecOffset = 0X1000;//文件偏移 83 DWORDDecSize = 0X0;//大小 84 DWORDindex = 0;//循环解密的次数 85 stringkey; 86 printf("请输入要脱壳的apk包路径:\n"); 87 scanf("%s",strAPK); 88 if (NULL == strAPK) 89 { 90 printf("路径不能为空!\n"); 91 return -1; 92 } 93 strcpy(apkd, "java -jar apktool.jar d "); 94 strcat(apkd, strAPK); 95 //--------bat解包 96 CFileapktool("apktool.bat", CFile::modeCreate | CFile::modeReadWrite); 97 apktool.Write(apkd, strlen(apkd)); 98 apktool.Write("\r\n", strlen("\r\n")); 99 apktool.Close(); 100 char* cmd1 = "apktool.bat"; 101 STARTUPINFOsi1; 102 GetStartupInfo(&si1); 103 si1.dwFlags = STARTF_USESHOWWINDOW; 104 si1.wShowWindow = SW_HIDE; 105 PROCESS_INFORMATIONpi1; 106 CreateProcess(NULL, 107 (LPSTR)cmd1, 108 NULL, 109 NULL, 110 FALSE, 111 CREATE_NEW_CONSOLE, 112 NULL, 113 NULL, 114 &si1, 115 &pi1); 116 printf("正在解包apk...\n"); 117 WaitForSingleObject(pi1.hProcess,INFINITE); 118 DeleteFile("apktool.bat"); 119 printf("解包完成...\n"); 120 //-----判断是否解包成功.......... 121 strncpy(FileDirectory, strAPK, strlen(strAPK)-strlen(".apk")); 122 DWORDdwFileAtt; 123 dwFileAtt = GetFileAttributes(FileDirectory); 124 //判断是否为目录 125 if( dwFileAtt != FILE_ATTRIBUTE_DIRECTORY) 126 { 127 printf("apk解包失败!...\n"); 128 return 0; 129 } 130 printf("apk解包成功!...\n"); 131 strcpy(dexDirectory, FileDirectory); 132 //得到包名 133 ret = GetPackName(FileDirectory,PackName); 134 if (FALSE == ret) 135 { 136 printf("获得包名失败...\n"); 137 return -1; 138 } 139 //计算MD5值 140 MD5md5(PackName); 141 key = md5.md5(); 142 strcat(dexDirectory, "\\assets\\rsprotect.dat"); 143 fp=fopen(dexDirectory, "rb"); 144 if(fp==NULL) 145 printf("打开文件失败!..."); 146 //求文件大小 147 fseek(fp, 0, SEEK_END); 148 fileSize = ftell(fp); 149 fseek(fp, 0, SEEK_SET); 150 ptr = (BYTE*)malloc(fileSize); 151 if (NULL == ptr) 152 { 153 puts("malloc error"); 154 } 155 memset(ptr,fileSize,0); 156 fread(ptr, sizeof(BYTE), fileSize, fp); 157 fclose(fp); 158 //--解密dex 159 DecSize = *(DWORD*)(ptr+12); 160 index = *(DWORD*)(ptr+0x10); 161 ptr += DecOffset; 162 if (0 == DecSize) 163 { 164 printf("要解密的dex大小出错\n"); 165 return -1; 166 } 167 memset(&rc4_test,0,sizeof(rc4_test)); 168 unsignedcharDecKey[32] = {0}; 169 for (inti=0; i<32; i++) 170 { 171 DecKey[i] = key[i]; 172 } 173 unsignedcharkey1[32] ={0}; 174 StrToHex(key1, DecKey, 0x10); 175 //--生成解密后的dex文件 176 fp = fopen("classes.dex","wb"); 177 if (NULL == fp) 178 { 179 printf("File open error\n"); 180 } 181 for (inti=0; i<index; i++) 182 { 183 //初始化Key 184 init_Key(&rc4_test, key1, 0x10); 185 //解密数据 186 rc4_crypt(&rc4_test, ptr, DecOffset); 187 188 fwrite(ptr, sizeof(BYTE), DecOffset, fp); 189 ptr+=DecOffset; 190 } 191 fclose(fp); 192 if (NULL != ptr) 193 { 194 free(Temp); 195 ptr = NULL; 196 Temp = NULL; 197 } 198 199 printf("解密完成!^_^\n"); 200 return 0; 201 }
0x06 测试与总结
1.运行UnPack.exe输入要解密的APK包路径,成功解密后重新打包并正常反编译,如图17 图18所示。
图17
图18
2.以上就是简单实现一般APK加固静态脱壳机的编写步骤,由于该加固核心so文件使用UPX默认加壳并未做变形处理,导致so被轻松的静态脱卓,而so模块中的反调试手段比较初级且模块化,可以非常简单的手工patch函数一处反回值就可完全过掉,总的来说无论是静态脱壳还是动态dump都是很容易的。
完。
样本及PDF IDB下载
http://yunpan.cn/cFzNPXB27awau (提取码:6937)