Dex文件格式简解

"DEX"

Posted by hbl on 2017-05-18

Dex文件格式简解

一个简单的Dex文件分析文章,学习自http://www.blogfshare.com/dex-format.htmlhttp://www.wjdiankong.cn/android%E9%80%86%E5%90%91%E4%B9%8B%E6%97%85-%E8%A7%A3%E6%9E%90%E7%BC%96%E8%AF%91%E4%B9%8B%E5%90%8E%E7%9A%84dex%E6%96%87%E4%BB%B6%E6%A0%BC%E5%BC%8F/
先上图,文尾给出github地址。

从最后一张图可以看出,整个dex文件包括:

  1. 文件头,主要包含校验和其他结构的偏移地址和长度信息。
  2. 索引区,包含string,type,proto,field,method等信息。
  3. 数据区,包含class数据,data数据和链接数据。

从中可以看到Header部分都包括哪些内容,简单介绍。

(1). magic value

这 8 个 字节一般是常量 ,为了使 .dex 文件能够被识别出来 ,它必须出现在 .dex 文件的最开头的
位置 。数组的值可以转换为一个字符串如下 :
{ 0x64 0x65 0x78 0x0a 0x30 0x33 0x35 0x00 } = “dex\n035\0”
中间是一个 ‘\n’ 符号 ,后面 035 是 Dex 文件格式的版本 。

(2). checksum 和 signature

文件校验码 ,使用alder32 算法校验文件除去 maigc ,checksum 外余下的所有文件区域 ,用于检查文件错误 。
signature , 使用 SHA-1 算法 hash 除去 magic ,checksum 和 signature 外余下的所有文件区域 ,
用于唯一识别本文件 。

(3). file_size

Dex 文件的大小 。

(4). header_size

header 区域的大小 ,单位 Byte ,一般固定为 0x70 常量 。

(5). endian_tag

大小端标签 ,标准 .dex 文件格式为 小端 ,此项一般固定为 0x1234 5678 常量 。

(6). link_size和link_off

这个两个字段是表示链接数据的大小和偏移值。

(7). map_off

map_item 的偏移地址 ,该 item 属于 data 区里的内容 ,值要大于等于 data_off 的大小 。
定义位置:data区。
引用位置:header 区 。
map_list 里先用一个 uint 描述后面有 size 个 map_item , 后续就是对应的 size 个 map_item 描述 。map_item 结构有 4 个元素 : type 表示该 map_item 的类型 ,本节能用到的描述如下 ,详细Dalvik Executable Format 里 Type Code 的定义 ;size 表示再细分此 item,该类型的个数;offset 是第一个元素的针对文件初始位置的偏移量 ; unuse 是用对齐字节的 ,无实际用处 。(这个部分略)

(8). string_ids_size和string_ids_off

这两个字段表示dex中用到的所有的字符串内容的大小和偏移值,我们需要解析完这部分,然后用一个字符串池存起来,后面有其他的数据结构会用索引值来访问字符串,这个池子也是非常重要的。

(9). type_ids_size和type_ids_off

这两个字段表示dex中的类型数据结构的大小和偏移值,比如类类型,基本类型等信息。

(10). proto_ids_size和type_ids_off

这两个字段表示dex中的元数据信息数据结构的大小和偏移值,描述方法的元数据信息,比如方法的返回类型,参数类型等信息,后面会详细介绍proto_ids的数据结构

(11). field_ids_size和field_ids_off

这两个字段表示dex中的字段信息数据结构的大小和偏移值,后面会详细介绍field_ids的数据结构

(12). method_ids_size和method_ids_off

这两个字段表示dex中的方法信息数据结构的大小和偏移值,后面会详细介绍method_ids的数据结构

(13). class_defs_size和class_defs_off

这两个字段表示dex中的类信息数据结构的大小和偏移值,这个数据结构是整个dex中最复杂的数据结构,他内部层次很深,包含了很多其他的数据结构,所以解析起来也很麻烦,所以后面会着重讲解这个数据结构

(14). data_size和data_off

这两个字段表示dex中数据区域的结构信息的大小和偏移值,这个结构中存放的是数据区域,比如我们定义的常量值等信息。

到这里我们就看完了dex的头部信息,头部包含的信息还是很多的,主要就两个个部分:

  1. 魔数+签名+文件大小等信息

  2. 后面的各个数据结构的大小和偏移值,都是成对出现的。

索引区

(1). string_ids数据结构

string_ids 区索引了 .dex 文件所有的字符串 。 本区里的元素格式为 string_ids_item。可以使用结
构体如下描述 。

class StringIdsItem {
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* struct string_ids_item
{
uint string_data_off;
}
*/
public int string_data_off;
public static int getSize(){
return 4;
}
@Override
public String toString(){
return Utils.bytesToHexString(Utils.int2Byte(string_data_off));
}
}
```
因为一个item是4字节,然后ids_size=0x0e,ids_off=0x70,然后可以算一下0e=14,那就是14*4=56,从70开始56个字节。在逆向的那篇文章中有相对应的数据截图,不过我当时看的时候发现ids_item的二进制少了后面2位,当时一直没看懂后来才明白,应该是作者红色标的不对。后面也有好几处我算的和作者标注不符的。然后在根据ids_item查找data_item,将获得的string放到一个池中。
这里说一下计算长度的方法,最开始我在看的时候没有理解ids_size的含义,off偏移地址我懂,然后就差在计算当前item的长度上,后来看了另一篇文章之后才明白,size的表示含有的item的个数,相对应的item对于string , proto , type , method是不一样的,首先你得知道item的结构体是有什么组成的,int 就是4,short就是2,先算一个item的长度,然后乘以size,这就是对应的数据的长度,从off出开始查找。
(2). type_ids数据结构
这个数据结构中存放的数据主要是描述dex中所有的类型,比如类类型,基本类型等信息。type_ids 区索引了 dex 文件里的所有数据类型 ,包括 class 类型 ,数组类型(array types)和基本类型(primitive types) 。 本区域里的元素格式为 type_ids_item , 结构描述如下 :

public class TypeIdsItem {
/**

 * struct type_ids_item
    {
    uint descriptor_idx;
    }
 */    
public int descriptor_idx;    
public static int getSize(){
    return 4;
}
@Override
public String toString(){
    return Utils.bytesToHexString(Utils.int2Byte(descriptor_idx));
}

}

1
2
3
4
5
6
7
8
**type_ids_item 里面 descriptor_idx 的值的意思 ,是 string_ids 里的 index 序号 ,是用来描述此type 的字符串。**
我们后面的其他数据结构也会使用到type_ids类型,所以我们这里解析完type_ids也是需要用一个池子来存放的,后面直接用索引index来访问即可。
(3). proto_ids数据结构
proto 的意思是 method prototype 代表 java 语言里的一个 method 的原型 。proto_ids 里的元素为 proto_id_item , 结构如下 。

public class ProtoIdsItem {
/**

 * struct proto_id_item
    {
    uint shorty_idx;
    uint return_type_idx;
    uint parameters_off;
    }
 */    
public int shorty_idx;
public int return_type_idx;
public int parameters_off;
//这个不是公共字段,而是为了存储方法原型中的参数类型名和参数个数
public List<String> parametersList = new ArrayList<String>();
public int parameterCount;
public static int getSize(){
    return 4 + 4 + 4;
}
@Override
public String toString(){
    return "shorty_idx:"+shorty_idx+",return_type_idx:"+return_type_idx+",parameters_off:"+parameters_off;
}

}

1
2
3
4
5
6
7
8
9
shorty_idx :跟 type_ids 一样 ,它的值是一个 string_ids 的 index 号 ,最终是一个简短的字符串描述 ,用来说明该 method 原型。
return_type_idx :它的值是一个 type_idsindex 号 ,表示该 method 原型的返回值类型 。
parameters_off :后缀 offoffset , 指向 method 原型的参数列表 type_list ; 若 method 没有参数 ,值为0 。参数列表的格式是 type_list ,结构从逻辑上如下描述 。size 表示参数的个数 ;type_idx 是对应参数的类型 ,它的值是一个 type_idsindex 号 ,跟 return_type_idx 是同一个品种的东西 。
注意:我们在这里会看到很多idx结尾的字段,这个一般都是索引值,所以我们要注意的是,区分这个索引值到底是对应的哪张表格,是字符串池,还是类型池等信息,这个如果弄混淆的话,那么解析就会出现混乱了。这个后面其他数据结构都是需要注意的。
(4). field_ids数据结构
filed_ids 区里面存放的是dex 文件引用的所有的 field 。本区的元素格式是 field_id_item ,逻辑结构描述如:

public class FieldIdsItem {
/**

 * struct filed_id_item
    {
    ushort class_idx;
    ushort type_idx;
    uint name_idx;
    }
 */    
public short class_idx;
public short type_idx;
public int name_idx;    
public static int getSize(){
    return 2 + 2 + 4;
}
@Override
public String toString(){
    return "class_idx:"+class_idx+",type_idx:"+type_idx+",name_idx:"+name_idx;
}    

}

1
2
3
4
5
6
7
8
9
10
11
class_idx :表示本 field 所属的 class 类型 , class_idx 的值是 type_ids 的一个 index , 并且必须指向一个class 类型 。
type_idx :表示本 field 的类型 ,它的值也是 type_ids 的一个 index
name_idx : 表示本 field 的名称 ,它的值是 string_ids 的一个 index
注意:这里的字段都是索引值,一定要区分是哪个池子的索引值,还有就是,这个数据结构我们后面也要使用到,所以需要用一个池子来存储。
(5). method_ids数据结构
method_ids 是索引区的最后一个条目 ,它索引了 dex 文件里的所有的 method
method_ids 的元素格式是 method_id_item , 结构跟 fields_ids 很相似:

public class MethodIdsItem {
/**

 * struct filed_id_item
    {
    ushort class_idx;
    ushort proto_idx;
    uint name_idx;
    }
 */    
public short class_idx;
public short proto_idx;
public int name_idx;    
public static int getSize(){
    return 2 + 2 + 4;
}    
@Override
public String toString(){
    return "class_idx:"+class_idx+",proto_idx:"+proto_idx+",name_idx:"+name_idx;
}

}
```

class_idx :表示本 method 所属的 class 类型 , class_idx 的值是 type_ids 的一个 index , 并且必须指向一个 class 类型 。
name_idx :表示本 method 的名称 ,它的值是 string_ids 的一个 index 。
proto_idx :描述该 method 的原型 ,指向 proto_ids 的一个 index 。

(6). class_defs数据结构 (这个太难了,略)

结束

后面都太难了,我也是心有余而力不足。就到此为止了。这篇文章主要的就是简单的header加索引区的分析,主要的还是得看上面的参考文章。