inline hook之fishhook objc_msgSend
Posted 普通网友
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了inline hook之fishhook objc_msgSend相关的知识,希望对你有一定的参考价值。
常见的Hook方案
- 基于 Objective-C Runtime 的 Method Swizzling
- 基于 fishhook 的 Hook
- 基于 Dobby 的 Inline Hook
Inline Hook
Inline Hook 就是在运行的流程中插入跳转指令来抢夺运行流程的一个方法。大体分为三步:
- 将原函数的前 N 个字节搬运到 Hook 函数的前 N 个字节;
- 然后将原函数的前 N 个字节填充跳转到 Hook 函数的跳转指令;
- 在 Hook 函数末尾几个字节填充跳转回原函数 +N 的跳转指令;
以上的 N 有多大,取决于你的跳转指令写得有多大(占用了多少指令)。
相较与 Inline Hook, fishhook 通过劫持 stub 从而达到替换的目的。
fishhook 跳转
通过查看 objc_msgSend ,我们知道 Runtime 的 Method Swizzling 并不适用,因为它并不是 Objective-C 方法,调用时并不会有我们经常说的“消息转发”;
我们可以使用 MachOView 来验证objc_msgSend的符号名:
fishhook实现
既然 objc_msgSend 已经视作 C 方法,那么我就可以使用 fishhook 来完成 Inline Hook 的第一步:跳到 Hook 方法。
Apple 自身的共享缓存库其实不会编译进我们自己的 Mach-O 中的,而是在 App 启动后的动态链接才会去做重绑定操作。
这里我们以某个APP举例,通过nm -n命令查看所有方法的符号以及对应地址:
> nm -n xxxxx
...
U _NSLocalizedRecoverySuggestionErrorKey
U _NSLog
U _NSLogv
U _NSMallocException
...
U _objc_msgSend
U _objc_msgSendSuper
U _objc_msgSendSuper2
...
0000000100000000 T __mh_execute_header
0000000100af3cb8 T __ZN5folly6detail15str_to_floatingIdEENS_8ExpectedIT_NS_14ConversionCodeEEEPNS_5RangeIPKcEE
...
在这里我们就可以发现,其实 NSLog 方法其实并没有地址,这些系统库函数并不会打入到我们的 App 包中;当我们使用它们时,dyld 就要从共享的动态库中查找对应方法,然后将具体的函数地址绑定到之前声明的地方,从而实现系统库方法的调用。
对于这种可在主存中任意位置正确地执行,并且不受其绝对地址影响的技术,在计算机领域称之为 PIC(Position Independent Code)技术。
fishhook 对于 Mach-O 利用
Mach-O 中 __DATA 段有两个 Section 与动态符号绑定有关系:
- __nl_symbol_ptr :存储了 non-lazily 绑定的符号,这些符号在 Mach-O 加载的时候绑定完成;
- __la_symbol_ptr :存储了 lazy 绑定的方法,这些方法在第一次调用时,由 dyld_stub_binder 进行绑定;
既然 __la_symbol_ptr 存储了所有 lazy 绑定的方法,那也就是说在这些位置应该存储了对应方法的地址。
写一段代码验证一下:
#import "ViewController.h"
#import "fishhook.h"
@implementation ViewController
static void (*ori_nslog)(NSString * format, ...);
void new_nslog(NSString * format, ...)
//自定义的替换函数
format = [format stringByAppendingFormat:@" FISHHOOK "];
ori_nslog(format);
- (void)viewDidLoad
[super viewDidLoad];
NSLog(@"hello world");
struct rebinding nslog;
nslog.name = "NSLog";
nslog.replacement = new_nslog;
nslog.replaced = (void *)&ori_nslog;
rebind_symbols((struct rebinding[1])nslog, 1);
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
NSLog(@"hello world");
@end
控制台输出如下:
2020-08-05 16:42:32.698782+0800 Fish[7569:336213] hello world
2020-08-05 16:42:34.032334+0800 Fish[7569:336213] hello world FISHHOOK
2020-08-05 16:42:34.776257+0800 Fish[7569:336213] hello world FISHHOOK
我们在第一个 hello world 的位置增加断点:
使用 image list 命令来获取 App 的基地址:
可以看到App 基地址为 0x0000000106f9a000。
然后我们使用 MachOView 来查看NSLog在 __la_symbol_str 中的偏移量,偏移量为0x5000。
我们使用lldb的x命令来查看一下 基地址+偏移量 位置的数据,发现在此位置的数据是 0x0106f9c3f0 (这一步看不懂的请看上一篇博文里面的大端序和小端序)。
使用反汇编 dis 命令, 来看对应地址所指向的代码段:
那么这段代码到底是我们的 NSLog 的代码吗?我们可以直接看断点的汇编代码:
可以看到汇编中 callq 命令对应对地址是 0x106f9c354 。
对这个地址再次进行 dis -s 反汇编来查看:
可以看到上图中方框圈起来的地址就是上面 基地址+偏移量 这个位置存储的地址。
在这之后,我们再对点击事件中的 NSLog 方法下一个断点,并且点击一下模拟器屏幕来触发一下。
我们再使用 x 和 dis -s 两个命令来查看一下 基地址+偏移量 中的新数据:
可以很清楚的看到此时的NSLog函数的地址已经被替换了:
至此,fishhook Hook C 方法已经完成。
fishhook 总结
fishhook 的 Hook 思路,也就是我们上述所描述的,当第一次调用系统动态库中 C 方法时,去替换掉 __la_symbol_str 的指针,dyld 更新 Mach-O 二进制的 __DATA segment 的 __la_symbol_str 中的指针,使用 rebind_symbol 方法更新两个符号位置来进行符号的重新绑定。。
如果想看具体的实现,推荐去阅读源码(不到两百行)。
以上是关于inline hook之fishhook objc_msgSend的主要内容,如果未能解决你的问题,请参考以下文章