☰
抽取壳还是真dump?apk-reverse的trivial-body比例判别法实战
2026/10/2 16:35:50 网站建设 项目流程

抽取壳还是真dump?apk-reverse的trivial-body比例判别法实战

【免费下载链接】apk-reverseSuitable for Android APK reverse engineering analysis项目地址: https://gitcode.com/gh_mirrors/ap/apk-reverse

做 APK 逆向分析时,一个让新手反复踩坑的场景是:dex dump 明明跑成功了,解出来的却是"空壳"——类名全在、方法签名全在,方法体却只剩一行return-void。apk-reverse 项目用trivial-body 比例(stub%)这一指标把"真 dump"和"抽取壳骨架"区分开,本文带你实战走一遍这套判别方法。

核心问题:dump 出来的到底是代码,还是一副骨架?

🔑抽取壳(extraction shell)的工作原理是"按需解密":方法体在首次被调用前只是一堆占位桩(stub),只有真正执行到该方法时,壳才会在内存中解密并填充真实代码。

这意味着任何"某一刻"的内存 dump 都只能拿到当时恰好被调用过的方法——绝大多数方法体依然是桩。你 dump 得到的是一个骨架(skeleton),而不是原始 dex。

骨架的典型特征:

特征说明
结构完整magic、版本、类表、字符串表全部正常,能通过校验
类数偏少但文件很大实测一个 8.9 MB 的壳 dex 只定义4 个类——字节是加密载荷,不是代码
方法体近乎全空5,061 个方法体里,真实代码占比极低,多数只剩return-void或 nop 填充

dump 的四种形态:先诊断,再选路线

拿到 dump 目录后,先判断它属于哪种形态,再决定下一步做什么。判别表来自 advanced-unpacking.md:

dump 形态stub%诊断下一步
可解析、类多、方法体真实接近基线落壳成功,dump 就是原版过滤后进入打补丁流程
可解析、类多、方法体多为return-void桩很高抽取壳骨架走 FART 主动调用循环
整类都是裸native声明,且对应Java_*导出存在n/aJNI 下沉(代码跑去了.so)去看 java2c-and-jni-sinking.md
方法体在、但指令流解不开基线真 Dex VMP先别下结论,用官方解码器验证

⚠️ 特别提醒:方法体"解不开"不等于 VMP。实测中曾有人用手写的指令宽度表判定出"1,393 个方法体无法解码 → VMP",结果官方dexdump对同一文件解码出 34,566 条指令、零结构错误——错的是那张手写表漏了一条 arm64 分支指令。结论:任何 VMP 判定前,先让没自己写的解码器跑一遍。

一键运行 trivial-body 比例判别法

判断工具就是项目里的 dex_dump_validate.py。trivial-body 比例的定义很朴素:

在所有有code_item的方法中,指令流(跳过普通 nop 单元后)只剩一条return*的方法占比。

没有code_item的方法是 abstract/native 声明,这种"空"是合法的,不计入比例。

一条命令跑完整个 dump 目录:

python skills/apk-reverse/scripts/dex_dump_validate.py <dump目录> --find 'Lcom/example/app/'

脚本会自动完成四件事:按 sha256 去重(绝不按大小去重)、结构校验并拒绝坏镜像(magic 错误、file_size不匹配等)、统计类数与桩方法体、对候选镜像排序——最可能是原版 dex 的排最前面。

实测输出长这样(取自基准 B3 的完整记录):

name size sha256[:12] ver cksum sig class noco code stub% erased% emptied% allstub.dex 982480 c8d7ca8fb163 035 ok ok 647 250 5061 100.0% 0.0% 100.0% ctrl.dex 982480 1b3a97896b40 035 ok ok 647 250 5061 1.9% 0.0% 1.9% nopfilled.dex 982480 de92dcea4eaf 035 ok ok 647 250 5061 0.0% 100.0% 100.0% throwstub.dex 982480 cab8fd51a743 035 ok ok 647 250 5061 1.9% 0.0% 1.9%

解读报告:stub%、erased%、emptied% 三列各是什么?

📌 报告里三个比例列有微妙但关键的差别:

列统计内容作用
stub%只剩一条return*的方法体占比经典抽取壳信号
erased%整个方法体全是 nop 的占比捕获"清槽型"骨架
emptied%上面两类之和判定骨架请看这一列

为什么要看emptied%而不是stub%?因为有一种壳会把方法体清成纯 nop 而不写 return——这种骨架的stub%是0.0%,比没动过的原始 dex 还低。在修复缺陷之前,它甚至会被排序算法冠以"最可能是原版"的头衔。emptied%把两类空法体合并,就能把它抓出来。

关键发现:不是阈值,而是"双峰分布"

📊 项目用一份真实应用 dex(982 KB、5,061 个方法体)+ 零改动对照组,把壳家族会用到的每种"清空手法"都造了一遍并实测,得到这张决定性的校准表(完整命令见 EXTENSION-extraction-shell-bench.md):

壳留下的形态stub%与对照组(1.9%)能否区分?
每个方法体都是裸return-void100.0%✅ 相差 98 个点
方法体截成const/4 v0,#0; return v093.2%✅
nop 填充、连 return 都没有(清槽型)0.0%❌ 比原版还低
new RuntimeException; throw桩(360/乐固常见形态)1.9%❌ 与对照组完全无法区分
只清空 25% / 50% / 75% 的方法体1.7 / 1.7 / 2.1%❌ 差 0.2 个点,属于噪声

三个直接推论:

  1. stub% 是双峰的:它要么落在应用自身基线附近(看不见),要么落在 ≈100%(看见了),中间不存在能放"阈值"的区间。早期文档里"几十个百分点就是骨架"的说法没有实测支撑,已被删除。
  2. 部分抽取是隐形的:删掉 75% 方法体的 dex 只比对照组高 0.2 个点——单看比例根本发现不了。
  3. 高 stub% 是决定性证据;低 stub% 什么都证明不了——它既可能是真 dex,也可能是全 nop 骨架,也可能是 throw 桩骨架。

排序与它的失败条件:别盲目相信"第一名"

脚本最后会给出ranking(最可能是原版的候选排序)。排序逻辑分三级:先剔除emptied% ≥ 50%的镜像,再按"minimal + nop 擦除"方法体数量升序(这个计数是单调的,能区分 25/50/75% 的抽取程度),最后按emptied%升序。

但有两个实测过的失败条件必须先知道(详见 advanced-unpacking.md §The ranking's failure conditions):

  • 部分抽取的镜像会排在重度桩化的前面——低比例区间内,比例已经不是信号了,1.7% 对 1.9% 只是噪声;
  • throw 桩骨架在所有镜像里都"隐形",它的stub%与未改动对照组一模一样,排序帮不上忙,只能逐方法体人工检查。

🛡️ 实用规则:当emptied%停在应用自身基线、又找不到能区分候选的信号时,把排序结果当作"无信息",不要拿第一名当补丁基线。排序的结论是一句"有冠军的陈述句",错误的冠军比没有冠军更危险。

确认是壳之后:FART 三步循环找回真实代码

判别出骨架后,真正的代码不在任何一次 dump 里——它需要被主动唤醒再截获。FART 系工具的思路是三步循环:

  1. 整图 dump 骨架:类定义、签名、字符串表都是完整的,这就是第 3 步的地址地图;
  2. 主动调用每一个方法:反射遍历类列表逐个调用(大多会抛异常,没关系——关键是壳会在方法进入瞬间解密并安装方法体),在每个方法开始执行时把已解密的code_item按method_idx落盘;
  3. 把方法体拼回骨架:按骨架给出的偏移覆盖桩方法体,修好 dex 头(先签名后校验和),必须用 jadx/baksmali 解析通过、方法数与骨架一致才算数——"拼上了"不等于"修好了"。

另外要提前知道:Android 12–16 上经典 hook 点(art::interpreter::Execute)因入口分流、结构偏移、存储限制、壳方对抗、编译策略漂移五个独立原因全部失效,dump 工具日志看起来正常、输出目录却空空如也。排查顺序与修复手段在 advanced-unpacking.md 有完整记录。

更多延伸资料

📚 本文涉及的判别法与全部实测证据,可在仓库中找到原文:

  • 抽取壳判别与 FART 循环详解:advanced-unpacking.md
  • 判别脚本(去重、校验、比例统计、排序一体):dex_dump_validate.py
  • 11 变体骨架基准与三处脚本缺陷修复记录:EXTENSION-extraction-shell-bench.md
  • 首个壳 dex 验收测试(7 个 fixture 的实测输出):EXTENSION-unpacking.md
  • 基准矩阵 B3 行(双峰结论的出处):benchmark.md
  • 整体技能入口与门控流程:SKILL.md

一句话总结:高 stub% 是骨架的铁证,低 stub% 只是"无法判断";判定骨架看 emptied%,选基线别迷信排序第一名,VMP 结论必须经官方解码器背书。

【免费下载链接】apk-reverseSuitable for Android APK reverse engineering analysis项目地址: https://gitcode.com/gh_mirrors/ap/apk-reverse

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询