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 NameWeChatTweak
Package Namecom.yourname.wechattweak
Author你的名字
Bundle IDcom.tencent.xin

生成后的目录结构长这样:

WeChatTweak/
├── Makefile          # 编译配置
├── Tweak.x           # 核心代码(Logos语法)
├── control           # 包信息(Cydia用)
└── WeChatTweak.plist # 作用范围配置
小技巧: 如果你要hook多个App,可以在plist的Filter字段里加多个Bundle ID。我经常这么干,比如同时hook微信和QQ。

Logos语法:让hook变得优雅

你可能会问:为什么不用纯C的MSHookFunction?当然可以,但Logos语法更简洁。它是一套预处理指令,编译时会自动转换成底层的MSHookMessageExMSHookFunction调用。

说白了,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

这条命令会做三件事:

  1. 编译:把Tweak.x编译成dylib
  2. 打包:生成deb包(Cydia的安装包格式)
  3. 安装:通过SSH传到手机,然后用dpkg安装

前提是你要在Makefile里配好手机IP和SSH端口:

export THEOS_DEVICE_IP = 192.168.1.100
export THEOS_DEVICE_PORT = 22
避坑指南: 我曾经因为WiFi信号不好,安装时断连,结果手机上的Cydia Substrate崩了,所有Tweak都失效。后来我学乖了——先用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从开发到生效的流程串起来。你一看就明白:

Tweak开发与生效流程 1. 编写Tweak.x %hook / %orig / %new 2. Theos编译 Logos → runtime API 3. 生成deb make package 4. 安装到手机 SSH / scp / dpkg -i 5. Cydia Substrate 进程启动时注入dylib 6. Hook生效 %orig 调用原方法 关键点:Logos语法在编译阶段被翻译为Objective-C runtime调用 运行时由Cydia Substrate负责注入和替换方法实现

这张图把流程分成了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,不崩再往里加逻辑。
警告: 千万别在生产环境App上试未经验证的Tweak!我有个朋友,直接在微信主号上测试,结果hook了消息发送方法,导致所有消息发不出去,被朋友骂惨了。建议用越狱手机+小号测试。

写在最后

这一章的内容其实就三个核心:Theos怎么用、Logos怎么写、Tweak怎么跑。你只要把%hook%orig%new这三个指令玩熟,就能应付90%的场景了。

我个人觉得,Tweak开发是iOS逆向里最有成就感的一环。你想想看,别人的App,你几行代码就能改它的行为——这种掌控感,是纯阅读代码给不了的。下一章我们会深入更底层的hook技术,比如fishhook和substrate的底层原理。但今天,先把Tweak跑起来再说。


公众号:蓝海资料掘金营,微信deep3321