Dex文件格式详解
什么是dex文件
他是Android系统的可执行文件,包含应用程序的全部操作指令以及运行时数据。
由于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特有的数据类型,更详细的参考:(深入到源码解析leb128数据类型)[http://i.woblog.cn/2016/07/23/leb128-format/]
dex文件结构
首先从宏观上来说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
magic
标识一个有效的dex文件,他的固定值为:64 65 78 0a 30 33 35 00,转换为字符串为dex.035.
在电子取证中也称“文件签名”
checksum
他是整个头部的校验和。它被用来校验头部是否损坏
signature
file_size
记录包括dexHeader在内的整个dex文件大小,用来计算偏移和方便定位某区段(section),他也有诸如唯一的标识dex,因为他是dex文件中计算sha-1区段的一个组成部分
header_size
存放整个DexHeadeer结构体的长度,它也可用来计算下一个区段在文件中的起始位置,目前值为0x70
endian_tag
指定dex运行环境的CPU字节序,存放的是一个固定值,所有dex文件都一样的,值为:78 56 34 12,0x12345678,表示默认采用little-endian字节序
link_size
link_off
当多个class文件被编译到一个dex文件是,他们会用到link_size和link_off,通常为0
可以看到上面的,link_off:为00 00 00 00
map_off
他指定了dexMapList结构的文件偏移量
string_ids_size
是指string存放区段的大小,用来计算string区段起始位置-相对于dex文件加载基地址的偏移量
string_ids_off存放string区段的实际偏移量,单位字节。他可以帮助编译器和虚拟机直接跳到这个区段,而不必从前读到后,一直读取到该位置。
type,prototype,method,class,data id的大小(size)和偏移量(offset)和string的作用一样
每个字符串都对应一个DexStringId数据结构,大小为4B,同时虚拟机可以通过头文件中的string_ids_size知道当前dex文件中字符串的总数,也就是string_ids区域中DexStringId数据结构的总数,所以虚拟机可以通过简单的乘法运算即可实现对字符串资源的索引,也可以根据kDexTypeStringIdItem获取字符串
我们举个例子来根据header里面的字符串信息索引字符串,还是以上面的classes.dex文件来分析:
根据stringIdsSize找到有多少个DexStringId(也就是有多少个字符串):
0x38:0x14,说明有20个字符串
根据stringIdsOff查看DexStringId的偏移量:
0x3c:0x70,说明DexStringId的开始位置在0x70
读取4个字节:6c 01 00 00,转为地址为0x16c,这就是第一个字符串的位置
在读取4个字节:74 01 00 00,0x174
分别获取这个两个位置的字符串:
06 3c 69 6e 69 74 3e 00:值为<init>\0
,其中06表示后面有6个字符(不包括\0)
0b 48 65 6c 6c 6f 20 57 6f 72 6c 64:值为Hello World\0,0b表示有11个字符
我们发现每个字符串是使用“\0”分割的
首先他的开始位置0x70,我们根据stringIdsSize的值得知接下来有20个字符,首先我们计算DexStringId的地址截止到:0x70+0x14(20)*4=0xc0(不包括0xc0)
先获取DexStringId,然后在获取地址位置的值
DexStringId偏移 | String偏移 | 值 |
---|---|---|
0x70 | 0x16c | <init> |
74 | 174 | Hello World |
78 | 181 | L |
7c | 184 | LHelloWorld; |
80 | 192 | LL |
84 | 196 | Ljava/io/PrintStream; |
88 | 1ad | Ljava/lang/Object; |
8c | 1c1 | Ljava/lang/String; |
90 | 1d5 | Ljava/lang/StringBuilder; |
94 | 1f0 | Ljava/lang/System; |
98 | 204 | V |
9c | 207 | VL |
a0 | 20b | [Ljava/lang/String; |
a4 | 220 | append |
a8 | 228 | args |
ac | 22e | main |
b0 | 234 | out |
b4 | 239 | println |
b8 | 242 | toString |
bc | 24c |
上面的字符串并非普通的ASCII字符串,他们是由MUTF-8编码来表示的,更详细的介绍参考这篇文章
dex文件结构分析
我们采用前面的classes.dex文件作为演示对象
dalvik虚拟机解析dex文件的内容,最终将其映射成DexMapList数据结构,DexHeader中的mapOff字段指定了DexMapList结构在dex在文件中的偏移,他的申明如下:
/dalvik/libdex/DexFile.h
struct DexMapList {
u4 size; /* 个数 */
DexMapItem list[1]; /* DexMapItem的结构 */
};
其中size字段表示dex接来下有多少个DexMapItem结构
struct DexMapItem {
u2 type; /* kDexType开头的类型 */
u2 unused; /*未使用,用于字节对齐 */
u4 size; /* 类型的个数 */
u4 offset; /* 类型的文件偏移 */
};
type字段为一个枚举常量,可以通过类型名称很容易判断他的具体类型:
enum {
kDexTypeHeaderItem = 0x0,
kDexTypeStringIdItem = 0x1,
kDexTypeTypeIdItem = 0x2,
kDexTypeProtoIdItem = 0x3,
kDexTypeFieldIdItem = 0x4,
kDexTypeMethodIdItem = 0x5,
kDexTypeClassDefItem = 0x6,
kDexTypeMapList = 0x0,
kDexTypeTypeList = 0x1,
kDexTypeAnnotationSetRefList = 0x2,
kDexTypeAnnotationSetItem = 0x3,
kDexTypeClassDataItem = 0x0,
kDexTypeCodeItem = 0x1,
kDexTypeStringDataItem = 0x2,
kDexTypeDebugInfoItem = 0x3,
kDexTypeAnnotationItem = 0x4,
kDexTypeEncodedArrayItem = 0x5,
kDexTypeAnnotationsDirectoryItem = 0x6,
};
这里我们以上面的clsses.dex来分析,DexHeader结构的mapOff字段为f8 02 00 00,根据小端序,他的值为0x2f8,读取出的双字值为0e 00 00 00(0x0e),表示接下来有14个DexMapItem结构,接着在读取0x2fc值为:0x00表示这个DexMapItem类型是kDexTypeHeaderItem,在读取0x2fe值为:0x00这个字段没有使用。在读取0x300值为:01 00 00 00表示有一个,在0x304读取:00 00 00 00表示偏移为0x0
根据上面的规则我们整理除了14个Item
类型 | 个数 | 偏移 |
---|---|---|
kDexTypeHeaderItem | 0x1 | 0x0 |
kDexTypeStringIdItem | 0x14 | 0x70 |
kDexTypeTypeIdItem | 0x8 | 0xc0 |
kDexTypeProtoIdItem | 0x5 | 0xe0 |
kDexTypeFieldIdItem | 0x1 | 0x11c |
kDexTypeMethodIdItem | 0x5 | 0x124 |
kDexTypeClassDefItem | 0x1 | 0x14c |
kDexTypeStringDataItem | 0x14 | 0x16c |
kDexTypeTypeList | 0x2 | 0x270 |
kDexTypeAnnotationSetItem | 0x2 | 0x280 |
kDexTypeDebugsssInfoItem | 0x1 | 0x288 |
kDexTypeCodeItem | 0x1 | 0x290 |
kDexTypeClassDataItem | 0x1 | 0x2f0 |
kDexTypeMapList | 0x1 | 0x2f8 |
对比文件我们发下DexHeader就是kDexTypeHeaderItem描述的结构,他占用了文件前0x70个字节空间,接下来的kDexTypeStringIdItem~kDexTypeClassDefItem与DexHeader中对应字段值是一样的
kDexTypeStringIdItem
对应DexHeader中的stringIdsSize与stringIdsOff字段,表示从0x70位置起有连续0x14个DexStringId:
struct DexStringId {
u4 stringDataOff; /* file offset to string_data_item */
};
他只有一个stringDataOff字段,指向字符串数据的偏移位置,开始地址为0x70+(14*4)=0xc0,所以我们最后一个dexStringId的偏移为:0xbc,我们根据此信息整理了所以字符串:
dexStringId偏移 | 真实字符串偏移 | 字符串 | 索引 |
---|---|---|---|
0x70 | 0x16c | <init> |
0x0 |
74 | 174 | Hello World | 1 |
78 | 181 | L | 2 |
7c | 184 | LHelloWorld; | 3 |
80 | 192 | LL | 4 |
84 | 196 | Ljava/io/PrintStream; | 5 |
88 | 1ad | Ljava/lang/Object; | 6 |
8c | 1c1 | Ljava/lang/String; | 7 |
90 | 1d5 | Ljava/lang/StringBuilder; | 8 |
94 | 1f0 | Ljava/lang/System; | 9 |
98 | 204 | V | a |
9c | 207 | VL | b |
a0 | 20b | [Ljava/lang/String; | c |
a4 | 220 | append | d |
a8 | 282 | args | e |
ac | 22e | main | f |
b0 | 234 | out | 10 |
b4 | 239 | println | 11 |
b8 | 242 | toString | 12 |
bc | 24c | 这是一个手写的smali实例 | 13 |
kDexTypeTypeIdItem
他对应DexHeader中的typeIdsSize和typeIdsOff字段,指向的结构体为:
struct DexTypeId {
u4 descriptorIdx; /* 指向DexStringId列表的索引 */
};
对应的字符串代表具体的类型,我们根据上面字段可知:从0xc0起有0x8个DexTypeId结构:
类型索引 | 字符串索引 | 字符串 | DexTypeId偏移 |
---|---|---|---|
0 | 0x3 | LHelloWorld; | 0xc0 |
1 | 0x5 | Ljava/io/PrintStream; | 0xc4 |
2 | 0x6 | Ljava/lang/Object; | 0xc8 |
3 | 0x7 | Ljava/lang/String; | 0xcc |
4 | 0x8 | Ljava/lang/StringBuilder; | 0xd0 |
5 | 0x9 | Ljava/lang/System; | 0xd4 |
6 | 0xa | L | 0xd8 |
7 | 0xc | [Ljava/lang/String; | 0xdc |
kDexTypeProtoIdItem
对应DexHeader中的protoIdsSize与protoIdsOff字段,声明如果:
struct DexProtoId {
u4 shortyIdx; /* 指向DexStringId列表的索引 */
u4 returnTypeIdx; /* 指向DexTypeId列表的索引 */
u4 parametersOff; /* 指向DexTypeList的偏移 */
};
他是一个方法的声明结构体,shortyIdx为方法声明字符串,returnTypeIdx为方法返回类型字符串,parametersOff指向一个DexTypeList的结构体存放了方法的参数列表
struct {
u4 size; /* 接下来DexTypeItem的个数 */
DexTypeItem list[1]; /* DexTypeItem结构 */
};
DexTypeItem声明:
struct DexTypeItem {
u2 typeIdx; /* 指向DexTypeId列表的索引 */
};
根据上面的信息我们得知从0xe0开始有0x5个DexProtoId对象
索引 | 方法声明 | 返回类型 | 参数列表 | paramOff偏移 |
---|---|---|---|---|
0 | L | Ljava/lang/String; | 无参数 | 0 |
1 | LL | Ljava/lang/StringBuilder; | Ljava/lang/String; | 0x278 |
2 | V | L | 无参数 | |
3 | VL | L | Ljava/lang/String; | 0x278 |
4 | VL | L | [Ljava/lang/String; | 0x270 |
kDexTypeFieldIdItem
对应DexHeader中的fieldIdsSize和fieldIdsOff字段,指向DexFieldId结构体
struct DexFieldId {
u2 classIdx; /* 类的类型,指向DexTypeId列表索引 */
u2 typeIdx; /* 字段类型,指向DexTypeId列表索引 */
u4 nameIdx; /* 字段名,指向DexStringId列表 */
};
可以看见这个结构的数据全部是索引信息,指明了字段所在的类,字段类型,字段名,通过上面的信息我们发现从0x11c有一个kDexTypeFieldIdItem
类类型 | 字段类型 | 字段名 |
---|---|---|
Ljava/lang/System; | Ljava/io/PrintStream; | out |
kDexTypeMethodIdItem
它对应DexHeader中的methodIdsSize与methodIdsOff字段,指向的结构体DexMethodId
struct DexMethodId {
u2 classIdx; /* 类的类型,指向DexTypeId列表的索引 */
u2 protoIdx; /* 声明类型,指向DexProtoId列表索引 */
u4 nameIdx; /* 方法名,指向DexStringId列表的索引 */
};
数据也是索引,指明了方法所在的类,方法声明和方法名。从0x124有0x5个kDexTypeMethodIdItem
类类型 | 方法声明 | 方法名 |
---|---|---|
LHelloWorld; | VL | main |
Ljava/io/PrintStream; | VL | println |
Ljava/lang/StringBuilder; | V | <init> |
Ljava/lang/StringBuilder; | LL | append |
Ljava/lang/StringBuilder; | L | toString |
kDexTypeClassDefItem
对应DexHeader中的classDefsSize和classDefsOff字段,指向结构体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是一个索引值,表示类的类型
accessFlags是类的访问标志,他是以ACC_开头的枚举值
superclassIdx是父类的类型
interfacesOff如果类含有接口声明或实现,他就会指向一个DexTypeList结构,否则为0
sourceFileIdx是类所在源文件名称
annotationsOff字段指向注解目录结构,根据类型不同有注解类,注解方法,注解字段,注解参数。如果没有注解,值为0
classDataOff指向DexClassData结构,是类的数据部分
staticValuesOff记录了类中的静态数据
DexClassData
声明在DexClass.h中
struct DexClassData {
DexClassDataHeader header; //指向DexClassDataHeader,字段和方法个数
DexField* staticFields; //静态字段
DexField* instanceFields; //实例字段
DexMethod* directMethods; //直接方法
DexMethod* virtualMethods;//虚方法
};
DexClassDataHeader
记录了当前类中的字段和方法的数目,声明如下:
struct DexClassDataHeader {
u4 staticFieldsSize; //静态字段个数
u4 instanceFieldsSize;//实例字段个数
u4 directMethodsSize;//直接方法个数
u4 virtualMethodsSize;//虚方法个数
};
DexField
描述了字段类型与访问标志,声明如下:
struct DexField {
u4 fieldIdx; /* 指向DexFieldId列表的索引 */
u4 accessFlags; //访问标志
};
DexMethod
描述了方法的原型,名称,访问标志和代码数据块,声明如下:
struct DexMethod {
u4 methodIdx; /* 指向DexMethodId列表的索引 */
u4 accessFlags;
u4 codeOff; /* 指向DexCode结构的偏移 */
};
codeOff字段指向一个DexCode结构体,声明如下:
/dalvik/libdex/DexFile.h
struct DexCode {
u2 registersSize;//使用寄存器个数
u2 insSize;//参数个数
u2 outsSize;//调用其他方法时使用的寄存器个数
u2 triesSize;//try/catch个数
u4 debugInfoOff;//指向调试信息的偏移
u4 insnsSize;//指令集个数,以2字节为单位
u2 insns[1];//指令集
/* followed by optional u2 padding */
/* followed by try_item[triesSize] */
/* followed by uleb handlersSize */
/* followed by catch_handler_item[handlersSize] */
};
registersSize指令了方法使用的寄存器个数,对应smali中的.locals 2
insSize指定了方法的参数个数,对应.param p1, "noteId" # Ljava/lang/String;
如果一个方法使用5个寄存器,其中2有两个参数寄存器,而该方法调用另一个方法使用了20个寄存器,那么虚拟机在分配该方法寄存器是会在分配20个寄存器
triesSize指定了方法中Try/Catch格式
debugInfoOff指向调试信息偏移,如果有,解析函数为dexDecodoDebugInfo在DexDebugInfo.cpp文件
insnsSize接下来指令个数
insns真正的代码部分
根据上面的信息,我们发现从0x14c有0x1个kDexTypeClassDefItem:
第一个字段值为0x0,表示对应DexType中的索引为0,值为LHelloWorld;,表示类名为HelloWorld
第二个字段值为0x1表示访问标识符为ACC_PUBLIC
第三个字段值0x2表示父类,表示指向DexType的索引,值为Ljava/lang/Object;,表示父类�为java/lang/Object;
第四个字段值0x0,表示没有接口
第五个字段值
第六个字段值0x0表示没有注解
第七个字段值0x2f0表示DexClassData的偏移
第八个字段值0x0表示没有静态值
我们继续分析DexClassData
DexClassDataHeader为4个uleb128数据类型,从0x2f0开始值为:0,0,1,0,表示静态字段为0个,实例字段为0个,1个直接方法,0个虚方法
由于没有静态字段,实例字段,虚方法,所以我们直接分析DexMethod
从0x2f4开始为一个直接方法相关数据,第一个字段为0,指向的DexMethod列表第0个,也就是main方法。第二个值09,表示public+static,具体的看下面表:
现在来说下accessFlags在字节码中的计算方式:
access_flags的计算公式为:access_flags = flagA | flagB | flagB ...
标志名称 | 标志值 | 含义 |
---|---|---|
ACC_PUBLIC | 0x0001 | public |
ACC_PRIVATE | 0x0002 | private |
ACC_PROTECTED | 0x0004 | protected |
ACC_STATIC | 0x0008 | static |
ACC_FINAL | 0x0010 | final |
ACC_VOLATILE | 0x0040 | volatile |
ACC_TRANSIENT | 0x0080 | transient |
ACC_SYNTHETIC | 0x1000 | 是否是编译器自动生成的 |
ACC_ENUM | 0x4000 | enum |
第三个字段为90 05,值为0x290,我们从0x290开始分析DexCode,值为:
0b 00=11
01 00=1
02 00=2
00 00=0
表示使用了11个寄存器,1个参数寄存器,调用其他方法使用了2个寄存器,没有Try/Catch
88 02 00 00=0x288,debugInfoOff
28 00 00 00=0x28,insnsSize,指令个数,以2字节为单位
我们先读取一个两个字节6200,查看dalvik bytecode发现为sget-object,指令格式为21c,查询instruction formats可以看到指令格式为:
AA|op BBBB
可以看出他需要两个16位,21C对应有以下几种格式:
op vAA, type@BBBB | check-cast |
---|---|
op vAA, field@BBBB | const-class |
op vAA, string@BBBB | const-string |
由于我们的指令是sget,所有这条指令格式为op vAA, field@BBBB
在读取两个字节0000,表示在字段索引0,所以这条指令为
sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
继续从0x2a4分析:
00 00,查看bytecode为10x,在查询指令格式为:
ØØ|op 10x op,可以看出只需要两个字节,所以这条指令为:nop
00 00:nop
00 00:nop
12 32:op=12,A=2,B=3查询字节码代码格式,const/4 vA, #+B,格式为11n,最终翻译为:const/4 v2, 0x3
13 03:op=13,const/16 vAA, #+BBBB,格式21s,格式为AA|op BBBB,所以需要两个字节,在读取两个字节,ff ff,最终指令格式为:const/16 v3, -0x1
剩下的指令可以按照上面的步骤翻译完
18 04:
const-wide vAA, #+BBBBBBBBBBBBBBBB
AA|op
BBBBlo BBBB BBBB BBBBhi
0000 0100 0000 0000
每两位调换位置:
0000 0010 0000 0000
从低位到高位排列
0000 0000 0010 0000
最后这条指令为:
const-wide v4, 0x100000
如果我的文章对来带来的帮助或者有不明白的地方,可加QQ群:129961195,大家一起交流,下一篇文章我们讲解odex文件格式