很多网上资料讲解fishhook原理,要么泛泛而谈,要么大而全,要么直接代码太高深。本文深入底层汇编,由浅入深,通过一条主脉络娓娓道来,剖析fishhook,玩转fishhook!
fishhook是什么?有何用
hook简介
fishhook是facebook开源的第三方框架。从名称上看含有**hook**这个词,其本意就是"钩子",钩子就是用来勾住某一种事物,比如fish鱼。在计算机中就可以勾住某一个程序(就是挂钩程序)或者某一个函数,从而扩展程序功能或者改变程序运行的流程,比如在iOS开发中经常会被用来做”逆行开发“。
hook使用场景
埋点
拦截用户手势交互或者进入某个指定页面等应用接口,进行用户行为统计分析。
应用加固
防止黑客通过Hook技术破解/攻破你的应用,来加固应用,比如ptrace调试防护、隐藏防护、注入对抗。
应用隔离
说白了就是隔离应用在安全区域,比如移动办公应用,就可以hook拦截网络、截屏、剪贴板等接口,来防止数据泄露。
hook技术
method swizzle
原理很简单就是利用OC的runtime特性来动态修改objc_msgSend函数中的id/selector参数,来改变id与selector之间的对应关系,以交换id对应的method,从而实现hook,这也是为啥OC是一门动态语言。比如经常这样使用:
fishhook
众所周知,C语言是一门静态语言,而静态语言在编译完成后变量、函数及其参数就已经确定,无法修改。但fishhook就能在程序运行时动态修改C函数,比如微信内存监控就使用fishhook来hook malloc/free函数监控堆内存分配。
Cydia
...
这里不做阐述,重点阐述
fishhook;
fishhook实战 如何使用?
既然fishhok能hook 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 hook,why?? 别着急,往下看,看下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跳转到0x100002394,0x100002394又跳转到0x100002384,进而跳转到dyld_stub_binder,字面意思就是dyld进行桩绑定,即dyld符号重绑定!!!
dyld与fishhook有何关系?
一句话概述:就是dyld动态加载器提供了获取镜像数据的接口,比如Mach-O header、ASLR,进而就可以通过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 header、ASLR等,进而就可以获取懒加载符号表、非懒加载符号表、间接符号表、符号表、字符串表等符号相关的所有信息;- 通过遍历函数符号建立函数符号与函数名称的对应关系,就可以通过函数名称就可以找到最终的函数符号地址,就可以修改函数符号的内容来指向自己的实现。
思考与探索
fishhook能够hookC函数,能不能hookC++函数?Linux系统能不能hookC/C++函数?
让学习成为一种乐趣!!!