作者:0xcc
原文链接:https://mp.weixin.qq.com/s/PGC7LKu-oC5ZaRxLFrhTsg
请注意本文与 kernelcache 没有任何关系。
只要逆向分析过 iOS 用户态程序,对 dyld_shared_cache [1] (下文简称 dsc)都不会陌生。这个机制将所有系统内置的动态链接库都绑定起来,变成一坨巨大的二进制文件,无疑给反编译工作带来了额外的工作量和难以磨灭的心理阴影。而近期转正的 macOS Big Sur 也用上了 dsc,本来岁月静好的桌面平台研究也突然变得麻烦起来。
分析 DSC 通常的做法是使用 dsc_extractor 提取出单独的库文件,然后像对待普通 MachO 二进制一样交给各种反编译器。这个方法简单快捷,就是会丢符号。
因为 dsc 在生成的时候会舍弃一些动态绑定功能,例如相当一部分原本使用 got 调用的函数,操作数被直接替换成真正的目标函数地址。即使仍然使用 __stubs 做动态绑定,也会出现链接到另一个库的情况。
如果简单地分割成单独的文件,这些函数所在的 segment 属于另一个库,就会变成类似 memory[0x10ABCDEF] 的无效地址。
IDA 在 7.2 当中强化了 dscu (dyld_shared_cache utils)的功能 [2] ,可参考官方文档的 IDA: IDA 7.2 – The Mac Rundown。
简单说就是采用了一种逐步加载的策略。一开始可以只选择 single module,然后在缺失的地址上右键允许动态载入新的 segment 或者模块。也可以在 IDAPython 当中使用封装过的 dscu_load_module 和 dscu_load_region 函数来实现同样的功能。
在 7.2 的时候这个功能还不够完善,原本只想还原一个 call stub 的符号名,却把这个 auth_stubs 所在的整个库的 text 载入到反编译进程当中。笔者曾经自作多情写了一个 IDAPython 插件解决了这个需求,只分析单个模块,然后修复其中的 Class、selector 等运行时信息和符号的引用。
然而 7.5 之后就优化了载入逻辑,目前不会再出现修复一个符号,分析整个模块的问题,我的插件就失去了意义。
下面进入硬核模式。
在一开始载入 dsc 的对话框其实有三个选项:
1.单个模块
2.单个模块和依赖项
3.整个文件
如果我们在分析代码的时候其实没有明确的目的,或者只是想找一些特定函数的全局交叉引用,那么前两个模式就不太够用了。
完整分析确实需要耗费大量时间,但经过一些尝试发现,倒也不是不可能的任务。一些小动作,其实可以提升 IDA Pro 分析大文件的效率。
首先分析大文件自然是挂机操作,硬件方面配置越高越好。记得关闭机器的休眠设置,电源选项设置为最大性能。
在分析大文件时,非常不推荐使用默认的 Qt 图形界面。如果不小心已经在用这个界面,而且左下角显示 AU 还一直在分析,舍不得中断当前进度,那么可以先关闭左侧的函数列表窗口。许多人都发现这个窗格一旦打开,IDA 会不断对这个变化的中的列表做无用的排序,从而拖慢分析速度。
IDA 也提供字符界面,在安装目录当中有 idat(.exe) 和 idat64 两个命令。相对来说,字符界面似乎比图形界面快上一些。
另外在分析 dsc 格式时常常会遇到一些 IDA 认为损坏的信息。如果没有使用脚本模式,就会弹出一个模态的消息框一直阻塞,直到用户关闭并勾选“下次不要再显示”。
所以最好还是写一段简单的 python,分析、保存,一气呵成。
import idc # generate an empty idb idc.auto_mark_range(0, idc.BADADDR, idc.AU_FINAL) idc.auto_wait() idc.qexit(0)
然后走命令行模式无人值守:
idat64 -c -A -Sidb.py -Lcache.log -odyld_shared_cache_arm64e dyld_shared_cache_arm64e
灵异的事情是,同样的一个文件,mac 下的 IDA 要比 Windows 下分析快很多。Linux 暂未实际测试。即使生成好的 idb 重新打开, mac 下速度仍然占优势。
笔者粗略估计了时间,在 i7 的 mac Mini 上和 i9 的台式机 Windows 上分析同一个文件,两边都是 SSD。mac Mini 仅用了三天左右结束,而 Windows 则要足足挂机一星期。
听说 M1 芯片暴打老师傅,有兴趣的读者也可以测一测。
文件分析好之后,找一个符号也是一件头痛的事情。符号名搜索不用说,搜一下卡一下,还是算了。
不过使用内置的跳转功能,还是能基本完成分析工作的。
g 快捷键除了可以输入地址之外,还支持直接跳转到符号。而 IDA 为 ObjectiveC 相关的运行时信息生成的符号也是有迹可循的。
例如查找所有调用了 interfaceWithProtocol: 这个方法的交叉引用,可以跳转到 selRef_interfaceWithProtocol: (注意在 IDA 界面中会把冒号替换成下划线,请按原样输入)
Foundation:__objc_selrefs:00000001C73672F8 selRef_interfaceWithProtocol_ DCQ sel_interfaceWithProtocol_ ; "interfaceWithProtocol:"
接着对右侧的 sel_interfaceWithProtocol: 做交叉引用即可。
那么为什么不直接跳转到 sel_interfaceWithProtocol: 上呢?
IDA Pro 目前有一个 bug,在 dsc 里直接跳转到 selector 上会显示成一个单独的 EXTERN 段,这个 segment 里的鼠标操作不正常,会出现选不中的情况。
回到刚才的交叉引用,除了调用的位置之外,在其中搜索 __objc2_meth 还可以定位到这个 selector 对应的方法的结构体(如果有多个类上的重名方法,都会显示)
而 __objc2_mth 的第三个成员就是函数的 IMP,双击跳转过去即可。
对于已知的类和方法,也可以直接用 g 快捷键跳转,例如:
+[NSXPCInterface interfaceWithProtocol:]
回车直达。
而要枚举某个 class 所有的 class methods 和 instance methods,还可以使用如下的符号名:
- _OBJC_CLASS_METHODS_NSXPCInterface
- _OBJC_INSTANCE_METHODS_NSXPCInterface
这样的导航比关键字舒服多了,不过只能支持完全匹配。如果仍然查找子串,还是老老实实地用搜索。又不是不能用。
对于 C 的库函数符号,例如 getenv,直接跳转到 _getenv 未必是本尊,也可能是某个 __stub。
在这种库函数链接的时候还发现有一些别名的现象,例如会生成多个 j_getenv_X (x 为数字)的符号。为了对全局的调用做分析,只能是手写脚本多一层向上的交叉引用,否则会出现较多遗漏。
参考资料
[1]https://iphonedevwiki.net/index.php/Dyld_shared_cache
[2]https://www.hex-rays.com/products/ida/news/7_2/the_mac_rundown/
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1551/