dex文件头解析需先读8字节魔数("dex\n035\0"或"dey\n035\0"),再读4字节校验和与4字节sha-1签名;版本号位于偏移0x8,常见为"035";所有索引表offset为文件绝对偏移,须校验越界;string_id中data_off指向uleb128编码的utf-8字符串长度,无结尾\0。

DEX文件头解析要先读魔数和版本号
DEX文件开头8字节是魔数("dex\n035\0"或"dey\n035\0"),紧接着4字节是校验和,再4字节是签名哈希。很多初学者直接用fread读结构体导致字节对齐错误,必须用uint8_t数组逐字节读,再按小端序手动拼出uint32_t字段。
关键点:
- 魔数必须严格匹配,
"dex\n035\0"对应标准DEX,"dey\n035\0"是优化版ODEX(但头部结构一致) - 版本号在偏移0x8处,目前常见为
"035"(即35),新版如039/045需注意string_ids_off等字段偏移变化 - 签名字段是SHA-1哈希值,不验证不影响解析,但可用于快速判断文件是否被篡改
如何安全读取string_ids、type_ids等索引区
DEX所有索引表都以uint32_t count + uint32_t offset成对出现,但offset是相对于文件起始的绝对偏移,不是相对section起始。容易踩坑的是:直接用reinterpret_cast强转指针到offset位置,而没检查offset是否越界或超出文件大小。
实操建议:
- 读
header.string_ids_off前先确认header.string_ids_size > 0,否则该区为空 - 每个
string_id_item含一个uint32_t偏移,指向data区的UTF-8字符串;这个偏移是相对于文件起始,不是data区起始 -
type_ids、proto_ids等表中的index字段,都是指向string_ids的下标,不是字符串内容本身
解析class_def_item时要注意字段顺序和间接引用
class_def_item本身不存类名、方法列表等数据,只存索引:比如class_idx指向type_ids表,interfaces_off指向data区的type_list结构。很多人误以为class_def_item里有方法定义,其实方法列表在data区,由class_data_item描述。
典型陷阱:
-
class_data_off为0表示该类无字段/方法(如接口或空类),不能跳过校验直接解引用 -
class_data_item开头是变长的uleb128编码,必须用标准ULEB128解码函数(不能用*(uint32_t*)ptr硬读) - 静态字段、实例字段、直接方法、虚方法四部分的count字段也可能为0,需分别判断
用C++读取DEX字符串和类名的实际代码片段
以下是最简可行的字符串提取逻辑(假设已加载整个DEX到const uint8_t* dex_data):
std::string get_string_by_idx(const uint8_t* dex_data, uint32_t idx) {
const auto& header = *(const DexHeader*)dex_data;
if (idx >= header.string_ids_size) return {};
const auto& string_id = ((const DexStringId*)(dex_data + header.string_ids_off))[idx];
uint32_t data_off = string_id.string_data_off;
if (data_off == 0) return {};
// data_off指向data区,首字节是ULEB128长度
const uint8_t* ptr = dex_data + data_off;
uint32_t len = read_uleb128(&ptr); // 自行实现read_uleb128
return std::string((const char*)ptr, len);
}
注意read_uleb128必须处理最多5字节,且ptr要传引用;返回的字符串不含结尾\0,DEX中字符串本身就是无终止符的UTF-8。
真正麻烦的从来不是读结构,而是data区里嵌套的encoded_method、code_item这些变长结构——它们依赖指令格式和寄存器映射,没完整反汇编器支持很难可靠还原逻辑。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











