第三十讲:移动应用逆向(iOS)——从IPA到Runtime的全链路分析
iOS逆向,说实话,比Android要麻烦不少。我最早接触iOS逆向是在2015年,那时候越狱设备还比较普遍,现在虽然环境变了,但核心思路没变。这一讲,我会带你走一遍完整的iOS逆向流程——从拿到一个IPA文件开始,到用Frida做动态Hook结束。
IPA文件结构:拆开看看里面有什么
IPA本质上就是个zip包。你直接把后缀改成.zip,解压就能看到内容。我习惯用命令行操作:
unzip target.ipa -d ipa_output
解压后,典型的目录结构是这样的:
| 路径 | 说明 |
|---|---|
| Payload/xxx.app | 核心bundle,里面是Mach-O可执行文件 |
| Payload/xxx.app/Info.plist | 应用元信息,Bundle ID、版本号等 |
| Payload/xxx.app/embedded.mobileprovision | 签名描述文件,包含设备UDID列表 |
| Payload/xxx.app/CodeResources | 代码签名校验文件 |
| iTunesMetadata.plist | App Store下载信息 |
嗯,这里要注意一点:如果你拿到的IPA是加密的(从App Store下载的通常都加密),那Payload里的Mach-O文件是加过密的。你需要先用工具解密,才能进行后续分析。我常用的解密工具是Clutch或者dumpdecrypted。
otool -l xxx | grep crypt查看cryptid字段,1表示已加密。
Mach-O文件格式:iOS可执行文件的骨架
Mach-O是iOS和macOS的原生可执行格式。说白了,它就是一个二进制文件的组织规范。你反编译一个iOS App,本质上就是在解析Mach-O。
Mach-O的结构分为三部分:
- Header:文件头,标识CPU架构、文件类型(可执行/动态库/对象文件)
- Load Commands:加载命令,告诉dyld(动态链接器)如何加载这个文件
- Data:实际代码和数据段,包括__TEXT(代码段)、__DATA(数据段)、__LINKEDIT(链接信息)
我个人习惯用otool和MachOView来查看Mach-O细节。比如查看所有加载命令:
otool -l Payload/xxx.app/xxx
你会看到一堆LC_SEGMENT_64、LC_MAIN、LC_LOAD_DYLIB之类的命令。其中LC_MAIN指定了程序入口点,LC_LOAD_DYLIB列出了所有依赖的动态库。
为什么理解这个很重要?因为你在做逆向时,经常需要定位某个函数的地址,或者修改某个段的权限。不懂Mach-O结构,你连函数在哪个段都找不到。
核心知识点:__TEXT段是只读可执行的,__DATA段是可读写不可执行的。如果你要做代码注入,通常需要修改__DATA段或者新增一个段。
使用Hopper/IDA反编译:把二进制变成可读代码
拿到解密后的Mach-O,下一步就是反编译。我主要用两个工具:
- Hopper Disassembler:轻量级,启动快,适合快速分析。我个人比较喜欢它的伪代码生成功能。
- IDA Pro:重型武器,插件生态丰富,适合复杂分析。但价格贵,启动慢。
操作流程其实差不多:
- 把Mach-O拖进工具,选择ARM64架构(现在基本没有32位的iOS App了)
- 等待自动分析完成,工具会识别函数、字符串、交叉引用
- 从入口点或者关键字符串入手,逐步追踪逻辑
举个例子,你想找某个按钮点击后的处理逻辑。在Hopper里搜索按钮标题对应的字符串,然后查看交叉引用,就能定位到处理函数。
// Hopper伪代码示例
int __cdecl -[ViewController buttonClicked:] (void *self, SEL sel, id arg2) {
r0 = [self someMethod];
if (r0 != 0x0) {
r0 = [self doSomething];
}
return r0;
}
伪代码虽然不一定完全准确,但足够帮你理解逻辑。我一般先看伪代码,需要精确分析时才看汇编。
Shift+E可以导出伪代码为C文件。我经常把关键函数导出,然后对照着看,比在工具里翻来翻去方便多了。
Objective-C/Swift Runtime分析:理解消息机制
iOS逆向绕不开Runtime。Objective-C是动态语言,方法调用本质上是发消息。这意味着你可以在运行时替换方法实现——这就是Hook的基础。
Objective-C的类结构大概是这样:
// 每个Objective-C对象都有一个isa指针,指向它的类
struct objc_object {
Class isa;
};
// 类结构体
struct objc_class {
Class isa; // 元类
Class super_class; // 父类
const char *name; // 类名
long version;
long info;
long instance_size;
struct objc_ivar_list *ivars; // 成员变量列表
struct objc_method_list **methodLists; // 方法列表
struct objc_cache *cache; // 方法缓存
struct objc_protocol_list *protocols; // 协议列表
};
Swift虽然更静态,但和Objective-C混编时,很多类仍然暴露在Runtime中。我分析Swift App时,会先用nm命令查看导出的符号:
nm Payload/xxx.app/xxx | grep "OBJC_CLASS"
这会列出所有暴露的Objective-C类。如果是纯Swift类,符号名会带模块前缀,比如_TtC7MyApp11ViewController。
为什么会这样?因为Swift编译器为了性能,默认会把大部分方法静态化。但只要类标记了@objc或者继承了NSObject,就会暴露在Runtime中。
实战经验:我在分析一个Swift写的社交App时,发现它的网络请求类没有暴露任何方法名。后来我用class-dump导出头文件,发现它用了@objcMembers修饰整个类——这下所有方法都暴露了。所以别急着放弃,多试几种方式。
使用Frida/Theos进行动态Hook:运行时修改行为
静态分析只能看到代码逻辑,动态Hook才能真正验证你的猜想。我主要用两个工具:
- Frida:跨平台,脚本化,适合快速验证。用JavaScript写Hook逻辑,实时注入。
- Theos:越狱设备上的开发套件,可以编译成dylib注入。适合做长期使用的Tweak。
Frida快速上手
Frida的基本用法是attach到进程,然后Hook指定方法。比如我想Hook一个Objective-C方法:
// Frida脚本
if (ObjC.available) {
var className = "ViewController";
var methodName = "- buttonClicked:";
var hook = ObjC.classes[className][methodName];
Interceptor.attach(hook.implementation, {
onEnter: function(args) {
console.log("[+] buttonClicked called");
console.log("[+] self: " + args[0]);
console.log("[+] selector: " + args[1]);
// 可以修改参数
},
onLeave: function(retval) {
console.log("[+] return value: " + retval);
// 可以修改返回值
}
});
}
运行方式:
frida -U -f com.example.app -l hook.js --no-pause
嗯,这里要注意:Frida需要手机端运行frida-server,而且iOS版本和frida-server版本要匹配。我曾经因为版本不匹配,折腾了一下午才发现是这个问题。
Theos制作Tweak
如果你需要长期Hook某个App,用Theos编译一个dylib更稳定。Theos的Tweak语法基于Logos,非常简洁:
%hook ViewController
- (void)buttonClicked:(id)sender {
%log; // 打印调用日志
NSLog(@"[Tweak] buttonClicked called");
// 调用原始实现
%orig;
// 或者完全替换
// UIAlertView *alert = [[UIAlertView alloc] init...];
// [alert show];
}
%end
编译后用dpkg-deb打包成deb,通过Cydia或Sileo安装到越狱设备上。
dyld_insert_libraries环境变量。不过操作起来比较麻烦,我建议初学者先搞一台越狱设备,省心很多。
知识体系总览
下面这张图总结了iOS逆向的核心链路,从IPA文件到动态Hook的完整流程:
这张图把整个流程串起来了。你从IPA出发,经过解密、Mach-O分析,然后分三条路走——静态反编译看代码逻辑,Runtime分析理解消息机制,动态Hook验证和修改行为。三条路最终汇合,帮你彻底理解一个App的内部运作。
iOS逆向是个需要耐心的活。我做了这么多年,每次遇到新App还是会有卡住的时候。但只要你把基础打牢——IPA结构、Mach-O格式、Runtime机制、Hook工具——大部分问题都能找到突破口。嗯,今天就到这里,剩下的靠你自己动手了。
公众号:蓝海资料掘金营,微信deep3321