聊到 App 解固脱壳很多人第一反应是“这不是破解工具吗”其实在移动安全、应用测试、漏洞挖掘和恶意样本分析这些正经领域里这是一项非常基础的技术动作。所谓解固就是去掉厂商给 APK 套上的加固保护所谓脱壳就是从加固后的安装包里把原本被加密、隐藏的 DEX 文件还原成可分析的状态。整个过程的核心只有一句话在代码加载进内存的那一瞬间把解密后的内容抓下来。这篇文章我不会绕弯子直接把我这些年实际跑过的脱壳方案、踩过的坑、以及遇到“DEX 全乱码”时怎么救回来一起整理出来。无论你是做安全测试的新手还是需要自查自研 App 防护强度的开发只要你手上有一台测试机按这个流程走一遍基本能覆盖绝大多数加固样本。1. 内容整体设计与思路拆解1.1 加壳到底是什么以及它为什么能保护代码你从应用市场下载的 APK本质上是一个 Zip 压缩包里面有 DEX 字节码、资源文件、so 库等。正常未加固的 AppDEX 直接躺在包里用 jadx 打开就能看到 Java 层代码逻辑。但加了加固壳之后原本的 DEX 通常被加密或抽取掉剩下一个壳 DEX 和若干 so 库在入口处接管启动逻辑。这个过程有点像把一本纸质书的正文全部拆掉只在封面写上书名请了一个“带路保安”守在门口。每当有人需要看书时保安才从保险箱里把正文取出来。脱壳要做的事就是在保安取书的那一瞬间把书的内容拍下来。加固方案通常分几代最早是整体加密壳在运行时负责把原始 DEX 解密并加载进内存后来出现抽取加固类被抽成空壳方法体在执行时才动态回填再往后就是 VMP、指令虚拟化这种把字节码翻译成自定义指令的方式。脱壳难度也是从“直接 Dump 内存”一路升到“要还原虚拟机指令”。1.2 脱壳的本质时机、位置、拷贝不管你面对的是哪一代加固脱壳的核心思路都是一样的找一个代码完整加载进内存的时机把运行时的 DEX 内容完整拿下来。这个时机一般是ClassLoader.loadClass被调用、libdvm.so/libart.so中的OpenMemory函数执行、或者某个加固 so 解密完 DEX 并回调给运行时的时候。位置则取决于系统版本和加固实现——Android 5以下跑 DalvikDEX 会被转换成 ODEXAndroid 7以上用 ARTDEX 文件头出现在内存里但方法区可能被抽取。不同加固定制方式不同所以才有“通用脱壳方案”和“针对某厂商魔改壳的方案”之分。你如果理解了这一点后面所有工具和步骤本质上就是围绕“怎么捕获这个完整内存镜像”展开的。1.3 这篇文章适合谁能解决什么问题如果你正在做下面这些事可以重点参考授权范围内的企业 App 安全测试想知道某个加固厂商把核心代码藏到哪了病毒样本分析恶意 App 用了加固来隐藏下载器和 C2 地址自研 App 上架前的防护验证想确认自己的加固能否扛住常规脱壳手段刚入行移动安全想搞懂 frida、Xposed、脱壳工具链到底是怎么配合的。我会尽量把“为什么这么做”讲透而不只是丢给你工具命令。有些步骤看起来简单比如 frida-dexdump 一条命令就完事但如果你不知道原理一旦碰上加固魔改、多 DEX、抽取壳照样一头雾水。2. 动手之前环境准备与工具选型2.1 测试设备与系统版本怎么选做脱壳不是直接在真机上跑个工具就行的环境比工具本身还重要。我个人的经验是尽量选一只系统版本可控的 Pixel 或者老款小米手机原因是内核和系统版本越接近官方ART 运行时的行为越稳定frida-server 的兼容性也越好经验丰富的加固厂商会针对常见框架做反调试常见操作是用海外 GSI 镜像或者 Magisk 隐藏模块去对抗但这涉及很多额外排查没必要在主力机上浪费生命模拟器不是不能用但部分加固 so 会检测模拟器特征并自动崩溃这会让你分不清是没脱成功还是被检测了。系统版本上我最常用的是 Android 8~11 之间的真机。Android 7以下的 Dalvik 跑 uptime 环境不顺手Android 12以上新增了不少运行时校验对普通 frida 方案有一定干扰。如果你就是偶尔分析一两个样本Android 9 的 Pixel 3 系列性价比最高二手也不贵。2.2 工具矩阵从内存抓取到代码还原我按“从脱壳到出代码”的顺序列一下目前最常用的工具避免你装一堆最后发现没用功能工具/脚本适用场景沙箱环境与文件系统访问Magisk MagiskDenyList隐藏 root对抗简单反调试日常分析必备动态插桩frida-server frida-tools主动调用方法、Hook Java 层和 so 层脱壳主方案自动化脱壳frida-dexdump、BlackDex、Youpk、FART快速批量脱壳不同加固场景各有优势DEX/代码分析jadx、GDA、JEB、Androguard查看还原后的 Java 代码JEB 对多 DEX 和混淆支持更好so 层分析IDA Pro、Ghidra分析脱壳过程中涉及的 native 解密逻辑抓包配置Burp Suite、Charles、Reqable辅助分析 App 行为多用于脱壳后的流量分析不算脱壳主链路frida-dexdump 适合多数整体加密壳BlackDex 则能免 root 跑在模拟器里对某些抽查样本有奇效。遇到抽取壳、VMP 时光靠 Dump 内存解决不了方法区缺失就得切到 主动调用 Hook 路线这一步我放到第 3 节详细讲。2.3 合规前提与使用边界这句话我会多写一遍脱壳技术本身是中性的但使用场景必须合法合规。请把脱壳限定在以下范围内自己开发的应用做自身安全测试公司已授权产品的渗透测试已公开的恶意样本分析学习研究不针对具体商业产品做未授权破解。如果你拿这套东西去逆向他人商业 App 的支付逻辑、盗取接口、批量爬数据那是另一回事后果自己承担。我做安全很多年见过太多因为“会脱壳就膨胀”然后翻车的案例。3. 三种主流脱壳路线的实操拆解3.1 路线一内存遍历 Dump最快拿到整个 DEX这是最常用、也是效率最高的路线适用绝大多数“整体加密壳”和部分“抽取壳”。它的原理是当加固壳把原始 DEX 解密并加载进内存后ART 运行时会在内存中保留一份完整的 dex 文件结构只要扫描进程内存中的 dex magicdex\n035\0或dex\n036\0把命中的内存块完整导出就得到了解密后的 DEX。3.1.1 最快速 Dump 操作假设测试机已连上电脑frida-server 已启动目标包名是com.example.target直接用pip install frida-tools frida-dexdump frida-dexdump -U -f com.example.target -o /tmp/dex_output参数解释-U表示使用 USB 连接的设备-f表示冷启动应用即重新从包管理器拉起进程-o指定输出目录。运行时脚本会自动暂停启动流程在内存里搜索 DEX 头并导出文件。完成后输出目录里会出现一堆以地址命名或自动编号的.dex文件比如0x7fa3c8a9.dex。用 jadx 打开前先用文本编辑器看一眼开头是不是dex\n035\0。如果一次抓出来的 DEX 不完整可以多跑几次因为内存扫描存在时机问题。尤其冷启动时壳的解密顺序和系统调度有关第二次、第三次抓到的内存碎片可能互补。3.1.2 为什么这条路线不是万能的frida-dexdump 最大的问题在于“只抓完整 DEX 文件结构”。对抽取壳而言类的方法体可能被全部替换成空实现运行时才逐方法回填。你即使拿到完整文件看到的也只是一堆空壳方法。这时候就要换到路线二。另外现在很多加固 so 会在内存中直接修改 dex 文件头或者分段存储单纯搜dex\n会漏掉。碰到这种情况可以先 hookart::DexFile::OpenMemory在函数入口处拿到baseAddr和size再按地址去 Dump绕开头疼的签名搜索。// frida 脚本示例Hook OpenMemory 获取 dex 地址 Java.perform(function () { var ArtMethod Java.use(java.lang.ClassLoader); // 此处省略 art 符号匹配细节一般用 Interceptor.attach 到 libart.so 导出函数 });3.2 路线二主动调用 Hook抓抽取方法区当方法被抽空内存里找不到完整 DEX 时就得盯住“方法回填”这个动作。加固 so 在执行到某个被抽取的方法前会先把真正的字节码恢复到对应 Method 结构体上然后才让 ART 去解释或编译执行。最常见的手法是两层 Hook第一层Hookjava.lang.ClassLoader.loadClass或Class.resolve拿到类元信息第二层Hookart::ArtMethod::Invoke在方法执行前对所有已加载类的方法做 Dump记录code_item和指令段。更自动化的方案是使用 FART 这种基于定制 ROM 的主动调用框架。它会在系统层遍历所有已加载类然后把每个类的所有方法主动调用一遍强制触发抽取壳的回填逻辑。在 FART 输出中你拿到的不只是 DEX还有回填后的方法区镜像再用配套脚本把方法合并回 DEX就能得到一份可分析的完整代码。如果你不想刷机也可以用 frida 脚本模仿主动调用Java.perform(function () { var classLoader Java.use(java.lang.ClassLoader); classLoader.loadClass.overload(java.lang.String, boolean).implementation function (name, resolve) { var clazz this.loadClass(name, resolve); // 在这里可以反射获取所有声明方法并 invoke 触发回填 return clazz; }; });这里的思路就是“人家不主动给你你就逼它执行一次”。主动调用对单样本分析比较管用但遇到方法数量极多、执行性能差的样本耗时会特别长跑一遍可能十几分钟起步。3.3 路线三定制 ROM 与影子自绘适合批量样本分析如果你经常和大量样本打交道比如恶意样本侧写靠手撸 frida 脚本就太慢了。这时候建议直接上定制 ROM 方案——FART、Youpk、或者基于 Android 源码改的 ART 运行时。定制 ROM 的核心思路是直接从系统层修改 class 加载和 method invoke 流程在 ART 内部自动完成 DEX 解密记录和抽取回填记录。样本装到这个环境里什么都不用管只要运行起来完整代码就被系统层记录下来了。BlackDex 是另一个路子它的亮点是不需要 root。原理大致是在可控进程里破坏掉加固 so 的自我保护让 App 自己把 DEX 解密进内存然后再从内存里抓取。实测对某些旧版加固效果特别好但对新版加固和 VMP 就不行了。我建议的路线选择很简单只是偶尔分析一个 App先 frida-dexdump不行再上 FART 模拟器每天分析大量样本直接整套定制 ROM 环境别浪费时间在反复调试 frida 脚本上App 用了 VMP单靠脱壳不够还需要专门还原指令集那是另一个大工程。4. 脱壳之后DEX 修复与代码还原4.1 为什么 Dump 出来的 DEX 经常打不开很多新手栽在最后一步明明 Dump 出了一堆文件用 jadx 一打开要么报错要么一片乱码要么只看到残缺的类。这里要明白内存 Dump 和文件 DEX 的区别。文件 DEX 有完整的文件头、索引区、数据区大小概念清晰但内存里的 DEX 可能只保留了解析后的数据结构文件头里的file_size、data_size、header_size等字段不一定正确或者某些区段被加固 so 做了偏移修改。直接拿内存镜像当文件用肯定出问题。最常见的症状包括打开就报DEX 文件头错误或magic 无效jadx 提示check failed: data_size ...能看到类名但方法体全是null字符串表错乱所有字符串变成乱码。4.2 DEX 修复的关键步骤拿到 Dump 结果后不要急着丢给 jadx先按这个顺序做一遍校验修复。4.2.1 检查文件头用十六进制编辑器打开 DEX 文件确认前 8 字节是64 65 78 0A 30 33 35 00对应dex\n035\0。如果是dex\n036\0说明是 Android 9 以后的新版本格式也能正常解析但某些老工具不支持。4.2.2 修正 file_size 和 data_size用脚本解析 DEX 头偏移大小字段说明0x008magic 标识0x084checksum 校验和0x0C20SHA-1 签名0x204file_size文件总长度0x244header_size头部大小通常 0x700x284endian_tag 字节序标记0x2C4link_size0x304link_off0x344map_off0x384string_ids_size0x3C4string_ids_off.........0x6C4data_size0x704data_off内存 Dump 出来的 DEXfile_size常常偏小或偏大。可以重新扫描该内存块的长度把整个dex文件的真实结束位置算出来回填到file_size。data_size同理通常以data_off 数据区长度来修正。4.2.3 修复 checksum 与 SHA-1DEX 文件头里还有两项校验signatureSHA-1和checksumAdler32。修改 DEX 之后必须重新计算否则 unzip 和 jadx 不一定拦你但 Android 系统加载时会拒绝。Python 里可以这样重算import hashlib import zlib def fix_dex(path): with open(path, rb) as f: data bytearray(f.read()) data[0x0C:0x20] hashlib.sha1(data[0x20:]).digest() data[0x08:0x0C] zlib.adler32(data[0x0C:]).to_bytes(4, big) with open(path .fixed, wb) as f: f.write(data)4.3 抽取方法怎么回填对抽取壳来说即使文件头修好方法区还是空的。回填思路是从 FART 或主动调用流程里拿到被抽取方法执行时的完整code_item再根据方法在dex文件里的method_idx找到对应的code_off把code_item写回原位置。这个过程市面上有不少自动化脚本比如DexRepair、FRIDA-DEXDump里内置的修复模块。它们的流程基本都是解析 DEX 文件建立method_idx - code_off映射从内存镜像或日志输出中提取真实code_item将真实指令写回 DEX 数据区对应位置更新data_size和相关偏移重算校验和。修复完之后再丢给 jadx 或 GDA这时候看到的才是真正的业务逻辑。如果修复完还是报错多半是code_item的insns_size、registers_size解析错了需要人工比对一下方法头。5. 实测问题排查与避坑实录5.1 常见问题速查表现象可能原因排查方向frida-dexdump 一条 DEX 都抓不到壳在早期做了反 frida 检测DEX 加密强度高没在内存中完整显现检查 frida-server 是否适配换 FART 定制 ROMDump 下来的 DEX 有文件头但大小不对内存扫描时只抓到了头部后续数据未同步重新跑 Dump或用dd扩大导出范围jadx 报data_size校验失败文件头字段被加固 so 修改过按 4.2 节修复文件头类能看到但方法全是空抽取壳已经把方法体抽走换主动调用或 FART 路线App 一运行就闪退反调试检测到 frida/模拟器/root尝试 MagiskDenyList 隐藏换非 root 的 BlackDex抓包时全部流量走 SSL Pinning只是加固不代表配置了全套防护参考证书校验绕过教程或直接分析 so 层 key 逻辑一个 APK 里有几十个 DEX分包加载内存中不只有一份frida-dexdump 输出目录里按地址匹配即可别漏了 low memory 区FART 跑完但 DEX 还是空方法加固厂商对主动调用做了过滤触发了发生条件下不恢复方法用动态触发场景代替批量触发5.2 几个容易栽跟头的细节细节一frida-server 版本一定要和 frida-tools 匹配。我之前吃过亏frida-tools 16 配 frida-server 15跑脚本时经常报unable to start或者莫名其妙的 segfault。后来统一都装最新主版本这类兼容问题少了九成。细节二Android 12 以上的运行时ART 结构体变化很大。网上很多老的脱壳脚本都是针对 Android 8~10 写的拿到 Android 12 上直接失效。如果你必须分析新版系统上的 App优先考虑 BlackDex 或者刷 Android 11 以下的测试机。细节三脱壳时不要用主力机。从启动到运行的整个过程中壳会尝试访问设备 ID、应用列表、调试端口、文件系统结构。这些都是反调试的经典检查项一旦命中就退出或者出现虚假行为。你拿主力机跑不仅容易失败还可能在不知情下触发厂商的告警。细节四脱壳之后验证代码的正确性用搜索关键函数名。修复完 DEX别傻乎乎人肉看全部代码直接搜目标业务相关的字符串比如网络地址、AES key 常量、HttpUrl地址等。搜索命中说明脱壳基本成功没命中可以优先怀疑修复流程有问题。5.3 从失败到搞定的一次典型经历有一次我分析某个加固应用包很小几 MB但 frida-dexdump 跑了两轮都只抓到一个看起来像壳的 DEX打开全是壳的方法。后来我用 hookart::DexFile::OpenMemory的方式抓参数才发现原始 DEX 是解密在 native 层的私有缓冲区里的长度几 MB但存放在内存里并不是完整文件布局所以按文件头扫描抓不全。最后我用 FART 定制 ROM 跑了一遍主动调用触发所有方法把真实指令片段回填到 DEX。修复前 jadx 打开只有 200 多个类修复后能搜到业务核心模块的接口代码。整个过程让我体会到两点一是脱壳方案的“通用性”永远是相对的二是当你发现一条路线不行不要死磕换一条思路往往比硬调脚本快得多。6. 进阶思路与自查建议脱壳只是起点不是终点。拿到完整代码后后续工作通常还包括资源解密。部分加固会把资源文件转存到 native 层光脱壳 DEX 不够还要处理resources.arsc和图片文件so 层逻辑分析。核心算法在 native 层时需要 IDA/Ghidra 配合 frida 做动态调试协议还原。结合抓包工具定位 App 的密文接口逆向出加解密参数类混淆还原。App 里大量a.a.a之类的混淆类名对恢复业务逻辑影响不大但分析速度会慢很多可以考虑先用自动化脚本统计引用关系。如果你只是想验证自家 App 的加固强度我建议按下面这个清单自查一遍未加固版本和加固版本做对比列出 SO 导入函数变化脱壳环境是否存在明显的检测点暴露默认端口、特权进程名脱壳后能否直接拿到完整的核心业务方法体反调试触发后是闪退还是伪数据用户可感知程度如何多次冷启动后加固壳的加密行为是否稳定一致。这套自查做完你对自家加固方案的强弱就心里有数了。7. 写在最后的一点个人经验做脱壳分析这些年我最深的体会是工具能解决 80% 的常规样本但那剩下的 20%靠的是对 ART 运行时的理解、对壳行为的耐心观察以及关键时刻换路线的冷静。如果你刚开始学不用急着把 frida 的每个 API 都背下来。先找几个典型样本练手一个整体加密壳、一个抽取壳、一个带反调试的加强版分别用本文里的路线一、路线二、路线三走一遍。等这三类样本都能顺利还原出 Java 层代码你就算真正入门了。后路再多动作也要合规。技术本身没有立场但使用技术的人要自己把握好边界。