有些人可能已经注意到,Frida 最近新增了 Android 支持,让你能像在 Windows、Mac、Linux 和 iOS 上一样轻松地对代码插桩。这听起来很酷,但 Android 会运行大量 Java 代码,这意味着你只能观察这些代码产生的原生层副作用。当然,你可以使用 Frida 的 FFI API 自己进入虚拟机,但这些繁琐工作难道不该由 Frida 代劳吗?当然应该!

实际效果如下:

Dalvik.perform(() => {
    const Activity = Dalvik.use('android.app.Activity');
    Activity.onResume.implementation = function () {
        send('onResume() got called! Let's call the original implementation');
        this.onResume();
    };
});

Dalvik.perform() 会负责把线程附加到虚拟机;在来自 Java 的回调中则无需调用它。第一次以某个类名调用 Dalvik.use() 时,Frida 会查询虚拟机并即时构建 JavaScript 包装器。上例取得 Activity 类,用我们自己的版本替换其 onResume 实现,向运行在 Windows、Mac 或 Linux 计算机上的调试器发送消息后,再调用原始实现。你也可以完全不调用原始实现,而是模拟其行为;或者模拟错误场景:

Dalvik.perform(() => {
    const Activity = Dalvik.use('android.app.Activity');
    const Exception = Dalvik.use('java.lang.Exception');
    Activity.onResume.implementation = function () {
        throw Exception.$new('Oh noes!');
    };
});

这样,你就实例化了一个 Java Exception,并直接从 Activity.onResume 的 JavaScript 实现中将其抛出。

此版本还带来了一些运行时增强:

  • Memory.copy(dst, src, n):与 memcpy 相同
  • Memory.dup(mem, size):依次调用 Memory.alloc() 和 Memory.copy() 的简写
  • Memory.writeXXX():补齐与 Memory.read() 对应的方法:S8、S16、U16、S32、U32、S64、U64、ByteArray、Utf16String 和 AnsiString
  • Process.pointerSize:让脚本更具可移植性
  • NativePointer 实例现在提供方便的 isNull() 方法
  • NULL 常量:无需到处写 ptr("0")
  • 面向高级用户的 WeakRef.bind(value, fn) 和 WeakRef.unbind(id):前者监视 value,在 value 被垃圾回收或脚本即将卸载时调用 fn,并返回一个 ID,可将其传给 unbind() 进行显式清理。构建语言绑定时,这个 API 很有用,因为 JS 值不再需要后,必须释放原生资源。

尽情体验吧!