一、HOOK
1.1 HOOK简介
- HOOK,中文译为“挂钩”或“钩子”.在iOS逆向中是指改变程序运行流程的一种技术.
- 通过hook可以让别人的程序执行自己所写的代码. 在逆向中经常使用这种技术
- 重点要了解其原理,这样能够对恶意代码进行有效的防护.
1.2 Hook的应用场景
- 描述一个HOOK实用技术流程.正常情况下: 抢红包的流程为
- 收到红包消息
- 等待用户点开红包
- 等待用户点击“抢”
- 调用抢红包代码
- 然而通过HOOK手段: 自己定义恶意代码--->不需要用户点击"抢"红包,自动执行代码 ---> 调用抢红包代码
二、iOS中HOOK技术的几种方式
2.1 Method Swizzle
- 利用OC的Runtime特性,动态改变SEL(方法编号)和IMP(方法实现)的对应关系,达到OC方法调用流程改变的目的.主要用于OC方法
- 在OC中,SEL和IMP之间的关系,就好像一本书的“目录”.
- SEL是方法编号,就像“标记”一样
- IMP是方法实现的真实地址,就像“页码”一样.他们是一一对应的关系
- Runtime提供了交换两个SEL和IMP对应关系的关系
/**
* Exchanges the implementations of two methods.
* @param m1 Method to exchange with second method.
* @param m2 Method to exchange with first method.
* @note This is an atomic version of the following:
* \code
* IMP imp1 = method_getImplementation(m1);
* IMP imp2 = method_getImplementation(m2);
* method_setImplementation(m1, imp2);
* method_setImplementation(m2, imp1);
* \endcode
*/
OBJC_EXPORT void
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
- 通过这个函数交换两个SEL和IMP对应关系的技术,我们称之为Method Swizzle(方法欺骗)
2.2 fishhook
- 它是Facebook提供的一个动态修改链接MachO文件的工具.利用MachO文件加载原理,通过修改懒加载和非懒加载两个表的指针达到C函数HOOK的目的.
- fishhook 作用于main函数之前/之后都可以,作用于外部函数
2.3 Cydia Substrate
2.3.1 Cydia Substrate简介
- Cydia Substrate 原名为Mobile Substrate,它的主要作用是针对OC方法,C函数以及函数地址进行HOOK操作.
- 当然它并不是仅仅针对iOS而设计的,安卓一样可以用.官方地址 Cydia Substrate
2.3.2 MobileHooker
- 顾名思义用于HOOK.它定义一系列的宏和函数,底层调用objc的Runtime和fishhook来替换系统或者目标应用的函数.
- 其中有两个函数:
- MSHookMessageEx: 主要作用于Objective-C方法
void MSHookMessageEx(Class class,SEL selector, IMP replacement,IMP result)
-
- MSHookFunction 主要作用于C和C++函数
void MSHookFunction(void function,void*replacement,void **p_original)
-
- Logos语法的%hook 就是对此函数做了一层封装
2.3.3 MobileLoader
- MobileLoader用于加载第三方dylib在运行的应用程序中.启动时MobileLoader会根据规则把指定目录的第三方的动态库加载进去,第三方的动态库也就是我们写的破解程序.
2.3.4 Safe Mode
- 破解程序本质是dylib,寄生在别人进程里.系统进程一旦出错,可能导致整个进程崩溃,崩溃后就会造成iOS瘫痪.
- 所以CydiaSubstrate引入了安全模式,在安全模式下所有基于CydiaSubstratede的三方dylib都会被禁用,便于查错和恢复.
三、fishHook简单使用
3.1 获取fishhook代码
git clone https://github.com/facebook/fishhook.git
3.2 fishhook关键函数
struct rebinding {
const char *name;//需要Hook的函数名称,字符串
void *replacement; // 替换到哪个新的函数上(函数指针,也就是函数名称)
void **replaced;//保存原始函数指针变量的指针(它是一个二级指针)
};
//用来重新绑定符号表的函数,使用它来交换
// 参数一: 存放rebinding结构体的数组(可以同时交换多个函数)
// 参数二: rebindings数组的长度
FISHHOOK_VISIBILITY
int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel);
3.3 fishhook使用:
- fishhook源码注释
- OC动态特性: 不是直接调用方法实现的地址
- C静态语言: 直接通过地址访问
3.3.1 下面看一个HOOK 系统NSLog函数的案例
//------------HOOK NSLog------------
- (void)viewDidLoad {
[super viewDidLoad];
//创建rebinding 结构体
struct rebinding nslog;
nslog.name = "NSLog";
nslog.replacement = my_NSLog;
//保存NSLog系统函数地址的指针!
nslog.replaced = (void *)&sys_nslog;
//需求:HOOK NSLog
struct rebinding bds[] = {nslog};
rebind_symbols(bds, 1);
NSLog(@"end");
}
//函数指针!
static void (*sys_nslog)(NSString *format, ...);
//新函数!
void my_NSLog(NSString *format, ...){
format = [format stringByAppendingString:@"\n我HOOK到了!"];
//走到系统的nslog里面去!
sys_nslog(format);
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
//编译的时候,知不知道NSLog 的真实地址!
NSLog(@"hello");//PIC
//bl 本地代码地址.(ASLR)
//MachO Text(只读\可执行!)
//Data(可读\可写) 符号!! 符号表!!
//符号绑定!!
}
- 综上: 当我们HOOK 系统函数时,需要
- 1、构建一个rebinding结构体
- name: 符号名称、一个C字符串,用来表明我们要Hook哪个函数
- replacement: 新函数的地址,用来表示要用哪个函数进行替换原函数实现
- replaced: 二级函数指针,用来保存原函数的实现地址,记录原函数的实现.
- 2、构建一个rebinding结构体数组,将要Hook的函数构建结构体完毕后,放在此数组中即可,可以Hook多个函数
- 3、调用rebind_symbols函数,进行符号绑定.
- 1、构建一个rebinding结构体
3.3.2 接着看一个HOOK自定义函数的案例
//----------HOOK Func -------
void func(const char * str){
NSLog(@"%s",str);
}
static void (*func_p)(const char * str);
void my_func(const char * str){
NSLog(@"HOOK了!!");
func_p(str);
}
- (void)viewDidLoad {
[super viewDidLoad];
//创建rebinding 结构体
struct rebinding func;
func.name = "func";
func.replacement = my_func;
//保存NSLog系统函数地址的指针!
func.replaced = (void *)&func_p;
//需求:HOOK NSLog
struct rebinding bds[] = {func};
rebind_symbols(bds, 1);
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
func("HOOK Func");
}
- 此时虽然走了fishhook的代码、但是并没有发生Hook、两者差别在于
- NSLog属于系统框架、func属于自定义本地函数
3.3.3 静态链接与动态链接
- build一个工程主要包含了 预编译、编译、汇编、链接 几个过程,在实际的开发过程中,我们的程序代码不可能都放在一个文件里,因此我们需要通过符号将不同的文件链接起来,链接又分为静态链接(编译时)和动态链接(运行时)。如图所示在build一个工程后生成可执行文件,发生的链接是静态链接,而在dyld加载程序到内存中发生的链接为动态链接。
1、静态链接的特点是将一个进程中用到的库都加载进内存,而不管内存中是否加载过。
2、动态链接发生在加载可执行文件到内存时,系统先检测应用程序依赖的库中有没有已经加载的,若有则直接去内存取,不会重复加载。
3.3.4 fishhook原理-- 符号重新绑定
- 符号就是不同文件之间相互引用的函数名和变量名,比如A、B两个文件,B中调用A中的函数func(),我们称A定义了一个函数func(),B引用了A中的函数func(),这里func就是一个符号,我们可以将符号看作链接的粘合剂,整个链接过程基于符号才能够正确的完成。而我们自定义的函数是放在我们自己的文件中的,不会生成符号,所以无法Hook成功,fishhook只能Hook能生成符号表的C函数。
- 在程序编译完成生成可执行文件时,会先进行静态链接,此时会给系统的C函数的符号指定一个无意义的地址,等到加载完共享缓存之后,会将共享缓存库中的函数地址赋值给这个符号。此时我们对符号地址进行重新绑定,使其指向我们自己的函数,就完成了系统C函数的符号重新绑定,实现了Hook的目的。如果我们希望函数保持原有的功能,则需要定义一个二级指针,用于在加载完共享缓存后,保存原有函数的实现地址。
3.3.5 fishhook如何通过符号找到函数
- 1、在 _DATA_ 段的懒加载表 _la_symbol_ptr 找到对应的符号,如果没有则符号未定义.
- 2、如果有则在 Indirect Symbol Table 中找到对应符号的记录 根据记录中的Data值.
- 3、将根据Data值在 Symbol Table 中找到对应下标的记录.
- 4、根据记录中的 String Table Index 在 String Table中找到对应的函数名或变量名.
3.3.6 fishhook总结
- fishhook是基于对符号的重新绑定进行Hook的,可以Hook有能生成符号的函数,对于同一个文件中的函数无法Hook,因为其不会生成符号。
四、总结
- 符号绑定的过程
- 外部函数调用是执行桩里面的代码! TEXT, subs
- 通过懒加载符号表里面的地址去执行!
- 懒加载符号表里面默认保存的是寻找binder的代码
- binder函数在非懒加载符号表里面(程序运行就绑定好了)
- HOOK: 改变程序原有执行流程
- iOS中的HOOK技术
- OC方法: MethodSwizzle
- 系统函数: fishhook
- MobileHooker
- fishhook:
- 重新绑定符号做到HOOK的目的
- 外部符号,会在懒加载和非懒加载表中保存函数的地址
- fishhook就是找到这两张表,并且修改里面的地址做到HOOK!
- 重新绑定符号做到HOOK的目的
- iOS中的HOOK技术
- 外部函数调用是执行桩里面的代码! TEXT, subs