dex结构

整个dex文件可以看作一个dex结构体的实例, 结构体使用 c 语言实现

dex结构体的源码位于

  • art/libdexfile/dex/dex_file.h(新目录)
  • dalvik/libdex/DexFile.h(较旧版本)

了解dex结构体是是否有必要的, 特别在加固与脱壳的领域

整体参考图(与现版本有些许差异)

对应

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct DexFile {
/* odex文件头 */
const DexOptHeader* pOptHeader;

/* 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;

/*辅助数据段,记录dex文件被优化后添加的一些信息*/
const DexClassLookup* pClassLookup;
const void* pRegisterMapPool;
const u1* baseAddr;
int overhead;
//void* auxData;
};

使用工具010 editor的模板功能, 能够十分有效的帮助我们学习dex结构体

使用以下源码得到的dex文件作为学习案例

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
public class Hello {

private int number;

public Hello(int number) {
this.number = number;
}

public int getNumber() {
return number;
}

public void setNumber(int number) {
this.number = number;
}

public static int add(int a, int b) {
return a + b;
}

public static void main(String[] args) {
Hello ahello = new Hello(10);
int result = add(5, 3);
System.out.println("The result of addition is: " + result);
System.out.println("The number in the object is: " + ahello.getNumber());
}
}

通过以下指令得到目标文件

1
2
javac ./Hello.java
AndroidSDKVersion/d8.bat --ouput . .\Hello.class

使用010editor打开

leb128

为了节省内存dex文件中会对部分整数使用可变长度编码

名称 说明
sleb128 有符号 LEB128,可变长度(见下文)
uleb128 无符号 LEB128,可变长度(见下文)
uleb128p1 无符号 LEB128 加 1,可变长度(见下文)

每个 LEB128 编码值均由 1-5 个字节组成,共同表示一个 32 位的值。每个字节均已设置其最高有效位(序列中的最后一个字节除外,其最高有效位已清除)。每个字节的剩余 7 位均为载荷,即第一个字节中有 7 个最低有效位,第二个字节中也是 7 个,依此类推。

对于有符号 LEB128 (sleb128),序列中最后一个字节的最高有效载荷位会进行符号扩展,以生成最终值。在无符号情况 (uleb128) 下,任何未明确表示的位都会被解译为 0

变体 uleb128p1 用于表示一个有符号值,其表示法是编码为 uleb128 的值加 1。这使得 -1 的编码(或被视为无符号值 0xffffffff)成为一个单字节(但没有任何其他负数),并且该编码在下面这些情况下非常实用:所表示的数值必须为非负数或 -1(或 0xffffffff);不允许任何其他负值(或不太可能需要使用较大的无符号值)。

编码序列 As sleb128 As uleb128 编码为 uleb128p1
00 0 0 -1
01 1 1 0
7f -1 127 126
80 7f -128 16256 16255

Header

header用于描述dex文件

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
34
35
36
/**************************************************** 
dex文件头
****************************************************/
struct DexHeader {
u1 magic[8]; /* dex版本标识 dex.035=>64 65 78 0a 30 33 35 00 */
u4 checksum; /* adler32 校验 checksum段为dex文件的校验和,通过它来判断dex文件是否被损坏或篡改 */
u1 signature[20]; /* SHA-1 hash */
u4 fileSize; /* 整个文件大小 */
u4 headerSize; /* DexHeader结构大小 0x70 */
u4 endianTag; /* 字节序标记 */
u4 linkSize; /* 链接段大小 */
u4 linkOff; /* 链接段偏移 */

u4 mapOff; /* DexMapList的文件偏移 */

u4 stringIdsSize; /* DexStringId的个数 */
u4 stringIdsOff; /* DexStringId的文件偏移 */

u4 typeIdsSize; /* DexTypeId的个数 */
u4 typeIdsOff; /* DexTypeId的文件偏移 */

u4 protoIdsSize; /* DexProtoId的个数 */
u4 protoIdsOff; /* DexProtoId的文件偏移 */

u4 fieldIdsSize; /* DexFieldId的个数 */
u4 fieldIdsOff; /* DexFieldId的文件偏移 */

u4 methodIdsSize; /* DexMethodId的个数 */
u4 methodIdsOff; /* DexMethodId的文件偏移 */

u4 classDefsSize; /* DexClassDef的个数 */
u4 classDefsOff; /* DexClassDef的文件偏移 */

u4 dataSize; /* 数据段的大小 */
u4 dataOff; /* 数据段的文件偏移 */
};

magic

magic字段格式一般是

1
b'dex\x0a035\x00'

其中035会随着dex文件版本发生变化

例如也有可能是

1
b'dex\x0a038\x00'

checksum

用于校验dex完整性

signature

20字节的SHA-1哈希

file_size

整个文件的大小

header_size

头部大小

endian_tag

标识字节序

  • 小端序0x78563412
  • 大端序0x12345678

以上这些在案例中的实例

link

link_sizelink_off用于标明链接段

大多数时候皆为空(例如案例中), 不作多关注

map_off

DexMapList的偏移

DexMapList描述了整个dex文件的布局

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
34
35
struct DexMapList {
u4 size; /* DexMapItem的个数 */
DexMapItem list[1]; /* DexMapItem结构 */
};

struct DexMapItem {
u2 type; /* kDexType开头的类型 */
u2 unused; /* 未使用,用于字节对齐 */
u4 size; /* DexMapItem中的size字段指定了特定类型的个数,它们以特定的类型在dex文件中连续存放 */
u4 offset; /* 指定类型数据的文件偏移 offset为该类型的文件起始偏移地址*/
};

/* DexMapItem的类型(type) */
enum {
kDexTypeHeaderItem = 0x0000, //模式整个DexHeader结构,它占用了文件的前0x70个字节的空间
kDexTypeStringIdItem = 0x0001, //stringIdsSize stringIdsSize
kDexTypeTypeIdItem = 0x0002, //typeIdsSize typeIdsOff
kDexTypeProtoIdItem = 0x0003, //protoIdsSize protoIdsOff
kDexTypeFieldIdItem = 0x0004, //fieldIdsSize fieldIdsSize
kDexTypeMethodIdItem = 0x0005, //methodIdsSize methodIdsOff
kDexTypeClassDefItem = 0x0006, //classDefsSize classDefsOff 指向的结构体为DexClassDef

kDexTypeMapList = 0x1000, //DexMapItem结构
kDexTypeTypeList = 0x1001, //DexTypeList结构
kDexTypeAnnotationSetRefList = 0x1002,
kDexTypeAnnotationSetItem = 0x1003,

kDexTypeClassDataItem = 0x2000, //DexClassData结构
kDexTypeCodeItem = 0x2001, //DexCode结构
kDexTypeStringDataItem = 0x2002, //指向DexStringId字符串列表的首地址
kDexTypeDebugInfoItem = 0x2003, //调式信息偏移
kDexTypeAnnotationItem = 0x2004,
kDexTypeEncodedArrayItem = 0x2005,
kDexTypeAnnotationsDirectoryItem = 0x2006,
};

string_id

string_idx_size

描述DexStringId的个数

string_idx_off

DexStringId的偏移

1
2
3
struct DexStringId {
u4 stringDataOff; /* 字符串数据偏移 */
};

也就是黄色的这部分

其描述的是一个stringData的偏移

例如第一个偏移是0x332

对应内容是

代表该字符串共有6个字符(注意并不是6个字节), 其中不包括结尾的\x00

所有的字符串按顺序排列生成一个索引

image-20250305142952451

type_id

type_ids_size

描述DexTypeId的个数

type_ids_off

指向类型描述符id的偏移

1
2
3
4
struct DexTypeId {
u4 descriptorIdx; /* 指向DexStringId列表的索引 */
};

内容如下

1
2
3
02 00 00 00 05 00 00 00 08 00 00 00 09 00 00 00
0A 00 00 00 0B 00 00 00 0C 00 00 00 0F 00 00 00
12 00 00 00

数字代表的就是字符串的索引

例如

02 00 00 00就代表索引2的字符串也就是I, int型

image-20250305143520029

proto_id

proto_ids_size

描述proto_id的个数

proto_ids_off

指向proto_ids的偏移

每一个proto_id的定义如下,用于描述一个方法原型

1
2
3
4
5
 struct DexProtoId{ 
u4 shortyIdx; /*指向DexStringId列表的索引*/
u4 returnTypeIdx; /*指向DexTypeId列表的索引*/
u4 parametersOff; /*指向DexTypeList的位置偏移*/
}
  1. 第一个字段是一个索引, 对应字符串中一个能描述方法的字符串

    例如III表示int (int, int)

  2. 第二个字段就是返回值类型的类型索引

  3. 第三个字段是指向一个DexTypeList的偏移

    1
    2
    3
    4
    5
    6
    7
    8
    struct DexTypeList {
    u4 size; /* 接下来DexTypeItem的个数 */
    DexTypeItem list[size]; /* DexTypeItem结构 */
    };

    struct DexTypeItem {
    u2 typeIdx; /* 指向DexTypeId列表的索引 */
    };

    例如02 00 00 00 00 00 00 00代表

    共有两个参数, 都位于类型索引表的0处, 也就是int, int

field_id

field_ids_size

描述DexFieldId的个数

field_ids_off

描述DexFieldId所在偏移

1
2
3
4
5
struct DexFieldId {
u2 classIdx; /* 类的类型,指向DexTypeId列表的索引 */
u2 typeIdx; /* 字段类型,指向DexTypeId列表的索引 */
u4 nameIdx; /* 字段名,指向DexStringId列表的索引 */
};
  1. 第一个字段代表所属类
  2. 第二个字段代表类型
  3. 第三个字段对应字符串表中的索引, 是字段的名称

例如01 00 00 00 17 00 00 00代表Hello.number字段, 其是一个int

image-20250305145055947

method_id

method_ids_size

method_id的个数

method_ids_off

所在偏移

1
2
3
4
5
struct DexMethodId {
u2 classIdx; /* 类的类型,指向DexTypeId列表的索引 */
u2 protoIdx; /* 声明类型,指向DexProtoId列表的索引 */
u4 nameIdx; /* 方法名,指向DexStringId列表的索引 */
};
  1. 第一个字段代表所属类索引
  2. 第二个字段代表方法原型声明索引
  3. 第三个字段代表方法名字

class_def

整个dex最复杂也是最关键的地方

class_defs_size

描述DexClassDef的个数

class_defs_off

DexClassDef结构所在偏移

1
2
3
4
5
6
7
8
9
10
struct DexClassDef {
u4 classIdx; /* 类的类型,指向DexTypeId列表的索引 */
u4 accessFlags; /* accessFlags是类的访问标志,以ACC_开头的一个枚举值 */
u4 superclassIdx; /* 父类类型,指向DexTypeId列表的索引 */
u4 interfacesOff; /* 接口,指向DexTypeList的偏移 */
u4 sourceFileIdx; /* 源文件名,指向DexStringId列表的索引 */
u4 annotationsOff; /* 注解,指向DexAnnotationsDirectoryItem结构 annotationsOff字段指向注解目录结构,根据类型不同会有注解类、注解方法、注解字段与注解参数*/
u4 classDataOff; /* 指向DexClassData结构的偏移 classDataOff字段是类的数据部分*/
u4 staticValuesOff; /* 指向DexEncodedArray结构的偏移 staticValuesOff字段记录类中的静态数据*/
};

访问标志枚举

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
/* accessFlags 访问标志 */
enum {
ACC_PUBLIC = 0x00000001, // class, field, method, ic
ACC_PRIVATE = 0x00000002, // field, method, ic
ACC_PROTECTED = 0x00000004, // field, method, ic
ACC_STATIC = 0x00000008, // field, method, ic
ACC_FINAL = 0x00000010, // class, field, method, ic
ACC_SYNCHRONIZED = 0x00000020, // method (only allowed on natives)
ACC_SUPER = 0x00000020, // class (not used in Dalvik)
ACC_VOLATILE = 0x00000040, // field
ACC_BRIDGE = 0x00000040, // method (1.5)
ACC_TRANSIENT = 0x00000080, // field
ACC_VARARGS = 0x00000080, // method (1.5)
ACC_NATIVE = 0x00000100, // method
ACC_INTERFACE = 0x00000200, // class, ic
ACC_ABSTRACT = 0x00000400, // class, method, ic
ACC_STRICT = 0x00000800, // method
ACC_SYNTHETIC = 0x00001000, // field, method, ic
ACC_ANNOTATION = 0x00002000, // class, ic (1.5)
ACC_ENUM = 0x00004000, // class, field, ic (1.5)
ACC_CONSTRUCTOR = 0x00010000, // method (Dalvik only)
ACC_DECLARED_SYNCHRONIZED =0x00020000, // method (Dalvik only)
ACC_CLASS_MASK =(ACC_PUBLIC | ACC_FINAL | ACC_INTERFACE | ACC_ABSTRACT| ACC_SYNTHETIC | ACC_ANNOTATION | ACC_ENUM),
ACC_INNER_CLASS_MASK =(ACC_CLASS_MASK | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC),
ACC_FIELD_MASK =(ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC | ACC_FINAL| ACC_VOLATILE | ACC_TRANSIENT | ACC_SYNTHETIC | ACC_ENUM),
ACC_METHOD_MASK =(ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC | ACC_FINAL | ACC_SYNCHRONIZED | ACC_BRIDGE | ACC_VARARGS | ACC_NATIVE | ACC_ABSTRACT | ACC_STRICT | ACC_SYNTHETIC | ACC_CONSTRUCTOR| ACC_DECLARED_SYNCHRONIZED),
};

annotations

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
struct DexAnnotationSetItem {
u4 size;
u4 entries[1]; /* 指向DexAnnotationItem结构 */
};

struct DexFieldAnnotationsItem {
u4 fieldIdx;
u4 annotationsOff; /* 指向DexAnnotationSetItem结构 */
};

struct DexMethodAnnotationsItem {
u4 methodIdx;
u4 annotationsOff; /* 指向DexAnnotationSetItem结构 */
};

struct DexParameterAnnotationsItem {
u4 methodIdx;
u4 annotationsOff; /* 指向DexAnotationSetRefList结构 */
};

struct DexAnnotationSetRefList {
u4 size;
DexAnnotationSetRefItem list[1];
};

struct DexAnnotationSetRefItem {
u4 annotationsOff; /* 指向DexAnnotationSetItem结构 */
};

struct DexAnnotationItem {
u1 visibility;
u1 annotation[1];
};

enum {
kDexVisibilityBuild = 0x00,
kDexVisibilityRuntime = 0x01,
kDexVisibilitySystem = 0x02,

kDexAnnotationByte = 0x00,
kDexAnnotationShort = 0x02,
kDexAnnotationChar = 0x03,
kDexAnnotationInt = 0x04,
kDexAnnotationLong = 0x06,
kDexAnnotationFloat = 0x10,
kDexAnnotationDouble = 0x11,
kDexAnnotationString = 0x17,
kDexAnnotationType = 0x18,
kDexAnnotationField = 0x19,
kDexAnnotationMethod = 0x1a,
kDexAnnotationEnum = 0x1b,
kDexAnnotationArray = 0x1c,
kDexAnnotationAnnotation = 0x1d,
kDexAnnotationNull = 0x1e,
kDexAnnotationBoolean = 0x1f,

kDexAnnotationValueTypeMask = 0x1f,
kDexAnnotationValueArgShift = 5,
};

class_data

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/* classDataOff */
struct DexClassData {
DexClassDataHeader header; //指定字段与方法的个数
DexField* staticFields; //静态字段,DexField结构
DexField* instanceFields;//实例字段,DexField结构
DexMethod* directMethods; //直接方法,DexMethod结构
DexMethod* virtualMethods;//虚方法,DexMethod结构
};

/* 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结构的偏移 */
};

struct DexCode {
u2 registersSize; /* 使用的寄存器个数 .register*/
u2 insSize; /* 参数个数 .paramter*/
u2 outsSize; /* 调用其他方法时使用的寄存器个数 outsSize指定方法调用外部方法时使用的寄存器个数*/
u2 triesSize; /* try/catch个数 */
u4 debugInfoOff; /* 指向调式信息的偏移 */
u4 insnsSize; /* 指令集个数,以2字节为单位 */
u2 insns[1]; /* 指令集 */
/* 2字节空间用于结构对齐 */
/* try_item[triesSize] DexTry结构 */
/* try/catch中handler的个数 */
/* catch_handler_item[handlersSize] DexCatchHandler结构 */
};

struct DexTry {
u4 startAddr;
u2 insnCount;
u2 handlerOff;
};

staticValues

1
2
3
struct DexEncodedArray {
u1 array[1];
};
1
2
3
4
5
6
7
8
9
10
11
// 定义 Encoded Value 结构体
typedef struct {
ubyte value_type;
ubyte value[value_type_size(value_type)];
} EncodedValue;

// 定义 Encoded Array 结构体
typedef struct {
uleb128 size;
EncodedValue values[size];
} EncodedArray;

data

data_size

数据段大小

data_off

数据段偏移

参考

Dalvik 可执行文件格式 | Android Open Source Project

dex_file.h - Android Code Search