利用Objective-C运行时hook函数的三种方法

  • Post author:
  • Post category:其他



方法一,hook已有公开头文件的类:

首先写一个Utility函数:


  1. #import <objc/runtime.h>

  2. inline


    void

    exchangeMethod(Class aClass, SEL oldSEL, SEL newSEL)
  3. {
  4. Method oldMethod = class_getInstanceMethod(aClass, oldSEL);
  5. assert(oldMethod);
  6. Method newMethod = class_getInstanceMethod(aClass, newSEL);
  7. assert(newMethod);
  8. method_exchangeImplementations(oldMethod, newMethod);
  9. }

现在,目标是hook UIWebView没公开的函数

  1. – (

    void

    )webView:(id)arg1 didFinishLoadForFrame:(id)arg2;

因为已知类的声明,所以可以使用category:

  1. @interface UIWebView (Hook)
  2. + (

    void

    )hook;
  3. – (

    void

    )hook_webView:(id)arg1 didFinishLoadForFrame:(id)arg2;
  4. @end
  5. @implementation UIWebView (Hook)
  6. + (

    void

    )hook
  7. {

  8. // hook UIWebView中表示一个HTML的frame加载完毕的函数
  9. exchangeMethod([UIWebViewclass],
  10. @selector(webView:didFinishLoadForFrame:),
  11. @selector(hook_webView:didFinishLoadForFrame:));
  12. }
  13. – (

    void

    )hook_webView:(id)arg1 didFinishLoadForFrame:(id)arg2
  14. {

  15. // 因为交换了selector和implementation的映射,原样调一下本函数实际会调用被hook的函数。
  16. [self hook_webView:arg1 didFinishLoadForFrame:arg2];
  17. NSLog(@

    “webView:didFinishLoadForFrame:”

    );
  18. }

在程序启动的时候调用一下 [UIWebView hook] 即可。使用一个UIWebView打开一个网页,即会打印NSLog。


方法二,hook没有公开头文件的类,需要另建一个类作为新函数载体,然后先为被hook的类增加函数,再替换:


UIWebView体系中有一个类叫UIWebBrowserView,它是真正显示网页的UIView,并有部分函数是作为WebCore向外发送回调信息的接收者。

现我们去hook UIWebBrowserView的这个函数:

  1. – (

    void

    )webView:(id)arg1 didFinishLoadForFrame:(id)arg2;

嗯,是的,这个函数和UIWebView的一样,实际上就是UIWebBrowserView会再调用通知到UIWebView的同名函数。

创建一个类,不要与被hook的类同名,例如加了个Hook后缀:

  1. @interface UIWebBrowserViewHook : NSObject
  2. + (

    void

    )hook;
  3. – (

    void

    )hook_webView:(id)arg1 didFinishLoadForFrame:(id)arg2;
  4. @end

其中以hook_为前缀的新增函数的实现与方法一中相同,差别在类函数中:

  1. @implementation UIWebBrowserViewHook
  2. + (

    void

    )hook
  3. {
  4. Class aClass = objc_getClass(

    “UIWebBrowserView”

    );
  5. SEL sel = @selector(hook_webView:didFinishLoadForFrame:);

  6. // 为UIWebBrowserView增加函数
  7. class_addMethod(aClass, sel, class_getMethodImplementation([self

    class

    ], sel),

    “v@:@@”

    );

  8. // 交换实现
  9. exchangeMethod(aClass, @selector(webView:didFinishLoadForFrame:), sel);
  10. }

在程序启动的时候调用一下 [UIWebBrowserViewHook hook] 即可。使用一个UIWebView打开一个网页,即会打印NSLog。


方法三,hook没有公开头文件的类,另建一个类作为新函数载体,用新函数替换旧函数,并把旧函数保存到静态变量里:

继续以UIWebBrowserView为例子。注意新函数可以与被hook的函数同名

  1. @interface UIWebBrowserViewHook : NSObject
  2. + (

    void

    )hook;
  3. – (

    void

    )webView:(id)arg1 didFinishLoadForFrame:(id)arg2;
  4. @end

需要用到另一个Utility函数:


  1. inline


    void

    replaceImplementation(Class newClass, Class hookedClass, SEL sel, IMP& oldImp)
  2. {
  3. Method old = class_getInstanceMethod(hookedClass, sel);
  4. IMP newImp = class_getMethodImplementation(newClass, sel);
  5. oldImp = method_setImplementation(old, newImp);
  6. }

当两个selector不同名时,以上函数再增加一个参数即可。

下面是实现:

  1. @implementation UIWebBrowserViewHook

  2. static

    IMP webView_didFinishLoadForFrame = NULL;
  3. + (

    void

    )hook
  4. {
  5. Class hookedClass = objc_getClass(

    “UIWebBrowserView”

    );
  6. SEL sel = @selector(webView:didFinishLoadForFrame:);
  7. replaceImplementation([self

    class

    ], hookedClass, sel, webView_didFinishLoadForFrame);
  8. }
  9. – (

    void

    )webView:(id)arg1 didFinishLoadForFrame:(id)arg2
  10. {

  11. // 需要这样来调用被替换掉的原实现
  12. webView_didFinishLoadForFrame(self, @selector(webView:didFinishLoadForFrame:), arg1, arg2);
  13. NSLog(@

    “webView:didFinishLoadForFrame:”

    );
  14. }
  15. @end

在程序启动的时候调用一下 [UIWebBrowserViewHook hook] 即可。使用一个UIWebView打开一个网页,即会打印NSLog。


三种方法的比较:

最方便的当然是第一种,但需要是hook有公开头文件的类。

方法二和方法三都新建了一个类,方法二需要描述selector的types,这个比较麻烦,如例子中的”v@:@@”表示返回值为void,第一和第二个参数都是id。方法三不用types,但要增加全局变量。

Objective-C的runtime参考资料:


http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008048