ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

vdexExtractor 完全指南:从 VDEX 还原 DEX 的实战与踩坑记录

vdexExtractor 完全指南:从 VDEX 还原 DEX 的实战与踩坑记录 我这两年经常跟系统固件和逆向分析打交道碰到最多的情况之一就是从设备里拉出来的应用数据目录里没有 dex 文件只有一堆 vdex。第一次遇到的时候我也愣了一下后来用 vdexExtractor 把 vdex 还原成原始的 dex 文件整个分析链路才重新走通。这篇文章就把 vdexExtractor 的完整使用过程、原理和踩坑记录整理出来给同样被这个环节卡住的朋友做个参考。1. 先搞清楚 VDEX 是什么为什么非得还原成 DEX1.1 从 APK 到 DEX 再到 VDEX 的编译链路Android 应用打包之后核心代码都在 classes.dex 里。普通情况下我们可以直接解压 APK 拿到 dex拖进 jadx 或 jeb 里看逻辑。但在 Android 8.0 之后系统引入了 ART 编译链路的优化安装应用或系统预编译时会生成 vdex 文件。vdex 的全称是 Verified DEX它的设计初衷是把校验verification和去重deduplication的结果缓存下来这样应用启动时不用再重新对 dex 做一遍校验。简单理解vdex 是一个容器里面包含了原始 dex 文件的内容但它的文件头、索引结构、验证状态数据都跟纯 dex 不一样所以直接改后缀或者用普通反编译工具去打开通常会失败。这个设计本身是为了提升系统流畅度但对于做安全研究、恶意样本分析、系统裁剪的人来说意味着我们不能像以前那样随手拖一个 dex 就开工了。vdex 就像是一个被加了层包装的礼品礼品还是那个礼品但你得先把包装拆开。1.2 什么场景下必须把 VDEX 还原成 DEX现实工作中至少有三类场景绕不开这一步。第一种是做系统级应用分析。厂商把某些系统应用直接预编译进 system 分区APK 里可能只剩资源文件和老旧的 dex真正的代码逻辑被 ART 提取到了 /system/framework 或 /system/priv-app 下的 oat/vdex 文件里。分析这类应用时拿不到原始 dex 等于啥都没看到。第二种是抓取内存中的类加载数据。有些加固方案会把类加载放到运行时动态进行但系统为了性能仍然会在 vdex 里保留一部分原始输入的副本。把这类 vdex 还原出来偶尔能直接看到明文 dex省掉脱壳的复杂操作对恶意样本分析来说价值很大。第三种是搞 ROM 定制或者做系统镜像裁剪的工程师。想移除某个内置应用但仍然保留系统启动能力或者想了解系统进程到底加载了哪些代码同样得先把 vdex 还原成 dex才能用 jadx 全文检索关键字。1.3 工具链全貌不只一个 vdexExtractorvdexExtractor 是目前最常见的开源工具但它不是唯一的东西。完整的工具链里通常还包括vdexExtractor负责解析 vdex 容器把里面的 dex 提取出来dexdump检查还原后的 dex 是否完整、头部是否有问题baksmali把 dex 转成 smali 汇编方便细粒度阅读和修改jadx / jadx-gui将 dex 还原为 Java 代码适合业务逻辑理解apktool处理 APK 资源和 smali 干扰在部分场景下会配合使用。只靠 vdexExtractor 拿到 dex 还不算完后面是否能用 jadx 打开、函数名是否完整、类结构有没有被截断才是真正影响效率的地方。2. 环境准备与 vdexExtractor 编译2.1 源码下载与依赖整理vdexExtractor 是开源项目托管在 GitHub 上仓库地址很直观搜索 vdexExtractor 就能找到。下载源码时建议直接拉最新 release 版本不要用 master 分支上那种实验性提交因为 vdexExtractor 对 Android 不同版本的兼容性差异很大实验性代码可能只适配了特定 API level。下载命令很简单git clone https://github.com/anestisb/vdexExtractor.git cd vdexExtractor编译之前先确认系统里装了几个基础依赖make、gcc、g 等基础编译工具或者 Android NDK因为部分组件需要用 NDK 交叉编译到 ARM 平台直接放到设备上跑。我自己的习惯是如果只处理本机上的 vdex 文件就直接在 x86 Linux 环境下编译本地工具如果需要在 Android 设备上直接解析 /data/data 下的 vdex再编一份 ARM64 版本推送到设备上。2.2 编译流程与参数选择项目里提供了现成的 Makefile如果只是编译本地工具./makefile或者make编译完成后会在当前目录下生成 vdexExtractor 可执行文件。如果想要生成 Android 设备上运行的版本需要先设置 NDK 交叉编译环境export NDK_HOME/path/to/your/ndk ./makefile ndk编译时注意两点。第一vdexExtractor 的具体版本决定了它支持哪些 Android 版本有些老版本对 Android 10 之后的 vdex 格式支持并不好。第二编译选项里默认会包含 oat2dex 相关组件这个组件会进一步处理 oat 文件里的 dex 缓存如果你的需求只是提取 vdex 中的 dex那可以不关注这部分但如果后面发现提取出来的 dex 不可用oat2dex 反而是救命的工具。2.3 按 Android 版本选择对应模块vdexExtractor 内部有多个模块不同 Android 版本的 vdex 格式差异很大Android 8.0/8.1vdex 里有完整的 dex有 checksum结构相对简单Android 9dex 和验证信息同时存在vdex 格式略有变化Android 10 及以后vdex 里的 dex 可能不是完整副本而是 quickened dex需要更进一步处理。如果不确定当前环境是哪个版本可以在设备上先用getprop ro.build.version.sdk查一下 API level再对照工具文档选择对应参数。这一步看似多余但能避免后面解析时报一堆莫名其妙的 invalid header 错误。3. 实操把 VDEX 批量还原为 DEX3.1 从系统镜像中定位 VDEX 文件vdex 文件的存放位置在不同场景下不一样。我总结了一下主要的几个路径系统预编译应用/system/framework/、/system/priv-app/包名/、/system/app/包名/用户安装应用/data/app/包名/目录下通常以base.vdex或split_config.架构.vdex形式存在OTA 升级包内在system/framework/或system/app/相关目录下与 boot.oat 一起出现如果你想抓取自己设备上的 vdexadb root 之后直接用adb pull /system/framework/arm64/boot-framework.vdex ./ adb pull /data/app/com.example.demo-xxx/base.vdex ./如果没有 root这部分基本拿不到但可以退而求其次去 OTA 包或者官方刷机镜像里提取把system.img解包后照样能拿到。解包 system.img 推荐用 simg2img 加 ext4 分区挂载或者用现成的工具比如 7-Zip 配合分区处理插件都能做到。3.2 使用 vdexExtractor 执行转换拿到 vdex 文件之后第一步是确认工具能识别它。直接在命令行执行./vdexExtractor -i input.vdex工具会自动打印 vdex 的版本信息、包含的 dex 数量、每个 dex 的 checksum 和是否包含 quickened 信息。正常情况下输出里会看到类似dex [0] checksum: 0xabcdef12的内容这代表解析成功。然后通过-o参数指定输出目录并加上--disassemble这类选项./vdexExtractor -i input.vdex -o output_dir --disassemble --ignore-error参数说明如下-i输入文件路径-o输出目录--disassemble对 dex 做脱优化处理de-optimize把 quickened 指令还原为普通 dex 指令--ignore-error遇到单个 dex 出错时继续处理其他 dex批量操作时建议加上--input-list可以用一个文本文件指定多个 vdex 路径适合批量场景。如果只想提取原始 dex不还原 quickened 指令可以不加--disassemble但这样拿到的 dex 很可能在 jadx 里无法正常阅读函数体都是空壳或者乱码。我建议默认加上这个参数。批量处理整个目录时可以写一个简单脚本for f in /path/to/vdex_dir/*.vdex; do ./vdexExtractor -i $f -o /path/to/output --disassemble --ignore-error done跑完之后输出目录里会出现以原始文件命名的 dex 文件文件名通常会追加 dex 序号。比如input.vdex_dex0.dex、input.vdex_dex1.dex。3.3 校验还原结果是否可用提取只是第一步还原出的 dex 是否能被反编译工具正确解析才是关键。先用 dexdump 做一个快速健康检查dexdump -d output_dir/input.vdex_dex0.dex | head -50如果能看到Class descriptor和Method信息就说明 dex 头部解析正常。然后直接拖进 jadx-guijadx-gui output_dir/input.vdex_dex0.dex重点检查这几个地方类列表是否完整有没有大量空类方法体是否能看到 Java 伪代码而不是一堆throw new RuntimeException(Stub!);字符串和常量池有没有被抽空。如果 jadx 能直接打开并且能看到完整代码逻辑说明还原成功。如果只有类名没有方法体大概率是 --disassemble 没开或者 vdexExtractor 版本不支持当前 Android 版本的 quickened 指令还原需要按第 4 部分的思路处理。3.4 配合反编译工具继续分析拿到可用的 dex 后后续分析路线就平滑了。通常我的工作流是用 jadx 建立整个项目的索引先看一眼包的层级结构和主要入口类。如果遇到 jadx 识别不了的混淆代码再退回到 smali 层面用 baksmali 把 dex 拆开逐段读指令这样即使类名和方法名被混淆也能从 smali 的 register 使用流程反推逻辑。如果是从恶意样本里提取的 dex还要额外注意资源混淆和字符串加密。vdexExtractor 还原出来的是系统编译时的 dex 快照并不代表应用运行时看到的字符串就是明文很多样本会把字符串做一次运行时解密。遇到这种情况建议在 jadx 里全局搜索decrypt、dec、loadClass之类关键词找到解密函数然后手动跑一遍对应的算法把字符串还原出来再读代码。比如某次分析一个内置恶意功能的系统应用vdex 还原出来的 dex 里所有 URL 和 C2 地址都是密文字符串的长度和特征像 AES 或者 DES 加盐。我直接用 jadx 打开后只看静态代码找不到明文后来在onCreate里发现一个initSecretKey方法动态解密后才挖到真正的地址。这个经验尤其适用于厂商内置应用分析。4. 常见问题与排查经验4.1 vdexExtractor 版本与 Android 版本不匹配这是最常见的坑。用旧版工具处理 Android 12 之后的 vdex经常出现 not a valid vdex file 或者 unsupported version: 006 之类的错误。原因在于 vdex 文件头里有版本号字段不同 Android 版本会用到不同的 vdex 版本号。比如 Android 8.x 是 004Android 9 是 006Android 10 之后会继续升版本。旧工具对新高版本号没有对应解析逻辑。排查方法很简单用十六进制编辑器打开 vdex 文件看文件头的 6 到 10 字节就能确认版本号。然后对照 vdexExtractor 的 release notes确认你使用的工具版本是否支持该版本。如果不支持优先升级工具。提示真实设备上的 vdex 版本号不一定和系统版本严格对应厂商可能修改过 ART 参数所以不要只看 Android 大版本而是以实际文件头里的版本号为准。4.2 提示无法解析 VDEX 文件头即使版本号匹配也会遇到 invalid dex size 或 header check failed。大部分情况是因为 vdex 文件本身不完整。有些分析场景里我们通过内存 dump 或者文件碎片恢复拿到了 vdex但尾部校验区被截断了。vdexExtractor 在解析时会对 dex 大小做校验一旦大小超过实际文件长度就会报错。这时候可以用--ignore-error强制继续或者用工具自带的-f参数尝试从文件尾部反向解析。如果还是不行就要去原始镜像或者设备上重新拉取一份完整的 vdex。另一种可能是 vdex 被厂商定制过。国内部分 ROM 会修改 ART 编译参数导致 vdex 的 dex 存储位置和标准实现不一样。遇到这种情况可以先试--raw模式直接按 dex magic 关键字去扫整个文件有些时候能暴力从 vdex 里抠出完整的 dex。4.3 还原出的 DEX 无法被 jadx 打开这大概率是 quickened dex 没有正确 de-optimize。vdex 里的 dex 指令被 ART 优化过比如某些方法调用被替换成快路径指令寄存器分配也做了调整直接用普通反编译工具会解析失败。处理办法比较暴力但很有效用 baksmali 把还原后的 dex 先转成 smali再用 smali 2.x 重新打包成一个 dex。虽然不能完全还原到原始源码级别但至少能让 jadx 或者 jeb 建立正确的类和方法索引然后部分函数就能看到逻辑了。另外如果只是为了快速确认关键逻辑也可以跳过 jadx直接用 grep 或者 strings 搜 dex 里的字符串常量。反正 vdex 里的 dex 包含大量字符串数据快速定位不需要完整反编译看一眼字符串特征也能猜出个大概。4.4 从内存和 OTA 包提取 VDEX 的补充思路不是所有场景都能直接从文件系统拿到 vdex。如果应用在运行期才把 dex 解密到内存然后由 ART 编译最终产物可能在/data/app/之外的临时目录或者直接存在于进程地址空间。这种场景下需要配合 frida 或内存 dump 工具在 ART 完成编译之后把内存中的 dex 快照抓出来再走 vdexExtractor 的流程。OTA 包是另一个可靠来源。购买或下载到官方 OTA 增量包之后解包得到system.new.dat或payload.bin再解出其中的 vdex 文件。整体思路和常规刷机包一样需要用到 payload-dumper-go 或者 ext4 解包工具但 vdex 文件的解析方式完全一致。这个补充路径适合那些没有 root 权限但又能搞到 OTA 包的场景尤其是分析厂商内置应用时非常实用。毕竟如果厂商 ROM 把系统应用设为不可卸载vdex 里的代码逻辑就是唯一快速入口。5. 实操过程中的几个心得最后一次分享两个小经验。第一vdexExtractor 处理大批量文件时尽量用一个干净的输出目录并且每个 vdex 用单独的文件夹隔离不然多个 vdex 还原出的 dex 文件名会重复互相覆盖。用 shell 循环的时候我习惯把输入文件名的前缀带上比如basename $f .vdex。第二别只依赖 vdexExtractor 的一次还原结果。遇到厂商深度定制的 ROM同一份 vdex 在工具的不同版本下还原结果可能有细微差别。特别是当你发现某个类的某个方法在 jadx 里看起来不完整可以试试用旧版工具重新提取再对比两份 dex 的差异有时候旧版反而能绕过新版对某些指令的误判。另外补一句vdexExtractor 本身并不是一个维护特别频繁的工具遇到新 Android 版本的格式变更社区往往需要一段时间才跟进。所以平时最好在自己的工具库里多留几个历史版本别一味追新。很多时候旧版本对老设备的 vdex 反而是最稳的方案。
返回列表