利用 LLDB 对微信进行分析, 然后利用分析的结果, 再逐步讲解如何 Hook 微信的登录过程, 截获微信密码.
在上一篇文章 (App 重签名 https://www.jianshu.com/p/4e3aa435d848 ) 中, 已经介绍了如何对 App 重签名, 并且利用 XCode 将微信跑起来, 既然到了这一步, 就万万不能错过强大的 LLDB. 这篇文章就讲为大家讲解到如何利用 LLDB 对微信进行分析, 然后利用分析的结果, 再逐步讲解如何 Hook 微信的登录过程, 截获微信密码.
老规矩, 片头先给福利: 点击下载 Demo:HookWeChat https://github.com/dengbin9009/HookWeChat , 这次有两份代码. 由于越狱版微信体积太大, 受到 GitHub 限制, 所以并没有将它传到 GitHub, 可以在下方链接单独下载
文中所需要用到的工具和文件:
越狱版本微信 7.0.2 https://pan.baidu.com/s/16MPzurhu15rWlq3Gjs1bxg 提取码: 2w87
MachOView https://pan.baidu.com/s/1P83AK1IDT0Wv8k2r-s9cIg 提取码: n3hy
yololib https://pan.baidu.com/s/1MRrYIpFzoKjjIZk4y3yTZQ 提取码: rvy3
class-dump https://pan.baidu.com/s/1SfM9rFLTjuNambRdYFVv-w 提取码: iCSS
接下来我们会从一下几部, 让微信脱下看似安全的外衣, 裸露在大家面前.
Framework 的作用
初探 MachO (原理分析)
代码注入 (代码过程)
ViewDebug,LLDB,class-dump 分析微信登录页面(原理分析)
Hook 登录, 自动获取密码 (代码过程)
总结
1,Framework 的作用(对 Framework 熟悉的同学可以跳过这一步)
什么是 Framework 这里就不多加叙述, 我参考这个网站, 非常详细, 看不懂你直接 @我. 点这里: Framework 最强讲解
废话不多说, 接下来直接演示如何创建一个 Framework, 并且介绍跟咱们 Hook 微信有关的基础原理.
新建一个工程 FrameworkDemo, 新建一个 Framework, 取名 FYHook
在新建出来的 FYHook 文件夹中新建 InjectCode(继承 NSObject)对象, 并且新建代码:
- + (void)load {
- NSLog(@"来了, 老弟");
- }
直接运行, 会发现来了, 老弟被输出, 证明用这种方法新建的 Framework 能够直接运行在我们的项目中.
2, 初探 MachO (如果不想看原理, 可以直接跳到第三部 代码注入)
根据上篇文章 App 重签名 https://www.jianshu.com/p/4e3aa435d848 讲到的, 我们可以使用 XCode 将微信跑起来, 那么是不是将两者结合起来, 就可以将我们的代码注入进微信的 App 呢?
Step 1 先思考一个问题.
根据 App 重签名 https://www.jianshu.com/p/4e3aa435d848 中的结论, 利用脚本可以便捷重签 App(因为我们用的 WeChat 举例, 所以下面简称 WeChat), 那么我们在重签脚本的工程中, 直接创建一个 Framework, 能不能让我们 Framework 中的代码在 WeChat 中运行?
很显然, 这是不行的(有兴趣的可以试一下)! 为什么? 这个问题下面会回答, 先把这个问题记在心中.
Step 2 MachO 作用
在我们用 XCode 新建 HYHook 的时候, 其实 XCode 帮我们做了一部操作: 创建 HYHook 时候, 同时将 HYHook 链接到我们的项目中(这是后期的 XCode 新增功能, 早年的 XCode 这一步是需要我们直接做的)
common+b Build 一下, 会发现在已经 Build 出来的文件中的 Frameworks 下已经有 FYHook 了, 已经已经表明 FYHook 被 Copy 我们的 ipa 文件了.(如何看 Build 出来的文件? 查看: App 重签名中 Step 8 App 重签名 https://www.jianshu.com/p/4e3aa435d848 )
但是 FYHook 在 ipa 文件中, 并不代表着 FYHook 就可以被我们的可执行文件所执行, 因为 FYHook 并没有没导报入我们的可执行文件, 只有在这个可行执行文件的某一个地方做好标记, 告知可执行文件, 在适当的时候需要加载外部的 FYHook, 才能够正常运行.
而这个地方所说的可执行文件就是 MachO 文件(具体什么是 MachO, 这不是本片文章的重点内容, 可以持续关注笔者之后的文章, 下一章详细介绍这至关重要的 MachO), 我们可以利用工具 MachOView 来查看 MachO 中到底有什么内容.
Step 3 MachOView
点击这里下载: MachOView https://pan.baidu.com/s/1P83AK1IDT0Wv8k2r-s9cIg 提取码: n3hy
用 MachOView 打开 FrameworkDemo 的 MachO, 可以看到如下图
可以看到其中的有个 Load Commons 组, 这里面就包括所有需要被动态加载的库. 也就是说, 如果在 Load Commons 中没有对应的 FYHook, 就不会加载 FYHook.
在上图中可以看到 FYHook 已经被加入了 Load Commons, 并且图右侧也标记了 FYHook 所属的目录(和 MachO 文件同级的 Frameworks 下 FYHook.framework 中 ,FYHook.framework 其实是个文件夹, 里面的 FYHook 也是个 MachO )
所以这里就得到了「为什么我们直接将 FYHook 加入我们的从重签脚本工程, 不能直接运行 FYHook」的答案. 因为在在我们 Build 出来的 MachO 文件中的 Load Commons 中没有加入 FYHook 的路径. 所以无法运行 FYHook 中的代码.
那么我们直接将 FYHook 加入我们 Build 出的 MachO 文件行吗? 显然也是不行的, 因为我们 Build 出的 MachO 文件始终会被原始包 (WeChat) 中的 MachO 给替换掉. 我们需要将 FYHook 加入原始包 (WeChat) 中的 MachO 中.
Step 4 将 FYHook 标记入 MachO 中
这里我们就需要用到终端命令行工具: yololib https://pan.baidu.com/s/1MRrYIpFzoKjjIZk4y3yTZQ 提取码: rvy3
将下载下来的 yololib.zip 解压后得到的 yololib 放在目录 / usr/local/bin下, 这样我们在终端中就可以使用 yololib 命令了
以下命令就是将 FYHook 注入 WeChat 的命令
- // yololib 「MachO 路径」 「FYHook 相对 MachO 的路径」
- yololib WeChat Frameworks/FYHook.framework/FYHook
3, 代码注入
Step 1 建立重签脚本工程
新建工程, 取名 InjectFrameWork, 过程可参照上一篇文章(App 重签名 https://www.jianshu.com/p/4e3aa435d848 ) 最后得到如下工程:
Step 2 创建 Framework 文件
新建一个 Framework 文件, 取名 FYHook, 在 FYHook 中新建文件 InjectCode, 在 InjectCode 加入之前提到的同样的 load 代码, 等到如下工程:
Step 3 修改源文件的 MachO 文件
找到 WeChat 的 MachO 文件, 打开终端, 进入此目录下 执行命令
- // yololib 「MachO 路径」 「FYHook 相对 MachO 的路径」
- yololib WeChat Frameworks/FYHook.framework/FYHook
Step 4 重新打包 WeChat.ipa
zip -ry WeChat.ipa Payload
Step 5 加入新的 WeChat.ipa, 运行工程
将新得到的 WeChat.ipa 重新加入 App 文件(这一步其实可以只加入文件, 而不用加入工程), 删除原来的 Wechat7.0.2 越狱. ipa.
common + R 运行代码, 会发现微信跑起来了, 我们的来了, 老弟也被输出了!
Step 6 新的思考
之前分析了我们创建了 FYHook, 但是没有对 MachO 注入, 得到的答案是来了, 老弟不能被输出, WeChat 能跑起来.
那么如果我们对 MachO 注入 FYHook, 却没有创建对应的 FYHook.framework, 会怎么样呢?
这就留给大家思考, 再去验证了, 有答案的同学也能下方留言, 并说出原因哦.
4, ViewDebug,LLDB,class-dump 分析微信登录页面
Step 1 ViewDebug
XCode 跑起微信之后, 跳转到登录页面, 利用 ViewDebug 查看具体的详细的 UI
可以看到, 登录按钮是一个 FixTitleColorButton 对象, 他的 Target 的名字存在地址 0x280afaa40 中, 他的 Action 名字存在地址 0x280afac00 中. 用同样的方法查看账号密码的输入框, 会发现他们都属于一个对象, 叫做 WCUITextField
Step 2 LLDB
利用 LLDB 查看登录按钮具体的 Target 和 Action 名称
得知: 登录按钮处于 WCAccountMainLoginViewController 这个页面之中 登录按钮的点击方法叫做 onNext
Step 3 class-dump
class-dump, 是可以把 Objective-C 运行时的声明的信息导出来的工具. 其实就是可以导出. h 文件. 用 class-dump 可以把未经加密的 App 的头文件导出来.
点击这下载命令行工具: class-dump https://pan.baidu.com/s/1SfM9rFLTjuNambRdYFVv-w 提取码: icss 同样的, 将 class-dump 拷贝到 Mac 的目录 / usr/local/bin下, 这样我们在终端中就可以使用 yololib 命令了
运行命令将 WeChat 所有的头文件导出来.
- // class-dump -H 「App 的 MachO 文件」 -o 「输入的目录」
- class-dump -H WeChat -o /Users/dengbin/Code/GitHub/HookWeChat/InjectFrameWork/App/WeChat-H
Step 4 找到输入框里面的内容
利用文本工具, 例如 Sublime 查看 WeChat 的头文件, 找到前面发现的 WCAccountMainLoginViewController
发现里面确实有方法 - (void)onNext;, 还有长得很像账号输入框, 密码输入框的对象_textFieldUserNameItem,_textFieldUserPwdItem.
接下来就是找到密码输入框里面的字符串了, 可以发现这两个都是 WCAccountTextFieldItem 对象, 所有我们继续在导出的文件里面找到 WCAccountTextFieldItem
在其中只发现一个 tips 对象 m_labelTip, 没有发现对应的 textfiled, 但是可以看到 WCAccountTextFieldItem 是继承于 WCBaseTextFieldItem 的, 所以继续查找 WCBaseTextFieldItem
从这就可以看到一个 m_textField 对象, 这是个 WCUITextField 对象, 疑似我们的目标 textField, 继续查看 WCUITextField
果然, 这就是一个 UITextField 文件, 那么我们就可以通过 text 字段取出其 string.
接下来在用 LLDB 试试看, 验证下我们的猜想:
随便在账号栏输入: qwerty
然后在密码栏输入: 123456
- po [(WCAccountMainLoginViewController *)0x1128bbc00 valueForKey:@"_textFieldUserPwdItem"]
- po [(WCAccountTextFieldItem *)0x28328e880 valueForKey:@"m_textField"]
- po [(WCUITextField *)0x112163a00 text]
其中第一个地址 0x1128bbc00 是在前两部利用 ViewDubg 找到的.
可以发现最后确实找到了我们输入的密码 123456, 证明我们的分析是正确的.
5,Hook 登录, 自动获取密码
接下来又是代码 Coding 了. 原理分析完, 其实代码就很简单了, 直接上代码:
- + (void)load {
- NSLog(@"来了, 老弟");
- Method onNext = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), sel_registerName("onNext"));
- //1. 保存原始的 IMP
- old_onNext = method_getImplementation(onNext);
- //2.SET
- method_setImplementation(onNext, (IMP)my_next);
- }
- IMP (*old_onNext)(id self,SEL _cmd);
- void my_next(id self,SEL _cmd){
- // 获取密码
- NSString *pwd = [[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"] performSelector:@selector(text)];
- NSString *accountTF = [[[self valueForKey:@"_textFieldUserNameItem"] valueForKey:@"m_textField"] performSelector:@selector(text)];
- NSLog(@"密码是!%@",pwd);
- // 将密码追加在账号栏的后面
- [[[self valueForKey:@"_textFieldUserNameItem"] valueForKey:@"m_textField"] performSelector:@selector(setText:) withObject:[NSString stringWithFormat:@"%@ %@",accountTF,pwd]];
- // 调用原来的方法
- old_onNext(self,_cmd);
- }
稍微解释一下, 在前面我们发现登录的响声事件是 onNext, 所有我们利用 Objective-C 的 Runtime 特性, 对 onNext 进行方法替换, 在响应原有的 onNext 之前, 我们加上我们自己的方法, 比如代码中的, 在账号栏中直接输入密码.
运行后结果如图:
我这用的是 setIMP 和 getIMP 的方式, 对原方法进行 HOOK, 其实方法有多种: 如: class_replaceMethod(),method_exchangeImplementations(), 这里只是举一个例子供大家参考.
这篇文章的所有代码都可以在这下载到: https://github.com/dengbin9009/HookWeChat
6, 总结:
先对 App 重签名, 让 App 能在 XCode 运行起来
利用 yololib 注入 Framework, 让 App 可以运行我们直接的代码
利用 ViewDebug,LLDB,class-dump 分析登录事件和密码框所在位置
利用 Runtime 的 MethodSwizzle,Hook 登录事件
这次只是简单的微信的一个静态页面进行了初步接触, 虽然思路简单, 但这运用到的工具, 却是无数大神前辈们为我们铺好的路, 感谢!
MachO 文件在本文中只是初略的提及, 其实在我们逆向过程中 MachO 是一个至关重要的存在, 如:
对 App 的砸壳, 其实就是对 MachO 解密
所有的方法名, 静态字符串都是存在 MachO 中
App 的架构 (arm64,arm7...) 也是在 MachO 中区分的
App 加载其实也是对 MachO 的一步步操作
...
所以, 在下篇文章, 笔者将会对 MachO 文件进行详细的讲解. 请持续关注, 觉得有帮助的点个收藏, 留言评估了哦.
来源: https://juejin.im/post/5c850fd85188257e8f616bf7