这是忙碌的一周。一起来看看。

沙箱环境

本周,@hsorbo 和我花了几天时间改进 Frida 在沙箱环境中的运行。目标是让 Frida 进入 iOS 上 Apple 的 SpringBoard 进程。不过为了增加一点挑战,我们决定先从处理 iMessage 协议的守护进程 imagent 入手。新版操作系统对它进行了大量加固,Frida 已无法附加。

为了方便调试,我们先研究 macOS 上的这个守护进程。找到其沙箱配置 /System/Library/Sandbox/Profiles/com.apple.imagent.sb 后,系统调用策略非常醒目:默认禁止全部系统调用,再谨慎启用若干组以及个别必需调用。

随后发现,第一个障碍是 Frida 使用了 pipe() 系统调用。这段代码并不在 Frida 本身,而是在 GLib 中;Frida 使用这个优秀的库提供数据结构、跨平台线程原语、事件循环等。GLib 用 pipe() 实现事件循环所需的原语,更准确地说,用它唤醒阻塞在 poll() 类系统调用中的事件循环线程。

我们注意到 kqueue() 属于明确允许的系统调用组。Apple 的 kqueue() 可同时轮询文件描述符和 Mach 端口等对象,许多地方都可能需要它,因此大量沙箱配置会允许它。它也非常适合我们:EVFILT_USER 提供了唤醒事件循环线程的方法,而且一个文件描述符也不占用。

经过大量咖啡和愉快的结对编程,我们完成了一个补丁,在支持的操作系统上把 GLib 事件循环切换到 kqueue()。接下来的障碍是 Frida 使用 socket API 传递文件描述符,这是用于对当前进程子进程插桩的 child-gating 功能的一部分。不过,加固的系统服务通常不允许 fork() 和 execve() 等操作,因此可以安全地让这部分功能降级。随后我们也解决了它,于是 Frida 终于能附加到 imagent。🎉

接着转到 iOS 进行测试。令我们惊喜的是,Frida 一开始就能附加到 SpringBoard。之后尝试 notifyd 和 profiled,也都成功,甚至在最新版 iOS 16 上也一样。不过仍有工作要做:Frida 尚无法在 iOS 上附加到 imagent 和 WebContent。尽管如此,这仍是令人兴奋的进展。

iOS >= 15 上的注入

在此过程中,我们还定位到 iOS 上的一个崩溃:在 iOS >= 15 上注入期间,frida-server 会因 EXC_GUARD 被终止。该问题也及时在发布前得到修复!

DebugSymbol API

另一项好消息是 @mrmacete 改进了 DebugSymbol API,使其始终提供完整路径,而非仅文件名。这解决了各平台后端长期存在的不一致。他还公开了 column,因此除行号外也能获得列号。

更快的 Interceptor.replace()

最后还要介绍 Interceptor 的一项新改进。对从 C 使用它的用户,现在新增 replace_fast() 作为 replace() 的补充。这个快速变体会生成直接跳转到替换函数的内联 Hook。仍可调用原函数,但必须通过 Interceptor 以可选输出参数提供的函数指针调用;它也不能与同一目标上的 attach() 组合使用。不过速度快得多,需要 Hook 热点代码路径中的函数时值得留意。

结语

本次内容就是这些。尽情体验吧!

变更日志

  • darwin:在加固进程中禁用高级功能。
  • darwin:将 GLib 的 MainContext 移植为使用 kqueue() 而不是 poll()。
  • darwin:修复 iOS >= 15 注入期间的 EXC_GUARD。
  • package-server-ios:将 launchd plist 移植到 iOS 16。
  • gadget:加载期间不隐藏主线程。
  • debug-symbol:确保路径为绝对路径并新增 column 字段。感谢 @mrmacete!
  • darwin:新增 query_hardened()。
  • interceptor:新增 replace_fast()。
  • interceptor:减少每个目标的内存用量。