如何判断method是否被swizzled(续)
承上次的文章介绍了一种方法用来检测Objective-C中Method是否被swizzled。但该方法只能检测非系统的方法,即,必须在源文件中的目标方法中添加上述的宏才能Work,对于系统类的方法被Hook就无计可施了。 突破口回想一下Objective-C中Method Swizzling的原理,看如下代码: @implementation UIViewController (Hook) - (void)viewDidLoad2 { [self viewDidLoad2]; } + (void)load { Method ori_method = class_getInstanceMethod([UIViewController class],@selector(viewDidLoad)); Method replace_method = class_getInstanceMethod([UIViewController class],@selector(viewDidLoad2)); method_exchangeImplementations(ori_method,replace_method); } @end
typedef id (*IMP)(id,SEL,...); 实际上是函数指针。 那我可以做一些推测(撞大运编程又开始了),当App的二进制格式不会被混淆的前提下,App加载的某个dylib或者编译时期某个framework的.a文件,其二进制肯定连续的,意思是在二进制文件中,某个dylib或者framework中符号肯定是相对固定的。譬如,对于上述这个例子, 写段代码来证明一下: NSMutableDictionary *offsetDict = @{}.mutableCopy; unsigned classCount = 0; Class *allClasses = objc_copyClassList(&classCount); for (unsigned classIndex = 0; classIndex < classCount; ++classIndex) { @autoreleasepool { Class cls = allClasses[classIndex]; //只管UI NS开头的类 if ([NSStringFromClass(cls) hasPrefix:@"UI"] || [NSStringFromClass(cls) hasPrefix:@"NS"]) { unsigned methodCount = 0; Method *methods = class_copyMethodList(cls,&methodCount); for (unsigned methodIndex = 0; methodIndex < methodCount; ++methodIndex) { Method mtd = methods[methodIndex]; NSString *mtdString = [NSString stringWithUTF8String:sel_getName(method_getName(mtd))]; //_开头的内部方法忽略 if ([mtdString hasPrefix:@"_"]){ continue; } IMP imp = method_getImplementation(mtd); int offset = (int) cls - (int) imp; offsetDict[[NSString stringWithFormat:@"[%@ %s]",NSStringFromClass(cls),sel_getName(method_getName(mtd))]] = @(offset); } } } } 所以我就拿到了所有类的所有方法相对于该类的偏移。这个offsetDict是在离线环境拿到的,必须是在纯净的(即保证没有任何 在实际项目环境中,再读取这个offsetDict,用来和实际的offset比较,就能判断该Method是否被swizzled或者overridden。代码如下: BOOL isSiwwzledOrOverridden(Class cls,SEL selector) { //省略部分代码。。。 if (offsetDict){ NSNumber *num = offsetDict[[NSString stringWithFormat:@"[%@ %s]",sel_getName(selector)]]; if (num == nil){ NSLog(@"Could not find selector!"); return NO; } IMP imp = [cls instanceMethodForSelector:selector]; int offset = (int) cls - (int) imp; if (offset != [num integerValue]) return YES; } return NO; } 试了一下,果然有用!对于下列示例: @implementation UIViewController (YouDonKnow) - (void)viewDidLoad2 { [self viewDidLoad2]; } + (void)load { Method ori_method = class_getInstanceMethod([UIViewController class],replace_method); } - (void)viewDidAppear:(BOOL)animated { } @end 我们只需要调用: BOOL ret = isSiwwzledOrOverridden([UIViewController class],@selector(viewDidLoad));//YES 这是swizzling的情况 ret = isSiwwzledOrOverridden([UIViewController class],@selector(viewDidAppear:));//YES 这是overridden的情况 ret = isSiwwzledOrOverridden([UIViewController class],@selector(viewWillAppear:))//NO 就能知道是否被替换或者被覆盖了。 问题一大堆
#if TARGET_CPU_ARM64 BOOL isSiwwzledOrOverriddenOnArm64(Class cls,SEL selector) { IMP imp = [cls instanceMethodForSelector:selector]; int offset = (int) cls - (int) imp; return offset < 0; } #endif 就不误人子弟了,自己参悟一下吧,没找到有确切证据,只是实验出来的。
后记其实总感觉还有其他方法。因为,在进行method swizzling之后在lldb中打印IMP (lldb) po (IMP)class_getMethodImplementation([UIViewController class],@selector(viewDidLoad)) (SwizzleDetector`-[UIViewController(Hi) viewDidLoad2] at ViewController.m:16) 致歉后面我会具体读一下MacOS/iOS二进制的格式,以及Apple文档 还有看看fishhook。 万罪万罪。 原作写于segmentfault 链接 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |