fishhook原理探究

上一篇整理了一下MachO文件的基本结构,但是对其主要的工作原理并没有深究,fishhook是iOS安全中常见的hook框架,可以通过对其原理探究更加深入的了解MachO的工作原理。

fishhook

fishHook是Facebook提供的一个动态修改链接mach-O文件的工具。利用MachO文件加载原理,通过修改懒加载表(Lazy Symbol Pointers)和非懒加载表(Non-Lazy Symbol Pointers)这两个表的指针达到C函数HOOK的目的。

首先需要下载fishhook源码,新建项目,将其中的fishhook.c和fishhook.h文件放入目录下:

fishhook简单利用

fishhook.h内容并不多,只有两个接口函数和一个结构体,具体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#ifndef fishhook_h
#define fishhook_h
#include <stddef.h>
#include <stdint.h>
#if !defined(FISHHOOK_EXPORT)
#define FISHHOOK_VISIBILITY __attribute__((visibility("hidden")))
#else
#define FISHHOOK_VISIBILITY __attribute__((visibility("default")))
#endif
#ifdef __cplusplus
extern "C" {
#endif //__cplusplus
struct rebinding {
const char *name; //需要HOOK的函数名称,字符串
void *replacement; //替换到哪个新的函数上(新函数地址)
void **replaced; //保存原始函数指针变量的指针
};
FISHHOOK_VISIBILITY
int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel);
FISHHOOK_VISIBILITY
int rebind_symbols_image(void *header,
intptr_t slide,
struct rebinding rebindings[],
size_t rebindings_nel);
#ifdef __cplusplus
}
#endif //__cplusplus
#endif //fishhook_h

rebind_symbols函数的第一个参数传入rebinding结构体数组,hook一个函数只需要传入一个rebinding结构体,hook多个函数则传入多个rebinding结构体。

rebinding结构体参数意义如下:

1
2
3
4
5
struct rebinding {
const char *name; //需要HOOK的函数名称,字符串
void *replacement; //替换到哪个新的函数上(新函数地址)
void **replaced; //保存原始函数指针变量的指针
};

demo的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#import "ViewController.h"
#import "fishhook.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//定义rebingding结构体
struct rebinding nslogBind;
//函数名称
nslogBind.name = "NSLog";
//新函数地址
nslogBind.replacement = myNSLog;
//保存原始函数地址的变量的指针
nslogBind.replaced = (void *)&old_nslog;
//数组
struct rebinding rebs[] = {nslogBind};
rebind_symbols(rebs, 1);
}
//函数指针,用来保存原始的函数的地址
static void(*old_nslog)(NSString *format, ...);
//新的NSLog
void myNSLog(NSString *format, ...){
format = @"~~勾上了!\n";
//再调用原来的nslog
old_nslog(format);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"点击了屏幕");
}
@end

运行点击屏幕,就可以Hook NSLog函数输出我们自己定义的字符串。

fishhook原理分析

首先有个问题需要思考,fishhook为什么可以Hook系统的C函数,只有动态才有可能被Hook,C函数是静态的,OC是动态的,这是因为系统的C函数有动态的部分,也就是符号绑定的过程。

PIC技术(位置代码独立):在文件内部生成一个表, 函数名称:函数地址,这玩意就是符号表
当MachO由dyld加载到内存中时,在调用NSLOg时,dyld将该函数地址赋值到符号表函数地址,这个过程就叫做符号绑定。

我们在之前的代码基础上添加两句NSLog输出

前面的NSLog是为了提前将该函数进行绑定,也即时懒加载符号表中,非懒加载符号在dyld加载时就会绑定真实的地址值,而懒加载不会,只有第一次去调用才会绑定真实地址,在第二次调用时直接使用真实地址。

编译demo,使用MachOView查看编译生成的MachO文件,可以看到NSLog是位于懒加载符号表的第一个,offset为0x8020,这里说一下为什么要在最上面的代码写一个NSLog,是为了先完成绑定真实地址的过程,下次调用就不用再去绑定了。

在rebind_symbols调用处下断点,调试输入命令image list查看demo运行时的基址。

用获取到的基址加上NSLog函数偏移0x8020,memory read查看内存,查看对应内存地址的汇编代码dis -s,发现是NSLog的函数真实地址

单步步过rebind_symbols(rebs, 1),再次查看NSLog偏移位置的内存,发现里面写入的地址有变化,查看对应的汇编代码,已经替换为我们自己写的myNSLog的真实地址。

通过上述的调试过程,可以看出fishhook实现原理其实就是重新绑定了符号表,将原本NSLog的地址替换为myNSLog的地址。至于如何重新绑定这个过程需要深入分析fishhook源码,之后单独开一篇进行分析。

class-dump原理

class-dump可以将应用的头文件导出,头文件包含了应用的类名和方法名对应关系,通过MachO文件格式来分析其原理。
同样以上述demo为例来探寻class-dump原理。
首先在数据段中寻找_objc_classlist节,该节中保存了程序中每个类的地址,下图可以看出0x8158地址上保存着数据0x100008E18,改地址保存的是类AppDelegate的信息

32 位类信息的结构体定义如下:

1
2
3
4
5
6
7
8
9
10
typedef struct objc_class_info{
int32_t isa;
int32_t wuperclass;
int32_t cache;
int32_t vtable;
int32_t data;
int32_t reserved1;
int32_t reserved2;
int32_t reserved3;
}objc_class_info;

64 位的类信息的结构体定义如下:

1
2
3
4
5
6
7
8
9
10
typedef struct objc_class_info_64{
int64_t isa;
int64_t wuperclass;
int64_t cache;
int64_t vtable;
int64_t data;
int64_t reserved1;
int64_t reserved2;
int64_t reserved3;
}objc_class_info_64;

0x100008E18属于数据段的_objc_data节

该类的Data数据是0x100008D30,其中保存类的数据信息

32位的结构体定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct objc_class_data{
int32_t flags;
int32_t instanceStart;
int32_t instanceSize;
int32_t ivarlayout;
int32_t name;
int32_t baseMethod;
int32_t baseProtocol;
int32_t ivars;
int32_t weakIvarLayout;
int32_t baseProperties;
};

64 位的结构体定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct objc_class_data_64{
int32_t flags;
int32_t instanceStart;
int32_t instanceSize;
int32_t reserved; //only arm64
int64_t ivarlayout;
int64_t name;
int64_t baseMethod;
int64_t baseProtocol;
int64_t ivars;
int64_t weakIvarLayout;
int64_t baseProperties;
};

在数据段的_objc_const节中查看0x100008D30, 该节保存是objc常量

Name中数据地址为0x100007711,保存的字符串位于__objc_classname节中

_objc_const节中Base Methods地址的数据是0x100008BD0,在_objc_const节寻找该地址,该类相关的函数都在其中。

其中方法名对应的字符串位于代码段的__objc_methname节中。

总结

通过fishhook和class-dump原理来熟悉MachO文件格式,反之通过MachO文件格式去解析各种工具实现原理,收获良多

有钱的捧个钱场
0%