1.HOOK概述
1.1 hook
HOOK,中文译为“挂钩”或者“钩子”。在iOS逆向中是指改变程序运行流程的一种技术。通过HOOK可以让别人的程序执行自己所写的代码。
1.2 iOS中的hook
- Method Swizzle: 利用OCRuntime特性,动态改变SEL(方法编号)和IMP(方法实现)的对应关系,达到IC方法调用流程改变的目的。主要用于OC方法。
- fishhook: 它是Facebook提供的一个动态修改链接mach-O文件的工具。利用MachO文件加载原理,通过修改懒加载和非懒加载两个表的指针达到C函数HOOK的目的。
- Cydia Substrate: Cydia Substrate 原名Mobile Substrate ,它的主要作用是针对OC方法,C函数以及函数地址进行HOOK操作。当然它并不是仅仅针对iOS而设计的,Android一样可以用。官方地址:www.cydiasubstrate.com/
1.3 Method Swizzle
在OC中,SEL和IMP之间的关系,就好像一本书的目录。SEL是方法编号,就像标题一样。IMP是方法实现的真实地址,就像页码一样。他们是一一对应的关系。
Runtime提供了交换两个SEL和IMP对应关系的函数。通过这个函数交换两个SEL和IMP对应关系的技术,我们称之为Method Swizzle(方法欺骗)
1.4 Cydia Substrate(MobileHooker)
它定义一系列的宏和函数,底层调用objc的runtime和fishhook来替换系统或者目标应用的函数。
- MSHookMessageEx: 主要作用于Objective-C方法
void MSHookMessageEx(Class class, SEL selector, IMP replacement, IMP result)
- MSHookFunction: 主要作用于C和C++函数,Logos语法的%hook 就是对此函数做了一层封装
void MSHookFunction(voidfunction,void* replacement,void** p_original)
2. fishHook
2.1 fishHook
它是Facebook提供的一个动态修改链接mach-O文件的工具。利用MachO文件加载原理,通过修改懒加载和非懒加载两个表的指针达到C函数HOOK的目的
2.2 获取地址
https://github.com/facebook/fishhook
2.3 关键函数
- 结构体
/*
* A structure representing a particular intended rebinding from a symbol
* name to its replacement
*/
struct rebinding {
const char *name; // 函数名称
void *replacement;// 交换函数的地址
void **replaced; // 原函数的地址
};
- 常用函数
/*
* For each rebinding in rebindings, rebinds references to external, indirect
* symbols with the specified name to instead point at replacement for each
* image in the calling process as well as for all future images that are loaded
* by the process. If rebind_functions is called more than once, the symbols to
* rebind are added to the existing list of rebindings, and if a given symbol
* is rebound more than once, the later rebinding will take precedence.
*/
FISHHOOK_VISIBILITY
int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel);
/*
参数一:存放着 rebinding 结构体数组,可以同时交换多个函数
参数二:rebindings 数组的长度
*/
2.4 示例(替换NSLog)
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
struct rebinding nsLog;
nsLog.name = "NSLog";
nsLog.replacement = myLog;
nsLog.replaced = (void *)&sys_log;
/// rebinding数组
struct rebinding rebs[1] = {nsLog};
/// 重新绑定
rebind_symbols(rebs, 1);
}
///函数指针,用来保存原始的函数地址
static void (*sys_log)(NSString * format,...);
/// 新的函数,(注:在C语言张红函数的名称就是函数的指针!)
void myLog(NSString * format,...) {
format = [format stringByAppendingFormat:@"Hook到了!"];
// 调用系统的函数
sys_log(format);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"打印一下");
}
- 打印结果: 打印一下Hook到了!
3.fishhook原理
3.1 Hook自定义函数
// 测试代码
struct rebinding func;
func.name = "func";
func.replacement = newFunc;
func.replaced = (void *)&funcP;
struct rebinding funcs[1] = {func};
rebind_symbols(funcs, 1);
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
func("打印一下");
}
//函数的实现地址在MachO本地文件。
void func(const char * str){
NSLog(@"%s",str);
}
//原始的函数指针
static void(*funcP)(const char *);
//新函数
void newFunc(const char * str){
NSLog(@"勾住了!");//指向的符号!!
funcP(str);
}
- 打印结果: 打印一下
- 为什么没有勾住呢,说明fishhook不支持hook自定义的C函数。系统的C函数存在着动态的部分。 NSLog 存在于:动态库共享缓存(dyld shared cache)。func的函数实现地址在MachO本地,在编译的时候就确定了,没有动态的特性。
3.2 PIC
fishhook可以Hook C函数,由于函数是静态的,也就是说在编译的时候,编译器就知道了它的实现地址,这也是为什么C函数只写函数声明在调用的时候会报错。那么为什么fishhook能hook C 函数呢?首先要了解一下PIC技术。
PIC技术: iOS的执行文件是MachO,在编译完成后会生成一个符号表,在MachO的DATA段。应用加载的时候dyld会将符号与函数的真实实现地址进行绑定。当调用系统函数的时候会先找到符号,符号的值就是函数的实现地址(也就是找到了函数的真实实现地址)。然后就可以根据函数的真实地址进行调用了。这就是Apple的PIC技术。所以自定义函数就不能进行hook,自定义函数不需要PIC技术,所以也就不需要符号,找不到符号就不能进行重新绑定,所以就不能进行hook。
- fishhook原理: 所以fishhook的原理就是重新给绑定符号的函数的实现地址,将符号的实现地址绑定到自己实现的地址(replacement),然后在保存函数的真实实现地址(replaced)。当调用函数时就可以调用我们自己实现的函数了。从而达到了hook效果。(即:通过符号查找字符串)详细解读请看:fishhook
4.验证符号绑定
4.1验证dyld和fishhook的符号绑定
测试代码:如图的两处设置断点。
上述两个地址相加得:0x100DBC000
依次过掉断点,读内存,查看汇编。
结论: 在第一个断点前,查看内存是乱数据,当执行完第一个断点后就看到了系统的Foundation 框架的 NSLog 所以说明 NSLog 是懒加载形式的绑定,所以存在于MachO文件里面的Lazy Symbol Pointer 里。在使用的时候dyld继续符号的绑定。在执行完fishhook的重新绑定后,读这个偏移量的内存已经替换成了我们自己的myLog。从而得知fishhook对我们的符号进行了重新的绑定。
5. fishhook查找符号
- 通过 MachOView 查看 MachO 文件的 Lazy Symbol Pointers 找到NSLog
- 上面NSLog对应 Dynamic Symbol Table 里面NSLog的位置(这里都是第一个)
- 根据 Dynamic Symbol Table NSLog Data段的内容(这里是000000B7)换算成10进制就是183 去 Symbol Table 里面找到编号是#183的符号,也就找到了偏移地址(箭头所示):000000E4
- 最后去String Table 里面根据首地址:000124F4 加偏移地址:000000E4 得到:0x115B8
自此通过读取MachO 通过传入的Name找到NSLog。
特别提醒!
原创文章,转载注明出处!