fishhook 源码学习

status
category
date
summary
slug
icon
tags
password
在学习fishhook之前,首先要了解Mach-O文件结构

什么是 fishhook

fishhook 是一个由 Facebook 开源的框架,可以动态修改链接 Mach-O 符号表。

demo演示

我们用这样一段C代码来演示:
首先我们构造了一个和原函数签名相同的函数指针*original_strlen,然后重新实现了新的new_strlen函数。在main函数中,创建了一个rebinding结构体,分别传入了需要hook的函数名,新实现的函数,与原函数签名相同的函数指针。最后通过rebind_symbols进行符号的重新绑定,运行时就会输出666。

源码分析

结构体定义

首先来看一看rebinding的定义:
这里通过函数名和一个函数签名,可以确定需要hook的是哪个函数,然后用新函数代替它,储存这些信息的数据结构就是一个rebinding
一个rebindings_entry可以理解为一次hook时的信息入口,它存储了一个rebinding数组、数组元素的数量和下一个节点。所以这里维护的是一个rebindings_entry链表,有多少次hook,链表就有多少个节点。

rebind_symbols

直接调用的函数实现:
rebind_symbols是使用fishhook的入口函数,它有两个作用,一是调用prepend_rebindings进行数据结构的初始化,二是注册了_rebind_symbols_for_image函数并在新映像加载时回调。如果调用_dyld_register_func_for_add_image时,系统已经加载了某些映像,则会分别调用它们注册的回调函数。也就是说,在加载、卸载映像,以及为映像注册回调函数时,回调函数都会被调用,所以这个函数通常被用来监控映像和统计系统数据。

prepend_rebindings 初始化

prepend_rebindings初始化了一个新的rebindings_entry节点并插入链表头部。如果在一个程序中多次调用rebind_symbols来hook函数,就有多个rebinding数组需要维护,rebindings_entry维护的是一个反向链表,每个节点都维护一个rebinding数组,通过链表可以判断是否是第一次hook。
notion image

_rebind_symbols_for_image 准备基址

调用_dyld_register_func_for_add_image进行注册时,需要满足特定的回调函数签名格式。
这里对Load Commands进行了两次遍历,第一次遍历根据cmd找到了三个需要的segment:LC_SEGMENT__LINKEDITLC_SYMTABLC_DYSYMTAB,为第二次遍历做好了地址计算的准备;第二次遍历找到了SECTION_TYPES_LAZY_SYMBOL_POINTERSS_NON_LAZY_SYMBOL_POINTERS的section,并调用perform_rebinding_with_section对section中的符号进行处理。
notion image

perform_rebinding_with_section 重绑定

这个查找匹配的过程描述起来比较绕口,首先通过符号在__la_symbol_prt的index,加上Load Command中__la_symbol_prt的保留信息reserved1,得到了Indirect Symbols中位于index + reversed1 的数据index2,然后在Symbol Table中index2的位置拿到偏移地址offset,最后拿到String Table中offset处的数据,这个数据就是函数名。如果函数名匹配,则更改__la_symbol_ptr表中的函数地址,完成hook。
notion image

总结

程序启动时,会链接很多动态库,函数的调用就是通过指令跳转到函数对应的内存地址。因为动态库是运行后开始链接,所以程序并不知道函数在哪里,所以这些函数放在__DATA,__la_symbol_prt表中。
例如我们现在要调用printf函数,表中相应内容并不会直接指向printf,而是指向了dyld_stub_binder,它的作用就是计算真正的printf地址,并且将表中的指针指向修改,这样下次就可以直接调用printf了,这就是懒加载。
而fishhook做的工作,就是在dyld绑定了地址之后再次做一个重绑定,200行左右的代码,只有一行是在修改函数指针,最复杂的逻辑主要是在计算地址和匹配字符串。hook的局限性,就是只能修改__la_symbol_ptr表中的函数指向,也就是不能hook静态库中的函数和自定义的函数。安全方面,我们可以通过替换函数的地址是否在映像内,来判断是否是恶意程序注入的hook。
Loading...

© 刘口子 2018-2025