在上一篇文章中,在上一篇文章中,我们讲了iOS应用重签名技术,同时我们也把微信的越狱ipa包安装到了我们的手机上了,那么,这一章,我们就来讨论讨论代码注入相关的话题吧。
接下来本文会从以下几点进行阐述:
- Framework注入
- yololib手动注入
- Dylib注入
- MethodSwizzle
- 破坏微信注册
准备工作: 本篇文章需要用到以下工具
越狱微信7.0.2 提取码:hyya
MacchOView
yololib
class-dump
1.Framework注入
一般修改原始的程序,是利用代码注入的方式,注入代码就会选择利用FrameWork或者Dylib等三方库的方式注入。
什么是Framework这里就不多加叙述,我参考这个网站,非常详细,看不懂你直接@我。点这里:Framework最强讲解
接下来直接演示如何创建一个Framework,并且介绍跟咱们Hook微信有关的基础原理。
1.1 新建项目,完成重签名过程
根据上一篇文章,我们学会了脚本自动代码重签名,这也是本次要用到的内容,不理解的小伙伴可以移步看看iOS代码重签名相关。
首先,我们要创建一个项目,名称叫做FrameworkInject,并且使用脚本对项目重签名。(这里我们还是签上一篇文章中介绍的微信项目)。
由于上一篇文章我们简单的介绍了下项目的目录结构,所以 这里我们使用Xcode快捷键 command+b 编译一下,可以看到项目的目录下出现了Temp文件夹,依次打开 Temp->Payload->WeChat右键显示包内容,找到 WeChat 的可执行文件,使用 MachOView 将WeChat打开,下一步我们简单分析下 MachO这个可执行文件。
1.2 MachO文件简单分析,注意库加载逻辑
1.将 WeChat 文件使用 MachOView 打开会得到如下的界面:
注意:微信用的第三方库有的是在app包里面,有的是在iOS系统里,当程序加载的时候回去 LC_LOAD_DYLIB 这里面查找库的路径,只有在这里的库才会被加载,否则不加载。
1.3 注入自己的三方库。
1.在当前项目中新建framework,打开项目,选择Targets,选择下面的添加按钮。注意:
在我们用XCode新建CZHook的时候,其实XCode帮我们做了一部操作:创建CZHook时候,同时将CZHook链接到我们的项目中(这是后期的XCode新增功能,早年的XCode这一步是需要我们自己做的)
如图:
但是CZHook在ipa文件中,并不代表着CZHook就可以被我们的可执行文件所执行,因为CZHook并没有没导报入我们的可执行文件,只有在这个可行执行文件的某一个地方做好标记,告知可执行文件,在适当的时候需要加载外部的CZHook,才能够正常运行。
而这个地方所说的可执行文件就是MachO文件,我们可以利用工具MachOView来查看MachO中到底有什么内容。结果肯定是找不到的,所以,下一步我们来将CZHook文件标记到我们的 MachO文件中。
5.这里我们就需要用到终端命令行工具 yololib
将下载下来的yololib.zip解压后得到的yololib放在目录/usr/local/bin下,这样我们在终端中就可以使用yololib命令了
以下命令就是将FYHook注入WeChat的命令
// yololib 「MachO路径」 「CZHook相对MachO的路径」
yololib WeChat Frameworks/CZHook.framework/CZHook
1.4 使用yololib手动注入。
这里有个细节需要注意下,我们使用脚本重新签名,每次重新签名的文件都在目录Temp下,所以我们修改Temp下的文件是没有用的。
这里我们要修改APP目录下的ipa才可以。
1.解压APP目录下的*.ipa包,依次进入Payload->WeChat显示包内容。
3.推出到Payload文件目录下执行 zip -ry WeChat.ipa Payload
,完成之后将 ipa替换到 APP文件目录下。
4.在 CZHook 中添加文件继承与NSObject,并添加以下代码
+(void)load{
NSLog(@"来了 。。。。 ");
}
如图:5.运行项目,当项目输出 来了 。。。。
这句话的时候说明我们手动注入代码成功。
2.Dylib注入
接下来我们来讲一下Dylib注入,Dylib也是常见的注入方法,只是Dylib注入不属于iOS注入的方法,而是属于Mac的注入方法,以下是步骤:
1.首先,我们要创建一个项目,名称叫做DylibInject,并且使用脚本对项目重签名。
2.创建新的TARGET,这里选择 macOS 的 Library.注意:
1. Dylib是给 macOS 使用的库,所以默认不支持iOS使用,我们需要手动设置两个地方。
2. 选择 CZHook -> Build Settings,将Base SDK 改成 iOS。此时我们会发现这个库支持运行在手机上了。
3. 修改CZHook的签名信息,选择 CZHook -> Build Settings,选择 ALL 搜索 sign,会发现 Code Signing Identity是使用的Mac develop证书签名的,我们要把它改成iOS develop证书签名。如下图:
3.此时,先选择CZHook,command+b Build一下,把CZHook库编译出来,然后选择我们的项目工程,再次Build一次,然后查看Products下的*.app,选择show in finder 查看信息。
4.此时查看 libCZHook.dylib 库是有的,但是我们要去看安装包中的Framework中有没有libCZHook.dylib这个库。目前是没有这个库的。
5.原因是我们需要自己把libCZHook.dylib这个库添加到我们的可执行文件中,操作方法如下:
- 在TARGETS中选择我们的工程项目
-
选中Build Phases,点击加号选择New Copy Files Phase。添加我们的libCZHook.dylib库
6.此时 Build一下,此时会发现libCZHook.dylib出现在我们项目可执行文件中了,但是没有在Frameworks这个文件夹中。
7.然后我们去脚本中注入libCZHook.dylib这个库。在脚本最后添加
yololib "$TARGET_APP_PATH/$APP_BINARY" "Frameworks/libCZHook.dylib"
8.此时,我们command+r 运行一下,我运行成功,没有问题。
注意:
- 假如这里报错,在 dyld 加载的时候报错,我们要查看报错的信息,看看是否加载了,或者没有加载成功,这里可能会遇到这个库被加载了,但是没有加载成功(可能是签名信息有误,没有成功)。
- 原因是我们使用脚本对Frameworks中的库多签名了一次,这里就没有必要使用Xcode再次帮我们拷贝一份新的库了,所以这里我们需要自己手动把这个包放到Framework中去,然后根据上一篇文章的方法把ipa重新打包一下。
参考代码02DylibInject
3.MethodSwizzle 简介
在前面的案例中,我们不停的注入代码,添加自己的代码,添加自己的逻辑,此时我们会用到Objective-C中的MethodSwizzle,这里我们简单介绍一下。
利用OC的Runtime特性,动态改变SEL(方法编号)和IMP(方法实现)的对应关系,达到OC方法调用流程改变的目的。主要用于OC方法。
在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(方法欺骗)
3.1 Objecive-C 消息案例(简单解析)
在OC中,函数调用我们可以理解成消息转发,就是给某一个对象发送一个消息,如下图:在简单了解一下后,我们来撸一个真实的案例如下:
1.使用NSUrl发送请求。这个请求是没有问题的。
NSURL *url2 = [NSURL URLWithString:@"www.baidu.com"];
NSURLRequest *request = [NSURLRequest requestWithURL:url2];
NSLog(@"%@",request);
2.模拟NSUrl有中文的情况。这时url不转码的话会为空,此时报错。
NSURL *url2 = [NSURL URLWithString:@"www.baidu.com/中文"];
NSURLRequest *request = [NSURLRequest requestWithURL:url2];
NSLog(@"%@",request);
3.此时我们怎么处理呢? 因为使用 URLWithString:的地方比较多,找起来很不好处理,这时我们可以用消息转发的方式,我们自己给他添加转码的逻辑。hook 怎个项目中URLWithString的方法。
4.使用分类处理。
分类代码如图:// 代码如下
+(void)load{
// 获取方法
Method urlWithStr = class_getClassMethod(self, @selector(URLWithString:));
Method czUrlWithStr = class_getClassMethod(self, @selector(CZUrlWithString:));
// 交换方法
method_exchangeImplementations(urlWithStr, czUrlWithStr);
}
+(instancetype)CZUrlWithString:(NSString *)str{
// 调用系统原来的方法
NSURL *url = [NSURL CZUrlWithString:str];
// 判断url是否为空,转码
if (url == nil) {
str = [str stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
}
url = [NSURL CZUrlWithString:str];
return url;
}
4.ViewDebug、LLDB、class-dump分析微信登录页面
4.1.class-dump介绍和简单使用
将class-dump拷贝到Mac的目录/usr/local/bin下,这样我们在终端中就可以使用class-dump命令了。class-dump,是可以把Objective-C运行时的声明的信息导出来的工具。其实就是可以导出.h文件。用class-dump可以把未经加密的app的头文件导出来。
1.在左面创建空的文件夹 WeChatHeadrs。并进入到当前的文件夹。
2.进入到 WeChat的可执行文件夹中,运行命令将WeChat所有的头文件导出来。
// class-dump -H 「app的MachO文件」 -o 「输入的目录」
class-dump -H WeChat -o /Users/XX/desktop/WeChatHeaders
4.2.通过ViewDebug、LLDB来找到微信的登录按钮,并Hook其方法。
4.2.1 尝试Hook微信的注册方法。
任务
1.先将微信项目跑起来,通过ViewDebug找到微信的注册按钮。
2.向微信的注册按钮事件中添加一句话。
操作如下:
2.选中注册按钮,这时会发现注册按钮的Clase Name为FixTitleColorButton这个是微信自定义的按钮。
3.按钮的Target保存了按钮添加在哪个视图,通过底下的po命令输出,我们会发现按钮被添加到了控制器WCAccountLoginControlLogic上,分析Action地址发现按钮执行的函数叫做onFirstViewRegester。
4.这时就要用到我们通过class-dump获取到的头文件了,搜索 @interface WCAccountLoginControlLogic
文件。找到 onFirstViewRegester
。如图:
5.接下来,我们就来Hook onFirstViewRegester这个方法了。
Hook代码:
+(void)load{
NSLog(@"来了 。。。。 ");
//错误写法,注意 "WCAccountLoginControlLogic" 前面不能加@
// Method oldMethod = class_getInstanceMethod(objc_getClass(@"WCAccountLoginControlLogic"), @selector(onFirstViewRegester));
Method oldMethod = class_getInstanceMethod(objc_getClass("WCAccountLoginControlLogic"), @selector(onFirstViewRegester));
Method newMethod = class_getInstanceMethod(self, @selector(CZHookOnFirstViewRegester));
method_exchangeImplementations(oldMethod, newMethod);
}
-(void)CZHookOnFirstViewRegester{
NSLog(@"检测到异常,不能注册!!!!!");
}
参考代码04FrameworkInject(动态调试Hook微信注册按钮)
4.2.2 尝试Hook微信的登录账号、密码。
操作步骤类似于上面的4.2.1,结果如下图:接下来我们来分析下登录注册文本框事件。
由以上内容的出,我们现在所在的类中,具体的文本框代码不清楚,这时就需要我们去分析头文件了。
首先,我们来找一下WCAccountMainLoginViewController这个类的头文件,看看有什么收获吧。
1.如图,我们找到了类似账号、密码登录的控件,但是此时并不敢确定,还是要用ViewDug去调试一下,发现文本框的类型并不是WCAccountTextFieldItem类型
2.我们接着去找WCAccountTextFieldItem的头问津啊查看情况,会发现WCAccountTextFieldItem里面的信息更少了,但是我们发现这个类继承于WCBaseTextFieldItem,我们去看看吧。
3.在 WCBaseTextFieldItem中我们发现了WCUITextField类型的变量m_textField
,此时和我们要找的控件类型一致了,然而,还是让我们去验证一下吧 。。
4.我们通过KVC的方式从WCAccountMainLoginViewController获取WCAccountTextFieldItem。
po [(WCAccountMainLoginViewController *)0x13606fa00 valueForKey:@"_textFieldUserNameItem"]
5.从WCAccountTextFieldItem中获取WCUITextField
po [(WCAccountTextFieldItem *)0x281c18d20 valueForKey:@"m_textField"]
最终结果如下:部分代码:
#import "CZHookInject.h"
#import <objc/runtime.h>
@implementation CZHookInject
+(void)load{
Method oldMyNext = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext));
Method newMyNext = class_getInstanceMethod(self, @selector(hook_myNext));
method_exchangeImplementations(oldMyNext, newMyNext);
}
-(void)hook_myNext{
NSString *userNmaeStr = [[[self valueForKey:@"_textFieldUserNameItem"] valueForKey:@"m_textField"] performSelector:@selector(text)];
NSLog(@"输入的用户名是:%@",userNmaeStr);
}
7.当获取完用户的密码的时候,我们需要代码走之前的逻辑,这时我们需要调用原来的方法,但是怎么调用呢?是调用hook_myNext方法吗?会发生什么呢?
参考代码05FrameworkInject(动态调试Hook微信账号密码)
4.2.3 解决崩溃
崩溃原因介绍:当我们调用hook_myNext的方式时,WCAccountMainLoginViewController的方法列表中没有hook_myNext方法的实现,所以崩溃了,这里不做多解释。
解决崩溃的方法:
- 通过动态添加方法解决
- 通过方法替换完成交换
- 通过getIMP和setIMP方式解决
添加方法的方式解决
代码如下:
/* 添加方法
+(void)load{
Method oldMyNext = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext));
// 添加新方法
BOOL didAddMethod = class_addMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(hook_myNext), hook_myNext, @"v@:");
// 获取新添加的方法
Method newMethod = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(hook_myNext));
method_exchangeImplementations(oldMyNext, newMethod);
}
void hook_myNext(id self,SEL _cmd) {
NSString *userNmaeStr = [[[self valueForKey:@"_textFieldUserNameItem"] valueForKey:@"m_textField"] performSelector:@selector(text)];
NSLog(@"输入的用户名是:%@",userNmaeStr);
[self performSelector:hook_myNext];
}
替换方法
1.定义一个函数指针,保存之前的函数
2.获取之前的函数
3.替换之前的函数
缺点:
如果你的交换没有onNext方法,使用 replaceMethod 会默认帮你添加一个空方法
代码:
IMP (*old_onNext)(id self,SEL _cmd);
+(void)load{
// 1,拿到原始的imp
old_onNext = method_getImplementation(class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext)));
// 替换imp
class_replaceMethod(objc_getClass(@"WCAccountMainLoginViewController"), @selector(onNext), my_next, @"v@:");
}
void my_next(id self,SEL _cmd){
NSString *userNameStr = [[[self valueForKey:@"_textFieldUserNameItem"] valueForKey:@"m_textField"] performSelector:@selector(text)];
NSLog(@"输入的用户名是:%@",userNameStr);
// 调用之前的函数
old_onNext(self,_cmd);
}
通过getIMP和setIMP方式解决
setIMP 和 getIMP 方法
/*
1.拿到旧的方法的IMP
2.保存旧的IMP
3.设置IMP
*/
IMP (*old_onNext)(id self,SEL _cmd);
+(void)load{
// 1.拿到原始的method,以下两句代码效果一样,下面的不会报警告
// Method onNext = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext));
Method onNext = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), sel_registerName("onNext"));
// 2.保存旧的IMP
old_onNext = method_getImplementation(onNext);
// 3.设置IMP
method_setImplementation(onNext, (IMP)my_next);
}
void my_next(id self,SEL _cmd){
NSString *userNameStr = [[[self valueForKey:@"_textFieldUserNameItem"] valueForKey:@"m_textField"] performSelector:@selector(text)];
NSLog(@"输入的用户名是:%@",userNameStr);
// 调用之前的函数
old_onNext(self,_cmd);
}
参考代码06FrameworkInject(解决hook的崩溃)
警告!
非越狱状态,玩逆向微信不要真的登录,有被警告甚至封号风险
参考文章:
作者:一缕清风扬万里
原文地址:https://www.jianshu.com/p/31232eef35c5