Hook汇编函数(objc_msgSend)
一、概览
Hook
汇编函数,首先可以了解一下汇编的本质和编译的本质。
汇编本质:操作寄存器和内存。如将数据暂存寄存器,将寄存器值存入内存,读内存值到寄存器,管理内存。
编译本质:编译器按照一定规则将高级语言翻译成汇编语言。如记录上下文,运算指令转换,查表等。
灵魂三问:
1、为什么会有汇编函数呢?
2、为什么汇编效率高不用汇编编码呢?
3、为什么汇编编码执行效率会比高级语言编码效率高呢?
灵魂三答:
1、因为编函数效率高;
2、因为汇编难以阅读和理解,不便于维护,敲多了四肢会变的僵硬;
3、因为编译器并不是很聪明,它只会按照流程手册办事,总喜欢把买来的萝卜放框里又拿出来(但它会变的聪明起来)。
不要问我为什么知道,我猜的……
二、高级语言到汇编
应用程序的诞生:高级语言-预编译-编译
-汇编-链接-可执行文件
因为主要看下如何hook
汇编函数,这里只看编译
过程,也就是高级语言到汇编。先了解一下高级代码和汇编代码的对应关系(开发工具Xcode
)。
1、用C
实现求和功能
1 | int sumFunc(int a, int b) { |
在函数调用出下断点,并切换为汇编查看,观察编译后的汇编代码(Debug-Debug Workflow-disassembly
):
1 | ASMDemo`-[ViewController viewDidLoad]: |
让我解释下:
x30
:也叫链接寄存器(Link Register
),简称lr
寄存器,存放返回的地址w0/w1
:通用寄存器x0/x1
的低32
位,用来存放函数的参数,也用作存放返回值bl
:是跳转指令,有子函数调用,就会保存x30
寄存器值到内存,以免x30
值被子函数修改ret
:返回指令,主要任务读取x30
寄存器存放的地址,并跳转到该地址处
知道上面的寄存器及指令,就可以理解一个函数是如何调用和返回的了(自己理一下)。
再看看求和函数的汇编指令:
1 | ASMDemo`sumFunc: |
- 第一步:开辟栈空间,栈区开辟方向由高低向低地址,因此使用
sub
减运算指令重新指定栈顶位置 - 第二步:存储参数,汇编中参数是通过寄存器传递,存储目的是保留上下文,因为寄存器会被重复使用
- 第三步:读取内存值到新寄存器
- 第四步:求和运算,并将结果存到
w0
寄存器中,返回值一般使用w0~w7
寄存器(w0
是x0
的低32位
) - 第五步:执行
ret
指令,读取x30
存放的地址,跳转到该地址,完成子函数返回
这里截图看下x30
寄存器的地址:
这样对比可以看出bl
和ret
在子函数调用和返回的作用。以上也就是编译规则的一部分,任何高级代码都会按照改则生成指定的汇编指令,CPU
会逐条执行每一句汇编指令,每一条汇编指令耗时是固定的,指令越多耗时越多。
为什么说高级语言效率低呢,下面用汇编实现一个相同的功能,对比一下。
2、用汇编实现求和功能
1 | .text |
运行结果如下:
对比高级语言生成的汇编指令,实现同样的功能,直接使用汇编编码,效率会高n
倍。这就是为什么有些使用频次较高的函数,需要使用汇编实现。汇编能够提升效率、降低功耗。
知道高级语言和汇编的关系后,那汇编函数可以hook
了吗?
首先声明
hook
是针对系统函数的,一般会使用fishhook
来实现,如果用来hook
系统汇编函数,在调用系统原始汇编函数时,则存在传参问题,因为汇编函数是通过寄存器传参的,而高级语言中无法直接设置寄存器,这时候就需要借助内嵌汇编
来解决传参问题。
内嵌汇编实现前,先熟悉下裸函数
。
三、裸函数
裸函数,就是在编译时不会生成保存上下文
和退出子函数
的指令,内部只能编写汇编指令。借用裸函数可以构建一个纯净的汇编环境。
编写函数和裸函数,观察生成的汇编指令的区别
普通函数代码:
1 | void normalFunc(int a, int b) { |
裸函数代码:
1 | __attribute__((__naked__)) |
普通函数编译后的汇编:
1 | ASMDemo`normalFunc: |
裸函数编译后的汇编:
1 | ASMDemo`nakedFunc: |
- 参数一:关联线程私有数据的key
- 参数二:一个析构函数,当线程结束时会调用该函数
2、取线程中的私有数据(无需指定线程id)
1 | void* _Nullable pthread_getspecific(pthread_key_t); |
3、设置线程中的私有数据
1 | int pthread_setspecific(pthread_key_t , const void * _Nullable); |
- 参数一:指定
key
- 参数二:指定数据,如结构体、数组
4、删除特定的key,并清除与key关联的线程特定的数据,无需用户释放
1 | int pthread_key_delete(pthread_key_t); |
使用示例:
1 |
|
输出:
1 | ① key=267 data_count=100 thread=<NSThread: 0x280c5e900>{number = 2, name = (null)} |
因此可以使用线程私有数据绑定,来解决多线程中数据安全问题。
五、Hook汇编函数objc_msgSend
Hook思路
1、保存上下文,即将寄存器值保存到内存,避免寄存器值被子函数修改;
2、汇编调用c函数,将参数传递到c语言环境,便于对记录hook数据(封装一个汇编函数),此处还需要保存lr链接寄存器的值,即返回的地址,解决多线程对lr值的修改(使用TSD技术点解决);
3、恢复上下文,由于调用了c函数,寄存器值会被修改,返回后不能直接使用,需要将内存值重新赋值给寄存器;
4、调用原始汇编函数;
5、保存上下文,保存寄存器值到内存,准备调用恢复lr值;
6、恢复lr链接寄存器的值,保证执行完成后正确返回;
7、恢复上下文,将内存的值重新赋值给寄存器;
8、完成hook任务后返回到上一级函数中。
具体实现如下:
创建一个c函数文件ASMHook.c
1 |
|
调用:
1 |
|
六、应用
- 打印方法调用
- 监控方法执行耗时
demo:https://github.com/yahibo/iOSReverse/tree/master/Hook汇编函数