iOS开发-hook之Method Swizzle更改原有方法实现流程
一 Hook是什么?
Hook 简介
Hook,中文译为“挂钩”或“钩子”。通过hook可以让别人的程序执行自己所写的代码。
一段程序的执行流程是 A -> B -> C,现在我们在 A 和 B 之间插入一段代码或者直接改变 B ,这样程序原有的执行流程就发生了改变。如下图所示:
Hook的方式:
-
- Method Swizzle
-
- fishhook
-
- Cydia Substrate
Hook实现方式:利用Objective-C的Runtime(运行时)特性,动态去改变SEL(方法编号)和IMP(方法实现)的对应关系,达到Objective-C方法调用流程改变的目的主要用于Objective-C方法。
Hook中主要用到的方法(参数: Class、SEL、IMP、Method):
- method_exchangeImplementations
- class_replaceMethod
- method_setImplementation
- class_getMethodImplementation
二、Class与Method Swizzle
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一个Objective-C方法时,只需要改变其SEL所指向的IMP时,就可以实现方法的交换的目的,或者使用class_replaceMethod 和 method_setImplementation 改变一个类原有方法的实现。
逆向中,Hook一个Objective-C方法其实就是改变其SEL所指向的IMP,从而找到另一个实现地址,执行另一个方法实现。但这种方法的局限在于,其只能针对Objective-C方法进行Hook,对于C函数则无法Hook。
三、使用inline函数
使用inline快速实现在执行系统方法时候执行我们的代码
通过class_replaceMethod与method_exchangeImplementations实现。
#ifndef SSDSwizzlingDefine_h
#define SSDSwizzlingDefine_h#import <objc/Runtime(运行时).h>
static inline void swizzling_exchangeMethod(Class clazz ,SEL originalSelector, SEL swizzledSelector){Method originalMethod = class_getInstanceMethod(clazz, originalSelector);Method swizzledMethod = class_getInstanceMethod(clazz, swizzledSelector);BOOL success = class_addMethod(clazz, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));if (success) {class_replaceMethod(clazz, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));} else {method_exchangeImplementations(originalMethod, swizzledMethod);}
}#endif /* SSDSwizzlingDefine_h */
四、使用swizzling_exchangeMethod函数
通过swizzling_exchangeMethod替换系统方法,如下
@implementation UINavigationBar (SSDSwizzling)+ (void)load{static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{swizzling_exchangeMethod([UINavigationBar class] ,@selector(layoutSubviews), @selector(swizzling_layoutSubviews));});
}
该代码扩展UINavigationBar,在load更改方法顺序swizzling_layoutSubviews -> layoutSubviews
#import "UINavigationBar+SSDSwizzling.h"
#import "SSDSwizzlingDefine.h"@implementation UINavigationBar (SSDSwizzling)+ (void)load{static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{swizzling_exchangeMethod([UINavigationBar class] ,@selector(layoutSubviews), @selector(swizzling_layoutSubviews));});
}#pragma mark - LayoutSubviews
#define TitleMargin 43- (void)swizzling_layoutSubviews{BOOL isIOS11 = ([[[UIDevice currentDevice] systemVersion] doubleValue] >= 11.0);[self swizzling_layoutSubviews];UINavigationItem *navigationItem = [self topItem];UIView *subview = [[navigationItem leftBarButtonItem] customView];//CGFloat navigationBtnMargin = isIOS11? -10 : 28;CGFloat navigationBtnMargin = isIOS11? -10 : 15;CGRect subViewFrame = subview.frame;subViewFrame.origin.x = navigationBtnMargin;subview.frame = subViewFrame;//解决标题过长时,设置navigationItem.title导致标题偏移的问题UILabel *label = (UILabel *)navigationItem.titleView;if ([label isKindOfClass:[UILabel class]]){label.lineBreakMode = NSLineBreakByTruncatingMiddle;}UIFont *font = self.titleTextAttributes[NSFontAttributeName];if (font) {label.font = font;}UIColor *color = self.titleTextAttributes[NSForegroundColorAttributeName];if (label && [label isKindOfClass:[UILabel class]] && color) {label.textColor = color;}[label sizeToFit];[self layoutLabel];
}#pragma mark - Private
- (void)layoutLabel{UINavigationItem *navigationItem = [self topItem];UIView *view = navigationItem.titleView;CGPoint centerPonit = CGPointMake(self.frame.size.width * .5f, self.frame.size.height *.5f);UIView *superView = view.superview;centerPonit = [superView convertPoint:centerPonit fromView:self];view.center = centerPonit;
}@end
五、小结
iOS开发-替换方法class_replaceMethod更改执行方法流程
利用Objective-C的Runtime(运行时)特性,动态去改变SEL(方法编号)和IMP(方法实现)的对应关系,达到Objective-C方法调用流程改变的目的主要用于Objective-C方法。
学习记录,每天不停进步。