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
,我们能够找到对应的符号,然后找到对应的字符串来与请求的符号名称进行比较,如果匹配,我们用替换项替换该节中的指针。