逆向工程实战手册 第 11 章

第 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 分析

动手练习

  1. 下载一个开源 iOS 应用/示例 IPA,用 class-dump 导出类结构,找一个登录/加密相关类。
  2. 用 Frida Hook 一个 ObjC 方法的参数和返回值。
  3. otool -l 检查一个 IPA 的 cryptid,判断是否加密。
  4. 练习 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