iOS逆向(二):Hopper/IDA for iOS、静态分析Objective-C/Swift
好,咱们接着聊iOS逆向。上一章我们把环境搭好了,工具也装上了。这一章,咱们要真刀真枪地干活了——用Hopper和IDA去分析一个iOS的Mach-O文件。
说实话,我第一次用Hopper反编译一个Objective-C的App时,整个人是懵的。满屏的objc_msgSend,各种奇怪的selector,根本不知道从哪下手。后来摸爬滚打了一段时间,才慢慢理清了门道。今天我就把这些经验掰开揉碎了讲给你听。
为什么是Hopper和IDA?
这两个工具,说白了就是逆向工程师的“显微镜”。
- Hopper:macOS原生,轻量级,对Objective-C和Swift的支持非常好。我个人习惯用它做快速分析。
- IDA Pro:重型武器,插件生态丰富,反编译能力更强。遇到加壳或者混淆严重的App,我会上IDA。
你可能会问:“我该学哪个?”我的建议是:两个都装。Hopper用来日常分析,IDA用来攻坚克难。
静态分析的核心:看懂Mach-O
iOS的App打包后是一个.ipa文件,解压后里面是Payload文件夹,里面就是Mach-O可执行文件。静态分析,就是直接分析这个二进制文件,不运行它。
嗯,这里要注意:Mach-O的结构和PE(Windows)或者ELF(Linux)不太一样。它有几个关键部分:
| 部分 | 说明 |
|---|---|
| Header | 文件头,标识架构、类型等 |
| Load Commands | 加载命令,告诉dyld如何加载这个文件 |
| __TEXT | 代码段,只读,包含可执行指令 |
| __DATA | 数据段,可读写,包含全局变量、Objective-C的类信息等 |
| __LINKEDIT | 链接信息,符号表、字符串表等 |
我在项目中遇到过一个问题:一个App在iOS 14上运行正常,iOS 15上就闪退。用Hopper一看,发现它调用了私有API,而iOS 15把这个API移到了__TEXT段之外。这就是静态分析的价值——不用跑代码,就能发现问题。
Hopper实战:分析Objective-C代码
咱们拿一个简单的Objective-C方法举例。假设我们反编译后看到这样的伪代码:
// Hopper 反编译结果
int -[ViewController loginWithUsername:password:] {
var_20 = arg2; // username
var_18 = arg3; // password
r0 = objc_retain(arg2);
r0 = objc_retain(arg3);
// 检查用户名和密码是否为空
if (var_20 == 0x0) {
r0 = objc_msgSend(*(_objc_class *)(self + 0x10), @selector(showAlert:));
return 0x0;
}
// 调用网络请求
r0 = objc_msgSend(*(_objc_class *)(self + 0x18), @selector(postRequest:url:));
return r0;
}
你看,Objective-C的方法调用在二进制层面就是objc_msgSend。第一个参数是self,第二个参数是selector。selector是一个字符串,在__DATA段的objc_selrefs区域可以找到。
我个人习惯的做法是:先找到所有objc_msgSend的调用,然后提取出selector字符串。这样就能快速了解这个App有哪些功能。
关键技巧:在Hopper中,按Shift + M可以快速跳转到objc_msgSend的调用点。按Ctrl + X可以查看交叉引用。
IDA Pro:处理Swift的利器
Swift的二进制比Objective-C复杂得多。因为Swift的类名和方法名会被mangling(名字改编),比如:
// Swift 源码
class UserManager {
func login(username: String, password: String) -> Bool {
// ...
}
}
// 编译后的符号名
_$s11MyAppModule12UserManagerC5login8username8passwordSbSS_SStF
这个符号名看着就头疼。但IDA Pro有Swift demangler插件,可以把它还原成可读的形式。
我曾经分析过一个用Swift写的金融App。它的核心逻辑都在一个叫TransactionService的类里。IDA反编译后,我看到了这样的伪代码:
// IDA 反编译结果(已demangle)
bool TransactionService.validateTransaction(amount: Double, currency: String) {
if (amount <= 0.0) {
return false;
}
// 检查金额是否超过限额
if (amount > 10000.0) {
// 调用风控接口
RiskControl.check(amount, currency);
return false;
}
// 正常交易
return true;
}
你看,虽然Swift的二进制很乱,但反编译后逻辑还是很清晰的。关键是要找到正确的入口点。
提示:在IDA中,按Alt + T可以搜索字符串。Swift的字符串通常以_Tt开头,后面跟着模块名和类名。
静态分析的通用流程
不管用Hopper还是IDA,我一般按这个流程走:
- 查看字符串表:先看所有硬编码的字符串,比如URL、API Key、错误提示等。这能快速了解App的功能。
- 定位关键类:搜索
AppDelegate、ViewController、Manager等关键词。 - 分析objc_msgSend:提取所有selector,建立方法调用关系图。
- 追踪数据流:从输入点(比如按钮点击)开始,跟踪数据流向。
- 识别加密/混淆:查找常见的加密函数(如AES、RSA)或者混淆特征(如花指令)。
说白了,静态分析就是“读代码”。只不过读的不是源码,是二进制。你需要把机器码还原成逻辑。
避坑指南
我曾经踩过的坑:
- 不要完全相信反编译结果:Hopper和IDA的反编译不是100%准确的。尤其是优化过的代码,可能会丢失一些变量名和逻辑分支。
- 注意ARC的影响:Objective-C的ARC会在二进制中插入大量的retain/release调用。这些调用会干扰你的分析,要学会忽略它们。
- Swift的协议方法:Swift的协议方法在二进制中可能没有直接的调用点,而是通过虚函数表(vtable)调用的。这时候需要分析vtable的结构。
知识体系图
下面这张图总结了本章的核心知识结构:
这张图把静态分析的几个维度串起来了。你分析的时候,就按这个框架来,不会乱。
写在最后
静态分析是逆向的基础功。说白了,就是“读代码”的能力。你读得越多,就越熟练。我刚开始的时候,一个简单的登录功能要分析半天。现在,十分钟就能摸清一个App的大致结构。
嗯,这一章的内容就到这里。记住:工具只是辅助,真正的功夫在于你对二进制和语言特性的理解。多练,多看,慢慢就有感觉了。