ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

iOS App数据导出与解密实战:从沙盒到SQLCipher分析结构

2026/9/9 16:25:28 拓冰建站 浏览量
iOS App数据导出与解密实战:从沙盒到SQLCipher分析结构 最近有个朋友找我说他接手了一个老项目数据库用 SQLCipher 加密了想导出数据做分析却无从下手。这种问题我以前也踩过不少坑正好借这个机会把 iOS App 数据解密导出到分析结构方法完整梳理一遍。无论你是开发者要查自己 App 的数据还是测试、数据分析岗位需要从 iOS 端拿数据做分析这篇文章都能给你一条能直接落地的路径。先说清楚这个内容能解决什么问题iOS App 的数据拿不到通常有几种情况沙盒机制挡着看不到文件、数据库被 SQLCipher 加密打不开、业务层又做了自定义加密导致导出的内容全是乱码。这篇文章会把“数据到底存哪里”“怎么把沙盒数据弄出来”“加密怎么识别和解除”“最后怎么整理成 CSV、JSON 这些分析友好的结构”全部讲透。而且全程不用越狱也能覆盖大部分场景适合 iOS 开发新手、测试工程师、爬虫逆向入门者以及想从 App 里回收数据做分析的运营同学。1. 先搞清楚数据到底“藏在”哪1.1 iOS 沙盒机制带来的天然屏障iOS 和 Android 最大的不同之一就是每个 App 都有自己独立的沙盒目录。这个目录只有 App 自己能读写其他 App 访问不了你通过电脑上的文件管理器也看不到。系统层面这么做是为了安全和隔离但对开发者来说就有点头疼了——尤其是你想导出数据的时候第一步就卡在“东西到底放哪了”。沙盒路径在真机和模拟器上不一样。在模拟器里路径是这样的~/Library/Developer/CoreSimulator/Devices/设备ID/data/Containers/Data/Application/AppUUID在真机上没有越狱的话你直接通过 Finder 是看不到这个目录的。只能通过 Xcode 的 Devices 窗口、iTunes 备份、或者第三方工具把沙盒内容捞出来。这也是很多人觉得 iOS 数据“拿不到”的根本原因——不是数据不存在而是入口被系统焊死了。我见过不少刚入行的同学上来就问“我的 App 数据库在手机哪个位置”这个问题本身就问错了。正确的问题是“我通过什么路径能把沙盒目录同步到电脑上”。搞懂这个区别后面的事就顺了。1.2 常见数据存储方式与路径在 iOS 里数据存放的位置基本有固定规律。你拿到一个 App 的沙盒目录后不用瞎翻直奔这几个关键路径就行。路径存放内容典型文件格式Documents/用户生成的文档、导出文件、需要持久化的数据.sqlite .db .json .txtLibrary/Preferences/NSUserDefaults 偏好设置.plistLibrary/Application Support/Core Data、SQLite、业务数据.sqlite .store .db .datLibrary/Caches/缓存数据系统可能随时清理.db .tmp .cachetmp/临时文件不保证持久不定绝大多数 App 的核心业务数据都在 Documents 和 Library/Application Support 里面。比如用 Core Data 的 App默认会在 Application Support 下生成一个 .sqlite 文件直接拼 SQLite 的 App一般会把数据库放在 Documents 或 Application Support用 Realm 的则会在 Documents 下放一个 .realm 文件。拿到沙盒目录之后第一件事不是急着解密而是先列一遍目录结构看看有哪些文件、文件大小是多少。文件大小往往能直接透露信息——一个几十 MB 的 .sqlite 文件里面大概率是核心业务表一个几 KB 的 .plist通常只是偏好设置参考价值有限。1.3 “拿不到”是哪种拿不到我在实际工作里遇到过几种典型的“拿不到”每种的原因和解法完全不一样。第一种是开发者自己调试自己的 App。这种最简单用模拟器跑起来后直接就能在 Finder 里打开沙盒目录或者用 Xcode 的 Devices 窗口点 Download Container 一键导出。如果你是这个情况直接看本文第 3 章的模拟器部分就行。第二种是接手别人的项目数据库是加密的。这种场景下你手里通常有源码或者至少有一部分源码可以顺着代码找到密钥和加密逻辑。但现实往往更残酷源码可能不全密钥可能存在钥匙串里加密逻辑可能是第三方 SDK 包了一层黑盒。这种情况最麻烦也是本文重点讲解的部分。第三种是非开发场景比如运营或者数据分析岗位的人要从某台 iPhone 上的 App 里把用户数据导出来做分析。这种人往往没有任何源码甚至不是技术人员但需求很明确——把 App 里的数据变成能导入 Excel 或 BI 工具的结构化数据。这种情况我会推荐备份法加工具导出不需要写代码也能搞定大部分明文数据库的场景。1.4 加密形态分三种识别不了就白忙很多人在第一步就栽了拿着一个加密数据库用常规工具打开直接报错“file is not a database”。然后就开始乱试浪费时间。我建议先搞清楚加密的三种常见形态再对症下药。第一种是 SQLCipher 整库加密。这是 iOS 上最流行的开源数据库加密方案。它本质上是 SQLite 的一个加密分支整个数据库文件从第一页开始就是密文用文本编辑器打开看不到任何“SQLite format 3”的明文头也看不到表名。识别方法是看文件头部正常 SQLite 文件的头 16 字节包含“SQLite format 3”字样而 SQLCipher 加密后的文件头是一串无法解读的随机字节。第二种是系统的 Data Protection 加密。这是 iOS 系统级的功能文件系统层面的加密文件本身还是明文 SQLite但你从备份里导出来之后有可能因为缺少对应的 keybag 而无法读取。这种加密通常发生在 App 设置了 NSFileProtection 属性之后。识别方法是文件头正常但读取时提示无权限或者内容为空。第三种是业务层的自研加密。这是最让人头疼的App 在写入数据库之前自己先把字段内容做了 AES、DES 或者 XOR 加密再存进 SQLite。这种情况下数据库本身是明文格式能打开但表里的数据全是乱码像什么“U2FsdGVkX1abc123”这种 Base64 字符。我见过很多人在没识别加密类型的情况下就盲目尝试结果浪费了大半天。正确的姿势是先做静态识别用file命令、hexdump和strings三个工具花五分钟就能定位是哪种加密形态。2. 工具准备与访问路线选型2.1 三条路线先选对再动手在开始搞数据之前先想清楚一个问题你的设备是什么状态不同状态对应不同的访问路线选错了就是白忙。路线适用场景优点缺点模拟器路线自己开发调试App 跑在 Mac 的 Simulator 里路径直接可见导出最快仅限模拟器无法覆盖真机环境越狱机 Frida/SSH逆向分析、测试环境灵活能动态调试拿密钥需要越狱设备门槛高非越狱备份路线真机环境、无越狱设备最稳适用于绝大多数场景需要 iTunes 备份速度偏慢我的建议是如果你只是自己在开发调试优先用模拟器路线省时省力。如果你需要从真机上导数据优先用备份路线不需要越狱iOS 15、16、17 都能用。只有在你需要动态调试、从内存里挖密钥的时候才需要上 Frida 那套越狱方案。2.2 常用工具清单我整理了一份工具清单都是实际工作中用得上的。不需要全部装按需选就行。Xcode自带simctl模拟器沙盒目录提取靠它。libimobiledevice开源工具集包含idevicebackup2、idevice_id等命令用于真机备份。爱思助手 / iMazing图形化工具新手友好一键导出沙盒和应用数据。sqlite3macOS 自带处理明文 SQLite 必备。sqlcipherSQLCipher 的命令行版本解密加密库靠它。Frida动态插桩工具用于 Hook 函数、抓取密钥。plutilmacOS 自带处理 plist 文件的利器。jqJSON 处理工具导出后整理数据时用。class-dump导出 App 的 Objective-C 类信息逆向分析时用。这些工具大部分是免费的idevicebackup2在 macOS 上通过 Homebrew 安装就行。Frida 稍微特殊一点需要先在电脑上安装frida-tools然后在越狱手机上安装 frida-server两边版本要对应否则连不上。2.3 为什么备份法最稳妥在非越狱场景下备份法是我的首选没有之一。原因很简单它不依赖设备状态不需要特殊权限只要是正常的 iPhone 都能用。iTunes 备份会把 App 的沙盒数据打包进一个备份目录这个目录在 macOS 上有固定的格式。更关键的是如果你在备份的时候设置了加密备份密码连 Keychain 里的数据也能一起导出来。Keychain 里往往存放着数据库的密钥、Token、密码等敏感信息有了这些后面解密你就会从容很多。备份法还有一个额外的好处不干扰原设备。你只是做了一次只读的备份不会在手机上安装任何额外的东西不影响 App 正常运行。对于生产环境的数据导出这个优势非常重要。有一点一定要记住如果备份时不设置加密备份密码那么即使备份成功Keychain 数据也不会包含在内。所以当你发现导出的备份里缺少钥匙串数据时先检查备份是不是加密的。2.4 快速搭建环境环境搭建这件事我在不同电脑上折腾过好几轮这里给出一套最少步骤的搭建方案。前提是你有一台 macOS。安装 libimobiledevicebrew install libimobiledevice安装 frida-toolspip3 install frida-tools然后验证环境是否正常。真机通过 USB 连接 Mac解锁屏幕信任这台电脑然后在终端里执行idevice_id -l如果输出了设备的 UDID说明 libimobiledevice 工作正常。Frida 的验证需要手机端也装了 frida-server并且两边版本一致。执行frida-ps -U能列出手机上的进程列表就说明通了。这里插一句很多新手在装完 libimobiledevice 后发现idevice_id能识别设备但idevicebackup2执行报错大概率是备份目录权限问题。用sudo跑或者确保当前用户对备份目录有读写权限基本就能解掉。3. 完整实操从沙盒到分析结构3.1 拿到沙盒数据的几种姿势模拟器路径最简单。先用xcrun simctl list确认模拟器名字或 UDID然后执行xcrun simctl get_app_container booted com.example.yourapp data这个命令会直接输出沙盒 data 目录的完整路径接到 Finder 里就能看。注意要先保证 App 在模拟器里安装过并且设备是 booted 状态。真机走备份法。先用idevicebackup2做一次全量备份idevicebackup2 backup --full --password yourpassword /path/to/backup--full表示全量备份--password后面跟的是备份的加密密码。如果密码这一步漏了后面会拿不到 Keychain 里的数据。备份完成后在/path/to/backup目录下会生成一堆随机文件名这些文件本质上是备份数据的分片。要找到指定 App 的数据需要用idevicebackup2的另一个功能或者用第三方工具解析。实际操作中我更喜欢用爱思助手这类图形化工具它能在备份后直接列出所有 App 的沙盒文件点击导出就能把整个 Documents 或 Library 目录拉到电脑上。对于不想折腾命令行的同学这个方式最省心。3.2 识别库文件是什么格式拿到沙盒数据后先别急着打开先识别文件格式。我用一个多 MB 的数据库文件举例file database.sqlite如果输出结果是SQLite 3.x database那说明它是明文的 SQLite直接上sqlite3就行。如果输出是“data”或者“application/octet-stream”那就要警惕了很可能是加密文件。再用 hexdump 确认hexdump -C database.sqlite | head -n 2明文 SQLite 的开头是00000000 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 |SQLite format 3.|如果用strings也搜不到任何表名或者字段名那就基本可以断定是 SQLCipher 加密了。我遇到过一个比较“狡猾”的情况文件头不是标准 SQLite 头但用strings能找到一些表名碎片这种是部分加密或者自定义存储格式处理起来要另想办法。3.3 明文 SQLite 直接导出对于明文 SQLite导出其实是水到渠成的事。先把表结构看一遍sqlite3 database.sqlite .schema这个命令会把所有建表语句列出来。分析结构的第一步就是看 schema了解有哪些表、表之间有什么关系、字段类型是什么。看明白了再导数据。导出成 CSVsqlite3 -header -csv database.sqlite SELECT * FROM user_table; user_table.csv-header会把字段名作为第一行输出-csv指定输出格式。导出来之后直接可以用 Excel 打开。导出成 JSON 稍微麻烦一点需要借助工具。我用 Python 写过一个简单脚本逻辑就是查询全部数据然后逐行写入 JSON 数组sqlite3 database.sqlite SELECT * FROM user_table; | python3 -c import sys, json rows [line.strip().split(|) for line in sys.stdin] print(json.dumps(rows, ensure_asciiFalse)) 这种方式适合临时用。如果数据量大或者字段多建议直接用 Python 的 sqlite3 模块读取再序列化成 JSON可控性更强。3.4 SQLCipher 解密实战SQLCipher 的解密流程已经比较成熟了。前提是你得拿到密钥也就是 passphrase。密钥一般来自几种渠道源码里硬编码的字符串、Keychain 中存储的值、服务端下发后缓存在内存中的数据。拿到之后用 sqlcipher 命令行工具操作。sqlcipher 的安装也很简单brew install sqlcipher解密的核心思路不是直接修改原文件而是把加密的库导出到一个新的明文库文件里。这样做的好处是不动原文件万一搞砸了还能重来。操作流程如下sqlcipher encrypted.db进入 sqlite 的交互界面后先设置密钥PRAGMA key your-secret-key;如果密钥正确后续查询就能成功。但有些库是旧版本的 SQLCipher需要先做一次迁移PRAGMA cipher_migrate;接着附加一个新的明文数据库ATTACH DATABASE decrypted.db AS plaintext KEY ;然后把加密库的内容导出到明文库SELECT sqlcipher_export(plaintext);最后分离并退出DETACH DATABASE plaintext; .exit执行完之后decrypted.db就是一份明文 SQLite 数据库用普通的sqlite3工具就能正常打开。我实际测试过几回这个方法对 SQLCipher 4 的默认配置也适用只要密钥正确基本不会出问题。如果你没有密钥那就麻烦多了。暴力破解是理论上可行但现实中不太划算的方式SQLCipher 的密钥派生函数PBKDF2计算成本很高跑字典都嫌慢。更现实的思路是动态调试用 Frida Hook 住sqlite3_key函数在 App 运行的时候把密钥从内存里抓出来。我后面会详细讲这个方法。3.5 自研 AES 解密思路自研加密比 SQLCipher 更灵活也更乱因为它没有统一标准。我在一个项目里遇到过这种情况数据库本身是明文 SQLite但里面的字段值都是类似U2FsdGVkX1...的 Base64 字符串。一看就知道是 OpenSSL 的 AES 加密格式加密的时候带了随机盐解密必须靠正确的密码。这种加密的破解思路分四步走。第一步在 App 的二进制文件或源码里搜索加密相关的字符串。用strings命令扫一遍strings AppBinary | grep -i AES\|CCCrypt\|encrypt\|decrypt如果找到了CCCrypt的调用说明用的是系统自带的 CommonCrypto如果找到 OpenSSL 相关的符号那多半是 AES-256-CBC 或 AES-128-CBC。第二步找密钥和 IV初始化向量。密钥可能硬编码在代码里也可能来自 Keychain还有可能每一轮都随机生成然后存到服务端。对于前两种情况静态分析就够了对于第三种情况就得抓包或者动态调试。第三步写解密脚本。以常见的 AES-256-CBC 为例用 Python 的pycryptodome库就能搞定from Crypto.Cipher import AES from Crypto.Util.Padding import unpad import base64 def decrypt_aes_cbc(encrypted_b64, key, iv): encrypted base64.b64decode(encrypted_b64) cipher AES.new(key, AES.MODE_CBC, iv) plaintext unpad(cipher.decrypt(encrypted), AES.block_size) return plaintext.decode(utf-8) key byour-32-byte-key-here!!!! iv byour-16-byte-iv! print(decrypt_aes_cbc(U2FsdGVkX1xxx..., key, iv))第四步把所有字段都跑一遍解密输出成明文 CSV。这里有个筛选思路可以先批量解密一部分数据看输出是否是可读文本。如果解密失败比如 Unpad 报错多半是 key 或 iv 不对调整后再试。你不需要一次性搞定全表先拿几条数据验证确认没问题再上全量。3.6 转成分析友好的结构数据解密完了最后一步是整理成分析结构。我理解的分析结构不只是一张 CSV而是要让数据“可被查询、可被筛选、可被关联”。我通常按这个顺序来做先导出表结构。无论数据量多大表结构是骨架。这里我做的第一件事永远是导出 schemasqlite3 decrypted.db .schema schema.sql导出全部数据为 CSV每个表一个文件。如果表之间有外键关系文件名用“主表_子表”这种命名方式来标识比如orders.csv、order_items.csv后面分析的时候一眼就能看出来关系。再生成一份 JSON 版本方便程序化处理。数据量大的情况下CSV 在 Excel 里会有行数上限JSON 配合 Python 或 pandas 更实用。最后也是很多人忽略的生成 ER 关系图。虽然标题里写的是“分析结构方法”但我认为表之间的关联关系本身就是最重要的分析结构。你可以根据 schema 手动梳理外键关系然后用graphviz或者drawio画出来。我在实际项目中用 graphviz 画过一次几十行的 dot 代码就能生成一份清晰的关系图比嘴上说十遍都直观。数据导出之前一定要处理好脱敏。手机号、身份证、地址这些敏感字段要么直接删掉要么用***替换。尤其是导出的数据要发给别人或者上传到分析平台的时候脱敏这步不能省。4. 常见问题与排查技巧实录4.1 备份里找不到 App 数据这是我被问过最多的问题。明明做了备份但导出的备份文件里没有目标 App 的沙盒数据。大概率是两个原因。第一个原因是 App 本身设置了“不备份”属性。iOS 开发中有些数据被标记为NSURLIsExcludedFromBackupKey这类数据不会进入 iTunes 备份。如果 App 的数据库存放在这个标记的目录下备份里自然是找不到的。这种时候就别死磕备份了改用 Frida 直接读取沙盒更靠谱。第二个原因是备份没有包含加密数据。确认一下你备份的时候是不是勾选了“加密本地备份”并输入了密码。我犯过这个错第一次做备份时没加密结果 Keychain 数据全部缺失数据库文件还在但钥匙串里的密钥信息全没了。后来改成加密备份数据才完整。4.2 文件打不开显示“not a database”这个报错几乎每个和 SQLite 打交道的人都见过。出现这个报错先不要慌用file命令看下文件类型再用hexdump看文件头。如果文件头不是标准 SQLite 头那可能是 SQLCipher 加密。如果文件头是标准 SQLite 头但仍然打不开那可能是文件损坏。我之前遇到过一次是 App 在写入过程中被杀掉SQLite 的 WAL 文件没有合并导致主库文件不完整。处理方式是找到同目录下的-wal文件把它和主库放在一起再用sqlite3打开SQLite 会自动进行恢复。4.3 密钥找不到密钥是整个解密流程里最关键也最容易卡住的一环。我总结了几种寻找密钥的渠道按性价比排序。第一是搜源码。如果手上有源码直接 grepgrep -r password\|passphrase\|secret\|key --include*.swift --include*.m --include*.h第二是搜二进制文件里的硬编码字符串。用strings配合 grepstrings AppBinary | grep -i key\|pass\|secret第三是用 Frida 动态 Hook。这是最接近“万能解法”的手段。比如想拿 SQLCipher 的密钥可以 Hooksqlite3_key函数if (ObjC.available) { Interceptor.attach(Module.findExportByName(null, sqlite3_key), { onEnter: function(args) { var keyPtr new NativePointer(args[1]); var keyLen args[2].toInt32(); var key keyPtr.readUtf8String(keyLen); console.log(SQLCipher key: key); } }); }用 Frida 启动目标 App 并加载这个脚本App 一调用sqlite3_key密钥就会打印到终端里。这个方法需要越狱环境或者经过重签名的调试版本但对逆向分析来说已经算是最常规的手段了。第四是抓 Keychain。如果密钥存在 Keychain 里在越狱机上用keychain-dumper可以直接导出来。非越狱机上就只能靠加密备份加合理推导了。4.4 导出数据中文乱码CSV 用 Excel 打开中文乱码这是 Windows 和 macOS 编码差异的经典问题。Excel 默认用 GBK 编码打开 CSV而 sqlite3 默认导出的是 UTF-8。解决方法有两个。一个是导数据之前先转换编码iconv -f UTF-8 -t UTF-8-BOM user_table.csv user_table_bom.csv加上 BOM 之后Excel 就能识别编码了。另一个是干脆不用 CSV改用 Excel 原生支持的格式比如直接导成 xlsx。Python 的pandas库一行代码就能做到import pandas as pd df pd.read_csv(user_table.csv) df.to_excel(user_table.xlsx, indexFalse)从实际体验来说数据量大或者字段多的时候xlsx 比 CSV 稳得多不会出现字段被截断或者编码错乱的问题。4.5 备份密码忘了怎么办加密备份如果忘了密码那 Keychain 数据基本等于丢了没什么好办法恢复。但沙盒数据还有救。备份文件本身虽然没有明文 Keychain但沙盒目录下的文件未必全部加密。你可以尝试用idevicebackup2的--unencrypted选项重新做一次备份或者通过第三方工具直接浏览备份内容。我在实际项目中还踩过一个坑备份的时候设了密码但后来想用明文方式读取备份里的文件却被系统拦住了。后来发现idevicebackup2有--decrypt相关操作可以用备份密码把备份解密成明文目录。具体命令在不同版本上略有差异建议先看idevicebackup2 --help的输出。我个人的经验是备份密码这玩意儿一定要记好。如果项目涉及长期数据导出最好在项目文档里留一份加密备份的说明密码单独放一个安全的密码管理工具里不要贴在聊天记录里。我自己在实际操作中的体会是iOS App 的数据导出这件事百分之八十的问题都出在“没识别清楚数据形态”上。拿到一个文件先花几分钟看清楚它是明文 SQLite、SQLCipher 还是自研加密后面的路就顺了。最后再分享一个小技巧不管数据量多大先导出 schema 看表结构比一上来就全表 dump 高效得多——结构清楚了你知道每一张表里装的是什么再去取数就有方向了。这一套流程我在不下十个项目里验证过不能说百分之百覆盖所有情况但至少能帮你避开大部分常见的坑。