先认清壳的结构
360 加固(jiagu)的核心思路:原 dex 加密后藏在壳 dex 的资源/资产里,运行时壳先启动,解密原 dex 并通过自定义 ClassLoader 加载。脱壳的本质就是等它解密加载完成后,从内存里把明文 dex 抠出来。
判断是不是 360 加固:jadx 打开 APK,入口 Application 是 com.stub.StubApp,assets 里有 libjiagu.so / libjiagu_a64.so,基本实锤。
脱壳时机:等"解密完成"这个窗口
壳的工作流程:StubApp.attachBaseContext → 解密 dex → 替换 ClassLoader → 启动原 Application。明文 dex 在内存中的存活窗口从解密完成开始,到进程结束。所以脱壳脚本要在原 Application 的 onCreate 之后动手——这时所有 dex 都已解密映射。
方案一:FRIDA-DEXDump 暴力搜内存
最省事的方案,用 FRIDA-DEXDump 工具直接扫内存里的 dex 魔数:
frida -U -f com.target.app -l agent.js
# 进入 REPL 后// agent.js 核心逻辑:搜内存里的 "dex\n035" 魔数
Java.perform(function() {
setTimeout(function() { // 等壳解密完成
Process.enumerateRanges('r--').forEach(function(range) {
try {
Memory.scan(range.base, range.size, '64 65 78 0a 30 33 35 00', {
onMatch: function(address, size) {
console.log('发现 dex @ ' + address);
// 读 dex 头里的 fileSize 字段(偏移 0x20)
var fileSize = address.add(0x20).readU32();
var dex = address.readByteArray(fileSize);
send({type: 'dex', addr: address}, dex);
},
onComplete: function() {}
});
} catch(e) {}
});
}, 3000);
});Python 侧接收内存块存成 .dex 文件。搜出来的通常有多个:壳自己的 dex、原 App 的 dex(可能拆成多个)、框架 dex。按大小和包名分辨。
方案二:hook 解密函数精准拿
360 加固的解密在 native 层(libjiagu.so),但 dex 最终要交给 Android 运行时。hook 运行时的 dex 加载点更精准:
// hook libart 的 OpenMemory / DexFile 构造
var openMemory = Module.findExportByName('libart.so',
'_ZN3art7DexFile10OpenMemoryEPKhmRKNSt3__112basic_stringIc...');
// 符号名随 Android 版本变化,用 frida 的 ApiResolver 模糊匹配:
var resolver = new ApiResolver('module');
resolver.enumerateMatches('exports:*!*OpenMemory*').forEach(function(m) {
if (m.name.includes('libart')) {
Interceptor.attach(m.address, {
onEnter: function(args) {
var base = args[1]; // dex 内存基址
var size = args[2]; // 大小
send({type: 'dex'}, base.readByteArray(size.toInt32()));
}
});
}
});精髓:dump 之后的修复才是关键
内存 dump 出来的 dex 经常不能直接分析,360 加固会做两类破坏:
- 方法体抽取(VMP 级防护):部分方法的 CodeItem 被清空,运行时才回填。dump 出来的 dex 里这些方法是空的(
No instructions)。修复办法是 hookArtMethod的 invoke,在方法被调用时把 CodeItem dump 出来回填——这就是"主动调用式脱壳",工具如 Youpk、Fart 走这个路线。 - 字符串加密:jadx 打开看到
StubApp.getString(12345)之类的调用,真实字符串运行时才解密。批量修复要写脚本模拟解密函数回填,或直接用 Frida 在运行时批量调用 getString 建映射表。
验证脱壳成功
# 看 dex 里有没有业务包名
strings dump.dex | grep "com/target/app" | head
# jadx 打开看入口 Activity 是否有真实代码
jadx-gui dump.dex总结
- 360 加固特征:StubApp 入口 + libjiagu.so
- 脱壳时机:原 Application onCreate 之后,dex 全部解密映射
- 暴力方案搜
dex\n035魔数,精准方案 hook libart 的 OpenMemory - dump 只是第一步,方法体抽取和字符串加密才是修复的主战场
交流微信:run1255