代码注入

代码注入

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

使用应用重签名创建的工程WeChat,接下来我们就来破解微信8.0.2.ipa包,让微信8.0.2.ipa包能够执行我们写的一些代码?如果我们把代码写在WeChat工程中,微信8.0.2.ipa包能够执行我们的代码吗?

  • WeChat工程中ViewController写入的代码不会执行,是因为整个MachO文件都被微信8.0.2.ipa替换了
查看Mach-O文件信息

Products -> WeChat.app -> Show in Finder -> 显示包内容,这个时候包里面已经是微信8.0.2.ipa包了,把包里面的可执行文件WeChat复制出来

  • 使用终端命令查看(有些信息查看不是很清楚)
$ otool -l WeChat
  • 使用MachOView工具查看,MachOView将可执行文件分析一遍可视化查看
  1. 直接将可执行文件拖入MachOView查看,可能会遇到不被信任的情况
  2. Cmd + o选择文件打开,这样的话就不会遇到要求信任的情况
分析代码注入原理

我们的可执行文件是dyld加载的,dyld去加载所有的可执行文件,也包括动态库,在去加载可执行文件的时候会先去查看Mach Header。微信除了要执行自己的代码之外,还需要执行Framework里面的代码。那么微信是怎么告诉dyld去加载Framework里面的代码呢?

  • 微信的MachO文件中load Commands(加载命令集)中会有LC_LOAD_DYLIB (andromeda)字段,从这里可以看出动态库的加载等。只要有这个字段,dyld就会去指定的路径下加载依赖的动态库,如果我们要注入代码,就需要创建一个动态库,让微信的load Commands中包含我们的库,这样的话微信就会去加载我们的动态库
微信MachO文件

综上所述,注入代码需要以动态库的形式注入

一. Framework注入

  • 创建一个Framework名称为 WNHook,创建步骤 target -> + -> ios ->Framework
  • WNHook动态库中创建inject文件,在生命周期方法load方法中写注入代码
// Inject.m文件内容
#import "Inject.h"
@implementation Inject
+(void)load{
    NSLog(@"注入成功!!");
}
@end
  • 编译工程,查看WeChat.app包内容,Products -> Show in Finder -> 显示包内容 -> Frameworks 里面可以看到WNHook.framework库,但是此时运行工程并不会执行load方法,原因是此时的MachO文件中Load Commands并没有发现WNHook,所以不会执行其中的load函数
yololib手动注入,修改MachO文件的Load Commands
  • 通过yololib工具修改MachO字段(需要将WeChat可执行文件拷贝过来)
// 第一个参数:目标可执行文件
// 第二个参数:WNHook路径
$ ./yololib WeChat Frameworks/WNHook.framework/WNHook

此时查看MachO文件中Load Commands发现了WNHook

  • 重新打包,过程如下:
  1. 解压微信8.0.2.ipa
  2. 替换Payload中的WeChat可执行文件为上一步修改过的的MachO文件
  3. 重新对Payload进行打包,打包成WeChat.ipa
$ zip -ry WeChat.ipa Payload/
  1. 然后在自己创建的WeChat工程APP目录下替换ipa包
  • 运行程序,可以看到成功执行了 WNHook中的load
成功注入

二. Dylib注入

创建空工程Dylib,运行安装至手机(安装的目的是想安装描述文件),对WeChat进行重签名,参考应用重签名中的脚本签名,把应用重签名到我们的手机

  • target -> + -> mac os -> Library,命名为WNHook
  • 在Build Setting中配置WNHook
  1. Base SDK 改为 ios
  2. Code Signing identify 改为 iOS Developer
  • 当前工程中拷贝lib
  1. Build Phase -> + -> New Copy Files Phase
  2. 然后选择拷贝到的目标文件:Destination 选择 Frameworks
  3. 点击 + 号添加 libWNHook.dylib
  • WNHook.m文件中重写load函数
#import "WNHook.h"

@implementation WNHook
+(void)load{
    NSLog(@"注入成功");
}
@end
  • 脚本注入dylib,将yololib拷贝至根目录,在appSign.sh末尾添加以下命令
#注入
./yololib "$TARGET_APP_PATH/$APP_BINARY" "Frameworks/libWNHook.dylib"
  • 编译工程,查看MachO文件中的Frameworks文件,已经包含了WNHookdylib
  • 运行程序,从日志可以看出执行了WNHook中的load
总结
Framwork注入
  • 通过Xcode新建Framwork,将库安装进入APP包
  • 通过yololib注入Framwork库路径。命令:$yololib(空格)MachO文件路径(空格)库路径
  1. 所有的Framwork加载都是由DYLD加载进入内存被执行的
  2. 注入成功的库路径会写入到MachO文件的LC_LOAD_DYLIB字段中
Dylib注入
  • 通过Xcode新建Dylib库(注意:Dylib属于MacOS所以需要修改属性)
  • 添加Target依赖,让Xcode将自定义Dylib文件打包进入APP包。
  • 利用yololib进行注入。

四. 破坏微信注册

Method Swizzing

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

在OC中,SEL 和 IMP 之间的关系,就好像一本书的“目录”。他们是一一对应的关系

  • SEL 是方法编号,就像“标题”一样。
  • IMP是方法实现的真实地址,就像“页码”一样。

Runtime提供了交换两个SEL和IMP对应关系的函数method_exchangeIMP
通过这个函数交换两个SEL和IMP对应关系的技术,我们称之为Method Swizzle(方法欺骗)

Method Swizzing练习

新建一个空工程破坏WeChat,进行重签名,采用Framework注入的方式

请思考? 如果我们想改变微信做一些事情,那么我们的代码就要改变微信的执行流程?
我们可以使用 runtime修改微信的执行流程,下面进行探讨

修改微信的注册
  • 通过lldb调试获取WeChat的注册,以获取注册的target、action
lldb调试微信注册
  • 通过class_getInstanceMethod、method_exchangeImplementations方法,hook注册的点击事件
#import "inject.h"
#import <objc/runtime.h>
@implementation inject

+ (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(@" 注册不了!");
}
@end
  • 重新运行,点击注册按钮,执行的是我们自己的方法Hook成功

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

点击登录时,获取用户的密码
  • 微信输入账号、密码,点击Debug View Hierarchy动态调试登录界面,获取登录按钮信息
获取登录按钮信息
  • 获取输入密码框信息
获取密码输入框信息
// lldb打印内存地址获取密码
(lldb) po [(WCUITextField *)0x10a1566e0 text]
123456

登录的时候,只要能拿到TextField就能拿到密码,那么怎么能拿到这个textField输入框呢?

  • 方案一 通过响应链,一层层查找,缺点很麻烦
响应链查找
  • 方案二 静态分析
  1. Hook登录按钮的点击事件
#import "inject.h"
#import <objc/runtime.h>
@implementation inject

+ (void)load{
    // 改变微信的登录
    Method oldMethod = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext));
    Method newMethod = class_getInstanceMethod(self, @selector(my_onNext));
    method_exchangeImplementations(oldMethod, newMethod);
}

- (void)my_onNext{
    
}
@end
  1. 通过class-dump获取OC类、方法的列表以及成员变量,其本质也是从MachO文件读出来

将class-dump、WeChat可执行文件拷贝至同一个文件夹

// dump出WeChat可执行文件中OC中类列表
$ ./class-dump -H WeChat -o ./headers/
WeChat类列表

通过sublime打开headers文件夹,在其中查找WCAccountMainLoginViewController类,找到密码的成员变量_textFieldUserPwdItem

WCAccountMainLoginViewController类

查找类文件顺序 WCAccountTextFieldItem -> WCBaseTextFieldItem -> WCUITextField

找到WCUITextField

通过获取的成员变量,利用lldb动态调试获取密码

成功获取登录密码

六. Cycript

登录按钮 - hook代码注入方式获取
  • 修改my_onNext方法获取登陆密码
#import "inject.h"
#import <objc/runtime.h>
#import <UIKit/UIKit.h>
@implementation inject

+ (void)load{
    //    改变微信的登录
    Method oldMethod = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext));
    Method newMethod = class_getInstanceMethod(self, @selector(my_onNext));
    method_exchangeImplementations(oldMethod, newMethod);
}

- (void)my_onNext{
    UITextField *pwd = (UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"];
    NSLog(@"密码是:%@", pwd.text);
}
@end
  • 此时需要在my_onNext中调用原来的方法,走回原来的登录流程,修改如下
- (void)my_onNext{
    UITextField *pwd = (UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"];
    NSLog(@"密码是:%@", pwd.text);
    //调回原来的方法
    [self my_onNext];
}
  • [self my_onNext];处加断点,查看此时的my_onNext中的self、_cmd
查看self和_cmd
  • 继续执行程序崩溃,执行objc_msgSend后会直接崩溃,原因是WCAccountMainLoginViewController找不到my_onNext
程序崩溃
解决崩溃的方案:给WCAccountMainLoginViewController添加一个method
  • 第一种解决方案 通过class_addMethodWCAccountMainLoginViewController新增一个方法,再和原来的onNext交换新增后的方法
#import "inject.h"
#import <objc/runtime.h>
#import <UIKit/UIKit.h>
@implementation inject

+ (void)load{
    //原始的method
    Method onNext = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext));
    //给WCAccountMainLoginViewController添加新方法
    class_addMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(new_onNext), new_onNext, "v@:");
    //获取添加后的方法
    Method newMethod = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(new_onNext));
    //交换
    method_exchangeImplementations(onNext, newMethod);
}

//新的IMP
void new_onNext(id self, SEL _cmd){
    UITextField *pwd = (UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"];
    NSLog(@"密码是:%@", pwd.text);    //调回原来的方法
    [self performSelector:@selector(new_onNext)];
}
@end

重新运行后可以走到原来的登录流程,也可以获取用户密码

  • 第二种解决方案 通过替换的方式
#import "inject.h"
#import <objc/runtime.h>
#import <UIKit/UIKit.h>
@implementation inject

+(void)load{
    //原始的Method
    Method onNext = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext));
    old_onNext = class_replaceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext), new_onNext, "v@:");
}
//原来的IMP
IMP (*old_onNext)(id self,SEL _cmd);
//新的IMP
void new_onNext(id self,SEL _cmd){
    UITextField * pwd = (UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"];
    NSLog(@"密码是:%@",pwd.text);
    //调用回原来的逻辑!!
    //调用原来的方法!
    old_onNext(self,_cmd);
    //objc_msgSend();
}
@end
  • 第三种解决方案 最优的方案
#import "inject.h"
#import <objc/runtime.h>
#import <UIKit/UIKit.h>

+ (void)load{
    // setImp 和 getImp的方式是最安全的
    //原始的method
    old_onNext = method_getImplementation(class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext)));
    //通过set覆盖原始的IMP
    method_setImplementation(class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext)), new_onNext);
}

//原来的IMP
IMP (*old_onNext)(id self, SEL _cmd);
//新的IMP
void new_onNext(id self, SEL _cmd){
    UITextField *pwd = (UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"];
    NSLog(@"密码是:%@", pwd.text);
    
    //调回原来的方法objc_msgSend(WCAccountMainLoginViewController,onNext的IMP)
    old_onNext(self, _cmd);
}
@end
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 一般修改原始程序,会利用代码注入的方式,注入代码就会选择利用FrameWork或dylib等三方库的方式注入。 注...
    帅驼驼阅读 5,126评论 0 8
  • 《iOS底层原理文章汇总》[https://www.jianshu.com/p/15af435341ce] 1.运...
    一亩三分甜阅读 3,222评论 0 0
  • 在上一篇文章中,在上一篇文章中,我们讲了iOS应用重签名技术,同时我们也把微信的越狱ipa包安装到了我们的手机上了...
    蚂蚁也疯狂阅读 2,885评论 0 0
  • 前言 上篇文章10-应用重签名[https://www.jianshu.com/p/cec9ab89e4fe],我...
    深圳_你要的昵称阅读 3,251评论 0 2
  • 表情是什么,我认为表情就是表现出来的情绪。表情可以传达很多信息。高兴了当然就笑了,难过就哭了。两者是相互影响密不可...
    Persistenc_6aea阅读 128,030评论 2 7

友情链接更多精彩内容