上次的文章介绍了一种方法用来检测Objective-C中Method是否被swizzled。但该方法只能检测非系统的方法,即,必须在源文件中的目标方法中添加上述的宏才能Work,对于系统类的方法被Hook就机关用尽了。
代码整理后我会放到个人githubhtml
回想一下Objective-C中Method Swizzling的原理,看以下代码:git
@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
Method Swizzling
的核心是交换两个Method的IMP
,而IMP
的定义则是:github
typedef id (*IMP)(id, SEL, ...);
其实是函数指针。shell
那我能够作一些推测(撞大运编程又开始了),当App的二进制格式不会被混淆的前提下,App加载的某个dylib或者编译时期某个framework的.a文件,其二进制确定连续的,意思是在二进制文件中,某个dylib或者framework中符号确定是相对固定的。譬如,对于上述这个例子,UIViewController
这个Class的symbol的地址相对于-[UIViewController viewDidLoad]
的IMP
的地址偏移确定是固定的。编程
写段代码来证实一下:segmentfault
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是在离线环境拿到的,必须是在纯净的(即保证没有任何method swizzling
的状况下)环境下得到的。app
在实际项目环境中,再读取这个offsetDict,用来和实际的offset比较,就能判断该Method是否被swizzled或者overridden。代码以下:dom
BOOL isSiwwzledOrOverridden(Class cls, SEL selector) { //省略部分代码。。。 if (offsetDict){ NSNumber *num = offsetDict[[NSString stringWithFormat:@"[%@ %s]", NSStringFromClass(cls), 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; }
试了一下,果真有用!对于下列示例:iphone
@implementation UIViewController (YouDonKnow) - (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); } - (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
就能知道是否被替换或者被覆盖了。
x86
armv7
arm64
binary的format是不同的,经实验的确如此,因此offsetdict须要三个,分别是三个平台的。
因为OC有Category,如Foundation里的Class某些Method的实现并非放在Foundation的二进制里的。好比:NSObject的Accessibility。这些method也有可能会被认为是swizzled。
上面说了,这样推断只不过是撞大运编程大法的升级版,其实并无证据代表iOS的二进制结构知足这个规律(实际上有这些文档与资料)。
其实arm64或许有更简单的方法:
#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
就不误人子弟了,本身参悟一下吧,没找到有确切证据,只是实验出来的。
只在DEBUG下试过,不太肯定是否会被ASLR影响。不过我持乐观态度,由于:
该文章的目的并非要在真正的运行时去检测Swizzling从而进行防护,由于若是是防护攻击,在有可能被hook的前提下,上述方法自己也可以被Hook。
ASLR应该只会作基址偏移(On program load, the address space offset of the program is randomized between 0x0 and 0x100000
),不会影响上述offset的计算,没看到说iOS的ASLR会将其余lib的二进制全弄乱。
其实总感受还有其余方法。由于,在进行method swizzling以后在lldb中打印IMPpo imp
,lldb会定位swizzling method的代码的位置,好比对于上述被hook过的-[UIViewController viewDidLoad]
。编译器确定是知道的,但IMP是个指针,怎么会有这些信息呢?
(lldb) po (IMP)class_getMethodImplementation([UIViewController class], @selector(viewDidLoad)) (SwizzleDetector`-[UIViewController(Hi) viewDidLoad2] at ViewController.m:16)
后面我会具体读一下MacOS/iOS二进制的格式,以及Apple文档 还有看看fishhook。
万罪万罪。
原做写于segmentfault 连接