fishhook hook动态库c函数

OC的runtime想必大家都用过,我们可以通过hook的方式,将Method的IMP的指向修改或替换从而达到方法交换替换的目的

关于OC的方法交换原理和注意点,可以参照这篇文章方法替换注意点;

那么OC的方法交换我们知道原理,那么C语言的函数了,我们能否对它也进行交换了?我们知道OC能交换是因为OC的方法他的IMP指针的指向是运行时才绑定确定的;

假设1如果C语言函数指针的地址也是在某个时机绑定的,而不是编译完成就确定地址的话,那么理论也是可以进行方法hook的。

  • 我们假设C函数的地址是编译时期确定的,那么系统的动态库的c函数的地址就需要是固定的,那么就是编译时期分配给这些函数的手机的内存地址都是预留给这些函数占用了;这显然不符合设计规范,iOS系统的动态库,所有的app在使用的时候就是共享的,并且是按需加载到内存的,没有app使用的是不会加载进去造成浪费的;

  • 苹果系统在动态库的c函数调用的时候,编译阶段会给一个符号地址,在运行时首次调用该函数的时候才会去对符号进行绑定,映射到它真正的函数地址,这种手段专业的术语PIC(Position Independent Code)

  • 对PIC感兴趣的可以查阅该文章GCC -fPIC option;
    Position Independent Code means that the generated machine code is not dependent on being located at a specific address in order to work.

由于PIC的存在,那么我们就可以修改符号的绑定来达到方法替换的目的,听着跟OC的runtime方法替换原理似曾相识;facebook开源的fishhook库就能对动态库的c函数进行hook

fishhook简介

fishhook is a very simple library that enables dynamically rebinding symbols in Mach-O binaries running on iOS in the simulator and on device. This provides functionality that is similar to using DYLD_INTERPOSE on OS X. At Facebook, we've found it useful as a way to hook calls in libSystem for debugging/tracing purposes (for example, auditing for double-close issues with file descriptors).

fishhook是一个非常简单的库,它支持在模拟器和设备的iOS上运行的Mach-O二进制文件中动态地重新绑定符号

Mach-O就是iOS app的可执行文件,在程序启动的时候,dyld读取Mach-O文件的配置,进行动态库的加载,对动态链接库进行 rebase 指针调整和 bind 符号绑定等操作。

fishhook rebinding定义如下,这个后面会用到,所以讲一下

struct rebinding {
  const char *name;      // 需要替换的函数名
  void *replacement;     // 新函数的指针
  void **replaced;       // 老函数的新指针-被替换的函数
};

rebinding需要用到的函数定义如下:

int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel);

关于fishhook源码的解析可以参照该文章从fishhook看runtime,hook系统C函数;讲述的很清晰了,源码的注视在demo中也有,感兴趣的可以学习下。

hook系统库c函数

我们hook系统动态库的c函数,一般步骤:

  1. 声明并定义新函数
  2. 声明一个函数指针用来存储老函数的地址,如果hook的函数内需要调用原始函数
  3. 初始化struct rebinding
  4. 调用 rebind_symbols

上代码,hook系统的objc_setAssociatedObject

static char kAssociatedKey;
static void (*system_objc_setAssociatedObject)(id _Nonnull object, const void * _Nonnull key,
                                               id _Nullable value, objc_AssociationPolicy policy);

- (void)testHookCocoaFrameworkMethod {
    NSLog(@"before objc_setAssociatedObject hook");
    objc_setAssociatedObject(self, &kAssociatedKey, @(111), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    struct rebinding rebindingStruct;
    rebindingStruct.name = "objc_setAssociatedObject";
    rebindingStruct.replacement = (void *)hook_objc_setAssociatedObject;
    rebindingStruct.replaced = (void **)&system_objc_setAssociatedObject;
    rebind_symbols(&rebindingStruct, 1);
    
    NSLog(@"after objc_setAssociatedObject hook");
    objc_setAssociatedObject(self, &kAssociatedKey, @(222), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

void hook_objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
                                   id _Nullable value, objc_AssociationPolicy policy) {
    NSLog(@"%s", __FUNCTION__);
    system_objc_setAssociatedObject(object, key, value, policy);
}

为了验证我们前面的假设1

  • 下个符号断点


    图片.png
  • 打开debug,查看汇编选项


    图片.png

可以看到调用的时候是个symbols stub for objc_setAssociatedObject

图片.png

查看汇编代码,看到函数真实的调用地址是0x0000000100b4eaa3

(lldb) dis -s 0x104395e58
RuntimeLearning`objc_setAssociatedObject:
    0x104395e58 <+0>: nop    
    0x104395e5c <+4>: ldr    x16, #0x64e4              ; (void *)0x0000000214001fc8: objc_setAssociatedObject
    0x104395e60 <+8>: br     x16

RuntimeLearning`objc_setProperty_atomic_copy:
    0x104395e64 <+0>: nop    
    0x104395e68 <+4>: ldr    x16, #0x64e0              ; (void *)0x0000000104396170
    0x104395e6c <+8>: br     x16

RuntimeLearning`objc_setProperty_nonatomic_copy:
    0x104395e70 <+0>: nop    
    0x104395e74 <+4>: ldr    x16, #0x64dc              ; (void *)0x000000010439617c

(lldb) dis -s 0x0000000214001fc8
libobjc.A.dylib`objc_setAssociatedObject:
    0x214001fc8 <+0>:  b      0x214003cc8               ; _object_set_associative_reference

libobjc.A.dylib`objc_removeAssociatedObjects:
    0x214001fcc <+0>:  cbz    x0, 0x214001fe4           ; <+24>
    0x214001fd0 <+4>:  tbnz   x0, #0x3f, 0x214001fe8    ; <+28>
    0x214001fd4 <+8>:  ldr    x8, [x0]
    0x214001fd8 <+12>: and    x8, x8, #0x3
    0x214001fdc <+16>: cmp    x8, #0x1                  ; =0x1 
    0x214001fe0 <+20>: b.ne   0x214001fe8               ; <+28>
    0x214001fe4 <+24>: ret    
(lldb) 

执行rebinding之后再次调用系统的objc_setAssociatedObject可以看到

图片.png

我们这个时候再查看objc_setAssociatedObject符号地址0x104395e58的汇编

(lldb) dis -s 0x104395e58
RuntimeLearning`objc_setAssociatedObject:
    0x104395e58 <+0>: nop    
    0x104395e5c <+4>: ldr    x16, #0x64e4              ; (void *)0x000000010437f81c: hook_objc_setAssociatedObject at /Users/hechao/Documents/Demos/RuntimeLearning/RuntimeLearning/fishhook/FishhookUsage.m:45
    0x104395e60 <+8>: br     x16

RuntimeLearning`objc_setProperty_atomic_copy:
    0x104395e64 <+0>: nop    
    0x104395e68 <+4>: ldr    x16, #0x64e0              ; (void *)0x0000000104396170
    0x104395e6c <+8>: br     x16

RuntimeLearning`objc_setProperty_nonatomic_copy:
    0x104395e70 <+0>: nop    
    0x104395e74 <+4>: ldr    x16, #0x64dc              ; (void *)0x000000010439617c
(lldb) 

可以看到函数的符号的指向变成了0x104395e5c <+4>: ldr x16, #0x64e4 ; (void *)0x000000010437f81c: hook_objc_setAssociatedObject at /Users/hechao/Documents/Demos/RuntimeLearning/RuntimeLearning/fishhook/FishhookUsage.m:45 说明rebinding之后函数符号保存的真实调用的地址被修改为我们定义的函数了。

hook自定义动态库c函数

既然系统的动态库的c函数可以hook,那么我们自定义的动态库的函数是否也可以了?
我们建一个动态库,添加一个c方法

int calculate_add(int a, int b) {
    return a + b;
}

hook代码如下:

static int (*libadd_calculate_add)(int a, int b);

- (void)testHookSelfDefineDynamicFrameworkMethod {
    int sum = calculate_add(2, 3);
    NSLog(@"before calculate_add hook: 2 + 3 = %d", sum);
    struct rebinding rebindingStruct;
    rebindingStruct.name = "calculate_add";
    rebindingStruct.replacement = (void *)hook_calculate_add;
    rebindingStruct.replaced = (void **)&libadd_calculate_add;
    rebind_symbols(&rebindingStruct, 1);

    int sum1 = calculate_add(2, 3);
    NSLog(@"after calculate_add hook: 2 + 3 = %d", sum1);
}

int hook_calculate_add(int a, int b) {
    NSLog(@"%s", __FUNCTION__);
    int sum = libadd_calculate_add(a, b);
    NSLog(@"正确的和是:%d", sum);
    return 100;
}

在hook前后分别对calculate_add下断点
hook前:

图片.png

(lldb) dis -s 0x10bacfc38
RuntimeLearning`calculate_add:
    0x10bacfc38 <+0>: jmpq   *0x64f2(%rip)             ; (void *)0x000000010bdeafa0: calculate_add at /Users/hechao/Documents/Demos/RuntimeLearning/RuntimeLearning/Dynamic/calculate.c:15

RuntimeLearning`ceil:
    0x10bacfc3e <+0>: jmpq   *0x64f4(%rip)             ; (void *)0x000000010bad00e6

RuntimeLearning`class_addMethod:
    0x10bacfc44 <+0>: jmpq   *0x64f6(%rip)             ; (void *)0x000000010c3d59f4: class_addMethod

RuntimeLearning`class_copyMethodList:
    0x10bacfc4a <+0>: jmpq   *0x64f8(%rip)             ; (void *)0x000000010bacfe98

RuntimeLearning`class_getClassMethod:
    0x10bacfc50 <+0>: jmpq   *0x64fa(%rip)             ; (void *)0x000000010c3cbb9e: class_getClassMethod
(lldb) dis -s 0x000000010bdeafa0
libadd.a`calculate_add:
    0x10bdeafa0 <+0>:  pushq  %rbp
    0x10bdeafa1 <+1>:  movq   %rsp, %rbp
    0x10bdeafa4 <+4>:  movl   %edi, -0x4(%rbp)
    0x10bdeafa7 <+7>:  movl   %esi, -0x8(%rbp)
->  0x10bdeafaa <+10>: movl   -0x4(%rbp), %esi
    0x10bdeafad <+13>: addl   -0x8(%rbp), %esi
    0x10bdeafb0 <+16>: movl   %esi, %eax
    0x10bdeafb2 <+18>: popq   %rbp
    0x10bdeafb3 <+19>: retq   
    0x10bdeafb4:       addl   %eax, (%rax)
    0x10bdeafb6:       addb   %al, (%rax)
    0x10bdeafb8:       sbbb   $0x0, %al
    0x10bdeafba:       addb   %al, (%rax)
    0x10bdeafbc:       addb   %al, (%rax)
    0x10bdeafbe:       addb   %al, (%rax)
(lldb) 

可以看到自定义的动态库的c函数也是symbol,那么就也可以像系统的那样rebinding symbol达到hook的目的;

执行rebinding之后,再次调用calculate_add的指向已经修改为hook_calculate_add;这说明hook成功了

(lldb) dis -s 0x10bacfc38
RuntimeLearning`calculate_add:
    0x10bacfc38 <+0>: jmpq   *0x64f2(%rip)             ; (void *)0x000000010bab8d10: hook_calculate_add at /Users/hechao/Documents/Demos/RuntimeLearning/RuntimeLearning/fishhook/FishhookUsage.m:63

RuntimeLearning`ceil:
    0x10bacfc3e <+0>: jmpq   *0x64f4(%rip)             ; (void *)0x000000010bad00e6

RuntimeLearning`class_addMethod:
    0x10bacfc44 <+0>: jmpq   *0x64f6(%rip)             ; (void *)0x000000010c3d59f4: class_addMethod

RuntimeLearning`class_copyMethodList:
    0x10bacfc4a <+0>: jmpq   *0x64f8(%rip)             ; (void *)0x000000010bacfe98

RuntimeLearning`class_getClassMethod:
    0x10bacfc50 <+0>: jmpq   *0x64fa(%rip)             ; (void *)0x000000010c3cbb9e: class_getClassMethod
(lldb) 

这时候执行了hook的方法


图片.png

总结

fishhook就是利用编译器的PIC,符号的绑定是运行时去绑定的,那么修改符号的绑定就能达到hook动态库的c函数。

参考资料

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350