众所周知,在使用 IDA 进行 iOS 逆向工程时,仅仅依赖静态分析很难直接确定方法的 Callers,借助于 Decompiler 和 IDA 自己的分析能力仅能分析出非常有限的 objc_msgSend 交叉引用。目前一般的解法是借助于动态调试的 backtrace 或是 IDA 脚本进行交叉引用的重建。
因此笔者做了一个工具,该工具支持加载 Mach-O 文件的关键信息,并结合 capstone 静态分析 + unicorn 动态分析结合的方式去模拟执行所有的 objc method,并尽可能建立起方法间的交叉引用,最后生成一个 IDA Script,用于将分析结果导入到 IDA 中。
话不多说,我们先来看一下分析的效果,以 We*t 为例,通过笔者开发的扫描工具 iblessing 可以模拟 Mach-O 的 class 加载、dyld 符号地址解析、local 符号解析、静态内存映射等过程,并启动多个 unicorn 实例去模拟执行所有的 objc 方法,并生成 objc_msgSend 的交叉引用报告:
报告的格式是方法信息的简单序列化,包含了方法地址、方法签名、前序调用和后续调用的信息:
报告可以通过两种方式进行可视化,其一是通过 iblessing 自带的 local query server:
另一种则是通过 iblessing 的 generator 组件生成导入 XREFs 的 IDA Script:
生成的 Script 内容为:
导入 IDA 后再查看交叉引用的效果:
要分析 objc_msgSend 动态调用,核心原理主要有两点:
在 aarch64 下,_objc_msgSend
需要通过一个 text stub 去取位于 __DATA,__la_symbol_ptr
的 _objc_msgSend
指针,因此我们只要记录下 dynamic symbol table 中的 text stub 地址即可在遇到 bl 时正确识别该符号:
因此这里的关键是如何正确的加载 dynamic symbol table,这里我们可以参考 fishhook 中的相关处理,主要步骤有:
关键代码如下,摘自 https://github.com/Soulghost/iblessing/blob/master/iblessing/iblessing/mach-o/symtab/SymbolTable.cpp#L71
这里的加载包括两部分:
对于 class realize,主要是为了递归建立类对象的内存结构,并构建起 method 和 ivar 的索引,这部分代码可以直接参考开源的 objc runtime 中的 realizeClassWithoutSwift 实现,这里的逻辑比较冗长就不贴了,在 iblessing 中对应的代码在 https://github.com/Soulghost/iblessing/blob/master/iblessing/iblessing/mach-o/runtime/ObjcClass.cpp#L24 。 这里说两个坑,一个是注意 Swift 下的 class_ro_t 的对齐问题,另一个是内存读取的安全性问题,目前 iblessing 中是直接读取 mapped file,有 segment fault 风险,后续打算借助 unicorn 虚拟内存来读写,来防止 segment fault 导致进程崩溃。
这一块主要是顺着 dyld_info
去执行各种绑定指令,这里归功于 dyld 良好的代码设计,我们可以直接从中拷贝 void ImageLoaderMachOCompressed::eachBind(const LinkContext& context, bind_handler handler)
的代码来完成这些 non-lazy 符号的绑定:
这里的模拟执行是基于 unicorn 的,这里的操作主要包括:
上面的描述中省略了一些细节,下面我们分别来说明。
这里针对每个 unicorn engine 构建了 12 GB 的虚拟内存,其中前 4GB 为 PAGE_ZERO,后面为正常的堆栈:
随后将 Mach-O 文件映射进来,需要注意的是这里的映射方式是不完整的,有一些段需要逐段映射才能得到正确结果,但对于当前的模拟执行要求而言不必去做逐段映射 :
这里我们还同时设置了 FPEN 以便能正确执行 SIMD 的相关操作,这里的 uc_context
作为一个初始状态的快照,可以在每次启动 unicorn engine 之前重置虚拟内存。
这一步在有了 realized class list 的情况下是非常简单地,只需要顺着 isa 聚合所有 method 即可,这里就不赘述了。
在模拟执行之前,我们要为 engine 设置 3 个 hook,一个指令 hook 和 2 个内存 hook,其中指令 hook 用于分析指令并协助 engine 做出下一步的决策;内存 hook 主要是处理遇到内存问题后能继续执行:
模拟执行的核心逻辑位于 insn_hook_callback 中,我们稍后重点讲解。在设定完这些基础信息后,我们就可以通过 method 取到 IMP,设定好 engine 环境开始执行:
这里我们会借助 insn_hook_callback 去处理指令,这里我们会借助 capstone 将机器码进行反汇编,随后根据当前 PC 和指令的内容作出决策。
由于我们选择跳过所有分支逻辑,可能会错误的陷入死循环,这里我们需要不断记录上一次的 PC 值 lastPC,如果发现新 PC 的值小于上次的 PC,则说明遇到了回跳的循环,我们选择跳到 lastPC + 4 的地址继续执行:
对于 objc 方法的 IMP,它的返回指令不一定是 RET
,还可能是:
等等一系列指令,因此我们封装了方法来检测方法返回:
由于我们只关心 objc_msgSend 调用,因此可以忽略掉其他的分支和条件分支逻辑继续执行,但这样会错过一些 path,有待后续优化:
这里需要注意的是有时候 objc_msgSend
是以 b
而非 bl
的形式调用的,例如:
我们的探测代码也非常简单,主要依赖于前面对 dynamic symbol table 的建立:
到这里我们已经获取到了 x0 和 x1,其中 x1 作为 SEL 其获取方法很简单,绝大多数情况下可以从虚拟内存中的 __objc_selrefs
中取到,但也不免有一些动态合成的 SEL 需要依赖模拟执行才能获取到,由于目前的模拟执行没有链接和执行外部符号的能力,无法处理这类 SEL,因此需要针对这类 SEL 做一个排除:
这里的 x1 在模拟执行启动时会写入用 SelfSelectorTrickMask 标记的值,用来指示是否复用了方法入参的 SEL,如果不是则尝试在虚拟内存中读取,这里的 vm2 也是一个 unicorn 实例,采用了逐段映射的方法构建起虚拟内存,来保证实现 Mach-O 文件加载后的正确寻址:
对于 x0,情况就比较多了,主要有:
对于前两种情况非常好处理,第一种可以类似 SEL 做一个 SelfInstanceTrickMask 标记来判断,而 Class 的情况模拟执行能够正确解析,此时的 x0 就是类对象指针,拿着它去已加载的 objc class 索引表中查询即可。
对于 x0 = instance 的情况,目前 iblessing 只支持来自 ivar 的,即将支持局部 allocate 的。对于 ivar 的支持,这里有一些 trick,首先在 class 加载时会记下一份 ivar getter 的索引表,当我们遇到 ivar 的 getter 时会用 IvarInstanceTrickMask 去标记 x0,那么此后如果是对 ivar instance 进行的 objc_msgSend 调用我们就能正确的检测:
目前未能支持 allocate 方式的主要原因是 iblessing 的一部分虚拟内存是直接在当前进程内模拟的,没有基于 unicorn 做虚拟化,从而导致内存异常会直接崩溃,下面一步需要将所有的内存模拟都迁移为 unicorn 方式才能安全的探测 allocated instance。
对于单 unicorn 实例似乎只能跑满 1 个 Core,因此在这里的分析中使用了多个 unicorn 实例并发执行来提升了分析速度,这里的改造思路是将所有的 oc 方法分成 N 组,每组独占一个 unicorn 实例然后并发执行,unicorn 实例以自身为索引查询索引表获取执行上下文,当检测到 objc_msgSend
需要写入交叉引用表时用锁保护临界区:
上面主要介绍了整个交叉引用分析的核心逻辑,其中还有不少细节,例如从实例到类方法的调用可能是通过如下方式进行的:
对于这类调用,在汇编层面会有两种表达:
因此我们需要分别模拟这两种情况下的结果从而得到正确的返回值供后续分析。其他细节这里就不展开了,欢迎大家去源码里挑猫饼和找答案 https://github.com/Soulghost/iblessing 。
目前整个交叉引用分析器已经有了不错的分析能力,但对于反射、创建和入参的分析能力还非常有限,但用作 iOS App 辅助逆向分析上已经有不错的效果。相比于直接编写 IDA Script,iblessing 有更快的执行速度和更加自由的玩法,目前刚刚开源,处于 beta 阶段,欢迎大家试用。
本文介绍的 objc_msgSend 交叉引用分析只是 iblessing 的一个模块,此外它还包含了 App 信息收集, symbol wrapper 检测与转换, Class 引用扫描等功能,今后将不断迭代以提升其分析能力,助力 iOS 逆向工程相关工作。
欢迎大家 Star: https://github.com/Soulghost/iblessing
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2020-8-1 19:20
被Sou1gh0st编辑
,原因: fix typo