前言
上篇文章10-应用重签名,我们利用CodeSign终端指令
和Shell脚本
2种方式,成功实现了对微信app
的重签名,已经能够查看微信的登录注册页面
的UI层级
,接下来,我们想做些自己的事情,例如注入
自己的代码,修改
微信注册
或登录
的流程。
一、代码注入
一般修改原始的程序,是利用代码注入
的方式,注入代码就会选择利用Framework
或者Dylib
等第三方库的方式注入。
1.1 FrameWork注入
我们知道,重签名
app后自己的壳工程
的代码就被替换
掉了(替换的是MachO二级制执行文件),那么原有的工程代码并不会执行。iOS系统是通过dyld
(iOS系统提供的动态链接器
)加载MachO可执行文件,而加载的过程,首先就是读取MachO的Load Commands
(其中包括_PAGEZERO、_TEXT、_DATA、_LINKEDIT
等)
App的执行过程,除了执行自己的工程代码外,还依赖一些系统基础库(例如UIKit,Foudation
等)和第三方的库(.framwork
或.a
库等),而这些库最终也是一个MachO
可执行文件,分析MachO文件会发现Frameworks
中的库在Load Commands
中以LC_LOAD_DYLIB
存在,路径是Frameworks下对应库
只要
Load Command
中有对应库的LC_LOAD_DYLIB
,dyld
就会去对应路径
下加载库
。
知道了这点,那么代码注入的过程就很明确了
- 确认目标App的
MachO
执行文件的Load Commands
中有对应的LC_LOAD_DYLIB
- 将自己的代码写入动态库中
1.1.1 注入步骤
- 给自己的壳工程添加一个
target
LGHook Framework
动态库
⚠️注意:这里的壳工程,用的是上篇文章10-应用重签名中的
shell脚本重签名
的模式。
-
LGHook
添加LGInject
类,并且重写+load
方法
如果LGHook
被加载进内存,则会打印log
。
-
run
,查看Produces
中.app
动态库LGHook
已经在目标App
的Framewroks
中了,接着我们查看MachO
- 查看MachO文件
Load Commands
中并没有LGHook
的LC_LOAD_DYLIB
,所以run
起来,控制台中并没有打印日志。
1.1.2 yololib手动注入
这个时候就需要使用yololib
工具修改MachO
文件,将LGHook
真正加入到目标App的Load Commands
中
- 拷贝
yololib
和目标App可执行文件
到同一目录
(可单独新建
一个文件夹)
然后执行命令
./yololib 目标可执行文件 要添加的Framework路径名称
例如
./yololib WeChat Frameworks/LGHook.framework/LGHook
再查看可执行文件Load Commands
此时就有LGHook
了。
- 拿取
原始.ipa包
,解压,在Payload
文件夹中找到.app
,右键显示包内容
,替换成上一步
生成的二进制MachO文件
,再回到Payload
所在目录,输入以下指令生成.ipa
包
zip -ry xx.ipa Payload/
⚠️注意:这一步一定要用
原始.ipa包
,替换原始包里的二进制MachO文件。
- 原始包
微信8.0.2.ipa
,将后缀改为.zip
,解压
- 右键
显示包内容
- 替换上一步的
WeChat
Mach-O
- 返回到
Payload
所在目录,输入指令生成.ipa包
- 将上一步生成的.ipa包,放入APP文件夹中
cmd+shift+k
先清空XCode项目的缓存,再来run
解决
修改
LGHook.framework
所支持的最低版本。
再次run
大功告成!
小结
以上步骤大致为
- 新建壳工程
WeChat
,配置重签名脚本
- 通过Xcode新建
LGHook.framwork
(注意改支持的版本
),真机运行,将库安装进入APP包 - 通过
yololib
,对WeChat
的MachO
注入LGHook.framwork
库路径。命令$./yololib MachO文件路径 库路径
- 使用原始微信.ipa包,将第3步生成的
MachO
,替换原始包里面的MachO
,再次打包生成.ipa
包 - 壳工程的
APP文件夹
中,替换第4步生成的.ipa包
,直接运行即可。
所有的Framwork
加载都是由DYLD
加载进入内存被执行的。注入成功的库路径会写入到MachO
文件的LC_LOAD_DYLIB
中。
1.2 Dylib注入
除了Framwork
注入外,还能用Dylib
注入。原理和Framwork
相同,过程和Framwork
差不多。
- 创建dylib库。这里选择了
macOS
,为了是库为动态库
修改Base SDK
为iOS
Code Signing Identity
修改为iOS Developer
build Phases
中添加Copy Files
,增加libLGDylibHook.dylib
- LGDylibHook.m添加Hook代码
+ (void)load {
NSLog(@"LGDylibHook Success ");
}
此时run,仍然只是Frameworks
中有libLGDylibHook.dylib
库,MachO
中Load Commands
仍然没有LC_LOAD_DYLIB
。
1.2.1 yololib自动注入
- 直接在
appResign.sh
脚本中添加yololib
的修改MachO
脚本的指令
#yololib修改MachO文件
#./yololib "$TARGET_APP_PATH/$APP_BINARY" "Frameworks/LGHook.framework/LGHook"
./yololib "$TARGET_APP_PATH/$APP_BINARY" "Frameworks/libLGDylibHook.dylib"
将yololib
库放在工程目录中
- 观察
libLGDylibHook.dylib
是否在Frameworks
中
MachO
中Load Commands
- 运行
⚠️注意:需要
先编译
库libLGDylibHook.dylib
,再编译壳工程
小结
注入步骤
- 通过Xcode新建
dylib
库(注意:dylib属于MacOS
,所以需要修改属性
)-
Base SDK
为iOS
-
Code Signing Identity
修改为iOS Developer
-
- 添加
Target依赖
,让Xcode将自定义Dylib
文件打包进入APP
包 - 利用yololib进行注入
二、示例演示
接下来,我们实战一下,针对微信的注册登录页面
,代码HOOK注册和登录
的流程。
2.1 注册分析
首先是注册
,我们查看注册按钮的UI层级
上图,打印地址可知,发现注册按钮的Target
是WCAccountLoginControlLogic
,action
是onFirstViewRegister
。
然后我们直接hook
这个这个方法就可以拦截了注册事件
#import "LGDylibHook.h"
#import
@implementation LGDylibHook
+ (void)load {
NSLog(@"LGDylib Hook success");
//拦截微信注册事件
Method oldMethod = class_getInstanceMethod(objc_getClass("WCAccountLoginControlLogic"), @selector(onFirstViewRegister));
Method newMethod = class_getInstanceMethod(self, @selector(hook_onFirstViewRegister));
method_exchangeImplementations(oldMethod, newMethod);
}
- (void)hook_onFirstViewRegister {
NSLog(@"WeChat click login");
}
@end
运行看看
成功拦截!接下来,我们想在拦截到事件后,先执行自己的代码,然后恢复原有的流程,怎么做到呢?相信大家都能想到 Method Swizzle。
Method Swizzle
在OC中,SEL
和 IMP
之间的关系,就好像一本书的目录
。SEL
是方法编号,就像标题
一样。IMP
是方法实现的真实地址,就像页码
一样。他们是一一对应
的关系。
Runtime
提供了交换
两个SEL
和IMP
对应关系的函数。
OBJC_EXPORT void
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
通过这个函数交换两个SEL
和IMP
对应关系的技术称之为Method Swizzle(方法欺骗)
。
2.2 登录调试分析
接下来我们调试下【登录】功能
上图可知,view debug
分析可以得到Target
是WCAccountMainLoginViewController
,action
是onNext
。同理,【登录】事件可以拦截拿到,那么pwd
怎么获取呢?
view debug
可以看到pwd
控件是WCUITextField
并且能看到对应的text
(即密码
)。假如我们不借助view debug
,怎么去获取密码呢?
2.2.1动态分析
第一种方式就是动态分析
可以通过响应链
一步步分析控件层级
和响应关系
结合presponder
和pviews
分析。不过这种方式一般比较麻烦
。
2.2.2静态分析
第二种就是静态分析
了 使用class-dump
工具导出头文件
(⚠️swift文件不行
)。
./class-dump -H WeChat -o ./Headers
执行完成后,所有头文件都在Headers文件夹
中
文件过多22335个,
不推荐Xcode
打开。
- 搜索
WCAccountMainLoginViewController
,找到onNext
onNext
方法既没有参数也没有返回值,再看看有没其它的和密码相关的
我们发现了_textFieldUserPwdItem
不就是密码的输入框吗。
- 搜索
WCAccountTextFieldItem
没有找到WCUITextField相关的内容,接着找父类WCBaseTextFieldItem
找到了WCUITextField
类型的m_textField
成员变量。这个不就是输入账号密码
的控件。
- 调试验证
接下来我们来验证下,是否是输入密码的控件? KVC
控制台打印查看
相关指令
(lldb) po [(WCAccountMainLoginViewController *)0x117813000 valueForKey:@"_textFieldUserPwdItem"]
(lldb) po [(WCAccountTextFieldItem *)0x2830f9ec0 valueForKey:@"m_textField"]
; layer = >
(lldb) po [(WCUITextField *)0x1120a4400 text]
qwer1234
果然找到了对应的类
和想要的密码
。
2.3 登录代码注入
⚠️注意:别用
自己的常用账号
去尝试,有可能被封号
。
最后,也是重点,注入自己的代码
+ (void)load {
NSLog(@"LGDylib Hook success");
//拦截微信登录事件
Method oldMethod = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext));
Method newMethod = class_getInstanceMethod(self, @selector(hook_onNext));
method_exchangeImplementations(oldMethod, newMethod);
}
- (void)hook_onNext {
NSLog(@"WeChat click login");
//获取密码
UITextField *textField = (UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"];
NSString *pwd = [textField text];
NSLog(@"password: %@",pwd);
}
接下来,在这个过程中我们应该调用回原来的方法,让登录
进行下去
- (void)hook_onNext {
NSLog(@"WeChat click login");
//获取密码
UITextField *textField = (UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"];
NSString *pwd = [textField text];
NSLog(@"password: %@",pwd);
[self hook_onNext];
}
运行
报错:找不到方法!WCAccountMainLoginViewController
中不存在hook_onNext
方法。因为
一般情况下我们进行
方法交换
在分类
中进行,现在不是在分类中
.
那有没有解决方案呢?当然有,且有3种
class_addMethod方式
利用AddMethod
方式,让原始方法可以被调用,不至于因为找不到SEL而崩溃
//1.class_addMethod 方式
+ (void)load {
//拦截微信登录事件
Class cls = objc_getClass("WCAccountMainLoginViewController");
Method onNext = class_getInstanceMethod(cls, @selector(onNext));
//给WC添加新方法
class_addMethod(cls, @selector(new_onNext), new_onNext, "v@:");
//交换
method_exchangeImplementations(onNext, class_getInstanceMethod(cls, @selector(new_onNext)));
}
//相当于imp
void new_onNext(id self, SEL _cmd) {
//获取密码
UITextField *textField = (UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"];
NSString *pwd = [textField text];
NSLog(@"password: %@",pwd);
[self performSelector:@selector(new_onNext)];
}
class_replaceMethod方式
利用class_replaceMethod
,直接给原始的方法替换IMP
//2.class_replaceMethod 方式
+ (void)load {
//拦截微信登录事件
Class cls = objc_getClass("WCAccountMainLoginViewController");
//替换
origin_onNext = class_replaceMethod(cls, @selector(onNext), new_onNext, "v@:");
}
//原始imp
IMP (*origin_onNext)(id self, SEL _cmd);
//相当于imp
void new_onNext(id self, SEL _cmd) {
//获取密码
UITextField *textField = (UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"];
NSString *pwd = [textField text];
NSLog(@"password: %@",pwd);
origin_onNext(self,_cmd);
}
method_setImplementation方式
利用method_setImplementation
,直接重新赋值
原始的IMP
//3.method_setImplementation 方式
+ (void)load {
//拦截微信登录事件
Class cls = objc_getClass("WCAccountMainLoginViewController");
//获取method
Method onNext = class_getInstanceMethod(cls,@selector(onNext));
//get
origin_onNext = method_getImplementation(onNext);
//set
method_setImplementation(onNext, new_onNext);
}
//原始imp
IMP (*origin_onNext)(id self, SEL _cmd);
//相当于imp
void new_onNext(id self, SEL _cmd) {
//获取密码
UITextField *textField = (UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"];
NSString *pwd = [textField text];
NSLog(@"password: %@",pwd);
origin_onNext(self,_cmd);
}
以上3种方式都可以成功回到原来的登录流程。大家可以自行验证。
Demo(包含微信.ipa原始包)
XFResignHook
⚠️注意:微信ipa包体过大,无法上传gitHub,如果有需要,请留言发邮箱或私信我。
总结
- 代码注入:Framework(推荐)/ dylib
- 创建
Framework/dylib
-
yololib
修改Macho Load Commands
./yololib 要修改的可执行文件 要添加的Framework/dylib路径&名称
- 创建
- 调试分析
-
动态
调试:view debug
调试控件层级
结合presponder
和pviews
-
静态
分析:通过class-dump
导出头文件
分析代码逻辑
-
- 代码Hook
-
+ load
方法中hook对应类的对应方法 -
hook
方法中调用回原来的方法class_addMethod
class_replaceMethod
method_setImplementation
-
相关链接
yololib
class-dump官网
class-dump github
class-dump 兼容Swift版本