第 11 章 iOS 逆向
iOS 逆向的难点在于闭源生态:App Store 二进制加密(FairPlay)、无符号就不给跑、ObjC/Swift 运行时结构。但 ObjC 的元数据也成了逆向的"金矿"——类结构直接可导出。
📍 知识点地图 | 主题:iOS逆向 | 前置:第6章 | 后续:第48章 | 核心概念:Mach-O、class-dump、ObjC runtime、Frida
11.1 前置概念
IPA = ZIP: Payload/AppName.app/
├── AppName # 主二进制(Mach-O)
├── Info.plist # 配置(ATS/URL Scheme/权限)
├── Frameworks/ # 动态库
└── .dSYM 不在 IPA 内(符号在服务端)
App Store 二进制: cryptid=1(加密)→ 需解密
企业包/开发包: cryptid=0(明文)
11.2 完整工作流(四阶段)
Phase 1: 信息收集
# 获取 IPA
# ipatool / Apple Configurator / 越狱机 frida-ios-dump
# 解密 App Store 二进制(需越狱机)
frida-ios-dump -u com.example.app
# 或 Clutch / bfdecrypt
# 静态侦察
class-dump -H AppName -o headers/ # 导出全部 ObjC 类(金矿!)
otool -L AppName # 动态库依赖
otool -l AppName | grep -A5 LC_ENCRYPTION_INFO # 看 cryptid
jtool2 -d AppName # Mach-O 详细信息
plutil -p Info.plist # 读配置
Phase 2: 静态分析
# ObjC 类结构(class-dump 产物)
grep -r "sign\|encrypt\|login" headers/
# Swift 符号还原
xcrun swift-demangle < mangled_symbols.txt
# Ghidra / Hopper 反编译主二进制
# 关注 __TEXT:__objc_methname 节区的 selector 字符串
# 交叉引用 selector → 找实现函数
ObjC 运行时逆向要点:
objc_msgSend(receiver, selector, ...) 是唯一分发机制
RDI = self (receiver)
RSI = selector (方法名字符串)
→ 所有调用点都是 xref 线索,顺着 selector 找实现
防篡改检测: method_exchangeImplementations / class_replaceMethod
Phase 3: 动态分析(Frida)
// Hook ObjC 方法
var cls = ObjC.classes.ClassName;
Interceptor.attach(cls['-methodName:'].implementation, {
onEnter(args) {
console.log("self=" + ObjC.Object(args[0]));
console.log("arg=" + ObjC.Object(args[2]));
},
onLeave(ret) {
console.log("ret=" + ObjC.Object(ret));
}
});
// 枚举类找目标
for (var cn in ObjC.classes) {
if (cn.toLowerCase().includes("crypt")) console.log(cn);
}
// 主线程调度
ObjC.schedule(ObjC.mainQueue, function(){ console.log("main thread"); });
Phase 4: 网络分析
Burp/mitmproxy 抓 HTTPS → 先过证书信任
SSL Pinning 绕过: Objection / Frida / SSL Kill Switch 2
11.3 常见绕过速查
SSL Pinning
objection -g "com.example.app" explore
ios sslpinning disable
越狱检测
objection -g "com.example.app" explore
ios jailbreak disable
检测点:fopen("/bin/bash")、Cydia 路径、sysctl 调试器检测、dylib 白名单。
反调试(PT_DENY_ATTACH)
程序调用 ptrace(PT_DENY_ATTACH) 拒绝调试器
绕过: Frida spawn 早期 hook ptrace / 或修改二进制
11.4 Swift 逆向要点
| 结构 | 逆向要点 |
|---|---|
| 符号 | xcrun swift-demangle 还原 |
| String | 小字符串内联(≤15 字节),大字符串堆上 |
| 动态分发 | Protocol Witness Table(类似 vtable) |
| 关键 runtime | swift_allocObject / swift_release / swift_once |
11.5 Mach-O 分析关键命令
file AppName # fat/架构
lipo -info AppName # fat 里有哪些架构
otool -l AppName # load commands(找 LC_ENCRYPTION_INFO)
otool -L AppName # 依赖库
otool -tV AppName # 反汇编
nm AppName # 符号
jtool2 --ent AppName # entitlements
codesign -dvv AppName # 签名信息
codesign -f -s - AppName # ad-hoc 重签(修改后)
11.6 修改后重签
# 去掉签名(解包到 Payload)
codesign --remove-signature AppName
# 修改二进制/资源
# ad-hoc 重签
codesign -f -s - AppName
# 重新打包
zip -r new.ipa Payload/
# 安装: 需签名证书或越狱设备
11.7 无越狱机方案(Frida Gadget)
1. 解密 IPA(frida-ios-dump)
2. 注入 FridaGadget.dylib(objection patchipa)
3. 重新签名
4. 安装 → 无需越狱即可 Hook
11.8 dyld 与动态链接
DYLD_INSERT_LIBRARIES 可注入(hardened runtime 会阻止,系统二进制有 SIP)
dyld 共享缓存: 所有系统框架都在一个缓存里
→ 用 dsc_extractor / Ghidra 的 dyld cache loader 分析
动手练习
- 下载一个开源 iOS 应用/示例 IPA,用 class-dump 导出类结构,找一个登录/加密相关类。
- 用 Frida Hook 一个 ObjC 方法的参数和返回值。
- 用
otool -l检查一个 IPA 的 cryptid,判断是否加密。 - 练习 Info.plist 分析:ATS 配置、URL Scheme 能说明什么?
深入阅读
- 仓库:
skills/mobile-reverse/SKILL.md(iOS 逆向四阶段方法论) - 仓库:
skills/mobile-reverse/references/ios-reverse-guide.md(iOS 专项深度) - 仓库:
skills/mobile-reverse/references/frida-objection-deep.md(Frida/Objection 深度) - 仓库:
skills/mobile-reverse/references/anti-detection-bypass.md(越狱/反调试绕过) - 仓库:
skills/reverse-engineering/platforms.md(Mach-O 格式、ObjC/Swift 运行时、dyld) - 实战案例:
skills/field-journal/seed-009_ios-jailbreak-detect-bypass.md