74、iOS逆向(三):Theos开发、Tweak编写、Logos语法
好,咱们继续聊iOS逆向。前两章我们把环境搭好了,也摸清了Mach-O和动态库的底细。这一章,我带你真正上手——写一个能用的Tweak。
说白了,Tweak就是给别人的App打补丁。你想改它的界面、改它的逻辑、甚至改它的网络请求,都可以。而Theos,就是我们用来干这活的工具箱。
Theos:逆向工程师的瑞士军刀
Theos是什么?它是一个跨平台的iOS开发套件。你可以在macOS上用它编译出iOS上的dylib(动态库)。我最早接触Theos是在越狱社区,那时候想改个微信红包的提示音,折腾了一整天。现在回想起来,其实核心就几个命令。
安装Theos
我个人习惯用Homebrew来装依赖,然后直接从GitHub拉Theos源码。你跟着我来:
# 安装依赖
brew install ldid xz
# 拉取Theos
git clone --recursive https://github.com/theos/theos.git $THEOS
# 设置环境变量(建议写进.zshrc)
export THEOS=~/theos
export PATH=$THEOS/bin:$PATH
嗯,这里要注意:$THEOS这个路径别搞错了。我曾经见过有人把Theos装到/opt下,结果权限问题折腾了半天。直接放用户目录下最省心。
创建第一个Tweak项目
装好之后,我们来建个项目。Theos提供了一个叫nic.pl的模板工具,交互式创建项目:
nic.pl
你会看到一堆选项。选第11项:iphone/tweak。然后填项目名、包名、作者、你要hook的App Bundle ID。比如我们想改微信:
| 字段 | 示例值 |
|---|---|
| Project Name | WeChatTweak |
| Package Name | com.yourname.wechattweak |
| Author | 你的名字 |
| Bundle ID | com.tencent.xin |
生成后的目录结构长这样:
WeChatTweak/
├── Makefile # 编译配置
├── Tweak.x # 核心代码(Logos语法)
├── control # 包信息(Cydia用)
└── WeChatTweak.plist # 作用范围配置
Filter字段里加多个Bundle ID。我经常这么干,比如同时hook微信和QQ。
Logos语法:让hook变得优雅
你可能会问:为什么不用纯C的MSHookFunction?当然可以,但Logos语法更简洁。它是一套预处理指令,编译时会自动转换成底层的MSHookMessageEx和MSHookFunction调用。
说白了,Logos就是一层语法糖。但这一层糖,能让你少写80%的样板代码。
核心指令
我整理了一张表,把最常用的几个指令列出来:
| 指令 | 作用 | 示例 |
|---|---|---|
| %hook | 开始hook一个类 | %hook MMViewController |
| %end | 结束hook | %end |
| %log | 打印方法调用日志 | %log; |
| %orig | 调用原始方法 | %orig; |
| %new | 添加新方法 | %new |
| %ctor | 构造函数(初始化) | %ctor { ... } |
| %dtor | 析构函数(清理) | %dtor { ... } |
实战:Hook一个OC方法
我们拿微信的登录界面举个例子。假设我想在用户点击登录按钮时,弹个自己的提示框。先找到目标方法——用class-dump或者FLEXible一查,发现LoginViewController里有个onLoginBtnClicked:方法。
好,开干:
%hook LoginViewController
- (void)onLoginBtnClicked:(id)sender {
%log; // 打印调用栈,调试用
NSLog(@"用户点了登录按钮!");
// 这里可以加自己的逻辑
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提示"
message:@"你被我hook了"
preferredStyle:UIAlertControllerStyleAlert];
[[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alert animated:YES completion:nil];
%orig; // 调用原始方法,让登录继续执行
}
%end
你看,就这么几行。你想想看,如果用纯C的MSHookMessageEx,你得先拿Method、再替换IMP、还得保存原IMP……Logos把这些脏活全包了。
%end!我刚开始用Logos时,经常漏掉这个,结果编译报错半天找不到原因。每个%hook必须配一个%end,就像写函数必须配大括号一样。
%new:给类添加新方法
有时候你不光想hook,还想给这个类加个新方法。比如给LoginViewController加个myCustomMethod:
%hook LoginViewController
%new
- (void)myCustomMethod {
NSLog(@"这是我添加的新方法!");
}
%end
加了%new标记后,Theos会在运行时把这个方法动态添加到类里。嗯,这里要注意:新方法的名字别跟系统方法冲突,否则会出奇怪的问题。我一般加个前缀,比如my_或custom_。
编译与部署
代码写完了,怎么跑起来?Theos的编译命令很简单:
make package install
这条命令会做三件事:
- 编译:把Tweak.x编译成dylib
- 打包:生成deb包(Cydia的安装包格式)
- 安装:通过SSH传到手机,然后用dpkg安装
前提是你要在Makefile里配好手机IP和SSH端口:
export THEOS_DEVICE_IP = 192.168.1.100
export THEOS_DEVICE_PORT = 22
make package只打包,然后用scp手动传deb到手机,再用dpkg -i安装。虽然多两步,但稳得多。
Logos底层原理(简单聊聊)
你可能会好奇:Logos到底是怎么工作的?其实它就是个预处理器。编译时,Theos会先扫描.x文件,把%hook、%orig这些指令翻译成Objective-C runtime的API调用。
比如上面的%hook LoginViewController,最终会被展开成类似这样的代码:
// 伪代码,实际更复杂
IMP originalIMP = class_getMethodImplementation(objc_getClass("LoginViewController"), @selector(onLoginBtnClicked:));
IMP newIMP = imp_implementationWithBlock(^(id self, SEL _cmd, id sender) {
// 你的代码
((void(*)(id, SEL, id))originalIMP)(self, _cmd, sender);
});
method_setImplementation(class_getInstanceMethod(objc_getClass("LoginViewController"), @selector(onLoginBtnClicked:)), newIMP);
说白了,Logos就是帮你省掉了这些runtime的脏活。你只需要关注业务逻辑,剩下的交给工具链。
SVG:Tweak开发流程
我画了张图,把整个Tweak从开发到生效的流程串起来。你一看就明白:
这张图把流程分成了6步。你注意看第2步到第5步——Logos语法在编译时被翻译成runtime代码,而真正的hook动作发生在运行时,由Cydia Substrate(或libhooker)完成。这两个阶段很多人搞混,其实一个是编译期,一个是运行期,完全不冲突。
调试技巧
写Tweak最怕什么?怕崩溃。一旦hook错了方法,App直接闪退,你连日志都看不到。我分享几个我常用的调试手段:
- %log + syslog:在方法开头加
%log;,然后用syslog | grep yourTweak看日志。这是最笨但最有效的方法。 - 用FLEXible动态调试:在Tweak里注入FLEXible,运行时直接查看UI层级和对象属性。我遇到界面布局问题时,全靠它。
- 先hook空方法:不确定方法名对不对?先写个空hook,只调
%orig,不崩再往里加逻辑。
写在最后
这一章的内容其实就三个核心:Theos怎么用、Logos怎么写、Tweak怎么跑。你只要把%hook、%orig、%new这三个指令玩熟,就能应付90%的场景了。
我个人觉得,Tweak开发是iOS逆向里最有成就感的一环。你想想看,别人的App,你几行代码就能改它的行为——这种掌控感,是纯阅读代码给不了的。下一章我们会深入更底层的hook技术,比如fishhook和substrate的底层原理。但今天,先把Tweak跑起来再说。
公众号:蓝海资料掘金营,微信deep3321