inline hook之fishhook objc_msgSend

常见的Hook方案

  • 基于 Objective-C Runtime 的 Method Swizzling
  • 基于 fishhook 的 Hook
  • 基于 Dobby 的 Inline Hook

Inline Hook

Inline Hook 就是在运行的流程中插入跳转指令来抢夺运行流程的一个方法。大体分为三步:

  1. 将原函数的前 N 个字节搬运到 Hook 函数的前 N 个字节;
  2. 然后将原函数的前 N 个字节填充跳转到 Hook 函数的跳转指令;
  3. 在 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 方法更新两个符号位置来进行符号的重新绑定。。
如果想看具体的实现,推荐去阅读源码(不到两百行)。

©️2020 CSDN 皮肤主题: 酷酷鲨 设计师:CSDN官方博客 返回首页