目录
  1. 1. 一、加固壳的原理回顾
  2. 2. 二、ZjDroid 脱壳原理
  3. 3. 三、DumpDex 方法
  4. 4. 四、Frida 内存 Dump 技术
    1. 4.1. 方法一:通过 ClassLoader 遍历 DEX
    2. 4.2. 方法二:通过 Memory.scan 搜索 DEX 魔数
  5. 5. 五、手动脱壳完整流程
  6. 6. 六、对抗抽取型加固
  7. 7. 面试常考问题
【逆向安全技术-工具篇】脱壳神器ZjDroid

一、加固壳的原理回顾

在深入脱壳之前,先理解壳是怎么保护 DEX 的:

// 典型的壳 Application
public class ShellApplication extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
// 1. 从 assets 或 lib 中读取加密的 DEX
byte[] encryptedDex = readEncryptedDex();

// 2. 解密 DEX
byte[] realDex = decrypt(encryptedDex);

// 3. 通过自定义 ClassLoader 加载
ClassLoader loader = new InMemoryDexClassLoader(
new ByteBuffer[]{ByteBuffer.wrap(realDex)},
getClassLoader()
);
// 4. 替换默认 ClassLoader
// ... 通过反射替换 mClassLoader 字段
}
}

加固的本质是 DEX 加密存放 + 运行时解密加载。因此脱壳的关键是:在 DEX 解密后、加载前,将其从内存中 dump 出来

二、ZjDroid 脱壳原理

ZjDroid(https://github.com/halfkiss/ZjDroid)是早期著名的 Xposed 脱壳模块,核心思路是 Hook ClassLoader 和 DexFile 相关方法

ZjDroid 的关键 Hook 点:

1. dalvik.system.DexFile.<init>(String)          → DEX 文件构造函数
2. dalvik.system.DexFile.loadClass(String, ...) → 类加载
3. dalvik.system.BaseDexClassLoader.findClass() → BaseDexClassLoader 类查找
4. java.lang.Runtime.exec() → 监控命令执行(反调试检测)

脱壳工作流:

# 1. 安装 ZjDroid 模块并在 Xposed 中启用
# 2. 重启设备
# 3. 启动目标应用,ZjDroid 自动工作
# 4. 通过广播获取 dump 信息
adb shell am broadcast -a com.zjdroid.invoke --ei target pid

# 5. 从设备拉取 dump 出的 DEX
adb pull /data/data/com.target.app/files/dex_dump/ ./

三、DumpDex 方法

DumpDex(https://github.com/WrBug/dumpDex)是另一种基于 Hook 的脱壳工具,原理是 Hook ClassLoader 的构造过程。

# Frida 实现的手动 DumpDex 脚本
frida -U -l dump_dex.js -f com.target.app --no-pause
// dump_dex.js 核心逻辑
Java.perform(function() {
var ClassLoader = Java.use("java.lang.ClassLoader");
var DexFile = Java.use("dalvik.system.DexFile");

// Hook ClassLoader 构造函数
var dex_classLoader = Java.use("dalvik.system.DexClassLoader");
dex_classLoader.$init.overload('java.lang.String', 'java.lang.String',
'java.lang.String', 'java.lang.ClassLoader').implementation = function(
dexPath, optDir, libPath, parent) {
console.log("[*] DexClassLoader init: " + dexPath);
// 在这里 dump dexPath 指向的 DEX 文件
return this.$init(dexPath, optDir, libPath, parent);
};
});

四、Frida 内存 Dump 技术

现代脱壳更多使用 Frida,因为更灵活、更新及时。

方法一:通过 ClassLoader 遍历 DEX

// Frida 脚本:遍历所有已加载的 DEX
Java.perform(function() {
Java.enumerateLoadedClasses({
onMatch: function(className) {
// 通过类名获取其 ClassLoader
var clazz = Java.use(className);
// 获取 defineClass 的参数...
},
onComplete: function() {
console.log("[*] Enumeration complete");
}
});
});

方法二:通过 Memory.scan 搜索 DEX 魔数

// DEX 文件以 "dex\n035" 开头(magic number)
var pattern = "64 65 78 0a 30 33 35"; // "dex\n035"

Process.enumerateRanges('r--').forEach(function(range) {
var results = Memory.scanSync(range.base, range.size, pattern);
results.forEach(function(match) {
console.log("[*] DEX header found at: " + match.address);
// dump 从 match.address 开始的数据
// DEX 文件大小在 header offset 0x20(4 bytes)
});
});

五、手动脱壳完整流程

针对一个未知壳的通用脱壳流程:

# Step 1:识别加固厂商
apktool d target.apk -o unpacked
ls unpacked/lib/armeabi-v7a/
# 找特征 so → libtup.so(乐固), libjiagu.so(360), libSecShell.so(梆梆)

# Step 2:安装并运行应用
adb install target.apk
adb shell am start -n com.target.app/.MainActivity

# Step 3:Frida attach
frida -U com.target.app

# Step 4:在 Frida 控制台中执行 dump 脚本
%load dump_dex.js

# Step 5:拉取 dump 的文件
adb pull /data/data/com.target.app/dumped_dex/ ./

# Step 6:用 JADX 验证 dump 出的 DEX
jadx-gui dumped_classes*.dex
# 若反编译出完整业务逻辑 → 脱壳成功
# 若仍为空壳代码 → 可能是抽取型/VMP型加固,需进一步分析

六、对抗抽取型加固

抽取型加固(如 360 的 VMP)不一次性解密整个 DEX,而是按需解密每个方法的字节码。

// 对抗方法:Hook ClassLoader 的 loadClass,收集所有已加载类
var classNames = [];
var loadClass = Java.use("java.lang.ClassLoader").loadClass;
loadClass.overload('java.lang.String').implementation = function(name) {
classNames.push(name);
return this.loadClass(name);
};
// 收集完毕后,通过 eachClassName → getDeclaredMethods → dump 每个方法的字节码

面试常考问题

Q1:为什么不能直接从 APK 中提取 DEX,必须要从内存 dump?
A:因为加固后的 APK 中,classes.dex 是加密的壳代码,真正的业务 DEX 被加密后放在 assets/lib/ 或加密隐藏在 APK 文件尾部。运行时壳代码解密 DEX 并在内存中加载,只有此时才能获取到真实的业务 DEX。因此脱壳必须在运行时进行操作。

Q2:抽取型加固和 VMP 加固脱壳的主要难点是什么?
A:抽取型加固不会在内存中一次性出现完整的 DEX,需要 Hook ClassLoader.defineClass 或在每个方法第一次被执行时收集其字节码(Function Code Extraction)。VMP 加固将原有的 DEX 字节码替换为自定义虚拟机的 opcode,即使 dump 出 DEX,其方法体也是 VMP 指令而非常规 Dalvik 字节码。分析 VMP 需要逆向壳自带的解释器引擎,理解其指令集映射关系。

Q3:如何检测一个应用使用了哪种加固方案?
A:(1)静态检测:apktool 解包后检查 lib/ 下的 so 文件:libtup.so→腾讯乐固,libjiagu.so→360,libSecShell.so→梆梆,libnqshield.so→网易易盾,libnaga.so→娜迦;(2)检查 Manifest 中是否有壳的 StubApplication;(3)使用 APKiD(https://github.com/rednaga/APKiD)工具自动检测加固类型;(4)检查 classes.dex 的内容——壳的 DEX 通常很薄,只有几个 Application/ClassLoader 相关的类。

打赏
  • 微信
  • 支付宝

评论