逆向工程实战手册 第 6 章

第 6 章 Frida 动态插桩

Frida 是跨平台动态插桩框架(Android/iOS/Windows/Linux/macOS),能在不重打包的前提下注入 JS 代码,Hook 函数、改参数、改返回值、扫内存。它是移动逆向和现代恶意样本分析的第一动态工具。

📍 知识点地图 | 主题:Frida动态插桩 | 前置:第5章 | 后续:第10-11章 | 核心概念:Hook、onEnter/onLeave、Java层、Objection

6.1 核心概念

你的 JS 脚本(Hook 逻辑)
        ↓
Frida 注入到目标进程
        ↓
拦截函数调用: onEnter(参数) / onLeave(返回值)
        ↓
修改或观察 → 打印 / 存储 / 直接改

两种启动方式:

# attach: 附加到已运行进程
frida -U -n com.example.app

# spawn: 启动时注入(能在早期代码执行前 Hook,绕过反调试首选)
frida -U -f com.example.app -l hook.js

6.2 第一个 Hook(通用)

// hook.js — Hook strcmp 观察比较内容
const strcmp = Module.findExportByName(null, "strcmp");
Interceptor.attach(strcmp, {
    onEnter(args) {
        console.log("[strcmp] a=" + Memory.readUtf8String(args[0]));
        console.log("[strcmp] b=" + Memory.readUtf8String(args[1]));
    },
    onLeave(retval) {
        console.log("[strcmp] => " + retval.toInt32());
    }
});
frida -U -f com.example.app -l hook.js

6.3 常用模式速查

观察 vs 修改

// 只观察(推荐先观察)
onEnter(args) { console.log(...); }

// 修改返回值(Hook 验证逻辑、绕检测)
onLeave(retval) { retval.replace(0); }

// 修改参数
onEnter(args) { args[0] = Memory.allocUtf8String("new_value"); }

按名称 Hook

// 任意模块导出函数
Interceptor.attach(Module.findExportByName("libc.so", "open"), {...});

// 未导出函数按地址
Interceptor.attach(ptr("0x7a0b1c00"), {...});

Stalker 指令跟踪

Stalker.follow(threadId, {
    events: { call: true },
    onCallSummary(summary) { console.log(JSON.stringify(summary)); }
});

6.4 Android Java 层 Hook

Java.perform(function () {
    // 1. Hook 方法并改返回值
    var RootBeer = Java.use("com.scottyab.rootbeer.RootBeer");
    RootBeer.isRooted.implementation = function () { return false; };

    // 2. Hook 加密拿密钥(最经典的用法)
    var Cipher = Java.use("javax.crypto.Cipher");
    Cipher.init.overload('int', 'java.security.Key').implementation =
        function (mode, key) {
            console.log("[Cipher] Key bytes: " +
                bytesToHex(key.getEncoded()));
            return this.init(mode, key);
        };

    // 3. 枚举已加载类(找目标)
    Java.enumerateLoadedClasses({
        onMatch: function (name) {
            if (name.includes("sign")) console.log(name);
        },
        onComplete: function () {}
    });

    // 4. 动态调用任意 Java 方法
    var Activity = Java.use("android.app.Activity");
    Java.scheduleOnMainThread(function () {
        // 主线程操作
    });
});

6.5 iOS Hook

// Hook ObjC 方法
var cls = ObjC.classes.NSString;
Interceptor.attach(ObjC.classes.NSString['-stringByAppendingString:']
    .implementation, {
        onEnter(args) {
            console.log("self=" + ObjC.Object(args[0]));
            console.log("param=" + ObjC.Object(args[2]));
        }
    });

// Hook 系统 C 函数(CCCrypt 拿密钥)
Interceptor.attach(Module.findExportByName(
    "libcommonCrypto.dylib", "CCCrypt"), {
        onEnter(args) {
            console.log("op=" + args[0] + " alg=" + args[1]);
            console.log("key=" + hexdump(args[3], {length: args[4].toInt32()}));
        }
    });

6.6 Objection(Frida 免脚本层)

日常移动测试 80% 的工作不需要写脚本:

objection -g "com.example.app" explore

# Android
android root disable
android sslpinning disable
android keystore list
android heap search instances com.example.Sign
android hooking list activities

# iOS
ios jailbreak disable
ios sslpinning disable
ios keychain dump
ios hooking list classes

# 通用
env
ls
sqlite connect /data/data/com.example.app/databases/db
memory dump from_base 0x0

6.7 SSL Pinning 绕过速查

# Objection 一行
objection -g "com.example.app" explore
android sslpinning disable

# Frida 通用脚本
frida -U -l ssl_pinning_bypass.js -f com.example.app

# Xposed(Android)
TrustMeAlready 模块  全局禁用证书校验

6.8 Root / 越狱检测绕过速查

# Objection
android root disable
ios jailbreak disable

# Frida 多层检测
Java.perform(function () {
    var RootBeer = Java.use("com.scottyab.rootbeer.RootBeer");
    RootBeer.isRooted.implementation = function () { return false; };
});

6.9 Frida Gadget(免 Root / 免越狱)

frida-gadget.so / FridaGadget.dylib 注入目标 APK/IPA,重新签名安装,即可在无 Root 设备上 Hook:

objection patchapk --source app.apk     # 全自动注入+重签
# iOS: frida-gadget.dylib 注入 + re-sign

6.10 r2frida(radare2 + Frida 合体)

r2 frida://usb//com.example.app    # r2 里直接操作运行中的进程
# 内存搜索、patch、dump 一步到位

6.11 Frida 高级技巧

  1. spawn 模式绕反调试-f 冷启动,在 onEnter 里先把反调试函数返回值改掉
  2. 内存扫描Memory.scan 搜特征字节
  3. 递归函数提速(记忆化):Hook 一个函数缓存结果,指数级提速
  4. Firebase Cloud Functions 绕过:Hook 拦截器层
  5. hook 全部 JNI 调用:观察 Java↔native 边界,判断核心逻辑在哪层

动手练习

  1. 在模拟器/真机上给一个普通 App 写 Hook:打印 onClick 的参数和返回值。
  2. 用 Objection 完成一次 sslpinning disable + keystore list
  3. 写一个"密钥窃取"脚本:Hook Cipher.init 打印所有密钥字节。
  4. 练习 spawn 模式,感受与 attach 的区别。

深入阅读

  • 仓库:skills/reverse-engineering/tools-dynamic.md(Frida 全章节:Stalker、r2frida、记忆化)
  • 仓库:skills/apk-reverse/SKILL.md(Android Hook 工作流)
  • 仓库:skills/mobile-reverse/references/frida-objection-deep.md(Frida/Objection 深度)
  • 仓库:skills/mobile-reverse/references/anti-detection-bypass.md(多层绕过方法论)
  • 仓库:skills/apk-reverse/references/frida-cookbook.md / frida-bypass-kit.md