fishHook原理----(1)
为了探索fishHOOK
的原理,我们首先要探索一下其HOOK
流程。我们首先新建个工程fishHookDemo
,编写HOOK
系统函数NSLog
的代码。在HOOK
之前和之后分别调用NSLog
方法,我们都是到由于aslr
的和虚拟地址存,在编译的时候外部C函数的真实地址是不知道的,DYLD
在加载一个MachO
文件的时候,需要重新找到Code Function
框架的NSLog
的地址。在如下:
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"123");
struct rebinding psyLog;
psyLog.name = "NSLog";
psyLog.replacement = psy_Log;
// 原函数
psyLog.replaced = (void *)&sysLog;
struct rebinding psysh[] = {
psyLog
};
rebind_symbols(psysh, 1);
NSLog(@"456");
}
// 函数指针
static void (*sysLog)(NSString *format, ...);
//新函数
void psy_Log(NSString *format, ...)
{
format = [format stringByAppendingString:@"被 psyLog Hook了!"];
sysLog(format);
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
// 变异的时候不知道,NSLog的真实地址
NSLog(@"这是NSLog");
}
在NSLog
第一次调用的时候下断点。然后通过LLDB
查看一下,在MachO
文件中内存地址和数据。通过image list
拿到基地址0x000000010036c000
,找到FishHookDemo
的MachO
文件,使用MachOView
打开,找到符号表中NSLog
,其offset是00008010
,对应的Data是0x10000650c
(这个是加过slide的,基地址也是加过slide的,所以为了不重复添加,去掉前面的1,取0x650c
),0x650c + 0x000000010036c000 = 0x010037250c
。我们也可以使用LLDB命令通过偏移8010
验证一下,读取内存数据:memory read 0x000000010036c000+0x8010
,可看到结果是一样的,都是0x010037250c
。
反编译0x010037250c
我们还可以反编译看一下汇编指令:dis -s 0x010037250c
,发现它会 b
到地址0x1003724f4
,通过计算(0x1003724f4 - 0x000000010036c000
)我们得到偏移值0x64F4
,查看MachO
,也就是最后都 b
到了一段代码。
这段代码干了什么?我们看到最后会
br x16
,这个x16
对应的地址是0x100008008
(也就是偏移8008
),我们找到偏移8008
是在Non-Lazy Symbol Pointers
非懒加载表中,调用的是dyld的dyld_stub_binder
函数完成符号绑定操作,将NSLog
与实际内存地址绑定:image.png
过掉断点在调用fishHook
函数rebind_symbols
之前,看一下偏移0x8010
内存的数据memory read 0x000000010036c000+0x8010
,发现他已经变成0x01fe90444c
,反编译看一下,这个地址,确实就是实际的NSLog
的地址,如下图:
将断点下在调用rebind_symbols
之后的第二次调用NSLog
之前位置,看一下偏移0x8010
内存的数据memory read 0x000000010036c000+0x8010
,此时已经变成0x100371578
,反编译看一下,这个地址就是实际的psy_Log
的地址,成功的实现了NSLog
的HOOK
,如下图:
我们还发现,系统的C函数,fishHook
可以所以实现HOOK
,但是自定义的C
函数,我们却不能简单的通过fishHook
实现。这个符号绑定过程就是PIC
技术,那PIC
技术就是简单的完成符号和地址的绑定吗?我们思考一下,为什么rebind_symbols
之后第二次调用NSLog
,内存memory read 0x000000010036c000+0x8010
地址指向的不是一段代码(像第一次调用一样,如上图:“反编译0x010037250c”),而是我们要的psy_Log
,难道是rebind_symbols
做了什么事?其实不是,我们可以新建个工程验证一下,什么都不做,也不引入fishHook
直接调用两次NSLog
,发现第二次调用也是一样的指向NSLog
的地址。我们再通过MachOView
分析发现在Section的_TEXT,stubs
,在偏移63EC
有一个对应的NSLog
,其对应的Data
又是干嘛的呢(如下图)?我们称之为“桩”,data
是1F2003D510E1005800021FD6
其实是一段代码的指令符号,通过这个桩去执行一段代码。不光是NSLog
,_NSStringFromClass
等后面的,data
都是1F2003D510E1005800021FD6
,说明所有系统外部C函数的符号绑定都是先通过一样的桩。然后找到Lazy Symbol Pointers
懒加载符号的Data
在决定是否进行符号绑定操作,还是已经指向实际的地址。
总结一下
1、程序一直行,进行非懒加载的符号绑定,调用dyld_stub_binder
函数;
2、当我们第一次调用NSLog
的时候,首先会到找桩去执行一段代码,这段代码就是去找到懒加载里面的值去执行,然后它默认会 b
到另一段代码,就是去找non_Lazy Symbol
去找dyld_stub_binder
的真实函数的地址完成符号绑定,第一次绑定完成之后,完成了第一次调用;
3、当第二次调用NSLog
的时候,还是会找到桩去执行一段代码,代码会跳到懒加载里面的Data的值去执行,但是这个时候,Data
里面的值指向了NSLog
的真实地址,不再是指向查找dyld_stub_binder
的真实函数的地址完成符号绑定;
4、外部函数先找桩;
那为什么要建符号表呢?因为外部函数的地址不确定,需要符号变在运行的时候实现查找到真实的地址执行。
5、并不是所有的C语言调用都是静态的,C的外部函数有时候就是对符号的重绑定;