FishHook
fishHook是Facebook提供的一个动态修改链接mach-O文件的工具。利用MachO文件加载原理,通过修改懒加载表(Lazy Symbol Pointers)和非懒加载表(Non-Lazy Symbol Pointers)这两个表的指针达到C函数HOOK的目的。
在逆向中经常使用fishHook这个工具。所以在学习过程中,我们重点要了解其原理,这样能够对恶意代码进行有效的防护。
FishHook的简单使用
-
Xcode新建项目工程,将fishHook拖入工程
-
写代码Hook系统函数NSLog
具体可以下载FishhooKDemo,具体代码具体代码如下:
#import "ViewController.h"
#import "fishhook.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//使用rebinding 结构体
struct rebinding nslog;
nslog.name = "NSLog";//要hook的函数名称
nslog.replacement = myNSLog;//这里是函数的指针,也就是函数名称
nslog.replaced = &sys_nslog;
// rebinding 结构体数组
struct rebinding rebs[1] = {nslog};
/***
存放rebinding 结构体数组
数组长度
*/
rebind_symbols(rebs, 1);
// Do any additional setup after loading the view.
}
//---------修改的NSLog---------
//NSLog函数指针
static void(*sys_nslog)(NSString * format,...);
//定义一个新函数
void myNSLog(NSString *format,...){
format = [format stringByAppendingString:@"~~~~~hook 到了!"];
sys_nslog(format);
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
NSLog(@"点击了屏幕");
}
当点击屏幕后可以看到输出结果:
可以看到HOOK到了系统函数NSLog了;这里注意fishHOOK 不能hook自己写的C语言函数;
fishHook原理
共享缓存库
在iOS or Mac系统中,几乎所有的程序都会用到动态库,而动态库在加载的时候都需要用dyld(位于/usr/lib/dyld)程序进行链接。很多系统库几乎都是每个程序都要用到的,如果在每个程序运行的时候在一个一个将这些动态库都加载进来,不仅耗费内存,而且耗时。为了降低内存,提高性能,苹果引入了共享缓存库,用来存储系统的库。
Mac下的共享缓存库位置:/private/var/db/dyld/
iOS下的共享缓存库位置:/System/Library/Caches/com.apple.dyld/”
文件名都是以“dyld_shared_cache_”开头,再加上这个dyld缓存文件所支持的指令集。
PIC技术(位置独立代码)
我们都知道C语言是静态的,也就是说,在编译的时候就已经确定了函数的地址。而系统的函数由于共享缓存库的存在,必须是dyld加载的时候(运行时)才能确定,这明显存在矛盾。为了解决这个问题,苹果针对Mach-O文件提供了一种PIC技术,即在MatchO的_Data段中添加懒加载表(Lazy Symbol Pointers)和非懒加载表(Non-Lazy Symbol Pointers)这两个表,让系统的函数在编译的时候先指向懒加载表(Lazy Symbol Pointers)或非懒加载表(Non-Lazy Symbol Pointers)中的符号地址,这两个表中的符号的地址的指向在编译的时候并没有指向任何地方,app启动,被dyld加载到内存,就进行链接, 给这2个表赋值动态缓存库的地址进行符号绑定。
在fishHOOK 之前加上一个打印;并打上断点
#import "ViewController.h"
#import "fishhook.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"viewDidLoad");
//使用rebinding 结构体
struct rebinding nslog;
nslog.name = "NSLog";//要hook的函数名称
nslog.replacement = myNSLog;//这里是函数的指针,也就是函数名称
nslog.replaced = &sys_nslog;
// rebinding 结构体数组
struct rebinding rebs[1] = {nslog};
/***
存放rebinding 结构体数组
数组长度
*/
rebind_symbols(rebs, 1);
// Do any additional setup after loading the view.
}
//---------修改的NSLog---------
//NSLog函数指针
static void(*sys_nslog)(NSString * format,...);
//定义一个新函数
void myNSLog(NSString *format,...){
format = [format stringByAppendingString:@"~~~~~hook 到了!"];
sys_nslog(format);
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
NSLog(@"点击了屏幕");
}
@end
首先使用image list指令,查看MatchO的首地址,也就是ASLR地址,偏移地址;
何为ASLR技术?
地址空间布局随机化。它会让 Mach-O 文件加载的时候是随机地址。有了这个技术,Mach-O 文件每次加载进内存的时候地址都是不一样的。主要是为了防止逆向技术。
Mach-O 文件里只有我们自己写的函数,系统的动态库的函数是不在 Mach-O 文件里的。也就是说每次启动从 Mach-O 文件到系统动态库函数的偏移地址都是变化的。
使用MatchOView查看MatchO懒加载表中的NSLog符号在文件中的偏移值
查看符号表绑定的地址,这个地址其实就是指向外部函数的指针的地址,也就是动态缓存区里面 NSLog 的真实函数地址。这一步是找到了 NSLog 的符号表(Symbols)。因为iOS为小端模式这里地址应该是00007fff25762dfa
这个真实的函数地址是什么时候保存进去的呢?并不是 Mach-O 文件加载进内存的时候保存的。由于 NSLog 在懒加载符号表里面,所有它是在整个 Mach-O 文件启动之后,代码第一次运行 NSLog 时,由 DYLD 绑定该 NSLog 符号指向真实的 NSLog 的地址。
这个时候,我们需要通过反汇编看一下地址的值:dis -s 0x00007fff25762dfa
以上是HOOK之前的地址和绑定函数;我们通过下断点;在点击屏幕处下断点来进行查看下
首先查看下地址中存储
可以看到存储的和之前的不一样了:0x000000010ba965d0 反编译下看看
总结: fishHook其实就是修改懒加载表(Lazy Symbol Pointers)、非懒加载表(Non-Lazy Symbol Pointers)中的符号地址的指向,从而达到hook的目的
fishhook 是如何通过字符串来找到我们的函数的呢?
首先,我们从懒加载符号表(Lazy Symbol Pointers)开始入手。懒加载符号表里面第一个符号是 NSLog 的指针。这个懒加载符号表有一个与之一一对应的符号表(Indirect Symbols)。
这里的Data 值,是一个真正的符号表的下标。这个符号表是对应着字条串的。比如:NSLog 的 Data 值为0xA1,换成十进制就是161。也就是说 NSLog 这个符号在我们的字符符号表里面的 index 值为161。接着就需要到符号表(Symbols)里面找第161个。这个时候还没到字符串。
这个时候,NSLog 在真正的字符串里面是在哪个地方呢?注意,上图有一个偏移0xD7,就是在字符串表(String Table)里面的一个index。也就是说这个 NSLog 在 String Table 里面的偏移地址是0xD7。
String Table 是从0x82D8开始的,所以开始地址0x82D8 + 偏移地址0xD7 = 0x83AF,就是字符串 NSLog 的位置。
_ 是函数的开始,. 是分隔符 。5F是从 _开始,往后依次 _NSLog ;
接下来,附上 fishhook 官方文档的在懒加载和非懒加载符号表里查找一个给定入口的名字的过程。