原文链接: https://frida.re/docs/javascript-api/#stalker
欢迎加入 Frida 交流群: 1049977261
Stalker
-
Stalker.exclude(range)
:
将指定的内存范围range
标记为被排除的状态,range
对象包含base
与size
属性 - 类似于Process.getModuleByName()
返回的对象.这意味着 Stalker (潜行者) 将不会跟踪执行这个范围内的指令调用. 您将因此可以观察或者修改传入的参数以及返回的结果, 但不会看到指令之间发生了什么.
这对提升性能以及降噪很有用.
-
Stalker.follow([threadId, options])
:
开始跟踪threadId
对应的线程, 如果省略则跟踪当前线程, 可选的options
可用于开启事件.例如:
const mainThread = Process.enumerateThreads()[0];
Stalker.follow(mainThread.id, {
events: {
call: true, // 调用指令
// 其他事件:
ret: false, // 返回指令
exec: false, // 全部指令:不推荐, 因为数据量过大
block: false, // 已计算的块: 粗略执行轨迹
compile: false // 已编译的块: 对覆盖率很有用
},
//
// 仅指定以下两个回调之一. (见下方的备注.)
//
//
// onReceive: 伴随 `events` 一起被调用. `events` 包含一个或多个
// GumEvent 结构. 关于格式的细节请参考 `gumevent.h`
// 您需要使用 `Stalker.parse()` 来解释其中的数据.
//
//onReceive: function (events) {
//},
//
//
// onCallSummary: 伴随 `summary` 一起被调用. `summary` 是一个由调用的
// 对象地址作为键, 当前时间窗口内调用次数为值构成的键值对对象.
// 通常情况下您应当实现这个方法而不是 `onReceive()`, 例如:
// 当您只想知道哪些目标被调用, 以及被调用了多少次, 但不关心
// 调用的顺序时.
//
onCallSummary: function (summary) {
},
//
// 进阶用例: 这里展示了您应当如何插入您自己的 StalkerTransformer.
// 您所提供的方法将在 Stalker 想要重新编译即将被已跟踪的线程
// 执行的一个基础代码块时被同步调用.
//
//transform: function (iterator) {
// var instruction = iterator.next();
//
// var startAddress = instruction.address;
// var isAppCode = startAddress.compare(appStart) >= 0 &&
// startAddress.compare(appEnd) === -1;
//
// do {
// if (isAppCode && instruction.mnemonic === 'ret') {
// iterator.putCmpRegI32('eax', 60);
// iterator.putJccShortLabel('jb', 'nope', 'no-hint');
//
// iterator.putCmpRegI32('eax', 90);
// iterator.putJccShortLabel('ja', 'nope', 'no-hint');
//
// iterator.putCallout(onMatch);
//
// iterator.putLabel('nope');
// }
//
// iterator.keep();
// } while ((instruction = iterator.next()) !== null);
//},
//
// 默认的实现为:
//
// while (iterator.next() !== null)
// iterator.keep();
//
// 上面的示例展示了您可以插入您自己的代码到应用内存范围内被跟踪的线程的
// 每一个 `ret` 指令前. 它插入的代码检查了 `eax` 寄存器是否包含一个 60
// 到 90 之间的值, 并插入了一个同步的回调, 每当出现这种情况时便调用 JavaScript
// 回调. 这个回调接受一个参数, 这个参数给与您接触 CPU 寄存器, 并修改它
// 的值的能力.
//
// function onMatch (context) {
// console.log('Match! pc=' + context.pc +
// ' rax=' + context.rax.toInt32());
// }
//
// 请注意, 不调用 keep() 将导致指令被遗弃, 这使得您在必要时完全替换特定的
// 指令成为了可能.
//
//
// 想要更好的性能? 试着在 C 中书写回调:
//
// /*
// * const cm = new CModule(\`
// *
// * #include <gum/gumstalker.h>
// *
// * static void on_ret (GumCpuContext * cpu_context,
// * gpointer user_data);
// *
// * void
// * transform (GumStalkerIterator * iterator,
// * GumStalkerWriter * output,
// * gpointer user_data)
// * {
// * cs_insn * insn;
// *
// * while (gum_stalker_iterator_next (iterator, &insn))
// * {
// * if (insn->id == X86_INS_RET)
// * {
// * gum_x86_writer_put_nop (output);
// * gum_stalker_iterator_put_callout (iterator,
// * on_ret, NULL, NULL);
// * }
// *
// * gum_stalker_iterator_keep (iterator);
// * }
// * }
// *
// * static void
// * on_ret (GumCpuContext * cpu_context,
// * gpointer user_data)
// * {
// * printf ("on_ret!\n");
// * }
// *
// * `);
// */
//
//transform: cm.transform,
//data: ptr(1337) /* user_data */
//
// 您也可以使用一个混合的方案, 并且只在 C 中书写部分回调.
//
});
性能方面的注意事项
回调对象对性能有显著的影响. 如果您只需要周期性的调用简介但不关心原始事件, 或者相反, 请确保您忽略您不需要的回调. 例如, 避免将您的逻辑放到 onCallSummary 并且让 onReceive 是一个空回调.
同样请注意可以将 Stalker 与 CModule 结合以使用在 C 中实现的回调.
Stalker.unfollow([threadId])
: 停止跟踪threadId
对应的线程, 如果省略, 则停止跟踪当前线程.-
Stalker.parse(events[, options])
: 解析 GumEvent 二进制大型对象, 可选的options
可用于定制输出格式.例如:
onReceive: function (events) {
console.log(Stalker.parse(events, {
annotate: true, // 展示事件类型
stringify: true
// 将指针的值格式化为字符串而不是 `NativePointer` 值. 例如: 如果
// 您只是想把事件通过 `send()` 发送出去而不是在代理侧解析它的话,
// 这么做就可以占用更少的消耗.
}));
},
Stalker.flush()
: 齐平任何已缓冲的事件.
当您不想要等到下一次Stalker.queueDrainInterval
触发时很有用.Stalker.garbageCollect()
: 在Stalker#unfollow
之后的安全位置释放模拟内存. 这在避免刚刚被停止跟踪线程执行它的最后一条指令是出现竞争情况时很有必要.-
Stalker.addCallProbe(address, callback[, data])
:
当address
处的方法被调用时触发callback
,callback
的签名与Interceptor#attach#onEnter
相同. 返回一个稍后可以传递给Stalker#removeCallProbe
的 id.当然, 您也可以通过 CModule 在 C 语言中实现的
callback
, 只需要指明一个NativePointer
而不是一个方法. 它的签名是:void onCall (GumCallSite * site, gpointer user_data)
在这种情况下, 第三个可选参数
data
应当是一个NativePointer
, 它的值将作为user_data
被传入回调中. Stalker.removeCallProbe
: 移除一个通过Stalker#addCallProbe
添加的调用检测器.Stalker.trustThreshold
:
一个整型数字, 指明了一段代码需要被运行多少次才能被假定为可认为是不变的.
-1 意味着不信任 (慢), 0 意味着在调用时被信任, N 意味着它在执行 N 次以后才获得信任. 默认是 1.Stalker.queueCapacity
:
一个整形数字, 指明了事件队列中事件的容量. 默认是 16384 个事件.Stalker.queueDrainInterval
:
一个整型数字, 指明了每次事件队列发送事件之间的毫秒数. 默认 250 毫秒, 这意味着事件队列每秒发送 4 次事件. 您可以将这个值设为 0 来禁止周期性的发送事件, 取而代之的是当您想要获得事件时调用Stalker.flush()
.