代码注入
一般修改原始的程序,是利用代码注入的方式,注入代码就会选择利用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将可执行文件分析一遍可视化查看
- 直接将可执行文件拖入
MachOView查看,可能会遇到不被信任的情况 -
Cmd + o选择文件打开,这样的话就不会遇到要求信任的情况
分析代码注入原理
我们的可执行文件是dyld加载的,dyld去加载所有的可执行文件,也包括动态库,在去加载可执行文件的时候会先去查看Mach Header。微信除了要执行自己的代码之外,还需要执行Framework里面的代码。那么微信是怎么告诉dyld去加载Framework里面的代码呢?
- 微信的MachO文件中
load Commands(加载命令集)中会有LC_LOAD_DYLIB (andromeda)字段,从这里可以看出动态库的加载等。只要有这个字段,dyld就会去指定的路径下加载依赖的动态库,如果我们要注入代码,就需要创建一个动态库,让微信的load Commands中包含我们的库,这样的话微信就会去加载我们的动态库

综上所述,注入代码需要以动态库的形式注入
一. 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
- 重新打包,过程如下:
- 解压
微信8.0.2.ipa - 替换Payload中的
WeChat可执行文件为上一步修改过的的MachO文件 - 重新对Payload进行打包,打包成
WeChat.ipa
$ zip -ry WeChat.ipa Payload/
- 然后在自己创建的
WeChat工程APP目录下替换ipa包
- 运行程序,可以看到成功执行了
WNHook中的load

二. Dylib注入
创建空工程Dylib,运行安装至手机(安装的目的是想安装描述文件),对WeChat进行重签名,参考应用重签名中的脚本签名,把应用重签名到我们的手机
-
target->+->mac os->Library,命名为WNHook - 在Build Setting中配置WNHook
-
Base SDK改为ios -
Code Signing identify改为iOS Developer
- 当前工程中拷贝lib
-
Build Phase->+->New Copy Files Phase - 然后选择拷贝到的目标文件:
Destination选择Frameworks - 点击
+ 号添加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文件,已经包含了WNHook的dylib - 运行程序,从日志可以看出执行了WNHook中的load
总结
Framwork注入
- 通过Xcode新建Framwork,将库安装进入APP包
- 通过yololib注入Framwork库路径。命令:$yololib(空格)MachO文件路径(空格)库路径
- 所有的Framwork加载都是由DYLD加载进入内存被执行的
- 注入成功的库路径会写入到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

- 通过
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输入框呢?
- 方案一 通过
响应链,一层层查找,缺点很麻烦

- 方案二
静态分析
- 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
- 通过
class-dump获取OC类、方法的列表以及成员变量,其本质也是从MachO文件读出来
将class-dump、WeChat可执行文件拷贝至同一个文件夹
// dump出WeChat可执行文件中OC中类列表
$ ./class-dump -H WeChat -o ./headers/

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

查找类文件顺序
WCAccountTextFieldItem -> WCBaseTextFieldItem -> 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

- 继续执行程序崩溃,执行objc_msgSend后会直接崩溃,原因是
WCAccountMainLoginViewController找不到my_onNext

解决崩溃的方案:给WCAccountMainLoginViewController添加一个method
- 第一种解决方案 通过
class_addMethod给WCAccountMainLoginViewController新增一个方法,再和原来的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