资讯专栏INFORMATION COLUMN

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

pepperwang / 3568人阅读

摘要:承上次的文章介绍了一种方法用来检测中是否被。在实际项目环境中,再读取这个,用来和实际的比较,就能判断该是否被或者。这些也有可能会被认为是。只在下试过,不太确定是否会被影响。

上次的文章介绍了一种方法用来检测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]", 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;
}

试了一下,果然有用!对于下列示例:

@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 链接

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/16111.html

相关文章

  • Android Okhttp 断点传面试解析

    摘要:怎么支持断点续传的协议中默认支持获取文件的部分内容,这其中主要是通过头部的两个参数和来实现的。我们在刷一下面试题的时候,有时候会看到一些大厂会问关于断点续传的原理,那么今天在这里从 HTTP 断点续传知识和 Android 中如何实现断点续传的思路来做一个关于 Android 断点续传原理的总结。 Http 断点续传知识点 什么是断点续传 指的是在上传/下载时,将任务(一个文件或压缩包)人为...

    ACb0y 评论0 收藏0
  • Aspects AOP 的实现

    摘要:我们知道在中,实现采用的是动态代理的方式,那么在中的实现,其实就是通过的方式进行啦。如果有实现了,则调用并返回。调用方法,尝试找到一个能响应该消息的对象。最常见的实现消息转发,就是重写方法和,吞掉一个消息或者代理给其他对象都是没问题的。 都传闻说 OC 的运行时非常NB,今天就来看看非常有名的Aspects,源码在这 https://github.com/steipete/A... 里...

    Anonymous1 评论0 收藏0
  • vue 大文件如何分片上传(断点传、并发上传、秒传)

      上传文件中出现内存比较大的情况要如何处理?其实不论是用户端还是服务端,假如采用一次性进行读取发送、接收都是不可取,很容易导致内存问题。这样我们可以考虑采用切块分段上传,从上传的效率来看,利用多线程并发上传能够达到最大效率。  本篇文章主要为大家讲述就是基于springboot+vue实现的文件上传,本现在我就来说说vue实现文件上传的步骤及代码实现,服务端(springboot)的实现步骤及实...

    3403771864 评论0 收藏0
  • DM 源码阅读系列文章(六)relay log 的实现

    摘要:作者张学程本文为源码阅读系列文章的第六篇,在上篇文章中我们介绍了处理单元的实现,对在增量复制过程中的读取过滤路由转换以及执行等逻辑进行了分析。值得注意的是,由于我们近期正在对处理单元进行重构,因此源码中会同时包含重构前后的相关代码实现。 作者:张学程 本文为 DM 源码阅读系列文章的第六篇,在 上篇文章 中我们介绍了 binlog replication 处理单元的实现,对在增量复制过...

    HitenDev 评论0 收藏0
  • DM 源码阅读系列文章(六)relay log 的实现

    摘要:作者张学程本文为源码阅读系列文章的第六篇,在上篇文章中我们介绍了处理单元的实现,对在增量复制过程中的读取过滤路由转换以及执行等逻辑进行了分析。值得注意的是,由于我们近期正在对处理单元进行重构,因此源码中会同时包含重构前后的相关代码实现。 作者:张学程 本文为 DM 源码阅读系列文章的第六篇,在 上篇文章 中我们介绍了 binlog replication 处理单元的实现,对在增量复制过...

    张金宝 评论0 收藏0

发表评论

0条评论

pepperwang

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<