loading

dex文件解析

  • Home
  • Blog
  • dex文件解析

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安装速度慢,占用的空间多。

来个一图流

+

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

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

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

在这里插入图片描述

DEX单独编译

其实比较简单

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

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

1C:\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

12345678910111213141516171819202122232425struct 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个字节,注意这里是采用小字节序的编码方式。

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

image-20250507153851094

signature

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

image-20250507153014577

fileSize

整个dex文件的大小

headerSize

头大小,一般来说0x70

endian_tag

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

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

,这个值也是恒定值 0x12345678。

link_size & link_off

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

从文件的开头到此项的字符串数据的偏移量

123DexStringId : { stringDataOff 字符串数据的起始偏移,u4}

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

名称

格式

描述

utf16_size

uleb16

字符串的解码长度,使用UTF-16编码(?真是utf16,吗,看起来像是单byte,但是010的模板说是utf16)

data

ubyte[]

具体的字符串内容,len=utf16_size

1234stringData : { size, 编码字符的个数 string 编码字符}

type_ids

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

名称

格式

描述

descriptor_idx

uint

这个类型的字符串描述在字符串id区域的索引

123DexTypeId : { 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代表没有参数

123456789101112DexProtoID : { 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列表的索引

12345DexFieldID : { classIdx, u2, 类的类型,指向TypeID typeIdx, u2, 字段类型,指向TypeID nameIdx, u4, 名称,指向StringID}

method_ids

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

名称

格式

描述

class_idx

ushort

这个方法所在的类在类型id列表中的索引

proto_idx

ushort

这个方法的原型在方法原型id列表中的索引

name_idx

uint

这个方法的名字在字符串id列表的索引

12345DexMethodId : { 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

静态成员的初始值列表在文件中的偏移量

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647DexClassId : { 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 编码中的单个字节。

1uint 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