DEX文件结构

  1. 1 背景
  2. 2 生成DEX文件
  3. 3 DEX文件结构
    1. 3.1 DEX文件整体结构
    2. 3.2 Header
    3. 3.3 String Ids
    4. 3.4 Type Ids
    5. 3.5 Proto Ids
    6. 3.6 Field Ids
    7. 3.7 Method Ids
    8. 3.8 Class Def
    9. 3.9 Map List
  4. 4 总结
  5. 5 参考文献

1 背景

DEX是Dalvik虚拟机的可执行文件。了解DEX文件结构对于学习热修复框架,加固和逆向相关知识是很有必要的。图1便是来自非虫的看雪神图。

图1 Android DEX文件格式

2 生成DEX文件

在解析DEX文件结构之前,先手动生成一个最简单的DEX文件,编写一个简单的Java程序,如下所示:

1
2
3
4
5
6
7
8
9
10
11
// Hello.java
public class Hello {
public int foo(int a, int b) {
return (a + b) * (a - b);
}

public static void main(String[] argc) {
Hello hello = new Hello();
System.out.println(hello.foo(5, 3));
}
}

接着使用javacSDK\build-tools\30.0.3\dx.bat在当前目录下生成Hello.dex文件:

javac Hello.java
dx –dex –output=Hello.dex Hello.class

3 DEX文件结构

3.1 DEX文件整体结构

如图2所示,DEX文件的整体结构包括文件头索引区数据区三部分。文件头指定DEX文件的一些属性并记录其他6部分数据结构在DEX文件中的物理偏移索引区记录着一些偏移和索引数据区用于存放真正所需要的数据

图2 DEX文件整体结构

DEX文件由DexFile结构体表示,定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
struct DexFile {
/* directly-mapped "opt" header */
const DexOptHeader* pOptHeader;
/* pointers to directly-mapped structs and arrays in base DEX */
const DexHeader* pHeader;
const DexStringId* pStringIds;
const DexTypeId* pTypeIds;
const DexFieldId* pFieldIds;
const DexMethodId* pMethodIds;
const DexProtoId* pProtoIds;
const DexClassDef* pClassDefs;
const DexLink* pLinkData;
/*
* These are mapped out of the "auxillary" section, and may not be
* included in the file.
*/
const DexClassLookup* pClassLookup;
const void* pRegisterMapPool; // RegisterMapClassPool
/* points to start of DEX file data */
const u1* baseAddr;
/* track memory overhead for auxillary structures */
int overhead;
/* additional app-specific data structures associated with the DEX */
//void* auxData;
};

图3 dex文件整体结构

3.2 Header

DEX文件头部分的具体格式也可以参考DexFile.h中的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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;
};

将Hello.dex文件拖入010 Editor中,F5运行DEX.bt脚本,即可显示出DEX文件头的信息,如图4所示。

图4 010 Editor解析DexHeader

最后,再多给出两张图片,加深自己对DEX文件头的印象。

图5 DexHeaderr

图6 DexHeader

3.3 String Ids

string_ids字符串标识符列表,这些是此文件使用的所有字符串的标识符,用于内部命名(例如类型描述符)或用作代码引用的常量对象。
string_ids索引了DEX文件中所有的字符串stringDataOff表示每个字符串在data区的偏移量

1
2
3
struct DexStringId {
u4 stringDataOff; /* file offset to string_data_item */
};

DexStringId这个数据结构从70h处开始,所有被选中的都是DexStringId这种数据结构的内容,DexStringId代表的是字符串的位置偏移,每个DexStringId占用4个字节,也就是说它里面存的还不是真正的字符串,它们只是存储了真正字符串的偏移位置

如图7所示,根据header里提供的入口位置string_ids_size = 10h, string_ids_off = 70h,再加上偏移量1D2h在data区拿到的数据中,第一个字节0A表示的是字符串长度,后面跟着的48 65 6C 6C 6F 2E 6A 61 76 61 00才是字符串数据Hello.java的ASCII码表示

图7 根据string_ids找到对应字符串

string_ids的终极奥义就是找到这些字符串。其实使用二进制编辑器打开DEX文件时,一般工具默认翻译成ASCII码,总会一大片熟悉的字符很是晃眼。刚才走过的一路子分析流程,就是顺藤摸瓜找到它们是怎么来的。以后的一些type-ids, method_ids也会引用到这一片熟悉的字符串。

图8 DexStringId

3.4 Type Ids

type_ids表示的是类型标识符列表,这些是此文件引用的所有类型(类、数组或原始类型)的标识符(无论文件中是否已定义)。descriptorIdx指向string_ids中元素。根据索引直接在3.3节中读取字符串池即可解析对应的类型信息。

1
2
3
struct DexTypeId {
u4 descriptorIdx; /* index into stringIds list for type descriptor */
};

图9 DexTypeId

type_ids区索引了DEX文件里的所有数据类型,包括class类型,数组类型(array types)和基本类型。如图10所示,根据header里提供的入口位置type_ids_size = 7h, type_ids_off = B0h,找到class类型Hello的字符串描述

图10 根据type_ids找到类型

3.5 Proto Ids

proto_ids表示方法原型信息,这些是此文件引用的所有原型的标识符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct DexProtoId {
u4 shortyIdx; /* index into stringIds for shorty descriptor */
u4 returnTypeIdx; /* index into typeIds list for return type */
u4 parametersOff; /* file offset to type_list for parameter types */
};

struct DexTypeList {
u4 size; /* #of entries in list */
DexTypeItem list[1]; /* entries */
};

struct DexTypeItem {
u2 typeIdx; /* index into typeIds */
};

如图11所示,header中的proto_ids_size = 4h, proto_ids_off = CCh,然后根据数据结构定义,我们就能看到函数返回值,参数列表及类型等函数原型的标记

图11 根据proto_ids找到原型

最后,来个图总结一下原型的数据结构。

图12 DexProtoId

3.6 Field Ids

field_ids表示的是字段信息,指明了字段所在的类、字段的类型以及字段名称,在DexFile.h中定义为DexFieldId,其结构体定义如下:

1
2
3
4
5
struct DexFieldId {
u2 classIdx; /* index into typeIds list for defining class */
u2 typeIdx; /* index into typeIds for field type */
u4 nameIdx; /* index into stringIds for field name */
};

header里field_ids_size = 1h, field_ids_off = FCh,说明本DEX文件只有一个field,如图13所示。

图13 根据field_ids找到字段

同理,来个图总结一下field_ids的数据结构吧。

图14 DexFieldId

3.7 Method Ids

method_ids指明了方法所在的类、方法声明以及方法名,在DexFile.h中用DexMethodId表示该项,其结构体定义如下:

1
2
3
4
5
struct DexMethodId {
u2 classIdx; /* index into typeIds list for defining class */
u2 protoIdx; /* index into protoIds for method prototype */
u4 nameIdx; /* index into stringIds for method name */
};

header里method_ids_size = 5h, method_ids_off = 104h,说明本DEX文件有5个方法,方法int Hello.foo(int, int)在010 Editor中如图15所示。

图15 根据method_ids找到方法

其他4个方法的查找思路与上雷同,找个图来概况method_ids的数据结构吧。

图16 DexMethodId

3.8 Class Def

class_def是DEX文件结构中最复杂也是最核心的部分,它表示了类的所有信息,对应DexFile.h中的DexClassDef,结构体定义如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// DexFile.h
struct DexClassDef {
u4 classIdx; /* index into typeIds for this class */
u4 accessFlags;
u4 superclassIdx; /* index into typeIds for superclass */
u4 interfacesOff; /* file offset to DexTypeList */
u4 sourceFileIdx; /* index into stringIds for source file name */
u4 annotationsOff; /* file offset to annotations_directory_item */
u4 classDataOff; /* file offset to class_data_item */
u4 staticValuesOff; /* file offset to DexEncodedArray */
};

struct DexCode {
u2 registersSize;
u2 insSize;
u2 outsSize;
u2 triesSize;
u4 debugInfoOff; /* file offset to debug info stream */
u4 insnsSize; /* size of the insns array, in u2 units */
u2 insns[1];
/* followed by optional u2 padding */
/* followed by try_item[triesSize] */
/* followed by uleb128 handlersSize */
/* followed by catch_handler_item[handlersSize] */
};

重点是classDataOff这个字段,它包含了一个类的核心数据,在Android源码中定义为DexClassData,它不在DexFile.h中了,而是在DexClass.h中,结构体定义如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// DexClass.h
struct DexClassData {
DexClassDataHeader header;
DexField* staticFields;
DexField* instanceFields;
DexMethod* directMethods;
DexMethod* virtualMethods;
};

struct DexClassDataHeader {
u4 staticFieldsSize;
u4 instanceFieldsSize;
u4 directMethodsSize;
u4 virtualMethodsSize;
};

struct DexField {
u4 fieldIdx; /* index to a field_id_item */
u4 accessFlags;
};

struct DexMethod {
u4 methodIdx; /* index to a method_id_item */
u4 accessFlags;
u4 codeOff; /* file offset to a code_item */
};

header里class_defs_size = 1h, class_defs_off = 12Ch,说明本DEX文件有1个类,类public Hello在010 Editor中如图17所示。

图17 找到class_def的类信息

这下终于把最复杂的class_def分析结束,必须整个总结图来消化一下。

图18 DexClassDef

3.9 Map List

header中的mapOff指定了DexMapList的结构在Dex文件中的位置偏移290h,这个DexMapList结构是其他结构的一个数据大纲,里面记录了这些结构的一些信息,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// DexFile.h
struct DexMapList {
u4 size; /* #of entries in list */
DexMapItem list[1]; /* entries */
};

struct DexMapItem {
u2 type; /* type code (see kDexType* above) */
u2 unused;
u4 size; /* count of items of the indicated type */
u4 offset; /* file offset to the start of data */
};

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,
};

这个dex_map_list结构保存在DEX文件的最末尾处,并且第一个字节就是保存的结构数量Dh,也就是map_item的数量。

map_item结构如图19所示,单一结构长度位12个字节,其中前两个字节是描述对应的段类型1h紧跟着的2个字节是对齐字节0h,无意义。接下来的4个字节是对应的段大小1h最后的4个字节70h是对应的段偏移。

图19 找到map_list的信息

所有的结构都分析完毕,来个图总结一下map_list的数据结构吧。

图20 DexMapList

4 总结

最后,这三天的分析经历就像看《盗梦空间》似的,有时也不清楚自己在第几层了,心情跌宕起伏,不过最终完整地分析了一个DEX文件😄😄😄,再来个超大图总结一下DEX文件结构吧。

图21 DEX文件格式

5 参考文献

[1]丰生强. Android软件安全权威指南[M]. 电子工业出版社, 2019.
[2]https://android.googlesource.com/platform/dalvik/+/refs/tags/android-4.4.2_r2/libdex/DexFile.h
[3]https://android.googlesource.com/platform/dalvik/+/refs/tags/android-4.4.2_r2/libdex/DexClass.h
[4]https://source.android.com/docs/core/dalvik/dex-format?hl=zh-cn#proto-id-item
[5]https://ctf-wiki.org/android/basic_operating_mechanism/java_layer/dex/dex/
[6]https://blog.csdn.net/zlmm741/article/details/104706841
[7]https://www.kanxue.com/chm.htm?id=13691&pid=node1001568


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达,可以邮件至 xingshuaikun@163.com。

×

喜欢就点赞,疼爱就打赏