apk本质是zip,但classes.dex不固定在根目录,可能重命名、嵌套或加密;解析dex头部须先用zip库定位条目数据起始偏移,再读取固定112字节并校验魔数“dex\n035\0”及小端序字段。

APK本质是ZIP,但classes.dex可能不在标准位置
APK 文件确实是 ZIP 格式,但 classes.dex 不一定在根目录下——它可能被重命名(如 classes2.dex、classes3.dex)、放在子目录(如 assets/ 或 lib/armeabi-v7a/ 中的 dex 伪装文件),甚至被加密或拆分。直接用 unzip -p app.apk classes.dex | head -c 112 很可能失败。
实操建议:
- 先用
zipinfo -l app.apk列出所有条目,过滤含.dex的路径:zipinfo -l app.apk | grep -i '\.dex$' - 注意大小写:某些打包工具生成
Classes.dex或CLASSES.DEX,ZIP 文件系统不区分大小写,但 Linux 下unzip默认严格匹配 - 若 ZIP 中无 .dex,检查是否使用了 Android App Bundle(.aab),此时需先用
bundletool解包
解析Dex头部必须跳过ZIP局部文件头
直接读取 APK 文件开头得到的是 ZIP 局部文件头(0x504B0304),不是 Dex 格式。Dex 文件若位于 ZIP 中,其内容前必然存在 ZIP 元数据(文件头 + 可选扩展 + 文件名 + 额外字段),长度可变。硬编码偏移(如从 offset 0x20 开始读)大概率错位。
正确做法是:用 ZIP 解析库定位目标 .dex 条目的数据起始偏移,再从此处开始解析 Dex 头部。
关键点:
- Dex 头部固定 0x70 字节(112 字节),魔数为
0x6465780A30333500(即 "dex\n035\0") - 不要用
fseek(fp, 0x20, SEEK_SET)—— 这只对未压缩、无额外字段的单文件 ZIP 有效,而现代 APK 几乎全不满足 - C++ 推荐用
minizip(zlib 附带)或libzip;避免手撕 ZIP 结构,尤其要处理extra field和 UTF-8 文件名标志位
用C++读取Dex头部字段需注意字节序和对齐
Dex 是小端序(little-endian),且头部结构体字段**无填充、自然对齐**(例如 uint32_t 在 offset 0x08,uint64_t 在 0x20)。C++ 结构体若直接 #pragma pack(1) 定义,仍可能因编译器对齐策略导致 sizeof ≠ 0x70。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
安全做法是逐字段读取并手动转换:
uint8_t header[0x70];
fread(header, 1, 0x70, fp);
// 检查魔数
if (memcmp(header, "dex\n035\0", 8) != 0) { /* error */ }
uint32_t checksum = le32toh(*reinterpret_cast<uint32_t>(header + 8));
uint64_t signature = le64toh(*reinterpret_cast<uint64_t>(header + 12));
uint32_t file_size = le32toh(*reinterpret_cast<uint32_t>(header + 32));
</uint32_t></uint64_t></uint32_t>
说明:
- 必须用
le32toh()/le64toh()(Linux/BSD)或__builtin_bswap32()(Clang/GCC),不能依赖平台原生ntohl(那是大端转主机) -
file_size是整个 dex 文件长度,不是头部长度;header_size固定为 0x70,但endian_tag字段(offset 0x28)若为0x12345678表示已交换字节序(极少见,仅用于调试) - Android 7.0+ 引入
compact_dex,头部魔数变为"cdex",需单独判断;普通解析工具会跳过
常见错误:混淆、加固、MultiDex导致解析失败
很多实际 APK 中的 .dex 并非原始格式:代码混淆(ProGuard/R8)不影响头部,但加固(360、腾讯乐固、梆梆)常修改魔数、校验和、甚至将 dex 加密后存为资源。MultiDex 场景下,主 dex(classes.dex)可能正常,而 classes2.dex 被动态加载或加密。
识别与应对:
- 读到魔数不是
"dex\n035\0"或"cdex\n006\0"?大概率被加密/损坏,先用file classes2.dex看类型,再尝试 hexdump 前 16 字节 - checksum 或 signature 字段全零?可能是加固壳清空校验字段,或运行时解密,此时需脱壳后再解析
- 用
readelf -h或file检查是否误把 so 文件当 dex(某些加固把 dex 打包进 so 的 .data 段) - 不建议在 C++ 中实现 dex 解密逻辑——复杂度高、易失效;优先用 Frida 动态 dump 或专用脱壳工具(如 DEXHunter)获取原始 dex
真正棘手的从来不是读 112 字节,而是确认你读的那 112 字节,确实属于一个未被篡改、可执行的 dex 文件头。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










