被问到一个问题:假如你做SDK给外部使用,怎样保证提供的函数不被外部hook
?
我们知道,iOS中的hook
基本原理有两个:
1.OC的动态性,利用 Method Swizzling 进行hook;
2.C语言在iOS中的动态性,利用符号重绑定进行hook。
所以,我们可以利用OC
的Method Swizzling
来hook
方法,有以下三种方法:
1、方法交换
OBJC_EXPORT void
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
2、替换方法
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);
3、setIMP & getIMP
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);
创建工程,在StoryBord
中画两个按钮,然后在ViewController
中实现对应的方法:
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
+(void)load{
NSLog(@"ViewController--Load");
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (IBAction)method1:(id)sender {
NSLog(@"这是方法一");
}
- (IBAction)method2:(id)sender {
NSLog(@"这是方法二");
}
@end
接着新创建HookClass
类,先实现hook
,以class_getInstanceMethod
为例:
+ (void)load {
//交换方法一
Method old1 = class_getInstanceMethod(objc_getClass("ViewController"), @selector(method1:));
Method new1 = class_getInstanceMethod(self, @selector(click1Hook1:));
method_exchangeImplementations(old1, new1);
//交换方法二
Method old2 = class_getInstanceMethod(objc_getClass("ViewController"), @selector(method2:));
Method new2 = class_getInstanceMethod(self, @selector(click1Hook2:));
method_exchangeImplementations(old2, new2);
}
实现需要的交换的两个方法
- (void)click1Hook1:(id)sender {
NSLog(@"点击了hook1");
}
- (void)click1Hook2:(id)sender {
NSLog(@"点击了hook2");
}
运行代码,点击两个按钮,发现其实现了方法交换:
2022-12-09 18:30:08.954801+0800 HookTest[80560:759752] 点击了hook1
2022-12-09 18:30:09.892046+0800 HookTest[80560:759752] 点击了hook2
那么问题来了,如果我们向外部提供的方法被hook
了,造成预期外的结果,那么该如何防止呢?由于dyld
加载程序时候,对于外部符号(例如系统函数)是lazybind
加载的,编译的时候并不是绑定真实的地址,而是在运行时动态绑定的,所以可以使用finishhook来hook
系统方法。如我们可以先把method_exchangeImplementations
先换成我们自己的函数,外部再使用method_exchangeImplementations
来交换方法时就失效啦。同理,class_replaceMethod
及method_setImplementation
& class_getMethodImplementation
也是一样。需要注意的是,dyld
在加载程序的时候,会先加载动态库,并且是按照MachO
文件存储的顺序加载(也就是Xcode链接库的顺序),所以我们要把hook
代码放到动态库最前面。完整代码如下:
#import "HookClass.h"
#import <objc/runtime.h>
#import "fishhook.h"
@implementation HookClass
//保留原来的交换函数
void (* exchangeProtect)(Method _Nonnull m1, Method _Nonnull m2);
IMP _Nonnull (* setIMP)(Method _Nonnull m, IMP _Nonnull imp);
IMP _Nonnull (* getIMP)(Method _Nonnull m);
//新的函数
void protectExchange(Method _Nonnull m1, Method _Nonnull m2){
NSLog(@"检测到了hook");
}
+ (void)load {
Method old1 = class_getInstanceMethod(objc_getClass("ViewController"), @selector(method1:));
Method new1 = class_getInstanceMethod(self, @selector(click1Hook1:));
method_exchangeImplementations(old1, new1);
Method old2 = class_getInstanceMethod(objc_getClass("ViewController"), @selector(method2:));
Method new2 = class_getInstanceMethod(self, @selector(click1Hook2:));
method_exchangeImplementations(old2, new2);
//在交换代码之前,把所有的runtime代码写完
//防护
struct rebinding bd;
bd.name = "method_exchangeImplementations";
bd.replacement=protectExchange;
bd.replaced=(void *)&exchangeProtect;
struct rebinding bd1;
bd1.name = "class_replaceMethod";
bd1.replacement=protectExchange;
bd1.replaced=(void *)&exchangeProtect;
struct rebinding bd2;
bd2.name = "method_setImplementation";
bd2.replacement=protectExchange;
bd2.replaced=(void *)&setIMP;
struct rebinding bd3;
bd3.name = "method_getImplementation";
bd3.replacement=protectExchange;
bd3.replaced=(void *)&getIMP;
struct rebinding rebindings[]={bd,bd1,bd2,bd3};
rebind_symbols(rebindings, 4);
}
- (void)click1Hook1:(id)sender {
NSLog(@"点击了hook1");
}
- (void)click1Hook2:(id)sender {
NSLog(@"点击了hook2");
}
@end
再建个HookClass+protect
分类来验证,如果外部想hook
我们的方法,就会有检测到了hook
的提示,代码如下:
#import "HookClass+protect.h"
#import <objc/runtime.h>
@implementation HookClass (protect)
+ (void)load {
Method oldM = class_getInstanceMethod([self class], @selector(test));
method_exchangeImplementations(oldM, class_getInstanceMethod([self class], @selector(hookExchange)));
class_replaceMethod([self class], @selector(test), class_getMethodImplementation(self.class, @selector(hookReplace)), "v@:");
method_getImplementation(oldM);
method_setImplementation(oldM, class_getMethodImplementation(self.class, @selector(myTest)));
}
- (void)test {
NSLog(@"test方法");
}
- (void)hookExchange {
NSLog(@"hookExchange 到了 test");
}
- (void)hookReplace {
NSLog(@"hookReplace 到了 test");
}
- (void)hookSet {
NSLog(@"hookSet 到了 test");
}
@end
运行代码,可以看到,我们的hook
防护起到了作用:
2022-12-09 18:29:59.395944+0800 HookTest[80560:759752] 检测到了hook
2022-12-09 18:29:59.396107+0800 HookTest[80560:759752] 检测到了hook
2022-12-09 18:29:59.396201+0800 HookTest[80560:759752] 检测到了hook
2022-12-09 18:29:59.396279+0800 HookTest[80560:759752] 检测到了hook