iOS程序在加载到内存时候,会有一个符号绑定的过程。有两种方式,一种是Non-Lazy,一种是Lazy(懒加载)。
通过MachOView,可以看出系统的大部分函数都是懒加载。
NSLog在MachO的偏移8010个字节,就是NSLog的符号
编写一段代码,功能是通过fishhook Hook NSLog函数
- (void)viewDidLoad {
[super viewDidLoad];
//HOOK -- NSLog函数!
NSLog(@"123");
//指定交换的函数
struct rebinding nslog;
nslog.name = "NSLog";
nslog.replacement = myNSLog;
//fishhook 运行的时刻,动态的获取到NSLog的地址。
nslog.replaced = (void *)&sys_nslog;
//结构体数组
struct rebinding rebs[1] = {nslog};
rebind_symbols(rebs, 1);
}
- 运行上边的代码,在NSLog(@"123");打个断点
控制台打印Demo的Mach-O在内存中的起始位置,0x0000000100bc4000
(lldb) image list
[ 0] F7B52DBD-C1FF-3BDD-8220-BCC8493609FB 0x0000000100bc4000 /绝对路径/fishHookDemo.app/fishHookDemo
- 将上边获得的两个地址Mach-O内存中的起始位置和NSLog的便宜位置相加,获得NSLog的符号内存地址
0x0000000100bc4000 + 8010 = 0x100BCC010
读一下内存,并且打印一下汇编代码。从汇编代码中看到目前没有绑定NSLog的实际地址。
(lldb) memory read 0x100BCC010
0x100bcc010: b4 a9 bc 00 01 00 00 00 b0 13 8b 94 01 00 00 00 ................
0x100bcc020: a0 7a 59 98 01 00 00 00 20 aa bc 00 01 00 00 00 .zY..... .......
(lldb) dis -s 0x0100bca9b4 // iOS是小端模式,倒着读前八个字节
0x100bca9b4: ldr w16, 0x100bca9bc
0x100bca9b8: b 0x100bca99c
0x100bca9bc: udf #0x0
0x100bca9c0: ldr w16, 0x100bca9c8
0x100bca9c4: b 0x100bca99c
0x100bca9c8: udf #0xd
0x100bca9cc: ldr w16, 0x100bca9d4
0x100bca9d0: b 0x100bca99c
- 然后断点单步走一个,运行NSLog(@"123");重新打印内存和汇编,可以看到NSLog的符号绑定成功。得出结论,NSLog符号为懒加载。
(lldb) memory read 0x100BCC010
0x100bcc010: c0 c2 8b 94 01 00 00 00 b0 13 8b 94 01 00 00 00 ................
0x100bcc020: a0 7a 59 98 01 00 00 00 20 aa bc 00 01 00 00 00 .zY..... .......
(lldb) dis -s 0x01948bc2c0
Foundation`NSLog:
0x1948bc2c0 <+0>: sub sp, sp, #0x20 ; =0x20
0x1948bc2c4 <+4>: stp x29, x30, [sp, #0x10]
0x1948bc2c8 <+8>: add x29, sp, #0x10 ; =0x10
0x1948bc2cc <+12>: adrp x8, 264921
0x1948bc2d0 <+16>: ldr x8, [x8, #0x110]
0x1948bc2d4 <+20>: ldr x8, [x8]
0x1948bc2d8 <+24>: str x8, [sp, #0x8]
0x1948bc2dc <+28>: add x8, x29, #0x10 ; =0x10
- 接下来过掉断点,执行完rebind_symbols,然后进入lldb调试模式,打印内存和汇编,通过fishhook成功的替换掉了NSLog的实现。
由此可见,fishhook的原理就是先找到符号的实际地址,然后替换掉地址的值。
(lldb) memory read 0x100BCC010
0x100bcc010: 88 9c bc 00 01 00 00 00 b0 13 8b 94 01 00 00 00 ................
0x100bcc020: a0 7a 59 98 01 00 00 00 c4 c9 14 94 01 00 00 00 .zY.............
(lldb) dis -s 0x0100bc9c88
fishHookDemo`myNSLog:
0x100bc9c88 <+0>: sub sp, sp, #0x30 ; =0x30
0x100bc9c8c <+4>: stp x29, x30, [sp, #0x20]
0x100bc9c90 <+8>: add x29, sp, #0x20 ; =0x20
0x100bc9c94 <+12>: mov x8, #0x0
0x100bc9c98 <+16>: stur x8, [x29, #-0x8]
0x100bc9c9c <+20>: sub x9, x29, #0x8 ; =0x8
0x100bc9ca0 <+24>: str x0, [sp, #0x10]
0x100bc9ca4 <+28>: mov x0, x9
后续
苹果有一个技术叫做共享缓存技术,就是程序中引用系统库的函数整个系统只有一份。
通过上边的分析,我们看到了NSLog的符号地址,使用 dis -s 0x01948bc2c0打印出了NSLog的汇编。结合共享缓存技术,可以猜到在一次系统启动后,任何程序中的0x01948bc2c0地址都是NSLog符号。当我们知道了所有的系统函数的符号地址,是不是能针对某一个app做点什么