2026年“美亚杯”手机数据库专题
原创
小谢
小谢
小谢取证
2026年9月30日 17:42
上海
在小说阅读器读本章
去阅读
在公众号小说中沉浸阅读
AI 取证实战 · 第 20 期
美亚杯手机数据库专题:iPhone 与安卓的库、表、字段一次讲清
从 Photos.sqlite 到 WhatsApp,从 Cocoa 时间戳到联表查询——把五年真题反复考的那些库、表和字段摊开讲,再附四张考场速查表
这篇是工具型长文,建议收藏当手册用。第 1—2 章讲清概念和找库的路径,第 3—5 章逐个拆解五届考过的库与表,第 6—8 章讲时间戳、联表查询和够用的 SQL,第 9 章把五届 87 道数据库题按届逐题还原,第 10 章把五年的考点做成地图(15 个考点谁年年考、谁刚成形),第 11 章是四张速查表(按平台、按问法、按字段反查),第 12—14 章给易错点清单、四级能力自测和五周练习路线。
全文所有库名、表名、字段名与真题问法,均取自 2021—2025 五届题解原文,涉及具体题目的地方都标了届次+题号,可回原文核对。
一、为什么手机题最后总要落在数据库上
先看一个真实场景。
题目问:「机主在 2025-04-25 17:11:37 使用 WhatsApp 拨打过哪个号码」。你用自动取证工具打开检材,WhatsApp 那一栏里能看到通话记录列表,也能看到那条记录——但你要的是一个精确到秒的时间点,列表里没有这一列。这时候你要么一个个点开来对,要么退回最原始的那一层:直接查 WhatsApp 自己的数据库文件。
这就是手机取证里最常见的岔路口。自动分析工具(火眼、X-Ways、Autopsy、iLEAPP、手机大师)把数据库读出来、翻译成人能看懂的界面,但它翻译的字段是它认为重要的那几个。出题人偏偏爱问它没翻译出来的那个。
i 一句话总结这五年的命题规律
2021—2025 五届美亚杯,手机检材的题目里,凡是「精确时间点、经纬度、某个 ID、某条被编辑前的原文、某个文件的落盘路径」这类问法,答案几乎都在数据库里。工具能给你 80%,剩下那 20% 的分,就是靠你会不会读库、会不会写一句 SQL。
1.1 手机里的数据,分三层
把手机想象成一栋楼,应用数据从上到下是三层:
| 层 | 是什么 | 取证时会碰到什么 |
|---|---|---|
| 第一层:文件 | 照片、视频、语音、文档这些「看得见」的东西 | 导出就能看,唯一难点是文件名叫什么、存在哪——而这两个信息恰恰存在数据库里 |
| 第二层:应用数据目录 | 每个 App 自己的一块私有地盘,安卓在 /data/data/<包名>/,iOS 在 /var/mobile/Applications/<BundleID>/ |
目录结构杂乱、命名看不懂(group.net.whatsapp.WhatsApp.shared 谁记得住),但它是通往第三层的路 |
| 第三层:数据库 | SQLite 文件(少数是 Core Data、Realm、Postgres) | 所有「谁、什么时候、对谁、说了什么、传了什么、在哪」的结构化答案都在这 |
所以有一句在圈内流传的话:手机取证做到最后,就是一场 SQLite 阅读比赛。这话在美亚杯上几乎年年应验。
1.2 数据库其实你早就会——它就是一本 Excel 工作簿
「数据库」这三个字劝退了不少人,但 SQLite 和 Excel 的对应关系几乎是 1:1 的,把术语换成你说惯了的话,门槛立刻降一半:
| 数据库里的叫法 | Excel 里的叫法 | 手机取证里长什么样 |
|---|---|---|
| 数据库(database) | 一个 .xlsx 文件 | 一个 ChatStorage.sqlite 文件 |
| 表(table) | 一个工作表 sheet | ZWAMESSAGE 、message、ZASSET |
| 字段(column) | 一列的表头 | ZTEXT (消息正文)、ZDATE(时间) |
| 记录(row) | 一行数据 | 一条消息、一张照片的元数据 |
| 主键(primary key) | 行号(能被别人引用) | Z_PK /_id,全表唯一 |
| 外键(foreign key) | 「指向另一个 sheet 的某一行」的引用 | ZMESSAGEINFO 里存着 ZWAMESSAGEINFO.Z_PK 的值 |
其中主键和外键这一对,是读懂一切联表题的钥匙。第 7 章会专门讲。
✓ 别被名字吓到
ZASSET 读作「Z-asset」,ZWAMESSAGE 读作「Z-W-A-message」。iOS 应用的数据库大多用苹果的 Core Data 框架生成,框架习惯给表名和字段名统统加一个 Z 前缀——所以你在 iOS 库里面看到的字段几乎全是 Z 开头,这不是加密,只是框架的命名口味。
1.3 五个必须分清的概念
新手最容易把这五个混成一团,而美亚杯恰恰喜欢在它们的边界上出题:
| 概念 | 一句话 | 形象说法 | 在库里的样子 |
|---|---|---|---|
| 文件后缀 | 文件名最后那截 | 包装袋上的字 | .jpg ↔ 真实内容可能是 PNG |
| 文件头 | 文件开头的几个魔数 | 身份证上的照片 | FF D8 FF = JPEG |
| EXIF 元数据 | 照片自带的拍摄参数 | 照片背面写的字 | 存在 ZEXTATTR 表(iOS) |
| 数据库元数据 | 系统给数据加的记账信息 | 图书馆的索书号 | Z_METADATA /Z_PRIMARYKEY 表 |
| 应用配置 | App 的设置项 | 电器的档位旋钮 | com.tencent.mm_preferences.xml |
举个 2025 年个人赛的例子:题目问「哪张照片可以确定不是由这台手机拍摄的」。答案不在文件名上(文件名都是 IMG_00xx),也不在文件本身,而在 Photos.sqlite 的 ZEXTATTR 表里——那里存着从 EXIF 抄过来的相机型号,写着「SONY」和「iPhone 14 Pro MAX」的那两条,显然不是同一台设备拍的。
1.4 「工具解析不出来」到底是什么意思
这是新手最常见的困惑。工具明明打开了检材,为什么还是解析不出来?通常是四种原因之一:
- ① 数据库版本太新,工具内置的解析规则还没更新
——2025 年团体赛有一题问 n8n 平台的数据库有多少张表,因为工具还不支持那个版本的 PostgreSQL,参赛者只能手动导出、或者用十六进制工具硬看;同届个人赛还遇到过 Apple Notes 结构变化导致现成脚本集体失效,只能改脚本。
* ② 库根本没被识别成数据库
——文件后缀不是 .sqlite/.db,例如苹果的通话记录叫 CallHistory.storedata,很多人扫库的时候因为这后缀把它漏掉了。
* ③ 数据还在 WAL 日志里,没落进主库
——这是最阴的一种,第 2.5 节专门讲。
* ④ 工具只翻译了「常用字段」,你问的字段它没展示
——不是它读不出来,是界面没给你显示。这种只要自己拿 DB Browser 打开库,一眼就能看到。
这四条对应四种不同动作:①② 要自己手动看库,③ 要会处理 WAL,④ 只要把工具扔了直接看库。所以无论哪种,结论都一样:你得会自己开库。
1.5 这五年,手机数据库题考了多少
我把 2021—2025 五届团体赛+个人赛的题解逐题过了一遍,把「答案必须靠读数据库、或读数据库里某个字段」才能拿到的题目挑出来,按届归了一下,并给出可以回原文核对的题号——这样你既能看清趋势,也能自己抽查我有没有数错:
| 届次 | 密度 | 逐题收录 | 主要库(附可核对题号) |
|---|---|---|---|
| 2021 (团体+个人) | 起步 | 6 题 | wa.db (2021G-Q30)、蓝牙设备库(2021I-Q9/Q32/Q33)、ChatStorage(2021I-Q8)、相册库(2021I-Q37) |
| 2022 (个人) | 明显上升 | 10 题 | Photos.sqlite (2022I-Q33/Q34/Q35)、NoteStore.sqlite(2022I-Q37/Q38)、以及 iBooks/MTR/waze/Gmail 四个第三方库(2022I-Q10—Q12、Q20、Q31) |
| 2023 (团体+个人) | 上台阶 | 18 题 | ChatStorage.sqlite (2023G-Q9、2023I-Q55)、msgstore.db(2023G-Q82)、CallHistory.storedata(2023I-Q64)、Photos.sqlite(2023I-Q58) |
| 2024 (团体+个人) | 成为主力题型 | 19 题 | msgstore.db (2024G-Q11)、ChatStorage.sqlite(2024G-Q90)、message_2.sqlite(微信)(2024I-Q1/Q19)、CallHistory+AddressBook(2024I-Q2)、Photos.sqlite(2024I-Q22) |
| 2025 (团体+个人) | 绝对主力 | 34 题 | ChatStorage.sqlite (2025G-Q183、2025I-Q50)、DeviceAgents.sqlite(2025G-Q158)、Photos.sqlite(2025I-Q40)、ZWAMESSAGE+ZWAMESSAGEINFO(2025I-Q58)、finder_main.db(2025I-Q48) |
「逐题收录」那一列的 6 / 10 / 18 / 19 / 34,是把题目一道一道列出来数出来的,不是估算——每一道都写在第九章里,你可以拿着第九章的清单和这一列对账。
! 这个数字是怎么来的,以及它不是什么
手机题有个特点:同一道题的答案常常能从多个检材取得(2024 年团体赛题解自己就写「很多题目的答案来源都不止一处」)。所以我定的口径是:只收「必须动数据库才能拿到答案」的题——题目直接问库表字段的、必须写 SQL 才算得出的、以及火眼解析不出来只能手动开库的。
凡答案虽然存在库里、但火眼的聊天记录/图片分析已经能直接给出的题(比如「两人对话显示了什么关系」),没有计入。
另外,本表只统计手机检材;题解里还有一批服务器侧数据库题(2023G 的 MySQL 备份、2025G 的 n8n 与网站库等),不属于手机范畴,单列在第九章 9.8 节。
趋势很明确:从 2023 年开始,手机数据库题的比重明显上抬,2025 年几乎成了主力题型。而且考法在变——早期只考「在哪张表里」,现在考「两张表怎么接起来」「这个字段为什么和另一个字段打架」。
二、找库:手机的数据库都藏在哪
所有数据库题的第一步都是同一个动作:把这个库找出来。这一步不难,但路径不熟就会在水下摸半天。这一章把路径地图一次给全。
2.1 Android:路径整齐,一看就懂
安卓的规矩最简单——每个应用的私有数据都在自己包里:
/data/data/<包名>/databases/<库名>.db /data/data/<包名>/shared\_prefs/<配置>.xml /data/data/<包名>/files/ /data/data/<包名>/cache/ WhatsApp → /data/data/com.whatsapp/databases/msgstore.db /data/data/com.whatsapp/databases/wa.db 微信 → /data/data/com.tencent.mm/MicroMsg/<32位哈希>/EnMicroMsg.db 通讯录 → /data/data/com.android.providers.contacts/databases/contacts2.db 短信 → /data/data/com.android.providers.telephony/databases/mmssms.db 通话记录 → /data/data/com.android.providers.contacts/databases/calllog.db
看到没?包名 + databases 目录,就是安卓找库的全部秘诀。把 /data/data 当成一栋宿舍楼,每个 App 一间房,数据库都放在各自房间的 databases 抽屉里。
✓ 实战小抄
在火眼或 X-Ways 里直接搜 /databases/ 这个目录名,能一次性把所有 App 的库列出来。先看库的大小,几百 KB 到几 MB 的往往是重点——空的库(0 字节或只有几张空表)说明那个 App 装过但没用过。2023 年个人赛有一题「手机安装了什么即时通讯软件」,答案就得靠「有没有数据」来判:WhatsApp 和微信都装了,但微信库里没数据,说明装了没登录,最后只能选 WhatsApp。
2.2 iOS:路径乱,但规律在 BundleID 上
iOS 的路径看着长,规律其实也简单:应用名被写成 BundleID,数据放在包的 Documents 或 Library 下。
/var/mobile/Applications/
注意三个「不像数据库」的名字,这是最容易漏掉的:CallHistory.storedata(通话记录)、NoteStore.sqlite(在共享域 AppDomainGroup 下,不在单个 App 包里)、Photos.sqlite(在 Media/PhotoData/,不在应用目录里)。
! 别用后缀筛库
如果用「后缀是 .sqlite 或 .db」去筛,CallHistory.storedata 这类文件会被整批漏掉。稳妥做法是同时看后缀和文件头:文件头是 SQLite format 3 的就是 SQLite 库,不管它叫什么名字。
2.3 iOS 备份检材:那四个必须认识的文件
近几年美亚杯的 iOS 检材大多是备份包(.zip)而不是完整镜像,解压之后在 /var 目录下一定会看到这几个文件,它们的名字和用途是固定的:
| 文件名 | 作用 | 取证上怎么用 |
|---|---|---|
| Manifest.db | 备份的文件清单数据库。记录每个文件的三件事:fileID(在备份里的哈希文件名)、domain(属于哪个 App 域)、relativePath(原始路径) |
想知道「某个文件原来叫什么、在哪」,就查它。备份目录里文件全是随机哈希名,靠这张表才能还原 |
| Manifest.plist | 备份的总配置 | 其中 IsEncrypted 标志决定这份备份能不能直接分析 |
| Status.plist | 备份的状态信息 | 用于确认备份完整性、备份时间、UUID |
| \*\_DEC (带此后缀的 4 个文件) | 提取工具生成的解密后版本 | 用它们覆盖掉同名加密文件,再把 IsEncrypted 改成 false,就能当普通文件集合挂载分析 |
2025 年个人赛题解给的正是上面这条流程。还有个更省事的变体:只删掉 Manifest.db 并把 Manifest.plist 的加密标志改掉,效果一样。两个都删也能挂载,但会缺一部分信息。
i 备份密码从哪来
iOS 备份若要保留 KeyChain 这类隐私数据,工具会强制设一个备份密码。赛方给检材时设的通常是 4 位数字弱口令,2025 年个人赛题解直接写明「大多数检材的密码为 0000 或 1234」。这就是第 8.5 节说的「先试默认值」——不是爆破,是撞常见值,两秒钟的事。
2.4 每个库都自带三张「说明书」表
这是新手最该记住的一招:打开任何一个陌生的库,先看这三张表,不用猜。
| 表名 | 是干什么的 | 常用查询 |
|---|---|---|
| sqlite\_master | 整个库的目录页——列出所有表、索引、视图的名字与建表语句 | SELECT name FROM sqlite_master WHERE type='table'; |
| Z\_PRIMARYKEY | Core Data 的主键计数器,列出每个实体的名字和当前最大主键值 | iOS 库里想快速看「这个库有哪些实体」,它是另一份目录 |
| Z\_METADATA | Core Data 的版本与元信息,通常只有一行 | 看库结构版本,判断这个库是哪个 iOS 版本生成的 |
2023 年个人赛有一题就是直接考这个:CallHistory.storedata 里「哪份表格显示了通话记录」,选项里混着 ZCALLRECORD、ZCALLBPROPERTIES、Z_METADATA、Z_MODELCACHE、Z_PRIMARYKEY——后面那三个正是 iOS 库里每张表都会有的「通用表」,它们当然是干扰项,答案是有实际业务含义的 ZCALLRECORD。
还有一题问「数据库里有哪两张表」(2023 团体赛,检材是一个 MySQL 备份文件),题解的做法就是直接看 dump 文件里的建表语句,找到 users 和 creditcard。可见无论 SQLite 还是 MySQL,「先列表名」永远是第一步。
2.5 一行 Python,把所有字段名导出来
知道表名之后,下一步是知道字段名。肉眼在 DB Browser 里翻很慢,2022 年个人赛的题解给了一段特别实用的脚本——那次考的正是「照片的哪个栏目标题能显示接收方式」,解题人干脆把全库字段导成 JSON 再搜:
import sqlite3, json con = sqlite3.connect('./Photos.sqlite') cur = con.cursor() tables = cur.execute( \"SELECT name FROM sqlite\_master WHERE type = 'table' ORDER BY name;\" ).fetchall() tables\_parsed = {} for tablename in tables: tablename = tablename[0] tables\_parsed[tablename] = [] cols = cur.execute('PRAGMA table\_info(%s);' % tablename).fetchall() for col in cols: tables\_parsed[tablename].append(col[1]) con.close() with open('./Photos.json', 'w') as f: json.dump(tables\_parsed, f, indent=4)
这段代码做了三件事,值得逐句理解:
sqlite_master
→ 拿到所有表名;
* PRAGMA table_info(表名)
→ 拿到这张表的所有字段名
* (PRAGMA 是 SQLite 特有的「查元信息」指令,不含数据,秒回);
* 输出成 JSON → 之后用编辑器一个 Ctrl+F 搜字段名,比在图形界面里翻快十倍。
那题的题干里给了四个候选字段名(ZIMPORTEDFROMSOURCEIDENTIFIER、ZIMPORTEDBYBUNDLEIDENTIFIER、ZRECEIVEDFROMIDENTIFIER、ZRECEIVEMETHODIDENTIFIER),把它们塞进上面的 find 列表里一搜就发现:只有 ZIMPORTEDBYBUNDLEIDENTIFIER 存在,而且同时出现在 ZADDITIONALASSETATTRIBUTES、ZCLOUDMASTER、ZGENERICALBUM 三张表里。
✓ 这一招的通用性
「把全库表名+字段名导成一份清单,然后搜关键词」——这是所有数据库题的开局起手式,无论题目问什么。赛场上没有比这更快的方式了:你不知道叫什么,那就让它自己告诉你。
2.6 WAL 日志:你看到的数据可能是旧的
在库文件旁边,你经常会看到一对同名兄弟:
ChatStorage.sqlite ChatStorage.sqlite-wal ← 预写日志(Write-Ahead Log) ChatStorage.sqlite-shm ← 共享内存索引
SQLite 为了性能,写入时先写 WAL、延迟合并进主库。所以第一个坑是:主库文件里的数据可能落后于 WAL,最近几条消息、最近的编辑记录很可能只存在于 -wal 里。
- 怎么办
:把 .sqlite、-wal、-shm三个文件放回同一目录再打开,DB Browser 会自动合并读取——单个文件拷出来是读不到最新数据的;
* 或者
先把 WAL 落盘:打开库执行一句 PRAGMA wal_checkpoint(FULL);;
* 注意
:分析时别在原检材上做任何写操作,先复制一份工作副本。
! 一个真实教训
2025 年个人赛有一题需要拿到 WhatsApp 里一条投票消息的完整信息,而投票详情并不在 ZWAMESSAGE 主表里,而是以 Protobuf 格式存在ZWAMESSAGEINFO 表的 ZRECEIPTINFO 字段中。这类「详细信息存在附属表、主表只放一个指针」的设计,加上 WAL 的延迟落盘,是两个最容易让新手得出「数据不存在」错误结论的地方。
三、iOS 相册库 Photos.sqlite:五年最重要的一个库
如果整篇文章只让你记一个库,那就是它。
2022、2023、2024、2025 四届都有题目直接落在 Photos.sqlite 上,考法从「字段名是什么」一路升级到「两张表的经纬度为什么不一样」。原因也不难理解:它是手机上信息密度最高的一个数据库——每张照片的拍摄时间、精确到小数点后六位的经纬度、拍摄设备型号、镜头、文件叫什么名字、什么时候进的相册、是不是从别的 App 收来的、是不是实况照片、是不是被删除过,全在里面。
3.1 库在哪
完整镜像:/var/mobile/Media/PhotoData/Photos.sqlite iOS 备份:/var/mobile/Media/PhotoData/Photos.sqlite (在 Manifest.db 的 domain 里可定位) 旁支文件:Photos.sqlite-wal / Photos.sqlite-shm (还有 Photos.sqlite-journal 等,同上,要一起带上)
注意路径——它在 Media/PhotoData/,不在应用沙盒里。这一点很关键:说明它是系统级数据,不受单个 App 的沙盒限制,也解释了为什么它能记录「这张照片是从哪个 App 收进来的」。
3.2 表族全貌:先认清这几张表
打开 Photos.sqlite,你会看到几十上百张表,但真正高频的只有下面这八九张。关键是记全称——因为公开的查询脚本(比如圈内常用的 iOS_Local_PL_Photos.sqlite_Queries)在 SQL 里习惯用缩写,你看题解时容易被绕晕:
| 表全称 | 脚本/题解里的写法 | 管什么 |
|---|---|---|
| ZASSET | ZASSET | 主表 :一张照片/一段视频一条记录,绝大多数问题都从它开始 |
| ZEXTENDEDATTRIBUTES | ZEXTATTR | EXIF 扩展属性 :相机型号、镜头、焦距、拍摄地经纬度、时区、方向 |
| ZADDITIONALASSETATTRIBUTES | ZADDASSETATTR | 附加属性 :来源、导入方、原始资产标识、指纹 |
| ZGENERICALBUM | ZGENALBUM | 相簿:相册名(ZTITLE)、创建时间 |
| ZCLOUDMASTER | ZCLDMAST | iCloud 侧的主记录:云资产 GUID、云端创建时间 |
| ZINTERNALRESOURCE | ZINTRESOU | 内部资源:缩略图、调整后版本等 |
| ZUNMANAGEDADJUSTMENT | ZUNMADJ | 未经管理的调整信息(编辑过的照片) |
| ZSCENEPRINT | ZSCENEP | 场景识别结果 |
| ZMEDIAANALYSISASSETATTRIBUTES | ZMEDANLYASTATTR | 媒体分析属性 |
| ZSHARE | ZSHARE | 共享相簿信息 |
i 怎么确认一张表到底叫什么
别背,去查。用第 2.5 节那段脚本把全库表名导出来,或者直接用一句 SELECT name FROM sqlite_master WHERE type='table';。2022 年个人赛的题解就是这么确认的:它先列出全库字段,发现候选字段里只有 ZIMPORTEDBYBUNDLEIDENTIFIER 存在,并且同时存在于 ZADDITIONALASSETATTRIBUTES、ZCLOUDMASTER、ZGENERICALBUM 这三张表里——连表名一起确认了。
3.3 ZASSET 常用字段速查
下面这张表是本文最该收藏的一张。「真题实证」列的 √ 表示这个字段在近五年题解里被明确用到过,后面跟的是届次与题号;— 表示它在公开资料里是标准字段,但这五年题目里没有被单独点问过。
| 字段名 | 含义 | 取值与坑 | 真题实证 |
|---|---|---|---|
| Z\_PK | 本表主键,全表唯一 | 所有「关联到这张照片」的表都存这个值 | — |
| ZUUID | 资产唯一标识(UUID 字符串) | 跨库关联的钥匙 :微信消息里的 m_assetUrlForSystem 存的就是它 |
√ 2024I-Q1/Q19 |
| ZFILENAME | 原始文件名 | IMG_0008.HEIC 、5005.JPG |
√ 2024I-Q22 |
| ZORIGINALFILENAME | 导入前的原文件名 | 从别的 App 收来的照片,这里会留痕 | — |
| ZDIRECTORY | 相对目录 | 通常是 DCIM/100APPLE |
— |
| ZUNIFORMTYPEIDENTIFIER | 统一类型标识(UTI) | public.heic /public.jpeg/public.mpeg-4——判文件真实类型比后缀更靠谱 |
√ 2024I-Q22 |
| ZDATECREATED | 拍摄/创建时间 | Cocoa 时间戳 ,见第 6 章 | √ 2025I |
| ZADDEDDATE | 加入相册的时间 | 收来的照片:创建时间早、加入时间晚,可据此判断「什么时候收到的」 | √ 2025I |
| ZLATITUDE | 纬度 | 注意:它和 ZEXTATTR 里那份可能不一致 |
√ 2025I-Q40 |
| ZLONGITUDE | 经度 | 同上 | √ 2025I-Q40 |
| ZEXTENDEDATTRIBUTES | 指向 ZEXTENDEDATTRIBUTES.Z_PK |
这是外键——想知道拍摄设备必须顺着它跳过去 | √ 2025I-Q40 |
| ZKIND | 资产种类 | 0 = 照片,1 = 视频 |
— |
| ZKINDSUBTYPE | 种类子类型 | 全景、慢动作、实况等细分 | — |
| ZVIDEOCPDURATIONVALUE | 附带视频片段时长 | 实况照片(Live Photo)这项不为 0 | √ 2024I-Q22 |
| ZTRASHEDSTATE | 是否在「最近删除」 | 被删除的照片可能仍留在这张表里 | — |
| ZTRASHEDDATE | 删除时间 | 配合上一字段还原删除动作 | — |
| ZHIDDEN | 是否被隐藏 | 「已隐藏」相簿的成员 | — |
| ZWIDTH /ZHEIGHT | 像素宽高 | 常用来区分原图与缩略图 | — |
| ZCLOUDASSETGUID | 云端资产 GUID | 与 ZCLOUDMASTER 呼应 |
— |
3.4 ZEXTATTR:EXIF 真正的家
很多人第一次做这类题会犯一个错:在 ZASSET 里翻相机型号,翻了半天没有。因为设备信息不在主表,在 ZEXTENDEDATTRIBUTES。
2025 年团体赛就考了这个:题目要求「列出可以确定不是由这台手机拍摄的图片」。题解的做法是把查询结果导出成 CSV,然后在 ZEXTATTR 的ZCAMERAMODEL 列里筛选——结果看到两个文件分别来自「SONY 相机」和「iPhone 14 Pro MAX」,而题目限定了 HEIC(HEVC)格式,那两个文件一个是 MOV 视频、一个是 JPG 图片,自然都不符合,最后锁定 JPG 那个。
ZEXTENDEDATTRIBUTES 里值得记住的字段:
| 字段名 | 含义 | 考点 |
|---|---|---|
| ZCAMERAMAKE | 相机厂商 | 「这台照片是谁拍的」第一顺位 |
| ZCAMERAMODEL | 相机型号 | 判「是否本机拍摄」的关键 |
| ZLENSMODEL | 镜头型号 | 区分同一厂商的不同机型/外接镜头 |
| ZFOCALLENGTH | 焦距 | 配合镜头型号判断 |
| ZFOCALLENGTHIN35MM | 等效 35mm 焦距 | 同上 |
| ZDIGITALZOOMRATIO | 数码变焦倍率 | >1 说明做过数码变焦 |
| ZLATITUDE /ZLONGITUDE | 拍摄地经纬度(EXIF 那份) | 与 ZASSET 那份对照,用哪份是真题考点 |
| ZGPSHORIZONTALACCURACY | GPS 水平精度 | 精度值大说明定位粗 |
| ZFLASHFIRED | 是否闪光 | 辅助信息 |
| ZORIENTATION | 拍摄方向 | 辅助信息 |
| ZTIMEZONENAME /ZTIMEZONEOFFSET | 拍摄时区 | 时间换算前必看 ——同一时刻在不同时区显示不同 |
| ZEXIFTIMESTAMPSTRING | EXIF 原始时间字符串 | 未经换算的「原话」,可与换算结果互校 |
3.5 两份经纬度打架,用哪一份?(2025 个人赛真题)
这是 Photos.sqlite 系列里最值得讲的一道题,因为它把「读字段」升级成了「辨字段」。
题目:「APP『照片』中,IMG_0027.HEIC 的原地理位置信息(WGS84)是」,四个选项的经纬度极其接近,只有小数点后几位不同。
题解的做法是:过滤文件名之后,看到两组地理位置信息,一组来自 ZASSET 表、一组来自 ZEXTATTR 表。两组值不一样。怎么办?解题人用的是最朴素也最可靠的办法——对比拍摄时间相近的几张照片,发现 ZEXTATTR 那份才和邻近照片的轨迹连贯,于是判定它是真实信息。最后还去看了一眼文件的 EXIF,印证了同样的结果。
✓ 这类题的通用解法:三方对照
当同一个信息在不同表里出现两个值时,不要猜,去交叉验证。顺序是:① 看两个值分别来自哪张表(ZASSET 还是 ZEXTATTR);② 拿同一时段的其他照片做轨迹对比,看哪一组更连续;③ 导出文件本身看 EXIF,作为第三方印证。三条证据一致的,就是答案。
还有一个同类现象更该警惕:自动取证工具算出来的值,可能和数据库里存的值不一样。2025 年个人赛另一题要求「某群组在某时刻传送的 WGS84 坐标」,题解发现选项里没有一个与工具输出的完全一致,回去翻数据库才发现——工具那条记录的 GPS 与库里存的不同。解题人最后是通过定位该消息的媒体记录(ZMEDIAITEM 字段的值),再去 ZWAMEDIAITEM 表按 Z_PK 过滤,才拿到精确经纬度。题解原话写得很实在:「目前还不确定为什么火眼分析出的 GPS 信息会与数据库中存储的值不同」。
! 记住这句话
工具给的是「它算的结果」,数据库给的是「原始记录」。答案冲突时,考场上的正确做法是——回数据库,并说明你的取值依据。
3.6 「这张照片是怎么进来的」(2022 个人赛真题)
这道题的问法很典型:「根据照片的数据库资料,哪一个栏目标题可以显示这张照片的接收方式」。
注意题干用词——「栏目标题」翻成人话就是「字段名」。四个候选是:
A. ZIMPORTEDFROMSOURCEIDENTIFIER B. ZIMPORTEDBYBUNDLEIDENTIFIER C. ZRECEIVEDFROMIDENTIFIER D. ZRECEIVEMETHODIDENTIFIER
题解给的解法就是第 2.5 节那段脚本:把全库表名和字段名导成 JSON,再把四个候选逐个搜一遍。结果是——四个里只有 ZIMPORTEDBYBUNDLEIDENTIFIER 真实存在,并且同时出现在 ZADDITIONALASSETATTRIBUTES、ZCLOUDMASTER、ZGENERICALBUM 三张表里。
! 这里有一处题解内部的不一致,如实说明
题解记录的答案是 A(ZIMPORTEDFROMSOURCEIDENTIFIER),但题解自己的检索结果只找到了 B——原话是「只能找到 ZIMPORTEDBYBUNDLEIDENTIFIER 字段」。也就是说这道题在题解里是存疑的。
复现时按 B 去理解逻辑是通的(它确实存在,也确实承担「记录来源」的作用);但考场上如果原题选项与真题一致,仍以官方答案为准。把这个不一致标出来,比替你选一个更负责任。
而下一题(「承上题,这张照片通过什么方式接收」)问的是那个字段的值:它是 com.apple.sharingd。题解特意查了这个守护进程的说明:sharingd 就是系统里负责 AirDrop、共享电脑、远程光盘的那个服务。所以答案是「以上皆非」——它既不是 WhatsApp 传的,也不是蓝牙、Signal、网页下载,而是隔空投送。
两题连起来看,路径其实很清楚:先确定字段名,再看字段的值,最后把值翻译成人话。
| 字段 | 值举例 | 对应含义 |
|---|---|---|
ZIMPORTEDBYBUNDLEIDENTIFIER |
com.apple.sharingd |
隔空投送(AirDrop) |
ZIMPORTEDBYBUNDLEIDENTIFIER |
com.apple.mobileslideshow |
系统相册自身 |
ZIMPORTEDBYDISPLAYNAME |
导入方的显示名 | 常与上一字段配对出现 |
ZIMPORTEDFROMSOURCEIDENTIFIER |
来源标识 | 外部来源 |
ZIMPORTSESSIONID |
导入会话 ID | 同一次批量导入共享同一个值 |
这类题的通用套路已经成型:先确定字段名(靠第 2.5 节的导字段脚本)→ 再取该字段的值 → 最后把值翻译成人话。值往往是 BundleID 形式(com.xxx.yyy),查一下这个包名属于哪个 App,答案就出来了。
3.7 怎么判断实况照片(Live Photo)
2024 年个人赛的一题:「以下哪张照片是实况照片(Live Photos)」,多选题。
题解的 SQL 只有三行,但把三个知识点塞在了一起:
SELECT Z\_PK, ZFILENAME FROM ZASSET WHERE ZFILENAME LIKE '%.HEIC' AND ZUNIFORMTYPEIDENTIFIER = 'public.heic' AND ZVIDEOCPDURATIONVALUE != 0;
ZFILENAME LIKE '%.HEIC'
——按文件名后缀过滤;
* ZUNIFORMTYPEIDENTIFIER = 'public.heic'
——按真实类型过滤(两者不一定一致,所以要双保险);
* ZVIDEOCPDURATIONVALUE != 0
——实况照片会附带一段几秒的视频,这个字段不为 0 就说明有视频分量,即 Live Photo。
题解还补了一句很实在的话:这题「有点白给」——因为选项里的 IMG_0005.HEIC 和 IMG_0006.HEIC 这两个文件根本不存在(实际扩展名是 PNG),多选题排除一下就出来答案了。「文件不存在但要靠数据库发现」——这也是 Photos.sqlite 的常见坑:库里有记录 ≠ 文件还在。
3.8 被删除的照片,痕迹在哪
照片实际被删、进「最近删除」、被隐藏,是三个不同状态,而 Photos.sqlite 把这三个状态都记下来了:
ZTRASHEDSTATE
:是否在「最近删除」里(等待 30 天自动清除);
* ZTRASHEDDATE
:进入「最近删除」的时间——这条时间线本身就有意义,能还原「什么时候动过手」;
* ZDELETEREASON
/ZTRASHEDBYPARTICIPANT:删除原因、由谁删除(共享相簿场景);
* ZHIDDEN
:是否被隐藏(隐藏相簿);
* ZCLOUDDELETESTATE
/ZPTPTRASHEDSTATE:云端删除状态、PTP 导入的删除状态。
✓ 删除类题目的答题骨架
① 这张表里还有没有这条记录(ZTRASHEDSTATE 是 1 还是 0)→ ② 什么时候被删的(ZTRASHEDDATE)→ ③ 原始文件还在不在 Media/DCIM/ 下 → ④ 有没有副本(缩略图、调整后版本、iCloud 侧记录)。四步走完,「删除」这个动作就还原完整了。
3.9 本章真题索引(附原题)
下面把本章涉及的真题逐题列出——题干与选项按题解原文照录(含原文的繁体用字、半角标点与大小写,不作改写);每题只标「考的是哪个库、哪张表、哪个字段」,完整答案与解题过程见第九章。
2022I-Q34 | 选择题
考的字段/表:ZIMPORTEDBYBUNDLEIDENTIFIER 等四个候选字段
根据照片的数据库资料,哪一个栏目标题可以显示这张照片的接收方式
A. ZIMPORTEDFROMSOURCEIDENTIFIER
B. ZIMPORTEDBYBUNDLEIDENTIFIER
C. ZRECEIVEDFROMIDENTIFIER
D. ZRECEIVEMETHODIDENTIFIER
考点:「栏目标题」= 字段名;题解答案写 A、检索结果只找到 B,属存疑
2022I-Q35 | 选择题
考的字段/表:该字段的值com.apple.sharingd
承上题,这张照片通过什么方式接收
A. WhatsApp软件传送
B. 蓝牙传送
C. Signal软件传送
D. 网页下载
E. 以上皆非
考点:把 BundleID 翻译成 AirDrop,答案「以上皆非」
2023I-Q58 | 填空/简答(原题无选项)
考的字段/表:ZASSET 视频 + ChatStorage 交叉
根据 Photos.sqlite 数据库中, 有多少段视频可能涉及 WhatsApp?
考点:两个库比对:库里 10 条,实际保存 7 条
2024I-Q22 | 选择题
考的字段/表:ZFILENAME/ZUNIFORMTYPEIDENTIFIER/ZVIDEOCPDURATIONVALUE
Emma 的 iPhone XR 内以下哪张照片是实况照片(Live Photos)?
A. IMG\_0002.HEIC
B. IMG\_0005.HEIC
C. IMG\_0004.HEIC
D. IMG\_0006.HEIC
考点:Live Photo 判定
2024I-Q1/Q19 | 选择题
考的字段/表:ZUUID(跨库)
2024I-Q1
Emma 和 Clara 的微信聊天记录, Emma 最后到警署报案并拍摄写有报案编号的卡片, 拍摄时的经纬值是多少?(检材:Emma 的手机)
A. 22.451721666667, 114.171853333333
B. 22.451553333333, 114.172845
C. 22.451928333333, 114.170503333333
D. 22.451638333333, 114.16993
2024I-Q19
Emma 的 iPhone XR 中\"IMG\_0008.HEIC\"的图像与相片名字为的\"5005.JPG\"看似为同一张相片, 在数码法理鉴证分析下, 以下哪样描述是正确?(检材:Emma 的手机)
A. 储存在不同的.db檔案里
B. 有不同哈希值
C. IMG\_0008.HEIC 为原图, \"5005.JPG\"为并非原图
D. IMG\_0008.HEIC 和名字\"5005.JPG\"是同一张相片
考点:微信消息 → 相册库,按 GUID 关联找经纬度
2025I-Q40 | 选择题
考的字段/表:ZASSET vs ZEXTATTR 的经纬度
APP \"照片\"中, \"IMG\_0027.HEIC\"的原地理位置信息(WGS84)是
A.(22.2816569, 114.1756115)
B.(22.2826366666667, 114.168503333333)
C.(22.2826216666667, 114.168525)
D.(22.2826216666667, 114.168503333333)
考点:两值冲突时如何取舍
2025I-Q30 | 选择题
考的字段/表:ZASSET 系列字段(含来源 App)
相册中有两张 GIF \"IMG\_0057.GIF\"及\"IMG\_0062.GIF\", 请指出由哪一个软件拍摄
A.Infltr
B.Discreet
C.Meitu
D.Prisma
考点:从相册库判照片由哪个软件产生
2025G-Q164/Q166 | 选择题
考的字段/表:ZEXTATTR.ZCAMERAMODEL
2025G-Q164
在此手机中, 请列出可以确定不是由此手机拍摄的图片文件(heic)完整文件名称(检材:冯子超的手机)
2025G-Q166
根据上一题,此图片文件是如何在此手机生成的(检材:冯子超的手机)
A. 接收自云端\"iCloud\"同步下载
B. 接收自即时通讯软件\"WhatsApp\"
C. 接收自即时通讯软件\"微信\"
D. 接收自空投(AirDrop)
考点:判「是否本机拍摄」;图片来源是 Safari 下载
四、WhatsApp:五年一届都没缺席的库
如果说 Photos.sqlite 是「信息密度最高」,那 WhatsApp 就是「出场次数最多」——2021 到 2025 五届,每一届的题目里都有它的身影。
| 届次 | 出现的库 | 典型问法 |
|---|---|---|
| 2021(团体/个人) | wa.db (安卓) |
某个账号下有哪些联系人、群组有哪些成员 |
| 2022(个人) | ChatStorage 相关 | 机主安装了什么即时通讯软件 |
| 2023(团体/个人) | ChatStorage.sqlite (iOS)、msgstore.db(安卓) |
被编辑前的消息原文、锁定的对话、群组创建者 |
| 2024(团体/个人) | msgstore.db 、ChatStorage.sqlite |
message_type 取值、媒体文件的本地路径 |
| 2025(团体/个人) | ChatStorage.sqlite 、DeviceAgents.sqlite |
已登录设备数、存档群组、投票记录、位置消息、群头像文件名、建群时间 |
有个规律值得注意:WhatsApp 的库读法在 iOS 和安卓上完全不同。它的安卓版和 iOS 版是两套独立实现,表名、字段名没有一点相似。所以做这类题的第一件事是先确认检材是哪个平台——看路径就知道:/data/data/com.whatsapp/ 是安卓,/var/mobile/Applications/group.net.whatsapp.WhatsApp.shared/ 是 iOS。
4.1 iOS:ChatStorage.sqlite
/var/mobile/Applications/group.net.whatsapp.WhatsApp.shared/ ├─ ChatStorage.sqlite ← 聊天主库(核心) ├─ DeviceAgents.sqlite ← 已登录设备(2025 考过) └─ Media/ ├─ Profile/ ← 头像(群组、联系人) └─ <各种媒体子目录> ← 收发的图片、视频、语音、文档
ChatStorage.sqlite 是标准的 Core Data 库,所以表名字段名全是 Z 开头:
| 表名 | 用一句话说 | 高频字段 |
|---|---|---|
| ZWACHATSESSION | 会话(一对一会话 + 群组会话都在这) | 联系人 JID、昵称、存档标志、最后消息时间 |
| ZWAMESSAGE | 消息主表,一条消息一行 | ZTEXT /ZFROMJID/ZTOJID/ZMESSAGETYPE/ZMESSAGEINFO/ZMEDIAITEM |
| ZWAMESSAGEINFO | 消息的详细附加信息 | ZRECEIPTINFO (Protobuf 格式的原始详情) |
| ZWAMEDIAITEM | 媒体文件记录 | 文件本地路径、媒体类型、位置消息的精确经纬度 |
| ZWAGROUPINFO | 群组的元信息 | 群创建者 ID 、群主 ID |
| Z\_PRIMARYKEY /Z\_METADATA | Core Data 通用表 | 查表名、库版本 |
4.1.1 ZWAMESSAGE:消息级提问的必查表
| 字段 | 含义 | 坑与实证 |
|---|---|---|
| ZTEXT | 消息正文 | 非文本消息这里可能是空或占位符,内容在别处 |
| ZFROMJID | 发送者 JID | [email protected] 或群 ID [email protected] |
| ZTOJID | 接收者 JID | 群消息里收发双方都可能写成群 JID |
| ZMESSAGETYPE | 消息类型枚举 | 实证:46 = 投票消息 (2025I-Q58) |
| ZMESSAGEINFO | 指向 ZWAMESSAGEINFO.Z_PK |
外键,要拿详情必须 JOIN 过去 |
| ZMEDIAITEM | 指向 ZWAMEDIAITEM.Z_PK |
位置消息、图片、文件的实体信息在这 |
| ZMESSAGEDATE | 消息时间 | Cocoa 时间戳 |
| ZISFROMME | 是否本机发出 | 判「谁发的」最直接的一个字段 |
4.1.2 ZWACHATSESSION:存档与群组
2025 年个人赛考「哪个聊天群被封存了」——题解特别提醒:「封存」是港台译法,大陆一般叫「存档」,英文是 Archive。对应的就是 ZWACHATSESSION 表里的那个存档标志字段(题解中记作 ARCHIVED,Core Data 风格命名通常带 Z 前缀,以你手上的库为准),值为 1 即为已存档。
同年团体赛还有一追问「这个群组的建立者完整手机号码」,靠的是 ChatStorage.sqlite 里的群信息记录。但 2023 年团体赛遇到了一个反例:题解发现 ZWAGROUPINFO 表中记着创建者 ID 的字段(ZCREATORJID)在三个目标群里全为空——这就是 4.3 节要讲的「群 ID 自带建群信息」那个技巧的用武之地。
4.2 群组 ID 本身就是一条线索(重点技巧)
这是近五年被反复考、但很多新手不知道的一个点:WhatsApp 的群组 ID(形如 [email protected])有两种格式并存,而老格式里直接编着建群者的手机号和建群时间。
| 格式 | 样子 | 能读出什么 |
|---|---|---|
| 老格式 | <建群者手机号>-<建群时间 UNIX 时间戳>@g.us 例:[email protected] |
建群者号码 = +852-63834763 建群时间 = 2019-07-20 15:26:13 |
| 新格式 | <长数字 ID>@g.us 例:[email protected] |
读不出建群信息,只能靠库里的记录 |
2025 年团体赛的那道题(问群组建立者的完整手机号码)就是靠解析群 ID 前那段数字拿到的,题解还额外用 ChatStorage.sqlite 里的建群时间记录做了交叉验证——把库里那个 Cocoa 时间戳换算成日期之后,和群 ID 里读出来的时间完全一致。
这道题还有后半段值得记:题解发现库里的「当前群主」和「建群者」不是同一个人,进而推断出这个群被转移过所有权。也就是说,两个 ID 不一样不是数据错,而是本身就有意义的线索。
✓ JID 的三种后缀
@s.whatsapp.net = 个人账号,@ 前面就是带国际区号的手机号(2023 年团体赛问「无人机卖家的电话号码」,答案就是从 JID 前半段读出来的);@g.us = 群组;@broadcast = 广播列表/状态。看到 @ 就自动去读前半段,这是条件反射级的基本功。
4.3 藏起来的详情:ZWAMESSAGEINFO 与 Protobuf
2025 年个人赛有一道高分题,问「总共在多少个投票活动中作出了投票」。这种题最容易被卡住——因为投票的选项、投票记录根本不在消息主表里。
题解的路径是这样的:先用 JID 过滤出目标群的消息,发现有投票消息,而它们的 ZMESSAGETYPE 都是 46;然后用 46 再筛,得到 3 条投票消息。但「投了几次票」这个信息还得往下钻——于是把 ZWAMESSAGEINFO 表 JOIN 进来:
SELECT ZWAMESSAGE.ZMESSAGETYPE, ZWAMESSAGE.ZMESSAGEINFO, ZWAMESSAGE.ZTEXT, ZWAMESSAGE.ZFROMJID, ZWAMESSAGE.ZTOJID, ZWAMESSAGEINFO.ZRECEIPTINFO FROM ZWAMESSAGE RIGHT JOIN ZWAMESSAGEINFO ON ZWAMESSAGE.ZMESSAGEINFO = ZWAMESSAGEINFO.Z\_PK WHERE ZMESSAGETYPE = 46;
关键在最后那一列 ZRECEIPTINFO——它存的是Protobuf 格式的十六进制字符串。题解把它丢进 CyberChef 解码,投票记录就出来了。而且题解还记了一个细节:只有群组内的投票保留投票记录,频道里的投票只留选项和 Reaction 信息——所以三个投票分别有 2、1、2 次记录,加起来是 3 次有效投票。
! 「主表只有一个指针」是通用设计
ZWAMESSAGE 里放 ZMESSAGEINFO,ZWAMESSAGEINFO 里放 ZRECEIPTINFO,而真正的复杂内容(投票、回复、引用、转发)藏在 Protobuf 里。凡是你觉得「这条消息明明有内容,库里却看不到」,就往这条链子上找:主表 → 外键 → 附属表 → Protobuf 字段。
4.4 位置消息:经纬度藏在 ZWAMEDIAITEM
2025 年个人赛连着考了三道位置题:某时刻发送的坐标是多少、以及那个坐标指向哪家餐厅。
题解给出的路径非常清晰,值得当模板背下来:
- 第一步
:在会话里按时间定位到那条消息;
* 第二步
:读它的 ZMEDIAITEM 字段的值(题解里是 504);
* 第三步
:去 ZWAMEDIAITEM 表里 WHERE Z_PK = 504,拿到精确到一个极高小数位的经纬度;
* 第四步
:把坐标丢进地图工具反查地名(题解拿到的是某海鲜酒家)。
前面 3.5 节提到的「工具算的 GPS 与库里的不一致」那件事,就是在这一步被发现的——精确值只存在于 ZWAMEDIAITEM。
4.5 DeviceAgents.sqlite:登录设备数
2025 年团体赛问「这个 WhatsApp 账号连同本机在内一共登录了多少台设备」。答案不在聊天库里,而在旁边的 DeviceAgents.sqlite,表名 ZWASMBDEVICEAGENT——这张表里有几条记录,就是登录了几台设备。
✓ 这个库很容易被忽略
它静静躺在 WhatsApp 数据目录里,体量很小,但「多设备登录」这类题只有它答得了。养成习惯:把 WhatsApp 目录整个列一遍,别只盯着 ChatStorage。
4.6 安卓:msgstore.db
/data/data/com.whatsapp/databases/ ├─ msgstore.db ← 消息主库(核心) ├─ msgstore.db-wal ├─ wa.db ← 通讯录 └─ axolotl.db ← 加密会话密钥
安卓版是一套完全不同的表结构,表名是普通的小写命名:
| 表名 | 作用 | 关键字段/关系 |
|---|---|---|
| message | 消息主表 | _id (主键)、chat_row_id(→ chat._id)、message_type、text_data |
| chat | 会话表 | _id 、jid_row_id(→ jid._id)、会话名 |
| jid | 号码/账号表 | _id 、user(JID 字符串)、raw_string |
| message\_edit\_info | 消息的编辑记录 | message_row_id (→ message._id)、编辑前后的内容 |
| message\_ftsv2\_content | 全文索引表,保留原始消息文本 | docid (→ message._id) |
| media /message\_media | 媒体文件 | 本地路径、文件哈希 |
4.6.1 经典联表:找出「被编辑前的原文」
2023 年团体赛那道题问「两部手机的对话曾被修改过,请找出修改前的内容」,是安卓 WhatsApp 联表查询的教科书级案例。题解把五张表串成了一条链子:
| 表 | 怎么接 | 拿到什么 |
|---|---|---|
| message | 起点,按会话筛出那 8 条消息 | 所有相关消息 |
| message\_edit\_info | message_row_id = message._id |
筛出被编辑过的 4 条 |
| chat | _id = message.chat_row_id |
确认这些消息属于哪个会话 |
| jid | _id = chat.jid_row_id |
确认发送者是谁 |
| message\_ftsv2\_content | docid = message._id |
原始消息内容 (编辑前的文本) |
最后一条是点睛之笔:编辑功能只会改主表里的文本,而全文索引表里还留着没被改的那份。这也解释了一个新手常问的问题——「为什么同一个库里有两张表都存消息文本」。答案就一句:一张给前台看(改得动),一张给搜索用(改不着)。
4.6.2 message\_type 怎么查
2024 年团体赛问「message_type 为哪个值代表表情包(Sticker)」,答案是 20。题解的做法是:先在聊天记录里找到那条表情包消息,把它的接收时间转成时间戳,再去库里按时间筛出那一条,看它的 message_type。
这是这类题最稳的解法——不要背枚举表,用一条已知的消息反查。如果你不确定某个值是什么,用这句一次看清整个库的类型分布:
SELECT message\_type, COUNT(\*) AS n, substr(text\_data, 1, 30) AS sample FROM message GROUP BY message\_type ORDER BY n DESC;
每行给出一类消息的数量和一条样本内容,对照样本就能把枚举值反推出来。零记忆量,且绝对不会错。
i iOS 那边同理
ZWAMESSAGE.ZMESSAGETYPE 也可以这样自查:把 ZMESSAGETYPE 分组、每组抓一条 ZTEXT 看,就能自己建一张映射表。前面 4.3 节的「46 = 投票」就是这么来的。
4.7 本章真题索引(附原题)
下面把本章涉及的真题逐题列出——题干与选项按题解原文照录(含原文的繁体用字、半角标点与大小写,不作改写);每题只标「考的是哪个库、哪张表、哪个字段」,完整答案与解题过程见第九章。
2021G-Q30 | 选择题
库/表:wa.db · 联系人表
特普的电话中的 WhatsApp 账号 [email protected] 中,有哪些其他人的 WhatsApp 用户数据记录?
A. [email protected]
B. [email protected]
C. [email protected]
D. [email protected]
考点:从通讯录找 WhatsApp 账号
2021I-Q42 | 选择题
库/表:iOS WhatsApp 群组
多选题阿力士 iPhone XR 中的 WhatsApp 群组【团购-新鲜猪肉牛肉-东涌群组-9/30】有以下哪一个成员?
A. [email protected]
B. [email protected]
C. [email protected]
D. [email protected]
考点:群组成员判定
2023G-Q8/Q9 | 填空/简答(原题无选项)
库/表:ChatStorage.sqlite(ZWAGROUPINFO、ZWACHATSESSION)
2023G-Q8
无人机卖家的电话号码是多少?(检材:手机(Android))
2023G-Q9
李佩妍在 Facebook 建立了一个群组, 该群组的名称是什么?(检材:手机(IOS))
考点:群创建者;JID 前半段 = 手机号
2023G-Q82 | 填空/简答(原题无选项)
库/表:msgstore.db(五表联查)
在潘志辉手机华为 P30 Pro 的 WhatsApp 与华为 NOVA 5T 的 WhatsApp 的对话曾被修改过, 请找出修改前的内容.
考点:编辑前的消息原文
2023I-Q55 | 选择题
库/表:ChatStorage.sqlite
根据 ChatStorage.sqlite, 哪些对话已锁定?
A. [email protected]
B. [email protected]
C. [email protected]
D. [email protected]
E. status@broadcast
考点:「锁定」对话(题解翻遍全库未找到相关字段)
2024G-Q11 | 填空/简答(原题无选项)
库/表:msgstore.db · message_type
应用程序 WhatsApp 的数据库(msgstore.db)中, 哪个 message\_type 代表发送的内容是表情包(Sticker)?
考点:枚举值 = 20 是表情包
2024G-Q17 | 填空/简答(原题无选项)
库/表:WhatsApp JID
于\"三五成群\"群中,电子表格文件 Personal\_data.xlsx 是由哪一个电话号码发送到该群组的?
考点:从 JID 反推发送者号码
2024G-Q90 | 填空/简答(原题无选项)
库/表:ZWAMEDIAITEM.ZMEDIALOCALPATH
Alice 在 2024 年 8 月 19 日收到了一个包含 15 个人个人资料的 Excel 文件, 她是从哪一个平台下载的?
考点:文件落盘路径
2025G-Q158 | 选择题
库/表:DeviceAgents.sqlite · ZWASMBDEVICEAGENT
根据镜像文件\"FUNG\_CC\_mobile.zip\"已登录的 WhatsApp 账号, 连同本机在内已登录了多少个设备
A. 1
B. 2
C. 3
D. 4
E. 5个以上
考点:已登录设备数
2025G-Q181/Q182/Q183 | 填空/简答(原题无选项)
库/表:ChatStorage.sqlite+群 ID 格式
2025G-Q181
在所有手机中, 有哪一个 WhatsApp 群组是被封存的, 它的名称是什么(检材:冯子超的手机)
2025G-Q182
根据上一题, 该 WhatsApp 群组的 WhatsApp ID 是什么(检材:冯子超的手机)
2025G-Q183
根据上一题, 这个群组的建立者的完整手机号码(检材:冯子超的手机)
考点:存档群组、建群者、建群时间
2025G-Q187 | 填空/简答(原题无选项)
库/表:ChatStorage.sqlite · Media/Profile/
这个群组的现有的头像图片的完整文件名称
考点:群头像文件名
2025I-Q50/Q52/Q54 | 选择题
库/表:ZWACHATSESSION
2025I-Q50
即时通讯软件 WhatsApp 中, 封存了下列哪个聊天群?(检材:冯子超的手机)
A.凤凰VIP会员心得交流群
B.币淘 群组1
C.Sportsmen
D.Titus Wong Manson Finance
2025I-Q52
即时通讯软件 WhatsApp 中, 下列哪个是群组\"Investors\"的管理员(检材:冯子超的手机)
A.只有 i)
B.只有 i) 和 ii)
C.只有 ii) 和 iii)
D.以上皆是
2025I-Q54
即时通讯软件 WhatsApp 中, 社群名称是什么(检材:冯子超的手机)
考点:存档、群组管理员、社群名称
2025I-Q58 | 填空/简答(原题无选项)
库/表:ZWAMESSAGE+ZWAMESSAGEINFO
承上题, 总共在多少个投票活动中作出了投票
考点:投票消息与 Protobuf 详情
2025I-Q67/Q68/Q74 | 选择题
库/表:ZWAMEDIAITEM
2025I-Q67
在 WhatsApp 与 [email protected] 聊天对话中, 于 2025-05-16 11:33:39 时的信息所传送的座标(WGS 84)是(检材:梁燕玲的手机)
2025I-Q68
在 WhatsApp 与 [email protected] 聊天对话中, 于 2025-05-16 11:33:39 时的信息所传送的座标(WGS 84)所指的餐厅英文名称是(检材:梁燕玲的手机)
2025I-Q74
WhatsApp 聊天群组 Happy Sharing within 3 于 2025-04-17 10:12:34 传送的 WGS 84 座标是多少(检材:梁燕玲的手机)
A.22.323436345441, 113.276894376508
B.22.326923370361, 114.168403625488
C.21.239876452236, 115.925422314543
D.20.124955642236, 114.168403625488
考点:位置消息的精确坐标
五、其他六个常考库
除了相册和 WhatsApp,近五年还有几个库反复出现。它们的共同特点是体量小、表少、但每次都问到痛处。
5.1 NoteStore.sqlite:被加密的备忘录(2022 个人赛)
路径:/AppDomainGroup-group.com.apple.notes/NoteStore.sqlite
题目问「林浚熙手机里有一个备忘录被上了锁,这个备忘录的名称是」,下一题追问「承上题,上述备忘录的内容有一串数字是」。
核心表:ZICCLOUDSYNCINGOBJECT——这张表里能看到哪些笔记是加密的。题解特别指出一个坑:「这里有两个脚本被加了密」,也就是说光看「有没有加密」还不够,得解密之后才知道哪个里面真有数字。
解密是这题最难的部分,题解也踩了两个坑,值得完整记下来:
- 坑一:现成解析脚本集体失效
——题解判断是 iOS 新版本 Notes 的库结构变了,「现有的解析脚本都不能顺利解析」。解法是改脚本里取字段的那两行:注释掉 AppleNoteStore.rb 中 Skipping Note ID 附近的整个 if/else 块,只保留赋值那一行;把 AppleNote.rb 里 to_csv、generate_html 两个函数引用的账号名、文件夹名改成空串;
* 坑二:环境必须在 Linux
——题解明说「不建议使用 Windows,因为在编译 Ruby 的 OpenSSL Gem 时会出问题」;如果 bundle install 卡住,是网络问题,换源到国内镜像即可;
* 最后一步
:拿弱口令字典跑 notes_cloud_ripper.rb,爆出密码 234567,解出备忘录标题 Halo、内容含 123456。
! 这题的知识点不在 SQL,在「工具链」
加密备忘录考的是你能不能把开源工具改到能用:看懂脚本在哪取字段 → 改掉不兼容的地方 → 换源解决依赖 → 跑起来。这也是本专题反复出现的一个信号:美亚杯的难题,难在工具链而不是知识点。
5.2 CallHistory.storedata:通话记录(2023、2024 都考了)
路径:/var/mobile/Library/CallHistoryDB/CallHistory.storedata
这张库有两个考点。第一是认表:2023 年个人赛问「哪份表格显示了通话记录」,答案是 ZCALLRECORD,干扰项是 ZCALLBPROPERTIES 和三个 Core Data 通用表。
第二是用表:2024 年个人赛要求「2024 年 8 月 30 日下午 2 点后 Emma 共致电 Clara 多少次」,题解只写了一句 SQL,把「时间过滤」和「按号码过滤」一次做完:
SELECT \* FROM ZCALLRECORD WHERE ZADDRESS = 63791704 AND ZDATE >= 746690400;
两个字段要记住:ZADDRESS(对方号码)和 ZDATE(通话时间,Cocoa 时间戳)。至于那个 746690400 是从哪来的——它是「2024-08-30 下午 2 点」换成的 Cocoa 秒数,第 6 章专门讲怎么算。题解还补了一句:Autopsy 里也能看到自动识别到的结果,但显示的是 UTC 时间——这就是时间题的第二个坑:时区。
5.3 AddressBook.sqlitedb:通讯录
路径:/var/mobile/Library/AddressBook/AddressBook.sqlitedb
2024 年个人赛用它来查号码归属:先在通讯录里找到 Clara 的手机号码,再拿这个号码去通话记录库里做条件。
这是「两个库接力」的标准范式,也是本专题最值得练熟的一条链:
✓ 「号码接力」三步
① AddressBook.sqlitedb 查出号码 ↔ 姓名的对应;② 拿号码去 CallHistory.storedata/WhatsApp 库/短信库做过滤条件;③ 结果再回通讯录翻译成人名。题目问「某人打了多少次电话」,考的就是这条链,不是某一张表。
5.4 sms.db:短信库(2023 个人赛点名)
iOS 的短信库在 /var/mobile/Library/SMS/sms.db,核心表是 message 和 chat——命名和 WhatsApp 安卓版很像(都是普通小写),容易混,注意区分路径。2023 年个人赛的候选题里点了它的名,考点是「短信记录在哪张表」这类认定题。
5.5 Manifest.db:iOS 备份的「文件总目录」
这个库不是 App 的,而是整份 iOS 备份的索引,路径就在备份根目录的 /var/ 下。
| 表/字段 | 含义 | 考点 |
|---|---|---|
| Files | 备份中所有文件的总表 | 想知道某个文件在备份里叫什么名字,查它 |
fileID |
备份中的哈希文件名 | 备份目录下看到的那串乱码名 |
domain |
所属域 | 如 AppDomain-com.tencent.xin、MediaDomain |
relativePath |
原始路径 | 还原「这个文件原本在手机上的哪个位置」 |
flags /file |
标志与二进制内容 | 部分版本直接把小文件内容存在库里 |
结合 2.3 节讲的那四个文件(Manifest.db/Manifest.plist/Status.plist/*_DEC)一起记,iOS 备份这一关就通了。
5.6 安卓三大系统库
安卓的系统数据都由 Provider 提供,库名固定,遇到就是送分:
| 库 | 路径(均在 /data/data/ 下) | 核心表 | 考点 |
|---|---|---|---|
| contacts2.db | com.android.providers.contacts/databases/ |
contacts 、raw_contacts、data、mimetypes |
通讯录。注意要联 mimetypes 才能知道某一行数据是电话还是邮箱 |
| mmssms.db | com.android.providers.telephony/databases/ |
sms 、threads |
短信。threads 是会话,sms 是单条 |
| calllog.db | com.android.providers.contacts/databases/ |
calls |
通话记录。type 字段区分呼入/呼出/未接 |
i 安卓与 iOS 的命名差异,一眼区分
iOS 大多 Z 开头、驼峰式(ZASSET、ZCALLRECORD);安卓大多小写下划线(message、chat_row_id、text_data)。看到表名就能判断手上这个库来自哪个平台,考场上看错平台是很亏的。
5.7 微信相关:iOS 与安卓
微信在近五年的题量不算多,但 2024 年个人赛连着出了几道,值得单独记一下。
| 对象 | 路径/表 | 考点 |
|---|---|---|
| iOS 聊天库 | com.tencent.xin/Documents/<32位哈希>/message_2.sqlite |
消息主表。图片消息存的是 XML 字符串,不是纯文本 |
| iOS 视频号 | .../finder/db/finder_main.db · 表 finderContactTable3 |
followState = 1 表示已关注 (2025I-Q48)。注意这是普通小写命名,不是 Core Data |
| 安卓配置 | com.tencent.mm_preferences.xml |
最后登录的微信 ID(2024I-Q52) |
| iOS 配置 | com.tencent.mm 相关 plist |
微信 ID、版本号 |
5.7.1 微信图片消息的 XML:一个高频卡点
2024 年个人赛的解法里,有一个结构必须认识。题目要求找出「Emma 报案时拍摄卡片的经纬值」,题解的发现是:微信数据里编号最大的那个文件(314.pic)对应消息库里的编号,打开 message_2.sqlite 能看到这条消息的原始 XML:
题解抓住了其中最容易被忽略的一节:m_assetUrlForSystem 里的那个 GUID(F58B98FE-...-F23AF15DCFCA),它是系统相册的资产标识。拿着这个 GUID 回到 iOS 相册库 Photos.sqlite 一搜,就找到了对应的照片记录,经纬度立刻出来了。
✓ 这条链子是本专题最漂亮的一环
微信消息库 → 消息 XML 里的 GUID → 相册库 ZASSET.ZUUID → 经纬度。跨了两个 App、三个库,靠一个 GUID 串起来。2024 年个人赛还有一题(比较 IMG_0008.HEIC 与 5005.JPG)走的是同一条链:先在相册库确认 IMG_0008.HEIC 的存在,再发现它的 UUID 出现在微信聊天记录里,从而判断后者是发送时生成的压缩副本。
同一道 XML 里另外几个字段也值得记一下,都是「文件在哪、是不是加密的」这类题的基础:
| 字段 | 含义 | 用途 |
|---|---|---|
aeskey |
图片的 AES 密钥 | 图片文件本体被加密 ,要用它解密 |
encryver |
加密版本标记 | 判断加密方式 |
md5 |
文件 MD5 | 与磁盘上的 .pic 文件比对,确认对应关系 |
originsourcemd5 |
原图 MD5 | 区分原图与压缩图 |
filekey |
文件名键 | 格式为 <wxid>_<编号>_<时间戳>,编号与磁盘上的文件名对应,时间戳可换算成收图时间 |
m_assetUrlForSystem |
系统相册 GUID | 通向 Photos.sqlite 的桥 |
5.8 本章真题索引(附原题)
下面把本章涉及的真题逐题列出——题干与选项按题解原文照录(含原文的繁体用字、半角标点与大小写,不作改写);每题只标「考的是哪个库、哪张表、哪个字段」,完整答案与解题过程见第九章。
2022I-Q37/Q38 | 填空/简答(原题无选项)
库/表:NoteStore.sqlite · ZICCLOUDSYNCINGOBJECT
2022I-Q37
林浚熙手机里有一个备忘录被上了锁,这个备忘录的名称是(检材:林浚熙的手机)
2022I-Q38
承上题,上述备忘录的内容有一串数字是(检材:林浚熙的手机)
考点:加密备忘录:先判加密、再爆破解密
2023I-Q64 | 选择题
库/表:CallHistory.storedata · ZCALLRECORD
根据 CallHistory.storedata, 哪份表格显示了通话记录?
A. ZCALLBPROPERTIES
B. ZCALLRECORD
C. Z\_2REMOTEPARTICIPANTHANDLES
D. Z\_METADATA
E. Z\_MODELCACHE
F. Z\_PRIMARYKEY
考点:认表(干扰项是 Core Data 通用表)
2024I-Q2 | 选择题
库/表:CallHistory.storedata+AddressBook.sqlitedb
2024 年 8 月 30 日下午 2 点后 Emma 共致电 Clara 多少次?
A. 85
B. 86
C. 87
D. 88
考点:号码接力 + 时间过滤
2024I-Q52 | 填空/简答(原题无选项)
库/表:com.tencent.mm_preferences.xml
根据\"com.tencent.mm\_preferences.xml\", David 的手机最后登录微信的微信 ID 是?
考点:微信 ID
2024I-Q1/Q19/Q12 | 选择题
库/表:message_2.sqlite
2024I-Q1
Emma 和 Clara 的微信聊天记录, Emma 最后到警署报案并拍摄写有报案编号的卡片, 拍摄时的经纬值是多少?(检材:Emma 的手机)
A. 22.451721666667, 114.171853333333
B. 22.451553333333, 114.172845
C. 22.451928333333, 114.170503333333
D. 22.451638333333, 114.16993
2024I-Q19
Emma 的 iPhone XR 中\"IMG\_0008.HEIC\"的图像与相片名字为的\"5005.JPG\"看似为同一张相片, 在数码法理鉴证分析下, 以下哪样描述是正确?(检材:Emma 的手机)
A. 储存在不同的.db檔案里
B. 有不同哈希值
C. IMG\_0008.HEIC 为原图, \"5005.JPG\"为并非原图
D. IMG\_0008.HEIC 和名字\"5005.JPG\"是同一张相片
2024I-Q12
Emma 发送了多少张 .PNG 图片给 Clara, 证明自己正被人追债?(检材:Emma 的手机)
A. 6
B. 7
C. 8
D. 9
考点:图片消息 XML、跨库 GUID 关联
2025I-Q47/Q48 | 选择题
库/表:finder_main.db · finderContactTable3
2025I-Q47
请指出即时通讯软件 WeChat 的 WeChat ID(检材:冯子超的手机)
2025I-Q48
承上题, 这个 WeChat ID 关注了多少个视频号(检材:冯子超的手机)
A.1
B.2
C.3
D.4
考点:视频号关注(followState = 1)
2025I-Q10/Q30 | 选择题
库/表:App 列表 + 相册库
2025I-Q10
安装了以下即时哪个通讯软件?(检材:陈民浩的手机(iOS))
A.只有 i) 和 ii)
B.只有 i), ii) 和 iii)
C.只有 i), ii) 和 iv)
D.以上皆是
2025I-Q30
相册中有两张 GIF \"IMG\_0057.GIF\"及\"IMG\_0062.GIF\", 请指出由哪一个软件拍摄(检材:冯子超的手机)
A.Infltr
B.Discreet
C.Meitu
D.Prisma
考点:从应用与数据判「装了什么/用了什么」
六、时间戳:跨库比对的命门
先说一个判断:近五年手机数据库题里,超过一半的题目都会碰到时间——「某时刻的消息」「某天之后打了几次电话」「什么时候建的群」。而时间题的失分,九成不是不会 SQL,是没换算对。
所以这一章只有一件事:把时间戳讲透。
6.1 为什么不换算就必错
数据库里存的时间,绝大多数不是给人看的那种,而是一串整数——「从某个纪元开始数,过了多少秒」。你拿到的 746690400 看不出是几点,而题干问的是「下午 2 点后」。换算这一步不做,后面全废。
6.2 五种时间格式,一张表分清
| 格式 | 纪元起点 | 位数感觉 | 常见于 |
|---|---|---|---|
| UNIX 时间戳 | 1970-01-01 00:00:00 UTC | 10 位(秒)/13 位(毫秒) | 安卓(多数为毫秒)、WhatsApp 安卓、日志 |
| Cocoa Core Time | 2001-01-01 00:00:00 UTC | 8—9 位 | iOS 全系 (ZDATE、ZDATECREATED) |
| WebKit/Chrome 时间 | 1601-01-01(Windows FILETIME 系) | 17 位(微秒) | 浏览器历史、部分系统日志 |
| 毫秒时间戳 | 同 UNIX,但单位是毫秒 | 13 位 | 安卓库最常见 |
| 可读字符串 | — | 如 2025-05-16 11:33:39 |
配置类、日志类。注意它不自带时区 |
✓ 一眼判断法是哪种
看位数。8 位半(746690400)左右,且算出来落在 2001 年之后 → Cocoa;10 位 → UNIX 秒;13 位 → UNIX 毫秒;17 位 → 微秒级。如果换算出来的年份离谱(比如 1970 或 2080),先怀疑纪元错了,再怀疑单位错了。
6.3 Cocoa ↔ UNIX:只差一个常数
这是本文最该背下来的一个数字:
Cocoa Core Time + 978307200 = UNIX 时间戳 UNIX 时间戳 - 978307200 = Cocoa Core Time 为什么?Cocoa 从 2001-01-01 起算, 而 2001-01-01 00:00:00 UTC 对应的 UNIX 时间戳正好是 978307200。
在 SQL 里换算成可读时间,就一句:
-- Cocoa → 可读时间(本地时间) SELECT datetime(ZDATE + 978307200, 'unixepoch', 'localtime'); -- UNIX(秒)→ 可读时间 SELECT datetime(ZDATE, 'unixepoch', 'localtime'); -- UNIX(毫秒)→ 可读时间 SELECT datetime(ZDATE / 1000, 'unixepoch', 'localtime'); -- 反向:把题干的时刻换成时间戳,用作筛选条件 SELECT strftime('%s', '2024-08-30 14:00:00') - 978307200;
最后那句反向换算是最实用的——题干给的是人话,你要给它换成库里那串数字。2024 年个人赛那道通话题用的 746690400,就是这么算出来的。
6.4 用真题验算一遍
空口说不算数,拿两道真题的数字实际算一次。
| 真题 | 库里那个数 | 换算 | 结果 |
|---|---|---|---|
| 2024I-Q2「8 月 30 日下午 2 点后」 | 746690400 (Cocoa) |
+978307200 = 1724997600,再转 UTC |
2024-08-30 06:00:00 UTC 即香港时间 14:00 ✓ |
| 2025G-Q183「建群时间」 | 1563607573 (UNIX) |
直接转 UTC | 2019-07-20 07:26:13 UTC 即香港时间 15:26:13 ✓ |
两个结果都和题解一致。特别注意第二个——UTC 是 07:26,加上 8 小时才是题解写的 15:26。这就是下一节要说的时区问题。
6.5 时区:时间题最大的坑
同一个时间戳,在不同时区显示的时间是不同的。香港是 UTC+8,北京也是 UTC+8,所以美亚杯的答案通常按当地(香港)时间给。但工具默认可能显示 UTC。
- 血泪案例
:2024 年个人赛题解明确写道,「在 Autopsy 中也可以看到自动识别到的结果(时间显示为 UTC)」——如果直接抄工具显示的数字,就会差 8 小时,和题目给的「下午 2 点」对不上;
* SQL 里的开关
:加 'localtime' 是按运行 SQL 的那台机器的时区换算。如果你的电脑不在东八区,结果会偏——稳妥做法是先按 UTC 算出来,再手动 ±8 小时,这样可控;
* 另一种更稳的写法
:datetime(值, 'unixepoch', '+8 hours'),直接写死偏移量,不依赖运行环境。
! 答题时养成一个习惯
凡是写时间的答案,在答案里带上时区或写明换算依据。例如「2025-04-29 08:31:02(HKT,即 UTC+8)」——这样即使阅卷方用的时区不同,也能看出你的思路是对的。
6.6 换算时的四个细节
| 细节 | 说明 | 怎么办 |
|---|---|---|
| 秒还是毫秒 | 安卓库常常是毫秒,比 Cocoa 大三个数量级 | 先看位数。13 位一律先 /1000 |
| 浮点数 | ZDATE 可能是小数(含毫秒的小数部分) |
SQLite 的 datetime() 能处理小数,不必先取整 |
| 0 与负值 | Cocoa 的 0 就是 2001-01-01;负值表示 2001 年之前 | 不要看到负值就以为数据坏了 |
| NULL 空值 | 字段为空时换算结果是 NULL,在 WHERE 里会比较失败 | 先 WHERE ZDATE IS NOT NULL 过滤掉空值 |
第三点有个现成的例子:2023 年个人赛那道联表题,题解在分析正确选项时特意加了一句 date.arrival_timestamp_HK IS NOT NULL——就是为了排掉空值。它说明出题人知道新手会在这里栽。
七、联表查询:把两张表接起来
如果只能从这篇文章带走一个技能,带走这个。近五年难度高的数据库题,几乎都是联表题——因为单表能问的东西有限,一旦问「谁发的、发到哪个群、那个群叫什么名字」,就必然跨表。
7.1 为什么必须联表:一个典型结构
以安卓 WhatsApp 为例,信息是刻意拆开放的:
message 表 jid 表 ┌────────┬──────────────┐ ┌──────┬────────────────────────┐ │ \_id │ chat\_row\_id │ │ \_id │ user │ ├────────┼──────────────┤ ├──────┼────────────────────────┤ │ 101 │ 7 │──┐ │ 7 │ [email protected] │ │ 102 │ 7 │ │ └──────┴────────────────────────┘ └────────┴──────────────┘ │ ▲ │ │ chat 表 └──────────────┘ ┌──────┬──────────────┐ │ \_id │ jid\_row\_id │ ├──────┼──────────────┤ │ 7 │ 7 │ └──────┴──────────────┘
消息表里没有「是谁发的」,只有一个 chat_row_id;会话表里没有名字,只有一个 jid_row_id;号码在第三张 jid 表里。想知道「这条消息是谁发的」,必须穿过三张表。这不是设计得不好,而是数据库的规范做法——避免同一个号码重复存一万遍。
7.2 外键的三种长相
找外键这件事看着玄,其实命名很有规律。记住三种长相,能解决九成情况:
| 长相 | 例子 | 怎么猜它指向谁 |
|---|---|---|
X_PK |
ZASSET.Z_PK 、ZWAMESSAGEINFO.Z_PK |
这是被指向方(主键) |
X_行号 型 |
chat_row_id 、message_row_id、jid_row_id |
xxx_row_id → 指向 xxx 表的 _id |
ZXXX 同名型 |
ZWAMESSAGE.ZMESSAGEINFO → ZWAMESSAGEINFO.Z_PK;ZASSET.ZEXTENDEDATTRIBUTES → ZEXTENDEDATTRIBUTES.Z_PK |
字段名就是目标表的表名 (去掉 Z 前缀、补全称) |
✓ 不确定指向谁?两条命令搞定
① PRAGMA foreign_key_list(表名); —— 如果建表时声明了外键,这里直接列出来;② 没声明的话,用值去试:把那个字段的值拿到候选表里 WHERE Z_PK = 那个值 查一下,命中就说明关系成立。第二种在 iOS 的 Core Data 库里特别常用,因为 Core Data 默认不在库里声明外键。
7.3 四种 JOIN,用「两本名册」理解
把两张表想成两本名册:左表是「消息名册」,右表是「号码名册」,两本都有「编号」这一列,JOIN 就是按编号把两边对齐。
| 写法 | 含义 | 通俗说法 | 什么时候用 |
|---|---|---|---|
| INNER JOIN | 只保留两边都能对上的 | 两本都有的人才留下 | 最常用 。要找「有人有号码的完整记录」 |
| LEFT JOIN | 保留左表全部,右表没有就填 NULL | 左表是主角,右表是补充 | 要找「左表全部记录,顺便看看右表有没有」 |
| RIGHT JOIN | 保留右表全部,左表没有就填 NULL | 反过来 | 2025I-Q58 那段 SQL 用的就是这个 |
| CROSS JOIN | 两边全组合 | 笛卡尔积,结果会爆炸 | 几乎不用。忘了写 ON 条件就会退化成它 |
! 最常见的翻车:忘了 ON
写成 FROM A JOIN B 而不写 ON A.x = B.y,SQLite 不会报错,而是给你两表行数相乘的结果。1000 行 × 5000 行 = 500 万行,查询卡死,你还以为是自己写错了条件。看到查询迟迟不返回,先检查 ON 有没有写。
7.4 四种真题联表范式
范式一:顺着外键链一路穿(2023G-Q82,五张表)
这是本文最长的一条链,也是安卓 WhatsApp 的经典结构。题解把起点、过滤器、终点列得很清楚:
| 顺序 | 表 | 接入条件 | 作用 |
|---|---|---|---|
| ① | message |
起点 | 筛出目标会话的 8 条消息 |
| ② | message_edit_info |
message_row_id = message._id |
筛出 4 条被编辑过的 |
| ③ | chat |
_id = message.chat_row_id |
确认所属会话 |
| ④ | jid |
_id = chat.jid_row_id |
确认发送者是谁 |
| ⑤ | message_ftsv2_content |
docid = message._id |
取编辑前的原文 ,答案是「再換busd」 |
这条链的价值在于它示范了「一路 JOIN 到底」的写法——从一个 _id 出发,每经过一张表就顺手取走一个信息,最后拼出完整答案。写的时候不要一次想全,一段一段加,每加一段就跑一次看结果。
范式二:JOIN 到附属表取详情(2025I-Q58)
前面 4.3 节讲过那条 SQL,用的是 RIGHT JOIN:ZWAMESSAGE.ZMESSAGEINFO = ZWAMESSAGEINFO.Z_PK,目的是把 Protobuf 格式的 ZRECEIPTINFO 取出来。范式特征是「主表筛、附表取」——筛选条件写在主表字段上,输出列却来自附表。
范式三:题目直接考 SQL 本身(2023I-Q98)
这道题很特别——它不给数据,只给你两个库的结构和四个 SQL 选项,问哪一个能正确「找出谁前往莫斯科」,并输出转换后的本地时间。题解逐条分析了每个选项,四个错误点全都不一样:
| 选项 | 问题 | 错在哪类知识 |
|---|---|---|
| A | 语法本身能跑,但缺了 IS NOT NULL 之类的限定 |
题解认为 A 与 D 都符合题意,属题目不严密 |
| B | ON customer_id = date.id —— 两表连接时字段名没带表前缀,应是 cus.customer_id |
联表时字段名歧义 |
| C | 没有做时间戳转换 | 漏了 datetime(..., 'unixepoch', 'localtime') |
| D | 与 A 相比多了 IS NOT NULL,把空值排掉 |
正确选项(题解认可) |
这道题把第七章要讲的三个坑一次性考全了:字段名要带表前缀、时间要转换、空值要过滤。注意正确答案里 datetime(date.arrival_timestamp_HK, 'unixepoch', 'localtime') AS arrival_time_hk 这一句——它同时做了三件事:指定源字段、指定纪元、指定时区,最后还给结果改了列名。
范式四:跨库接力(2024I-Q2)
这类题目不在一张库内部 JOIN,而是手工接力:先在通讯录库查出号码,再拿号码去通话记录库做条件。因为 SQLite 默认不能在两条 SQL 里跨文件 JOIN(除非用 ATTACH),所以考场上的常规做法就是分两次查、人脑接。
✓ 也可以让 SQLite 帮你接
先进命令行执行 ATTACH DATABASE 'AddressBook.sqlitedb' AS ab;,之后就能在一条 SQL 里 JOIN ab.ZABCDRECORD 了。不过实践中手工接力更快也更不容易错——结果集小的时候,两次查询 + 复制粘贴比写跨库 SQL 稳。
7.5 聚合:怎么回答「多少次」「多少个」
「多少次电话」「多少张图」「多少个群」这类问题,靠的是聚合函数:
| 函数 | 作用 | 典型题目 |
|---|---|---|
| COUNT(\*) | 数行数 | 「打了多少次电话」——过滤完直接 COUNT |
| COUNT(DISTINCT 字段) | 数去重后的个数 | 「涉及多少个人」——同一人发多条只算一个 |
| GROUP BY | 按某字段分组 | 「每个人各发了多少条」 |
| SUM /MAX/MIN | 求和/最大/最小 | 「最后一通电话是什么时候」(MAX 时间) |
一个组合技,专治「有多少个不同的 X」:
SELECT COUNT(DISTINCT ZTOJID) AS 联系过的人数 FROM ZWAMESSAGE WHERE ZISFROMME = 1;
! COUNT 的两个常见误用
① COUNT(字段)不数 NULL,COUNT(*) 才数所有行——想数「全部尝试次数」要用后者;② 忘记 DISTINCT 会重复计数——题目问「多少个人」你却返回「多少条消息」,这是最常见的失分点。
7.6 联表五个坑,逐个拆
| 坑 | 症状 | 解法 |
|---|---|---|
| 忘了 ON | 结果行数暴增、查询卡死 | 检查每个 JOIN 后面都有 ON |
| 字段名歧义 | 报 ambiguous column name |
所有字段都加表前缀 :cus.customer_id 而不是 customer_id |
| 空值参与比较 | 条件筛掉/漏掉记录 | IS NOT NULL ;空值不能用 = 比较,要用 IS NULL |
| 类型不匹配 | 数字和字符串比较,结果对不上 | JID、号码这类在库里可能存成文本,比较时加引号 |
| 以为只有一张表有数据 | 查不到就下结论「数据不存在」 | 同一信息可能在多张表里各存一份 (如 ZASSET 与 ZEXTATTR 的经纬度) |
八、SQL 速成:够用就好的八个句式
你不需要成为 SQL 工程师。看懂 + 改写上面这些真题 SQL,需要掌握的就只有八个句式。这一章按「从最常用到最不常用」排。
8.1 八个句式
| 句式 | 样子 | 干什么 | 真题里长啥样 |
|---|---|---|---|
| SELECT | SELECT 字段1, 字段2 FROM 表 |
选列 | SELECT Z_PK, ZFILENAME FROM ZASSET |
| WHERE | WHERE 条件 AND 条件 |
筛行 | WHERE ZADDRESS = 63791704 AND ZDATE >= 746690400 |
| LIKE | WHERE 字段 LIKE '%.HEIC' |
模糊匹配。% = 任意多个字符,_ = 一个字符 |
WHERE ZFILENAME LIKE '%.HEIC' |
| ORDER BY | ORDER BY 字段 DESC |
排序(DESC 倒序) | 按时间倒序找「最后一条」 |
| LIMIT | LIMIT 10 |
只看前 N 行 | 先看看数据长什么样 |
| GROUP BY | GROUP BY 字段 |
分组统计 | GROUP BY message_type 反查枚举含义 |
| JOIN ... ON | JOIN 表2 ON 表1.x = 表2.y |
联表 | 见第七章 |
| IN / BETWEEN | WHERE 字段 IN (a, b) |
多值匹配 / 区间 | 筛多个号码、某个时间段 |
8.2 三个最实用的现成模板
下面三段可以直接背下来改字段名用。
模板一:查「某时刻发生了什么事」
SELECT \* FROM ZWAMESSAGE WHERE ZMESSAGEDATE BETWEEN 749000000 AND 749100000 ORDER BY ZMESSAGEDATE;
模板二:查「某人物相关的所有记录」
SELECT \* FROM ZCALLRECORD WHERE ZADDRESS = <号码> AND ZDATE >= <起始时间戳> ORDER BY ZDATE;
模板三:自查某字段有哪些取值(万能起手式)
SELECT <可疑字段>, COUNT(\*) AS n FROM <表名> GROUP BY <可疑字段> ORDER BY n DESC LIMIT 30;
第三个模板是本文最有用的「万能开局」。任何陌生的枚举字段(消息类型、状态码、方向、来源),先用它把取值分布拉出来,数量多的那几类通常就是「普通消息」,稀有的那几类就是「考点」。
8.3 真题剖析:一道考 SQL 语法的题(2023I-Q46)
这道题考得很特别——题干说「一个名为 Account 的表拥有 5 个列」,然后问「以下哪一个指令会产生错误讯息」:
| 选项 | 语句 | 会不会报错 | 原因 |
|---|---|---|---|
| A | SELECT * from Account WHERE name='Alex' OR '1'=1 |
不报错 | '1'=1 恒真,是全表返回——这是经典的 SQL 注入写法,语法完全合法 |
| B | ... UNION SELECT NULL, NULL, NULL, NULL |
会报错 | 左边 5 列、右边 4 列,列数不一致。报错信息是 The used SELECT statements have a different number of columns |
| C | SELECT * from Account WHERE name='Candy' ORDER BY 6 |
会报错 (本题答案) | 表只有 5 列,ORDER BY 6 超出上限 |
| D | SELECT name FROM sys.tables |
会报错 | sys.tables 是 SQL Server 的系统视图,SQLite 里不存在这张表 |
题解的判断是:「五个列,ORDER BY 6 显然超出上限」,选 C。它还老老实实补了一句——「B 也会报错,但是和题意不符」。
i 这道题真正在考什么
考的是「你对 SQL 的边界有多熟」。四个选项分别踩在四种边界上:恒真条件、列数必须一致、排序列号不能越界、系统表不是通用的。这类题不需要你有检材,纯语法知识就能拿分,是性价比最高的分——考前花半小时把这几条边界过一遍就够。
8.4 用什么工具写 SQL
| 工具 | 适合什么 | 备注 |
|---|---|---|
| DB Browser for SQLite(DB4S) | ★ 首选。图形界面,能直接点开表看数据、写 SQL、导出 CSV | 五届题解里出现频率最高的数据库工具之一,免费 |
| Navicat Premium | 商业工具,界面更顺手,导出 CSV 方便 | 2025G-Q164 那道题就是「把 Navicat 的查询结果导出为 CSV 再筛」 |
| 命令行 sqlite3 | 脚本化、批量处理、.headers on 看列名 |
Linux 自带,适合配合 PRAGMA 用 |
| Python + sqlite3 | 批量导出表结构、自动化处理(见 2.5 节那段脚本) | 适合需要反复跑的活儿 |
✓ 导出 CSV 这一招别忘
很多题目的答案需要排序、筛选、比大小,在 SQL 界面里滚动看很痛苦。导出成 CSV 用 Excel 打开,筛选排序一秒搞定。2025 年团体赛题解就是这么处理相机型号那一列的。
九、近五年真题逐题还原(2021—2025)
前面八章讲的是「库、表、字段、怎么查」。这一章换个方向:把五届真题里真正考到数据库的题,按届一道一道摊开——题干和选项照抄题解原文,下面再分四段写解题思路——答案/用到的工具/知识点/解析,能有多条路的题在「解析」里给出「另解」。看完这一章,你会对「考试到底怎么考」有具体印象,而不是停在概念上。
i 读法:先看「原题怎么问」,再看四段解题思路
每一道题一张卡片,结构固定:题干与选项(照抄题解原文,选择题全部列出)+ 四段解题思路:
① 答案——以题解记录的为准,题解标了「存疑」或「官方答案有误」的照实标注;
② 用到的工具——这一步用什么开的库、拿什么算的哈希、跑的是哪个脚本;
③ 知识点——这道题真正在考的那条规则(库结构、字段语义、时间戳换算、联表方式);
④ 解析——解题过程分步写,能有多条路的地方写「另解」:题解的路子往往不是唯一解,考场上有更快的走法。
题号写法是「届次+赛道+题号」——2023G-Q82 里的 G 是团体赛,I 是个人赛;题干与选项按题解原文照录,含原文的繁体用字、半角标点与大小写,不作改写。
9.1 收录标准:什么样的题算「数据库题」
两年下来我一共挑出 87 道。挑的标准只有一条——答案的落点必须是某个库的某张表的某个字段,具体分三种情况:
- 问的就是数据库结构本身
:比如「哪个栏目标题可以显示接收方式」(2022I-Q34)、「哪份表格显示了通话记录」(2023I-Q64)——这类题不懂表名字段名就是零分。
* 必须用 SQL 才能算出来
:计数、求和、按条件筛、联表比对。火眼的界面给不了这种答案,只能自己写语句。
* 火眼解析不出来的库
:第三方 App 自建的库、被压缩或编码过的字段、以及加密的陈年格式。这类题只能手动开库。
! 一个容易误解的地方
「聊天记录在哪」不等于「这题要读数据库」。
像「王晓琳发出这个 PDF 的原因」「两人的对话显示了什么关系」这类题,答案虽然也躺在 WhatsApp 的库里,但火眼的聊天记录分析已经能直接给出来,不需要你写 SQL——所以没有计进这 87 道。
本章收的是「必须动数据库才能拿到答案」的那一批,这也是最难、最容易丢分的一批。
9.2 2021 年:数据库题刚冒头(6 题)
这一年数据库题很少,而且集中在两类:安卓 WhatsApp 的通讯录库和iOS 的蓝牙、相册数据库。考法也简单——知道去哪张表里翻就行。
9.3%202022%20年:个人赛独撑(10%20题)
2022%20年团体赛那道「WordPress%20比特币标题题」考的是服务器侧的%20MySQL,不属于手机数据库,本章不列。手机数据库题全部来自个人赛,而且出现了两个此前没考过、后来反复考的库:Photos.sqlite 与 NoteStore.sqlite。
✓%20这一年最值钱的一条经验
2022I-Q38 说明了一件事:现成工具解不出来的题,才是拉分题。
题解没有换工具,而是打开脚本源码、改掉取字段的那两行——因为「我们只需要标题和内容两个字段」,其他字段全都可以不管。
读懂脚本在取哪个字段,比换一个更贵的工具有用得多。
9.4%202023%20年:从「在哪张表」升级到「怎么把表接起来」(18%20题)
这一年题量翻倍,考法也变了:开始考联表。最有代表性的是团体赛 Q82——一道题串起%20WhatsApp%20安卓库的四张表;个人赛 Q98 干脆让你自己写一段%20SQL。
团体赛(9%20题)
个人赛(9%20题)
✓%20这一届给出的三张「答案地图」
① 系统表当排除项:Z_METADATA/Z_MODELCACHE/Z_PRIMARYKEY 是%20Core%20Data%20的固定配件,问「哪张表存业务数据」时先排除它们。
② 数字类答案多半要计数:45、7%20这两个答案,都是「按某个后缀或条件筛完再数」。
③ 跨库比对才会给对:Q58%20的%2010%20与%207%20就是一次典型陷阱。
9.5%202024%20年:成为主力题型(19%20题)
两届加起来%2019%20题,而且开始出现「同一批检材跨库关联」的考法:微信的聊天记录%20→%20关联相册库拿经纬度、档案名在相册库与微信库之间交叉验证。这一年也是实况照片(Live%20Photo)判定第一次成体系地出现。
团体赛(9%20题)
个人赛(10%20题)
✓%20这一年留下的三条可复用套路
① 「用时间换记录」:先把时间换算成时间戳,再去库里筛那一条(2024G-Q11、2024I-Q2 都是这么做的)。
② 「一个值串两个库」:UUID/GUID%20是跨库的桥(2024I-Q19)。
③ 「工具没显示,不代表库里没有」:UUID、后置镜头这类字段,火眼的界面不展示,但库里有(2024I-Q8)。
9.6%202025%20年:绝对主力(34%20题)
这一年两届一共%2034%20道,是全五届最多的一年,也是考法最成熟的一年:相册库连着%209%20道题、同一个字段的两组值要判断该信哪一个、还要从字段格式反推时间、从群组%20ID%20反推创建者手机号。
团体赛(17%20题)
个人赛(17%20题)
!%202025%20年这批题里,四处「答案或题干本身有问题」
这一届题解的复盘质量很高,把出题瑕疵都标了出来,值得单独记住:
① 检材标错:160—168%20题说用冯子超的%20iPhone,答案却取自陈民浩的%20iPhone;169—177%20题同理(2025G)。
② 字段用错:官方题解用 ZDATECREATED 排序,但「写进数据库的时间」应该是 ZADDEDDATE。
③ 题干与选项不符:Q164%20要求%20heic%20文件,实际答案却是%20JPG;Q166/Q167%20直接标「此题无解」。
④ 库内两值打架:同一文件的经纬度在两张表里不一致(2025I-Q40)。
考场上遇到这种题,把依据写清楚比猜一个答案更划算。
9.7%20回头看第三到第五章:库的出现频次
把上面%2087%20道题按「库」重新归一次类,就得到一张很实用的表——哪些库是必考的、哪些是一届一现的:
| 库 | 出现届次 | 代表题号 | 一句话考点 |
|---|---|---|---|
Photos.sqlite |
五届全勤%20(2021—2025) | 2021I-Q37;2022I-Q33/Q34/Q35;2023I-Q58/Q59;2024I-Q22/Q23;2025I-Q30/Q40/Q64;2025G-Q160—Q168 | 字段最多、坑最多。字段名(ZIMPORTEDBY…)、来源%20App、两组经纬度取舍、Live%20Photo%20判定、ZADDEDDATE vs ZDATECREATED |
ChatStorage.sqlite%20(iOS%20WhatsApp) |
四届:2021/2023/2024/2025 | 2021I-Q8;2023I-Q55/Q56/Q62;2024G-Q18;2025G-Q183/Q187/Q189;2025I-Q50 | 锁定/存档(ZWACHATSESSION)、ZWAMESSAGE 投票(type%2046)、群组%20ID%20拆手机号、头像文件名里藏时间戳 |
msgstore.db%20(安卓%20WhatsApp) |
三届:2021/2023/2024 | 2021G-Q30(wa.db);2023G-Q82;2024G-Q11 |
四表串链(message/message_edit_info/chat/jid)、message_type 取值 |
NoteStore.sqlite |
2022 | 2022I-Q37/Q38 | 加密备忘录要看 ZICCLOUDSYNCINGOBJECT,解密要改脚本取字段位置 |
CallHistory.storedata |
两届:2023/2024 | 2023I-Q64;2024I-Q2 | 表名 ZCALLRECORD;和 AddressBook.sqlitedb 接号码后按时间计数 |
sms.db |
2023 | 2023I-Q60 | 按%20GUID%20定位验证码短信 |
AddressBook.sqlitedb |
2024 | 2024I-Q2 | 查号码,作为通话记录计的输入 |
MobileBluetooth.ledevices.other.db |
两届:2021/2024 | 2021I-Q9/Q32/Q33;2024I-Q8 | UUID%20与%20MAC%20两个字段;火眼默认只显示%20MAC |
DeviceAgents.sqlite |
2025 | 2025G-Q158 | ZWASMBDEVICEAGENT%20表数记录%20=%20已登录设备数 |
finder_main.db%20(微信) |
2025 | 2025I-Q48 | finderContactTable3%20表,followState=1%20为已关注 |
message_2.sqlite%20(微信) |
2024 | 2024I-Q1/Q3 | 消息的%20XML%20原始数据里带经纬度;可与附件文件编号互查 |
EnMicroMsg.db%20(安卓微信) |
2024 | 2024I-Q41 | 库名本身即答案 |
client.db%20(CloudDocs) |
2025 | 2025I-Q23;2025G-Q253 | iCloud%20Drive%20同步记录 |
| 第三方%20App%20自建库 | 四届:2022—2025 | 2022I-Q10/Q11/Q12/Q31;2023G-Q3/Q5/Q7/Q83;2024G-Q125/Q126/Q127;2025I-Q12/Q19/Q43 | 火眼普遍没有解析%20——iBooks、waze、MTR、Fitbit、Carousell、Foodpanda、hiPrint、Send%20Anywhere、POE,只能手动开库 |
✓%20从这张表能读出三件事
① 两个库必须练到闭眼能开:Photos.sqlite 与 ChatStorage.sqlite,五届里几乎每届都考,而且都是多题连考。
② 第三方%20App%20的库是「送分题与丢分题的混合区」——它们本身不难,难在火眼不给解析、你也不知道该去哪个目录找。
③ 安卓%20WhatsApp%20与%20iOS%20WhatsApp%20是两套表,别把 msgstore.db 的表名往 ChatStorage.sqlite 上套。
9.8%20同届还有一批「数据库题」,但不在本章范围
为了不让你误会「数据库题就这些」,这里把同届考到、但属于服务器侧数据库的题一并列出来。它们同样要开库、写%20SQL,只是不在手机里,而是落在服务器镜像、虚拟机或备份文件上:
| 届次 | 题号 | 数据库类型 | 考什么 |
|---|---|---|---|
| 2022%20团体 | Q6、Q9 | MySQL(WordPress) | wp_comments%20表按 comment_post_ID 关联文章;用户全名要从%20WordPress%20库取 |
| 2022%20个人 | Q68、Q69 | MySQL(Docker%20容器) | 哪个库存了大量个人资料、某人的出生日期 |
| 2023%20团体 | Q43、Q46、Q48 | MySQL | 库密码(从 .bash_history 拿)、被清空的 creditcard 表如何从 dump.sql 恢复并计数 |
| 2023%20团体 | Q124、Q125 | SQL%20dump%20文件 | 从备份%20SQL%20里列出所有表名、判断备份日期 |
| 2024%20团体 | Q76 | WAF%20流量日志里的%20SQL%20注入 | 从日志判断攻击类型 |
| 2025%20团体 | Q86、Q90、Q95 | 网站系统自建库 | 广告机系统的 action_log/player_attribute/user 表 |
| 2025%20团体 | Q136—Q152 | SQLite%20+%20PostgreSQL(n8n) | 工作流平台的两套库、n8n_chat_histories 表、按 session_id 数问答 |
| 2025%20团体 | Q110、Q117—Q128 | MySQL | 邮件服务器库版本、网站库用户名密码、按 userID 统计交易次数与金额 |
为什么分开列?因为解法差别很大:手机库考「认得库和字段」,服务器库考「怎么把库恢复出来再查」——后者往往要先从 .bash_history、.env、dump.sql 或者%20Docker%20配置里把连接信息挖出来。如果这两类混着准备,很容易两边都不熟。
手机数据库这一块的完整地图,就是本章%2087%20道题的落点。下一章把这些落点压成四张速查表,考场上直接查。
十、五年考点地图:15%20个考点,哪些年年考、哪些刚成形
第九章是按届看题,这一章反过来——把%2087%20道题重新打散,按「答案最终落在哪一类知识、哪一个动作上」归成%2015%20个考点,再看每个考点在五年里出现了几届、几道题。同一道题可以挂多个考点(例如%202025%20个人赛的%20WhatsApp%20位置消息题,既属于「消息类型与媒体内容」,也属于「位置坐标」),全部%2087%20道题合计%20135%20个考点标签。
i%20为什么用「覆盖届数」而不是「题数」看重复度
五届收录的题量差得很远:2021%20年%206%20题、2022%20年%2010%20题、2023%20年%2018%20题、2024%20年%2019%20题、2025%20年%2034%20题。比题数会把%202025%20年的小考点抬成「重点」,比届数才能看出它是不是每年都在考。所以下文一律用「覆盖%20N%20届」当重复度的尺子,题数只作参考。
10.1%20地图是怎么画出来的
- 切分依据是「答案落在哪」
,不是按库名、也不是按届次。问「这张照片怎么来的」和问「这个文件谁拍的」,题干完全不同,但答案都落在同一族来源字段上,就该归同一个考点。
*%20一个考点里可以有多个库
:K14「系统库」同时装了蓝牙库、通话记录、短信、iCloud%20同步清单;一个库里也可以有多个考点:相册库一个库就撑起了%20K03%20至%20K07%20五个考点。
*%2015%20个考点没有一个只考过一届
——最少的也覆盖了%202%20届。这一点本身就是结论:五年真题里没有「偶然考点」,凡是出现过的知识点,都值得按「还会再考」来准备。
10.2%20考点总览:15%20个考点按重复度排序
| 编号 | 考点 | 维度 | 覆盖届数 | 涉及题数 | 出现在哪几届 |
|---|---|---|---|---|---|
| K04 | 相册库:来源与生成方式 | 相册库 | 5%20届 | 12 | 2021、2022、2023、2024、2025 |
| K06 | 相册库:属性与文件类型 | 相册库 | 5%20届 | 10 | 2021、2022、2023、2024、2025 |
| K10 | 消息类型与媒体内容 | 聊天库 | 5%20届 | 10 | 2021、2022、2023、2024、2025 |
| K15 | 第三方%20App%20业务库 | 系统与业务库 | 4%20届 | 24 | 2022、2023、2024、2025 |
| K14 | 系统库 | 系统与业务库 | 4%20届 | 12 | 2021、2023、2024、2025 |
| K07 | 位置坐标:库内字段与%20EXIF%20交叉 | 跨库技法 | 4%20届 | 10 | 2021、2023、2024、2025 |
| K01 | 找库:数据目录与包名 | 定位与格式 | 4%20届 | 7 | 2022、2023、2024、2025 |
| K11 | 时间戳换算 | 跨库技法 | 4%20届 | 6 | 2022、2023、2024、2025 |
| K08 | 聊天库:库结构与消息定位 | 聊天库 | 3%20届 | 9 | 2021、2023、2025 |
| K03 | 相册库:主表与文件身份 | 相册库 | 3%20届 | 7 | 2022、2023、2025 |
| K09 | 聊天库:联系人与群组 | 聊天库 | 3%20届 | 6 | 2023、2024、2025 |
| K05 | 相册库:入库时间%20vs%20拍摄时间 | 相册库 | 3%20届 | 3 | 2021、2023、2025 |
| K12 | 联表查询与%20SQL%20语法 | 跨库技法 | 2%20届 | 8 | 2023、2024 |
| K02 | 辨形:非%20SQLite%20库与加密库 | 定位与格式 | 2%20届 | 6 | 2022、2025 |
| K13 | 文件本体:哈希与编码 | 跨库技法 | 2%20届 | 5 | 2024、2025 |
维度是更大的归类:相册库(K03—K06)、聊天库(K07—K09)、定位与格式(K01、K02,找库与辨明库的形态)、跨库技法(K10—K13,时间、位置、SQL、文件本体)、系统与业务库(K14、K15)。
排在最上面的三个(K04%20相册库来源、K06%20相册库属性、K10%20消息类型与媒体)五届一届没落;再往下五个覆盖%204%20届、四个覆盖%203%20届、三个覆盖%202%20届。把这%2015%20行当成复习清单,从上往下练即可。
10.3%20重复考点地图:考点%20×%20届次
下面这张矩阵是本章的核心。横向看一行——格子越满,说明这个考点越「年年考」;纵向看一列——格子越多,说明那一届的考核面越宽。格子里的数字是该届挂在此考点的题数,— 表示该届没有考。行底色按重复度分档:深蓝=5%20届,浅蓝=4%20届,淡蓝=3%20届,白=2%20届。
| 考点 | 2021 | 2022 | 2023 | 2024 | 2025 | 届数/题数 |
|---|---|---|---|---|---|---|
| K04%20相册库:来源与生成方式 | 1 | 2 | 1 | 2 | 6 | 5/12 |
| K06%20相册库:属性与文件类型 | 1 | 1 | 2 | 2 | 4 | 5/10 |
| K10%20消息类型与媒体内容 | 1 | 1 | 2 | 4 | 2 | 5/10 |
| K15%20第三方%20App%20业务库 | — | 6 | 6 | 5 | 7 | 4/24 |
| K14%20系统库 | 3 | — | 2 | 3 | 4 | 4/12 |
| K07%20位置坐标:库内字段与%20EXIF%20交叉 | 2 | — | 2 | 5 | 1 | 4/10 |
| K01%20找库:数据目录与包名 | — | 2 | 1 | 3 | 1 | 4/7 |
| K11%20时间戳换算 | — | 1 | 1 | 1 | 3 | 4/6 |
| K08%20聊天库:库结构与消息定位 | 2 | — | 2 | — | 5 | 3/9 |
| K03%20相册库:主表与文件身份 | — | 1 | 1 | — | 5 | 3/7 |
| K09%20聊天库:联系人与群组 | — | — | 1 | 3 | 2 | 3/6 |
| K05%20相册库:入库时间%20vs%20拍摄时间 | 1 | — | 1 | — | 1 | 3/3 |
| K12%20联表查询与%20SQL%20语法 | — | — | 6 | 2 | — | 2/8 |
| K02%20辨形:非%20SQLite%20库与加密库 | — | 2 | — | — | 4 | 2/6 |
| K13%20文件本体:哈希与编码 | — | — | — | 2 | 3 | 2/5 |
三个观察直接给出来:① 相册库占了最上面的两行,它是五年里最稳定的出题池;② 2025%20年那一列几乎每行都有数字(15%20个考点里覆盖%2014%20个),出题面在这一届达到最宽;③ 2022%20那一列格子最少,但%202022%20只有%2010%20道题入库,属于题量小而不是考得浅。
10.4%20年年考的三个考点(覆盖%205%20届)
K04 相册库:来源与生成方式(12%20题,五届全勤)
这是五年重复度最高的单一考点(2021、2022、2024、2025%20各届都考)。题干从「提供了什么取证信息」到「通过哪种方式生成」再到「由哪一个%20App%20拍摄」,问法一直在换,但答案永远落在同一个字段族:ZIMPORTEDBYBUNDLEIDENTIFIER/ZIMPORTEDFROMSOURCEIDENTIFIER 一类。真正的考点是把%20BundleID%20翻译成人话:com.apple.sharingd 是隔空投送、SpringBoard 是截图、同步下来的看云端与本地时间先后。
该背什么:来源字段的值不是中文描述,而是%20BundleID%20或系统组件名:com.apple.sharingd =%20隔空投送、SpringBoard =%20截图、Safari =%20网页下载。问「怎么来的」先想这个字段。
代表题:2021I-Q37、2022I-Q34、2024G-Q19、2025G-Q166、2025I-Q35(完整题干与解法见第九章对应卡片)
K06 相册库:属性与文件类型(10%20题,五届全勤)
相册库里「东西最多」的一个考点,出题方式是给四条描述判断哪几条对/错,每条描述对应一个字段。2023%20个人赛考「哪项描述是错的」,2024%20个人赛考后置镜头张数,2025%20年连考隐藏相册与缩时摄影(个人赛)、机型比对(团体赛);安卓侧还多一层——库里存着%20AI%20打标的主体标签(2023%20团体赛的拉面题)。这一类题不难,但要求手快:先 PRAGMA%20table_info 认字段,再逐条对。
该背什么:这一类题的标准动作是先 PRAGMA%20table_info 认字段,再逐条核对描述;「由哪个%20App%20拍摄」是来源字段,「是哪一类文件」是类型标记,两者别混。
代表题:2023I-Q59、2024I-Q22、2024I-Q23、2025I-Q39(完整题干与解法见第九章对应卡片)
K10 消息类型与媒体内容(10%20题,五届全勤)
把聊天库里「非纯文本」的那部分单独归一类。2023%20至%202025%20连续三届出现,且题型越来越细:统计语音对话段数(按 .opus 后缀计数)、判一条消息是发出还是接收(ZISFROMME)、读位置消息的经纬度、数投票活动的次数、认出表情包对应的枚举值(20)。共同动作是:先确定类型字段的取值含义,再统计或取值——枚举表题解不给,要从实际数据反推。
该背什么:类型字段是枚举值,题解不给表,只能从实际数据反推(20 =%20表情包、46 =%20投票);收发判定看 ZISFROMME(1%20为发出、0%20为接收);位置消息的经纬度不在消息表,在媒体表里。
代表题:2023I-Q56、2024G-Q11、2024G-Q18、2025I-Q58、2025I-Q74(完整题干与解法见第九章对应卡片)
10.5%20常客:五个覆盖%204%20届的考点
这五个考点不是每届都出现,但隔一年就会回来一次,属于「必须会」的第二梯队。
K15 第三方%20App%20业务库(24%20题,出现于%202022、2023、2024、2025)
五年里变化最大、也最能体现「读库」价值的一类:库里存的都是工具没解析的业务数据。2022%20年的电子书与备忘录、2023%20年的运动与电商、2024%20年的外卖与邮件、2025%20年的%20AI%20聊天与智能家居,几乎没有重复的%20App,但动作完全一样:先按包名定位目录、罗列全部表名、按业务关键词猜字段、再用时间戳筛那一条记录。题解反复提示的一句话是「自动取证工具读不完这些」,这就是所谓剩下%2020%%20的分。
该背什么:标准四步:按包名定位目录%20→%20罗列全部表名%20→%20按业务关键词猜字段%20→%20用时间戳筛那一条记录。库很小也不代表没证据(外卖两个库各%201%20条记录,就是答案)。
代表题:2022I-Q10、2024G-Q90、2024G-Q125、2025G-Q175
K14 系统库(12%20题,出现于%202021、2023、2024、2025)
贯穿五届中四届的「基础分仓库」。2021%20年靠蓝牙库(UUID%20与%20MAC%20辨析)开场,2023%20年出现在通话与短信题里(「哪张表存通话记录」、短信%20GUID%20定位),2025%20年转向%20iCloud%20同步清单、空投痕迹与安卓媒体总库。有两个必须记住的点:火眼只给%20MAC,要%20UUID%20得自己开蓝牙库;Core%20Data%20库里%20Z\_METADATA/Z\_MODELCACHE/Z\_PRIMARYKEY%20三张表天然是「哪张表存业务数据」题的干扰项。
该背什么:两个高频细节:火眼的蓝牙信息只给%20MAC,要%20UUID%20必须自己开蓝牙库;Core%20Data%20库里 Z_METADATA/Z_MODELCACHE/Z_PRIMARYKEY 三张系统表,天然是「哪张表存业务数据」题的干扰项。
代表题:2021I-Q9、2023I-Q64、2024I-Q8、2025I-Q23
K07 位置坐标:库内字段与%20EXIF%20交叉(10%20题,出现于%202021、2023、2024、2025)
2023%20至%202025%20三届连考。考法有两面:同一张照片库里两组经纬度不同,该信哪一个(2025%20个人赛,ZASSET 与 ZEXTATTR 各一组,要靠同地点其他照片交叉比对);原文件%20EXIF%20里根本没有%20GPS,只能回库读(2025%20个人赛另一题)——这两题刚好互为反面,说明「EXIF%20与库谁更可信」没有固定答案。运动%20App%20的轨迹表(2023%20团体赛跑步起点)与聊天库的位置消息(2024%20个人赛微信%20XML、2025%20个人赛%20WhatsApp%20位置消息)也归这一考点的动作:先找到坐标寄存在哪张表。
该背什么:同一份坐标可能有两份(ZASSET 一份、ZEXTATTR 或%20EXIF%20一份),没有哪一份天然更可信,要靠同地点的其他照片交叉比对;EXIF%20里没有%20GPS%20时就回库读,别以为没有。
代表题:2023G-Q7、2025I-Q40、2025I-Q64、2024I-Q1
K01 找库:数据目录与包名(7%20题,出现于%202022、2023、2024、2025)
五年里每一届都至少有一题只问「数据存在哪个库/这个%20App%20的包名是什么」。考法一直在换外衣:2022%20问电子书的书库在哪、2024%20问照片数据在哪个库、2025%20问小程序式的包名(智能家居、AI%20聊天),但动作始终是同一步——先定位目录,再开库。这类题分值好拿,前提是认得包名规律:安卓按包名切目录、iOS%20分私有域与%20AppDomainGroup%20共享域。
该背什么:安卓统一在 /data/<包名>/databases/;iOS%20分私有域与 AppDomainGroup 共享域;认得开发者就能猜出包名(Quora%20→ com.quora.app.Experts,小米%20→ com.xiaomi.smarthome)。
代表题:2022I-Q10、2023G-Q3、2024G-Q12、2025G-Q169
K11 时间戳换算(6%20题,出现于%202022、2023、2024、2025)
五年里最「稳定送分又稳定送命」的考点,2021、2022、2023、2024、2025%20各届都有。坑一共四个:①格式——Cocoa%20以%202001%20年为%200%20点,忘加%20978307200%20就错到离谱;②时区——题干写%20UTC+8%20而工具默认输出%20UTC,少加%208%20小时(2023%20个人赛的「I%20am%20already%20home」就是这题);③反向坑——库里存的已经是本地时间,再%20+8%20反而多%208%20小时(2023%20团体赛跑步时间);④单位——安卓常见毫秒级,先除%201000。每一届都要重踩一遍,说明它必须练到条件反射。
该背什么:四条一起背:Cocoa%20加%20978307200、UTC+8%20再加%208%20小时、库里已是本地时间就别再加、安卓毫秒先除%201000。每次作答前先回题干确认它要的是%20UTC+8%20还是%20UTC+0。
代表题:2021I-Q33、2023G-Q6、2023I-Q62、2024I-Q2、2024I-Q29
10.6%20正在成形:三届与两届的考点
三届的四个考点已经算「稳定」,两届的三个考点则要留意——它们往往是出题人新加的方向,2026%20年大概率会接着考。
覆盖%203%20届(稳定)
K08%20聊天库:库结构与消息定位(9%20题,2021、2023、2025):安卓%20WhatsApp%20双库(wa.db%20联系人+msgstore.db%20消息)、iOS%20单库%20ChatStorage.sqlite;精确定位靠%20GUID%20或关键字
该背什么:三个库名背死:iOS%20WhatsApp%20只有 ChatStorage.sqlite;安卓是 wa.db(联系人)+ msgstore.db(消息);微信是 EnMicroMsg.db(通常加密)。精确定位靠%20GUID%20或关键字,不靠翻。
代表题:2021G-Q30、2024I-Q42、2024I-Q41、2025I-Q50
K03%20相册库:主表与文件身份(7%20题,2022、2023、2025):ZASSET%20主表、Z\_PK%20自增主键、ZFILENAME、本地名与原文件名、修改后另存
该背什么:Z_PK 是%20Core%20Data%20的自增主键,写入顺序=主键顺序;库里同时存本地文件名与原始(云端)文件名,「原名」问的是后者。
代表题:2025G-Q160、2025G-Q162、2025I-Q38、2025I-Q42
K09%20聊天库:联系人与群组(6%20题,2023、2024、2025):群组两张表(群信息+会话)、群%20ID%20里含建群者手机号与建群时间戳、封存/锁定/多设备登录
该背什么:群组这组题的值都藏在「名字」里:群%20ID%20形如 <建群者手机号>-<建群时间戳>@g.us,群头像文件名 85263834763-1563607573-1732782278.jpg 第三段就是更改时间。文件名与 ID 本身就是证据。
代表题:2023G-Q9、2025G-Q183、2025G-Q187、2025G-Q189
K05 相册库:入库时间 vs 拍摄时间(3 题,2021、2023、2025):ZADDEDDATE=写进数据库的时间,ZDATECREATED=文件元数据里的创建时间,题干问哪个就用哪个
该背什么:两个日期字段分得很清:写进数据库/储存到相册看 ZADDEDDATE,拍摄时间看 ZDATECREATED。
代表题:2021I-Q37、2023I-Q59、2025G-Q160
覆盖 2 届(新方向)
K12 联表查询与 SQL 语法(8 题,2023、2024):两表靠共同字段接起来、表别名用了就必须全程带、自连接解「原名→现名」、COUNT 前先定筛选字段
该背什么:联表的连接键要从题干和两表的共同字段推,不能想当然;表别名一旦用了(FROM cus c),后续每一列都得带别名;「多少条」之前先定筛选字段,再 COUNT。
代表题:2023G-Q4、2023I-Q98、2023G-Q82、2024I-Q2、2024I-Q23
K02 辨形:非 SQLite 库与加密库(6 题,2022、2025):Realm、MMKV、plist、加密库、扩展名伪装——先辨明是什么格式,再决定怎么开
该背什么:打不开先别怀疑工具,先看它是什么:.realm 是 Realm 库、无扩展名的可能是 MMKV、加密库要么找密钥(微信按 uin + IMEI 生成)要么上字典。
代表题:2022I-Q38、2025I-Q12、2025G-Q175、2025I-Q43
K13 文件本体:哈希与编码(5 题,2024、2025):哈希算的是文件本体不是数据库记录;源文件与库互证;同一记录可能被 GBK/UTF8 两套编码搜出两次
该背什么:哈希是对文件本体算的,不是对数据库记录算的——先把库里的路径还原成磁盘文件;同名不同源、同图两名,一律用哈希说话;全局搜索的结果要按编码去重。
代表题:2024G-Q10、2024I-Q19、2025G-Q165、2025I-Q31
! 最该警惕的三个「两届考点」
K02 非 SQLite 库与加密库(2022、2025)、K13 文件本体哈希与编码(2024、2025)、K12 联表查询与 SQL 语法(2023、2024)——前两个是纯技术新增项,2025 年一次性出现了 4 道,说明出题人正在加码;K12 是唯一一个「间隔年」出现的考点,2025 年虽然没单独设题,但解法已经融进了相册与聊天的题里。
10.7 五年趋势:考核面在逐年扩大
| 届次 | 收录题数 | 当届涉及考点 | 具体考点 | 首次出现的考点 |
|---|---|---|---|---|
| 2021 | 6 | 7 | K04、K05、K06、K07、K08、K10、K14 | K04、K06、K10、K14、K07、K08、K05 |
| 2022 | 10 | 8 | K01、K02、K03、K04、K06、K10、K11、K15 | K15、K01、K11、K03、K02 |
| 2023 | 18 | 13 | K01、K03、K04、K05、K06、K07、K08、K09、K10、K11、K12、K14、K15 | K09、K12 |
| 2024 | 19 | 11 | K01、K04、K06、K07、K09、K10、K11、K12、K13、K14、K15 | K13 |
| 2025 | 34 | 14 | K01、K02、K03、K04、K05、K06、K07、K08、K09、K10、K11、K13、K14、K15 | — |
- 考核面从 7 个考点扩到 14 个
(2021 → 2025),2023 年是一次跃迁(业务 App 类考点一次铺开),2024 年略微收窄,2025 年达到最宽。
* 「读库」的比重在上升。
K15 第三方 App 业务库覆盖 4 届、涉及 24 题,是全部 15 个考点里题数最多的一个——数据库题的答案越来越多地藏在业务库里,而不是在通信软件这类「常规考点」里。
* 物理层与格式层开始入场。
K13(哈希与编码)、K02(非 SQLite 库与加密库)都是后两年才出现的——以前只需要「打开库读字段」,现在还要处理文件本身。
* 相册库从未离场。
K03 至 K07 五个考点覆盖 3 到 5 届不等,合计 42 题。哪怕 2025 年加了再多新方向,相册库仍是出题密度最高的地方。
10.8 同一个考点的多种问法(三张对照表)
「重复考点」不是「原题重现」。出题人的手法是同一个知识点,换一个入口问。下面三张表把这种问法变化摊开——左边是题干原句,右边是它真正要读的字段或要做的动作。
① 相册库「来源」(K04,五届全勤)
| 届次·题号 | 题干原句(节选) | 实际要读的字段/动作 |
|---|---|---|
| 2021I-Q37 | 相片檔 IMG\_0056.HEIC 提供了什么电子数据取证的信息? | 一次问三项:来源 + 机型 + 拍摄时间 |
| 2022I-Q34 | 哪一个栏目标题可以显示这张照片的接收方式 | ZIMPORTED* 与 ZRECEIVED* 的命名区别 |
| 2022I-Q35 | 承上题,这张照片通过什么方式接收 | 把 BundleID 翻成人话(sharingd = 隔空投送) |
| 2024G-Q19 | IMG\_0011.PNG 是通过哪种方式生成的? | 值是系统组件名(SpringBoard = 截图) |
| 2025G-Q161 | 这个图片文件是通过什么方法在此手机生成 | iCloud 同步——比云端与本地时间的先后 |
| 2025G-Q166 | 此图片文件是如何在此手机生成的 | 网页下载——来源是浏览器 |
| 2025I-Q30 | 请指出由哪一个软件拍摄 | 来源 App(GIF 由 Infltr 拍摄) |
| 2025I-Q35 | 图片文件 IMG\_0079.JPG 是由哪一个 APP 拍摄 | 按文件名定位 asset 再读来源字段 |
② 时间戳换算(K11)的四种坑
| 坑 | 长什么样 | 代表题 |
|---|---|---|
| 格式:Cocoa Core Time | 库里是 2001-01-01 起算的秒数,忘加 978307200 差 31 年 | 2023I-Q62、2024I-Q2 |
| 时区:题干要 UTC+8 | 工具默认输出 UTC,少加 8 小时 | 2023I-Q62(答案 2023-04-01 11:21:51) |
| 反向坑:库里已是本地时间 | 再加 8 小时反而错 8 小时 | 2023G-Q6(跑步开始时间) |
| 单位:毫秒 | 安卓字段常见 13 位,先除 1000 | 2024I-Q29(Gmail 首封邮件日期) |
| 题干要 UTC+0 | 这次不要加 8 小时 | 2025G-Q189(群头像更改时间) |
③ 聊天库三个考点的问法迁移(K07/K08/K09/K10)
| 届次 | 在聊天库里问了什么 | 归属考点 |
|---|---|---|
| 2021 | 对方名下的 WhatsApp 账号有哪些、黑名单有几条 | K07 库结构、K08 联系人与状态 |
| 2023 | 哪些对话被锁定、有多少段录音、被修改前的内容是什么、群组名称 | K07/K08/K09 |
| 2024 | 哪个库存 WhatsApp 消息、表情包对应哪个枚举、一条消息是发还是收 | K07/K09/K10 |
| 2025 | 封存了哪个群、群建立者手机号、群头像的更改时间、投了几次票、位置消息坐标 | K08/K09/K10 |
从 2021 到 2025,聊天库的题从「读一个字段」走到「一条链上取三个值」——这就是重复考点的演化方式:不是重复题,是重复难度加码。
10.9 按重复度排出的复习优先级
- 第一优先(覆盖 5 届,共 32 题):
K04 相册库来源、K06 相册库属性、K10 消息类型与媒体。这三个必须在不查资料的情况下能说出「读哪个字段」。
* 第二优先(覆盖 4 届,共 59 题):
K15 业务库、K14 系统库、K07 位置坐标、K01 找库与包名、K11 时间戳。这五个是补分的主战场,练法见第十三章的五周路线。
* 第三优先(覆盖 3 届,共 25 题):
K08 库结构与消息定位、K03 相册库主表、K09 联系人与群组、K05 时间双轨。这四个是「会了就有、不会就丢」的稳定分。
* 第四优先(覆盖 2 届,共 19 题):
K12 联表与 SQL、K02 非 SQLite 库与加密库、K13 文件本体哈希与编码。题数少但都是新方向,建议至少各做一道原题。
10.10 考点自查表
最后给自己打一次分。逐行问自己:这个考点,我能不能不看笔记说出「去哪个库、读哪个字段、要注意什么」?说不出来的,把编号记下来,回第九章找代表题重做一遍。
| 编号 | 考点 | 重复度 | 做完这几题就算过关 | 自评 |
|---|---|---|---|---|
| K04 | 相册库:来源与生成方式 | 5 届 | 2021I-Q37、2022I-Q34、2024G-Q19 | □ |
| K06 | 相册库:属性与文件类型 | 5 届 | 2023I-Q59、2024I-Q22、2024I-Q23 | □ |
| K10 | 消息类型与媒体内容 | 5 届 | 2023I-Q56、2024G-Q11、2024G-Q18 | □ |
| K15 | 第三方 App 业务库 | 4 届 | 2022I-Q10、2024G-Q90、2024G-Q125 | □ |
| K14 | 系统库 | 4 届 | 2021I-Q9、2023I-Q64、2024I-Q8 | □ |
| K07 | 位置坐标:库内字段与 EXIF 交叉 | 4 届 | 2023G-Q7、2025I-Q40、2025I-Q64 | □ |
| K01 | 找库:数据目录与包名 | 4 届 | 2022I-Q10、2023G-Q3、2024G-Q12 | □ |
| K11 | 时间戳换算 | 4 届 | 2021I-Q33、2023G-Q6、2023I-Q62 | □ |
| K08 | 聊天库:库结构与消息定位 | 3 届 | 2021G-Q30、2024I-Q42、2024I-Q41 | □ |
| K03 | 相册库:主表与文件身份 | 3 届 | 2025G-Q160、2025G-Q162、2025I-Q38 | □ |
| K09 | 聊天库:联系人与群组 | 3 届 | 2023G-Q9、2025G-Q183、2025G-Q187 | □ |
| K05 | 相册库:入库时间 vs 拍摄时间 | 3 届 | 2021I-Q37、2023I-Q59、2025G-Q160 | □ |
| K12 | 联表查询与 SQL 语法 | 2 届 | 2023G-Q4、2023I-Q98、2023G-Q82 | □ |
| K02 | 辨形:非 SQLite 库与加密库 | 2 届 | 2022I-Q38、2025I-Q12、2025G-Q175 | □ |
| K13 | 文件本体:哈希与编码 | 2 届 | 2024G-Q10、2024I-Q19、2025G-Q165 | □ |
✓ 这一章的用法
别把它当知识点汇总来读,当进度表用:15 行全部能说出「怎么查」,手机数据库这类题就不会再丢分了。第九章提供逐题拆解,这一章提供重复度依据,两张表合起来看,就能判断哪些投入是值得的。
十一、速查表:考场上的四张表
这一章是全文的落点。前面八章都在讲「为什么」,这一章只有「去哪查」。
i 这四张表怎么用
表一、表二:按平台列出每个库的路径与核心字段,遇到陌生检材时先对一遍;表三:按题目的问法索引——这是最该打印出来的一张,题目怎么问,你顺着它找到「去哪个库、看哪个字段」;表四:反查表,看到题干里冒出一个字段名,回来查它是哪个库的。
10.1 速查表一:iOS 常考库
| 库文件 | 路径关键段 | 核心表 | 必记字段 |
|---|---|---|---|
| Photos.sqlite | /var/mobile/Media/PhotoData/ |
ZASSET 、ZEXTENDEDATTRIBUTES、ZADDITIONALASSETATTRIBUTES、ZGENERICALBUM、ZCLOUDMASTER |
ZUUID 、ZFILENAME、ZUNIFORMTYPEIDENTIFIER、ZDATECREATED、ZADDEDDATE、ZLATITUDE/ZLONGITUDE、ZVIDEOCPDURATIONVALUE、ZEXTENDEDATTRIBUTES |
| ChatStorage.sqlite | /var/mobile/Applications/group.net.whatsapp.WhatsApp.shared/ |
ZWAMESSAGE 、ZWAMESSAGEINFO、ZWACHATSESSION、ZWAMEDIAITEM、ZWAGROUPINFO |
ZTEXT 、ZFROMJID/ZTOJID、ZMESSAGETYPE、ZMESSAGEINFO、ZMEDIAITEM、ZISFROMME、ZCREATORJID |
| DeviceAgents.sqlite | 同 WhatsApp 目录 | ZWASMBDEVICEAGENT |
登录设备记录(几条记录 = 几台设备) |
| NoteStore.sqlite | /AppDomainGroup-group.com.apple.notes/ |
ZICCLOUDSYNCINGOBJECT |
笔记加密标志、标题、内容 |
| CallHistory.storedata | /var/mobile/Library/CallHistoryDB/ |
ZCALLRECORD (另有 ZCALLBPROPERTIES) |
ZADDRESS (号码)、ZDATE(Cocoa 时间) |
| AddressBook.sqlitedb | /var/mobile/Library/AddressBook/ |
ZABCDRECORD 等 |
号码 ↔ 姓名 |
| sms.db | /var/mobile/Library/SMS/ |
message 、chat |
短信正文、收发方、时间 |
| Manifest.db | iOS 备份根目录 /var/ |
Files |
fileID 、domain、relativePath |
| message\_N.sqlite | com.tencent.xin/Documents/<32位哈希>/ |
消息表 | 图片消息的 XML:aeskey、filekey、m_assetUrlForSystem(GUID) |
| finder\_main.db | .../finder/db/ |
finderContactTable3 |
followState (1 = 已关注) |
10.2 速查表二:Android 常考库
| 库文件 | 路径 | 核心表 | 必记字段 |
|---|---|---|---|
| msgstore.db | /data/data/com.whatsapp/databases/ |
message 、chat、jid、message_edit_info、message_ftsv2_content、media |
_id 、chat_row_id、jid_row_id、message_row_id、docid、message_type、text_data |
| wa.db | /data/data/com.whatsapp/databases/ |
wa_contacts (题解原文写作 wa_contects,应为笔误) |
联系人的 WhatsApp 账号 |
| contacts2.db | /data/data/com.android.providers.contacts/databases/ |
contacts 、raw_contacts、data、mimetypes |
要联 mimetypes 才知道某行是电话、邮箱还是姓名 |
| mmssms.db | /data/data/com.android.providers.telephony/databases/ |
sms 、threads |
单条短信与会话 |
| calllog.db | /data/data/com.android.providers.contacts/databases/ |
calls |
type 区分呼入/呼出/未接 |
| EnMicroMsg.db | /data/data/com.tencent.mm/MicroMsg/<哈希>/ |
消息表(需解密) | 微信安卓版聊天库 |
| com.tencent.mm\_preferences.xml | /data/data/com.tencent.mm/shared_prefs/ |
—(XML 配置,非数据库) | 最后登录的微信 ID |
10.3 速查表三:按题目问法索引(最常用的一张)
做题时从这里开始查。左边是题目里出现的话,右边是你的动作。
| 题目这么问 | 去哪个库 · 哪张表 | 看哪些字段/怎么做 |
|---|---|---|
| 某时刻谁发了什么消息 | ChatStorage ZWAMESSAGE |
按时间过滤,取 ZTEXT、ZFROMJID、ZISFROMME |
| 某条消息是谁发的(安卓) | msgstore.db message→chat→jid |
chat_row_id → jid_row_id → jid.user,三级联表 |
| 某时刻发送的位置坐标 | ChatStorage ZWAMESSAGE→ZWAMEDIAITEM |
取 ZMEDIAITEM 的值,去 ZWAMEDIAITEM 按 Z_PK 过滤 |
| 这张照片在哪拍的 | Photos.sqlite ZASSET + ZEXTATTR |
两处经纬度都要看,冲突时用邻近照片轨迹判定 ,再用 EXIF 印证 |
| 这张照片是否本机拍摄 | Photos.sqlite ZEXTENDEDATTRIBUTES |
ZCAMERAMAKE /ZCAMERAMODEL |
| 这张照片是怎么来的 | Photos.sqlite ZADDITIONALASSETATTRIBUTES |
ZIMPORTEDBYBUNDLEIDENTIFIER 的值(BundleID),翻译成 App 名 |
| 是不是实况照片 | Photos.sqlite ZASSET |
ZVIDEOCPDURATIONVALUE != 0 |
| 照片删了没有/何时删的 | Photos.sqlite ZASSET |
ZTRASHEDSTATE 、ZTRASHEDDATE、ZHIDDEN |
| 群主/建群者是谁 | ZWAGROUPINFO.ZCREATORJID ; 或解析群 ID 老格式 |
<手机号>-<时间戳>@g.us ,前半段就是建群者号码 |
| 群是什么时候建的 | 群 ID(老格式)/ChatStorage 记录 |
解析群 ID 里的 UNIX 时间戳,再与库里时间交叉验证 |
| 哪个群被存档了 | ChatStorage ZWACHATSESSION |
存档标志字段(值为 1) |
| 群头像叫什么文件 | ChatStorage Media/Profile/ |
库里有路径,磁盘上有文件,两边对上 |
| 登录了几台设备 | DeviceAgents.sqlite |
ZWASMBDEVICEAGENT 表的记录数 |
| 打了多少次电话 | CallHistory.storedata ZCALLRECORD |
ZADDRESS + ZDATE 过滤后 COUNT(*) |
| 这个号码是谁 | AddressBook.sqlitedb ; 或 JID 的 @ 前半段 |
号码 ↔ 姓名接力 |
| 消息被编辑前是什么 | msgstore.db message_edit_info + message_ftsv2_content |
message_row_id → message._id ← docid |
| 某个类型值的含义 | 任意库对应表 | GROUP BY 该字段 + 抓样本内容反推 |
| 某个文件存在哪 | ChatStorage ZWAMEDIAITEM.ZMEDIALOCALPATH; 备份里查 Manifest.db |
库里的路径 + 磁盘上的文件互校 |
| 微信图片的原图 | message\_N.sqlite 消息 XML | filekey (编号对应磁盘文件名)、aeskey(解密)、md5(校验) |
| 微信图片的地理位置 | message\_N.sqlite → Photos.sqlite | XML 里的 m_assetUrlForSystem GUID = 相册库的 ZUUID |
| 关注了几个视频号 | finder\_main.db finderContactTable3 |
followState = 1 计数 |
| 这个库有哪些表 | 任意库 | SELECT name FROM sqlite_master WHERE type='table'; |
| 某个字段名存不存在 | 任意库 | PRAGMA table_info(表名); 或导出全库字段清单 |
| 某张表里有哪些取值 | 任意库 | GROUP BY 字段 + COUNT(*) |
10.4 速查表四:看到字段名,反查它属于哪个库
题干或选项里直接给你一串字段名时,这张表能帮你秒判方向。
| 字段名(或片段) | 属于 | 含义 |
|---|---|---|
ZASSET /ZEXTATTR/ZADDASSETATTR |
Photos.sqlite | 相册主表/EXIF/附加属性 |
ZUUID |
Photos.sqlite | 资产唯一标识,跨库关联用 |
ZMESSAGETYPE /ZMESSAGEINFO |
ChatStorage.sqlite | 消息类型/详情指针 |
ZFROMJID /ZTOJID |
ChatStorage.sqlite | 收发方 |
ZMEDIAITEM |
ChatStorage.sqlite | 媒体/位置指针 |
ZRECEIPTINFO |
ChatStorage.sqlite · ZWAMESSAGEINFO |
Protobuf 格式的消息详情 |
ZCREATORJID |
ChatStorage.sqlite · ZWAGROUPINFO |
群创建者 |
ZICCLOUDSYNCINGOBJECT |
NoteStore.sqlite | 备忘录对象 |
ZCALLRECORD /ZADDRESS/ZDATE |
CallHistory.storedata | 通话记录/对方号码/时间 |
chat_row_id /jid_row_id/message_row_id |
msgstore.db(安卓) | 三种外键 |
message_ftsv2_content /docid |
msgstore.db(安卓) | 编辑前的原始文本 |
text_data /message_type |
msgstore.db(安卓) | 正文/消息类型 |
followState /finderContactTable3 |
finder\_main.db(微信) | 视频号关注状态 |
m_assetUrlForSystem /aeskey/filekey |
微信 message_N.sqlite |
消息 XML 中的图片元数据 |
fileID /domain/relativePath |
Manifest.db(iOS 备份) | 备份文件清单 |
Z_PRIMARYKEY /Z_METADATA/Z_MODELCACHE |
任何 Core Data 库 | 通用表,八成是干扰项 |
10.5 一页纸的做题流程
把前面所有内容压成一条流程,考场上照着走:
- 第 1 步 · 判平台
:看路径。有 /data/data/ 是安卓,有 /var/mobile/ 或 Applications/ 是 iOS。如果是备份包,先处理 Manifest 四件套。
* 第 2 步 · 找库
:搜 /databases/ 目录,或按文件头 SQLite format 3 扫。别只按后缀筛,会漏掉 .storedata。
* 第 3 步 · 认表
:sqlite_master 列表名 → 对照 10.1/10.2 两张表,看是哪个 App 的库。
* 第 4 步 · 认字段
:PRAGMA table_info(表),或直接跑一遍 2.5 节那段导字段脚本。找不到就搜,别猜。
* 第 5 步 · 定位答案
:照 10.3 表按问法找到字段,注意时间是 Cocoa 还是 UNIX、要不要联表。
* 第 6 步 · 交叉验证
:同一信息若有两个来源(工具 vs 库、主表 vs 附表),都要看一遍,答案以原始记录为准并说明依据。
* 第 7 步 · 记下依据
:答案带上届次题号式的出处,例如「来自 ZASSET 表 ZFILENAME=IMG_0027.HEIC 的记录」。
十二、五年踩过的坑:15 条易错点
| # | 易错点 | 正确做法 |
|---|---|---|
| 1 | 用文件后缀筛数据库,漏掉 .storedata 这类 |
同时按文件头判:SQLite format 3 就是库 |
| 2 | 只读主库文件,忽略 -wal/-shm |
三个文件放同一目录再打开 ,否则拿不到最新数据 |
| 3 | 把 Cocoa 时间戳当 UNIX 用 | Cocoa + 978307200 = UNIX ,iOS 库几乎都是 Cocoa |
| 4 | 直接抄工具显示的时间 | 工具可能显示 UTC。东八区要 +8 小时,答案里写明时区 |
| 5 | 在 ZASSET 里找相机型号 |
设备信息在 ZEXTENDEDATTRIBUTES,顺着外键跳过去 |
| 6 | 发现两份经纬度不一致就随便选一个 | 对比邻近记录 + EXIF 印证 ,三方一致才落笔 |
| 7 | 以为工具解析不出来 = 数据不存在 | 工具只翻译常用字段。自己开库看原始表 |
| 8 | 忘记 JOIN 的 ON 条件 |
结果会变成笛卡尔积,行数暴增、查询卡死 |
| 9 | 联表时字段名不加表前缀 | 报 ambiguous column name。一律写 cus.customer_id 这种全名 |
| 10 | 只在一张表里找答案 | 同一信息可能分散在多张表(主表存记录、附表存详情) |
| 11 | 看到「封存/存档」找不到字段就放弃 | 港台用词,英文是 Archive;大陆叫「存档」 |
| 12 | 被字段简写绕晕(ZEXTATTR 到底是什么) |
去 sqlite_master 对全称,或用导字段脚本搜关键词 |
| 13 | 用 COUNT(字段) 数总数 |
它不数 NULL。数全部用 COUNT(*),数「多少个人」要 COUNT(DISTINCT 字段) |
| 14 | 直接背消息类型枚举表 | 用 GROUP BY 字段 + 抓样本内容现场反推,零记忆且不会错 |
| 15 | 在原检材上直接写 SQL、做实验 | 先复制一份工作副本,所有操作在副本上做 |
十三、要掌握到什么程度:四级能力清单
把「会做手机数据库题」拆成四级。可以拿它给自己打分,看看卡在哪一级,再去看对应章节。
| 级别 | 名称 | 要会什么 | 对应章节 |
|---|---|---|---|
| L1 | 认得出 | 看到一个检材,知道去哪个目录找库;能打开库、能用 sqlite_master 列出表名;能说出这个库属于哪个 App。 |
第 2 章 |
| L2 | 找得到 | 题目问某个信息,能判断它在哪张表;能列出该表字段、认出外键;会写单表 SELECT ... WHERE ... 把记录捞出来。 |
第 3/4/5 章 |
| L3 | 接得起 | 会写联表:能顺着 Z_PK/xxx_row_id 串两三张表;会把题干的时刻换算成时间戳当筛选条件;会 GROUP BY 反查枚举值;会在两个库之间做接力。 |
第 6/7 章 |
| L4 | 辨得清 | 遇到同一信息有两个来源时,能交叉验证并说明取舍依据;工具与库冲突时,知道回原始记录;库结构变了、工具不支持时,能改脚本、手动硬看,把答案挖出来。 | 第 3.5/4.3 节 + 第 5.1 节 |
十四、五周练习路线
不用买课,全程只用公开资源 + 你手上的这几篇题解。
| 周 | 目标 | 具体动作 |
|---|---|---|
| 第 1 周 | 把「认库认表」练成条件反射 | ① 拿任意一份 iOS 检材,对着 10.1 表把每个库打开一遍;② 每个库跑一次 sqlite_master,把表名抄一遍;③ 用 2.5 节的脚本导出全库字段清单,存成自己的参考文件 |
| 第 2 周 | 吃透 Photos.sqlite | ① 逐条复现第 3.9 节的 8 道真题;② 重点练 3.5 节的「两份经纬度」判定;③ 把 ZASSET 和 ZEXTENDEDATTRIBUTES 的字段对着库抄一遍 |
| 第 3 周 | 吃透 WhatsApp 双平台 | ① 复现第 4.7 节的 14 道题;② iOS 侧练 ZWAMESSAGE → ZWAMESSAGEINFO → ZWAMEDIAITEM 三段链;③ 安卓侧练 4.6.1 节的五表链,把「编辑前原文」亲手查出来 |
| 第 4 周 | 时间与联表专项 | ① 把第 6 章的验算自己动手算一遍(不要只看);② 写一句 strftime 把任意给定时刻换成 Cocoa 秒数;③ 复现第 7.4 节四个范式,每个至少独立写一遍,不看答案 |
| 第 5 周 | 综合演练 + 查漏 | ① 拿一份完整检材,从找库到给答案走完 10.5 的七步;② 把 10.3 速查表打印出来,盖住右列自测;③ 过一遍第十二章 15 条易错点,把踩过的标出来 |
结语
写这一篇的起因,是发现近五年美亚杯的手机题有个很集中的落点:出题人越来越喜欢把答案埋在一个具体字段里——一个 ZVIDEOCPDURATIONVALUE、一个 ZCREATORJID、一个 followState、一个 ZRECEIPTINFO。自动取证工具能帮你走到门口,但最后那一脚得你自己迈。
而这条路其实比想象中窄——常用的库就那十来个,核心的表就那二三十张,真正被反复考的字段不到六十个。本文把它们全部收在了第十一章的四张表里。
所以别再觉得「数据库题要背很多」。你要记的不是几十张表的几百个字段,而是七个动作:判平台、找库、列表名、查字段、定条件、接外键、交叉验证。把这七个动作变成肌肉记忆,剩下的都是体力活。
✓ 最后一句提醒
手机取证里的「答案」,永远以原始记录为准。工具给你的是它算出来的结果,而库给你的是当时真实写下的那串数字。这也是为什么——五年了,这个考点一次都没落过。
敬请各位大佬关注:小谢取证
扫取二维码获取
更多精彩
小谢取证
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论