2026年“美亚杯”手机数据库专题

admin 2026-10-02 05:07:23 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文详细解析了美亚杯手机数据库专题,包括iPhone和安卓的库、表、字段,通过分析历年真题,提供了数据库查找、解析和提取数据的方法,适合作为取证手册使用。 综合评分: 85 文章分类: 渗透测试,代码审计,应急响应,漏洞分析,威胁情报


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//Documents/ /var/mobile/Applications//Library/ /var/mobile/Containers/Data/Application//Documents/   ← 新版系统  WhatsApp → /var/mobile/Applications/group.net.whatsapp.WhatsApp.shared/               ChatStorage.sqlite        ← 聊天数据(重点)               DeviceAgents.sqlite        ← 已登录设备(2025 考过)               Media/Profile/             ← 群组/联系人头像 微信      → /var/mobile/Applications/com.tencent.xin/Documents/<32位哈希>/               finder/db/finder_main.db   ← 视频号(2025 考过) 相册      → /var/mobile/Media/PhotoData/Photos.sqlite   ← 照片元数据(重点) 备忘录    → /AppDomainGroup-group.com.apple.notes/NoteStore.sqlite 通话记录  → /var/mobile/Library/CallHistoryDB/CallHistory.storedata 通讯录    → /var/mobile/Library/AddressBook/AddressBook.sqlitedb

注意三个「不像数据库」的名字,这是最容易漏掉的: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 里换算成可读时间,就一句:

    评论:0   参与:  0