MethodSwizzle
在上一章04我们知道运用了脚本注入我们自己的代码,这一章介绍下具体调试代码怎么写;首先我们得充分运用OC运行时的特性:Runtime - MethodSwizzle - 即Hook源代码。 MethodSwizzle原理

小试牛刀-Hook微信注册方法
1.先Run已经被重签名的微信程序,然后使用可视化画板查看视图层级

WCAccountLoginControlLogic,至于方法action 是onFirstViewRegister
2.用终端运行工具class-dump获取微信的头文件集合放在相同目录中headers文件夹中
./class-dump -H Wechat -o ./headers/


- (void)onFirstViewRegister;为什么要猜,逆向不就是一个个去尝试吗?只是我这里开了上帝视角而已。可以看到这是个实例不带参数的方法;
4.hook 点击注册的动作


Hook 微信密码调试分析
既然能hook注册按钮,那试试是否能获取登录密码
分析
1.还是运行进行可视化调试
1.1获取点击动作

WCAccountMainLoginViewController以及动作onext

onext应该就是点击登录的事件,并且我们能看到两个控件item试着找下他们的父类
WCAccountTextFieldItem *_textFieldUserNameItem;
WCAccountTextFieldItem *_textFieldUserPwdItem;
1.2
先看下目标控件WCUITextField

WCAccountTextFieldItem *_textFieldUserNameItem;
WCAccountTextFieldItem *_textFieldUserPwdItem;
用sublime查看继承关系找到目标控件WCUITextField
@interface WCAccountTextFieldItem : WCBaseTextFieldItem
{
_Bool m_bUseGrayColor;
UILabel *m_labelTip;
}
########################
@interface WCBaseTextFieldItem : WCBaseInfoItem <UITextFieldDelegate>
{
WCUITextField *m_textField;
int m_iMaxInputLen;
_Bool m_bRealLen;
_Bool m_bTextFieldHasBecomeFirstResponder;
}
在 WCBaseTextFieldItem 中找到了目标控件 WCUITextField *m_textField; 2.lldb验证
(lldb) po 0x2819a7380
{
className = WCAccountMainLoginViewController;
memoryAddress = 0x10a80bc00;
}
//kvc方式取到对象
(lldb) po [(WCAccountMainLoginViewController *)0x10a80bc00 valueForKey:@"_textFieldUserPwdItem"]
<WCAccountTextFieldItem: 0x2821e8cc0>
(lldb) po [(WCAccountTextFieldItem *)0x2821e8cc0 valueForKey:@"m_textField"]
<WCUITextField: 0x10a86be00; baseClass = UITextField; frame = (20 0; 290 44); text = '12345678'; opaque = NO; autoresize = W+H; tintColor = UIExtendedSRGBColorSpace 0.00784314 0.733333 0 1; gestureRecognizers = <NSArray: 0x2809d4900>; layer = <CALayer: 0x280411ae0>>
(lldb)
上面text = '12345678'不就是刚才输入的密码吗;
执行注入
直接上代码
#import "InjectCode.h"
#import <objc/runtime.h>
#import <UIKit/UIKit.h>
@implementation InjectCode
+ (void)load {
NSLog(@"*****************\n\n\n\nInjectCode\n\n\n\n*****************");
Method oldMethod = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext));
Method newMehtod = class_getInstanceMethod(self, @selector(rf_onNext));
method_exchangeImplementations(oldMethod, newMehtod);
}
- (void)rf_onNext {
//拿出用户密码
UITextField *textfd = [[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"];//这里的self是WCAccountMainLoginViewController,又因为WCUITextField继承UITextField,所以这里用UITextField去接收
NSLog(@"*********截取到密码是:%@",textfd.text);
//继续响应登录操作
[self rf_onNext];
}
@end
run,查看打印,密码获取成功

通过添加方法解决崩溃
细心的同学应该会发现上面的截图是有崩溃的,在rf_onNext这个方法中,因为WCAccountMainLoginViewController并没有rf_onNext这个方法,所以需要手动添加,在WCAccountMainLoginViewController内部让它交换;直接上代码
#import "InjectCode.h"
#import <objc/runtime.h>
#import <UIKit/UIKit.h>
@implementation InjectCode
+ (void)load {
NSLog(@"*****************\n\n\n\nInjectCode\n\n\n\n*****************");
//获取原始方法
Method onNext_old = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext));
/**
添加新方法:给WCAccountMainLoginViewController加了一个rf_onNext方法
v@:方法编号 -- v:void @:第一个参数id类型 ::第二个参数是SEL类型
1、给哪个类添加方法
2、方法i编号
3、方法的实现地址
*/
BOOL didAddSuccess = class_addMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext_new),onNext_new, "v@:");
// 交换
method_exchangeImplementations(onNext_old, class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext_new)));
}
//方法实现imp
void onNext_new(id self,SEL _cmd) {
//拿出用户密码
UITextField *textfd = [[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"];//这里的self是WCAccountMainLoginViewController,又因为WCUITextField继承UITextField,所以这里用UITextField去接收
NSLog(@"*********截取到密码是:%@",textfd.text);
//继续响应登录操作
[self performSelector:@selector(onNext_new)];
}
@end
当然还有更简单的方式:替换方法
#import "InjectCode.h"
#import <objc/runtime.h>
#import <UIKit/UIKit.h>
@implementation InjectCode
+ (void)load {
NSLog(@"*****************\n\n\n\nInjectCode\n\n\n\n*****************");
//替换原始微信的登录方法
//先获取method
Method onNext_oldmethod = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext));
//转成imp
old_imp = method_getImplementation(onNext_oldmethod);
//替换
class_replaceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext), onNext_new, "v@:");
}
//保存oldimp
IMP (*old_imp)(id self,SEL _cmd);
//方法实现imp
void onNext_new(id self,SEL _cmd) {
//拿出用户密码
UITextField *textfd = [[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"];//这里的self是WCAccountMainLoginViewController,又因为WCUITextField继承UITextField,所以这里用UITextField去接收
NSLog(@"*********截取到密码是:%@",textfd.text);
//继续响应登录操作
old_imp(self,_cmd);
}
@end
更好的getIMP/setIMP
直接上代码
#import "InjectCode.h"
#import <objc/runtime.h>
#import <UIKit/UIKit.h>
@implementation InjectCode
+ (void)load {
NSLog(@"*****************\n\n\n\nInjectCode\n\n\n\n*****************");
//getIMP
old_imp = method_getImplementation(class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext)));
//setIMP
method_setImplementation(class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext)), onNext_new);
}
//保存oldimp
IMP (*old_imp)(id self,SEL _cmd);//就是一个实现指针
//方法实现imp
void onNext_new(id self,SEL _cmd) {
//拿出用户密码
UITextField *textfd = [[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"];//这里的self是WCAccountMainLoginViewController,又因为WCUITextField继承UITextField,所以这里用UITextField去接收
NSLog(@"*********截取到密码是:%@",textfd.text);
//继续响应登录操作
old_imp(self,_cmd);
}
@end
总结:
