一文读懂fishhook原理

9,133 阅读7分钟

很多网上资料讲解fishhook原理,要么泛泛而谈,要么大而全,要么直接代码太高深。本文深入底层汇编,由浅入深,通过一条主脉络娓娓道来,剖析fishhook,玩转fishhook!

fishhook是什么?有何用

hook简介

fishhookfacebook开源的第三方框架。从名称上看含有**hook**这个词,其本意就是"钩子",钩子就是用来勾住某一种事物,比如fish鱼。在计算机中就可以勾住某一个程序(就是挂钩程序)或者某一个函数,从而扩展程序功能或者改变程序运行的流程,比如在iOS开发中经常会被用来做”逆行开发“。

hook使用场景

埋点

拦截用户手势交互或者进入某个指定页面等应用接口,进行用户行为统计分析。

应用加固

防止黑客通过Hook技术破解/攻破你的应用,来加固应用,比如ptrace调试防护、隐藏防护、注入对抗。

应用隔离

说白了就是隔离应用在安全区域,比如移动办公应用,就可以hook拦截网络、截屏、剪贴板等接口,来防止数据泄露。

hook技术

method swizzle

原理很简单就是利用OCruntime特性来动态修改objc_msgSend函数中的id/selector参数,来改变idselector之间的对应关系,以交换id对应的method,从而实现hook,这也是为啥OC是一门动态语言。比如经常这样使用:

fishhook

众所周知,C语言是一门静态语言,而静态语言在编译完成后变量、函数及其参数就已经确定,无法修改。但fishhook就能在程序运行时动态修改C函数,比如微信内存监控就使用fishhookhook malloc/free函数监控堆内存分配。

Cydia
...

这里不做阐述,重点阐述fishhook

fishhook实战 如何使用?

既然fishhokhook C函数,就来个需求:就hook经常使用的Foundation.framework中的NSLog来实战演练下具体使用,直接代码如下:

//原函数指针变量
static void (*sys_nslog)(NSString *format, ...);
//hook新函数
void hook_nslog(NSString *format, ...) {
    format = [format stringByAppendingString:@"❤️ ( ⚫︎ー⚫︎ ) balalala~"];
    sys_nslog(format);
}

- (void)viewDidLoad {
    [super viewDidLoad];
    //提出需求->分解需求->完成需求->测试验收
    //【需求】hook NSLog
    //1. 点击屏幕输出日志;
    //2.fishhook hook NSLog让日志内容发生点变化!!!
    
  	//定义hook的函数的结构体变量
    struct rebinding nslog_reb;
    nslog_reb.name = "NSLog";
    nslog_reb.replacement = hook_nslog;
    nslog_reb.replaced = (void *)&sys_nslog;
    //定义需要hook的函数的结构体数组变量
    struct rebinding rebs[] = {nslog_reb};
  	//很简单,传递结构体数组地址及其成员变量数目
    rebind_symbols(rebs, 1);
}

//点击屏幕输出日志
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"click!!!");
}

输出结果如下:

结果说明fishhook能够hook动态链接库Foundation.framework中的NSLog,**那fishhook能不能hook所有的C函数???**这里通过实例自定义函数来验证下,代码如下:

//原函数
static void func(void) {
    printf("%s \n", __func__);
}
//新函数
static void hook_func(void) {
    printf("%s \n", __func__);
}

- (void)viewDidLoad {
    [super viewDidLoad];
    //提出需求->分解需求->完成需求->测试验收
    //【需求】hook NSLog
    //1. 点击屏幕输出日志;
    //2. fishhook hook NSLog让日志内容发生点变化!!!
    //3. hook 自定义函数func 是否函数名称发送变化!!!
    
    func();
    
  	//hook NSLog
    struct rebinding nslog_reb;
    nslog_reb.name = "NSLog";
    nslog_reb.replacement = hook_nslog;
    nslog_reb.replaced = (void *)&sys_nslog;
    //hook func
    struct rebinding func_reb;
    func_reb.name = "func";
    func_reb.replacement = hook_func;
    func_reb.replaced = nil;
    
    struct rebinding rebs[] = {nslog_reb, func_reb};
    rebind_symbols(rebs, 2);

    func();
    
}

结果如下:

说明fishhook无法hook自定义函数!!!why????

fishhook原理探索 为何这么用

通过底层汇编来揭秘fishhook为何只能hook动态链接库函数,不能hook自定义函数!!!使用Hopper disassembler反汇编上面的代码可执行文件,自定义函数func汇编结果如下:

说明func函数没有发生变化,即未被fishhook hookwhy?? 别着急,往下看,看下func函数是在代码段还是数据段?

使用MachOView来看下func函数是否在代码段还是数据段,如图:

自定义函数在代码段,代码段具有只读可执行权限,因此fishhook无法hook自定义函数!那NSLog在代码段还是数据段呢???

使用MachOView来看下具体的段,如下图;

NSLog函数符号位于数据段,因此fishhook能够hook 动态链接库Foundation.framework中的NSLog重要原因之一就是:函数符号位于数据段,只有数据段内容才能被修改!!!

函数符号为何在数据段?

一图读懂,如下图:

一句话简述:真正的函数符号NSLog位于外部动态链接库Foundation.framework,属于外部符号,由于ASLR偏移量动态链接库每次加载到内存的地址不定的,就需要可执行文件加载到内存时_NSLog_ptr重新进行”符号重绑定“以修正其地址内容,来执行真正的NSLog实现。

继续看下函数符号NSLog位于具体的哪个段,如下图:

位于_la_symbol_ptr段,就是”懒加载符号“,即使用时再去加载!!!相对的就是”非懒加载符号“,如下图:

同样位于数据段,内容为空值,即程序启动后就去进行”符号重绑定“。

懒加载符号如何被加载?

懒加载符号NSLog跳转到0x1000023940x100002394又跳转到0x100002384,进而跳转到dyld_stub_binder,字面意思就是dyld进行桩绑定,即dyld符号重绑定!!!

dyld与fishhook有何关系?

一句话概述:就是dyld动态加载器提供了获取镜像数据的接口,比如Mach-O headerASLR,进而就可以通过Mach-O中的信息获取函数符号的所有相关数据信息,比如懒加载符号、非懒加载符号等;

如何确定函数符号地址?

这就是fishhook的重要流程,就是符号查找过程,很简单,一张图就可以明白,如下图:

流程如下:

  • 懒加载符号表Lazy Symbol Pointer Table与间接符号表Indirect Symbol Table中的符号一一对应;
  • 间接符号表保存了函数符号在符号表Symbol Table中的偏移量;
  • 符号表中每项为struct nlist结构体,其中保存了函数符号在字符串表String Table中的偏移量;
  • 通过字符串表的偏移量就可以找到最终的函数符号对于的函数名称

通过遍历以上流程就可以建立函数符号与函数名称的对应关系,就可以通过函数名称来找到最终的函数符号,进而就可以修改函数符号的指向,来指向自己的实现。因此,fishhook提供的函数接口rebind_symbols中的结构体struct rebinding需要提供函数名称,如下:

struct rebinding {
  const char *name;//函数名称
  void *replacement;//新函数指针
  void **replaced;//原函数地址的指针
};

总结

缕一缕fishhook能够hook C函数的原因不外乎这几点:

  • 函数符号属于外部符号,位于动态链接库,进而位于数据段,只有数据段的内容才能被修改;
  • dyld提供了获取镜像信息的接口,如获取Mach-O headerASLR等,进而就可以获取懒加载符号表、非懒加载符号表、间接符号表、符号表、字符串表等符号相关的所有信息;
  • 通过遍历函数符号建立函数符号与函数名称的对应关系,就可以通过函数名称就可以找到最终的函数符号地址,就可以修改函数符号的内容来指向自己的实现。

思考与探索

  • fishhook能够hook C函数,能不能hook C++函数?
  • Linux系统能不能hook C/C++函数?

让学习成为一种乐趣!!!

Reference

通过修改GOT表,hook glibc函数

iOS hook C++ 尝试

探究Mach-O文件

《程序员的自我修养》

fishhook

iOS微信内存监控