fishHook是Facebook提供的开源库,利用MachO文件的加载原理,动态修改懒加载和非懒加载两张符号表,用来HOOK外部调用的C函数。详情可查看 官方文档
rebinding结构体
struct rebinding {const char *name;void *replacement;void **replaced;};
name:需要HOOK的函数名称,C字符串
replacement:新函数的地址
replaced:原始函数地址的指针
rebind_symbols
fishHook的入口函数,在所有image中,HOOK指定函数
1:将rebindings数组,添加到_rebindings_head链表的头部。fishHook采用链表的方式,存储每一次调用rebind_symbols函数传入的参数。每次调用,就会在链表的头部插入一个节点,链表的头部是_rebindings_head
2:容错处理,根据prepend_rebindings函数的返回结果,判断小于0,返回错误码3:使用next为空,判断是否为首次调用4:首次调用,调用_dyld_register_func_for_add_image注册监听方法。已经被dyld加载的image会立刻进入回调,之后的image会在dyld装载的时候触发回调5:非首次调用,遍历调用_rebind_symbols_for_image函数,对已经加载的image进行的hook
_rebind_symbols_for_image函数:首次加载的回调和非首次加载的遍历调用,都触发此函数,入参为header和ASLR
- 内部调用
rebind_symbols_for_image函数,在指定image中HOOK函数
rebind_symbols_image
fishHook的入口函数,在指定image中,HOOK指定函数
1:将rebindings数组,添加到rebindings_head链表的头部2:在指定image中HOOK函数
rebind_symbols_for_image
负责符号表重绑定的最终函数,入参为将要交换的结构体数组、
header、ASLR
1:dladdr函数的作用,在程序中找header。确定指定的address是否位于构成进程的进址空间的其中一个加载模块(可执行库或共享库)内,如果某个地址位于在其上面映射加载模块的基址和为该加载模块映射的最高虚拟地址之间(包括两端),则认为该地址在加载模块的范围内。如果某个加载模块符合这个条件,则会搜索其动态符号表,以查找与指定的address最接近的符号。最接近的符号是指其值等于,或最为接近但小于指定的address的符号。如果指定的address不在其中一个加载模块的范围内,则返回0。且不修改Dl_info结构的内容。否则,将返回一个非零值,同时设置Dl_info结构的字段。如果在包含address的加载模块内,找不到其值小于或等于address的符号,则dli_sname、dli_saddr和dli_size字段将设置为0。dli_bind字段设置为STB_LOCAL,dli_type字段设置为STT_NOTYPE2:定义变量,从MachO中找到并赋值
3:跳过header的大小,找到LoadCommands4:找到LC_SEGMENT_64 (__LINKEDIT),赋值给linkedit_segment5:找到LC_SYMTAB,赋值给symtab_cmd6:找到LC_DYSYMTAB,赋值给dysymtab_cmd7:获取的三个变量,任意为空则直接返回
1:链接时程序的基址 = ASLR + __LINKEDIT.VM_Address - __LINKEDIT.File_Offset2:符号表的地址 = 基址 + 符号表偏移量3:字符串表的地址 = 基址 + 字符串表偏移量4:动态符号表地址 = 基址 + 动态符号表偏移量5:寻址数据段,如果不是跳过6:寻址懒加载表7:寻址非懒加载表
Dl_info:
dli_fname:image路径dli_fbase:image的基地在dli_sname:函数名称dli_saddr:函数地址
perform_rebinding_with_section
HOOK的核心代码,入参为最初的链表、懒加载表/非懒加载表、ASLR、符号表地址、字符串表地址、动态符号表地址
1:懒加载表/非懒加载表中的reserved1字段,指明对应的间接符号表起始的index
2:符号对应的存放函数实现的数组,存储了懒加载表/非懒加载表中的函数指针,所以可以去寻找到函数的地址
3:遍历懒加载表/非懒加载表中的每一个符号4:找到符号在间接符号表中的值,读取此符号的数据
1:以symtab_index作为下标,访问Symbol Table,找到符号在String Table中的偏移值2:在字符串表中拿到符号名称3:判断函数名称至少有两个字符,函数前面有个_,名称最少占一个字符4:遍历最初的链表,进行HOOK5:判断函数名称是否一致6:判断replaced的地址不为NULL,以及方法的实现和replacement的方法不一致7:让replaced保存原始函数地址8:将替换后的方法给原先的方法,也就是替换内容为自定义函数地址
总结
rebinding结构体
name:需要HOOK的函数名称,C字符串replacement:新函数的地址replaced:原始函数地址的指针入口函数
rebind_symbols:在所有image中,HOOK指定函数rebind_symbols_image:在指定image中,HOOK指定函数
rebind_symbols
rebinding数组添加到链表- 根据链表判断是否为首次调用,目的是保证注册方法只调用一次。两种情况都会调用
_rebind_symbols_for_image函数
◦ 首次调用,利用_dyld_register_func_for_add_image注册监听方法的回调
◦ 非首次,循环遍历已经装载的image,依次调用
rebind_symbols_image
- 第一步:拿到三张表在内存中的地址
◦ 符号表的地址
◦ 字符串表的地址
◦ 间接符号表的地址- 第二步:找到懒加载和非懒加载表
- 第三步:调用
perform_rebinding_with_section函数,入参:最初的链表、懒加载表/非懒加载表、ASLR、符号表地址、字符串表地址、动态符号表地址
perform_rebinding_with_section
- 得到
indirect_symbol_bindings,懒加载表/非懒加载表中所有函数地址的数组- 遍历间接符号表,最终找到符号的过程
- 判断是否为需要
HOOK的函数- 保存原始地址,替换懒加载符号表中的地址












