前言
上篇文章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
- 将自己的代码写入动态库中
- 给自己的壳工程添加一个
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(@"LGDylibHook 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
【11-代码注入】运行看看
文章图片
成功拦截!接下来,我们想在拦截到事件后,先执行自己的代码,然后恢复原有的流程,怎么做到呢?相信大家都能想到 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
-
class-dump官网
class-dump github
class-dump 兼容Swift版本