在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 许可协议进行许可,转载请注明出处!