本次带来了许多令人兴奋的新功能。下面直接进入正题。
线程名称
Process.enumerateThreads() API 现在会在可用时一并公开线程名称:
$ frida -U -F
[Pixel 6 Pro::com.google.android.calculator ]-> Process.enumerateThreads()[1]
{
"id": 9579,
"name": "Signal Catcher",
"state": "waiting",
"context": { … }
}
[Pixel 6 Pro::com.google.android.calculator ]->感谢 @Hexploitable 提交出色的拉取请求,推动了这项工作 🙌
内存保护属性查询
有时必须快速确定内存页当前的保护属性。感谢 @mrmacete,现在可以这样做:
$ frida -p 0
[Local::SystemSession ]-> Memory.queryProtection(Module.getExportByName(null, 'open'))
"r-x"
[Local::SystemSession ]->QuickJS 2024-01-13
本版本还包含上个月发布的最新版 QuickJS。这意味着几乎完整支持 ES2023,甚至支持即将 到来的 ES2024 规范中的部分功能,并包含不少错误修复。值得一提的是,现在支持顶层 await, 更容易编写可在两种 JavaScript 运行时上工作的可移植脚本。
隐匿
Frida 会跟踪自身的内存范围、线程等资源,避免进程自省时看到自己。 Process.enumerateThreads() 等自省 API 会确保隐藏 Frida 自身资源,使结果看起来如同 Frida 并不在被插桩进程内部。
使用 Stalker 或其他会暴露原始内存位置的功能时,你可能看到不属于任何已加载模块的代码, 并疑惑其来源。例如,用 Stalker.follow() 跟踪进入或离开被挂钩函数的执行过程时,会执行 一些由 Interceptor 生成的跳板代码。
使用 Gum C API 的 agent 早已能查询给定内存地址、线程等是否归 Frida 所有,但此功能 此前尚未公开给 JavaScript 绑定。得益于 @mrmacete 的又一项出色贡献,现在已经支持。 例如:
$ frida -p 0
[Local::SystemSession ]-> open = Module.getExportByName(null, 'open')
"0x7f929a325840"
[Local::SystemSession ]-> Interceptor.attach(open, () => {})
{}
[Local::SystemSession ]-> Instruction.parse(open).toString()
"jmp 0x7f928940c408"
[Local::SystemSession ]-> Cloak.hasRangeContaining(ptr('0x7f928940c408'))
true
[Local::SystemSession ]-> pointInsideOpen = open.add(16)
"0x7f929a325850"
[Local::SystemSession ]-> Instruction.parse(pointInsideOpen).toString()
"push rbx"
[Local::SystemSession ]-> Cloak.hasRangeContaining(pointInsideOpen)
false
[Local::SystemSession ]->快速 Interceptor
一个鲜为人知且较新的功能 Interceptor.replaceFast() 现在也可从 JavaScript 使用。 它的不同之处在于,目标会被修改为直接跳转到替换实现,因此比 Interceptor.replace() 开销更低。这也意味着,如需调用原始实现,必须使用返回的指针;同一目标也不能同时搭配 Interceptor.attach()。不过,如果处理的是高频目标,这绝对是值得收入工具箱的功能, 尤其适合与 CModule 结合使用。
ELF 导出回归
我们很晚才发现 Frida 16.1.0 破坏了部分 ELF 二进制文件上的 Module#enumerateExports(),现在终于修复。问题源于我将 Gum.ElfModule 改造成跨平台 API、为 Barebone 后端等离线场景添加支持时引入的边界检查错误。(在这类场景中,我们 用它将 Rust 代码动态注入操作系统内核和裸机目标。)
ELF 导入槽
在 Apple 平台使用 Frida 的用户可能已经注意到,Module#enumerateImports() 提供的导入 对象始终带有 slot 属性。可以向该地址写入新指针,从而按模块重新绑定导入。当 Interceptor 无法挂钩函数,或希望避免内联 hook 时,这非常实用。
从本版本开始,基于 ELF 的平台也会提供 slot 属性,因此 Linux、Android、FreeBSD 和 QNX 上也能使用此功能。太棒了!
Android 稳定性
新版 Android 的重度用户可能遇到过“软循环”:Frida 随机导致 Zygote 崩溃,使大量用户 空间组件重启。原因是运行时在准备 fork() 时轮询 /proc/self/stat,等待线程数降为一, 也就是等待进程变成单线程。
现在通过扣除 Frida 自身拥有的线程解决了问题;这些线程通过本文前面提到的 Cloak API 在内部确定。我们还使用新的 ELF 导入槽功能,只为 libart.so 中的调用者挂钩 read()。 在 libc.so 的 read() 中插入内联 hook 并不合适,因为所在进程可能用它执行无限阻塞的 读取。卸载时会因此陷入等待,无法获得回滚内联 hook 的机会。
总之,这是一个相当有趣的问题。感谢 @enovella_ 报告并协助定位 🙌
说到 Android,本版本还改进了 Android 14 上的 Java hook;启用 –enable-optimizations 时,系统会使用新的 ART quick 入口点。感谢 @cr4zyserb 提供这项精彩贡献。
更好的 iOS 16 支持
如果你曾在 iOS 上安装 Frida 时遇到 Service cannot load in requested session,
这个问题现在终于修复。感谢 @as0ler 协助修复。
已越狱 tvOS
另一个令人兴奋的进展是现在支持已越狱 tvOS,可直接从我们的仓库获取 .deb: https://build.frida.re/. 特别感谢 @tmm1 提交拉取请求促成此事。
其他
本版本还有更多内容,其余变更如下:
- symbolutil-libdwarf:修复 DWARF 5 中 DW_AT_ranges 的处理,使 DebugSymbol API 能在新版工具链构建的二进制文件上正常工作。
- elf-module:修复在线模式下的重定位地址。
- interceptor:在 arm64 上认领 graft 时检查模块前缀,以兼容 Xcode 在 iOS 17 上新增的 libLogRedirect.dylib interpose。感谢 @mrmacete!
- interceptor:在 arm64 上隐藏 thunk。感谢 @mrmacete!
- windows:确保 MSVC source-charset 设为 UTF-8,使嵌入式 JavaScript 能在运行时正确 解析。感谢 @Qfrost911!
- freebsd:改进近处分配策略。
- gumjs:修复 QJS 使用 ESM 执行 load() 时的泄漏。
- objc:确保 implementation setter 中的 block 结构可写。感谢 @mrmacete!
- compiler:将 @types/frida-gum 升级到 18.6.0。
尽情享用吧!
oleavr