iOS 数据存储逆向:从 Keychain 到 iCloud 备份

说实话,iOS 的数据存储体系,是我做逆向这么多年觉得最有意思的部分之一。为什么?因为苹果在设计时确实花了很多心思在安全上,但开发者一旦用错,这些防护就形同虚设。今天我们就来聊聊,怎么从逆向的角度,把这些存储的数据「请」出来。

核心观点:iOS 数据存储逆向,本质上就是找到「数据在哪」和「数据怎么加密的」这两个问题的答案。别被 Keychain 吓到,也别小看 Plist 文件——它们各有各的软肋。

iOS 数据存储逆向知识体系 Keychain 逆向 钥匙串访问 + 解密 NSUserDefaults / Plist 明文存储的「重灾区」 CoreData / SQLite 数据库文件提取 文件系统访问 iFunBox / Filza iCloud 备份提取 聊天记录实战 实战:聊天记录 从备份到明文 最终目标:从加密/备份中提取明文数据 🔓 数据提取成功

Keychain 逆向:苹果的保险柜,其实有后门

Keychain 是 iOS 上最安全的本地存储方式,没有之一。它把数据加密后存放在一个独立的数据库里,普通应用根本读不到别的应用的 Keychain 数据。但——注意这个「但」——它并不是无懈可击的。

我个人习惯把 Keychain 的逆向分为三个层次:

  1. 越狱设备:直接用 keychain_dumper 工具,一把梭。我记得第一次用这个工具时,看到微信的会话密钥就那么躺在那里,说实话有点震惊。
  2. 非越狱设备:通过备份提取 Keychain 数据。iTunes 备份里有一个 KeychainDomain 文件夹,里面就是加密后的 Keychain 数据。你需要用 keychain_backup_decrypt 这类工具配合备份密码来解密。
  3. 运行时 Hook:用 Frida 或 Cycript Hook SecItemCopyMatching 等 API,在 App 运行时直接拦截 Keychain 读写操作。

避坑指南:我曾经在分析一个金融 App 时,发现它把用户交易密码直接存进了 Keychain。我当时想:「这总该安全了吧?」结果一查,它用的 kSecAttrAccessible 属性是 kSecAttrAccessibleAlways——也就是说,设备锁屏后也能访问。这相当于把保险柜的钥匙挂在了门上。

这里给出一段 Frida Hook Keychain 的示例代码:

// Hook SecItemCopyMatching 获取 Keychain 数据
Interceptor.attach(Module.findExportByName("Security", "SecItemCopyMatching"), {
    onEnter: function(args) {
        console.log("[*] SecItemCopyMatching called");
        var query = new ObjC.Object(args[0]);
        console.log("\tQuery: " + query.toString());
    },
    onLeave: function(retval) {
        console.log("\tReturn: " + retval);
    }
});

NSUserDefaults 与 Plist 文件:开发者的「便利贴」

说句实话,NSUserDefaults 是我见过最容易被忽视的安全漏洞。很多开发者觉得「用户偏好设置嘛,又不重要」,于是直接把 token、用户 ID 甚至密码明文往里塞。

NSUserDefaults 的数据存在哪?就在 App 的 Library/Preferences/ 目录下,文件名是 你的BundleID.plist。用 iFunBox 或者 Filza 打开,直接就是明文。

存储方式 文件路径 加密情况 逆向难度
NSUserDefaults Library/Preferences/*.plist 无加密 ★☆☆☆☆
自定义 Plist Documents/*.plist 通常无加密 ★★☆☆☆
Keychain /private/var/Keychains/ 硬件加密 ★★★★☆

你想想看,一个 Plist 文件,用 Xcode 的 Property List Editor 或者任何文本编辑器都能打开。我见过最离谱的一次,是一个社交 App 把用户的聊天记录缓存直接写进了 Plist——连 SQLite 都懒得用。

注意:iOS 13 之后,NSUserDefaults 在未越狱设备上可以通过 mdfind 命令搜索到,但读取需要沙盒权限。不过一旦你拿到了备份,这些限制就不存在了。

CoreData / SQLite 数据库提取

CoreData 底层其实也是 SQLite,只不过包了一层对象关系映射。所以逆向 CoreData,本质上就是找 SQLite 文件。

CoreData 的默认存储位置在 Documents/Library/Application Support/ 目录下,文件名通常是 AppName.sqliteModel.sqlite。用 SQLite Browser 或者 sqlite3 命令行工具就能直接打开。

我记得有一次分析一个笔记 App,它的 CoreData 模型里有一个 Note 实体,里面有个 content 字段。我直接用 SELECT * FROM ZNOTE; 就把所有笔记内容拉出来了——包括那些标记为「私密」的笔记。为什么?因为 CoreData 默认不加密。

# 用 sqlite3 命令行提取数据
sqlite3 AppName.sqlite ".tables"
sqlite3 AppName.sqlite "SELECT * FROM ZNOTE LIMIT 10;"

当然,有些 App 会用 SQLCipher 对 SQLite 文件进行加密。这时候就需要动态分析了——Hook 数据库打开时的密码传入点,或者直接 dump 内存中的解密后数据。

文件系统访问:iFunBox 与 Filza

这两个工具,说白了就是 iOS 上的「文件管理器」。但它们的应用场景完全不同:

  • iFunBox:适合非越狱设备。通过 iTunes 的 AFC 服务,可以访问 App 的 Documents 和 Library 目录。我经常用它来快速拉取 Plist 文件和 SQLite 数据库。
  • Filza:越狱设备专用。可以访问整个文件系统,包括 /var/mobile/Containers/Data/Application/ 下的所有 App 沙盒。配合 iFile 的文本编辑器,可以直接查看和修改文件。

实战技巧:用 iFunBox 导出 App 数据时,记得先让 App 运行一下,确保数据已经写入磁盘。有些 App 会在退出时才 flush 数据到文件,这时候你导出的可能是旧版本。

实战:从 iCloud 备份中提取聊天记录

好,终于到了最刺激的部分。假设我们要从 iCloud 备份中提取微信或 WhatsApp 的聊天记录,该怎么做?

整体流程分三步:

  1. 获取 iCloud 备份:iCloud Backup ExtractoriMazing 下载备份文件。注意,iCloud 备份是加密的,你需要 Apple ID 密码或者备份密码。
  2. 解析备份文件:iCloud 备份是一个 .mbdb 格式的数据库,里面记录了所有文件的元信息。用 mbdb 解析工具可以列出所有文件路径。
  3. 提取聊天数据库:微信的聊天记录存在 Documents/ 下的 MM.sqliteWCDB.sqlite 中。WhatsApp 则在 ChatStorage.sqlite 里。

这里有个坑——微信的聊天记录数据库是加密的。它用了 SQLCipher,密码是用户的 UID 加上一些固定字符串拼接而成。怎么破?

嗯,我一般用两种方法:

  • 方法一:从内存中 dump 出 SQLCipher 的密码。用 Frida Hook sqlite3_keysqlcipher_key 函数,在 App 打开数据库时拦截密码。
  • 方法二:如果拿不到密码,就尝试暴力破解。微信的密码生成算法已经被公开,你可以用 Python 实现一遍,然后用 sqlcipher 命令行工具尝试解密。
# 用 sqlcipher 尝试解密微信数据库
sqlcipher MM.sqlite
sqlite> PRAGMA key = '你的密码';
sqlite> PRAGMA cipher_use_hmac = OFF;
sqlite> .tables
# 如果看到表名,说明解密成功

个人经验:我曾经帮一个朋友恢复他去世亲人的微信聊天记录。备份是拿到了,但密码不知道。我花了三天时间,通过分析微信的二进制文件,找到了密码生成逻辑,最终成功解密。那一刻,我觉得逆向工程不只是技术,更是一种「数字考古」——从废墟中还原记忆。

最后说一句:iCloud 备份提取聊天记录这件事,技术上完全可行。但请务必遵守法律法规,不要用于非法目的。我们做逆向的,技术是工具,良知是底线。


公众号:蓝海资料掘金营,微信deep3321