第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 直接调用 | 无 |