fishhook

fishhook 是一个非常简单的库,它能够在 iOS 模拟器和设备上运行的 Mach-O 二进制文件中动态地重新绑定符号。这提供了与在 OS X 上使用 DYLD_INTERPOSE 类似的功能。在 Facebook,我们发现它对于在 libSystem 中 hook 调用以进行调试/跟踪非常有用(例如,审核文件描述符的双重关闭问题)。

用法

一旦你将 fishhook.h/fishhook.c 添加到你的项目,你就可以如下所示重新绑定符号:

#import <dlfcn.h>

#import <UIKit/UIKit.h>

#import "AppDelegate.h"
#import "fishhook.h"
 
static int (*orig_close)(int);
static int (*orig_open)(const char *, int, ...);
 
int my_close(int fd) {
  printf("Calling real close(%d)\n", fd);
  return orig_close(fd);
}
 
int my_open(const char *path, int oflag, ...) {
  va_list ap = {0};
  mode_t mode = 0;
 
  if ((oflag & O_CREAT) != 0) {
    // mode only applies to O_CREAT
    va_start(ap, oflag);
    mode = va_arg(ap, int);
    va_end(ap);
    printf("Calling real open('%s', %d, %d)\n", path, oflag, mode);
    return orig_open(path, oflag, mode);
  } else {
    printf("Calling real open('%s', %d)\n", path, oflag);
    return orig_open(path, oflag, mode);
  }
}
 
int main(int argc, char * argv[])
{
  @autoreleasepool {
    rebind_symbols((struct rebinding[2]){{"close", my_close, (void *)&orig_close}, {"open", my_open, (void *)&orig_open}}, 2);
 
    // Open our own binary and print out first 4 bytes (which is the same
    // for all Mach-O binaries on a given architecture)
    int fd = open(argv[0], O_RDONLY);
    uint32_t magic_number = 0;
    read(fd, &magic_number, 4);
    printf("Mach-O Magic Number: %x \n", magic_number);
    close(fd);
 
    return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
  }
}

示例输出

Calling real open('/var/mobile/Applications/161DA598-5B83-41F5-8A44-675491AF6A2C/Test.app/Test', 0)
Mach-O Magic Number: feedface 
Calling real close(3)
...

工作原理

dyld 通过更新 Mach-O 二进制文件的 __DATA 段中特定部分的指针来绑定惰性和非惰性符号。 fishhook 通过确定要更新的每个传递给 rebind_symbols 的符号名称的位置,然后写入相应的替换项来重新绑定这些符号。

对于给定的镜像,__DATA 段可能包含两个与动态符号绑定相关的节:__nl_symbol_ptr__la_symbol_ptr__nl_symbol_ptr 是指向非惰性绑定数据的指针数组(这些在库加载时绑定),__la_symbol_ptr 是指向导入函数的指针数组,通常由名为 dyld_stub_binder 的例程在首次调用该符号时填充(也可以告诉 dyld 在启动时绑定这些函数)。 为了找到与这些节中的特定位置对应的符号名称,我们必须跳过几个间接层。 对于两个相关的节,节头(来自 <mach-o/loader.h>struct section)提供一个偏移量(在 reserved1 字段中)到所谓的间接符号表。 间接符号表位于二进制文件的 __LINKEDIT 段中,它只是一个指向符号表(也在 __LINKEDIT 中)的索引数组,其顺序与非惰性和惰性符号节中的指针顺序相同。 因此,给定 struct section nl_symbol_ptr,该节中第一个地址在符号表中的对应索引是 indirect_symbol_table[nl_symbol_ptr->reserved1]。 符号表本身是 struct nlist 的数组(参见 <mach-o/nlist.h>),每个 nlist 包含一个指向 __LINKEDIT 中字符串表的索引,实际的符号名称存储在该表中。 因此,对于每个指针 __nl_symbol_ptr__la_symbol_ptr,我们能够找到对应的符号,然后找到对应的字符串来与请求的符号名称进行比较,如果匹配,我们用替换项替换该节中的指针。

在惰性或非惰性指针表中查找给定条目的名称的过程如下所示: Visual explanation