75、iOS逆向(四):Cycript/FLEXible使用、运行时分析
好,咱们继续聊iOS逆向。前面几章我们把工具链搭起来了,也看了Mach-O和Objective-C的底层结构。今天要聊的,是真正让我觉得「逆向有意思」的部分——运行时分析。
说白了,就是App跑起来之后,我们怎么动态地看它、改它、甚至玩它。我个人觉得,动态分析比静态分析爽多了。静态分析就像看一张地图,动态分析就像亲自开车上路。你想想看,哪个更刺激?
Cycript:一个让你和App「对话」的工具
Cycript,这名字你可能听过。它本质上是JavaScript + Objective-C的混合体。你可以把它理解成一个REPL(交互式解释器),挂到正在运行的App进程上,然后直接敲代码。
我记得第一次用Cycript的时候,感觉就像开了上帝视角。你敲一句 UIApp.keyWindow.recursiveDescription(),整个界面层级就全出来了。这在调试UI布局时简直是神器。
安装与注入
Cycript的安装其实很简单。你从官网下载或者用Homebrew装都行。但关键一步是注入到目标进程。
# 找到进程PID
ps aux | grep YourApp
# 注入
cycript -p PID
嗯,这里要注意:越狱设备上才能直接注入。非越狱环境需要配合Frida或者自己写dylib注入。我在项目中遇到过非越狱机调试的情况,那会儿折腾了好几天,最后还是用Frida的spawn方式搞定的。
常用操作速查
一旦进入Cycript的交互环境,你就可以开始玩了。我列几个最常用的操作:
| 操作 | 代码示例 | 说明 |
|---|---|---|
| 查看当前控制器 | UIApp.keyWindow.rootViewController |
拿到最顶层的VC |
| 递归打印视图 | UIApp.keyWindow.recursiveDescription() |
所有子视图层级 |
| 查找某个类的实例 | choose(NSClassFromString("YourClass")) |
返回所有存活实例 |
| 调用任意方法 | [instance someMethod] |
直接OC语法调用 |
| 修改属性值 | instance.property = newValue |
运行时改值 |
我个人最常用的是 choose() 函数。为什么?因为很多时候你根本不知道某个对象存在哪里,但你知道它的类名。比如你想找一个叫 LoginManager 的单例,直接 choose(LoginManager) 就出来了。省去了你翻遍整个Mach-O找引用的时间。
ObjectiveC.classes 列出所有已加载的类。然后配合 grep 过滤。比如 Object.keys(ObjectiveC.classes).filter(function(k) { return k.indexOf('Login') != -1 })。
FLEXible:可视化调试的瑞士军刀
Cycript虽然强大,但毕竟是个命令行。有时候你更想「看」而不是「敲」。这时候FLEXible就登场了。
FLEXible是Flipboard开源的一个工具,后来被社区改造成了FLEX。它本质上是一个浮窗,点开之后能看到App运行时的所有信息:视图层级、网络请求、UserDefaults、文件系统、甚至Core Data数据库。
集成方式
在越狱设备上,你可以直接用Cydia装FLEXible。非越狱环境,我建议用CocoaPods集成到Debug配置里:
pod 'FLEX', :configurations => ['Debug']
然后在App启动时调用:
// AppDelegate.m
#ifdef DEBUG
#import <FLEX/FLEXManager.h>
#endif
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
#ifdef DEBUG
[[FLEXManager sharedManager] showExplorer];
#endif
return YES;
}
嗯,这里有个坑。我曾经在Release包忘了去掉这个宏,结果App上线后用户能看到调试浮窗……那场面,你懂的。所以一定要确认只在Debug模式下启用。
FLEXible能做什么?
点开FLEX浮窗,你会看到一个菜单。我挑几个最实用的功能说说:
- View Hierarchy:3D视图层级。你可以旋转、缩放,看每个View的frame、约束、甚至背景色。比Cycript的recursiveDescription直观一百倍。
- Network History:所有网络请求的列表。点进去能看到URL、Header、Body、响应时间。我在分析一个加密API时,就是靠这个功能抓到了未加密的请求参数。
- User Defaults:直接编辑NSUserDefaults。改个开关值、调个服务器地址,不用重启App。
- File Browser:浏览App沙盒里的所有文件。Documents、Library、tmp,随便看。
- Heap Objects:列出所有存活的对象实例。和Cycript的
choose()类似,但带内存地址和引用计数。
运行时分析:从「看」到「改」
工具只是手段。真正的核心是运行时分析的思路。我总结了一个三步法:
- 观察:用FLEXible看界面层级、网络请求、UserDefaults。找到可疑的类或方法。
- 验证:用Cycript注入,调用那个方法、查看返回值、修改属性。确认你的猜测。
- 利用:写一个Tweak或者Hook脚本,在App启动时自动修改行为。
举个例子。我曾经分析一个金融App的登录逻辑。先用FLEXible看到登录按钮的响应方法是 loginButtonTapped:。然后用Cycript注入:
// 找到当前控制器
var vc = UIApp.keyWindow.rootViewController;
// 调用登录方法,传入一个假的事件对象
[vc loginButtonTapped:nil];
结果发现它直接跳过了输入验证,弹出了登录请求。这说明验证逻辑不在这个方法里,而是在更早的输入框代理方法中。顺着这个思路,我找到了真正的验证代码。
为什么会这样?因为很多开发者习惯把UI事件和业务逻辑混在一起。你从UI入手,往往能发现他们「偷懒」留下的痕迹。
避坑指南
最后,说几个我踩过的坑:
- Cycript注入后App崩溃:最常见的原因是调用了不存在的selector。记得先用
respondsToSelector:检查一下。 - FLEXible浮窗不显示:检查是否在非主线程调用了
showExplorer。UI操作必须在主线程。 - 修改属性后界面不刷新:有些View不会自动重绘。手动调用
setNeedsLayout或setNeedsDisplay试试。 - 非越狱设备无法注入:用Frida的
frida-trace或者自己写dylib通过DYLD_INSERT_LIBRARIES注入。
我曾经在分析一个直播App时,用Cycript改了某个View的alpha值,结果画面直接卡死了。后来发现那个View的drawRect方法里做了大量计算,每次修改都会触发重绘。嗯,所以改之前最好先看看那个类的实现复杂度。
知识体系总览
这一章的内容,我画了一张图帮你理清思路:
这张图把三个工具的关系和各自的能力范围画清楚了。Cycript和FLEXible是「观察」和「验证」阶段的主力,而运行时分析是贯穿始终的方法论。
好了,这一章就到这里。工具只是手段,关键是思路。下次你拿到一个App,别急着反编译,先跑起来,用Cycript和FLEXible「摸一摸」它的脾气。你会发现,很多秘密就藏在运行时里。