如何判断method是否被swizzled(续)

如何判断method是否被swizzled(续),第1张

概述承 上次的文章介绍了一种方法用来检测Objective-C中Method是否被swizzled。但该方法只能检测非系统的方法,即,必须在源文件中的目标方法中添加上述的宏才能Work,对于系统类的方法被Hook就无计可施了。 代码整理后我会放到我的github 突破口 回想一下Objective-C中Method Swizzling的原理,看如下代码: @implementation UIViewC 承

上次的文章介绍了一种方法用来检测Objective-C中Method是否被swizzled。但该方法只能检测非系统的方法,即,必须在源文件中的目标方法中添加上述的宏才能Work,对于系统类的方法被Hook就无计可施了。
代码整理后我会放到我的github

突破口

回想一下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

Method Swizzling的核心是交换两个Method的IMP,而IMP的定义则是:

typedef ID             (*IMP)(ID,SEL,...);

实际上是函数指针。

那我可以做一些推测(撞大运编程又开始了),当App的二进制格式不会被混淆的前提下,App加载的某个dylib或者编译时期某个framework的.a文件,其二进制肯定连续的,意思是在二进制文件中,某个dylib或者framework中符号肯定是相对固定的。譬如,对于上述这个例子,UIVIEwController这个Class的symbol的地址相对于-[UIVIEwController vIEwDIDLoad]IMP的地址偏移肯定是固定的。

写段代码来证明一下:

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的情况下)环境下获得的。

在实际项目环境中,再读取这个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

就能知道是否被替换或者被覆盖了。

问题一大堆

x86 armv7 arm64 binary的format是不一样的,经实验的确如此,所以offsetdict需要三个,分别是三个平台的。

由于OC有category,如Foundation里的Class某些Method的实现并不是放在Foundation的二进制里的。比如:NSObject的Accessibility。这些method也有可能会被认为是swizzled。

上面说了,这样推断只不过是撞大运编程大法的升级版,其实并没有证据表明iOS的二进制结构满足这个规律(实际上有这些文档与资料)。

其实arm64或许有更简单的方法:

#if TARGET_cpu_ARM64BOol 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 链接

总结

以上是内存溢出为你收集整理的如何判断method是否被swizzled(续)全部内容,希望文章能够帮你解决如何判断method是否被swizzled(续)所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1079122.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-27
下一篇 2022-05-27

发表评论

登录后才能评论

评论列表(0条)

保存