HOOK原理及介绍
注入小结
通过之前的学习,我们知道了利用动态库注入的两种方式:
- Framework
- Dylib
- 注入 App 后,使得 项目和动态库产生关联关系。
- 修改 Mach-o 文件的 Load Commands 段。
然后开始在注入的动态库中,开发你想实现的逻辑功能!
一、HOOK概述
HOOK(钩子) 其实就是改变程序执行流程的一种技术的统称!
![](https://img.haomeiwen.com/i4790087/f49e3d5c464f29b5.jpg)
iOS中HOOK技术的几种方式
1、Method Swizzle
利用OC的Runtime特性,动态改变SEL(方法编号)和IMP(方法实现)的对应关系,达到OC方法调用流程改变的目的。主要用于OC方法。
2、fishhook
它是Facebook提供的一个动态修改链接mach-O文件的工具。利用MachO文件加载原理,通过修改懒加载和非懒加载两个表的指针达到C函数HOOK的目的。
OC 是动态语言,C 则是静态语言,一经编译,所有的都可以获取到,那 Fishhook 是怎么做到的呢????
3、Cydia Substrate
Cydia Substrate 原名为 Mobile Substrate ,它的主要作用是针对OC方法、C函数以及函数地址进行HOOK操作。当然它并不是仅仅针对iOS而设计的,安卓一样可以用。官方地址:http://www.cydiasubstrate.com/
Cydia Substrate主要由3部分组成:
-
MobileHooker
MobileHooker顾名思义用于HOOK。它定义一系列的宏和函数,底层调用objc的runtime和fishhook来替换系统或者目标应用的函数.
其中有两个函数:- MSHookMessageEx 主要作用于Objective-C方法
void MSHookMessageEx(Class class, SEL selector, IMP replacement, IMP result)
- MSHookFunction 主要作用于C和C++函数
void MSHookFunction(voidfunction,void* replacement,void** p_original)
Logos语法的%hook 就是对此函数做了一层封装
-
MobileLoader
MobileLoader用于加载第三方dylib在运行的应用程序中。启动时MobileLoader会根据规则把指定目录的第三方的动态库加载进去,第三方的动态库也就是我们写的破解程序.
-
safe mode
因为APP程序质量参差不齐崩溃再所难免,破解程序本质是dylib,寄生在别人进程里。 系统进程一旦出错,可能导致整个进程崩溃,崩溃后就会造成iOS瘫痪。所以CydiaSubstrate引入了安全模式,在安全模 式下所有基于CydiaSubstratede 的三方dylib都会被禁用,便于查错与修复。
fishhook 的简单使用
Github 有相关的介绍,使用方法等;
https://github.com/facebook/fishhook
- fishhook Demo 测试 1:系统方法交换
- (void)viewDidLoad {
[super viewDidLoad];
// rebinding 结构体的定义
// struct rebinding {
// const char *name; // 需要 HOOK 的函数名称,字符串
// void *replacement; // 替换的新函数(函数指针,也就是函数名称)
// void **replaced; // 保存原始函数指针变量/地址的指针(它是一个二级指针!)
// };
// C 语言传参是值/址传递的,把它的值/址穿过去,就可以在函数内部修改函数指针变量的值
// 定义 rebinding 结构体
struct rebinding nslogRebind;
// 函数名称
nslogRebind.name = "NSLog";
// 新的函数指针
nslogRebind.replacement = myLog;
// 保存原始函数地址的变量的指针
nslogRebind.replaced = (void *)&old_Nslog;// fishhook 将 old_Nslog 这个指针指向了 NSLog 这个函数,怎么做的呢???
// 定义数组
struct rebinding rebs[] = {nslogRebind};
/*
1. rebindings : 存放 rebinding 结构体的数组,可以交换 N 种方法
2. size_t rebindings_nel : 数组的长度???
*/
rebind_symbols(rebs, 1);
}
// 函数指针,用来保存原始的函数地址 (C 语言语法,函数指针类型变量)
static void (*old_Nslog)(NSString *format, ...);
// 自定义的 Log
void myLog (NSString *format, ...){
format = [format stringByAppendingString:@"——> 勾上了!!!💖💖💖💖💖💖💖💖"];
old_Nslog(format);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
NSLog(@"点击了屏幕");
}
点击屏幕,可以看到控制台日志:
![](https://img.haomeiwen.com/i4790087/b9fe16fb19b33767.png)
- fishhook Demo 测试2:自定义方法交换 :
- (void)viewDidLoad {
[super viewDidLoad];
// 交换简写!
// (struct rebinding [1]) 数组类型
// {{"func",newFunc,(void *)&funcP}} 值为结构体,三个参数
rebind_symbols((struct rebinding [1]){{"func",newFunc,(void *)&funcP}}, 1);
}
// 原始函数指针
static void (*funcP)(const char *);
// 新函数
void newFunc(const char *str){
NSLog(@"———> 勾住了!!!💖💖💖💖💖💖");
funcP(str);
}
// 原始函数
void func(const char * str){
NSLog(@"%s",str);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
func("Hello world!!!");
}
![](https://img.haomeiwen.com/i4790087/61363285b41a4fe3.png)
首先代码是没有问题的,为什么呢?自定义方法交换不了呢???
fishhook 原理探究
-
Mach-O 文件是怎么加载的?
DYLD 工具动态加载,加载完 Mach-O 文件后,加载依赖的动态库,通过 image list 可以看到加载的相关类库。 -
ASLR 地址空间随机布局,Mach-O 文件加载的时候是随机地址的。
系统类库,每次启动,所有地址也是随机布局。 -
PIC 位置代码独立
???
控制器(Programmable Interrupt Controller)”的简称。
???
可编程中断控制器(Programmable Interrupt Controller),也简称为PIC,是微处器与外设之间的中断处理的桥梁,由外设发出的中断请求需要中断控制器来进行处理。
如果 Mach-o 文件内部需要调用系统的函数时:
-
现在 Mach-o _data 段 建立一个指针(就是符号,实现指向内部的函数调用,指向外部的函数地址),指向外部函数,(dyld 加载)
可读可写,当 Mach-o 被加载进去的时候,它就会指向所指的函数。 -
DYLD会进行动态的绑定,将 Mach-o 文件Data 段中的指针指向外部的函数!!!所以 DYLD 叫动态加载。
故 fishhook 中 的绑定方法叫做:rebind_symbols 重新绑定符号,就是绑定程序内部的函数。
这也就是为什么内部/自定义的函数修改不了,只能修改 Mach-O 文件外部的函数,如果是另一个动态库的或者是需要动态符号绑定的就可以,在符号表中能找到的才可以。
接下来,验证一下:
还是用 fishhook Demo 测试 1 来测试,运行,查看 Mach-o 符号表:
![](https://img.haomeiwen.com/i4790087/b0120a6d7de10218.png)
![](https://img.haomeiwen.com/i4790087/e535d6689ad078bb.png)
offset :8018, NSLog文件偏移地址,也就是说懒加载这个表也就在 Mach-O 文件偏移的地址 + 函数偏移地址
动态调试:
![](https://img.haomeiwen.com/i4790087/311292f9921e0734.png)
![](https://img.haomeiwen.com/i4790087/382129d1b12a74a1.png)
Mach-O 在内存中的偏移地址也就是 Mach-O 的真实地址。
Mach-o 文件Data 段中的函数指针
![](https://img.haomeiwen.com/i4790087/656e459371fb51b7.png)
通过符号表找方法的地址,通过 dis -s
反汇编命令查看函数详情。
接下来,过段点继续查看:
![](https://img.haomeiwen.com/i4790087/d44ea1550105ead9.png)
通过上面可以看出,fishhook 之所以能够 Hook C 函数,是因为根据 Mach-O 文件 特点,PIC 位置代码独立 也就造就了所谓的静态语言 C 也有动态的部分,通过 DYLD 进行动态绑定的时候,我们做了手脚,替换为我们自定义的方法。
备注: 值得注意的是,调用之前先调用一下 NSLog 函数,(符号表中应该没有 NSLog 函数的地址,但其实是有,但是可能是不正确的/固定的地址??)否则则调用 rebind_symbols 方法前不能正确的获取 函数的符号表地址(fishHook 内部会调用吧??)
- 懒加载表中 NSLog 函数什么时候给他赋值呢??
现在是存放在 Mach-o 文件偏移 8018 的位置 ,而它存放的一个 8 个字节的指针,保存的就是 动态缓存区 中保存 NSLog 函数的真实的地址,那这个地址是什么时候保存进去的呢???
并不是 Mach-O 文件加载的时候保存的,而是第一次调用 该函数时保存进去的,绑定一下,由 DYLD 绑定 NSLog 这个符号指向真实的 NSLog 的地址。
![](https://img.haomeiwen.com/i4790087/1941814d78cdd8f2.png)
![](https://img.haomeiwen.com/i4790087/5b5d4ed996b68f54.png)
那 fishhook 根据方法字符串名字 "NSLog" 也就是 struct rebinding const char *name 是怎么找到的呢 ???
通过符号表查看函数名称字符串
再次查看 Mach-o 文件,上面查看的懒加载表中的 NSLog 函数:
![](https://img.haomeiwen.com/i4790087/5fea9e789667a8b5.png)
懒加载表和动态符号表是一一对应 的关系,如下:
![](https://img.haomeiwen.com/i4790087/b0df1fb0baaffbfd.png)
动态符号表中的 Data 其实就是 方法的 Symbol Table 中的下标,转换为 10 进制 (0x88 = 136),到 Symbol Table 中查看:
![](https://img.haomeiwen.com/i4790087/0442db42e2ecd821.png)
_ 和 .
_ 是函数的开始,. 是分隔符,\0 的转义字符。
这里还不是最终的 NSLog 字符串,可以看到 _NSLog 在 String Table 中的 Index 偏移为 0x000000AC。接下来继续查看 String Table :
![](https://img.haomeiwen.com/i4790087/012aced97352c678.png)
String Table 首地址0xD0A4 + 偏移地址 0xAc = 0xD150
以上过程也就是 GitHub fishhook 的说明图,如下:
小结:
![](http://upload-images.jianshu.io/upload_images/4790087-f97571b62f205d39.png)