Fishhook
Fishhook是facebook出品的,可以用来Hook C函数的一个开源库。它的主要接口就一个:
struct rebinding {
const char *name; //字符串名称
void *replacement; //替换后的方法
void **replaced; //原始的方法(通常要存储下来,在替换后的方法里调用)
};
//两个参数分别是rebinding结构体数组,以及数组的长度
int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel);
实现也就不到两百行,本文就一步步揭开这个方法背后的黑魔法。
如果读者对dyld和Mach-O两个名词感到陌生,建议先看我的上一篇文章《深入理解iOS App的启动过程》
原理
在讲解具体过程之前,首先介绍一个概念:PIC(Position Indepent Code),位置无关代码。
使用PIC的Mach-O文件,在引用符号(比如printf)的时候,并不是直接去找到符号的地址(编译期并不知道运行时printf的函数地址),而是通过在
__DATA
Segment上创建一个指针,等到启动的时候,dyld动态的去做绑定(bind),这样__DATA
Segment上的指针就指向了printf
的实现。
知道这一点很关键,因为这是fishhook能够工作的核心原理,finshhook就是通过
rebind_symbols
修改__DATA
Segment上的符号指针指向,来动态的hook C函数。在
__DATA
段中,有两个Sections和动态符号绑定有关:__nl_symbol_ptr
存储了non-lazily
绑定的符号,这些符号在mach-o加载的时候绑定。__la_symbol_ptr
存储了lazy
绑定的符号(方法),这些方法在第一调用的时候,由dyld_stub_binder
来绑定,所以你会看到,每个mach-o的non-lazily
绑定符号都有dyld_stub_binder
。
通过dyld相关的API,我们可以很容易的访问到这些Symbols指针,但是并不知道这些指针具体代表哪种函数。
所以,要解决的问题就是找到这些指针代表的字符串,和当前的要替换的进行比较,如果一样替换当前指针的实现即可。
接下来我们就来看看,如何通过一系列操作来找到这些指针代表的字符串。
准备工作
新建一个iOS单页面工程,添加一个C函数,然后,编译生成.app文件,在.app文件中获取可执行文件,用MachOView打开,选中__la_symbol_ptr
,接下来我们就看看如何找到objc_msgSend
的符号。
遍历存储Symbols的两个Section
其中,表示Section Header的数据结构如下,这里我们用到的是reserved1字段。由于是遍历,所以我们知道index,比如1061
struct section { /* for 32-bit architectures */
char sectname[16]; /* name of this section */
char segname[16]; /* segment this section goes in */
uint32_t addr; /* memory address of this section */
uint32_t size; /* size in bytes of this section */
uint32_t offset; /* file offset of this section */
uint32_t align; /* section alignment (power of 2) */
uint32_t reloff; /* file offset of relocation entries */
uint32_t nreloc; /* number of relocation entries */
uint32_t flags; /* flags (section type and attributes)*/
uint32_t reserved1; /* reserved (for