前言
上篇文章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,解压👇

- 右键
显示包内容👇

- 替换上一步的
WeChatMach-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 <objc/runtime.h>
@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"]
<WCAccountTextFieldItem: 0x2830f9ec0>
(lldb) po [(WCAccountTextFieldItem *)0x2830f9ec0 valueForKey:@"m_textField"]
<WCUITextField: 0x1120a4400; baseClass = UITextField; frame = (20 0; 345 44); text = 'qwer1234'; opaque = NO; autoresize = W+H; tintColor = UIExtendedSRGBColorSpace 0.027451 0.756863 0.376471 1; gestureRecognizers = <NSArray: 0x2818beca0>; layer = <CALayer: 0x281441dc0>>
(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原始包)
⚠️注意:微信ipa包体过大,无法上传gitHub,如果有需要,请留言发邮箱或私信我。
总结
- 代码注入:Framework(推荐)/ dylib
- 创建
Framework/dylib -
yololib修改Macho Load Commands./yololib 要修改的可执行文件 要添加的Framework/dylib路径&名称
- 创建
- 调试分析
-
动态调试:view debug调试控件层级结合presponder和pviews -
静态分析:通过class-dump导出头文件分析代码逻辑
-
- 代码Hook
-
+ load方法中hook对应类的对应方法 -
hook方法中调用回原来的方法class_addMethodclass_replaceMethodmethod_setImplementation
-
