背景
在上一篇「为什么使用汇编可以 Hook objc_msgSend(上)- 汇编基础」中,我们介绍了 ARM64 中的汇编基础,并且知道了在汇编当中,调用一个方法的前后在栈空间是怎样表现的,以及内存中的几个特殊寄存器是如何操作的。
今天这篇文章,我们来详细详细分析一下使用汇编来 Hook objc_msgSend
的全部流程。
Hook 思路梳理
对于 objc_msgSend
这个我们要 Hook 的方法,我们首先要搞清楚,这是一个什么样的方法?我需要用什么方案才能 Hook 到它的入口。首先我们来整理以下我们拥有的 Hook 方案:
基于 Objective-C Runtime 的 Method Swizzling:也就是我们经常使用的
class_replaceMethod
方法;基于 fishhook 的 Hook:由于在 Mach-O 当中,有 Bind 和 Lazy Bind 的两个概念,所以 Facebook 通过修改
__la_symbol
和__nl_symbol
两个表的指针,在二次调用的时候,直接通过__la_symbol_ptr
找到函数地址直接调用,从而不用多次繁琐的进行函数寻址;基于 Dobby 的 Inline Hook:Dobby 是通过插入
__zDATA
段和__zTEXT
段到 Mach-O 中。__zDATA
用来记录 Hook 信息(Hook 数量、每个 Hook 方法的地址)、每个 Hook 方法的信息(函数地址、跳转指令地址、写 Hook 函数的接口地址)、每个 Hook 的接口(指针)。__zText
用来记录每个 Hook 函数的跳转指令。Dobby 通过 mmap 把整个 Mach-O 文件映射到用户的内存空间,写入完成保存本地。所以 Dobby 并不是在原 Mach-O 上进行操作,而是重新生成并替换。 关于 Dobby 中的奇技淫巧还有很多,如果有可能后续会出一个分析文章(插旗子)。
当然,成熟的 Hook 方案还有很多,并且这些经常出现在逆向工程中,我这里只是列举了最常用的三个。
什么是 Inline Hook
首先给个定义:Inline Hook 就是在运行的流程中插入跳转指令来抢夺运行流程的一个方法。Inline Hook 示意图
上图展示了 Inline Hook 大致的思路:
将原函数的前 N 个字节搬运到 Hook 函数的前 N 个字节;
然后将原函数的前 N 个字节填充跳转到 Hook 函数的跳转指令;
在 Hook 函数末尾几个字节填充跳转回原函数 +N 的跳转指令;
以上的 N 有多大,取决于你的跳转指令写得有多大(占用了多少指令)。
相较与 Inline Hook, fishhook 使用的是很 Trick 的方式,通过劫持 stub 从而达到替换的目的。
在罗巍的「iOS 应用逆向与安全之道」中将 fishhook 归类成 Inline Hook。从广义的定义上来说,只要完成重定向到我们自己的方法,并在远方法前后可定制处理就可以算作 Inline Hook。但是我和页面仔讨论的结果是,这里的 Inline 应该要理解为 Inline Modification,这种技术通常如上图所示,覆盖方法开头指令中的前几个字节,完成 Hook 方法的重定向工作。
fishhook 完成跳转
汇编实现的 objc_msgSend
为什么可以当作 C 方法?
通过查看 objc_msgSend
,我们知道 Runtime 的 Method Swizzling 并不适用,因为它并不是 Objective-C 方法,调用时并不会有我们经常说的“消息转发”;通过查看 Runtime 源码,我们发现 objc_msgSend
是使用纯汇编实现函数,通过汇编文件我们可以看到以下定义:
ENTRY _objc_msgSend
这里的 ENTRY
是什么意思呢?在文件中继续搜索 ENTRY
我们找到了这么一个宏:
.macro ENTRY /* name */
.text
.align 5
.globl $0
$0:
.endmacro
这里定义了一个汇编宏,表示在 text 段定义一个 global 的 _objc_msgSend
,$0
其实就是这个宏传入的参数,也就是一个方法入口。我们可以手动将这个宏来展开:
.text
.align 5
.globl _objc_msgSend
; ...
这里我们发现,在第三行的位置通过 C 的 name mangling 命名规则,将符号 _objc_msgSend
映射为 C 的全局方法符号。也就是说,这段汇编可以通过头文件声明,便已完成了 C 的函数定义。我们在后续处理的时候可以将其视为 C 方法。
当然我们也可以使用 MachOView 来验证这个符号名。
验证 _objc_msgSend 为全局 C 符号
这里如果不太明白如何使用纯汇编实现 C 方法,可以看高级页面仔的这篇文章「在Xcode工程中嵌入汇编代码」。
fishhook 实现的基础
既然我们将 objc_msgSend
已经视作 C 方法,那么我就可以使用 fishhook 来完成 Inline Hook 的第一步:跳到 Hook 方法。fishhook 是如何做的呢?它是在什么阶段完成这个动作的?来看下图:
fishhook 调用时机
我们知道,Apple 自身的共享缓存库其实不会编译进我们自己的 Mach-O 中的,而是在 App 启动后的动态链接才会去做重绑定操作。这里我们要如何去验证呢?首先我们写一个 fishhook 的 demo:
#import "ViewCon