美文网首页
iOS逆向实战--015:代码注入

iOS逆向实战--015:代码注入

作者: 帅驼驼 | 来源:发表于2021-04-25 18:42 被阅读0次

    一般修改原始程序,会利用代码注入的方式,注入代码就会选择利用FrameWorkdylib等三方库的方式注入。

    注入原理

    当运行重签名的App时,想让它触发当前项目中的代码,例如:写在ViewController的中代码,这是不可能的。因为项目中的App在安装时会被重签名App替换掉,它们根本不是一个MachO文件。

    代码注入原理:

    使用MachOView分析可执行文件

    wx8.0.2.ipa中导出WeChat可执行文件,拖入MachOView,可能会出现没有权限的错误提示

    解决方法,使用MachOView菜单中的File -> Open...

    选择WeChat可执行文件

    成功解析WeChat中的内容

    dyld加载可执行文件时,先读取MachO中的Header,获取MachO类型。然后读取Load Commands,通过读取__PAGEZERO__TEXT__DATA__LINKEDIT等段,可以得到MachO的大小,代码段和数据段的位置,告知dyld应该如何将MachO加载到内存中。当dyld读取代码段时,通过读取LC_MAIN找到程序入口。所以读取Load Commands几乎可以读取到MachO的所有信息

    dyld除了加载MachO,还要加载UIkitFoundation等系统库,以及Frameworks目录下的动态库

    Load Commands中,列出MachO所依赖的所有系统库以及三方库

    所以dyld会加载Load Commands中包含的所有库。如果将注入的代码包装成一个动态库,将其插入到Load Commands中,理论上动态库可以被加载,注入的代码也可以被执行

    Framework注入

    Framwork注入流程:

    • 通过Xcode新建Framwork,将库安装进入App
    • 通过yololib注入Framwork库路径
    ./yololib MachO文件路径 库路径
    
    • 所有的Framwork加载都是由dyld加载进入内存被执行的
    • 注入成功的库路径会写入到MachO文件的LC_LOAD_DYLIB字段中

    案例1:

    使用Framework注入代码

    打开自动重签名WeChat项目,创建Framework动态库

    命名HOOK

    动态库中创建Class,命名Inject

    打开Inject.m文件,写入以下代码:

    #import "Inject.h"
    
    @implementation Inject
    
    +(void)load{
       NSLog(@"\n\n\n\n\n🍺🍺🍺🍺🍺\n\n\n\n\n");
    }
    
    @end
    

    编译项目,在App包中的Frameworks目录,可以找到HOOK.framework动态库

    此时HOOK动态库中的代码还不能被执行,因为MachOLoad Commands中,还没有插入HOOK动态库的LC_LOAD_DYLIB字段

    使用yololibMachO注入动态库

    MachO文件,拷贝到yololib文件的同级目录

    注入动态库

    ./yololib WeChat Frameworks/HOOK.framework/HOOK
    -------------------------
    2021-04-22 17:14:52.339 yololib[22774:33058817] dylib path @executable_path/Frameworks/HOOK.framework/HOOK
    2021-04-22 17:14:52.340 yololib[22774:33058817] dylib path @executable_path/Frameworks/HOOK.framework/HOOK
    Reading binary: WeChat
    
    2021-04-22 17:14:52.341 yololib[22774:33058817] Thin 64bit binary!
    2021-04-22 17:14:52.341 yololib[22774:33058817] dylib size wow 72
    2021-04-22 17:14:52.341 yololib[22774:33058817] mach.ncmds 124
    2021-04-22 17:14:52.341 yololib[22774:33058817] mach.ncmds 125
    2021-04-22 17:14:52.341 yololib[22774:33058817] Patching mach_header..
    2021-04-22 17:14:52.399 yololib[22774:33058817] Attaching dylib..
    
    2021-04-22 17:14:52.399 yololib[22774:33058817] size 71
    2021-04-22 17:14:52.399 yololib[22774:33058817] complete!
    

    查看MachOLoad Commands,成功将HOOK动态库插入到最后,路径是注入时设置的参数2

    解压缩wx8.0.2.ipa,将MachO文件替换,然后重新打包ipa

    将重新打包的wx8.0.2.ipa,拷贝到项目中APP目录

    真机运行项目,App安装成功,正常运行,同时打印注入代码

    dylib注入

    dylib注入流程:

    • 通过Xcode新建dylib库(注意:dylib属于macOS,所以需要修改属性)
    • 添加Target依赖,让Xcode将自定义dylib文件打包进入App
    • 利用yololib进行注入

    案例1:

    使用dylib注入代码

    打开自动重签名WeChat项目,创建dylib动态库

    命名HOOK

    Build Settings中,将Base SDK设置项修改为iOS

    Code Signing Identity设置项修改为iOS Developer

    切换到AppTarget,选择Build Phases,点击+,选择New Copy Files Phase

    Copy FilesDestination中,选择Frameworks

    点击+,选择libHOOK.dylib

    打开HOOK.m文件,写入以下代码

    #import "HOOK.h"
    
    @implementation HOOK
    
    +(void)load{
      NSLog(@"\n\n\n\n\n🍺🍺 dylib 🍺🍺\n\n\n\n\n");
    }
    
    @end
    

    使用脚本注入动态库:将yololib文件,拷贝到项目根目录

    打开rsign.sh文件,加入以下代码:

    ./yololib "$TARGET_APP_PATH/$APP_BINARY" "Frameworks/libHOOK.dylib"
    

    真机运行项目,App安装成功,正常运行,同时打印注入代码

    Method Swizzle

    利用OCRuntime特性,动态改变SEL(方法编号)和IMP(方法实现)的对应关系,达到OC方法调用流程改变的目的。主要用于OC方法。

    OC中,SELIMP之间的关系,就好像一本书的“目录

    • SEL是方法编号,就像“标题”一样
    • IMP是方法实现的真实地址,就像“页码”一样
    • 它们是一一对应的关系

    Runtime提供了交换两个SELIMP对应关系的函数

    OBJC_EXPORT void
    method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2) 
       OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
    

    通过这个函数交换两个SELIMP对应关系的技术,我们称之为Method Swizzle(方法欺骗)

    多种HOOK方式

    • class_addMethod方式:让原始方法可以被调用,不至于因为找不到SEL而崩溃
    • class_replaceMethod方式:直接给原始的方法替换IMP
    • method_setImplementation方式:直接重新赋值新的IMP
    窃取登录密码

    案例1:

    点击登录按钮,输出用户输入的密码

    延用Framwork代码注入的案例

    打开rsign.sh文件,加入以下代码:

    ./yololib "$TARGET_APP_PATH/$APP_BINARY" "Frameworks/HOOK.framework/HOOK"
    

    真机运行项目,进入登录页,使用Debug View Hierarchy动态调试

    找到登录按钮的TargetAction

    • TargetWCAccountMainLoginViewController
    • ActiononNext

    想要打印密码框的内容,必须先找到密码框的位置,然后通过控件的属性拿到内容

    【方式一】:动态调试

    找到密码框的位置,找到存储内容的text属性

    • 可以通过响应链条,根据左侧树状图结构,对ViewController.view.subviews进行递归遍历,找到符合条件的WCUITextField为止

    使用动态调试,虽然也能找到密码框的位置,但寻找的过程太过繁琐,不推荐使用

    【方式二】:静态分析

    目前通过动态分析,可以确定登录按钮和密码框都在WCAccountMainLoginViewController

    使用class-dump工具,通过MachO导出OC中所有类和方法列表以及成员变量

    MachO文件,拷贝到class-dump文件的同级目录

    导出OC中所有类和方法列表

    ./class-dump -H WeChat -o ./headers/
    -------------------------
    2021-04-25 13:51:24.070 class-dump[31659:33427860] Warning: Parsing instance variable type failed, ready_
    2021-04-25 13:51:26.308 class-dump[31659:33427860] Warning: Parsing instance variable type failed, underlying
    2021-04-25 13:51:26.308 class-dump[31659:33427860] Warning: Parsing instance variable type failed, enable
    ...
    2021-04-25 13:52:09.288 class-dump[31659:33427860] Warning: Parsing method types failed, getKeyExtensionList:
    2021-04-25 13:52:09.292 class-dump[31659:33427860] Warning: Parsing method types failed, getKeyExtensionList:
    2021-04-25 13:52:09.292 class-dump[31659:33427860] Warning: Parsing method types failed, getExtensionListForSelector:
    

    打开headers目录,列出所有导出的文件列表,找到WCAccountMainLoginViewController文件

    打开WCAccountMainLoginViewController文件,没有找到WCUITextField控件,但发现一个WCAccountTextFieldItem类型控件,名称为_textFieldUserPwdItem

    打开WCAccountTextFieldItem文件,还是没有找到WCUITextField控件,但它继承自WCBaseTextFieldItem类,我们要找的控件很有可能在父类中

    打开WCBaseTextFieldItem文件,找到WCUITextField控件

    使用代码之前,可以先结合动态调试,验证能否顺利获取到用户密码

    通过动态调试,成功获取密码框里的内容

    使用代码,获取用户密码

    打开Inject文件,写入以下代码:

    #import "Inject.h"
    #import <objc/runtime.h>
    #import <UIKit/UIKit.h>
    
    @implementation Inject
    
    +(void)load{
       Method wx_onNext = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext));
       Method my_onNext = class_getInstanceMethod(self, @selector(hook_onNext));
       method_exchangeImplementations(wx_onNext, my_onNext);
    }
    
    -(void)hook_onNext{
       UITextField *txtPwd = (UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"];
       NSLog(@"密码:%@", txtPwd.text);
    }
    
    @end
    

    真机运行项目,点击登录按钮

    密码:123456
    

    成功输出用户密码

    案例2:

    窃取密码后,希望可以调用原始的代码逻辑

    日常开发中,方法交互一般写在当前类的分类中。由于交互后hook_onNext指向了onNextIMP,所以代码中直接调用hook_onNext即可

    打开Inject文件,修改hook_onNext方法:

    -(void)hook_onNext{
       UITextField *txtPwd = (UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"];
       NSLog(@"密码:%@", txtPwd.text);
       
       [self hook_onNext];
    }
    

    但是,HOOK动态库中的Inject文件,和WCAccountMainLoginViewController类没有任何关联。虽然hook_onNext指向onNextIMP,但是调用objc_msgSend函数时,给VC发送hook_onNext,程序会因为找不到SEL而崩溃

    下面介绍三种方式,都可以解决上述问题

    【方式一】:class_addMethod

    既然VC中没有hook_onNext方法,可以使用class_addMethod函数给VC添加一个hook_onNext,调用时不会因为找不到SEL而崩溃

    打开Inject文件,写入以下代码:

    #import "Inject.h"
    #import <objc/runtime.h>
    #import <UIKit/UIKit.h>
    
    @implementation Inject
    
    +(void)load{
       Method wx_onNext = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext));
       
       class_addMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(hook_onNext), hook_onNext, @"v@:");
       Method my_onNext = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(hook_onNext));
       
       method_exchangeImplementations(wx_onNext, my_onNext);
    }
    
    void hook_onNext(id self, SEL _cmd){
       UITextField *txtPwd = (UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"];
       NSLog(@"密码:%@", txtPwd.text);
       
       [self performSelector:@selector(hook_onNext)];
    }
    
    @end
    
    • 之前的hook_onNext方法,改为函数实现,增加OC方法的两个隐式参数
    • 使用class_addMethod函数,给VC增加hook_onNext方法,函数名即是IMP
    • VC下的hook_onNextonNext进行方法交互
    • hook_onNext函数中,如果直接调用函数,调用的不是替换后的IMP,而是hook_onNextIMP,这样会形成递归。所以想调用原始方法,要使用objc_msgSendperformSelector方式调用

    真机运行项目,点击登录按钮,窃取密码后,调用原始的代码逻辑

    密码:123456
    

    【方式二】:class_replaceMethod

    VConNext方法的IMP替换

    打开Inject文件,写入以下代码:

    #import "Inject.h"
    #import <objc/runtime.h>
    #import <UIKit/UIKit.h>
    
    @implementation Inject
    
    IMP (*oldImp)(id self, SEL _cmd);
    
    +(void)load{
       oldImp = class_replaceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext), hook_onNext, @"v@:");
    }
    
    void hook_onNext(id self, SEL _cmd){
       UITextField *txtPwd = (UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"];
       NSLog(@"密码:%@", txtPwd.text);
       
       oldImp(self, _cmd);
    }
    
    @end
    
    • 声明IMP类型的oldImp函数指针
    • 使用class_replaceMethod函数,直接将onNextIMP替换为hook_onNextIMP,将返回的原始IMP赋值给oldImp
    • hook_onNext函数中,想调用原始方法,直接调用oldImp并传入隐式参数即可

    【方式三】:method_setImplementation

    获取原始的IMP,重新赋值新IMP

    打开Inject文件,写入以下代码:

    #import "Inject.h"
    #import <objc/runtime.h>
    #import <UIKit/UIKit.h>
    
    @implementation Inject
    
    IMP (*oldImp)(id self, SEL _cmd);
    
    +(void)load{
       Method wx_onNext = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext));
       oldImp = method_getImplementation(wx_onNext);
       
       method_setImplementation(wx_onNext, hook_onNext);
    }
    
    void hook_onNext(id self, SEL _cmd){
       UITextField *txtPwd = (UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"];
       NSLog(@"密码:%@", txtPwd.text);
       
       oldImp(self, _cmd);
    }
    
    @end
    
    • 使用method_getImplementation函数,获取onNextIMP,赋值给oldImp
    • 使用method_setImplementation函数,直接赋值hook_onNextIMP
    总结

    注入原理:

    • Xcode将注入的动态库打包进App包中
    • MachOLoad Commands中,包含注入动态库的LC_LOAD_DYLIB字段
    • DYLD加载注入的动态库

    注入方式:

    • Framework注入
    • dylib注入

    Framwork注入流程:

    • 通过Xcode新建Framwork,将库安装进入App
    • 通过yololib注入Framwork库路径

    dylib注入流程:

    • 通过Xcode新建dylib库(注意:dylib属于macOS,所以需要修改属性)
    • 添加Target依赖,让Xcode将自定义dylib文件打包进入App
    • 利用yololib进行注入

    案例,窃取登录密码:

    • 分析思路
    • 动态调试,界面入手
    • 静态分析,使用class-dump,通过MachO导出OC的类和方法列表

    Method Swizzle

    • 使用method_exchangeImplementations交互SELIMP对应关系,此方式存在隐患,如果不在同一个类,无法调用原始方法,因为找不到SEL而崩溃
    • 使用class_addMethod添加方法,不至于因为找不到SEL而崩溃。过程比较复杂,不推荐使用
    • 使用class_replaceMethod替换SELIMP
    • 使用method_getImplementationmethod_setImplementation配合,直接重新赋值新的IMP。逻辑清晰,大部分HOOK框架使用此方式,推荐使用

    相关文章

      网友评论

          本文标题:iOS逆向实战--015:代码注入

          本文链接:https://www.haomeiwen.com/subject/heaxrltx.html