利用method Swizzling黑魔法可以轻松的 hook 系统的已知类的方法, 但是对于系统的delegate方法, 其实际调用类存在多种, 如何针对未知调用类的 hook 呢?
我们结合webView进行探讨, 并且假定调用webView的类已经实现了其所有的代理方法, 在不改变原类调用的请求下, 如何 hook 它呢?
首先,如果我们设置webView的代理方法, 一定会调用:
webView.delegate = xxx;
这样一定会调用webView的setDelegate方法, 因此我们想到可不可以hook webView的setDelegate拿到实际调用其代理的类, 然后对其进行方法交换.
- (void)hook_setDelegate:(id<UIWebViewDelegate>)delegate{
[self hook_setDelegate:delegate];
// 获得delegate的实际调用类
Class aClass = [delegate class];
// 传递给HookWebViewDelegateMonitor来交互方法
[HookWebViewDelegateMonitor exchangeUIWebViewDelegateMethod:aClass];
}
// 在load中交换系统的setDelegate 和我们要hook的代理方法
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method originalMethod = class_getInstanceMethod([UIWebView class], @selector(setDelegate:));
Method swizzledMethod = class_getInstanceMethod([UIWebView class], @selector(hook_setDelegate:));
method_exchangeImplementations(originalMethod, swizzledMethod);
});
}
首先我们新建一个类(HookWebViewDelegateMonitor), 用于交互方法并实现:
#import "HookWebViewDelegateMonitor.h"
#import <objc/runtime.h>
#import <objc/message.h>
static void hook_exchangeMethod(Class originalClass, SEL originalSel, Class replacedClass, SEL replacedSel){
Method originalMethod = class_getInstanceMethod(originalClass, originalSel);
Method replacedMethod = class_getInstanceMethod(replacedClass, replacedSel);
IMP replacedMethodIMP = method_getImplementation(replacedMethod);
// 将样替换的方法往代理类中添加, (一般都能添加成功, 因为代理类中不会有我们自定义的函数)
BOOL didAddMethod =
class_addMethod(originalClass,
replacedSel,
replacedMethodIMP,
method_getTypeEncoding(replacedMethod));
if (didAddMethod) {// 添加成功
NSLog(@"class_addMethod succeed --> (%@)", NSStringFromSelector(replacedSel));
// 获取新方法在代理类中的地址
Method newMethod = class_getInstanceMethod(originalClass, replacedSel);
// 交互原方法和自定义方法
method_exchangeImplementations(originalMethod, newMethod);
}else{// 如果失败, 则证明自定义方法在代理方法中, 直接交互就可以
method_exchangeImplementations(originalMethod, replacedMethod);
}
}
@implementation HookWebViewDelegateMonitor
+ (void)exchangeUIWebViewDelegateMethod:(Class)aClass{
hook_exchangeMethod(aClass, @selector(webViewDidStartLoad:), [self class], @selector(replaced_webViewDidStartLoad:));
hook_exchangeMethod(aClass, @selector(webViewDidFinishLoad:), [self class], @selector(replaced_webViewDidFinishLoad:));
hook_exchangeMethod(aClass, @selector(webView:didFailLoadWithError:), [self class], @selector(replaced_webView:didFailLoadWithError:));
hook_exchangeMethod(aClass, @selector(webView:shouldStartLoadWithRequest:navigationType:), [self class], @selector(replaced_webView:shouldStartLoadWithRequest:navigationType:));
}
- (BOOL)replaced_webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
NSLog(@"-------replaced_webView-shouldStartLoadWithRequest---------");
return [self replaced_webView:webView shouldStartLoadWithRequest:request navigationType:navigationType];
}
- (void)replaced_webViewDidStartLoad:(UIWebView *)webView
{
NSLog(@"-------replaced_webViewDidStartLoad--------");
[self replaced_webViewDidStartLoad:webView];
}
- (void)replaced_webViewDidFinishLoad:(UIWebView *)webView
{
NSLog(@"-------replaced_webViewDidFinishLoad--------");
[self replaced_webViewDidFinishLoad:webView];
}
- (void)replaced_webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error{
NSLog(@"--------replaced_webView-didFailLoadWithError--------");
[self replaced_webView:webView didFailLoadWithError:error];
}
@end
当我们在ViewController中实现了WebView的调用打印日志如下:
这个方法还有一个弊端就是只能hook 原类中已经调用了的方法 , 如果原类中没有实现shouldStartLoadWithRequest这个方法的话, replace_shouldStartLoadWithRequest这个方法也不会调用.
Demo 地址如下
Demo
本人还在进一步的探索中, 如有问题, 欢迎指正, 非常感谢. 如果大佬们有更好的hook方案, 也希望可以告知小弟.
参考资料:
通过Method Swizzling实现Hook中的深坑
小白笔记(“动态”hook某class的delegate方法”)
Objective-C的hook方案(一): Method Swizzling
利用Objective-C运行时hook函数的三种方法