免责声明
-
本文为一篇严肃的学术研究型文章,文中所提及的注入手段仅供 iOS 底层技术的学习与交流,不针对任何公司与 App。
-
为防止读者将本文用于商业或者非法用途,本文不提供任何注入工具与重签名脚本。
-
读者如果将本文用于商业或者非法用途,则所产生的后果由读者自行承担。
非越狱下的代码注入步骤
-
材料与工具
WeChat.ipa
包(已脱壳)class-dump
与Sublime Text
yololib
与MachOView
XCode
与iPhone
- 重签名脚本
-
① 解压
WeChat.ipa
包,获取WeChat
的MachO
文件,使用class-dump
从MachO
文件中导出Objective-C
类的头文件将导出的
Objective-C
类的头文件拖入Sublime Text
中,以备后面分析 App 时查找和使用 -
② 新建
iOS - App
工程InjectCodeDemo
在工程
InjectCodeDemo
的根目录下新建IPA
目录,并将已脱壳的WeChat.ipa
拖入IPA
目录中 -
③ 在工程
InjectCodeDemo
中新建动态库HzpService.framework
和HcgService.dylib
关于
HzpService.framework
:- 新建注入工具类
HzpInjectTool
关于
HcgService.dylib
:- 新建注入工具类
HcgInjectTool
- 将
Target(HcgService) - Build Settings - Architectures - Base SDK
修改为iOS
- 将
Target(HcgService) - Build Settings - Signing - Code Signing Identity
修改为iOS Developer
- 新建注入工具类
-
④ 在工程
InjectCodeDemo
中导入新建的动态库HzpService.framework
和HcgService.dylib
并修改Target(InjectCodeDemo) - Build Phases
-
⑤ 将重签名脚本拷贝到工程
InjectCodeDemo
的根目录下
并在Target(InjectCodeDemo) - Build Phases
中添加New Run Script Phase
注意:
Run Script
的顺序需要在Embed Frameworks
之前
即需要先使用WeChat.app
覆盖InjectCodeDemo.app
,然后再将动态库拷贝到WeChat.app/Frameworks
目录下
-
⑥ 连入
iPhone
真机,并将Target(InjectCodeDemo)
改为真机调试 -
⑦ 注意:
App
安装期间需要保持iPhone
处于开启状态
否则会提示Unlock iPhone to Continue
- 因为
XCode
编译阶段动态库拷贝机制的问题
所以每次在运行 App 之前,都需要使用Command + Shift + K
清空 XCode 缓存
以保证(执行脚本)和(拷贝动态库)之间相对顺序的正确性
否则会提示The file "InjectCodeDemo" couldn’t be opened because you don’t have permission to view it.
使用 ViewDebug 和导出的 Objective-C 类的头文件分析目标 App
-
① 分析登录按钮点击事件
登录按钮的类型为
FixTitleColorButton
,继承自UIButton
点击登录按钮将会触发WCAccountMainLoginViewController
类的onNext
方法
通过class-dump
导出的头文件进行验证,确实在WCAccountMainLoginViewController.h
中找到了-onNext
方法
-
分析账号输入框和密码输入框
账号输入框和密码输入框的类型均为
WCUITextField
,均继承自UITextField
在WCAccountMainLoginViewController.h
中找到了疑似存储账号和密码的成员变量:
存储账号的成员变量:WCAccountTextFieldItem* _textFieldUserNameItem
存储密码的成员变量:WCAccountTextFieldItem* _textFieldUserPwdItem
那么,WCAccountMainLoginViewController
类中存储账号和密码的成员变量(WCAccountTextFieldItem
类型)和ViewDebug
中的账号密码输入框(WCUITextField
类型)之间有什么联系呢?我们尝试在继承链中查找一下:
WCAccountTextFieldItem
继承自WCBaseTextFieldItem
在WCBaseTextFieldItem
中有WCUITextField
类型的m_textField
成员变量
而WCUITextField
又继承自UITextField
,UITextField
可以通过text
属性拿到输入框中的内容
-
理一下登录按钮与账号密码输入框之间的联系
- 点击登录按钮将会触发
WCAccountMainLoginViewController
类的-onNext
方法 - 账号密码输入框是
WCAccountMainLoginViewController
类中两个WCAccountTextFieldItem
类型的成员变量(_textFieldUserNameItem
、_textFieldUserPwdItem
) - 在
-onNext
方法中可以拿到成员变量_textFieldUserNameItem
、_textFieldUserPwdItem
WCAccountTextFieldItem
继承自WCBaseTextFieldItem
WCBaseTextFieldItem
中有WCUITextField
类型的成员变量m_textField
而WCUITextField
又继承自UITextField
UITextField
可以通过text
属性拿到输入框中的内容
- 点击登录按钮将会触发
Objective-C 方法常见的 Hook 方式
-
① 使用 method_exchangeImplementations
备注:
onceToken
应该定义为静态局部变量static dispatch_once_t onceToken;
-
② 使用 class_replaceMethod
备注:
onceToken
应该定义为静态局部变量static dispatch_once_t onceToken;
-
③ 使用 method_getImplementation 和 method_setImplementation
备注:
onceToken
应该定义为静态局部变量static dispatch_once_t onceToken;
-
注意
在
Objective-C
方法中,self
代表当前实例对象或者当前类对象,这种说法不准确
在Objective-C
方法中,self
代表的是当前方法调用者