2016年10月28日星期五

Android 逆向学习笔记 (六)- 安卓脱壳之 dvmDexFileOpenPartial

加壳

学习脱壳之前,首先先学习一下什么是加壳,加壳是在二进制的程序中植入一段代码,在运行的时候这段植入的代码会优先取得程序的控制权,做一些额外的工作(将原来的代码还原出来执行,以达到保护原来的代码直接暴露的问题)。大多数病毒就是基于此原理。

Android 作为新兴出现的平台,加壳方式不像 PC 端一样成熟,本次分析常见的 Dex 加壳和脱壳方式。在此之前,首先需要简单了解 Dex 文件的结构如下表所示。

字段名称偏移值长度描述
magic0x08'Magic'值,即魔数字段,格式如”dex/n035/0”,其中的035表示结构的版本。
checksum0x84校验码。
signature0xC20SHA-1签名。
file_size0x204Dex文件的总长度。
header_size0x244文件头长度,009版本=0x5C,035版本=0x70。
endian_tag0x284标识字节顺序的常量,根据这个常量可以判断文件是否交换了字节顺序,缺省情况下=0x78563412。
link_size0x2C4连接段的大小,如果为0就表示是静态连接。
link_off0x304连接段的开始位置,从本文件头开始算起。如果连接段的大小为0,这里也是0。
map_off0x344map数据基地址。
string_ids_size0x384字符串列表的字符串个数。
string_ids_off0x3C4字符串列表表基地址。
type_ids_size0x404类型列表里类型个数。
type_ids_off0x444类型列表基地址。
proto_ids_size0x484原型列表里原型个数。
proto_ids_off0x4C4原型列表基地址。
field_ids_size0x504字段列表里字段个数。
field_ids_off0x544字段列表基地址。
method_ids_size0x584方法列表里方法个数。
method_ids_off0x5C4方法列表基地址。
class_defs_size0x604类定义类表中类的个数。
class_defs_off0x644类定义列表基地址。
data_size0x684数据段的大小,必须以4字节对齐。
data_off0x6C4数据段基地址


可以看到 Dex 文件都是以 "dex/n035/0" 开头的,然后后面跟着一堆记录 Dex 文件基本信息的数据,这些不管了,下图有简单的展示,更加具体的 Dex 文件结构可以到给出的参考链接中查看。


举一个简单的加壳例子简单了解加壳过程,流程图如下:


可以看到此时原来的 app 被加密之后放入 “加密 Source DEX 数据” 段,在 “解壳 DEX Body” 段的后面,所以当程序执行时先执行到 “解壳 DEX Body” 部分后将 “加密 Source DEX 数据” 部分解密,然后再执行解密后的内容(即是原 app 的内容)。

加壳程序工作流程:                
1、加密源程序 APK 文件为解壳数据                
2、把解壳数据写入解壳程序 Dex 文件末尾,并在文件尾部添加解壳数据的大小。                
3、修改解壳程序 DEX 头中 checksum、 signature 和 file_size 头信息。                
4、修改源程序 AndroidMainfest.xml 文件并覆盖解壳程序 AndroidMainfest.xml 文件。
        

解壳DEX程序工作流程:                
1、读取 DEX 文件末尾数据获取借壳数据长度。                
2、从 DEX 文件读取解壳数据,解密解壳数据。以文件形式保存解密数据到 a.APK 文件                
3、通过 DexClassLoader 动态加载 a.apk。

脱壳

本次脱壳针对 classes.dex 的重加载,重加载时会动态调用到 libdvm.so 里面的 dvmDexFileOpenPartial 这个函数,这个时候只需要用 IDA 的动态调试并且下断点就行了。

android_server -> adb forward -> adb shell am start -D -n -> ida attach -> add breakpoint -> ddms -> jdb
IDA 动态调试不详细说了,另外提一下,因为这里先运行的是壳程序所以 adb 启动程序的时候需要按照 AndroidManifest.xml 里面显示的 :
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.ali.tg.testapp">
启动 com.ali.tg.testapp/.MainActivity。


在 dvmDexFileOpenPartial 下好断点之后一路点击 IDA 的绿色小箭头运行,直到在 dvmDexFileOpenPartial 这个地方停下来。



然后 Hex-View 设置和 R0 同步,可以看到熟悉的 “dex\n035\0”,开始说到的 Dex 头,然后再设置成和 R1 同步,可以看到 R1 的值。


为什么要着重强调这两个值呢,因为通过函数原型  dvmDexFileOpenPartial(addr, len, &pDvmDex) 可以知道, addr(R0) 保存的是原来 apk classes.dex 的文件起始地址,len(R1) 保存的是原来 apk classes.dex 的文件长度。

然后依次 dump,可以将原 classes.dex 恢复出来,这里有一个 dump 的小脚本,使用 shift + F2 执行。

auto fp, dex_addr, end_addr;
fp = fopen("classes.dex", "wb");
end_addr = r0 + r1;
for (dex_addr = r0; dex_addr < end_addr; dex_addr++)
    fputc(Byte(dex_addr), fp);

然后原 Dex 就 dump 出来了,然后直接反编译成 smali 文件查看即可。这里有一个小问题需要提一下:

有时断 dexFileParse 会有更好的效果,有的壳判断系统版本,>=4.0则使用dvmRawDexFileOpenArray 函数,<4 的话才用 dvmDexFileOpenPartial。

reference