一、代码注入
重签名app后自己的壳工程的代码就被替换掉了(替换了整个MachO),并不会执行。iOS系统是通过dyld加载可执行文件,最重要的是读取MachO的Load Commands(其中包括_PAGEZERO、_TEXT、_DATA、_LINKEDIT等)。
对于一个App来说除了执行自己的代码还需要执行UIKit以及自身Frameworks(本身也是一个MachO)中的代码。分析MachO文件会发现Frameworks中的库在Load Commands中以LC_LOAD_DYLIB存在,路径是Frameworks下对应库:

只要
Load Command中有对应库的LC_LOAD_DYLIB,dyld就会去对应路径下加载库。
如果自己的代码放入动态库中,并且让目标App的Load Commands中有对应的LC_LOAD_DYLIB代码就可以注入了。一般修改原始的程序,是利用代码注入的方式,选择利用FrameWork或者Dylib等三方库的方式注入。
1.1、FrameWork注入
1.给自己的壳工程添加一个HPHook Framework动态库

2.HPHook 添加HPInject类,并且重写+load方法
@implementation HPInject
+ (void)load {
NSLog(@"HPInject Hook");
}
@end
如果HPHook被加载进内存,则会打印log。
3.build运行后查看Produces中.app

这个时候动态库HPHook已经在目标App的Framewroks中了,运行后HPInject的+ load方法并没有执行。
4.查看Macho文件

在Load Commands中并没有发现HPHook的LC_LOAD_DYLIB。
1.1.1、yololib手动注入
这个时候就需要使用yololib工具修改MachO文件,将HPHook加入到目标App的Load Commands中。
1.拷贝yololib和目标App可执行文件到同一目录执行命令:
./yololib 目标可执行文件 要添加的Framework路径名称
./yololib WeChat Frameworks/HPHook.framework/HPHook

这个时候可执行文件Load Commands中就已经有HPHook了:

2.打包修改后的目标App可执行文件为.ipa包
zip -ry WeChat.ipa Payload/
使用新的ipa包替换APP目录中的ipa包。
3.编译运行
这个过程中可能会出现HPHook没有被编译进入Frameworks的情况,删除Products多试几次(Xcode问题)。
如果没有问题就注入成功了:

⚠️运行出现问题首先排查Frameworks和Load Commands中都存在HPHook。
注入步骤
- 通过
Xcode新建Framwork,将库安装进入APP包 - 通过
yololib注入Framwork库路径。命令:$yololib MachO文件路径 库路径- 所有的
Framwork加载都是由DYLD加载进入内存被执行的 - 注入成功的库路径会写入到
MachO文件的LC_LOAD_DYLIB字段中
- 所有的
1.2、Dylib注入
通过FrameWork可以注入,也可以通过dylib注入,原理和framework相同。
1.创建dylib库
这里选择了macOS,为了是库为动态库:

修改Base SDK为iOS:

Code Signing Identity修改为iOS Developer:

build Phases中添加Copy Files增加libHPDylibHook.dylib:

2.HPDylibHook.m添加Hook日志
@implementation HPDylibHook
+ (void)load {
NSLog(@"HPDylibHook Hook Success");
}
@end
这个时候仍然只是Frameworks中有libHPDylibHook.dylib库,MachO中Load Commands仍然没有LC_LOAD_DYLIB。
1.2.1 yololib自动注入
1.直接在appResign.sh脚本中添加yololib修改MachO脚本:
#yololib修改MachO文件
#./yololib "$TARGET_APP_PATH/$APP_BINARY" "Frameworks/HPHook.framework/HPHook"
./yololib "$TARGET_APP_PATH/$APP_BINARY" "Frameworks/libHPDylibHook.dylib"
- 直接运行
注意观察libHPDylibHook.dylib是否在Frameworks中:

MachO中Load Commands:

注入成功:

注入步骤
- 通过
Xcode新建dylib库(注意:dylib属于MacOS所以需要修改属性) - 添加
Target依赖,让Xcode将自定义Dylib文件打包进入APP包。 - 利用
yololib进行注入。
二、注册分析

调试代码可以发现注册按钮的Target是WCAccountLoginControlLogic,action是onFirstViewRegister。
直接hook这个这个方法就拦截了注册事件:
#import "HPInject.h"
#import <objc/runtime.h>
@implementation HPInject
+ (void)load {
NSLog(@"HPInject 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

这个时候注册事件就拦截到了。
三、登录调试分析

view debug分析可以得到Target是WCAccountMainLoginViewController,action是onNext。同理登录事件可以拦截拿到,那么pwd怎么获取呢?

view debug可以看到pwd控件是WCUITextField并且能看到对应的text。要做的就是拿到WCUITextField。
(lldb) po [(WCUITextField *)0x158ad6cb0 text]
987654321
3.1动态分析
可以通过响应链一步步分析控件层级和响应关系。

结合
presponder和pviews分析。不过这种方式一般比较麻烦。
3.2静态分析
使用class-dump工具导出头文件(swift文件不行)。
./class-dump -H WeChat -o ./Headers

导出头文件后用Sublime打开Headers文件夹(文件过多22335个,不推荐Xcode)。
1.搜索WCAccountMainLoginViewController

找到onNext方法,发现没有参数。但是找到了_textFieldUserPwdItem,看着和密码相关:

2.搜索WCAccountTextFieldItem

没有找到
WCUITextField相关的内容。
3.继续搜索WCAccountTextFieldItem的父类WCBaseTextFieldItem

找到了
WCUITextField类型的m_textField成员变量。这个时候感觉找到了输入账号密码的控件。
4.调试验证
(lldb) po [(WCAccountMainLoginViewController *)0x112054a00 valueForKey:@"_textFieldUserPwdItem"]
<WCAccountTextFieldItem: 0x281382a00>
(lldb) po [(WCAccountTextFieldItem *)0x281382a00 valueForKey:@"m_textField"]
<WCUITextField: 0x1112c1050; baseClass = UITextField; frame = (20 0; 345 44); text = '654321'; opaque = NO; autoresize = W+H; tintColor = UIExtendedSRGBColorSpace 0.027451 0.756863 0.376471 1; gestureRecognizers = <NSArray: 0x283bce0a0>; placeholder = 请填写密码; borderStyle = None; background = <_UITextFieldNoBackgroundProvider: 0x2835904c0: textfield=<WCUITextField 0x1112c1050>>; layer = <CALayer: 0x283774b80>>
(lldb) po [(WCUITextField *)0x1112c1050 text]
654321
验证找到了对应的类和想要的内容。
四、登录代码注入
+ (void)load {
NSLog(@"HPInject 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);
}
这个时候就能拿到密码了:
WeChat[8322:4143129] WeChat click login
WeChat[8322:4143129] password: 654321
WeChat[8322:4143129] WeChat click login
WeChat[8322:4143129] password: 654321
在这个过程中我们应该调用回原来的方法,让登录进行下去:
- (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];
}
直接在hook_onNext调用[self hook_onNext],这个时候运行会直接崩溃,方法不存在。一般情况下我们进行方法交换在分类中进行,现在由于不是在分类中,所以在WCAccountMainLoginViewController中并不存在hook_onNext方法:

有3种方式调用回原方法。
4.1 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)];
}
4.2 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);
}
4.3 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);
}
至此能够拦截到密码,并且能调用原来的登录方法了。
⚠️:别用自己的常用账号去尝试,有可能被封号。
Demo
总结
- 代码注入
-
Framework(推荐)/dylib注入-
Xcode自动打包进入App包 -
MachO中Load Command需要LC_LOAD_DYLIB字段( -
dyld加载动态库 -
yololib修改Macho Load Commands:./yololib 要修改的可执行文件 要添加的Framework/dylib路径&名称
-
-
- 调试分析
- 动态调试:
view debug调试控件层级结合presponder和pviews - 静态分析:通过
class-dump导出头文件(OC类和方法列表)分析代码逻辑
- 动态调试:
- 代码
Hook-
+ load方法中hook对应类的对应方法 -
exchange函数交换SEL与IMP的对应关系(类似书的目录和页码)- 隐患:没法调用原来的实现
-
hook方法中调用回原来的方法
1.添加方法解决class_addMethod
2.replace替换class_replaceMethod
3.getIMP和setIMP配合method_setImplementation(推荐,大部分HOOK框架使用这个)
-









