浅谈ios的hook原理

作者: golgotha | 来源:发表于2017-07-21 11:33 被阅读2212次

    今天在看ReactiveCocoa的rac_signalForSelector源码时,很好奇它们是怎么做到的。看到它其实和Aspects、jspatch的hook原理其实是一样的。好吧。。。走起。

    oc中的Method实际是这样一个结构。

    struct objc_method {
        SEL method_name                                          OBJC2_UNAVAILABLE;
        char *method_types                                       OBJC2_UNAVAILABLE;
        IMP method_imp                                           OBJC2_UNAVAILABLE;
    }   
    

    SEL是方法名,IMP其实就是一个C函数指针,可以直接强制转换的。runtime 提供了一些api,比如method_getImplementation,可以直接操作这些函数。

    方法调用流程

    在 Objective-C 中,所有的 [receiver message] 都会转换为 objc_msgSend(receiver, @selector(message));

    寻找 IMP 的过程(查找过程):

    1. 在当前 class 的方法缓存里寻找(cache methodLists)
      找到了跳到对应的方法实现,没找到继续往下执行
    2. 从当前 class 的 方法列表里查找(methodLists),找到了添加到缓存列表里,然后跳转到对应的方法实现;没找到继续往下执行
    3. 从 superClass 的缓存列表和方法列表里查找,直到找到基类为止
      以上步骤还找不到 IMP,则用_objc_msgForward函数指针代替 IMP 。最后,执行这个 IMP 。
      这里是关键点。
      贴个伪代码:
    //  objc-runtime-new.mm 文件里与 _objc_msgForward 有关的三个函数使用伪代码展示
    //  Created by https://github.com/ChenYilong
    //  Copyright (c)  微博@iOS程序犭袁(http://weibo.com/luohanchenyilong/). All rights reserved.
    //  同时,这也是 obj_msgSend 的实现过程
    
    id objc_msgSend(id self, SEL op, ...) {
        if (!self) return nil;
        IMP imp = class_getMethodImplementation(self->isa, SEL op);
        imp(self, op, ...); //调用这个函数,伪代码...
    }
    
    //查找IMP
    IMP class_getMethodImplementation(Class cls, SEL sel) {
        if (!cls || !sel) return nil;
        IMP imp = lookUpImpOrNil(cls, sel);
        if (!imp) return _objc_msgForward; //_objc_msgForward 用于消息转发
        return imp;
    }
    
    IMP lookUpImpOrNil(Class cls, SEL sel) {
        if (!cls->initialize()) {
            _class_initialize(cls);
        }
    
        Class curClass = cls;
        IMP imp = nil;
        do { //先查缓存,缓存没有时重建,仍旧没有则向父类查询
            if (!curClass) break;
            if (!curClass->cache) fill_cache(cls, curClass);
            imp = cache_getImp(curClass, sel);
            if (imp) break;
        } while (curClass = curClass->superclass);
    
        return imp;
    }
    

    下面是消息转发的流程(转发过程):

    1. 第一个接手的具体方法是+(BOOL)resolveInstanceMethod:(SEL)sel 和+(BOOL)resolveClassMethod:(SEL)sel,当方法是实例方法时调用前者,当方法为类方法时,调用后者。这个方法设计的目的是为了给类利用 class_addMethod 添加方法的机会。(这不是重点)
    2. 第二个阶段是备援接收者阶段,对象的具体方法是-(id)forwardingTargetForSelector:(SEL)aSelector ,此时,运行时询问能否把消息转给其他接收者处理,也就是此时系统给了个将这个 SEL 转给其他对象的机会。(这也不是重点)
    3. 第三个阶段是完整消息转发阶段,对应方法-(void)forwardInvocation:(NSInvocation *)anInvocation,这是消息转发流程的最后一个环节。(这个是重点,hook方案的核心集中在这里)
      用图表示如下:
    aa.png
    1. 如果这里还处理不了。那只能抛出没有找到方法的异常了

    =================================================
    流程讲完了,我们开始探讨hook到底是怎么实现的。第二次进入正题。

    hook大体分两步:

    1. 直接替换原方法的实现为_objc_msgForward。当原来的函数被调用时,就不会在类方法,父类方法列表里查找实现了,直接表示找不到,进入转发流程。代码如下:
    class_replaceMethod(class, selector, _objc_msgForward, method_getTypeEncoding(targetMethod));
    

    用_objc_msgForward来代替原来的函数。

    1. 替换forwardInvocation:的实现,当进入转发流程时,阶段一和阶段二都不接手,在阶段三forwardInvocation:里会接手。
      代码如下:
        id newForwardInvocation = ^(id self, NSInvocation *invocation) {
            //hook时要添加的代码放在这里
    
            if (originalForwardInvocation == NULL) {
                [self doesNotRecognizeSelector:invocation.selector];
            } else {
                originalForwardInvocation(self, forwardInvocationSEL, invocation);
            }
        };
    
        class_replaceMethod(class, @selector(forwardInvocation:), imp_implementationWithBlock(newForwardInvocation), "v@:@");
    

    这里会替换forwardInvocation:的实现。用newForwardInvocation代替。这样就可以hook啦,完成自己的逻辑后,还要调用被hook的函数原来的逻辑。

    相关文章

      网友评论

      本文标题:浅谈ios的hook原理

      本文链接:https://www.haomeiwen.com/subject/glgskxtx.html