From:https://blog.csdn.net/sinat_18268881/article/details/55832757
Dex文件格式详解:https://www.jianshu.com/p/f7f0a712ddfe
dex文件解析(第三篇):https://blog.csdn.net/tabactivity/article/details/78950379
深入理解DEX文件格式( 有个 Python 读 dex 文件 ):https://mybeibei.net/archives/1103
一文读懂 DEX 文件格式解析:https://cloud.tencent.com/developer/article/1663852
Android逆向之旅—解析编译之后的Dex文件格式:http://www.520monkey.com/archives/579
图解Dex文件结构及解析要点:https://blog.csdn.net/beyond702/article/details/52460721
从 Android 运行时出发,打造我们的脱壳神器:https://blog.csdn.net/earbao/article/details/51516116
http://www.droidsec.cn/从android运行时出发,打造我们的脱壳神器/
参考资料:《Android软件安全与逆向分析》.非虫
- DexFile.h:https://www.androidos.net.cn/android/9.0.0_r8/xref/dalvik/libdex/DexFile.h
- DexFile.cpp:https://www.androidos.net.cn/android/9.0.0_r8/xref/dalvik/libdex/DexFile.cpp
0x00■ 构造 DEX 文件
什么是 dex 文件?
DEX 文件就是 Android Dalvik 虚拟机运行的程序,关于 DEX 文件的结构的重要性我就不多说了。
dex 是 Android 系统的可执行文件,包含应用程序的全部操作指令以及运行时数据。
简单的说,就是优化后的 android版.exe。每个apk安装包里都有。相对于PC上的 java 虚拟机能运行.class;android上的 Davlik 虚拟机能运行.dex。
为何要研究 dex 格式 ?
因为 dex 里面包含了所有 app 代码,利用反编译工具可以获取 java 源码。理解并修改 dex 文件,就能更好的 逆向 APK。
由于dalvik是一种针对嵌入式设备而特殊设计的java虚拟机,所以dex文件与标准的class文件在结构设计上有着本质的区别
当 java 程序编译成 class 后,还需要使用 dx 工具将所有的 class 文件整合到一个 dex 文件,目的是其中各个类能够共享数据,在一定程度上降低了冗余,同时也是文件结构更加经凑,实验表明,dex 文件是传统 jar 文件大小的 50% 左右
可以看见:dex 将原来 class 每个文件都有的共有信息合成一体,这样减少了 class 的冗余
数据结构
类型 | 含义 |
---|---|
u1 | unit8_t,1字节无符号数 |
u2 | unit16_t,2字节无符号数 |
u4 | unit32_t,4字节无符号数 |
u8 | unit64_t,8字节无符号数 |
sleb128 | 有符号LEB128,可变长度1~5 |
uleb128 | 无符号LEB128, |
uleb128p1 | 无符号LEB128值加1, |
其中 u1~u8 很好理解,表示 1 到 8 个字节的无符号数,后面三个是dex特有的数据类型,
不理解的可以参考这里( https://blog.csdn.net/zklth/article/details/7978362 )
更详细的参考:(深入到源码解析leb128数据类型)[http://i.woblog.cn/2016/07/23/leb128-format/]
下面开练。。。。。
建议:不要只看,跟着做。看再多遍不如自己亲自实践一遍来的可靠,别问我为什么知道。泪崩ing.....
下面会自己构造一个 dex 文件,因为自己构造的比较简单,分析起来比较容易。等你简单的会了,难的自然也就懂了。
首先,编写一个简单的 Java 程序,如下:
public class HelloWorld { int a = 0; static String b = "HelloDalvik"; public int getNumber(int i, int j) { int e = 3; return e + i + j; } public static void main(String[] args) { int c = 1; int d = 2; HelloWorld helloWorld = new HelloWorld(); String sayNumber = String.valueOf(helloWorld.getNumber(c, d)); System.out.println("HelloDex!" + sayNumber); }
}
然后将其编译成 dex 文件:打开命令行,进入 HelloWorld.class 所在文件夹下,执行命令:
javac HelloWorld.java
接下来会出现一个HelloWorld.class文件,然后继续执行命令( dx 工具需要安装Android SDK才能有的工具 ):
dx --dex --output=HelloWorld.dex HelloWorld.class
就会出现 HelloWorld.dex 文件了。
在当前工作路径下 , 编译方法如下 :
1. 编译成 java class 文件,执行命令 : javac Hello.java 。编译完成后 ,目录下生成 Hello.class 文件 。可以使用命令 java Hello 来测试下 ,会输出代码中的 “Hello, Android!” 的字符串 。
2. 编译成 dex 文件
编译工具在 Android SDK 的路径如下 ,其中 19.0.1 是Android SDK build_tools 的版本 ,请按照在本地安装的 build_tools 版本来 。建议该路径加载到 PATH 路径下 ,否则引用 dx 工具时需要使用绝对路径 :./build-tools/19.0.1/dx
执行命令 : dx –dex –output=Hello.dex Hello.class
编译正常会生成 Hello.dex 文件 。
3. 使用 ADB 运行测试
测试命令和输出结果如下 :
$ adb root
$ adb push Hello.dex /sdcard/
$ adb shell
root@maguro:/ # dalvikvm -cp /sdcard/Hello.dex Hello
4. 重要说明
(1) 测试环境使用真机和 Android 虚拟机都可以的 。核心的命令是
dalvikvm -cp /sdcard/Hello.dex Hello
-cp 是 class path 的缩写 ,后面的 Hello 是要运行的 Class 的名称 。网上有描述说输入 dalvikvm –help
可以看到 dalvikvm 的帮助文档 ,但是在 Android4.4 的官方模拟器和自己的手机上测试都提示找不到
Class 路径 ,在Android 老的版本 ( 4.3 ) 上测试还是有输出的 。
(2) 因为命令在执行时 , dalvikvm 会在 /data/dalvik-cache/ 目录下创建 .dex 文件 ,因此要求 ADB 的
执行 Shell 对目录 /data/dalvik-cache/ 有读、写和执行的权限 ,否则无法达到预期效果 。
这时,我们需要下载一个十六进位文本编辑器,因为用它可以解析二进制文件,我们用它打开 dex 文件就会全部以十六进制的数进行展现了。
这里推荐 010Editor,下载地址:https://www.sweetscape.com/010editor/(收费软件,可以免费试用30天)。
下载完成之后,我们可以用它打开dex文件了,打开之后,你的界面应该是这样的:
一下子看到这些东西,是不是立马懵逼了,正常,我刚开始看的时候也是,这什么玩意儿啊!其实,这就是二进制流文件中的内容,010Editor把它转化成了16进制的内容,以方便我们阅读的。
0x01■ DEX文件结构总览
一张图搞懂dex
( 可以 右键 ---> 在新标签页中打开图片 ,就可以看清图片)
不要慌,下面我跟你解释,这些东西我们虽然看了懵逼,但是 Dalvik 虚拟机不会,因为它就是解析这些东西的,这些东西虽然看起来头大,但是它是有自己的格式标准的。dex文件的结构如下图所示:
这就是 dex 的文件格式了,下面我们从最上面的 Header 说起,Header 中存储了什么内容呢?下面我们还得来一张图:
从宏观上来说 dex 的文件结果很简单,实际上是由多个不同结构的数据体以首尾相接的方式拼接而成。如下图:
数据名称 | 解释 |
---|---|
header | dex文件头部,记录整个dex文件的相关属性 |
string_ids | 字符串数据索引,记录了每个字符串在数据区的偏移量 |
type_ids | 类似数据索引,记录了每个类型的字符串索引 |
proto_ids | 原型数据索引,记录了方法声明的字符串,返回类型字符串,参数列表 |
field_ids | 字段数据索引,记录了所属类,类型以及方法名 |
method_ids | 类方法索引,记录方法所属类名,方法声明以及方法名等信息 |
class_defs | 类定义数据索引,记录指定类各类信息,包括接口,超类,类数据偏移量 |
data | 数据区,保存了各个类的真是数据 |
link_data | 连接数据区 |
/dalvik/libdex/DexFile.h 定义如下:
struct DexFile {const DexHeader* pHeader;const DexStringId* pStringIds;const DexTypeId* pTypeIds;const DexFieldId* pFieldIds;const DexMethodId* pMethodIds;const DexProtoId* pProtoIds;const DexClassDef* pClassDefs;const DexLink* pLinkData;
}
注意:其中一些定义的字段是在内存中并没有存到真实的 dex 文件中
header 简单记录了dex文件的一些基本信息,以及大致的数据分布。长度固定为0x70,其中每一项信息所占用的内存空间也是固定的,好处是虚拟机在处理 dex 时不用考虑 dex 文件的多样性
字段名称 | 偏移值 | 长度 | 说明 |
---|---|---|---|
magic | 0x0 | 8 | 魔数字段,值为"dex\n035\0" |
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 | 标示字节顺序的常量 |
link_size | 0x2c | 4 | 链接段的大小,如果为0就是静态链接 |
link_off | 0x30 | 4 | 链接段的开始位置 |
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 | 数据段的大小,必须4k对齐 |
data_off | 0x6c | 4 | 数据段基址 |
/dalvik/libdex/DexFile.h 定义如下:
struct DexHeader {u1 magic[8]; /* includes version number */u4 checksum; /* adler32 checksum */u1 signature[kSHA1DigestLen]; /* SHA-1 hash */u4 fileSize; /* length of entire file */u4 headerSize; /* offset to start of next section */u4 endianTag;u4 linkSize;u4 linkOff;u4 mapOff;u4 stringIdsSize;u4 stringIdsOff;u4 typeIdsSize;u4 typeIdsOff;u4 protoIdsSize;u4 protoIdsOff;u4 fieldIdsSize;u4 fieldIdsOff;u4 methodIdsSize;u4 methodIdsOff;u4 classDefsSize;u4 classDefsOff;u4 dataSize;u4 dataOff;
};
我们可以用:hexdump -c classes.dex 查看 dex 单字节显示的结果,如下:
0000000 d e x \n 0 3 5 \0 022 217 ? w z ? 031 221
0000010 ? \f ? ? ? ? ? ? 217 235 200 z ? 030 I ?
0000020 ? 003 \0 \0 p \0 \0 \0 x V 4 022 \0 \0 \0 \0
0000030 \0 \0 \0 \0 ? 002 \0 \0 024 \0 \0 \0 p \0 \0 \0
0000040 \b \0 \0 \0 ? \0 \0 \0 005 \0 \0 \0 ? \0 \0 \0
0000050 001 \0 \0 \0 034 001 \0 \0 005 \0 \0 \0 $ 001 \0 \0
0000060 001 \0 \0 \0 L 001 \0 \0 8 002 \0 \0 l 001 \0 \0
0000070 l 001 \0 \0 t 001 \0 \0 201 001 \0 \0 204 001 \0 \0
0000080 222 001 \0 \0 226 001 \0 \0 ? 001 \0 \0 ? 001 \0 \0
0000090 ? 001 \0 \0 ? 001 \0 \0 004 002 \0 \0 \a 002 \0 \0
00000a0 \v 002 \0 \0 002 \0 \0 ( 002 \0 \0 . 002 \0 \0
00000b0 4 002 \0 \0 9 002 \0 \0 B 002 \0 \0 L 002 \0 \0
00000c0 003 \0 \0 \0 005 \0 \0 \0 006 \0 \0 \0 \a \0 \0 \0
00000d0 \b \0 \0 \0 \t \0 \0 \0 \n \0 \0 \0 \f \0 \0 \0
00000e0 002 \0 \0 \0 003 \0 \0 \0 \0 \0 \0 \0 004 \0 \0 \0
00000f0 004 \0 \0 \0 x 002 \0 \0 \n \0 \0 \0 006 \0 \0 \0
0000100 \0 \0 \0 \0 \v \0 \0 \0 006 \0 \0 \0 x 002 \0 \0
0000110 \v \0 \0 \0 006 \0 \0 \0 p 002 \0 \0 005 \0 001 \0
0000120 020 \0 \0 \0 \0 \0 004 \0 017 \0 \0 \0 001 \0 003 \0
0000130 021 \0 \0 \0 004 \0 002 \0 \0 \0 \0 \0 004 \0 001 \0
0000140 \r \0 \0 \0 004 \0 \0 \0 022 \0 \0 \0 \0 \0 \0 \0
0000150 001 \0 \0 \0 002 \0 \0 \0 \0 \0 \0 \0 ? ? ? ?
0000160 \0 \0 \0 \0 ? 002 \0 \0 \0 \0 \0 \0 006 < i n
0000170 i t > \0 \v H e l l o W o r l d
0000180 \0 001 L \0 \f L H e l l o W o r l d
0000190 ; \0 002 L L \0 025 L j a v a / i o /
00001a0 P r i n t S t r e a m ; \0 022 L j
00001b0 a v a / l a n g / O b j e c t ;
00001c0 \0 022 L j a v a / l a n g / S t r
00001d0 i n g ; \0 031 L j a v a / l a n g
00001e0 / S t r i n g B u i l d e r ; \0
00001f0 022 L j a v a / l a n g / S y s t
0000200 e m ; \0 001 V \0 002 V L \0 023 [ L j a
0000210 v a / l a n g / S t r i n g ; \0
0000220 006 a p p e n d \0 004 a r g s \0 004 m
0000230 a i n \0 003 o u t \0 \a p r i n t l
0000240 n \0 \b t o S t r i n g \0 016 ? ? 231
0000250 ? 230 ? ? ? 200 ? ? ? ? 211 213 ? 206 231 ?
0000260 232 204 s m a l i ? ? 236 ? ? 213 \0 \0 \0
0000270 001 \0 \0 \0 \a \0 \0 \0 001 \0 \0 \0 003 \0 \0 \0
0000280 \0 \0 \0 \0 \0 \0 \0 \0 \0 001 017 \a \0 \0 \0 \0
0000290 \v \0 001 \0 002 \0 \0 \0 210 002 \0 \0 ( \0 \0 \0
00002a0 b \0 \0 \0 \0 \0 \0 \0 \0 \0 022 2 023 003 ? ?
00002b0 030 004 \0 \0 001 \0 \0 \0 \0 \0 034 005 003 \0 001 &
00002c0 " \a 004 \0 p 020 002 \0 \a \0 032 \b 023 \0 n
00002d0 003 \0 207 \0 \f \a n 020 004 \0 \a \0 \f \t n
00002e0 001 \0 220 \0 032 001 001 \0 n 001 \0 020 \0 016 \0
00002f0 \0 \0 001 \0 \0 \t 220 005 016 \0 \0 \0 \0 \0 \0 \0
0000300 001 \0 \0 \0 \0 \0 \0 \0 001 \0 \0 \0 024 \0 \0 \0
0000310 p \0 \0 \0 002 \0 \0 \0 \b \0 \0 \0 ? \0 \0 \0
0000320 003 \0 \0 \0 005 \0 \0 \0 ? \0 \0 \0 004 \0 \0 \0
0000330 001 \0 \0 \0 034 001 \0 \0 005 \0 \0 \0 005 \0 \0 \0
0000340 $ 001 \0 \0 006 \0 \0 \0 001 \0 \0 \0 L 001 \0 \0
0000350 002 \0 \0 024 \0 \0 \0 l 001 \0 \0 001 020 \0 \0
0000360 002 \0 \0 \0 p 002 \0 \0 003 020 \0 \0 002 \0 \0 \0
0000370 200 002 \0 \0 003 \0 \0 001 \0 \0 \0 210 002 \0 \0
0000380 001 \0 \0 001 \0 \0 \0 220 002 \0 \0 \0 \0 \0
0000390 001 \0 \0 \0 ? 002 \0 \0 \0 020 \0 \0 001 \0 \0 \0
00003a0 ? 002 \0 \0
00003a4
还可以用 -C 显示 16 进制 和 ASCII码:hexdump -C classes.dex
00000000 64 65 78 0a 30 33 35 00 12 8f b1 77 7a e9 19 91 |dex.035....wz...|
00000010 f2 0c ff ce a0 ce aa cd 8f 9d 80 7a ac 18 49 bf |...........z..I.|
00000020 a4 03 00 00 70 00 00 00 78 56 34 12 00 00 00 00 |....p...xV4.....|
00000030 00 00 00 00 f8 02 00 00 14 00 00 00 70 00 00 00 |............p...|
00000040 08 00 00 00 c0 00 00 00 05 00 00 00 e0 00 00 00 |................|
00000050 01 00 00 00 1c 01 00 00 05 00 00 00 24 01 00 00 |............$...|
00000060 01 00 00 00 4c 01 00 00 38 02 00 00 6c 01 00 00 |....L...8...l...|
00000070 6c 01 00 00 74 01 00 00 81 01 00 00 84 01 00 00 |l...t...........|
00000080 92 01 00 00 96 01 00 00 ad 01 00 00 c1 01 00 00 |................|
00000090 d5 01 00 00 f0 01 00 00 04 02 00 00 07 02 00 00 |................|
000000a0 0b 02 00 00 20 02 00 00 28 02 00 00 2e 02 00 00 |.... ...(.......|
000000b0 34 02 00 00 39 02 00 00 42 02 00 00 4c 02 00 00 |4...9...B...L...|
000000c0 03 00 00 00 05 00 00 00 06 00 00 00 07 00 00 00 |................|
000000d0 08 00 00 00 09 00 00 00 0a 00 00 00 0c 00 00 00 |................|
000000e0 02 00 00 00 03 00 00 00 00 00 00 00 04 00 00 00 |................|
000000f0 04 00 00 00 78 02 00 00 0a 00 00 00 06 00 00 00 |....x...........|
00000100 00 00 00 00 0b 00 00 00 06 00 00 00 78 02 00 00 |............x...|
00000110 0b 00 00 00 06 00 00 00 70 02 00 00 05 00 01 00 |........p.......|
00000120 10 00 00 00 00 00 04 00 0f 00 00 00 01 00 03 00 |................|
00000130 11 00 00 00 04 00 02 00 00 00 00 00 04 00 01 00 |................|
00000140 0d 00 00 00 04 00 00 00 12 00 00 00 00 00 00 00 |................|
00000150 01 00 00 00 02 00 00 00 00 00 00 00 ff ff ff ff |................|
00000160 00 00 00 00 f0 02 00 00 00 00 00 00 06 3c 69 6e |.............<in|
00000170 69 74 3e 00 0b 48 65 6c 6c 6f 20 57 6f 72 6c 64 |it>..Hello World|
00000180 00 01 4c 00 0c 4c 48 65 6c 6c 6f 57 6f 72 6c 64 |..L..LHelloWorld|
00000190 3b 00 02 4c 4c 00 15 4c 6a 61 76 61 2f 69 6f 2f |;..LL..Ljava/io/|
000001a0 50 72 69 6e 74 53 74 72 65 61 6d 3b 00 12 4c 6a |PrintStream;..Lj|
000001b0 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74 3b |ava/lang/Object;|
000001c0 00 12 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 |..Ljava/lang/Str|
000001d0 69 6e 67 3b 00 19 4c 6a 61 76 61 2f 6c 61 6e 67 |ing;..Ljava/lang|
000001e0 2f 53 74 72 69 6e 67 42 75 69 6c 64 65 72 3b 00 |/StringBuilder;.|
000001f0 12 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 79 73 74 |.Ljava/lang/Syst|
00000200 65 6d 3b 00 01 56 00 02 56 4c 00 13 5b 4c 6a 61 |em;..V..VL..[Lja|
00000210 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 00 |va/lang/String;.|
00000220 06 61 70 70 65 6e 64 00 04 61 72 67 73 00 04 6d |.append..args..m|
00000230 61 69 6e 00 03 6f 75 74 00 07 70 72 69 6e 74 6c |ain..out..printl|
00000240 6e 00 08 74 6f 53 74 72 69 6e 67 00 0e e8 bf 99 |n..toString.....|
00000250 e6 98 af e4 b8 80 e4 b8 aa e6 89 8b e5 86 99 e7 |................|
00000260 9a 84 73 6d 61 6c 69 e5 ae 9e e4 be 8b 00 00 00 |..smali.........|
00000270 01 00 00 00 07 00 00 00 01 00 00 00 03 00 00 00 |................|
00000280 00 00 00 00 00 00 00 00 00 01 0f 07 00 00 00 00 |................|
00000290 0b 00 01 00 02 00 00 00 88 02 00 00 28 00 00 00 |............(...|
000002a0 62 00 00 00 00 00 00 00 00 00 12 32 13 03 ff ff |b..........2....|
000002b0 18 04 00 00 01 00 00 00 00 00 1c 05 03 00 01 26 |...............&|
000002c0 22 07 04 00 70 10 02 00 07 00 1a 08 13 00 6e 20 |"...p.........n |
000002d0 03 00 87 00 0c 07 6e 10 04 00 07 00 0c 09 6e 20 |......n.......n |
000002e0 01 00 90 00 1a 01 01 00 6e 20 01 00 10 00 0e 00 |........n ......|
000002f0 00 00 01 00 00 09 90 05 0e 00 00 00 00 00 00 00 |................|
00000300 01 00 00 00 00 00 00 00 01 00 00 00 14 00 00 00 |................|
00000310 70 00 00 00 02 00 00 00 08 00 00 00 c0 00 00 00 |p...............|
00000320 03 00 00 00 05 00 00 00 e0 00 00 00 04 00 00 00 |................|
00000330 01 00 00 00 1c 01 00 00 05 00 00 00 05 00 00 00 |................|
00000340 24 01 00 00 06 00 00 00 01 00 00 00 4c 01 00 00 |$...........L...|
00000350 02 20 00 00 14 00 00 00 6c 01 00 00 01 10 00 00 |. ......l.......|
00000360 02 00 00 00 70 02 00 00 03 10 00 00 02 00 00 00 |....p...........|
00000370 80 02 00 00 03 20 00 00 01 00 00 00 88 02 00 00 |..... ..........|
00000380 01 20 00 00 01 00 00 00 90 02 00 00 00 20 00 00 |. ........... ..|
00000390 01 00 00 00 f0 02 00 00 00 10 00 00 01 00 00 00 |................|
000003a0 f8 02 00 00 |....|
000003a4
0x02■ DEX文件结构解析
先看下就行,不用着急,下面我们一步一步来,首先点击你的010Editor的这里:
对,就是箭头指的那里,点击之后,你会发现上面的有一片区域成了选中的颜色,这部分里面存储的就是Header中的数据了,下面我们根据Header的数据图以此来进行分析。
首先,我们看到DexHeader中每个数据前面有个u1或者u4,这个是什么意思呢?它们其实就是代表1个或者4个字节的无符号数。下面我们依次根据Header中的数据段进行解释。
1. 从第一个看起,magic[8];它代表dex中的文件标识,一般被称为魔数。是用来识别dex这种文件的,它可以判断当前的dex文件是否有效,可以看到它用了8个1字节的无符号数来表示,我们在010Editor中可以看到也就是“64 65 78 0A 30 33 35 00 ”这8个字节,这些字节都是用16进制表示的,用16进制表示的话,两个数代表一个字节(一个字节等于8位,一个16进制的数能表示4位)。这8个字节用ASCII码表转化一下可以转化为:dex.035(点击这里可以进行十六进制转ASCII,你可以试试:其中,'.' 不是转化来的)。目前,dex的魔数固定为dex.035。
2. 第二个是,checksum; 它是dex文件的校验和,通过它可以判断dex文件是否被损坏或者被篡改。它占用4个字节,也就是“5D 9D F9 59”。这里提醒一下,在010Editor中,其实可以分别识别我们在DexHeader中看到的这些字段的,你可以点一下这里:
你可以看到这个header列表展开了,其实我们分析下来就和它这个结构是一样的,你可以先看下,我们现在分析到了checksum中了,你可以看到后面对应的值是“59 F9 9D 5D”。咦?这好像和上面的字节不是一一对应的啊。对的,你可以发现它是反着写的。这是由于dex文件中采用的是小字节序的编码方式,也就是低位上存储的就是低字节内容,所以它们应该要反一下。
3. 第三个到了 signature[kSHA1DigestLen] 了,signature字段用于检验dex文件,其实就是把整个dex文件用SHA-1签名得到的一个值。这里占用20个字节,你可以自己点010Editor看一看。
4. 第四个 fileSize ;表示整个文件的大小,占用4个字节。
5. 第五个 headerSize ;表示 DexHeader 头结构的大小,占用4个字节。
可以看到它一共占用了112个字节,112对应的16进制数为70h。
6. 第6个是 endianTag ;代表 字节序标记,用于指定dex运行环境的cpu,预设值为0x12345678,对应在101Editor中为“78 56 34 12”(小字节序)。
7. 接下来两个分别是 linkSize 和 u4 linkOff ;这两个字段,它们分别指定了链接段的大小和文件偏移,通常情况下它们都为0。linkSize 为 0 的话表示静态链接。
8. 再下来就是 mapOff 字段了,它指定了DexMapList的文件偏移,这里我们先不过多介绍它,你可以看一下它的值为“14 04 00 00”,它其实对应的16进制数就是414h(别忘了小字节序),我们可以在414h的位置看一下它在哪里:
其实就是dex文件最后一部分内容。关于这部分内容里面是什么,我们先不说,继续往下看。
9. stringIdsSize 和 stringIdsOff 字段:这两个字段指定了dex文件中所有用到的字符串的个数和位置偏移,我们先看stringIdsSize,它的值为:“1C 00 00 00”,16进制的1C也就是十进制的28,也就是说我们这个dex文件中一共有28个字符串,然后stringIdsOff为:“70 00 00 00”,代表字符串的偏移位置为70h,这下我们找到70h的地方:
这下我们就要先介绍一下DexStringId这个结构了,图中从70h开始,所有被选中的都是DexStringId这种数据结构的内容,DexStringId代表的是字符串的位置偏移,每个DexStringId占用4个字节,也就是说它里面存的还不是真正的字符串,它们只是存储了真正字符串的偏移位置。
下面我们先分析几个看看,
① 取第一个“B2 02 00 00”,它代表的位置偏移是2B2h,我们先找到这个位置:
可以发现我一共选中了10个字节,这10个字节就表示了一个字符串。下面我们看一下dex文件中的字符串是如何表示的。dex中的字符串采用了一种叫做MUTF-8这样的编码,它是经过传统的UTF-8编码修改的。在MTUF-8中,它的头部存放的是由uleb128编码的字符的个数。(至于uleb128编码是什么编码,这里我不详细展开说,有兴趣的可以搜索看看。)
也就是说在“08 3C 63 6C 69 6E 69 74 3E 00”这些字节中,第一个08指定的是后面需要用到的编码的个数,也就是8个,即“ 3C 63 6C 69 6E 69 74 3E”这8个,但是我们为什么一共选中了10个字节呢,因为最后一个空字符“0”表示的是字符串的结尾,字符个数没有把它算进去。下面我们来看看“ 3C 63 6C 69 6E 69 74 3E”这8个字符代表了什么字符串:
依旧可以点这里查询ASCII ( http://www.ab126.com/goju/1711.html )。(要说明的一点是,这里凑巧这几个uleb128编码的字符都用了1个字节,所以我们可以这样进行查询,uleb128编码标准用的是1~5个字节, 这里只是恰好都是一个字节)。也就是说上面的70h开始的第一个DexStringId指向的其实是字符串“<clinit>”(但是貌似我们的代码中没有用到这个字符串啊,先不用管,我们接着分析)。再看到这里:
② 刚刚我们分析到“B2 02 00 00”所指向的真实字符串了,下面我们接着再分析一个,我们直接分析第三个,不分析第二个了。第三个为“C4 02 00 00”,对应的位置也就是2C4h,我们找到它:
看这里,这就是2C4h的位置了。我们首先看第一个字符,它的值为0Bh,也就是十进制的11,也就是说接下来的11个字符代表了它的字符串,我们依旧是查看接下来11个字符代表的是什么,经过查询整理:
依旧可以点这里查询ASCII ( http://www.ab126.com/goju/1711.html )。上面就是“HelloDalvik”这个字符串,可以看看我们的代码,我们确实用了一个这样的字符串,bingo。下面剩下的字符串就不分析了。经过整理,可以整理出我们一共用到的28个字符串为:
ok,字符串这里告一段落,下面我们继续看DexHeader的下面的字段。头好晕~乎乎
噢,读了,还不能结束呢,你现在可以看一下最开始发的那张dex结构图了:
看到了吧,我们这半天分析的stringIdsSize 和 stringIdsOff字段指向的位置就是上面那个箭头指向的位置,它们里面存储的是真实字符串的位置偏移,它们都存储在data区域。(先透露一下,后面我们要分析的几个也和stringIdsSize 与stringIdsOff字段类似,它们里面存储的基本都是位置偏移,并不是真正的数据,真正的数据都在data区域)
好,我们继续。
10. 继续看DexHeader图,我们现在该typeIdsSize和typeIdsOff了。它们代表什么呢?它们代表的是类的类型的数量和位置偏移,也是都占4个字节,下面我们看它们的值
可以看到,typeIdsSize的值为9h,也就是我们dex文件中用到的类的类型一共有9个,位置偏移在E0h位置,下面我们找到这个位置
看到了吧,我选中的位置就是了。这里我们又得介绍一种数据结构了,因为这里的数据也是一种数据结构的数据组成的。那就是DexTypeId,也就是说选中的内容都是DexTypeId这种数据,这种数据结构中只有一个变量,如下所示:
struct DexTypeId{u4 descriptorIdx; /*指向DexStringId列表的索引*/
}
看到了吧,这就是DexTypeId数据结构,它里面只有一个数据descriptorIdx,它的值的内容是DexStringId列表的索引。还记得DexStringId是什么吗?在上面我们分析字符串时,字符串的偏移位置就是由DexStringId这种数据结构描述的,也就是说descriptorIdx指向的是所有的DexStringId组成的列表的索引。上面我们整理出了所有的字符串,你可以翻上去看看图。然后我们看这里一共是9个类的类型代表的都是什么。先看第一个“05 00 00 00”,也就是05h,即十进位的5。然后我们在上面所有整理出的字符串看看5索引的是什么?翻上去可以看到是“I”。接下来我们依次整理这些类的类型,也可以得到类的类型的列表
看到了吧,这就是我们dex文件中所有用到的类的类型。比如“I”代表的就是int,LHelloWorld代表的就是HelloWorld,Ljava/io/PrintStream代表的就是java.io.PrintStream。后面的几个先就不说了。我们接着往下分析。
11. 这下到了protoIdsSize和protoIdsOff了,它们代表的是dex文件中方法原型的个数和位置偏移。我们先看它们的值
如上图就是它们的值了,protoIdsSize的值为十进制的7,说明有7个方法原型,然后位置偏移为104h,我们找到这个位置
看到了吧,这里就是了。对,下面又有新的数据结构了。这下一个数据结构不能满足这块的内容了,我们先看第一个数据结构,DexProtoId
struct DexProtoId{u4 shortyIdx; /*指向DexStringId列表的索引*/u4 returnTypeIdx; /*指向DexTypeId列表的索引*/u4 parametersOff; /*指向DexTypeList的位置偏移*/
}
可以看到,这个数据结构由三个变量组成。第一个shortyIdx它指向的是我们上面分析的DexStringId列表的索引,代表的是方法声明字符串。第二个returnTypeIdx它指向的是 我们上边分析的DexTypeId列表的索引,代表的是方法返回类型字符串。第三个parametersOff指向的是DexTypeList的位置索引,这又是一个新的数据结构了,先说一下这里面 存储的是方法的参数列表。可以看到这三个参数,有方法声明字符串,有返回类型,有方法的参数列表,这基本上就确定了我们一个方法的大体内容。
我们接着看看DexTypeList这个数据结构,看看参数列表是如何存储的。
struct DexTypeList{u4 size; /*DexTypeItem的个数*/DexTypeItem list[1]; /*DexTypeItem结构*/
}
看到了嘛,它有两个参数,其中第一个size说的是DexTypeItem的个数,那DexTypeItem又是啥咧?它又是一种数据结构。我们继续看看
struct DexTypeItem{u2 typeIdx; /*指向DexTypeId列表的索引*/
}
恩,还好,里面就一个参数。也比较简单,就是一个指向DexTypeId列表的索引,也就是代表参数列表中某一个具体的参数的位置。
分析完这几个数据结构了,下面我们具体地分析一个类吧。别走神,我们该从上图的104h开始了。
在104h这里,由于 都是DexProtoId这种数据结构的数据,一个DexProtoId一共占用12个字节。所以,我们取前12个字节进行分析。“06 00 00 00,00 00 00 00,94 02 00 00”,这就是那12个字节了。首先“06 00 00 00”代表的是shortyIdx,它的值是指向DexStringId列表的索引,我们找到DexStringId列表中第6个对应的值,也就是III,说明这个方法中声明字符串为三个int。接着,“00 00 00 00”代表的是returnTypeIdx,它的值指向的是DexTypeId列表的索引,我们找到对应的值,也就是I,说明这个方法的返回值是int类型的。最后,我们看“94 02 00 00”,它代表的是DexTypeList的位置偏移,它的值为294h,我们找到这个位置
这里是DexTypeList结构,首先看前4个字节,代表的是DexTypeItem的个数,“02 00 00 00 ”也就是2,说明接下来有2个DexTypeItem的数据,每个DexTypeItem占用2个字节,也就是两个都是“00 00”,它们的值是DexTypeId列表的索引,我们去找一下,发现0对应的是I,也就是说它的两个参数都是int型的。因此这个方法的声明我们也就确定了。也就是int(int,int),可以看看我们的源代码,getNumber方法确实是这样的。好,第一个方法就这样分析完了,下面我们依旧是将这些方法的声明整理成列表,后面可能有数据会指向它们的索引。
终于又完了一个。我们准备继续下面的。累了就先去听听歌吧,歇一歇再看 -_-
12. fieldIdsSize和fieldIdsOff字段。这两个字段指向的是dex文件中字段名的信息。我们看到这里
可以看到,fieldIdsSize为3h,说明共有3个字段。fieldIdsOff为158h,说明偏移为158h,我们继续看到158h这里
咳咳,又该新的数据结构了,再忍一忍,接下来的数据结构是DexFieldId,我们看下
struct DexFieldId{u2 classIdx; /*类的类型,指向DexTypeId列表的索引*/u2 typeIdx; /*字段类型,指向DexTypeId列表的索引*/u4 nameIdx; /*字段名,指向DexStringId列表的索引*/
}
可以看到,这三个数据都是指向的索引值,具体的就不说了,看后面的备注就是。我们依旧是分析一下第一个字段,“01 00 ,00 00,13 00 00 00”,类的类型为DexTypeId列表的索引1,也就是HelloWorld,字段的类型为DexTypeId列表中的索引0,也就是int,字段名为DexStringId列表中的索引13h,即十进制的19,找一下,是a,也就是说我们这个字段就确认了,即int HelloWorld.a。这不就是我们在HelloWorld.java文件里定义的变量a嘛。然后我们依次把我们所有的3个字段都列出来:
〇int HelloWorld.a , ①java.lang.String HelloWorld.b ,②java.io.PrintStream java.lang.System.out
ok,先告一段落。继续分析下一个
13. methodIdsSize和methodIdsOff字段。这俩字段指明了方法所在的类、方法的声明以及方法名。我们看看
先是,methodIdsSize,为Ah,即十进制的10,说明共有10个方法。methodIdsOff,为170h,说明它们的位置偏移在170h。我们看到这里
对对对,又是新的数据结构,不过这个和上个一样简单,请看DexMethodId
struct DexMethodId{u2 classIdx; /*类的类型,指向DexTypeId列表的索引*/u2 protoIdx; /*声明类型,指向DexProtoId列表的索引*/u4 nameIdx; /*方法名,指向DexStringId列表的索引*/
}
对吧,这个也简单,三个数据也都是指向对应的结构的索引值。我们直接分析一下第一个数据,“01 00, 04 00, 00 00 00 00”,首先,classIdx,为1,对应DexTypeId列表的索引1,也就是HelloWorld;其次,protoIdx,为4,对应DexProtoId列表中的索引4,也就是void();最后,nameIdx,为0,对应DexStringId列表中的索引0,也就是<clinit>。因此,第一个数据就出来了,即void HelloWorld.<clinit>() 。后面的不进行分析了,我们依旧是把其余的9个方法列出来
好了,这个就算分析完了。下面真正开始我们的重头戏了。先缓一缓再继续吧。
14. classDefsSize和classDefsOff字段。这两个字段指明的是dex文件中类的定义的相关信息。我们先找到它们的位置。
classDefsSize字段,为1,也就是只有一个类定义,classDefsOff,为1C0h,我们找到它的偏移位置。
这里就是了,到了这里,你现在应该也知道又有新的数据结构了。对的,接下来的数据结构是DexClassDef,请看
struct DexClassDef{u4 classIdx; /*类的类型,指向DexTypeId列表的索引*/u4 accessFlags; /*访问标志*/u4 superclassIdx; /*父类类型,指向DexTypeId列表的索引*/u4 interfacesOff; /*接口,指向DexTypeList的偏移*/u4 sourceFileIdx; /*源文件名,指向DexStringId列表的索引*/u4 annotationsOff; /*注解,指向DexAnnotationsDirectoryItem结构*/u4 classDataOff; /*指向DexClassData结构的偏移*/u4 staticValuesOff; /*指向DexEncodedArray结构的偏移*/
}
不多说了,我们直接根据结构开始分析吧,反正就只有一个类定义。classIdx为1,对应DexTypeId列表的索引1,找到是HelloWorld,确实是我们源程序中的类的类型。accessFlags为1,它是类的访问标志,对应的值是一个以ACC_开头的枚举值,1对应的是 ACC_PUBLIC,你可以在010Editor中看一下,说明我们的类是public的。superclassIdx的值为3,找到DexTypeId列表中的索引3,对应的是java.lang.object,说明我们的类的父类类型是Object的。interfaceOff指向的是DexTypeList结构,我们这里是0说明没有接口。如果有接口的话直接对应到DexTypeList,就和之前我们分析的一样了,这里不多解释,有兴趣的可以写一个有接口的类验证下。再下来sourceFileIdx指向的是DexStringId列表的索引,代表源文件名,我们这里位4,找一下对应到了字符串"HelloWorld.java",说明我们类程序的源文件名为HelloWorld.java。annotationsOff字段指向注解目录接口,根据类型不同会有注解类、注解方法、注解字段与注解参数,我们这里的值为0,说明没有注解,这里也不过多解释,有兴趣可以自己试试。
接下来是classDataOff了,它指向的是DexClassData结构的位置偏移,DexClassData中存储的是类的数据部分,我们开始详细分析一下它,首先,还是先找到偏移位置3F8h
接着,我们看看DexClassData数据结构
struct DexClassData{DexClassDataHeader header; /*指定字段与方法的个数*/DexField* staticFields; /*静态字段,DexField结构*/DexField* instanceFields; /*实例字段,DexField结构*/DexMethod* directMethods; /*直接方法,DexMethod结构*/DexMethod* virtualMethods; /*虚方法,DexMethod结构*/
}
可以看到,在DexClassData结构中又引入了三种结构,我们一起写出来看一下吧
struct DexClassDataHeader{u4 staticFieldsSize; /*静态字段个数*/u4 instanceFieldsSize; /*实例字段个数*/u4 directMethodsSize; /*直接方法个数*/u4 virtualMethodsSize; /*虚方法个数*/
}struct DexField{u4 fieldIdx; /*指向DexFieldId的索引*/u4 accessFlags; /*访问标志*/
}struct DexMethod{u4 methodIdx; /*指向DexMethodId的索引*/u4 accessFlags; /*访问标志*/u4 codeOff; /*指向DexCode结构的偏移*/
}/*指向DexFieldId的索引*/u4 accessFlags; /*访问标志*/
}struct DexMethod{u4 methodIdx; /*指向DexMethodId的索引*/u4 accessFlags; /*访问标志*/u4 codeOff; /*指向DexCode结构的偏移*/
}
代码中的注释写的也都很清楚了,我们就不多说了。但是请注意,在这些结构中的u4不是指的占用4个字节,而是指它们是uleb128类型(占用1~5个字节)的数据。关于uleb128还是不多说,想了解的可以自己查查看。
好,接下来开始分析,对于DexClassData,第一个为DexClassDataHeader,我们找到相应的位置,第一个staticFieldsSize其实只占用了一个字节,即01h就是它的值,也就是说共有一个静态字段,接下来instanceFieldsSize,directMethodsSize,virtualMethodsSize也都是只占用了一个字节,即实例字段的个数为1,直接方法的个数为3,虚方法的个数为1。(这里只是凑巧它们几个都占用一个字节,并不一定是只占用一个字节,这关于到uleb128数据类型,具体可以自己了解下)。
然后接下来就是staticFields了,它对应的数据结构为DexField,可以看到,第一个fieldIdx,是指向DexFieldId的索引,值为1,找到对应的索引值为java.lang.String HelloWorld.b。第二个accessFlags,值为8,对应的ACC_开头的数据为ACC_STATIC(可以在010Editor中对应查看一下),说明我们这个静态字段为:static java.lang.String HelloWorld.b。可以对应我们的源代码看一下,我们确实定义了一个static的b变量。
接着看instanceFields,它和staticFields对应的数据结构是一样的,我们直接分析,第一个fieldIdx,值为0,对应的DexField的索引值为int HelloWorld.a。第二个accessFlags,值为0,对应的ACC_开头的数据为空,就是什么也没有。说明我们这个实例字段为:int HelloWorld.a。可以对应我们的源码 看看,我们确实定义了一个a实例变量。
再接着,根据directMethodsSize,有3个直接方法,我们先看第一个,它对应的数据结构是DexMethod,首先methodIdx指向的是DexMethodId的索引,值为0,找到对应的索引值为void HelloWorld.<clinit>()。然后accessFlages为......为......为....我的个天!我以为就这样能蒙混过关了,没想到还真碰到一个uleb128数据不是占用一个字节的,这个accessFlags对应的值占用了三个字节,“88 80 04”,为什么?因为是按照uleb128格式的数据读出来的(还是自己去查查吧,这个坑先不填了,其实这种数据也不麻烦,就是前面字节上的最高位指定了是否需要下一个字节上的内容)。“88 80 04”对应的ACC_开头的数据为 ACC_STATIC ACC_CONSTRUCTOR,表明这个方法是静态的,并且是构造方法。最后,看看codeOff,它对应了DexCode结构的偏移,DexCode中存放了方法的指令集等信息,也就是真正的代码了。我们暂且不分析DexCode,就先看看它的偏移位置为“E0 03”,这个等于多少呢?uleb128转化为16进制数结果为:1E0h。也就是DexCode存放在偏移位置1E0h的位置上。
具体的DexCode我们就先不分析了,因为它里面存放的一些指令局需要根据相关资料一一查找,有兴趣的自己可以找资料看看。剩下的两个直接方法我们也不分析了。
接下来,我们看根据virtualMethodsSize,有1个虚方法,我们直接看。首先methodIdx的值为2,对应的DexMethodId的索引值为int HelloWorld.getNumber(int, int)。然后accessFlags为1,对应的值为ACC_PUBLIC,表明这是一个public类。codeOff为“FC 04”,对应的位置为27Ch,这里就不上图了,自己找找吧。
好了,我们整个DEX文件的结构就这样从DexHeader开始基本分析完了,好累啊,不过这样分析一遍,对DEX文件的格式会有更深刻的认识。总是看别人的真不如自己来一遍来的实在!
0x03■ 参考资料
参考资料:
《Android软件安全与逆向分析》.非虫