前端时间看了一些逆向工程,扫了一眼'小黄书',不得不感叹runtime的强大之处。在不越狱情况下注入一些dylib来hook定位,步数等系统函数后,发现用到的工具都是高度封装好的,就回头写了一个runtime的小例子;
①动态添加属性
建立公开类的Category,此处以UIWebView
为例子;
引如objc/runtime.h
,声明属性:
#import "UIWebView+Swizzling.h"
#import <objc/runtime.h>
@interface UIWebView ()
@property(nonatomic,copy)NSString* associatedProp;
@end
用runtime
的objc_setAssociatedObject
实现setter
,objc_getAssociatedObject
实现getter
:
-(void)setAssociatedProp:(NSString *)associatedProp{
objc_setAssociatedObject(self, &associatedPropKey, associatedProp, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(NSString*)associatedProp{
return objc_getAssociatedObject(self, &associatedPropKey);
}
使用:
[self.webView setValue:@"associatedPropSuccess" forKey:@"associatedProp"];
NSLog(@"\nassociatedProp:%@",[self.webView valueForKey:@"associatedProp"]);
打印:
②hook公开类的public||private方法
建立公开类的Category,此处以UIWebView
的loadRequest:
和webView:didFinishLoadForFrame:
为例子;
使用class-dump
可以查看很多私有方法;
在+(void)load
方法中如下(为什么在load
中?可以想一下):
@implementation UIWebView (Swizzling)
+ (void)load
{
//hook公开类的public方法
Method loadRequest = class_getInstanceMethod([self class], NSSelectorFromString(@"loadRequest:"));
Method swizzling_loadRequest = class_getInstanceMethod([self class], @selector(swizzling_loadRequest:));
method_exchangeImplementations(loadRequest, swizzling_loadRequest);
//hook公开类&private方法
Method didFinishLoadForFrame = class_getInstanceMethod([self class], NSSelectorFromString(@"webView:didFinishLoadForFrame:"));
Method swizzling_didFinishLoadForFrame = class_getInstanceMethod([self class], @selector(swizzling_webView:didFinishLoadForFrame:));
method_exchangeImplementations(didFinishLoadForFrame, swizzling_didFinishLoadForFrame);
}
可以看到这个方法获取了UIWebView
中两个方法的入口imp并且交换了,效果从A->a B->b
变成了A->b B->a
;
下面只需要实现我们自己的方法:
- (void)swizzling_loadRequest:(id)request;
{
NSLog(@"\nswizzling_loadRequest\narg1:%@\n",request);
[self swizzling_loadRequest:request];
}
-(void)swizzling_webView:(id)webView didFinishLoadForFrame:(id)frame{
NSLog(@"\nswizzling_didFinishLoadForFrame\narg1:%@\narg2:%@\n",webView,frame);
[self swizzling_webView:webView didFinishLoadForFrame:frame];
}
由于imp互换,当UIWebView
调用loadRequest
时会进入我们的swizzling_loadRequest:(id)request
,而我们调用时候也是如此,不会产生递归;
打印:
③hook未知类的方法,如代理
如果我们想要hook一个代理方法,甚至一个未知viewController
的viewDidLoad
,又要怎么做呢?
这里我们以UIWebView
的webViewDidStartLoad:
举例;
(webView:😅😅😅)
步骤如上,不过要先考虑一下实现代理方法前提是一定遵循了代理,那我们可以从setDelegate:
入手:
+ (void)load{
Method setDegelagte = class_getInstanceMethod([self class], NSSelectorFromString(@"setDelegate:"));
Method swizzling_setDelegate = class_getInstanceMethod([self class], @selector(swizzling_setDelegate:));
method_exchangeImplementations(setDegelagte, swizzling_setDelegate);
}
const char *type = "v@:@@";
//v==void,@==object,:=selector 详查Type Encoding
-(void)swizzling_setDelegate:(id)delegate{
[self swizzling_setDelegate:delegate];
Class vc = [delegate class];
NSLog(@"\nswizzling_delegateClass\nclass:%@\n",vc);
if ([vc isSubclassOfClass:[UIViewController class]]) {
//不同于load方法,设置单例保证交换1次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//生命周期不同 所以先加入目标类中 再在目标类中进行exchange
class_addMethod(vc, @selector(swizzling_webViewDidStartLoad:), class_getMethodImplementation([self class], @selector(swizzling_webViewDidStartLoad:)), type);
Method webViewDidStartLoad = class_getInstanceMethod(vc, NSSelectorFromString(@"webViewDidStartLoad:"));
Method swizzling_webViewDidStartLoad = class_getInstanceMethod(vc, @selector(swizzling_webViewDidStartLoad:));
method_exchangeImplementations(webViewDidStartLoad, swizzling_webViewDidStartLoad);
NSLog(@"addMethodOK?:%d",class_addMethod(vc, @selector(addMethod), class_getMethodImplementation([self class], @selector(addMethod)), type));;
});
}
}
我们首先让方法顺利执行,然后就可以获取到delegate的class,但是此时机不唯一,所以要在单例中进行exchange;
由于exchange方法的类不是UIWebView
,所以要先把方法add到delegate class
,然后再进行exchange;
同时在目标类中加入了一个方法;
接下来实现:
-(void)swizzling_webViewDidStartLoad:(id)webView{
NSLog(@"\nswizzling_webViewDidStartLoad\n");
//执行添加的方法 此处已进入目标类 直接执行self addMethod
[self addMethod];
[self swizzling_webViewDidStartLoad:webView];
}
-(void)addMethod{
NSLog(@"addMethodRunSuccess");
[self performSelector:NSSelectorFromString(@"back:") withObject:nil afterDelay:1];
}
再看看打印:
至此我们已经利用
UIWebView
的delegate
成功获取到目标class并hook代理方法,添加了自定义方法并调用;
小结
可以猜测逆向工程正是利用runtime
,先编译Category到dylib,再把动态的dylib压到二进制文件读取列表中,再进行重签名,在app运行时会加载我们的Category,从而完成hook等操作;
runtime
和Category了解的还是不够多,有时间真的要把runtime
中的api研究一下。。。
一点小的见解,还望各位多多交流和教导。