iOS 18.4 - dlsym 被认为是有害的

上周,苹果在所有支持的 iPhone 上发布了 iOS 18.4。在支持 PAC(指针认证)的设备上,在使用dlsym()进行某些符号解析时遇到了一个奇怪的 bug。本博客详细介绍了我们的观察结果和问题的根本原因。

观察结果

  • 在为 arm64e 架构编译的自定义 iOS 应用中首次观察到该 bug(支持 PAC 指令),该应用通过使用dlopen()dlsym()对各种系统函数进行动态符号解析。
  • dlopen()以共享库路径为参数并返回一个句柄,可被dl API 的其他函数使用;dlsym()以句柄和符号名为参数,返回相应的地址,在支持 PAC 的设备上,此地址用指令键 A 和 NULL 上下文签名,可用于 C 中的间接调用。
  • 例如,以下代码动态加载 strcpy 函数地址并将其用作函数指针,日志显示了不透明句柄、签名指针和动态调用的结果,生成的程序集也符合预期。
  • 但当尝试动态解析并使用strcmp()时,应用崩溃,日志显示了错误信息,查看生成的 ips 文件可知原因是内核保护失败,PC 寄存器指向了错误的位置。

一些 PAC 实验

  • 多次运行应用程序,发现指针要么未签名,要么用无效签名签名,手动去除签名并重新签名指针后,一切正常。

调查 bug

  • 提取libsystem_c.dylib动态库,使用ipsw工具,发现\_strcmp有特定标志EXPORT_SYMBOL_FLAGS_REEXPORT,与\_\_platform\_strcmp字符串相关,而strcpy没有触发该 bug。
  • 运行相同的解析器在libsystem\_platform.dylib库上,发现只有\_\_platform\_strcmp有特定标志EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER,表示首次执行时延迟解析真实实现。
  • dlsym()的实现是APIs::dlsym()的简单包装器,会调用Loader::hasExportedSymbol(),如果符号有特定标志且传入Loader::runResolver参数,会执行一系列操作,最后返回结果。通过转储dlsym()调用后的堆栈,发现目标运行时偏移量的高位位被设置,似乎解析器函数返回的指针在转换为偏移量之前未被剥离。
  • 查看编译的dyld代码调用解析器,发现缺少XPACI指令,在 iOS 18.3.2 中该指令存在。

遗留问题

  • 已确定 bug 的根本原因,但不知道苹果是否更改了dyld源代码或编译器认为剥离是不必要的。通过再次运行应用程序并添加双重签名,可重现错误签名,分析发现指针实际上被签名了两次,根据 Arm 规范,当指针返回时未签名是因为第二次签名与第一次签名异或后抵消,而错误签名的唯一可能原因是第二次签名时所有高位位被设置为 1,与规范不符,通过计算验证了理论。一个问题仍然存在,为什么不是所有导入strcmp的应用都崩溃?查看dyld源代码发现加载器总是用Loader::skipResolver参数调用hasExportedSymbol,从而未触发 bug。

结论

找到此 bug 的根本原因是一个漫长的过程,但让我们深入了解了共享缓存、dyld和最近 iPhone 上的 PAC 实现的内部细节,希望苹果能尽快修复它。

阅读 20
0 条评论