dex文件解析

学壳先学结构捏,前面师傅丢了3篇,深度优先看了(

前置

JVM

JVM是Java Virtual Machine的简称,即Java虚拟机

DVM

DVM是Dalvik Virtual Machine的简称,是Android4.4及以前使用的虚拟机,所有Android程序都运行在Android系统进程中,每个进程对应着一个Dalvik虚拟机实例。

JVM和DVM都提供了对对象生命周期管理,堆栈管理,安全和异常管理及垃圾回收等重要功能。

但是DVM却不能和JVM一样能直接运行Java字节码,它只能运行.dex文件,而这个.dex文件则是由Java字节码通过Android的dx工具生成的文件。

ART

ART是Android Runtime,在Android5.0开始使用ART虚拟机来替代Dalvik虚拟机,为什么Google要换Android程序运行的虚拟机呢 因为ART虚拟机更优秀。

前面说了Dalvik虚拟机会在APP打开时去运行.dex文件,而这个是实时的,也就是JIT特性(Just In Time),这也就会导致在启动APP时会先将.dex文件转换成机器码,这就导致了APP启动慢的问题。

而ART虚拟机有个很好的特性叫做AOT(ahead of time),这个特性可以在安装APK的时候将dex直接处理成可直接供ART虚拟机使用的机器码,ART虚拟机将.dex文件转换成可直接运行的.oat文件,而且ART虚拟机天生支持多dex,所以ART虚拟机可以很大提升APP的冷启动速度。

除了这个优点外,ART还提升了GC速度,提供功能更全面的Debug特性,但是缺点也就是APK安装速度慢,占用的空间多。

来个一图流

img+

DEX文件头较简单,不涉及编码等,真好

同时还有源码看http://androidxref.com/9.0.0_r3/xref/dalvik/libdex/DexFile.h

010也有提供模板,看得也挺舒服的

在这里插入图片描述

DEX单独编译

其实比较简单

就是用Java8的jdk组件中的javac编译成.class,然后再用Android套件中的dx工具

中间采了个坑,安卓套件的路径有空格SB Windows一直没识别出来:

1
C:\Users\xxx\AppData\Local\Android\Sdk\build-tools\30.0.3\dx.bat --dex --output=H.dex .\HelloDex.class

这样就得到了一个dex

生成图

img

分层

image.png
img

DEX HEADER

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]; /*魔数字段,格式如"dex\n035\x00",其中035表示结构的版本*/
u4 checksum; /*校验码, 是小端序*/
u1 signature[kSHA1DigertLen]; /*SHA-1签名,长度20*/
u4 file_Size; /*Dex文件总长度*/
u4 header_Size; /*文件头长度,009版本=0x5C,035版本=0x70*/
u4 endian_Tag; /*标识字节顺序的常量,根据这个常量可以判断是否交换了字节顺序,缺省情况下=0x78563412*/
u4 link_Size; /*连接段大小,0表示静态链接*/
u4 link_Offset; /*连接段开始位置,从本文件头开始算起。 上为0此也为0*/
u4 map_Offset; /*map数据基地址*/
u4 string_ids_size; /*字符串列表字符串个数*/
u4 string_ids_off; /**/
u4 type_ids_size; /*类型列表里类型个数*/
u4 type_ids_offset; /**/
u4 proto_ids_size; /*原型列表里原型个数*/
u4 proto_ids_offset; /**/
u4 field_ids_size; /*字段列表里字段个数*/
u4 field_ids_offset; /**/
u4 method_ids_size; /*方法列表里方法个数*/
u4 method_ids_offset; /**/
u4 class_defs_size; /*类定义列表里类的个数*/
u4 class_defs_offset; /**/
u4 data_size; /*数据段大小,必须以4自己对齐*/
u4 data_offset; /**/
}

Dex里面很多数据是小端序。包括前面的0x78563412,如下图:

image-20250505180725654

Magic value

魔数字段,格式如"dex\n035\x00",其中035表示结构的版本

checksum

dex文件的校验和,它可以判断dex文件是否损坏或者篡改,占用4个字节,注意这里是采用小字节序的编码方式。

计算方式为去除 magicchecksum 以外的文件部分作 alder32 算法得到的校验值,用于判断 DEX 文件是否被篡改。

image-20250507153851094

signature

SHA-1签名,计算方式为去掉magic valuechecksumsignature的dex文件进行SHA-1计算,即去掉头0x20个字节

image-20250507153014577

fileSize

整个dex文件的大小

headerSize

头大小,一般来说0x70

endian_tag

用于标记 DEX 文件是大端表示还是小端表示。由于 DEX 文件是运行在 Android 系统中的,所以一般都是小端表示, 这里具体的值就2个,

表示字节序,,标准的.dex格式采用小段字节序,但具体实现可能会选择执行字节交换,所以这个改变就由这个tag来判断。

,这个值也是恒定值 0x12345678

2个字段指定了链接段的大小和文件偏移,linkSize为0表示为静态链接,此时LinkOff也是0。

map_off

这个字段表示DexMapList的文件偏移

string_ids_size & string_ids_off

这2个字段指定了dex文件中所有用到的字符串的个数和位置偏移,注意这里指的是位置偏移,而不是真正的字符串值。

偏移会指向string_id_list表,表里储存的是字符串的偏移地址。

type_ids_size & type_ids_off

类的类型的数量和位置偏移

偏移的位置指向一个type表,表的每个单元内容是前面string_id_list的下标,对应的字符串就是对应的type

proto_ids_size & proto_ids_off

方法原型的个数和位置偏移

对应的数据还是和type字符串类似

field_ids_size & field_ids_off

表示java文件中字段的信息的个数和位置偏移

method_ids_size & method_ids_off

dex文件中的方法个数和偏移

class_defs_Size & class_defs_off

类定义的


下面再看一次dex结构图

image.png

id区

id 区存储着字符串,type,prototype,field, method 资源的真正数据在文件中的偏移量,我们可以根据 id 区的偏移量去找到该 id 对应的真实数据

string_ids

这个区块是一个偏移量列表,每个偏移量对应了一个真正的字符串资源,每个偏移量占32位。我们可以通过偏移量找到对应的实际字符串数据。具体格式如下:

名称 格式 描述
string_data_off uint 从文件的开头到此项的字符串数据的偏移量
1
2
3
DexStringId : {
stringDataOff 字符串数据的起始偏移,u4
}

最终这个偏移的位置应该是落在数据区的。找到这个偏移量的位置后,根据下面的格式就可以读取出这个字符串资源的具体数据:

名称 格式 描述
utf16_size uleb16 字符串的解码长度,使用UTF-16编码(?真是utf16,吗,看起来像是单byte,但是010的模板说是utf16)
data ubyte[] 具体的字符串内容,len=utf16_size
1
2
3
4
stringData : {
size, 编码字符的个数
string 编码字符
}

type_ids

这个区块是一个索引列表,索引的值对应字符串id区域偏移量列表中的某一项。数据格式如下:

名称 格式 描述
descriptor_idx uint 这个类型的字符串描述在字符串id区域的索引
1
2
3
DexTypeId : {
DescriptorIdx DexTypeID 在String ID 中的索引
}

流程大概为:读取descriptor_idx,从string_ids中读取string_data_off,从data里面读取对应的string

下面是示例,后面的流程都相同,后面就不演示:

image-20250507220757655

这里是0x8,于是去string_ids中找:

image-20250507220935443

再去data找:

image-20250507221013166

proto_ids

这个区块是一个方法原型 id 列表,数据格式为:

名称 格式 描述
shorty_idx uint 一个字符串id区的索引,这个索引对应的字符串id列表项中的偏移量存储的字符串是这个方法原型的短格式描述符。
return_type_idx uint 这个方法原型的返回值类型在类型id列表中索引
parameters_off uint 这个方法原型的参数值列表类型数据的偏移量。0代表没有参数
1
2
3
4
5
6
7
8
9
10
11
12
DexProtoID : {
shortyIdx, u4,指向DexStringId的索引
returnTypeIdx, u4,指向DexTypeId的索引,返回类型
parametersOff u4,指向DexTypeList的索引
}

parametersOff --> DexTypeList : {
size, u4, 参数个数
DexTypeItem[] : {
idx u2,指向DexTypeId 的索引,参数类型
}
}
image-20250507221744040

field_ids

这个区块存储着原型 id 列表,数据格式为:

名称 格式 描述
class_idx ushort 这个成员所在的类在类型id列表中的索引
type_idx ushort 这个成员的类型在类型id列表中的索引
name_idx uint 这个成员的名字在字符串id列表的索引
1
2
3
4
5
DexFieldID : {
classIdx, u2, 类的类型,指向TypeID
typeIdx, u2, 字段类型,指向TypeID
nameIdx, u4, 名称,指向StringID
}

method_ids

这个区块存储着方法 id 列表,数据格式为:

名称 格式 描述
class_idx ushort 这个方法所在的类在类型id列表中的索引
proto_idx ushort 这个方法的原型在方法原型id列表中的索引
name_idx uint 这个方法的名字在字符串id列表的索引
1
2
3
4
5
DexMethodId : {
classIDx, u2,指向TypeId,类声明
protoIdx, u2,指向TypeId,原型声明
nameIdx u4,指向StringId,名字
}

class_ids

名称 格式 描述
class_idx uint 这个类在类型id列表中的索引
access_flags uint 这个类的访问标记(如:public,final等)
superClass_idx uint 这个类的父类在类型id列表中的索引,如果此类没有父类(即它是根类,例如 Object),该值为常量值 NO_INDEX
interfaces_off uint 这个类使用的接口列表在文件中的偏移量;
如果没有就为0,该偏移量(如果为非零值)应该位于 data 区段。
source_file_idx uint 这个类的源码文件的文件名称在字符串id列表中的索引
若该值为特殊值 NO_INDEX,以表示缺少这种信息。
annotations_off uint 这个类的注解数据在文件中的偏移量。
class_data_off uint 这个类的具体数据在文件中的偏移量
static_values_off uint 静态成员的初始值列表在文件中的偏移量
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
DexClassId : {
class_idx, 类的类型,u4,指向TypeId
access_flags, 访问标志,u4,具体值代表见下面解析
superclass_idx, 父类类型,u4,指向TypeId
interfaces_off, 接口偏移,u4,指向type_list偏移
source_file_idx, 源文件名,u4,指向stringId
annotations_off, 注解区偏移,u4,指向 annotations_directory_item
class_data_off, 类数据偏移,u4,指向 class_data_item
static_values_off 静态数据,u4,指向 encoded_array_item
}
------------------------------------------------------------------------
interfaces_off --> type_list : {
size, u4, 参数个数
type_item[size] : {
idx u2,指向DexTypeId 的索引,接口类型
}
}

annotations_off --> annotations_directory_item : {
class_annotations_off, u4,从文件开头到直接在该类上所做的注解的偏移量,应该在data区段
fields_size, u4,此项所注释的字段数量
annotated_methods_size, u4,此项所注释的方法数量
annotated_parameters_size u4,此项所注释的方法参数列表的数量
field_annotations list,len = fields_size,该列表中的元素必须按 field_idx 以升序进行排序
method_annotations list,len = annotated_methods_size,该列表中的元素必须按 method_idx 以升序进行排序。
parameter_annotations list,len = annotated_parameters_size,该列表中的元素必须按 method_idx 以升序进行排序。
}

class_data_off --> class_data_item : {
static_fields_size, uleb128, 定义的静态字段的数量
instance_fields_size, uleb128, 定义的实例字段的数量
direct_methods_size, uleb128, 定义的直接方法的数量
virtual_methods_size, uleb128, 定义的虚拟方法的数量
static_fields, list,定义的静态字段,这些字段必须按 field_idx 以升序进行排序。
instance_fields, list,定义的实例字段,这些字段必须按 field_idx 以升序进行排序。
direct_methods, list,定义的直接(static、private 或构造函数的任何一个)方法, 这些方法必须按 method_idx 以升序进行排序。
virtual_methods list,定义的虚拟(非 static、private 或构造函数)方法;此列表不得包括继承方法,除非被此项所表示的类覆盖。这些方法必须按 method_idx 以升序进行排序。虚拟方法的 method_idx 不得与任何直接方法相同。
}

static_values_off --> encoded_array_item : {
encoded_array : { encoded_value, 用于表示已编码数组值的字节
size, uleb128
values encoded_value[size]
}
}


access_flag

具体数值请参考相关定义

https://source.android.google.cn/docs/core/runtime/dex-format?hl=zh-cn#access-flags

LEB128:

("Little-Endian Base 128"), 表示任意有符号或无符号整数的可变长度编码.

uleb128中每个字节只有7位为有效位,如果第一个字节的最高位为1,表示LEB128需要使用第二个字节,如果第二个字节的最高位为1,表示会使用到第三个字节,以此类推,直到最后的字节最高位为0,当然LEB128最多使用到5个字节,如果读取5个字节后下一个字节最高位仍为1,则表示该Dex文件无效,Dalvik虚拟机遇到这种情况是直接报错。

EXP:

sleb128

LEB128编码格式中还有一种特殊的编码格式uleb128p1

这种编码格式的值为uleb128的值加上1。

通常将这个值转换为uleb128格式,然后这个值的基础上减去一,得到的值就是uleb128p1格式的值。

NO_INDEX:

https://source.android.google.cn/docs/core/runtime/dex-format?hl=zh-cn#no-index

常量 NO_INDEX 用于表示索引值不存在。

NO_INDEX 的选定值可表示为 uleb128p1 编码中的单个字节。

1
uint NO_INDEX = 0xffffffff;    // == -1 if treated as a signed int

参考资料

https://zhuanlan.zhihu.com/p/66800634

https://juejin.cn/post/7078164422761381918

https://blog.csdn.net/qq_41374107/article/details/104636659

https://tech.youzan.com/qian-tan-android-dexwen-jian/

https://chan-shaw.github.io/2020/03/17/DEX%E6%96%87%E4%BB%B6%E8%A7%A3%E6%9E%90/

https://source.android.google.cn/docs/core/runtime/dex-format?hl=zh-cn#class-def-item