在iOS中,如果我们需要替换一个函数的实现,我们会想到通过
Method Swizzle
来实现,其实也就是通过runtime
提供的API在运行时将函数的实现给替换了,正是因为Objective-C
是动态语言,所以我们可以通过runtime
做各种事情。。。但是如果是静态语言(C),我们是否也可以将函数hook
住呢?
答案也是肯定的,Facebook
提供了一个强大的库Fishhook,静态语言我们也仍然可以hook住接下来,我们就来分析下这个强大的库是如何实现的
先来看下官方的解释:
dyld
通过更新 Mach-O
二进制文件 __DATA
段中的指针来绑定 lazy
和 non-lazy
的符号(在 Mach-O 中,相对应的就是 _nl_symbol_ptr
(non-lazy符号表)和 _la_symbol_ptr
(lazy符号表), 这两个指针表,保存了与符号表表对应的函数指针。
) ,fishhook
先确定某一个符号在 __DATA
段中的位置,然后保存原符号对应的函数指针,并使用新的函数指针覆盖原有符号的函数指针,实现重绑定。
##源码
在fishhook
的.h
文件中,只有很少的东西,两个函数接口和一个结构体
1 |
|
rebind_symbols
先来看一下rebind_symbols
函数
int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel);
这个方法便是我们用于实现hook的方法,我们来看下实现
1 | int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel) { |
这个函数传递了一个rebinding
结构体数组,以及数组的个数,在 rebind_symbols
函数中,首先调用了 prepend_rebindings
函数,传入了 _rebindings_head
, rebindings
数组,以及数组个数 . 如果retval
小于0,则直接返回retval
,如果retval
大于0,并且_rebindings_head->next
为空,则直接调用_dyld_register_func_for_add_image
注册回调函数_rebind_symbols_for_image
, 如果_rebindings_head->next
不为空,则直接执行回调函数
dyld
加载镜像(image ,在 Mach-O 中,所有的可执行文件、dylib、Bundle 都是 image)时,会为增加的镜像注册回调函数并执行(包括已经存在的镜像)。传入镜像的mach_header
和 slide
,同时也会为所有已加载的 image
执行回调。
prepend_rebindings
1 | static int prepend_rebindings(struct rebindings_entry **rebindings_head, |
1 | struct rebindings_entry { |
首先定义一个 rebindings_entry
类型的 new_entry
结构体,并初始化,给 new_entry
以及 new_entry->rebindings
分配内存。 然后通过memcpy
函数拷贝传入的 rebindings
数组 到 new_entry->rebindings
中 , 同时给 new_entry->rebindings_nel
赋值数组的个数,将 new_entry->next
赋值_rebindings_head 内的值
,最后再使 _rebindings_head
与 new_entry
指向同一个地址 , 也就是将new_entry
添加到 _rebings_head
这个链表的头部
rebind _symbols _for _image
1 | static void _rebind_symbols_for_image(const struct mach_header *header, |
_rebind_symbols_for_image
函数中仅仅是调了rebind_symbols_for_image
而rebind_symbols_for_image
才是核心函数
1 | static void rebind_symbols_for_image(struct rebindings_entry *rebindings, |
这个函数主要是在寻找__LINKEDIT
、LC_DYSYMTAB
、LC_SYMTAB
的信息, 并计算符号表的地址
__LINKEDIT段
: 含有为动态链接库使用的原始数据,比如符号,字符串,重定位表条目等等
ASLR
:Address space layout randomization
,将可执行程序随机加载载到内存中,这里的随机只是偏移,而不是打乱,就是通过内核将Mach-O
的段偏移某个随机(slide)系数. 也就是说程序的基址等于__LINKEDIT的地址减去偏移量,然后再加上ASLR造成的偏移
1 | // 链接时程序的基址 = slider(ASLR偏移) + __LINKEDIT内存地址 - __LINKEDIT文件偏移 |
之后然后再次遍loadcommands
,查找整个镜像中的 SECTION_TYPE
为 S_LAZY_SYMBOL_POINTERS
或者 S_NON_LAZY_SYMBOL_POINTERS
的 section
,并调用perform_rebinding_with_section
对__nl_symbol_ptr
以及__la_symbol_ptr
进行rebind
perform _rebinding _with _section
接下来还有一个很重要的函数
1 | static void perform_rebinding_with_section(struct rebindings_entry *rebindings, |
通过
indirect_symtab
+section->reserved1
获取indirect_symbol_indices
,也就是符号表的数组通过
(void **)((uintptr_t)slide + section->addr)
获取函数指针列表indirect_symbol_bindings
遍历符号表数组
indirect_symbol_indices
中的所有符号表中,获取其中的符号表索引symtab_index
通过符号表索引
symtab_index
获取符号表中某一个n_list
结构体,得到字符串表中的索引symtab[symtab _index].n_un.n_strx
最后在字符串表中获得符号的名字
char *symbol_name
最后就是将符号表中的 symbol_name
与 rebinding
中的名字 name
进行比较,如果匹配,进行实现替换,达到函数替换的目的
总结
程序运行时,动态链接的 C
函数地址会记录在__DATA segment
的la_symbol_ptr
中;初始时,程序只知道函数的符号名而不知道函数的实现地址;首次调用时,程序通过__TEXT segment
中的stub_helper
取得绑定信息,通过dyld_stub_binder
来更新la_symbol_ptr
中的符号实现地址;这样,再次调用时,就可以通过la_symbol_ptr
直接找到函数的实现;如果要实现函数的替换,只需要修改__la_symbol_ptr
中的符号实现地址.
最后更新: 2023年03月25日 22:39:55
本文链接: http://aeronxie.github.io/post/fc046380.html
版权声明: 本作品采用 CC BY-NC-SA 4.0 许可协议进行许可,转载请注明出处!