加壳
学习脱壳之前,首先先学习一下什么是加壳,加壳是在二进制的程序中植入一段代码,在运行的时候这段植入的代码会优先取得程序的控制权,做一些额外的工作(将原来的代码还原出来执行,以达到保护原来的代码直接暴露的问题)。大多数病毒就是基于此原理。Android 作为新兴出现的平台,加壳方式不像 PC 端一样成熟,本次分析常见的 Dex 加壳和脱壳方式。在此之前,首先需要简单了解 Dex 文件的结构如下表所示。
字段名称 | 偏移值 | 长度 | 描述 |
---|---|---|---|
magic | 0x0 | 8 | 'Magic'值,即魔数字段,格式如”dex/n035/0”,其中的035表示结构的版本。 |
checksum | 0x8 | 4 | 校验码。 |
signature | 0xC | 20 | SHA-1签名。 |
file_size | 0x20 | 4 | Dex文件的总长度。 |
header_size | 0x24 | 4 | 文件头长度,009版本=0x5C,035版本=0x70。 |
endian_tag | 0x28 | 4 | 标识字节顺序的常量,根据这个常量可以判断文件是否交换了字节顺序,缺省情况下=0x78563412。 |
link_size | 0x2C | 4 | 连接段的大小,如果为0就表示是静态连接。 |
link_off | 0x30 | 4 | 连接段的开始位置,从本文件头开始算起。如果连接段的大小为0,这里也是0。 |
map_off | 0x34 | 4 | map数据基地址。 |
string_ids_size | 0x38 | 4 | 字符串列表的字符串个数。 |
string_ids_off | 0x3C | 4 | 字符串列表表基地址。 |
type_ids_size | 0x40 | 4 | 类型列表里类型个数。 |
type_ids_off | 0x44 | 4 | 类型列表基地址。 |
proto_ids_size | 0x48 | 4 | 原型列表里原型个数。 |
proto_ids_off | 0x4C | 4 | 原型列表基地址。 |
field_ids_size | 0x50 | 4 | 字段列表里字段个数。 |
field_ids_off | 0x54 | 4 | 字段列表基地址。 |
method_ids_size | 0x58 | 4 | 方法列表里方法个数。 |
method_ids_off | 0x5C | 4 | 方法列表基地址。 |
class_defs_size | 0x60 | 4 | 类定义类表中类的个数。 |
class_defs_off | 0x64 | 4 | 类定义列表基地址。 |
data_size | 0x68 | 4 | 数据段的大小,必须以4字节对齐。 |
data_off | 0x6C | 4 | 数据段基地址 |
可以看到 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。
然后原 Dex 就 dump 出来了,然后直接反编译成 smali 文件查看即可。这里有一个小问题需要提一下:
有时断 dexFileParse 会有更好的效果,有的壳判断系统版本,>=4.0则使用dvmRawDexFileOpenArray 函数,<4 的话才用 dvmDexFileOpenPartial。
举一个简单的加壳例子简单了解加壳过程,流程图如下:
可以看到此时原来的 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 的值。
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 执行。
然后依次 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
* Dex 文件结构