第7章 Native Hook 详解

7.1 Hook 导出函数

// Hook 已知的导出函数
Interceptor.attach(
    Module.findExportByName("libnative-lib.so",
        "Java_com_example_NativeClass_secretFunction"), {
    onEnter(args) {
        console.log('[*] secretFunction 被调用');
        // args[0] = JNIEnv*, args[1] = jobject (this)
        // args[2+] = 实际参数
    },
    onLeave(retval) {
        console.log('    返回值: ' + retval);
    }
});

7.2 通过偏移 Hook 非导出函数

// 从 IDA/Ghidra 获取函数偏移
var baseAddr = Module.findBaseAddress("libtarget.so");
var targetAddr = baseAddr.add(0x1234);  // 偏移量
 
Interceptor.attach(targetAddr, {
    onEnter(args) {
        console.log('[*] 偏移 0x1234 处函数被调用');
        // ARM64 寄存器
        console.log('    x0 = ' + this.context.x0);
        console.log('    x1 = ' + this.context.x1);
    }
});

7.3 枚举查找函数

// 枚举模块导出
var module = Process.findModuleByName("libtarget.so");
module.enumerateExports().forEach(function(exp) {
    if (exp.name.indexOf("encrypt") !== -1) {
        console.log('[*] 找到: ' + exp.name + ' @ ' + exp.address);
    }
});
 
// 枚举所有符号(包括非导出)
module.enumerateSymbols().forEach(function(sym) {
    if (sym.name.indexOf("AES") !== -1) {
        console.log('[*] 符号: ' + sym.name + ' @ ' + sym.address);
    }
});
 
// 17.9.11 新增: 直接按名称查找符号(无需全量枚举,性能更好)
var addr = module.findSymbolByName("target_symbol");
if (addr) {
    console.log('[*] 直接查找: ' + addr);
}

7.4 Hook JNI_OnLoad(库加载时机)

// Hook 动态库加载,在加载完成后 hook 目标函数
Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), {
    onEnter(args) {
        this.path = args[0].readUtf8String();
    },
    onLeave(retval) {
        if (this.path && this.path.indexOf("libtarget.so") !== -1) {
            console.log('[*] libtarget.so 已加载,开始 hook...');
            hookNativeFunctions();  // 现在可以安全 hook
        }
    }
});
 
function hookNativeFunctions() {
    Interceptor.attach(
        Module.findExportByName("libtarget.so", "target_func"), {
        onEnter(args) { /* ... */ }
    });
}

7.5 读写 Native 内存

// 读取 C 字符串
var strPtr = args[0];
console.log("String: " + strPtr.readUtf8String());
 
// 读取二进制数据
var buf = args[1].readByteArray(32);
console.log("Buffer:\n" + hexdump(buf));
 
// 写入内存
args[0].writeUtf8String("modified");
 
// 分配并替换参数
var newStr = Memory.allocUtf8String("injected_value");
this.newStr = newStr;  // 防止 GC!
args[0] = newStr;
 
// 17.9.11 新增: 带保护属性的内存分配(页对齐大小时可用)
var rwxPage = Memory.alloc(Process.pageSize, {
    near: Module.findBaseAddress("libtarget.so"),
    maxDistance: 0x7fffffff
});

7.6 替换 Native 函数实现

// 保存原始函数引用
var originalFunc = new NativeFunction(
    Module.findExportByName("libtarget.so", "validate_license"),
    'int',       // 返回类型
    ['pointer']  // 参数类型
);
 
// 完全替换
Interceptor.replace(
    Module.findExportByName("libtarget.so", "validate_license"),
    new NativeCallback(function(input) {
        console.log("[*] validate_license 被绕过,始终返回 1");
        return 1;  // 始终有效
    }, 'int', ['pointer'])
);

NativeFunction 高级选项(17.9.11)

// scheduling: 控制 JS 锁行为
// - 'cooperative' (默认): 允许其他 JS 线程执行
// - 'exclusive': 保持 JS 锁,更快但有死锁风险
var fastFunc = new NativeFunction(targetAddr, 'int', ['pointer'], {
    scheduling: 'exclusive'
});
 
// exceptions: 控制原生异常处理
// - 'steal': 将原生异常转为 JS 异常(可能导致状态不一致)
// - 'propagate': 让应用自行处理异常
var safeFunc = new NativeFunction(targetAddr, 'void', ['pointer'], {
    exceptions: 'propagate'
});
 
// traps: 控制 Interceptor 回调触发
// - 'default': Interceptor 回调正常触发
// - 'none': 跳过所有 hook(直接调用原始实现)
// - 'all': 可能激活 Stalker
var directFunc = new NativeFunction(targetAddr, 'int', [], {
    traps: 'none'  // 绕过所有 Interceptor hook
});

NativeCallback 结构体返回(17.9.11)

// 支持返回结构体类型(数组形式定义)
var structCallback = new NativeCallback(function() {
    // 返回包含 3 个 int 的结构体
    return [42, 100, 200];
}, ['int', 'int', 'int'], ['pointer']);

FORCE 标志强制内联 Hook(17.6.0+)

// 对于函数体过小的函数,使用 FORCE 标志强制 hook
// 注意: 可能覆盖相邻函数的前几条指令,谨慎使用
Interceptor.attach(tinyFuncAddr, {
    onEnter(args) {
        console.log('[*] tiny function called');
    }
}, { traps: 'all' });

7.7 从 Java 上下文 Hook Native

Java.perform(function() {
    // 找到 native 库
    var lib = Process.getModuleByName('libnative-lib.so');
 
    // Hook JNI 函数
    var jniFunc = lib.getExportByName(
        'Java_com_example_app_NativeLib_decrypt');
 
    Interceptor.attach(jniFunc, {
        onEnter(args) {
            // args[0] = JNIEnv*, args[1] = jobject/jclass, args[2+] = 参数
            console.log('[*] JNI decrypt 被调用');
 
            // 通过 JNI 读取 Java 字符串参数
            var env = Java.vm.getEnv();
            var jstr = args[2];
            console.log('输入: ' +
                env.getStringUtfChars(jstr, null).readCString());
        },
        onLeave(retval) {
            console.log('解密返回: ' + retval);
        }
    });
});

7.8 文件 I/O 监控

const openPtr = Module.getExportByName('libc.so', 'open');
const readPtr = Module.getExportByName('libc.so', 'read');
const writePtr = Module.getExportByName('libc.so', 'write');
 
Interceptor.attach(openPtr, {
    onEnter(args) {
        this.path = args[0].readCString();
    },
    onLeave(retval) {
        if (this.path && this.path.indexOf('/data/') === 0) {
            console.log('open("' + this.path + '") => fd:' + retval.toInt32());
        }
    }
});
 
Interceptor.attach(readPtr, {
    onEnter(args) {
        this.fd = args[0].toInt32();
        this.buf = args[1];
        this.count = args[2].toInt32();
    },
    onLeave(retval) {
        var bytesRead = retval.toInt32();
        if (bytesRead > 0 && bytesRead <= 256) {
            console.log('read(fd=' + this.fd + ', ' + bytesRead + ' bytes)');
            console.log(hexdump(this.buf, {length: Math.min(bytesRead, 64)}));
        }
    }
});
 
Interceptor.attach(writePtr, {
    onEnter(args) {
        var fd = args[0].toInt32();
        var count = args[2].toInt32();
        if (count < 1024) {
            console.log('write(fd=' + fd + ', ' + count + ' bytes)');
        }
    }
});

7.9 调用 Native 函数

// 包装 libc 函数
const malloc = new NativeFunction(
    Module.getExportByName('libc.so', 'malloc'),
    'pointer', ['size_t']
);
const free = new NativeFunction(
    Module.getExportByName('libc.so', 'free'),
    'void', ['pointer']
);
const strcpy = new NativeFunction(
    Module.getExportByName('libc.so', 'strcpy'),
    'pointer', ['pointer', 'pointer']
);
 
// 使用
const buf = malloc(256);
const src = Memory.allocUtf8String('Hello!');
strcpy(buf, src);
console.log(buf.readUtf8String());  // "Hello!"
free(buf);

7.10 ARM64 注意事项

  • 32 位 ARM 上,目标地址的 LSB 指示 ARM (0) 或 Thumb (1) 模式
  • ARM64 寄存器通过 this.context 访问: x0-x28, fp, lr, sp, pc
  • 函数参数通过 x0-x7 传递(ARM64 调用约定)
  • 返回值在 x0(整数)或 d0(浮点)
  • 17.9.11: 支持 PAC(Pointer Authentication Code)指针认证处理
Interceptor.attach(targetAddr, {
    onEnter(args) {
        // 直接访问寄存器
        console.log('PC: ' + this.context.pc);
        console.log('LR: ' + this.context.lr);
        console.log('SP: ' + this.context.sp);
        console.log('X0: ' + this.context.x0);
        console.log('X1: ' + this.context.x1);
    }
});

7.11 Stalker 代码跟踪(17.9.11 增强)

Stalker 是 Frida 内置的代码跟踪引擎,可逐指令追踪线程执行流。

基本使用

// 跟踪当前线程
Stalker.follow(Process.getCurrentThreadId(), {
    events: {
        call: true,    // 函数调用
        ret: true,     // 函数返回
        exec: false,   // 每条指令(开销大)
        block: false,  // 基本块
        compile: false // 编译事件
    },
    onReceive(events) {
        // 批量接收事件
        console.log('收到 ' + events.byteLength + ' 字节事件数据');
    },
    onCallSummary(summary) {
        // 按目标地址聚合的调用计数
        for (var addr in summary) {
            var module = Process.findModuleByAddress(ptr(addr));
            if (module) {
                console.log(addr + ': ' + summary[addr] + ' 次调用 (' +
                    module.name + ')');
            }
        }
    }
});
 
// 停止跟踪
Stalker.unfollow(Process.getCurrentThreadId());

ARM64 Transformer(自定义指令变换)

// 使用 Transform 回调修改执行流
Stalker.follow(threadId, {
    transform(iterator) {
        var instruction;
        while ((instruction = iterator.next()) !== null) {
            // 在特定指令前插入回调
            if (instruction.mnemonic === 'bl') {
                iterator.putCallout(function(context) {
                    console.log('BL 调用目标: ' + context.pc);
                    console.log('X0=' + context.x0 + ' X1=' + context.x1);
                });
            }
            iterator.keep();  // 保留原始指令
        }
    }
});

Call Probe(函数探针)

// 在被 Stalker 追踪的线程中 hook 特定函数
var probeId = Stalker.addCallProbe(targetAddr, function(args) {
    console.log('[Probe] 函数被调用, x0=' + args[0]);
});
 
// 移除探针
Stalker.removeCallProbe(probeId);

排除系统库(性能优化)

// 排除不需要追踪的模块
Stalker.exclude(Process.findModuleByName('libc.so'));
Stalker.exclude(Process.findModuleByName('libart.so'));
 
// trustThreshold: 代码执行多少次后信任不会变异
// -1 = 不信任(最慢),0 = 立即信任,N = N 次后信任
Stalker.trustThreshold = 1;  // 默认值

ARM64 特殊处理

// Stalker 识别的 ARM64 调用指令类型:
// BL, BLR, BLRAA, BLRAAZ, BLRAB, BLRABZ
// 17.9.11: PAC 相关分支指令完整支持
 
// 使用 Arm64Writer 生成自定义指令
Stalker.follow(threadId, {
    transform(iterator) {
        var instruction;
        while ((instruction = iterator.next()) !== null) {
            if (instruction.address.equals(targetAddr)) {
                // 插入自定义 ARM64 指令
                iterator.putCallout(function(context) {
                    // 修改寄存器值
                    context.x0 = ptr(1);  // 修改返回值
                });
            }
            iterator.keep();
        }
    }
});

7.12 CModule 高性能回调(推荐)

对于性能敏感的 hook 场景,使用 CModule 直接编写 C 代码:

// 编译 C 代码到内存(比 JS 回调快 10-100 倍)
const cm = new CModule(`
#include <gum/guminterceptor.h>
#include <stdio.h>
 
static int call_count = 0;
 
void onEnter(GumInvocationContext *ic) {
    call_count++;
    if (call_count % 1000 == 0) {
        printf("[CModule] 调用次数: %d\\n", call_count);
    }
}
 
void onLeave(GumInvocationContext *ic) {
    // 修改返回值
    gum_invocation_context_replace_return_value(ic,
        GINT_TO_POINTER(1));
}
`);
 
Interceptor.attach(targetAddr, {
    onEnter: cm.onEnter,
    onLeave: cm.onLeave
});

7.13 MemoryAccessMonitor 内存访问监控

// 监控内存区域的读写执行操作
MemoryAccessMonitor.enable([
    {base: ptr('0x1000'), size: 0x1000}  // 监控范围
], {
    onAccess(details) {
        console.log('[MemAccess] ' + details.operation + ' @ ' +
            details.address + ' from ' + details.from);
        // details.operation: 'read', 'write', 'execute'
        // details.address: 被访问的地址
        // details.from: 访问来源的指令地址
    }
});
 
// 停止监控
MemoryAccessMonitor.disable();

7.14 ⚠️ 内存管理注意事项

// ❌ 错误: 字符串可能被 GC 回收
Interceptor.attach(target, {
    onEnter(args) {
        args[0] = Memory.allocUtf8String("replacement");  // GC 风险!
    }
});
 
// ✅ 正确: 存储引用在 this 上下文中
Interceptor.attach(target, {
    onEnter(args) {
        var buf = Memory.allocUtf8String("replacement");
        this.buf = buf;  // 防止 GC 直到 onLeave 完成
        args[0] = buf;
    }
});
 
// ✅ 长期存活的分配,存在模块级
var persistentString = Memory.allocUtf8String("permanent_value");

7.15 性能优化

// ❌ 低效: 重复 args[] 访问触发多次 GUM 查找
Interceptor.attach(target, {
    onEnter(args) {
        if (args[0].toInt32() > 10 && args[0].toInt32() < 100) {
            send({value: args[0].toInt32()});
        }
    }
});
 
// ✅ 高效: 缓存参数值
Interceptor.attach(target, {
    onEnter(args) {
        var val = args[0].toInt32();  // 单次访问
        if (val > 10 && val < 100) {
            send({value: val});
        }
    }
});
 
// ✅ 极致性能: 使用 CModule 替代 JS 回调
// 见 7.12 节

性能对比

方式适用场景相对开销
Interceptor + JS开发调试、低频函数
Interceptor + CModule高频函数、性能敏感
Stalker + transform指令级追踪极高
NativeFunction traps:‘none’绕过 hook 直接调用