Interceptor 拦截器用法——attach / replace / 调用时机
attach:在函数入口/出口插钩子
Interceptor.attach 不替换原函数,只是在函数执行前(onEnter)和执行后(onLeave)插入自定义逻辑。原函数照常执行。
Interceptor.attach(functionPtr, {
onEnter: function (args) {
// 函数执行前调用
// args 是参数数组(NativePointer)
console.log('[+] 函数被调用');
},
onLeave: function (retval) {
// 函数执行后调用
// retval 是返回值(NativePointer)
console.log('[+] 返回值: ' + retval);
}
});
什么时候触发 onEnter / onLeave
onEnter:函数被调用时立即触发,参数已准备好但函数体还没执行onLeave:函数执行完毕即将返回时触发,返回值已产生
两个回调里都可以修改参数或返回值,但要注意时机:
onEnter里改args→ 影响原函数接收到的参数onLeave里改retval→ 影响调用方拿到的返回值
修改返回值
Interceptor.attach(funcPtr, {
onLeave: function (retval) {
retval.replace(ptr(1)); // 强制把返回值改成 1
}
});
修改参数
Interceptor.attach(funcPtr, {
onEnter: function (args) {
args[1] = ptr(999); // 把第二个参数改成 999
}
});
replace:直接替换整个函数
Interceptor.replace 把函数实现完全替换成你指定的地址。原函数不再被调用。
// 先写一个替换函数
var replacement = new NativeCallback(function (arg1, arg2) {
console.log('[+] 替换函数被调用');
return 42;
}, 'int', ['int', 'int']);
// 替换
Interceptor.replace(originalPtr, replacement);
和 attach 的区别:
| 方面 | attach | replace |
|---|---|---|
| 原函数执行 | ✅ 照常执行 | ❌ 完全不执行 |
| 能保留原逻辑 | ✅(只是加钩子) | ❌(必须自己重写) |
| 性能开销 | 略高(两次回调) | 很低(直接跳转) |
| 适合场景 | 加日志、改参数/返回值 | 完全改写行为 |
Java 层的 Interceptor
Java.use().method.implementation 本质上就是 Java 层的 replace。Interceptor 主要用于 native 层。
如果要在 Java 层用类似 attach 的效果(只加钩子不替换实现),用 implementation + 在末尾调原方法:
TargetClass.method.implementation = function () {
console.log('[+] 加钩子');
return this.method.apply(this, arguments); // 原方法照常执行
};
调用时机
spawn vs attach
Frida 有两种注入时机:
- spawn:目标应用启动前就注入。函数还没被调用过,Hook 生效。
- attach:目标应用已经在运行了,动态注入。已经执行过的函数不会被追溯 Hook——只能拦截之后的调用。
JsHook 的 Hook 服务开关就涉及这个差异:
- 关 Hook 服务 → 开 Hook 服务 → 启动目标应用:等同于 spawn,钩子从一开始就生效
- 目标应用已在运行 → 开 Hook 服务:等同于 attach,只拦截后续调用
注入在运行途中(解决闪退问题)
如果目标应用一启动就闪退,可能是注入时机太早干扰了初始化流程。JsHook 支持运行途中注入:
- 先关 Hook 服务
- 启动目标应用,等它完全跑起来(几秒)
- 开 Hook 服务
- Frida 会 attach 到已运行的进程
这时候注入发生在应用运行途中,避开了初始化阶段。更多闪退排查见 目标应用一开就闪退。
常见坑
args 是 NativePointer,不是原始类型
// 错误:直接把 args[1] 当数字用
console.log('参数: ' + args[1]); // 输出 NativePointer(0x1)
// 正确:先转成对应类型
var val = args[1].toInt32();
console.log('参数: ' + val); // 输出 1
返回值也一样
// 错误
console.log('返回: ' + retval);
// 正确
var returnVal = retval.toInt32();
console.log('返回: ' + returnVal);