iOS-应用安全05 代码注入之运用

321 阅读4分钟

MethodSwizzle

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

小试牛刀-Hook微信注册方法

1.先Run已经被重签名的微信程序,然后使用可视化画板查看视图层级

通过lldb调试获取当前按钮操作对象 我们拿到了targetWCAccountLoginControlLogic,至于方法actiononFirstViewRegister

2.用终端运行工具class-dump获取微信的头文件集合放在相同目录中headers文件夹中

./class-dump -H Wechat -o ./headers/

3.在headers文件夹中找打1中WCAccountLoginControlLogic类,建议使用sublime查找
猜测下,点击方法--- (void)onFirstViewRegister;为什么要猜,逆向不就是一个个去尝试吗?只是我这里开了上帝视角而已。可以看到这是个实例不带参数的方法;

4.hook 点击注册的动作

5.run,再次点击,发现不再响应注册方法,并且有打印,说明hook成功

Hook 微信密码调试分析

既然能hook注册按钮,那试试是否能获取登录密码

分析

1.还是运行进行可视化调试

1.1获取点击动作

在上面已经解包的headers文件夹中查找WCAccountMainLoginViewController以及动作onext
这个onext应该就是点击登录的事件,并且我们能看到两个控件item试着找下他们的父类

WCAccountTextFieldItem *_textFieldUserNameItem;
WCAccountTextFieldItem *_textFieldUserPwdItem;

1.2 先看下目标控件WCUITextField

有根据代码看到的两个控件item试着找下他们的父类

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

总结: