乐趣区

关于ios:被冰封的-BugFishhook-Crash-修复纪实

作者:郝连福,业界资深计算机技术专家,现任声网 Agora 首席前端架构师。先后负责过 Principal Engineer/Engineering Director(UTStarcom)、Sr. architect(Intel)、T4 architect(YY)等职,曾设计开发电信核心网专用操作系统、高性能 TCP/IP 协定栈、以及声网 SDK 架构重构等重大项目。

引言

本文是声网 Agora 与 RTC 开发者社区独特发动的 Dev for Dev(Developer for Developer)互动翻新实际流动的开篇,同时也是开源技术爱好者在一线工作中的实在记录。文中遇到的状况颇具代表性,特整顿分享进去以飨读者。


通常在 iOS 中实现利用 Hook 的形式有以下三种:

1.Method Swizzling:利用 OC(Objective C)的 Runtime 个性,动静扭转 SEL(办法编号)IMP(办法实现)的对应关系,达到 OC 办法调用流程扭转的目标,只实用于动静的 OC 办法;

2.Fishhook:FaceBook(现更名为 Meta)提供的一个动静批改链接 Mach-O 文件的工具,利用 Mach-O 文件加载原理,通过批改懒加载和非懒加载两个表的指针实现 C 函数 HOOK 的成果;实用于动态的 C 办法;

3.Cydia Substrate:原名为 Mobile Substrate,是一个弱小的框架,它的次要作用是针对 OC 办法、C 函数以及函数地址进行 HOOK 操作,实用于 OC 办法、C 函数以及函数地址,亦实用于 Android 平台。

Fishhook 是一个由 Meta 公司开源的第三方框架,它可能在模拟器和设施上动静地从新绑定运行在 iOS/macOS 上的 Mach-O 二进制文件的符号,从而实现动静批改 C 语言函数,罕用于利用的调试 / 追踪。这个框架只蕴含两个外围文件:fishhook.c 以及 fishhook.h 所以十分轻量,在许多企业级利用中颇受青眼。然而这个以简练著称的开源我的项目中,却埋藏着一个不易觉察的问题……

随着 iOS 15 Beta 版的公布,许多开发者发现了广泛的应用程序解体──这通常由零碎兼容性问题引发,而随着排查过程的不断深入,咱们发现问题并没有那么简略。起初,开发者把问题反馈到 Fishhook 之后,有不同的个人和集体奉献了好几个修复的 PR,但都未能从根本上解决这个问题。在仔细分析了 iOS 和 macOS 的操作系统内核 XNU 源码后,咱们最终定位到了问题的 RootCause。

对 Fishhook Crash 问题的溯源

为了定位问题,咱们通常会依据现有的报错日志尝试对问题进行复现,通过调试追踪咱们发现,在 iOS 15 或者 macOS 12 的环境下 Fishhook 代码在重绑定符号时会 100% 地产生解体景象,正是这个解体导致集成了 Fishhook 的利用变的不可用。鉴于这个问题的影响很大,一些应用了 fishhook 我的项目的利用在发现问题后紧急 移除了该组件 以缓解其影响。

造成 fishhook 解体的根本原因

Fishhook 的工作原理须要 Hook 批改符号动静绑定数据段,这些数据段的默认权限个别是 只读 的,所以须要加上“写”权限能力批改,而问题恰好就出在这里──咱们在排查过程中发现 Fishhook 里 减少“写”权限的代码存在 Bug,问题相干代码如下:

这段代码外面有 3 个严重错误,为了便于浏览,咱们别离以 红绿蓝 3 个色彩的框将相干代码标识进去,对这些谬误的具体解释如下:

1. 首先,不能仅依据 __DATA_CONST 这个 segname 来判断是否须要减少“写”权限,因为从 iOS 14.5 甚至更早的版本开始,都须要 Hook 一个叫 __AUTH_CONST 的 segment,因而只 Hook 一个 __DATA_CONST 字段是 不够的

2. 其次,获取以后的 vm prot 时,传错了地址,不应该是 rebindings,因为咱们要写入的地址是 indirect_symbol_bindings

3. 最初,XNU 内核的 C-O-W 机制与 Linux Kernel 不同,对于 RO 的 vm segment mapping 须要显式指定 VM_PROT_COPY 能力减少“写”权限,然而 XNU BSD 的 mprotect 零碎调用基本就做不到这一点,故而这句 mprotect 零碎调用 形同虚设,相当于什么也没做!XNU MACH 要害代码逻辑如下:

Fishhook 代码存在的上述 3 个谬误叠加在一起,最终导致在批改 indirect_symbol_bindings 所指向的数据时产生了“写”爱护谬误,进而产生的 Crash 影响了整个利用零碎。

修复 Fishhook 解体的最佳办法

既然咱们曾经找到了 Bug 地位所在,修复的思路便只需隔靴搔痒即可:

  • 将原来写错的地址 rebindings 批改成 indirect_symbol_bindings
  • 将 mprotect 零碎调用改成应用 vm_protect 零碎调用,并减少 VM_PROT_COPY 选项;
  • 代码逻辑上批改为只有 vm_protect 零碎调用执行胜利时,能力去做“写”动作。

因而 Bug 修复的外围代码如下:

这里需注意,首先,为符号动静绑定的数据段减少“写”权限时肯定要增加 VM_PROT_COPY 选项,否则写入操作会失败;其次,要在代码逻辑中增加“只有 vm_protect 零碎调用返回胜利”能力真正去执行“写”这些数据段的操作,否则就什么都不要做。

通过严格的测试和重复验证,咱们彻底修复了这个 Bug,并在 2021 年的 6 月 12 日向 Fishhook 官网提交了 PR(https://github.com/facebook/f…),Fishhook 的保护团队在比照了多个修复计划后,最终抉择 Merge 了咱们的修复补丁并将其合并进主分支,至此该问题最终得以解决。

零碎升温(级)使“冰封”的 Bug 得见天日

读者大概率会好奇,为什么在 iOS 15 或者 macOS 12 之前的版本没有这个问题呢?

事实上,在 iOS 15 或者 macOS 12 之前的操作系统本身也存在这个缺点,对这些数据段的爱护并不谨严,对 应该“只读”的数据段并没有去掉“写”权限,咱们考察到相干的证据如下:

在上述证据片段中,protection 数值 3 示意权限为“可读可写”,因而 Fishhook 代码外面做 Hook 动作的“写”操作在老版本的 iOS/macOS 中并没有任何问题。然而 iOS 15/macOS 12 新版本操作系统中对这些数据段的爱护更加严格,对相应的权限做了一些调整──将本应赋予“只读”数据段的“可读可写”权限修改为“只读”,也就是说上述证据片段中 protection 的数值产生了变动,相干的证据如下:

上述代码片段中的 protection 数值 1 代表“只读” ──也理当如此。但正是这种“修改”与原来“不当”的配置产生了逻辑上的抵触,最终 Fishhook 的这个 Bug 在较新的 iOS 15/macOS 12 零碎中裸露进去,导致了重大的解体问题。从代码的角度来看 Fishhook 的这个 Bug 显然是始终存在的,只是在晚期的 iOS 和 macOS 版本中没有形成触发的条件,故而隐患始终被雪藏,直到相干的条件被扭转。

总结

通常在利用开发过程中,本着不反复造轮子和疾速上线、一直迭代的准则,咱们常常会引入第三方模块,尤其是有着广泛应用的底层开源组件。但随着 IT 基础设施的变迁,零碎环境会随着工夫的推移一直减少新个性、摈弃旧实现,在这个过程中因为依赖问题咱们的利用不可避免地会一直遭逢不可用的挑战。作为业务利用的开发者,咱们必须一直进步向上游组件进行问题溯源的能力,秉持开发者的初心,取自开源、回馈开源。

Dev for Dev 专栏介绍

Dev for Dev(Developer for Developer)是声网 Agora 与 RTC 开发者社区独特发动的开发者互动翻新实际流动。透过工程师视角的技术分享、交换碰撞、我的项目共建等多种形式,汇聚开发者的力量,开掘和传递最具价值的技术内容和我的项目,全面开释技术的创造力。

退出移动版