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_NOTYPE
2
:定义变量,从MachO
中找到并赋值
3
:跳过header
的大小,找到LoadCommands
4
:找到LC_SEGMENT_64 (__LINKEDIT)
,赋值给linkedit_segment
5
:找到LC_SYMTAB
,赋值给symtab_cmd
6
:找到LC_DYSYMTAB
,赋值给dysymtab_cmd
7
:获取的三个变量,任意为空则直接返回
1
:链接时程序的基址 = ASLR + __LINKEDIT.VM_Address - __LINKEDIT.File_Offset
2
:符号表的地址 = 基址 + 符号表偏移量
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
:遍历最初的链表,进行HOOK
5
:判断函数名称是否一致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
的函数- 保存原始地址,替换懒加载符号表中的地址