Hook简介
- Hook就是一种改变程序执行流程的一种技术的统称;
- 一段程序的执行流程是 A --> B --> C,现在我们在 A 和 B 之间插入一段代码或者直接改变 B ,这样程序原有的执行流程就发生了改变。如下图所示:
- Hook的方式:Method Swizzle,fishhook,Cydia Substrate;
Hook原理
一、Method Swizzle 原理
- 利用OC的Runtime特性,动态去改变SEL(方法编号)和IMP(方法实现)的对应关系,达到OC方法调用流程改变的目的主要用于OC方法。
- Hook中主要用到的方法(参数: Class、SEL、IMP、Method):
OBJC_EXPORT void
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
OBJC_EXPORT IMP _Nullable
class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp,
const char * _Nullable types)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
OBJC_EXPORT IMP _Nonnull
method_setImplementation(Method _Nonnull m, IMP _Nonnull imp)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
OBJC_EXPORT IMP _Nullable
class_getMethodImplementation(Class _Nullable cls, SEL _Nonnull name)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
- Class 一个 objc_class 类型的结构体指针,用于说明对象是哪个类;
- Method 一个 objc_method 类型的结构体指针,用于定义一个方法,在objc源码中定义如下:
struct objc_method {
SEL _Nonnull method_name OBJC2_UNAVAILABLE;
char * _Nullable method_types OBJC2_UNAVAILABLE;
IMP _Nonnull method_imp OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
- SEL 可以发现在 Method 的定义中,也能看见SEL。在苹果官方文档中定义这种类型:
typedef struct objc_selector *SEL;
- IMP 同样在 Method 的定义中也能看到,在苹果官方文档的定义如下:
id (IMP *)(id, SEL, ...)
- SEL是一个C String,用于表示一个方法的名称,IMP是一个方法实现首地址,默认有两个参数 self 和 _cmd。其实SEL和IMP的关系可以类比一本书的目录,SEL就是目录中的内容标题,IMP是后面的页码。一个方法调用时,通过SEL找到对应的IMP,进而找到方法的实现。如下图:
- 在Hook一个OC方法时,只需要改变其SEL所指向的IMP时,就可以实现方法的交换的目的,或者使用class_replaceMethod 和 method_setImplementation 改变一个类原有方法的实现,原理如下图:
- 逆向中,Hook一个OC方法其实就是改变其SEL所指向的IMP,从而找到另一个实现地址,执行另一个方法实现。但这种方法的局限在于,其只能针对OC方法进行Hook,对于C函数则无法Hook。
二、fishHook 原理
- fishHook是由faceBook开发的,是一个动态修改MachO文件的工具,主要是通过修改懒加载和非懒加载表里的指针的指向来达到hook的目的,因此一般用它来hook系统的C函数。
- DATA区有两个section和动态符号链接相关:__nl_symbol_ptr 、__la_symbol_ptr;__nl_symbol_ptr为一个指针数组,直接对应non-lazy绑定数据。__la_symbol_ptr也是一个指针数组,通过dyld_stub_binder辅助链接。
- 示例
static void (* old_log)(NSString* str);
void newLog(NSString * str){
str = [str stringByAppendingString:@"\n 勾住了"];
old_log(str);
}
rebind_symbols((struct rebinding [1]){{"NSLog",newLog,(void *)&old_log}}, 1);
- rebind_symbols的简单逻辑
假设:
A -> 原方法 ,B -> 新方法 ,Temp -> 中间变量
Temp = A;
A = B;
hook后需要调用原方法就调用Temp
- 具体如下:
1 > 首先,MachO文件是被dyld(dynamic load)动态加载的,也就是说,MachO文件被加载到内存的时候是不固定的,它有一个ASLR随机值;
2 > 同样,依赖的系统库的加载也是随机的,因此,当MachO文件加载到内存之前根本不知道系统库在哪;
3 > 因此 ,苹果采用了PIC技术(位置代码独立,位置和代码无关);
- 假设要调用NSLog函数:首先会在映射表中增加一个间接指针,指向外部的NSLog函数;然后dyld会动态的去绑定,将指针指向外部的函数地址。
三、Cydia Substrate原理
① 原理
- Cydia Substrate 原名为 Mobile Substrate ,它的主要作用是针对OC方法、C函数以及函数地址进行HOOK操作。
- 跟method Swizzle类似,也是用的OC的动态性,知识Method Swizzle是用的方法交换,Cydia Substrate 是用的method_getImplementation和 method_setImplementation这两个方法,获取方法实现和设置方法实现。
② 函数组成
Cydia Substrate主要由3部分组成:
- MobileHooker
它定义一系列的宏和函数,底层调用objc的runtime和fishhook来替换系统或者目标应用的函数。主要有两个函数:MSHookMessageEx 主要作用于Objective-C方法,MSHookFunction 主要作用于C和C++函数;
MSHookMessageEx函数的作用对象是Objective-C函数,其原理是调用Objective-C中高等级的运行时函数API:class_getInstanceMethod、method_setImplementation、method_exchangeImplementations等来替换原函数的逻辑,其实MobileHooker就是对fishhook和runtime做了封装,就是和AFNetworking和NSURLSession的关系是一样的;
void MSHookMessageEx(Class class, SEL selector, IMP replacement, IMP result)
void MSHookFunction(voidfunction,void* replacement,void** p_original)
- MobileLoader
MobileLoader的作用就是去加载第三方dylib,在ios启动的时候,会由launchd将MobileLoader载入内存,然后MobileLoader会根据同名的plist文件指定的作用范围,有选择地在不同的进程当中去通过dlopen函数打开目录/Library/MobileSubstrate/DynamicLibraries/下的所有的dylib。
- safe mode
因为APP程序质量参差不齐崩溃再所难免,破解程序本质是dylib,寄生在别人进程里。 一旦出错,就可能导致整个进程崩溃,而一旦崩溃的是SpringBoard等系统进程,崩溃后就会造成iOS瘫痪。所以CydiaSubstrate引入了安全模式,在安全模式下所有基于CydiaSubstratede 的三方dylib都会被禁用,便于查错与修复。
Hook流程(以Objective-C为例)
在 Objective-C 中,所有的 [receiver message] 都会转换为 objc_msgSend(receiver, @selector(message));
一、寻找 IMP 过程
- 在当前 class 的方法缓存里寻找(cache methodLists)
找到了跳到对应的方法实现,没找到继续往下执行;
- 从当前 class 的 方法列表里查找(methodLists),找到了添加到缓存列表里,然后跳转到对应的方法实现;没找到继续往下执行;
- 从 superClass 的缓存列表和方法列表里查找,直到找到基类为止;
- 以上步骤还找不到 IMP,则用_objc_msgForward函数指针代替 IMP ,最后执行这个 IMP;
id objc_msgSend(id self, SEL op, ...) {
if (!self) return nil;
IMP imp = class_getMethodImplementation(self->isa, SEL op);
imp(self, op, ...); }
IMP class_getMethodImplementation(Class cls, SEL sel) {
if (!cls || !sel) return nil;
IMP imp = lookUpImpOrNil(cls, sel);
if (!imp) return _objc_msgForward;
return imp;
}
IMP lookUpImpOrNil(Class cls, SEL sel) {
if (!cls->initialize()) {
_class_initialize(cls);
}
Class curClass = cls;
IMP imp = nil;
do {
if (!curClass) break;
if (!curClass->cache) fill_cache(cls, curClass);
imp = cache_getImp(curClass, sel);
if (imp) break;
} while (curClass = curClass->superclass);
return imp;
}
二、 消息转发流程
- 第一个阶段的具体方法是+(BOOL)resolveInstanceMethod:(SEL)sel 和+(BOOL)resolveClassMethod:(SEL)sel,当方法是实例方法时调用前者,当方法为类方法时,调用后者。这个方法是为了给类利用 class_addMethod 添加方法的机会;
- 第二个阶段是备援接收者阶段,对象的具体方法是-(id)forwardingTargetForSelector:(SEL)aSelector ,此时,运行时询问能否把消息转给其他接收者处理,也就是此时系统给了个将这个 SEL 转给其他对象的机会;
- 第三个阶段是完整消息转发阶段,对应方法-(void)forwardInvocation:(NSInvocation *)anInvocation,这是消息转发流程的最后一个环节(hook方案的核心)。
三、主要步骤
- 直接替换原方法的实现为_objc_msgForward。当原来的函数被调用时,就不会在类方法,父类方法列表里查找实现了,直接表示找不到,进入转发流程。用_objc_msgForward来代替原来的函数,代码如下:
class_replaceMethod(class, selector, _objc_msgForward, method_getTypeEncoding(targetMethod));
- 替换forwardInvocation:的实现,当进入转发流程时,阶段一和阶段二都不接手,在阶段三forwardInvocation:里会接手;这里会替换forwardInvocation:的实现,用newForwardInvocation代替,这样就可以hook,完成自己的逻辑后,还要调用被hook的函数原来的逻辑。
id newForwardInvocation = ^(id self, NSInvocation *invocation) {
if (originalForwardInvocation == NULL) {
[self doesNotRecognizeSelector:invocation.selector];
} else {
originalForwardInvocation(self, forwardInvocationSEL, invocation);
}
};
class_replaceMethod(class, @selector(forwardInvocation:), imp_implementationWithBlock(newForwardInvocation), "v@:@");
简单防护
一、防护Method Swizzle
- 我们知道Method Swizzle原理是方法交换,那么可以使用fishHook 修改method_exchangeImplementations函数的指向,这个修改指向要在我们的方法交换之后进行(保证自己能改,别人不能改)。
- 我们的方法交换要在别人hook之前执行,这个地方就需要将我们的方法封装到静态库中,静态库中的load方法会先加载;
二、防护Cydia Substrate
- 它的原理是修改imp的set和get方法,因此我们也可以通过fishHook修改method_getImplementation和method_setImplementation方法。