1 .DEX文件中使用的数据类型
u1,u2,u4,u8表示占某固定字节的无符号数
sleb128表示有符号的LEB128类型数据,uleb128表示无符号的LEB128,uleb128p1表示无符号的LEB128+1 ,
关于LEB128:
LEB128是一种DEX文件中特有的用来存储最大32位数的数据类型,他的特点是字节数可以1-5可变。每个字节的第一位用来表示是否用到下个字节,剩下的7位为有效位,所以第5个字节的收位一定不能为1。有符号LEB128(SLEB128)的符号由最后字节的有效位的最高位决定。也就是最后字节的第二位(0为正 1为负)。
LEB128的编码函数:
将一个整型值的二进制值通过在头部添加零,将其扩展成7位的整数倍,然后每7位一组进行分组;从最低有效位到最高有效位方向,在每组的头部添加一位构成一个字节,最高有效位所在的组的头部一bit添加的是0;然后将这些组顺序进行反转,得到这一整数的LEB128编码。
这里举例:0x98765=10011000011101100101 把它补成7的倍数010011000011101100101然后按7位分组0100110 0001110 1100101然后在每组前加一位1,第一组加0得出 00100110 10001110 11100101 也就是0x268ee5 ,
DEX_INLINE u1* writeUnsignedLeb128(u1* ptr, u4 data){
while (true) { //循环
u1 out = data & 0x7f; ; 跟7F进行与运算 得出最后7位
if (out != data) {
*ptr++ = out | 0x80; 80就等于10000000 也就是跟前面补1
data >>= 7; 继续下个7个字节
}
else {
*ptr++ = out; break;
}
}
return ptr;
}
安卓源码读取LEB128的函数:
DEX_INLINE int readUnsignedLeb128(const u1** pStream) {
const u1* ptr = *pStream;
int result = *(ptr++);
if (result > 0x7f) { //判断第一个字节的第一位是不是1,7F=二进制1111111
int cur = *(ptr++); //指向第二个字节
result = (result & 0x7f) | ((cur & 0x7f) << 7); //通过逻辑运算符结合二进制
if (cur > 0x7f) {
cur = *(ptr++); //指向第三个
result |= (cur & 0x7f) << 14;//结合
if (cur > 0x7f) {
cur = *(ptr++);
result |= (cur & 0x7f) << 21;
if (cur > 0x7f) {
cur = *(ptr++);
result |= cur << 28;
}
}
}
}
*pStream = ptr;
return result;
}
2.DEX结构:
DEX结构图:不会画
文件头: header 文件头
索引区: string_ids 字符串的索引
type_ids 类型的索引
proto_ids 方法原型的索引
field_ids 域的索引
method_ids 方法的索引
Class_defs 类的定义区 (这一位有的资料说是数据区 无所谓)
数据区 data
Link_data
Header的内容:网上找了一张表
字段名称 |
偏移值 |
长度 |
描述 |
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 |
数据段基地址 |
我们参照具体的例子分析:
这个是QQ的安卓版本头文件如下 :
首先 模数字段为 64 65 78 0A 30 33 35 00也就是字符串dex.035.接下来是4个字节的checksum E2 21 E9 E8 。用来校验文件是否被传该。然后是20个字节的SHA-1签名,判断唯一性的。注意和上面的用途的区分,然后是文件大小 这里是0X929480,跟文件实际大小符合,文件头大小header_size是0x70.cpu字节序little-endian为0X012345678.link_size为0为0就表示是静态连接。link_off也一样。MAP_OFF制定dexmaplist的偏移。接下来就是各个表的基址和大小了 。
我们挨个分析 ,首先是map_off:
这个表示map item 的偏移地址 ,该 item 属于 data 区里的内容 ,值要大于等于 data_off 的大小 。结构如
struct dexmaplist
{
u4 size;
dexmapitem list [size];
}
struct dexmapitem
{
u2 type;
u2 unuse;
u4 size;
u4 offset;
}
我们根据上面例子的偏移定位到图中的位置
可以看到9293B0处的值是0x11 该值便为dexmaplist.size
然后紧接着便是0x11个dexmapitem。Dexmapitem的第一个元素为类型
enum{
kDexTypeHeaderItem = 0x0000,
kDexTypeStringIdItem = 0x0001,
kDexTypeTypeIdItem = 0x0002,
kDexTypeProtoIdItem = 0x0003,
kDexTypeFieldIdItem = 0x0004,
kDexTypeMethodIdItem = 0x0005,
kDexTypeClassDefItem = 0x0006,
kDexTypeMapList = 0x1000,
kDexTypeTypeList = 0x1001,
kDexTypeAnnotationSetRefList = 0x1002,
kDexTypeAnnotationSetItem = 0x1003,
kDexTypeClassDataItem = 0x2000,
kDexTypeCodeItem = 0x2001,
kDexTypeStringDataItem = 0x2002,
kDexTypeDebugInfoItem = 0x2003,
kDexTypeAnnotationItem = 0x2004,
kDexTypeEncodedArrayItem = 0x2005,
kDexTypeAnnotationsDirectoryItem = 0x2006,
};
这里包含头里面的那些,但是那里又更全面一些 ,又包括了 HEADER_ITEM , TYPE_LIST , STRING_DATA_ITEM 等 ,最后还有它自己 TYPE_MAP_LIST 。至此 , header 部分描述完毕 ,它包括描述 .dex 文件的信息 ,其余各索引区和 data 区的偏移信息 , 一个map_list 结构 。map_list 里除了对索引区和数据区的偏移地址又一次描述 ,也有其它诸如 HEAD_ITEM ,DEBUG_INFO_ITEM 等信息 。
看到这里才发现自己找的例子太大了。几万个字符串。。。。。。
--------------------------睡觉
换第三章最后的例子来解析,QQ的这是几十M 。太大了。换老赵手写的第三章的DEX文件来分析
我们先用二进制编辑文件打开该DEX ,可以看到文件二进制信息如下
00000000 64 65 78 0A 30 33 35 00 A7 6C 26 11 EB EB E1 27 dex.035.&.腚?
00000016 9B 3D 51 47 7A 57 A3 7C DB 6D 82 8D 1A 44 62 43 ?QGzW踡倣.DbC
00000032 20 03 00 00 70 00 00 00 78 56 34 12 00 00 00 00 ...p...xV4.....
00000048 00 00 00 00 74 02 00 00 0F 00 00 00 70 00 00 00 ....t.......p...
00000064 09 00 00 00 AC 00 00 00 03 00 00 00 D0 00 00 00 ....?......?..
00000080 01 00 00 00 F4 00 00 00 03 00 00 00 FC 00 00 00 ....?......?..
00000096 01 00 00 00 14 01 00 00 EC 01 00 00 34 01 00 00 ........?..4...
00000112 34 01 00 00 3C 01 00 00 49 01 00 00 57 01 00 00 4...<...I...W...
00000128 6E 01 00 00 82 01 00 00 9D 01 00 00 B1 01 00 00 n...?..?..?..
00000144 C5 01 00 00 C8 01 00 00 CC 01 00 00 D0 01 00 00 ?..?..?..?..
00000160 E5 01 00 00 EB 01 00 00 F0 01 00 00 02 00 00 00 ?..?..?......
00000176 03 00 00 00 04 00 00 00 05 00 00 00 06 00 00 00 ................
00000192 07 00 00 00 08 00 00 00 0A 00 00 00 0B 00 00 00 ................
00000208 08 00 00 00 06 00 00 00 00 00 00 00 09 00 00 00 ................
00000224 06 00 00 00 FC 01 00 00 09 00 00 00 06 00 00 00 ....?..........
00000240 04 02 00 00 04 00 01 00 0D 00 00 00 00 00 02 00 ................
00000256 0C 00 00 00 01 00 01 00 0E 00 00 00 03 00 00 00 ................
00000272 00 00 00 00 00 00 00 00 01 00 00 00 05 00 00 00 ................
00000288 00 00 00 00 FF FF FF FF 00 00 00 00 6C 02 00 00 ........l...
00000304 00 00 00 00 06 3C 69 6E 69 74 3E 00 0B 48 65 6C .....<init>..Hel
00000320 6C 6F 20 57 6F 72 6C 64 00 0C 4C 48 65 6C 6C 6F lo World..LHello
00000336 57 6F 72 6C 64 3B 00 15 4C 6A 61 76 61 2F 69 6F World;..Ljava/io
00000352 2F 50 72 69 6E 74 53 74 72 65 61 6D 3B 00 12 4C /PrintStream;..L
00000368 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 java/lang/String
00000384 3B 00 19 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 ;..Ljava/lang/St
00000400 72 69 6E 67 42 75 69 6C 64 65 72 3B 00 12 4C 6A ringBuilder;..Lj
00000416 61 76 61 2F 6C 61 6E 67 2F 53 79 73 74 65 6D 3B ava/lang/System;
00000432 00 12 4C 6A 61 76 65 2F 6C 61 6E 67 2F 4F 62 6A ..Ljave/lang/Obj
00000448 65 63 74 3B 00 01 56 00 02 56 4C 00 02 5B 49 00 ect;..V..VL..[I.
00000464 13 5B 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 .[Ljava/lang/Str
00000480 69 6E 67 3B 00 04 6D 61 69 6E 00 03 6F 75 74 00 ing;..main..out.
00000496 07 70 72 69 6E 74 6C 6E 00 00 00 00 01 00 00 00 .println........
00000512 02 00 00 00 01 00 00 00 08 00 00 00 00 00 00 00 ................
00000528 00 01 00 07 00 00 00 00 04 00 01 00 02 00 00 00 ................
00000544 10 02 00 00 22 00 00 00 00 00 00 00 00 00 00 00 ...."...........
00000560 13 00 08 00 12 51 12 32 01 21 23 00 07 00 21 01 .....Q.2.!#...!.
00000576 22 01 03 00 70 10 02 00 01 00 39 00 03 00 28 0D "...p.....9...(.
00000592 82 22 A6 02 02 02 2D 00 02 02 62 00 00 00 1A 01 ??..-...b.....
00000608 01 00 6E 20 01 00 10 00 0E 00 0E 00 00 00 01 00 ..n ............
00000624 00 09 98 04 0E 00 00 00 00 00 00 00 01 00 00 00 ..?............
00000640 00 00 00 00 01 00 00 00 0F 00 00 00 70 00 00 00 ............p...
00000656 02 00 00 00 09 00 00 00 AC 00 00 00 03 00 00 00 ........?......
00000672 03 00 00 00 D0 00 00 00 04 00 00 00 01 00 00 00 ....?..........
00000688 F4 00 00 00 05 00 00 00 03 00 00 00 FC 00 00 00 ?..........?..
00000704 06 00 00 00 01 00 00 00 14 01 00 00 02 20 00 00 ............. ..
00000720 0F 00 00 00 34 01 00 00 01 10 00 00 02 00 00 00 ....4...........
00000736 FC 01 00 00 03 10 00 00 01 00 00 00 0C 02 00 00 ?..............
00000752 03 20 00 00 01 00 00 00 10 02 00 00 01 20 00 00 . ........... ..
00000768 01 00 00 00 18 02 00 00 00 20 00 00 01 00 00 00 ......... ......
00000784 6C 02 00 00 00 10 00 00 01 00 00 00 74 02 00 00 l...........t...
下面具体分析各个索引 :
string_ids:
string_ids 区索引了文件所有的字符串 。 本区里的元素格式为 string_ids_item , 可以使用结构体如下描述 。
struct string_ids_item
{
uint string_data_off;
}
以 _ids 结尾的各个区段里放置的都是对应数据的偏移地址 ,只是一个索引 ,所以才会在 .dex文件布局里把这些区归类为 “索引区” 。string_data_off 只是一个偏移地址 ,它指向的数据结构为 string_data_item
struct string_data_item
{
uleb128 utf16_size; 这里用到了前面的LEB128 可变字节类型!
ubyte data;
}
我们根据头文件中提供的string_ids_size=0F和string_ids_off=70定位到字符串索引区
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F
这里有0F个string_ids_item结构的索引 :
00000070 34 01 00 00 3C 01 00 00 49 01 00 00 57 01 00 00 4...<...I...W...
00000080 6E 01 00 00 82 01 00 00 9D 01 00 00 B1 01 00 00 n...?..?..?..
00000090 C5 01 00 00 C8 01 00 00 CC 01 00 00 D0 01 00 00 ?..?..?..?..
000000A0 E5 01 00 00 EB 01 00 00 F0 01 00 00 ?..?..?..
我们再具体定位到string_data_item :
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F
00000130 00 00 00 00 06 3C 69 6E 69 74 3E 00 0B 48 65 6C .....<init>..Hel
00000140 6C 6F 20 57 6F 72 6C 64 00 0C 4C 48 65 6C 6C 6F lo World..LHello
00000150 57 6F 72 6C 64 3B 00 15 4C 6A 61 76 61 2F 69 6F World;..Ljava/io
00000160 2F 50 72 69 6E 74 53 74 72 65 61 6D 3B 00 12 4C /PrintStream;..L
00000170 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 java/lang/String
00000180 3B 00 19 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 ;..Ljava/lang/St
00000190 72 69 6E 67 42 75 69 6C 64 65 72 3B 00 12 4C 6A ringBuilder;..Lj
000001A0 61 76 61 2F 6C 61 6E 67 2F 53 79 73 74 65 6D 3B ava/lang/System;
000001B0 00 12 4C 6A 61 76 65 2F 6C 61 6E 67 2F 4F 62 6A ..Ljave/lang/Obj
000001C0 65 63 74 3B 00 01 56 00 02 56 4C 00 02 5B 49 00 ect;..V..VL..[I.
000001D0 13 5B 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 .[Ljava/lang/Str
000001E0 69 6E 67 3B 00 04 6D 61 69 6E 00 03 6F 75 74 00 ing;..main..out.
000001F0 07 70 72 69 6E 74 6C 6E 00 00 00 00 01 00 .println......
这样我们解析到了所有的字符串。这里具体分析: 接下来的都大同小一
索引 |
偏移 |
长度 |
内容 |
0 |
134 |
06 |
<init> |
1 |
13c |
0b |
Hello World |
2 |
149 |
0c |
LHelloWorld; |
3 |
157 |
15 |
Ljava/io/PrintStream; |
4 |
16e |
12 |
Ljava/lang/String; |
5 |
182 |
19 |
Ljava/lang/StringBuilder; |
6 |
19d |
12 |
Ljava/lang/System; |
7 |
1b1 |
12 |
Ljave/lang/Object; |
8 |
1c5 |
01 |
V |
9 |
1c8 |
02 |
VL |
10 |
1cc |
02 |
[I |
11 |
1d0 |
13 |
[Ljava/lang/String; |
12 |
1e5 |
04 |
main |
13 |
1eb |
03 |
out |
14 |
1f0 |
07 |
println |
type_ids:
type_ids 区索引了文件里的所有数据类型 ,包括 class 类型 ,数组类型和基本类型 。 本区域里的元素格式为 type_ids_item , 结构描述如下 :
struct type_ids_item
{
uint descriptor_idx;
}
type_ids_item 里面 descriptor_idx 的值的意思 ,是 string_ids 里的 index 序号 ,是用来描述此type 的字符串 。
根据 header 里 type_ids_size = 0x09 , type_ids_off = 0xaC , 找到对应的二进制描述区 。
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F
000000A0 02 00 00 00 ....
000000B0 03 00 00 00 04 00 00 00 05 00 00 00 06 00 00 00 ................
000000C0 07 00 00 00 08 00 00 00 0A 00 00 00 0B 00 00 00 ................
这里有9个type_ids_item ,他的元素descriptor_idx存的是在string_ids的索引号
我根据这些索引号去上面找出所有类型的名称 这里具体分析:
索引 |
值 |
内容 |
0 |
02 |
LHelloWorld; |
1 |
03 |
Ljava/io/PrintStream; |
2 |
04 |
Ljava/lang/String; |
3 |
05 |
Ljava/lang/StringBuilder; |
4 |
06 |
Ljava/lang/System; |
5 |
07 |
Ljave/lang/Object; |
6 |
08 |
V |
7 |
0a |
[I |
8 |
0b |
[Ljava/lang/String; |
proto_ids:
这里存的是方法method原型。机构如下 :
struct proto_id_item
{
uint shorty_idx; 指向字符串 索引
uint return_type_idx; 指向类型索引
uint parameters_off; type_list的偏移
}
索引值如下 :
shorty_idx和上面的type_ids的descriptor_idx; 的元素一样 存的是string_ids的索引 。得出一个字符串,用来说明method 原型
return_type_idx的值是依个type_ids的索引 。用来表示该method原型的返回值类型。
parameters_off用来指向method的参数列表的类型type_list结构的表。如果parameters_off为0 则表明该函数无参数。
parameters_off指向的type_list结构描述如下(也就是参数列表结构) :
struct type_list
{
uint size;
ushort type_idx[size]; 也就是书上的 type_item
}
Size是参数数量 type_idx 也就是对应的类型了 里面存的是type_ids的索引了 每个占两个字节 我们根据头文件中的proto_ids_size = 0x03 , proto_ids_off = 0xD0得到
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F
000000D0 08 00 00 00 06 00 00 00 00 00 00 00 09 00 00 00 ................
000000E0 06 00 00 00 FC 01 00 00 09 00 00 00 06 00 00 00 ....?..........
000000F0 04 02 00 00 ....
得到type_liet如下 :
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F
000001F0 01 00 00 00 ....
00000200 02 00 00 00 01 00 00 00 08 00 00 00 ............
这里有点乱。等下一起研究。
索引 |
0 |
1 |
2 |
shorty_idx |
08 |
09 |
09 |
return_type_idx |
06 |
06 |
06 |
parameters_off |
0 |
1fc |
204 |
shorty string |
V |
VL |
VL |
return string |
V |
V |
V |
Param num |
0 |
1 |
1 |
typeIDx |
|
2(Ljava/lang/String;) |
8([Ljava/lang/String;) |
索引 |
偏移 |
长度 |
内容 |
0 |
134 |
06 |
<init> |
1 |
13c |
0b |
Hello World |
2 |
149 |
0c |
LHelloWorld; |
3 |
157 |
15 |
Ljava/io/PrintStream; |
4 |
16e |
12 |
Ljava/lang/String; |
5 |
182 |
19 |
Ljava/lang/StringBuilder; |
6 |
19d |
12 |
Ljava/lang/System; |
7 |
1b1 |
12 |
Ljave/lang/Object; |
8 |
1c5 |
01 |
V |
9 |
1c8 |
02 |
VL |
10 |
1cc |
02 |
[I |
11 |
1d0 |
13 |
[Ljava/lang/String; |
12 |
1e5 |
04 |
main |
13 |
1eb |
03 |
out |
14 |
1f0 |
07 |
println |
field_ids:
这个是存啥的 ?书上说是字段 本区的元素格式是 field_id_item ,结构如下
struct filed_id_item
{
ushort class_idx;
ushort type_idx;
uint name_idx;
}
class_idx , 表示本field所属的class 类型,class_idx 的值是 type_ids 的索引 , 并且必须指向一个class 类型 。
type_idx , 表示本 field 的类型 ,它的值也是 type_ids 的一个索引 。
name_idx , 表示本 field 的名称 ,它的值是 string_ids 的一个索引 。
header 里 field_ids_size = 1 , field_ids_off = 0xF4 。说明本 .dex 只有一个 field ,这部分的二进制描述如下 :
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F
000000F0 04 00 01 00 0D 00 00 00 ........
现在具体分析 :
。。。。。。。。。。。
索引 |
|
Class_idx |
04 |
Type_idx |
01 |
name_idx |
OD |
class string |
Ljava/lang/System; |
Type string |
Ljava/io/PrintStream; |
name string |
out |
method_ids
最后一个了 1点16了 。。。。还没整理完 。。method_ids 这个就是所有方法的条目索引 ,method_ids 的元素格式是 method_id_item。结构如下
struct filed_id_item
{
ushort class_idx;
ushort proto_idx;
uint name_idx;
}
class_idx , 和 name_idx 跟 fields_ids 是一样的 。
class_idx , 表示本 method 所属的 class 类型 , class_idx 的值是 type_ids 的一个索引 , 并且必须指向一个 class 类型 。
name_idx , 表示本 method 的名称 ,它的值是 string_ids 的一个索引 。
proto_idx 描述该 method 的原型 ,指向 proto_ids 的一个索引
header 里 method_ids_size = 0x03 , method_ids_off = 0xfC 。本部分的二进制描述如下 :
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F
000000F0 0D 00 00 00 00 00 02 00 ........
00000100 0C 00 00 00 01 00 01 00 0E 00 00 00 03 00 00 00 ................
索引 |
0 |
1 |
2 |
class_idx |
0 |
01 |
03 |
proto_idx |
02 |
01 |
00 |
name_idx |
0C |
0E |
00 |
class string |
LHelloWorld; |
Ljava/io/PrintStream; |
Ljava/lang/StringBuilder; |
protostring |
VL([Ljava/lang/String;)V |
VL |
V |
name string |
main |
println |
<init> |
Class_defs
class_def_item
class 的定义 他的结构较前面的都比较复杂
结构如下
struct class_def_item
{
uint class_idx; 类的类型
uint access_flags; 访问标志
uint superclass_idx; 父类的类型
uint interfaces_off; 接口 ?
uint source_file_idx; 源文件名
uint annotations_off; 注解
uint class_data_off; 指向DexClassData !!!!
uint static_value_off; 指向DexEncodedarray
}
摘抄:
(1) class_idx 描述具体的 class 类型 ,值是 type_ids 的一个索引 。值必须是一个 Class 类型 ,不能是数组类型或者基本类型 。
(2) access_flags 描述 class 的访问类型 ,诸如 public , final , static 等 。在 dex-format.html 里 “access_flagsDefinitions” 有具体的描述 。
(3) superclass_idx , 描述 supperclass 的类型 ,值的形式跟 class_idx 一样 。
(4) interfaces_off , 值为偏移地址 ,指向 class 的 interfaces , 被指向的数据结构为 type_list 。class 若没有interfaces ,值为 0。
(5) source_file_idx , 表示源代码文件的信息 ,值是 string_ids 的一个 index 。若此项信息缺失 ,此项值赋值为NO_INDEX=0xffff ffff 。
(6) annotions_off , 值是一个偏移地址 ,指向的内容是该 class 的注释 ,位置在 data 区,格式为annotations_direcotry_item 。若没有此项内容 ,值为 0 。(难道会把注释也打包进去 ?????????????????)
(7) class_data_off , 值是一个偏移地址 ,指向的内容是该 class 的使用到的数据 ,位置在 data 区,格式为class_data_item 。若没有此项内容 ,值为 0 。该结构里有很多内容 ,详细描述该 class 的 field , method, method 里的执行代码等信息.
(8) static_value_off , 值是一个偏移地址 ,指向 data 区里的一个列表 ( list ) ,格式为 encoded_array_iteM。若没有此项内容 ,值为 0 。
header 里 class_defs_size = 0x01 , class_defs_off = 0x 0114 。则此段二进制描述为 :
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F
00000110 00 00 00 00 01 00 00 00 05 00 00 00 ............
00000120 00 00 00 00 FF FF FF FF 00 00 00 00 6C 02 00 00 ........l...
00000130 00 00 00 00 ....
元素 |
值 |
内容 |
class_idx |
0 |
LHelloWorld; |
access_flags |
1 |
|
superclass_idx |
5 |
Ljave/lang/Object; |
interfaces_off。。 |
0 |
|
source_file_idx |
FFFFFFFF |
无源文件信息 |
annotions_off。。 |
0 |
|
class_data_off。。 |
26C |
|
static_value_off。。 |
0 |
|
class_data_off 指向 data 区里的 class_data_item 结构 ,class_data_item 里存放着本 class 使用到的各种数据 ,下面是 class_data_item 的逻辑结构 :
struct class_data_item
{
U4 static_fields_size;
U4 instance_fields_size;
U4 direct_methods_size;
U4 virtual_methods_size; 不是U4 其实是LEB128
;上面为头
encoded_field static_fields [ static_fields_size ];
encoded_field instance_fields [ instance_fields_size ];
encoded_method direct_methods [ direct_method_size ];
encoded_method virtual_methods [ virtual_methods_size ];
}
struct encoded_field
{
uleb128 filed_idx_diff; // index into filed_ids for ID of this filed
uleb128 access_flags; // access flags like public, static etc.
}
struct encoded_method
{
uleb128 method_idx;
uleb128 access_flags;
uleb128 code_off;
}
(1)method_idx指向前面的方法那里 。
(2)access_flags , 访问权限 , 比如 public、private、static、final 等 。
(3)code_off , 一个指向 data 区的偏移地址 ,目标是本 method 的代码实现 。被指向的结构是code_item ,有近 10 项元素 .这里放着这个软件的各种代码的具体实现,也就是所谓的字节码
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F
00000260 00 00 01 00 ....
00000270 00 09 98 04 0E 00 00 00 00 00 00 00 01 00 00 00 ..?............
00000280 00 00 00 00 01 00 00 00 0F 00 00 00 70 00 00 00 ............p...
00000290 02 00 00 00 09 00 00 00 AC 00 00 00 03 ........?...
Element |
value |
static_fields_size |
0 |
instance_fields_size |
0 |
directive_methods_size |
1 |
vitual_methods_size |
0 |
static_fields[] |
|
instance_fields[] |
|
directive_methods[] |
00 09 98 04 0e |
vitual_methods[] |
|
得到上面的描述后从uleb128 转换过来
method |
son-element |
value |
meaning |
directive_method[0] |
method_idx_diff |
00 |
LHelloWorld;->main([Ljava/lang/String;)V |
access_flags |
09 |
quanxian |
|
code_off |
0X218 |
|
着重看+218处的 code_off吧
struct code_item
{
ushort registers_size;
ushort ins_size;
ushort outs_size;
ushort tries_size;
uint debug_info_off;
uint insns_size;
ushort insns [ insns_size ];
ushort paddding; // optional
try_item tries [ tyies_size ]; // optional
encoded_catch_handler_list handlers; // optional 指令集
}
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F
00000210 04 00 01 00 02 00 00 00 ........
00000220 10 02 00 00 22 00 00 00 00 00 00 00 00 00 00 00 ...."...........
00000230 13 00 08 00 12 51 12 32 01 21 23 00 07 00 21 01 .....Q.2.!#...!.
00000240 22 01 03 00 70 10 02 00 "...p...
我们粘贴下来22*2个字节
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F
00000220 00 00 00 00 00 00 00 00 ........
00000230 13 00 08 00 12 51 12 32 01 21 23 00 07 00 21 01 .....Q.2.!#...!.
00000240 22 01 03 00 70 10 02 00 01 00 39 00 03 00 28 0D "...p.....9...(.
00000250 82 22 A6 02 02 02 2D 00 02 02 62 00 00 00 1A 01 ??..-...b.....
00000260 01 00 6E 20 01 00 10 00 0E 00 0E 00 ..n ........
名称 |
值 |
registers_size |
04 |
ins_size |
01 |
outs_size |
02 |
tries_size |
00 |
debug_info_off |
0210 |
insns_size |
22 |
Insnsdf[二进制字节码 ] |
上面那些 |
好乱 理下思路
整个DEX文件被分为9个区 class_defs索引了DEX文件中用到的CLASS ,和对这个CLASS的描述,CLASS_DEFS里面实际是class_def_item结构。这个结构描述了类的各种信息 。例如名称啥的 。class_def_item 里有一个元素 class_data_off , 指向data 区里的一个 class_data_item 结构 ,用来描述 class 使用到的各种数据 。 class_data_item 结构 ,里描述值着 class 里使用到的 static field , instance field , direct_method ,和 virtual_method 的数目和描述。encoded_method 结构 ,描述某个 method 的 method 类型 , access flags 和一个指向 code_item的偏移地址 ,里面存放的是该 method 的具体实现 。
好乱
末尾的 3 项标志为 optional , 表示可能有 ,也可能没有 ,根据具体的代码来 。
(1) registers_size, 本段代码使用到的寄存器数目。
(2) ins_size, method 传入参数的数目 。
(3) outs_size, 本段代码调用其它method 时需要的参数个数 。
(4) tries_size, try_item 结构的个数 。
(5) debug_off, 偏移地址 ,指向本段代码的 debug 信息存放位置 ,是一个 debug_info_item 结构。
(6) insns_size, 指令列表的大小 ,以 16-bit 为单位 。 insns 是 instructions 的缩写 。
(7) padding , 值为 0 ,用于对齐字节 。
(8) tries 和 handlers , 用于处理 java 中的 exception , 常见的语法有 try catch 。
;Dalvik VM Instruction Format
接下来我们翻译这些指令
我们粘贴下来22*2个字节
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F
00000220 00 00 00 00 00 00 00 00 ........
00000230 13 00 08 00 12 51 12 32 01 21 23 00 07 00 21 01 .....Q.2.!#...!.
00000240 22 01 03 00 70 10 02 00 01 00 39 00 03 00 28 0D "...p.....9...(.
00000250 82 22 A6 02 02 02 2D 00 02 02 62 00 00 00 1A 01 ??..-...b.....
00000260 01 00 6E 20 01 00 10 00 0E 00 0E 00 ..n ........
http://www.netmite.com/android/mydroid/dalvik/docs/instruction-formats.html
http://www.netmite.com/android/mydroid/dalvik/docs/instruction-formats.html我们根据这个来解析部分代码
0000很容易 是4个NOP
步骤 (1) 《Dalvik VM Instruction Format》 里操作符 op 都是位于首个 16bit 数据的低 8 bit ,起始的是 op =0x13。
(2) 在 《Bytecode for Dalvik VM》 里找到对应的 Syntax 和 format 。
syntax=const/16
Format=21S
(3) 在《Dalvik VM Instruction Format》里查找 21s , 得知 op = 0x13 的指令占据 2 个 16 bit 数据 ,
格式是 AA|op BBBB ,解释为 op vAA, #+BBBB 。因此这 8 组 16 bit 数据里 ,前 2 个是一组 。
对比数据得 AA=0x00, BBBB = 0x0008。
(4)返回《Bytecode for Dalvik VM》里查阅对 const/16 的解释, AA 的值表示 Value Register ,即0 号寄存器; BBBB 表示 signed int ,就是整形的一个立即数
A: destination register (8 bits)
B: signed int (16 bits)
所以13 00 08 00 就可以解释为const/16 v0,8....是不是很容易 以此解释就可以解释完这条CLass的这个 method的全部代码
我们继续5112 我们按第一条步骤 ,找出op=12 按照步骤2 找出该指令的描述 12 11n const/4 vA, #+B A: destination register (4 bits)
B: signed int (4 bits) Format是11N 按步骤3继续 该OP对应的格式是B|A|op
解释为 op vA, #+B,该指令占16个字节 按照这个格式 所以前4个字节代表B 然后4个代表A 最后8个是OP 那么a=1,B=5 然后去查这个OP的解释 A: destination register (4 bits)
B: signed int (4 bits) Format 那么A是寄存器 B是值,大小都是4字节
该指令应该为 const/4,V1,5
接下来 又是个12 那么很容易推出 是 const/4,V2,3
接下来 是2101 我们查下01对应的OP 按照上面的方法推出是 move v1,v2
下面是个0023 我们得出OP是
22C
new-array vA, vB, type@CCCC
A: destination register (8 bits)
B: size register
C: type index
格式是B|A|op CCCC 那么它占了4个字节 我们再往后取两个字节 就是0023 0007
A=0 b=0 C=7 这里有个问题 C的描述是type index ,我们要去上面的类型表里找
得到 该指令的结果为 :
new-array v0, v0, 【l
下面看较难的一条指令+244处 1070 该指令的OP我们查找 是 70: invoke-direct
描述如下 :
B: argument word count (4 bits)
C: method index (16 bits)
D..G, A: argument registers (4 bits each)
|
[B=5] op{vD, vE, vF, vG, vA}, meth@CCCC |
Format=35c 该指令的格式为 B|A|op CCCC G|F|E|D 我们看该指令的描述 B未描述了改指令的具体格式 从0到5 。B=1 A=0 那么该指令占6个字节 继续取两个字节整个为1070 0002 0001 C=2 D=1按格式整理 该指令为
invoke-direct {V1},Ljava/lang/StringBuilder;-> <init>()V
下面大家继续。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
原文:http://www.cnblogs.com/Cnforce/p/3817911.html