您的位置:首页 > 其它

APK加固之类抽取分析与修复

2015-09-29 17:39 369 查看
转载地址:http://mp.weixin.qq.com/s?__biz=MjM5NzAxMzk4NA==&mid=210073533&idx=1&sn=93f458546a4631e0d6633564f0d5b3c9&scene=0&key=949503d320dbced03c3805aea6a657b1e4ac0e3e85d082f52f17c67a980d7df749837be99b2025e75c000e2bf827681a&ascene=0&uin=MTY1NjQ5MjM0MA%3D%3D&devicetype=iMac+MacBookPro11%2C1+OSX+OSX+10.10.1+build(14B25)&version=11020201&pass_ticket=eHSlFVpR5TFuMX3QcDGHw0eWqA82PkE4v9YLuY%2BaCuO8BGUM%2BwBR5hXkGqn8mm3%2F

注:由于篇幅有限,文章中部分代码有删减,具体请点击左下角原文链接查看。

测试环境与工具

手机系统: 华为U9508 android 4.2.2

IDA Pro 6.8

AndroidKiller 1.2

高手不要见笑,仅供小菜玩乐,有不对或不足的地方还请多多指教,不胜感激!

0x00 简单介绍

目前我己知的APK加固主要有以下两种方式(或有其它的方式有待发现)

隐藏dex文件:通过对目标DEX文件进行整体加密或压缩方式把整个dex转换为另外一个文件存放在assets文件夹中或者其它地方,然后利用类加载器技术进行内存解密并加载运行。

修改dex结构:抽取DexCode中的字节码指令后用零去填充,或者修改方法属性等操作,运行时在内存中做修正、修复等处理工作。

0x01 APK加固前后对比

整体来看一下原始APK包和加固后的APK包结构相关变化



图1

图1所示加固后的APK包变化如下:

新增2个文件夹:

assets文件夹中增加3个文件

data

dx

pk

lib文件夹中增加了2个so文件

libedog.so

libfdog.so

被修改的文件:

AndroidManifest.xml

classes.dex

0x02 壳流程分析

我们用AndroidKiller反编译加固后的APK, 反编译出错,错误日志如下:

图2

从图2可以看出反编译时出现了很多错误,我们用IDA对DEX进行反编译查看代码,发现方法指令都被零填充了,反编译后代码显示为nop样式,如图3所示。

图3

我们再来看看APK中的AndroidManifest.xml文件被修改了什么地方?

图4

从图4看到AndroidManifest.xml中的application新增了如下项做为壳的入口

android:name="com.edog.AppWrapper"该类为壳的入口,继续分析AppWrapper都做了些什么?

图5

图6

图7

从图5-7可以看出最终会调用到libedog.so中的dl函数,下面就开始动态调试分析该so的功能流程(如何动态调试就不说了,网上己经有很多的教程了)。

通过动态分析libedog.so中的dl函数主要功能是: 获得系统版本号->验证加固前后的签名是否一致->反调试->将抽走的指令映射到内存中还原指令时用到->HOOK函数dvmResolveClass->结束。

代码流程如下:

libedog.so:5D692C64
20 1C MOVS R0, R4

libedog.so:5D692C66 01 99 LDR R1, [SP,#0xF0+var_EC]

libedog.so:5D692C68 00 F0 8A FC BL _Z6verifyP7_JNIEnvP8_jobject ; 比较加固前后的签名是否一致

libedog.so:5D692C6C 16 49 LDR R1, =(aDataDataSLibLi - 0x5D692C76)

libedog.so:5D692C6E 2A 68 LDR R2, [R5]

libedog.so:5D692C70 03 A8 ADD R0, SP, #0xF0+var_E4

libedog.so:5D692C72 79 44 ADD R1, PC ; "/data/data/%s/lib/libfdog.so"

libedog.so:5D692C74 FF F7 B0 EE BLX sprintf

libedog.so:5D692C78 03 A8 ADD R0, SP, #0xF0+var_E4

libedog.so:5D692C7A 01 1C MOVS R1, R0

libedog.so:5D692C7C 00 F0 02 FD BL _Z4antiPKcS0_ ; 反调试

libedog.so:5D692C80 00 F0 3E F9 BL _Z10openMemoryv ; 将抽走的指令映射到内存中来

libedog.so:5D692C80 ; assets中的data文件

libedog.so:5D692C84 23 68 LDR R3, [R4]

libedog.so:5D692C86 A9 22 92 00 MOVS R2, #0x2A4

libedog.so:5D692C8A 9B 58 LDR R3, [R3,R2]

libedog.so:5D692C8C 00 99 LDR R1, [SP,#0xF0+var_F0]

libedog.so:5D692C8E 20 1C MOVS R0, R4

libedog.so:5D692C90 00 22 MOVS R2, #0

libedog.so:5D692C92 98 47 BLX R3

libedog.so:5D692C94 0D 49 LDR R1, =(unk_5D6A2A0D - 0x5D692C9A)

libedog.so:5D692C96 79 44 ADD R1, PC

libedog.so:5D692C98 FF F7 A4 EE BLX strstr

libedog.so:5D692C9C 00 28 CMP R0, #0

libedog.so:5D692C9E 02 D1 BNE loc_5D692CA6

libedog.so:5D692CA0 33 68 LDR R3, [R6]

libedog.so:5D692CA2 14 2B CMP R3, #0x14 ; 判断版本

libedog.so:5D692CA4 00 DD BLE loc_5D692CA8 ; 根据操作系统的版本

libedog.so:5D692CA4 ; hook对应的dvmResolveClass函数

libedog.so:5D692CA6

libedog.so:5D692CA6 loc_5D692CA6 ; CODE XREF: Java_com_edog_ELibrary_d1+86j

libedog.so:5D692CA6 01 20 MOVS R0, #1

libedog.so:5D692CA8

libedog.so:5D692CA8 loc_5D692CA8 ; CODE XREF: Java_com_edog_ELibrary_d1+8Cj

libedog.so:5D692CA8 00 F0 E8 FB BL _Z7restorei ; 根据操作系统的版本

libedog.so:5D692CA8 ; hook对应的dvmResolveClass函数

libedog.so:5D692CAC 35 9A LDR R2, [SP,#0xF0+var_1C]

libedog.so:5D692CAE 3B 68 LDR R3, [R7]

libedog.so:5D692CB0 9A 42 CMP R2, R3

libedog.so:5D692CB2 01 D0 BEQ loc_5D692CB8

libedog.so:5D692CB4 FF F7 9C EE BLX sub_5D6929F0

libedog.so:5D692CB8 ; ---------------------------------------------------------------------------

libedog.so:5D692CB8

libedog.so:5D692CB8 loc_5D692CB8 ; CODE XREF: Java_com_edog_ELibrary_d1+9Aj

libedog.so:5D692CB8 37 B0 ADD SP, SP, #0xDC

libedog.so:5D692CBA F0 BD POP {R4-R7,PC}

libedog.so:5D692CBA ; End of function Java_com_edog_ELibrary_d1

libedog.so:5D692CBA

libedog.so:5D692CBA ; -------------------------------------

0x03 指令还原算法分析

原始指令还原时机就是在dvmResolveClass的hook函数中对对指令进行解密还原,以下结构的中的几个值会用到,因为被保护后的方法中的debugInfoOff的值被修改成从0x20000000开始的一个值,该值在指令还原时起到重要作用。

struct DexCode {

u2 registersSize;

u2 insSize;

u2 outsSize;

u2 triesSize;

u4 debugInfoOff; /* file offset to debug info stream */

u4 insnsSize; /* size of the insns array, in u2 units */

u2 insns[1];

};

指令还原大致流程如下:

判断是否为保护的类->判断debuginfo值大于0x1FFFFFFF->将debuginfo值左移8位再右移6位->将移位后的值加上加密指令在内存中的开始址取4字节做为偏移->将偏移加上加密指令在内存中的开始地址定位到对应方法的指令->解密指令并还原->清零debuginfo值->结束。

解密指令算法流程如下:(每4字节进行xor)

XorArray函数中进行解密操作->将方法debuginfo值进行crc32计算得到一个值->crc32计算得到的值与指令每4字节进行xor->4字节结束后再将crc32值用PolyXorKey函数生成一个新的4字节数做为密钥,一直循环到解密完成。

代码流程如下

libedog.so:5D693174
25 4B LDR R3, =0x1FFFFFFF

libedog.so:5D693176 02 9A LDR R2, [SP,#0x38+Debug_info]

libedog.so:5D693178 9A 42 CMP R2, R3 ; 判断debuginfo值大于 0x1FFFFFFF (因为被保护的方法debuginfo从0X20000000开始)

libedog.so:5D69317A 44 D9 BLS loc_5D693206

libedog.so:5D69317C 24 49 LDR R1, =(aLandroid - 0x5D693184)

libedog.so:5D69317E 20 1C MOVS R0, R4

libedog.so:5D693180 79 44 ADD R1, PC ; "Landroid/"

libedog.so:5D693182 FF F7 30 EC BLX strstr ; 是系统的类就跳过

libedog.so:5D693186 00 28 CMP R0, #0

libedog.so:5D693188 3D D1 BNE loc_5D693206

libedog.so:5D69318A 36 78 LDRB R6, [R6]

libedog.so:5D69318C 01 96 STR R6, [SP,#0x38+var_34]

libedog.so:5D69318E 00 2E CMP R6, #0

libedog.so:5D693190 39 D1 BNE loc_5D693206

libedog.so:5D693192 20 4B LDR R3, =(aFjFj0fjFjFj4fj+0xC - 0x5D69319C)

0x04 编写修复程序

修复程序主要分为解析dex与解密两个步骤来完成,这里只贴出部分代码详细的请看工程,代码写得比较粗操,看下思路就行了,有性趣的就慢慢撸吧!

void
fixdexClassData()

{

DexFile *dexFile = &gDexFile;

char * Tag = "L";

char * ClassTag = "Landroid/";

const DexClassDef* classdef;

u4 count = dexFile->pHeader->classDefsSize;

printf("该DEX共有%d 个类\n", count);

const u1* pEncodedData = NULL;

DexClassData* pClassData = NULL;

const char *descriptor = NULL;

int FileSize = file_size();

gCodeData = (u1*)malloc(FileSize);

if (NULL == gCodeData)

{

printf("分配内存失败!\n");

return;

}

memset(gCodeData, 0, FileSize);

//获得加密指令数据

GetCodeData(gCodeData, FileSize);

if (NULL == gCodeData)

{

printf("获取加密指令数据出错!\n");

return;

}

for(u4 i=0; i<count; i++){

classdef = dexGetClassDef(dexFile, i);

descriptor = getTpyeIdString(dexFile, classdef->classIdx);

if (strstr(descriptor,Tag) == NULL)

{

continue;

}

//跳过一些系统的类

if (strstr(descriptor, ClassTag) != NULL)

{

continue;

}

pEncodedData = dexFile->baseAddr + classdef->classDataOff;

pClassData = dexReadAndVerifyClassData(&pEncodedData, NULL);

if (pClassData == NULL) {

continue;

}

FixdexMethodInsns(dexFile, pClassData, descriptor);

}

}

void FixdexMethodInsns(DexFile *dexFile, const DexClassData*classData
,const char* className)

{

int idx = 0;

DexMethod *method = NULL;

const DexMethodId* methodId = NULL;

DexCode* code = NULL;

const char* methodName;

method = classData->directMethods;

methodId = dexFile->pMethodIds;

unsigned int CodeDataOffset = 0;

u1 * tempCode = NULL;

for (int i = 0; i < (int) classData->header.directMethodsSize; i++)
{

idx = classData->directMethods[i].methodIdx;

methodId = dexGetMethodId(dexFile, idx);

methodName = dexStringById(dexFile, methodId->nameIdx);

DexCode* pCode = dexGetCode(dexFile, &classData->directMethods[i]);

if (NULL == pCode)

{

continue;

}

//判断是否为保护后的方法,如果是就修复指令

if ( (pCode->debugInfoOff > 0x1FFFFFFF) && (pCode->insns[0] == 0X00))

{

//求加密指令的偏移

CodeDataOffset = pCode->debugInfoOff << 0x8;

CodeDataOffset >>= 0x6;

//解密指令

tempCode = DecCode(gCodeData+CodeDataOffset, pCode->insnsSize*sizeof(u2),
pCode->debugInfoOff, gCodeData);

//修复指令

memcpy(pCode->insns, tempCode, (pCode->insnsSize)*sizeof(u2));

pCode->debugInfoOff = 0x00;

printf("修复%s 类中的%s 方法成功! 大小%X\n",className, methodName,pCode->insnsSize);

if (NULL != tempCode)

{

free(tempCode);

tempCode = NULL;

}

}

}

for (int i = 0; i < (int) classData->header.virtualMethodsSize; i++)
{

idx = classData->virtualMethods[i].methodIdx;

methodId = dexGetMethodId(dexFile, idx);

methodName = dexStringById(dexFile, methodId->nameIdx);

DexCode* pCode = dexGetCode(dexFile, &classData->virtualMethods[i]);

if (NULL == pCode)

{

continue;

}

//判断是否为保护后的方法,如果是就修复指令

if ( (pCode->debugInfoOff > 0x1FFFFFFF) && (pCode->insns[0] == 0X00))

{

//求加密指令的偏移

CodeDataOffset = pCode->debugInfoOff << 0x8;

CodeDataOffset >>= 0x6;

//解密指令

tempCode = DecCode(gCodeData+CodeDataOffset, pCode->insnsSize*sizeof(u2),
pCode->debugInfoOff, gCodeData);

//修复指令

memcpy(pCode->insns, tempCode, (pCode->insnsSize)*sizeof(u2));

pCode->debugInfoOff = 0x00;

printf("修复%s 类中的%s 方法成功! 大小%X\n",className, methodName,pCode->insnsSize);

if (NULL != tempCode)

{

free(tempCode);

tempCode = NULL;

}

}

}

return;

}

unsigned int PolyXorKey(DWORD crckey)

{

unsigned int dwKey;

char temp;

unsigned __int8 temp1;

unsigned __int8 temp2;

char *pKey;

int temp3;

int j;

int i;

j = 0;

temp3 = 0;

pKey = (char *)&dwKey;

temp2 = 0;

temp1 = 0;

temp = 0;

dwKey = crckey ^ 0xDF138530;

i = 0;

while ( i <= 3 )

{

temp2 = *pKey;

j = 128;

temp3 = 7;

while ( j > 1 )

{

temp = (temp2 & j / 2) >> (temp3 - 1);

temp1 = ((signed int)(unsigned __int8)(temp2 & j) >> temp3) ^ temp;

temp1 <<= temp3;

temp2 |= temp1;

j /= 2;

--temp3;

}

temp = temp2 & 1;

temp1 = temp2 & 1 ^ temp2 & 1;

*pKey = temp2;

++i;

++pKey;

}

return dwKey;

}

0x05 测试与总结

将加固后的 APK中assets文件夹中的data文件与classes.dex放在修复程序同一个目录中,然后运行修复程序。



图8

去掉AndroidManifest.xml中的壳入口,将修复后的classes.dex重新打包反编译,成功运行,如图9所示能正常反编译源码,至此,分析完毕。


壳流程总结:

AndroidManifest.xml中的壳入口->com.edog.AppWrapper->

so中Java_com_edog_ELibrary_d1->hookdvmResolveClass函数->在dvmResolveClass
hook函数中修复指令->结束。

语言表达不行,说的很杂,自己都觉得文章没有任何逻辑可言,如果大家能从中获得一些思路那也是好的, 不过这次分析让自己学到了很多,感谢APK加固作者。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: