首页
社区
课程
招聘
[原创]DEX文件格式解析:深入Android字节码结构
发表于: 2025-12-20 17:49 88852

[原创]DEX文件格式解析:深入Android字节码结构

2025-12-20 17:49
88852

Dex(Dalvik Executable)是Android系统中Java字节码的优化格式,相比传统的class文件,Dex具有更高的执行效率和更小的文件体积。

Dex文件整体架构

Dex文件采用索引+数据的分离设计,所有索引区在前,实际数据在后。这种设计优化了内存访问和加载速度。

下面是解析Dex结构可能用到的数据类型

Android源码 定义了dex文件用到的数据结构

sleb128、uleb128、uleb128p1是Dex文件中特有的LEB128类型.在下述Android源码位置可以找到LEB128的实现.

sleb128:有符号LEB128

uleb128:无符号LEB128

uleb128p1:uleb128+1

每个LEB128由1-5字节组成,所有字节组合在一起表示一个32位的数据, 每个字节只有低7位为有效位,最高位标识是否需要使用额外字节

如果第1个字节的最高位为1,表示LEB128需要使用第2个字节,如果第2个字节的最高位为1,表示会使用第3个字节,依次类推,直到最后一个字节的最高位为0

uleb128读取代码如下

值得注意的是参数为二级指针,也就是说,调用该函数时会移动一级指针,一级指针的偏移量即为读取到的uleb128的大小

将LEB128编码的字节序列解码为32位无符号整数。

合并两个字节的操作十分巧妙:

encoded_value是Dex文件中用于存储常量值的通用编码格式。它可以表示各种类型的常量数据,如数字、字符串、类型引用等。

头字节解析(1字节)

value_type含义(低五位)

指定数据的类型格式:

value_arg函数(高3位)

根据类型不同,含义不同:

对于数值类型:value_arg = 字节数 - 1

对于布尔类型:value_arg直接表示值

对于索引类型(STRING、TYPE、FIELD、METHOD)

value_arg = 索引字节数 - 1

对于特殊类型(NULL,ARRAY等)

value_arg通常为0或有特殊含义

encoded_array是Dex文件中用于存储数组常量的数据结构,主要用于:

数据结构定义:

由于encoded_array.values数组元素为encoded_value,所以每个元素的大小不固定,不能当作一般的数组解析

encoded_annotation是Dex文件中用于存储注解实例的数据结构,它表示一个具体的注解及其参数值。

该类型主要在DexClassDef的Annotations部分使用,此处仅做介绍

注解是什么?

特点:

官方好像不支持头文件,所以我们需要手搓或者偷一个,这里直接去安卓源码copy一下

e1cK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6S2L8X3c8J5L8$3W2V1i4K6u0W2k6$3!0G2k6$3I4W2M7$3!0#2M7X3y4W2i4K6u0W2j5$3!0E0i4K6u0r3M7r3I4S2N6r3k6G2M7X3#2Q4x3V1k6V1j5h3I4$3K9h3E0Q4x3V1k6Q4x3V1u0Q4x3V1k6J5k6h3k6K6i4K6u0r3K9r3g2S2k6s2y4Q4x3V1k6E0j5i4y4@1k6i4u0Q4x3V1k6D9K9h3u0V1k6i4S2Q4x3V1k6p5k6i4S2r3K9h3I4W2i4K6u0W2K9l9`.`.

fc0K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6S2L8X3c8J5L8$3W2V1i4K6u0W2k6$3!0G2k6$3I4W2M7$3!0#2M7X3y4W2i4K6u0W2j5$3!0E0i4K6u0r3M7r3I4S2N6r3k6G2M7X3#2Q4x3V1k6S2M7Y4c8Q4x3V1k6Q4x3V1u0Q4x3V1k6J5k6h3k6K6i4K6u0r3K9r3g2S2k6s2y4Q4x3V1k6E0j5i4y4@1k6i4u0Q4x3V1k6D9K9h3u0V1k6i4S2X3K9h3I4W2i4K6u0r3k6r3g2^5i4K6u0r3k6r3g2^5i4K6g2X3k6X3W2D9k6g2)9J5k6h3R3`.

copy之后还需要修改,让ai来即可

Magic

魔数和版本

作用:标识文件类型和DEX格式版本

格式:

用途:快速识别文件是否为DEX文件,以及使用的格式版本

checksum校验和

作用:验证文件完整性

计算范围:从signature字段开始到文件末尾的所有数据

算法:Adler-32(比CRC32更快但稍弱的校验算法)

用途:检测文件是否被损坏或篡改

signature[20]-sha1签名

作用:文件的唯一标识和完整性验证

计算范围:从file_size字段开始到文件末尾

算法:SHA-1(160位/20字节)

用途:

filesize-文件大小

作用:记录DEX文件的总大小

用途:

Header_size-头部大小

作用:DEX头部结构的大小

固定值:0x70 (112字节)

用途:

endian_tag-字节序标记

作用:标识文件使用的字节序

标准值:0x12345678(小端序,Android标准)

用途:

link_size & link_off 链接段

作用:静态链接数据(很少使用)

通常值:都为0

用途:预留给静态链接的DEX文件使用(实际很少见)

map_off-映射表偏移

作用:指向DEX文件的映射表(map_list)

用途:

映射表具体指的是什么:映射表是Dex文件的完整目录

映射表在文件末尾(例子中在 0x00000b04),它会列出所有数据区:

假设要找字符串"hello world"

方法一:只用Header

方法二:用映射表

解析代码

string_ids_size & string_ids_off-字符串索引表

作用:管理DEX中所有的字符串

内容:类名、方法名、字段名、常量字符串等

结构:每个条目4字节,指向实际字符串数据

用途:

type_ids_size&type_ids_off-类型索引表

作用:存储所有类型描述符

内容:类类型、基本类型、数组类型等

格式:每个条目4字节,指向string_ids中的类型描述符

proto_ids_size & proto_ids_off-方法原型索引表

作用:存储方法签名(参数类型+返回类型)

内容:方法的参数列表和返回类型组合

结构:每个条目12字节

用途:

** field_ids_size & field_ids_off - 字段索引表**

作用:存储所有字段的引用

内容:所属类、字段类型、字段名

结构:每个条目8字节

用途:字段访问和引用

method_ids_size & method_ids_off - 方法索引表

作用:存储所有方法的引用

内容:所属类、方法原型、方法名

结构:每个条目8字节

用途:

class_defs_size & class_defs_off - 类定义表

作用:存储DEX中定义的所有类

内容:类的完整信息(字段、方法、访问标志等)

结构:每个条目32字节

用途:

data_size & data_off - 数据段

作用:存储实际的代码和数据

内容:

特点:通常占DEX文件的大部分空间

直接打印即可

实际字符串数据格式(在data区)

utf16_size:

数据类型:ULEB128变长编码
含义:字符串的UTF-16字符数量(不是字节数)
为什么用UTF-16长度:因为Java内部使用UTF-16编码,这个长度对应Java String的length()方法返回值
举例:字符串"Hello"的utf16_size = 5,中文"你好"的utf16_size = 2

data[]字段:

编码格式:MUTF-8 (Modified UTF-8)

结尾标记:必须以0x00字节结尾

长度:变长,实际字节数取决于字符内容和编码

什么是MUTF-8编码?

MUTF-8 (Modified UTF-8) 是Java虚拟机使用的一种UTF-8变体,与标准UTF-8有几个关键区别

需要注意MUTF-8编码和UTF-8编码不同的情况只有:

MUTF-8字符串头部保存的是字符串长度,是uleb128类型

二级索引结构

所以0x70就是string_id的起始位置,0x3C转换即为60,也就是string_id的数量

可以看到存储的字符串为"1.0"

我们看一下第一个字符串

可以看到string_data_off为0x542

03 31 2E 30 00

0x3表示长度为UTF-16字符长度为3

MUTF-8编码的字符串数据为"1.0"结尾为00

我们再以一个最长的为例子

首先第一个字节0x58即为后面data的长度88,由于结尾还有00,即为89

解析代码

辅助函数为:

辅助函数处理不同长度的data[]利用了c语言字符串以NULL结尾的特性。

作用:类型系统的核心

和string一样,读取的只是索引

所以我们直接读取字符串即可获取类型了

辅助函数

这个辅助函数其实还是调用get_string_by_idx函数,只需要传进去正确的索引即可

shorty_idx和return_type_idx其实我们已经知道是什么了,那么parameters_off呢?

参数列表结构:

其实这个还是指向type_ids

在我们逆向的时候,ProtoIDs就是我们见到的

对应的type_list

第一个在字符串的索引为9,第二个type_idx为0,那么就按照type_idx的解析方式解析,type_idx为0,对应的string为I

现在我们去找一下uint parameters_off值为0x52C

0x11也就是17,即

可以和上图对照验证

解析代码

辅助函数都是原来用过的,就不讲解了

存储所有字段的索引

对应java代码

这个比较简单,都是我们刚才学过的概念

存储所有方法的引用

类似

第二个字段proto_idx

解析代码

这是最复杂的部分

Class Defs(类定义)是DEX文件中本DEX定义的类的完整信息。

含义: 指向type_ids表,标识这个类的类型

示例: type_ids[5] → "Lcom/example/MainActivity;"

用途: 获取类的完整限定名,也就是类名

含义: 指向type_ids表,标识父类

特殊值: 0xFFFFFFFF (DEX_NO_INDEX) 表示没有父类

注意: 只有java.lang.Object没有父类

含义: 指向string_ids表,表示源文件名

示例: "MainActivity.java"

可选:可能为DEX_NO_INDEX(混淆或优化时)

<font style="color:rgb(255, 255, 255);">限定名限定名限定名</font>

含义: 描述类的访问权限和特性

可能的值为:

解析代码

含义: 指向type_list结构的偏移,包含实现的接口列表

值为0: 表示不实现任何接口

结构: 与方法参数的type_list相同格式

与其他结构的关系

解析代码

含义: 指向class_data_item结构

重要性: 包含类的字段和方法定义

值为0: 表示没有字段和方法(如接口的某些情况)

字段索引差值是什么

encoded_method比encoded_field多了一个code_off

内存布局示意图

解析代码

为什么传递二级指针呢?

C语言是值传递,在c语言中,函数参数都是复制一份副本传入的

一级指针的情况

二级指针


传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!

最后于 2026-8-3 10:23 被A0tem编辑 ,原因:
上传的附件:
收藏
免费 13
打赏
分享
最新回复 (6)
雪    币: 89
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
tql,学习
2025-12-21 02:01
1
雪    币: 6
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
感谢分享
2025-12-21 11:14
1
雪    币: 206
活跃值: (2748)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
感谢分享 ,学习 
2025-12-21 14:54
1
雪    币: 6516
活跃值: (11612)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
收藏
2025-12-22 09:29
0
雪    币: 112
活跃值: (9090)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
tql
2025-12-22 10:53
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
7
感谢大佬分享,欢迎回踩,相互学习
2025-12-25 17:03
0
游客
登录 | 注册 方可回帖
返回