Fishhook 学习笔记

一、Fishhook 是什么?

简单来说Fishhook就是hook函数的一种工具,当然它hook的原理和我们熟知的Method Swizzle 方式是不一样的,它是Facebook提供的一个动态修改链接mach-O文件的工具。

  • Method Swizzle 是利用OC的Runtime特性,动态改变SEL(方法编号)和IMP(方法实现)的对应关系,达到OC方法调用流程改变的目的。主要用于OC方法。
  • Fishhook 是利用MachO文件加载原理,通过修改懒加载和非懒加载两个表的指针达到C函数HOOK的目的。

二、Fishhook的简单使用

首先在github上下载fishhook库:


Fishhook 学习笔记_第1张图片
image.png

解压zip包后,将其中的.c和.h文件拖入你想hook的项目当中,我们所用到的就是这两个文件,当然网上有大把的关于fishhook的源码剖析资料,大家可以自行查阅,这里我就直接上代码了,拖入步骤不作详细介绍。

#import "ViewController.h"
#import "fishhook.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
 
    //定义rebinding 结构体
    struct rebinding rebind = {};
    rebind.name = "NSLog";
    rebind.replacement = hookNSLog;
    rebind.replaced = (void *)&nslogMethod;
    
    //将上面的结构体 放入 reb结构体数组中
    struct rebinding red[]  = {rebind};
    
    /*
     * arg1 : 结构体数据组
     * arg2 : 数组的长度
     */
    
    rebind_symbols(red, 1);
    
}

//定义一个函数指针 用于指向原来的NSLog函数
static void (*nslogMethod)(NSString *format, ...);

void hookNSLog(NSString *format, ...){
    
    format = [format stringByAppendingString:@"被勾住了"];
    
    nslogMethod(format);
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    NSLog(@"原有NSLog函数");
}

将文件导入项目后,在ViewController.m 文件中写入代码如上,代码目的是:将点击屏幕时执行的NSLog函数,替换成执行hookNSLog函数。
点击屏幕时执行结果:


Fishhook 学习笔记_第2张图片
image.png

我们先不管原理是怎样,大家是不是觉得,这执行结果和以上代码干的事,是不是很像runtime的方法交换。
但是从表面上看,runtime和fishhook的区别,大家都知道使用runtime交换的是OC的方法,而fishhook却是交换C函数,这里就会有个疑惑,C语言不是静态语言吗,为什么fishhook可以直接交换函数呢?
ok ! 在疑问之前,我们先将以上代码稍作修改,在看一下结果。

#import "ViewController.h"
#import "fishhook.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
 
    //定义rebinding 结构体
    struct rebinding rebind = {};
    rebind.name = "funcDlog";
    rebind.replacement = hookNSLog;
    rebind.replaced = (void *)&nslogMethod;
    
    //将上面的结构体 放入 reb结构体数组中
    struct rebinding red[]  = {rebind};
    
    /*
     * arg1 : 结构体数据组
     * arg2 : 数组的长度
     */
    
    rebind_symbols(red, 1);
    
}

//定义一个函数指针 用于指向原来的NSLog函数
static void (*nslogMethod)(NSString *format, ...);

void hookNSLog(NSString *format, ...){
    
    format = [format stringByAppendingString:@"被勾住了"];
    
    nslogMethod(format);
}

void funcDlog(NSString *format, ...){
    
    NSLog(@"%@", format);
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    
    funcDlog(@"原有NSLog函数");
}


@end

来我们看下代码的改动:
我们将原来hook的目标转换了,自己定义了一个函数void funcDlog(NSString *format, ...),在函数调用NSLog函数,而我们这次Hook的目标变成了 funcDlog函数,按照之前的运行结果,猜测这次的运行结果应该还是“ 原有NSLog函数被勾住了”,但是实际运行结果却是:

Fishhook 学习笔记_第3张图片
image.png

结果显示我们的代码并没有hook成功,综合以上两次运行结果,我们发现了,fishhook可以实现runtime做不到的将C函数进行交换,但是我们自己定义的C函数却不能交换,只能hook系统的函数,这是为什么呢?我们来看下一个章节。

三、Fishhook的原理探究

首先在探究fishhook的原理之前,我要清楚几个问题:

  • MachO是怎么加载的
    • 首先我们要知道,MachO文件是被dyld加载的,dyld是动态加载,也负责动态库的加载。我们都知道动态库加载时并不在我们自己的MachO文件内部,是存在系统动态缓存区,当我们MachO文件加载过后会动态加载我们所依赖的那些动态库。
  • ASLR技术
    • 我们的MachO文件在内存当中每次的运行地址是不一样的,MachO文件加载的时地址是随机的,这就是ASLR技术。苹果每次加载MachO文件时会随机分配一个地址,来降低缓冲区溢出。

那问题来了,每次MachO文件加载的地址不一样,我们大家都知道,系统的动态库缓存区的地址也是变化的,那每次dyld加载动态库的时候是怎样找到依赖动态库进行加载的呢?

  • PIC 位置代码独立

    • 当MachO内部需要调用系统的库函数时,会现在MachO文件_DATA段中建立一个指针,指向外部函数,dyld就会将指针与函数进行动态绑定,将MachO中的DATA段中的指针指向外部函数,从而实现加载。

我们来看下第一份代码的MachO文件,如图:


Fishhook 学习笔记_第4张图片
image.png

当MachO内部有两个指针用来加载动态库:

  • Non-Lazy Symbol Pointers :不懒加载指针
  • Lazy Symbol Pointers :懒加载指针

这两个指针就是dyld用来在MachO内部调用系统库函数时来指向外部函数进行动态绑定加载。从上图我们可以看出在Lazy Symbol Pointers指针表中NSLog的位置,以及偏移地址是0x8018。

那我们根据这个偏移地址在第一份代码中进行LLDB调试看看结果如何:

  • 首先我们先在 fishhook的代码执行前设置断点:


    Fishhook 学习笔记_第5张图片
    image.png
  • 然后我们先拿到MachO文件在内存中的首地址:0x0000000100154000


    image.png
  • 根据偏移地址拿到指针指向的地址值:


    image.png
  • 根据地址值中的地址进行反汇编,我看下结果:


    Fishhook 学习笔记_第6张图片
    image.png

反汇编后结果清楚显示地址指向的是Foundation下的 NSLog函数

ok! 这是还没有进行fishhook前的NSLog ,那我们将断点断到fishhook代码执行后在看结果:


Fishhook 学习笔记_第7张图片
image.png

同样的偏移地址,地址值却发生了变化,反汇编后却发现,地址指向的是fishhookDemo1下的hookNSLog函数。

这就是Fishhook的原理:在dyld加载MachO文件所需要的系统函数时,通过改变MachO文件中的DATA段中指向外部函数的指针所指向的地址,来实现动态交换。

这里就可以解释我们在两次运行结果对比留下的两个疑惑:

  • 为什么fishhook可以交换C函数
    • fishhook 是通过改变dyld加载动态库所用的指针指向的函数地址,来实现的函数交换 ,所以C函数通过dyld动态加载动态库也展现出C函数动态的一面。
  • 为什么我们自己写的C函数fishhook交换不了
    • 我们自己写的C函数不在系统动态库缓存区,而是存在我们自己的MachO文件当中,不经过dyld加载,直接编译成汇编语言,在MachO中的TEXT段中。所以fishhook的原理不能交换我们自己写的C函数。

四、通过符号找到字符串

在上面我们知道了系统的动态库与我们的MachO文件是通过dyld在加载时在MachO文件中的一个指针指向了外部函数来进行加载,但是这只是个地址,我们在代码中调用函数时通过函数名称来调用的,那这个地址和我们调用的函数名称是如何建立起联系的呢?我们先来看下刚才的MachO文件,如图:

图1:


Fishhook 学习笔记_第8张图片
image.png

图2:


Fishhook 学习笔记_第9张图片
image.png

从图1、图2中我们可以看出Lazy Symbol Pointers 表 和 Dyamic Symbol Table 表中的关系是一一对应的,遍历前一张表的index就可以对应到后一张表。注意图2中的对应index行中的 Data 值为0x7F。

图3:


Fishhook 学习笔记_第10张图片
image.png

在Symbol Table 表中先拿到第一条的偏移地址0xC500。

图4:


Fishhook 学习笔记_第11张图片
image.png

在图2中index中获取的Data值为0x7F,这值是图4中index的标号,由于图4中index的偏移为0x10,所以偏移地址+Data对应的偏移地址为0xCCF0,找到当前index的Data值是0xA7。

图5:


Fishhook 学习笔记_第12张图片
image.png

最后在String Table 表中 偏移首地址+0xA7 的值为0x0000D02B,在表中差得字符串以“_”开头,以“.”结尾。所得的字符串就是外部函数地址所对应的函数名。

你可能感兴趣的:(Fishhook 学习笔记)