原文链接: https://frida.re/docs/javascript-api/#interceptor
欢迎加入 Frida 交流群: 1049977261
Interceptor
-
Interceptor.attach(target, callbacks[, data])
:
拦截位于target
的方法的调用.target
是一个NativePointer
类型的对象, 指明了您想要拦截的方法的地址.
请注意, 在 32 位 ARM 上, 对于 ARM 函数, 此地址的最低有效位必须设置为 0, 对于 Thumb 函数, 此地址必须设置为 1.
如果您通过 Frida 的 API 来获得一个地址, 例如Module.getExportByName()
, 那么 Frida 就会替您处理这个细节.callbacks
是一个包含一个或多个以下方法的对象:-
onEnter: function (args)
:
在原方法被调用前触发.
args
是一个可以用来读写原方法参数的NativePointer
数组. -
onLeave: function (retval)
:
在原方法返回前触发.
retval
是一个含有原返回值的NativePointer
驱动的对象.
您可以调用retval.replace(1337)
将返回值替换为整型 1337, 或调用retval.replace(ptr("0x1234"))
将返回值替换为一个指针.
请注意, 这个对象在 onLeave 调用完后将被回收, 因此请不要保存并在回调以外的地方使用这个对象. 如果您需要保存它包含的值, 您可以执行一次深度复制, 例如:ptr(retval.toString())
.
如果钩子函数被频繁的调用,
onEnter
和onLeave
有可能是指向由 CModule
编译的本地 C 方法的NativePointer
. 它们的签名是:-
void onEnter (GumInvocationContext * ic)
-
void onLeave (GumInvocationContext * ic)
这种情况下, 第三个可选参数
data
可以是一个gum_invocation_context_get_listener_function_data()
可接触的NativePointer
对象.您也可以通过传递一个与
onEnter
有相同的签名方法而不是callbacks
对象来拦截任意指令.
此时传递给它的args
参数只会在被拦截的指令位于函数的开头或寄存器/堆栈尚未偏离的指定的点时为您提供合理的值.和上面一样, 这个方法也通过给
callbacks
指明一个NativePointer
对象来调用 C 实现而不是通过 JavaScript 方法实现.返回一个可用于
Interceptor#detach()
的监听器对象.请注意, 这些方法将伴随着
this
被一起调用,this
是一个可存储任意数据的局部线程对象, 当您需要在onLeave
中读取onEnter
里的参数时十分有用.例如:
-
Interceptor.attach(Module.getExportByName('libc.so', 'read'), {
onEnter: function (args) {
this.fileDescriptor = args[0].toInt32();
},
onLeave: function (retval) {
if (retval.toInt32() > 0) {
/* do something with this.fileDescriptor */
}
}
});
-
另外, 这个对象包含一些实用的属性:
-
returnAddress
:NativePointer
类型的返回地址 -
context
: 带有键pc
和sp
的NativePointer
对象, 分别为 ia32 / x64 / arm 指定 EIP / RIP / PC 和 ESP / RSP / SP. 也可以使用其他处理器特定的键, 例如eax
,rax
,r0
,x0
等.
您也可以通过分配这些键来更新寄存器值. -
errno
: (UNIX) 当前的 errno 值 (您可以替换它) -
lastError
: (Windows) 当前操作系统的 error 值 (您可以替换它) -
threadId
: 操作系统线程 ID -
depth
: 相对其他调用的调用深度
例如:
-
Interceptor.attach(Module.getExportByName(null, 'read'), {
onEnter: function (args) {
console.log('Context information:');
console.log('Context : ' + JSON.stringify(this.context));
console.log('Return : ' + this.returnAddress);
console.log('ThreadId : ' + this.threadId);
console.log('Depth : ' + this.depth);
console.log('Errornr : ' + this.err);
// Save arguments for processing in onLeave.
this.fd = args[0].toInt32();
this.buf = args[1];
this.count = args[2].toInt32();
},
onLeave: function (result) {
console.log('----------')
// Show argument 1 (buf), saved during onEnter.
var numBytes = result.toInt32();
if (numBytes > 0) {
console.log(hexdump(this.buf, { length: numBytes, ansi: true }));
}
console.log('Result : ' + numBytes);
}
})
性能方面的注意事项
回调对象对性能有显著的影响. 如果您只需要检查参数但不关心返回结果, 或者相反, 请确保您忽略您不需要的回调. 例如, 避免将您的逻辑放到 onEnter 并且让 onLeave 是一个空回调.
在一台 iPhone 5S 上, 当只提供 onEnter 时, 基础开销是 6 毫秒, 当 onEnter 和 onLeave 一起被提供是则是 11 毫秒.
同样的, 请注意那些每秒钟调用成千上万次的方法. 因为 send() 是异步的, 发送单个消息的总开销并未针对高频进行优化, 因此, Frida 让您来决定是否需要将多个值分批处理到单个 send() 调用中, 以便获得低延迟还是高吞吐量.
当钩住热函数时, 可以将 Interceptor 与 CModule 结合以使用在 C 中实现的回调.
-
Interceptor.detachAll()
: 分离所有之前附加上的回调. -
Interceptor.replace(target, replacement[, data])
:
使用replacement
替换target
处的方法. 这通常在您需要完全或部分地替换已有方法时很有用.您可以使用
NativeCallback
来实现一个 JavaScript 的replacement
.如果被替换的方法被频繁的调用, 您可以通过 CModule 在 C 语言中实现
replacement
. 您也可以稍后指明第三个可选参数data
, 它应当是一个gum_invocation_context_get_listener_function_data()
可以接触到的NativePointer
对象. 您可以使用gum_interceptor_get_current_invocation()
来获得GumInvocationContext *
.请注意,
replacement
将持续存活直到Interceptor#revert
被调用.如果您想要链接到原实现, 您可以在您的实现中通过
NativeFunction
同步调用target
, 这将直接调用原实现.例如:
var openPtr = Module.getExportByName('libc.so', 'open');
var open = new NativeFunction(openPtr, 'int', ['pointer', 'int']);
Interceptor.replace(openPtr, new NativeCallback(function (pathPtr, flags) {
var path = pathPtr.readUtf8String();
log('Opening "' + path + '"');
var fd = open(pathPtr, flags);
log('Got fd: ' + fd);
return fd;
}, 'int', ['pointer', 'int']));
-
Interceptor.revert(target)
: 将target
处的方法还原到之前的实现. -
Interceptor.flush()
:
确保任何待定的更改已提交到内存. 这应该仅在少有的必要的情况中被执行, 例如, 您刚刚attach()
或replace()
了一个您即将通过 NativeFunction 调用的方法. 待定的改动将自动地在当前线程即将离开 JavaScript 运行时或send()
被调用时被齐平, 比如在 RPC 方法中返回, 以及调用 console 中的任意 API.
网友评论