本系列文章仅供安全研究与学习使用,请在合法授权环境下操作,切勿用于非法用途。
一、什么是加固,为什么需要砸壳
1.1 加固的本质
发布到应用市场的 APK,开发者通常会对其进行加固(加壳)处理。加固的本质是在原始 DEX 文件外面套一层"保护壳",让逆向分析者无法直接用 jadx 等工具看到真实的业务代码。
加固后的 APK 运行流程大致如下:
系统启动 APK,先执行壳代码(Application 或 ContentProvider)
壳代码在内存中解密/解压原始 DEX
将解密后的 DEX 加载进虚拟机(DexClassLoader)
原始业务代码正式运行
因此,APK 静态分析时看到的只是壳代码,真正的逻辑藏在加密 DEX 里。砸壳就是把内存中已解密的 DEX 转储(dump)出来,还原为可分析的代码。
1.2 为什么逆向分析必须先砸壳
| 场景 | 不砸壳的后果 |
|---|
| jadx 打开 APK | 只看到壳代码,关键类几乎为空 |
| Frida Hook 业务类 | 类名/方法名不存在,脚本报错 |
| 静态搜索关键字符串 | 搜不到,字符串也被加密存储 |
| 分析签名算法 | 真正的加密逻辑完全不可见 |
二、主流加固厂商与技术原理对比
2.1 加固技术分类
市场上的加固技术主要分三个层次:
| 技术层级 | 原理 | 保护强度 | 代表厂商 |
|---|
| 整体加密 | 将整个 DEX 加密,运行时解密后加载 | ★★☆ | 娜迦(Nagapt)早期版本、部分小厂 |
| 函数抽取 | 将方法体(指令)从 DEX 中抽空,运行时填回 | ★★★ | 梆梆企业版、腾讯御安全 |
| VMP/VDEX | 将 Java 字节码转换为自定义虚拟机指令 | ★★★★ | 网易易盾、阿里聚安全 |
2.2 主流厂商详细对比
| 厂商 | 产品名称 | 主要技术 | 特征标识 | 砸壳难度 |
|---|
| 梆梆安全 | BangBang Shield | 整体加密 + 函数抽取 | bangcle、secapk | 中 |
| 腾讯御安全 | Tencent Shield | 函数抽取 + VMP | legu、tencent. 前缀 | 高 |
| 阿里聚安全 | Ali Shield | 整体加密 + VMP | aliprotect、walling | 高 |
| 网易易盾 | NetEase Shield | 整体加密 + VMP | netease、yidun | 高 |
| 爱加密 | iJiami | 整体加密 + 函数抽取 | ijiami、com.ijm | 中 |
| 几维安全 | Jiwei Shield | 整体加密 + Native保护 | kiwisec | 中高 |
| 通付盾 | Tongfudun | 整体加密 | payegis | 低 |
| 娜迦安全 | Nagapt | 整体加密 | naga、nagaprotect | 低 |
2.3 各技术原理深入解析
整体加密型:
APK 安装 → Application.attachBaseContext() → 解密 classes.dex → 替换 ClassLoader
优点是实现简单,缺点是 DEX 整体在内存中必然存在,一次性可以全部 dump。
函数抽取型:
DEX 中方法体被清空(nop填充) → 运行时在方法被调用前填入真实指令
这种方式 dump 整个 DEX 是无效的,需要借助 ART 的 dex2oat 回调在每个方法执行时单独 dump。
VMP 型:
Java 字节码 → 自定义虚拟机指令(opcode混淆) → 专属解释器执行
最难分析,通常只能分析自定义解释器的行为,完全还原成本极高。
三、APKiD 识别加固类型
在砸壳前,先用 APKiD 识别目标 APK 使用了哪种加固,针对性选择方案。
3.1 安装 APKiD
pip install apkid# 或者使用 Docker(推荐,依赖更完整)docker pull rednaga/apkiddocker run -it--rm-v$(pwd):/apks rednaga/apkid /apks/target.apk
3.2 基本用法
# 分析单个 APKapkid target.apk# 输出 JSON 格式apkid -j target.apk > result.json# 分析并显示详细信息apkid -v target.apk
3.3 典型输出解读
[INFO] APKiD 2.1.3 :: from RedNaga Security[INFO] Processing target.apktarget.apk!classes.dex |-> anti_disassembly : dalvik byte order |-> compiler : dexlib 2.xtarget.apk!assets/libjiagu_64.so |-> protector : Bangcle/SecNeotarget.apk |-> anti_vm : Build.FINGERPRINT check
关键标识对照:
| APKiD 输出 | 对应加固 | 推荐砸壳方案 |
|---|
Bangcle/SecNeo | 梆梆加固 | frida-dexdump |
Qihoo 360 | 360加固 | FART / frida-dexdump |
Tencent Legu | 腾讯御安全 | FART + 函数抽取修复 |
Ali/Alibaba | 阿里聚安全 | frida-dexdump |
iJiami | 爱加密 | BlackDex / frida-dexdump |
NetEase | 网易易盾 | FART |
四、方案一:frida-dexdump
frida-dexdump 是基于 Frida 的通用 DEX dump 工具,适合整体加密类加固,成功率较高。
4.1 环境要求
4.2 安装
pip install frida-dexdump# 验证安装frida-dexdump --version
4.3 启动 Frida Server
# PC 端adb push frida-server /data/local/tmp/adb shell "chmod +x /data/local/tmp/frida-server"adb shell "su -c '/data/local/tmp/frida-server &'"# 验证连接frida-ps -U
4.4 执行砸壳
# 方式一:先启动 App,再 dump(推荐)# 1. 手机上手动打开目标 App,等待启动完成# 2. 执行 dumpfrida-dexdump -U-f com.target.app# 方式二:spawn 模式(App 冷启动时 dump)frida-dexdump -U-f com.target.app --spawn# 方式三:指定输出目录frida-dexdump -U-f com.target.app -o /tmp/dex_output/# 方式四:深度搜索(搜索内存中所有 DEX 魔数)frida-dexdump -U-f com.target.app --deep-search
4.5 输出结果
[*] Found DEX file, loading...[*] Dump 0x71a3400000 to /tmp/dex_output/classes.dex (4.2 MB)[*] Dump 0x71a8c00000 to /tmp/dex_output/classes2.dex (1.1 MB)[*] Done! 2 DEX file(s) dumped.
4.6 核心原理(简化版脚本)
frida-dexdump 的核心是在内存中搜索 DEX 魔数 64 65 78 0a(dex\n),找到后按照 DEX 文件头中的 file_size 字段读取完整数据:
# 简化版原理示例importfridadefdump_dex(pid):session = frida.get_usb_device().attach(pid)script = session.create_script(""" // 搜索内存中所有 DEX 文件 Process.enumerateRanges('r--').forEach(function(range) { try { var magic = Memory.readByteArray(range.base, 4); var bytes = new Uint8Array(magic); // DEX magic: 64 65 78 0a (dex\n) if (bytes[0] == 0x64 && bytes[1] == 0x65 && bytes[2] == 0x78 && bytes[3] == 0x0a) { // 读取文件大小(offset 0x20) var fileSize = Memory.readU32(range.base.add(0x20)); var dexData = Memory.readByteArray(range.base, fileSize); send({type: 'dex', base: range.base.toString()}, dexData); } } catch(e) {} }); """)# ... 保存处理逻辑
五、方案二:BlackDex(免 Root 方案)
BlackDex 是一款无需 Root 的 Android 端图形化砸壳工具,适合没有 Root 设备的场景。
5.1 原理
BlackDex 利用 Android 的多用户机制和 VirtualApp 技术,在独立的用户空间中运行目标 App,通过监控 DexClassLoader 的调用时机来 dump DEX,无需系统 Root 权限。
5.2 下载与安装
从 GitHub 下载最新版本:https://github.com/CodingGay/BlackDex
手机安装 BlackDex-arm64.apk(或 BlackDex-arm32.apk,取决于设备架构)
授予必要权限(存储权限)
5.3 操作步骤
Step 1:安装目标 APK
目标 App 需要先安装在手机上(或直接提供 APK 路径)。
Step 2:打开 BlackDex
BlackDex 主界面 → 选择要砸壳的 App → 点击"开始提取"
Step 3:等待完成
BlackDex 会在后台启动目标 App,监控 DEX 加载事件并 dump。过程通常需要 30-120 秒。
Step 4:获取结果
# dump 结果保存路径/sdcard/BlackDex/包名/# 使用 adb 拉取adb pull /sdcard/BlackDex/com.target.app/ ./dex_output/
5.4 BlackDex 的局限性
| 限制 | 说明 |
|---|
| 函数抽取类 | dump 出的方法体仍为空,需配合 FART |
| VMP 保护 | 无法还原虚拟机指令,仅能 dump DEX 框架 |
| 部分加固检测 VirtualApp | 可能导致 App 闪退或触发反调试 |
| Android 12+ 兼容性 | 部分 ROM 上运行不稳定 |
六、方案三:FART 定制 ROM(函数抽取专杀)
FART(Function ARTificially)是专门针对函数抽取类加固的最强工具,需要刷入定制 ROM。
6.1 FART 原理
函数抽取加固的 DEX 在磁盘上方法体是空的,只在执行时才填回真实指令。FART 的思路是:
修改 ART 虚拟机源码,在每个方法首次执行时记录方法的 code_item
主动调用 App 中所有可访问的方法,触发方法体填充
将 DEX 文件 + 每个方法的真实 code_item 合并,还原完整 DEX
6.2 获取 FART 镜像
# FART 支持的设备(Google 原生系统)# Pixel 系列:Pixel 1/2/3(Android 7/8/9/10)# 下载地址:https://github.com/hanbinglengyue/FART# 刷入方式(以 Pixel 2 为例)adb reboot bootloaderfastboot flash boot fart_boot.imgfastboot reboot
6.3 FART 砸壳步骤
方式一:通过 am 命令触发
# 1. 在设备上执行 FART dumpadb shell am start-n com.target.app/.MainActivity# 2. 触发 FART dump(通过特殊 Activity)adb shell am start-a android.intent.action.FART -n com.target.app/.MainActivity# 3. 等待 dump 完成(约 1-3 分钟)# 4. 拉取结果adb pull /sdcard/fart/com.target.app/ ./fart_output/
方式二:通过 API 主动调用
# Frida 脚本配合 FART ROM 主动 dumpimportfridascript_code = """Java.perform(function() { // 枚举所有已加载的类 Java.enumerateLoadedClasses({ onMatch: function(className) { try { var clz = Java.use(className); // 获取所有方法并主动调用(触发 FART 记录) var methods = clz.class.getDeclaredMethods(); methods.forEach(function(method) { // FART ROM 会自动记录每个方法的真实 code_item }); } catch(e) {} }, onComplete: function() { console.log('[*] 所有类遍历完成,FART dump 触发'); } });});"""6.4 FART 输出目录结构
/sdcard/fart/com.target.app/├── classes.dex # 原始(方法体为空的)DEX├── classes_fart.dex # FART 填充后的完整 DEX├── codeitems/ # 每个方法 code_item 的二进制记录│ ├── Lcom/target/LoginManager;->login.bin│ └── ...└── dump_log.txt # dump 日志
七、砸壳流程图
流程说明:
APKiD 扫描 → 识别加固类型(整体加密/函数抽取/VMP)
选择方案:
执行 dump → 获取原始 DEX 文件
dex修复 → 使用 dexfixer 修复可能损坏的结构
jadx 验证 → 确认代码可读性
八、dex 修复:dexfixer 用法
dump 出的 DEX 文件可能因为内存对齐、校验和等问题无法直接被 jadx 识别,需要先修复。
8.1 常见问题
| 问题现象 | 原因 | 工具 |
|---|
jadx 报 DEX checksum mismatch | 文件校验和未更新 | dexfixer / baksmali |
unexpected magic value | DEX 头部损坏 | 手动修复或重新 dump |
| 方法体显示为 nop | 函数抽取未还原 | FART 重新 dump |
| 类缺失、方法缺失 | dump 时机不对 | spawn 模式或延迟 dump |
8.2 dexfixer 安装与使用
# 下载 dexfixer# https://github.com/F8LEFT/ART-dex-fixer# 编译(需要 Android NDK)cd dexfixerndk-build# 或直接使用预编译版本# 放入 PATH# 修复单个 DEXdexfixer input.dex output.dex# 批量修复for f in dex_output/*.dex; do dexfixer "$f""fixed_$(basename $f)"done
8.3 使用 baksmali + smali 修复
# 方法一:重新计算校验和# 安装工具pip install androguardpython3 << 'EOF'from androguard.core.bytecodes.dvm import DalvikVMFormatimport hashlib, structwith open('dump.dex', 'rb') as f: data = bytearray(f.read())# 重新计算 checksum(Adler-32)import zlibchecksum = zlib.adler32(bytes(data[12:])) & 0xffffffffstruct.pack_into('<I', data, 8, checksum)# 重新计算 SHA-1sha1 = hashlib.sha1(bytes(data[32:])).digest()data[12:32] = sha1with open('fixed.dex', 'wb') as f: f.write(data)print('修复完成')EOF# 方法二:baksmali 重组java -jar baksmali.jar d dump.dex -o smali_output/java -jar smali.jar a smali_output/ -o rebuilt.dex
九、砸壳后验证:jadx 打开测试
9.1 jadx 基本验证
# 打开修复后的 DEXjadx -d output_dir/ fixed.dex# 检查输出ls output_dir/sources/
9.2 验证清单
# 1. 检查类数量grep-r"class " output_dir/sources/ | wc-l# 2. 检查关键类是否存在grep-r"LoginManager\|SignatureUtil\|encrypt" output_dir/sources/# 3. 检查方法体是否为空# 如果看到大量这样的代码,说明函数抽取未还原:# public void someMethod() {# // 空方法体或仅有 return# }# 4. 用 jadx-gui 直观检查jadx-gui fixed.dex9.3 质量评估标准
| 指标 | 正常砸壳 | 函数抽取未修复 |
|---|
| 类总数 | 正常(数百到数千) | 数量正常 |
| 方法可读性 | 有实际逻辑代码 | 方法体为空/仅 return |
| 字符串可见性 | 明文字符串可见 | 部分/全部加密 |
| 继承关系 | 完整的类继承树 | 完整 |
十、常见失败原因与对策
| 失败现象 | 可能原因 | 解决方案 |
|---|
| frida-dexdump 无输出 | App 还未完全启动 | 等待 5-10s 再执行 dump |
| DEX 文件大小为 0 | dump 时机过早 | 使用 --spawn + 延迟钩子 |
| jadx 打开报错 | DEX 结构损坏 | dexfixer 修复后重试 |
| 方法体全为 nop | 函数抽取加固 | 改用 FART 方案 |
| BlackDex 闪退 | App 检测了 VirtualApp | 改用 frida-dexdump |
| FART 设备不支持 | 非 Pixel 机型 | 找对应机型的 FART 移植版 |
| 壳检测 frida | 反调试机制 | 先用第 09 篇的反检测方案 |
| dump 出多个 DEX | 多 dex 架构 | 合并或逐个分析 |
| 代码混淆严重 | ProGuard/R8 混淆 | 参考第 02 篇反混淆技巧 |
10.1 多 DEX 合并技巧
# 如果 dump 出多个 DEX,可以合并后统一分析# 工具:dex-toolsjava -jar d2j-dex-recompute-checksum.jar classes.dexjava -jar d2j-dex2jar.jar classes.dex -o classes.jar# 将多个 jar 合并# 然后用 jadx 打开合并后的 jar
10.2 Spawn 模式延迟 Hook
对于在 Application 极早期就完成解密的加固,需要延迟 dump 时机:
# frida-dexdump 延迟 dump 技巧importfrida, timedefon_message(message, data):print(message)device = frida.get_usb_device()# spawn 启动,暂停在入口pid = device.spawn(['com.target.app'])session = device.attach(pid)# 注入等待脚本,等 Application.onCreate 完成后再 dumpscript = session.create_script("""var Application = Java.use('android.app.Application');Application.onCreate.implementation = function() { this.onCreate(); // Application 初始化完成,发信号 dump send('ready_to_dump');};""")script.on('message', on_message)script.load()# 恢复执行device.resume(pid)time.sleep(2)# 此时 Application 已初始化,DEX 已解密,执行 dump
小结
砸壳是安卓逆向的必经之路,核心思路是在正确的时机,从内存中捞出已解密的代码。
下一篇我们将进入网络层,学习如何绕过 SSL Pinning,实现 HTTPS 抓包。
文章持续更新,欢迎关注。转载请注明出处。