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 手机里的数据,分三层
把手机想象成一栋楼,应用数据从上到下是三层:
| 第一层:文件 | ||
| 第二层:应用数据目录 | /data/data/<包名>/,iOS 在 /var/mobile/Applications/<BundleID>/ | group.net.whatsapp.WhatsApp.shared 谁记得住),但它是通往第三层的路 |
| 第三层:数据库 | 所有「谁、什么时候、对谁、说了什么、传了什么、在哪」的结构化答案都在这 |
所以有一句在圈内流传的话:手机取证做到最后,就是一场 SQLite 阅读比赛。这话在美亚杯上几乎年年应验。
1.2 数据库其实你早就会——它就是一本 Excel 工作簿
「数据库」这三个字劝退了不少人,但 SQLite 和 Excel 的对应关系几乎是 1:1 的,把术语换成你说惯了的话,门槛立刻降一半:
| 数据库(database) | ChatStorage.sqlite 文件 | |
| 表(table) | ZWAMESSAGEmessage、ZASSET | |
| 字段(column) | ZTEXTZDATE(时间) | |
| 记录(row) | ||
| 主键(primary key) | Z_PK_id,全表唯一 | |
| 外键(foreign key) | ZMESSAGEINFOZWAMESSAGEINFO.Z_PK 的值 |
其中主键和外键这一对,是读懂一切联表题的钥匙。第 7 章会专门讲。
✓ 别被名字吓到
ZASSET 读作「Z-asset」,ZWAMESSAGE 读作「Z-W-A-message」。iOS 应用的数据库大多用苹果的 Core Data 框架生成,框架习惯给表名和字段名统统加一个 Z 前缀——所以你在 iOS 库里面看到的字段几乎全是 Z 开头,这不是加密,只是框架的命名口味。
1.3 五个必须分清的概念
新手最容易把这五个混成一团,而美亚杯恰恰喜欢在它们的边界上出题:
| 文件后缀 | .jpg | ||
| 文件头 | FF D8 FF | ||
| EXIF 元数据 | ZEXTATTR 表(iOS) | ||
| 数据库元数据 | Z_METADATAZ_PRIMARYKEY 表 | ||
| 应用配置 | 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.dbChatStorage(2021I-Q8)、相册库(2021I-Q37) | |
| 2022 | 10 题 | Photos.sqliteNoteStore.sqlite(2022I-Q37/Q38)、以及 iBooks/MTR/waze/Gmail 四个第三方库(2022I-Q10—Q12、Q20、Q31) | |
| 2023 | 18 题 | ChatStorage.sqlitemsgstore.db(2023G-Q82)、CallHistory.storedata(2023I-Q64)、Photos.sqlite(2023I-Q58) | |
| 2024 | 19 题 | msgstore.dbChatStorage.sqlite(2024G-Q90)、message_2.sqlite(微信)(2024I-Q1/Q19)、CallHistory+AddressBook(2024I-Q2)、Photos.sqlite(2024I-Q22) | |
| 2025 | 34 题 | ChatStorage.sqliteDeviceAgents.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/<BundleID>/Documents/ /var/mobile/Applications/<BundleID>/Library/ /var/mobile/Containers/Data/Application/<UUID>/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 | ||
| Status.plist | ||
| *_DEC |
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 | ||
| Z_METADATA |
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 | 主表 | |
| ZEXTENDEDATTRIBUTES | EXIF 扩展属性 | |
| ZADDITIONALASSETATTRIBUTES | 附加属性 | |
| ZGENERICALBUM | ZTITLE)、创建时间 | |
| ZCLOUDMASTER | ||
| ZINTERNALRESOURCE | ||
| ZUNMANAGEDADJUSTMENT | ||
| ZSCENEPRINT | ||
| ZMEDIAANALYSISASSETATTRIBUTES | ||
| ZSHARE |
i 怎么确认一张表到底叫什么
别背,去查。用第 2.5 节那段脚本把全库表名导出来,或者直接用一句 SELECT name FROM sqlite_master WHERE type='table';。2022 年个人赛的题解就是这么确认的:它先列出全库字段,发现候选字段里只有 ZIMPORTEDBYBUNDLEIDENTIFIER 存在,并且同时存在于 ZADDITIONALASSETATTRIBUTES、ZCLOUDMASTER、ZGENERICALBUM 这三张表里——连表名一起确认了。
3.3 ZASSET 常用字段速查
下面这张表是本文最该收藏的一张。「真题实证」列的 √ 表示这个字段在近五年题解里被明确用到过,后面跟的是届次与题号;— 表示它在公开资料里是标准字段,但这五年题目里没有被单独点问过。
| Z_PK | |||
| ZUUID | 跨库关联的钥匙m_assetUrlForSystem 存的就是它 | ||
| ZFILENAME | IMG_0008.HEIC5005.JPG | ||
| ZORIGINALFILENAME | |||
| ZDIRECTORY | DCIM/100APPLE | ||
| ZUNIFORMTYPEIDENTIFIER | public.heicpublic.jpeg/public.mpeg-4——判文件真实类型比后缀更靠谱 | ||
| ZDATECREATED | Cocoa 时间戳 | ||
| ZADDEDDATE | |||
| ZLATITUDE | ZEXTATTR 里那份可能不一致 | ||
| ZLONGITUDE | |||
| ZEXTENDEDATTRIBUTES | ZEXTENDEDATTRIBUTES.Z_PK | ||
| ZKIND | 01 = 视频 | ||
| ZKINDSUBTYPE | |||
| ZVIDEOCPDURATIONVALUE | |||
| ZTRASHEDSTATE | |||
| ZTRASHEDDATE | |||
| ZHIDDEN | |||
| ZWIDTH | |||
| ZCLOUDASSETGUID | 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 | ||
| ZDIGITALZOOMRATIO | ||
| ZLATITUDE | 与 ZASSET 那份对照,用哪份是真题考点 | |
| ZGPSHORIZONTALACCURACY | ||
| ZFLASHFIRED | ||
| ZORIENTATION | ||
| ZTIMEZONENAME | 时间换算前必看 | |
| ZEXIFTIMESTAMPSTRING |
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 | |
ZIMPORTEDBYBUNDLEIDENTIFIER | com.apple.mobileslideshow | |
ZIMPORTEDBYDISPLAYNAME | ||
ZIMPORTEDFROMSOURCEIDENTIFIER | ||
ZIMPORTSESSIONID |
这类题的通用套路已经成型:先确定字段名(靠第 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 五届,每一届的题目里都有它的身影。
wa.db | ||
ChatStorage.sqlitemsgstore.db(安卓) | ||
msgstore.dbChatStorage.sqlite | message_type | |
ChatStorage.sqliteDeviceAgents.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 | ||
| ZWAMESSAGE | ZTEXTZFROMJID/ZTOJID/ZMESSAGETYPE/ZMESSAGEINFO/ZMEDIAITEM | |
| ZWAMESSAGEINFO | ZRECEIPTINFO | |
| ZWAMEDIAITEM | ||
| ZWAGROUPINFO | 群创建者 ID | |
| Z_PRIMARYKEY |
4.1.1 ZWAMESSAGE:消息级提问的必查表
| ZTEXT | ||
| ZFROMJID | 85254974406@s.whatsapp.netxxxx@g.us | |
| ZTOJID | ||
| ZMESSAGETYPE | 实证:46 = 投票消息 | |
| ZMESSAGEINFO | ZWAMESSAGEINFO.Z_PK | |
| ZMEDIAITEM | ZWAMEDIAITEM.Z_PK | |
| ZMESSAGEDATE | ||
| 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(形如 xxxx@g.us)有两种格式并存,而老格式里直接编着建群者的手机号和建群时间。
| 老格式 | <建群者手机号>-<建群时间 UNIX 时间戳>@g.us85263834763-1563607573@g.us | 建群者号码 = +852-63834763 建群时间 = 2019-07-20 15:26:13 |
| 新格式 | <长数字 ID>@g.us120363401289578356@g.us |
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 | _idchat_row_id(→ chat._id)、message_type、text_data | |
| chat | _idjid_row_id(→ jid._id)、会话名 | |
| jid | _iduser(JID 字符串)、raw_string | |
| message_edit_info | 消息的编辑记录 | message_row_idmessage._id)、编辑前后的内容 |
| message_ftsv2_content | docidmessage._id) | |
| media |
4.6.1 经典联表:找出「被编辑前的原文」
2023 年团体赛那道题问「两部手机的对话曾被修改过,请找出修改前的内容」,是安卓 WhatsApp 联表查询的教科书级案例。题解把五张表串成了一条链子:
| message | ||
| message_edit_info | message_row_idmessage._id | |
| chat | _idmessage.chat_row_id | |
| jid | _idchat.jid_row_id | |
| message_ftsv2_content | docidmessage._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 账号 85268421495@s.whatsapp.net 中,有哪些其他人的 WhatsApp 用户数据记录?
A. 85222117188@s.whatsapp.net
B. 85289853825@s.whatsapp.net
C. 85264795287@s.whatsapp.net
D. 85231882226@s.whatsapp.net
考点:从通讯录找 WhatsApp 账号
2021I-Q42 | 选择题
库/表:iOS WhatsApp 群组
多选题阿力士 iPhone XR 中的 WhatsApp 群组【团购-新鲜猪肉牛肉-东涌群组-9/30】有以下哪一个成员?
A. 85260617332@s.whatsapp.net
B. 85260452579@s.whatsapp.net
C. 85248791565@s.whatsapp.net
D. 85264630956@s.whatsapp.net
考点:群组成员判定
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. 447380449879@.whatsapp.net
B. 79096209701@.whatsapp.net
C. 923109725619@.whatsapp.net
D. 85256026169@.whatsapp.net
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 与 85254974406@s.whatsapp.net 聊天对话中, 于 2025-05-16 11:33:39 时的信息所传送的座标(WGS 84)是(检材:梁燕玲的手机)
2025I-Q68
在 WhatsApp 与 85254974406@s.whatsapp.net 聊天对话中, 于 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 | 原始路径 | |
flagsfile |
结合 2.3 节讲的那四个文件(Manifest.db/Manifest.plist/Status.plist/*_DEC)一起记,iOS 备份这一关就通了。
5.6 安卓三大系统库
安卓的系统数据都由 Provider 提供,库名固定,遇到就是送分:
| contacts2.db | com.android.providers.contacts/databases/ | contactsraw_contacts、data、mimetypes | mimetypes 才能知道某一行数据是电话还是邮箱 |
| mmssms.db | com.android.providers.telephony/databases/ | smsthreads | 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 | |
| iOS 视频号 | .../finder/db/finder_main.dbfinderContactTable3 | followState = 1 表示已关注 |
| 安卓配置 | com.tencent.mm_preferences.xml | |
| iOS 配置 | com.tencent.mm |
5.7.1 微信图片消息的 XML:一个高频卡点
2024 年个人赛的解法里,有一个结构必须认识。题目要求找出「Emma 报案时拍摄卡片的经纬值」,题解的发现是:微信数据里编号最大的那个文件(314.pic)对应消息库里的编号,打开 message_2.sqlite 能看到这条消息的原始 XML:
<msg> <img length="139561" aeskey="b3f5f12230235d3dfe4f90e4984c8ebf" encryver="1" md5="44c089af8c9a1414edb8ee6026be3307" filekey="wxid_t7zgo57j9m0j22_314_1725012210"/> <MMAsset> <m_assetUrlForSystem><![CDATA[F58B98FE-8010-44B7-8BF7-F23AF15DCFCA/L0/001]]> </m_assetUrlForSystem> </MMAsset> </msg>
题解抓住了其中最容易被忽略的一节: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 | 图片文件本体被加密 | |
encryver | ||
md5 | .pic 文件比对,确认对应关系 | |
originsourcemd5 | ||
filekey | <wxid>_<编号>_<时间戳>,编号与磁盘上的文件名对应,时间戳可换算成收图时间 | |
m_assetUrlForSystem | 通向 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 时间戳 | |||
| Cocoa Core Time | 2001-01-01 00:00:00 UTC | iOS 全系ZDATE、ZDATECREATED) | |
| WebKit/Chrome 时间 | |||
| 毫秒时间戳 | |||
| 可读字符串 | 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 用真题验算一遍
空口说不算数,拿两道真题的数字实际算一次。
746690400 | 1724997600,再转 UTC | 2024-08-30 06:00:00 UTC | |
1563607573 | 2019-07-20 07:26:13 UTC |
两个结果都和题解一致。特别注意第二个——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 换算时的四个细节
| 秒还是毫秒 | /1000 | |
| 浮点数 | ZDATE | datetime() 能处理小数,不必先取整 |
| 0 与负值 | ||
| NULL 空值 | 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 │ 85246257801@s.whatsapp │ │ 102 │ 7 │ │ └──────┴────────────────────────┘ └────────┴──────────────┘ │ ▲ │ │ chat 表 └──────────────┘ ┌──────┬──────────────┐ │ _id │ jid_row_id │ ├──────┼──────────────┤ │ 7 │ 7 │ └──────┴──────────────┘
消息表里没有「是谁发的」,只有一个 chat_row_id;会话表里没有名字,只有一个 jid_row_id;号码在第三张 jid 表里。想知道「这条消息是谁发的」,必须穿过三张表。这不是设计得不好,而是数据库的规范做法——避免同一个号码重复存一万遍。
7.2 外键的三种长相
找外键这件事看着玄,其实命名很有规律。记住三种长相,能解决九成情况:
X_PK | ZASSET.Z_PKZWAMESSAGEINFO.Z_PK | |
X_行号 型 | chat_row_idmessage_row_id、jid_row_id | xxx_row_idxxx 表的 _id |
ZXXX 同名型 | ZWAMESSAGE.ZMESSAGEINFOZWAMESSAGEINFO.Z_PK;ZASSET.ZEXTENDEDATTRIBUTES → ZEXTENDEDATTRIBUTES.Z_PK | 字段名就是目标表的表名 |
✓ 不确定指向谁?两条命令搞定
① PRAGMA foreign_key_list(表名); —— 如果建表时声明了外键,这里直接列出来;② 没声明的话,用值去试:把那个字段的值拿到候选表里 WHERE Z_PK = 那个值 查一下,命中就说明关系成立。第二种在 iOS 的 Core Data 库里特别常用,因为 Core Data 默认不在库里声明外键。
7.3 四种 JOIN,用「两本名册」理解
把两张表想成两本名册:左表是「消息名册」,右表是「号码名册」,两本都有「编号」这一列,JOIN 就是按编号把两边对齐。
| INNER JOIN | 最常用 | ||
| LEFT JOIN | |||
| RIGHT JOIN | |||
| CROSS JOIN | 笛卡尔积,结果会爆炸 |
! 最常见的翻车:忘了 ON
写成 FROM A JOIN B 而不写 ON A.x = B.y,SQLite 不会报错,而是给你两表行数相乘的结果。1000 行 × 5000 行 = 500 万行,查询卡死,你还以为是自己写错了条件。看到查询迟迟不返回,先检查 ON 有没有写。
7.4 四种真题联表范式
范式一:顺着外键链一路穿(2023G-Q82,五张表)
这是本文最长的一条链,也是安卓 WhatsApp 的经典结构。题解把起点、过滤器、终点列得很清楚:
message | |||
message_edit_info | message_row_idmessage._id | ||
chat | _idmessage.chat_row_id | ||
jid | _idchat.jid_row_id | ||
message_ftsv2_content | docidmessage._id | 取编辑前的原文 |
这条链的价值在于它示范了「一路 JOIN 到底」的写法——从一个 _id 出发,每经过一张表就顺手取走一个信息,最后拼出完整答案。写的时候不要一次想全,一段一段加,每加一段就跑一次看结果。
范式二:JOIN 到附属表取详情(2025I-Q58)
前面 4.3 节讲过那条 SQL,用的是 RIGHT JOIN:ZWAMESSAGE.ZMESSAGEINFO = ZWAMESSAGEINFO.Z_PK,目的是把 Protobuf 格式的 ZRECEIPTINFO 取出来。范式特征是「主表筛、附表取」——筛选条件写在主表字段上,输出列却来自附表。
范式三:题目直接考 SQL 本身(2023I-Q98)
这道题很特别——它不给数据,只给你两个库的结构和四个 SQL 选项,问哪一个能正确「找出谁前往莫斯科」,并输出转换后的本地时间。题解逐条分析了每个选项,四个错误点全都不一样:
IS NOT NULL 之类的限定 | ||
ON customer_id = date.idcus.customer_id | ||
| 没有做时间戳转换 | datetime(..., 'unixepoch', 'localtime') | |
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(DISTINCT 字段) | ||
| GROUP BY | ||
| SUM |
一个组合技,专治「有多少个不同的 X」:
SELECT COUNT(DISTINCT ZTOJID) AS 联系过的人数 FROM ZWAMESSAGE WHERE ZISFROMME = 1;
! COUNT 的两个常见误用
① COUNT(字段)不数 NULL,COUNT(*) 才数所有行——想数「全部尝试次数」要用后者;② 忘记 DISTINCT 会重复计数——题目问「多少个人」你却返回「多少条消息」,这是最常见的失分点。
7.6 联表五个坑,逐个拆
| 忘了 ON | ||
| 字段名歧义 | ambiguous column name | 所有字段都加表前缀cus.customer_id 而不是 customer_id |
| 空值参与比较 | IS NOT NULL= 比较,要用 IS NULL | |
| 类型不匹配 | ||
| 以为只有一张表有数据 | 同一信息可能在多张表里各存一份 |
八、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 | ||
| LIMIT | LIMIT 10 | ||
| 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 个列」,然后问「以下哪一个指令会产生错误讯息」:
SELECT * from Account WHERE name='Alex' OR '1'=1 | 不报错 | '1'=1 | |
... UNION SELECT NULL, NULL, NULL, NULL | 会报错 | The used SELECT statements have a different number of columns | |
SELECT * from Account WHERE name='Candy' ORDER BY 6 | 会报错 | ORDER BY 6 超出上限 | |
SELECT name FROM sys.tables | 会报错 | sys.tables |
题解的判断是:「五个列,ORDER BY 6 显然超出上限」,选 C。它还老老实实补了一句——「B 也会报错,但是和题意不符」。
i 这道题真正在考什么
考的是「你对 SQL 的边界有多熟」。四个选项分别踩在四种边界上:恒真条件、列数必须一致、排序列号不能越界、系统表不是通用的。这类题不需要你有检材,纯语法知识就能拿分,是性价比最高的分——考前花半小时把这几条边界过一遍就够。
8.4 用什么工具写 SQL
| DB Browser for SQLite(DB4S) | ||
| Navicat Premium | ||
| 命令行 sqlite3 | .headers on 看列名 | PRAGMA 用 |
| Python + sqlite3 |
✓ 导出 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 2022 年:个人赛独撑(10 题)
2022 年团体赛那道「WordPress 比特币标题题」考的是服务器侧的 MySQL,不属于手机数据库,本章不列。手机数据库题全部来自个人赛,而且出现了两个此前没考过、后来反复考的库:Photos.sqlite 与 NoteStore.sqlite。










✓ 这一年最值钱的一条经验
2022I-Q38 说明了一件事:现成工具解不出来的题,才是拉分题。
题解没有换工具,而是打开脚本源码、改掉取字段的那两行——因为「我们只需要标题和内容两个字段」,其他字段全都可以不管。
读懂脚本在取哪个字段,比换一个更贵的工具有用得多。
9.4 2023 年:从「在哪张表」升级到「怎么把表接起来」(18 题)
这一年题量翻倍,考法也变了:开始考联表。最有代表性的是团体赛 Q82——一道题串起 WhatsApp 安卓库的四张表;个人赛 Q98 干脆让你自己写一段 SQL。
团体赛(9 题)









个人赛(9 题)









✓ 这一届给出的三张「答案地图」
① 系统表当排除项:Z_METADATA/Z_MODELCACHE/Z_PRIMARYKEY 是 Core Data 的固定配件,问「哪张表存业务数据」时先排除它们。
② 数字类答案多半要计数:45、7 这两个答案,都是「按某个后缀或条件筛完再数」。
③ 跨库比对才会给对:Q58 的 10 与 7 就是一次典型陷阱。
9.5 2024 年:成为主力题型(19 题)
两届加起来 19 题,而且开始出现「同一批检材跨库关联」的考法:微信的聊天记录 → 关联相册库拿经纬度、档案名在相册库与微信库之间交叉验证。这一年也是实况照片(Live Photo)判定第一次成体系地出现。
团体赛(9 题)






个人赛(10 题)
✓ 这一年留下的三条可复用套路
① 「用时间换记录」:先把时间换算成时间戳,再去库里筛那一条(2024G-Q11、2024I-Q2 都是这么做的)。
② 「一个值串两个库」:UUID/GUID 是跨库的桥(2024I-Q19)。
③ 「工具没显示,不代表库里没有」:UUID、后置镜头这类字段,火眼的界面不展示,但库里有(2024I-Q8)。
9.6 2025 年:绝对主力(34 题)
这一年两届一共 34 道,是全五届最多的一年,也是考法最成熟的一年:相册库连着 9 道题、同一个字段的两组值要判断该信哪一个、还要从字段格式反推时间、从群组 ID 反推创建者手机号。
团体赛(17 题)
个人赛(17 题)
! 2025 年这批题里,四处「答案或题干本身有问题」
这一届题解的复盘质量很高,把出题瑕疵都标了出来,值得单独记住:
① 检材标错:160—168 题说用冯子超的 iPhone,答案却取自陈民浩的 iPhone;169—177 题同理(2025G)。
② 字段用错:官方题解用 ZDATECREATED 排序,但「写进数据库的时间」应该是 ZADDEDDATE。
③ 题干与选项不符:Q164 要求 heic 文件,实际答案却是 JPG;Q166/Q167 直接标「此题无解」。
④ 库内两值打架:同一文件的经纬度在两张表里不一致(2025I-Q40)。
考场上遇到这种题,把依据写清楚比猜一个答案更划算。
9.7 回头看第三到第五章:库的出现频次
把上面 87 道题按「库」重新归一次类,就得到一张很实用的表——哪些库是必考的、哪些是一届一现的:
Photos.sqlite | 五届全勤 | ZIMPORTEDBY…)、来源 App、两组经纬度取舍、Live Photo 判定、ZADDEDDATE vs ZDATECREATED | |
ChatStorage.sqlite | ZWACHATSESSION)、ZWAMESSAGE 投票(type 46)、群组 ID 拆手机号、头像文件名里藏时间戳 | ||
msgstore.db | wa.db);2023G-Q82;2024G-Q11 | message/message_edit_info/chat/jid)、message_type 取值 | |
NoteStore.sqlite | ZICCLOUDSYNCINGOBJECT,解密要改脚本取字段位置 | ||
CallHistory.storedata | ZCALLRECORD;和 AddressBook.sqlitedb 接号码后按时间计数 | ||
sms.db | |||
AddressBook.sqlitedb | |||
MobileBluetooth.ledevices.other.db | |||
DeviceAgents.sqlite | ZWASMBDEVICEAGENT | ||
finder_main.db | finderContactTable3followState=1 为已关注 | ||
message_2.sqlite | |||
EnMicroMsg.db | |||
client.db | |||
| 火眼普遍没有解析 |
✓ 从这张表能读出三件事
① 两个库必须练到闭眼能开:Photos.sqlite 与 ChatStorage.sqlite,五届里几乎每届都考,而且都是多题连考。
② 第三方 App 的库是「送分题与丢分题的混合区」——它们本身不难,难在火眼不给解析、你也不知道该去哪个目录找。
③ 安卓 WhatsApp 与 iOS WhatsApp 是两套表,别把 msgstore.db 的表名往 ChatStorage.sqlite 上套。
9.8 同届还有一批「数据库题」,但不在本章范围
为了不让你误会「数据库题就这些」,这里把同届考到、但属于服务器侧数据库的题一并列出来。它们同样要开库、写 SQL,只是不在手机里,而是落在服务器镜像、虚拟机或备份文件上:
wp_commentscomment_post_ID 关联文章;用户全名要从 WordPress 库取 | |||
.bash_history 拿)、被清空的 creditcard 表如何从 dump.sql 恢复并计数 | |||
action_log/player_attribute/user 表 | |||
n8n_chat_histories 表、按 session_id 数问答 | |||
userID 统计交易次数与金额 |
为什么分开列?因为解法差别很大:手机库考「认得库和字段」,服务器库考「怎么把库恢复出来再查」——后者往往要先从 .bash_history、.env、dump.sql 或者 Docker 配置里把连接信息挖出来。如果这两类混着准备,很容易两边都不熟。
手机数据库这一块的完整地图,就是本章 87 道题的落点。下一章把这些落点压成四张速查表,考场上直接查。
十、五年考点地图:15 个考点,哪些年年考、哪些刚成形
第九章是按届看题,这一章反过来——把 87 道题重新打散,按「答案最终落在哪一类知识、哪一个动作上」归成 15 个考点,再看每个考点在五年里出现了几届、几道题。同一道题可以挂多个考点(例如 2025 个人赛的 WhatsApp 位置消息题,既属于「消息类型与媒体内容」,也属于「位置坐标」),全部 87 道题合计 135 个考点标签。
i 为什么用「覆盖届数」而不是「题数」看重复度
五届收录的题量差得很远:2021 年 6 题、2022 年 10 题、2023 年 18 题、2024 年 19 题、2025 年 34 题。比题数会把 2025 年的小考点抬成「重点」,比届数才能看出它是不是每年都在考。所以下文一律用「覆盖 N 届」当重复度的尺子,题数只作参考。
10.1 地图是怎么画出来的
- 切分依据是「答案落在哪」
,不是按库名、也不是按届次。问「这张照片怎么来的」和问「这个文件谁拍的」,题干完全不同,但答案都落在同一族来源字段上,就该归同一个考点。 - 一个考点里可以有多个库
:K14「系统库」同时装了蓝牙库、通话记录、短信、iCloud 同步清单;一个库里也可以有多个考点:相册库一个库就撑起了 K03 至 K07 五个考点。 - 15 个考点没有一个只考过一届
——最少的也覆盖了 2 届。这一点本身就是结论:五年真题里没有「偶然考点」,凡是出现过的知识点,都值得按「还会再考」来准备。
10.2 考点总览:15 个考点按重复度排序
| 5 届 | |||||
| 5 届 | |||||
| 5 届 | |||||
| 4 届 | |||||
| 4 届 | |||||
| 4 届 | |||||
| 4 届 | |||||
| 4 届 | |||||
| 3 届 | |||||
| 3 届 | |||||
| 3 届 | |||||
| 3 届 | |||||
| 2 届 | |||||
| 2 届 | |||||
| 2 届 |
维度是更大的归类:相册库(K03—K06)、聊天库(K07—K09)、定位与格式(K01、K02,找库与辨明库的形态)、跨库技法(K10—K13,时间、位置、SQL、文件本体)、系统与业务库(K14、K15)。
排在最上面的三个(K04 相册库来源、K06 相册库属性、K10 消息类型与媒体)五届一届没落;再往下五个覆盖 4 届、四个覆盖 3 届、三个覆盖 2 届。把这 15 行当成复习清单,从上往下练即可。
10.3 重复考点地图:考点 × 届次
下面这张矩阵是本章的核心。横向看一行——格子越满,说明这个考点越「年年考」;纵向看一列——格子越多,说明那一届的考核面越宽。格子里的数字是该届挂在此考点的题数,— 表示该届没有考。行底色按重复度分档:深蓝=5 届,浅蓝=4 届,淡蓝=3 届,白=2 届。
三个观察直接给出来:① 相册库占了最上面的两行,它是五年里最稳定的出题池;② 2025 年那一列几乎每行都有数字(15 个考点里覆盖 14 个),出题面在这一届达到最宽;③ 2022 那一列格子最少,但 2022 只有 10 道题入库,属于题量小而不是考得浅。
10.4 年年考的三个考点(覆盖 5 届)
K04 相册库:来源与生成方式(12 题,五届全勤)
这是五年重复度最高的单一考点(2021、2022、2024、2025 各届都考)。题干从「提供了什么取证信息」到「通过哪种方式生成」再到「由哪一个 App 拍摄」,问法一直在换,但答案永远落在同一个字段族:ZIMPORTEDBYBUNDLEIDENTIFIER/ZIMPORTEDFROMSOURCEIDENTIFIER 一类。真正的考点是把 BundleID 翻译成人话:com.apple.sharingd 是隔空投送、SpringBoard 是截图、同步下来的看云端与本地时间先后。
该背什么:来源字段的值不是中文描述,而是 BundleID 或系统组件名:com.apple.sharingd = 隔空投送、SpringBoard = 截图、Safari = 网页下载。问「怎么来的」先想这个字段。
代表题:2021I-Q37、2022I-Q34、2024G-Q19、2025G-Q166、2025I-Q35(完整题干与解法见第九章对应卡片)
K06 相册库:属性与文件类型(10 题,五届全勤)
相册库里「东西最多」的一个考点,出题方式是给四条描述判断哪几条对/错,每条描述对应一个字段。2023 个人赛考「哪项描述是错的」,2024 个人赛考后置镜头张数,2025 年连考隐藏相册与缩时摄影(个人赛)、机型比对(团体赛);安卓侧还多一层——库里存着 AI 打标的主体标签(2023 团体赛的拉面题)。这一类题不难,但要求手快:先 PRAGMA table_info 认字段,再逐条对。
该背什么:这一类题的标准动作是先 PRAGMA table_info 认字段,再逐条核对描述;「由哪个 App 拍摄」是来源字段,「是哪一类文件」是类型标记,两者别混。
代表题:2023I-Q59、2024I-Q22、2024I-Q23、2025I-Q39(完整题干与解法见第九章对应卡片)
K10 消息类型与媒体内容(10 题,五届全勤)
把聊天库里「非纯文本」的那部分单独归一类。2023 至 2025 连续三届出现,且题型越来越细:统计语音对话段数(按 .opus 后缀计数)、判一条消息是发出还是接收(ZISFROMME)、读位置消息的经纬度、数投票活动的次数、认出表情包对应的枚举值(20)。共同动作是:先确定类型字段的取值含义,再统计或取值——枚举表题解不给,要从实际数据反推。
该背什么:类型字段是枚举值,题解不给表,只能从实际数据反推(20 = 表情包、46 = 投票);收发判定看 ZISFROMME(1 为发出、0 为接收);位置消息的经纬度不在消息表,在媒体表里。
代表题:2023I-Q56、2024G-Q11、2024G-Q18、2025I-Q58、2025I-Q74(完整题干与解法见第九章对应卡片)
10.5 常客:五个覆盖 4 届的考点
这五个考点不是每届都出现,但隔一年就会回来一次,属于「必须会」的第二梯队。
K15 第三方 App 业务库(24 题,出现于 2022、2023、2024、2025)
五年里变化最大、也最能体现「读库」价值的一类:库里存的都是工具没解析的业务数据。2022 年的电子书与备忘录、2023 年的运动与电商、2024 年的外卖与邮件、2025 年的 AI 聊天与智能家居,几乎没有重复的 App,但动作完全一样:先按包名定位目录、罗列全部表名、按业务关键词猜字段、再用时间戳筛那一条记录。题解反复提示的一句话是「自动取证工具读不完这些」,这就是所谓剩下 20% 的分。
该背什么:标准四步:按包名定位目录 → 罗列全部表名 → 按业务关键词猜字段 → 用时间戳筛那一条记录。库很小也不代表没证据(外卖两个库各 1 条记录,就是答案)。
代表题:2022I-Q10、2024G-Q90、2024G-Q125、2025G-Q175
K14 系统库(12 题,出现于 2021、2023、2024、2025)
贯穿五届中四届的「基础分仓库」。2021 年靠蓝牙库(UUID 与 MAC 辨析)开场,2023 年出现在通话与短信题里(「哪张表存通话记录」、短信 GUID 定位),2025 年转向 iCloud 同步清单、空投痕迹与安卓媒体总库。有两个必须记住的点:火眼只给 MAC,要 UUID 得自己开蓝牙库;Core Data 库里 Z_METADATA/Z_MODELCACHE/Z_PRIMARYKEY 三张表天然是「哪张表存业务数据」题的干扰项。
该背什么:两个高频细节:火眼的蓝牙信息只给 MAC,要 UUID 必须自己开蓝牙库;Core Data 库里 Z_METADATA/Z_MODELCACHE/Z_PRIMARYKEY 三张系统表,天然是「哪张表存业务数据」题的干扰项。
代表题:2021I-Q9、2023I-Q64、2024I-Q8、2025I-Q23
K07 位置坐标:库内字段与 EXIF 交叉(10 题,出现于 2021、2023、2024、2025)
2023 至 2025 三届连考。考法有两面:同一张照片库里两组经纬度不同,该信哪一个(2025 个人赛,ZASSET 与 ZEXTATTR 各一组,要靠同地点其他照片交叉比对);原文件 EXIF 里根本没有 GPS,只能回库读(2025 个人赛另一题)——这两题刚好互为反面,说明「EXIF 与库谁更可信」没有固定答案。运动 App 的轨迹表(2023 团体赛跑步起点)与聊天库的位置消息(2024 个人赛微信 XML、2025 个人赛 WhatsApp 位置消息)也归这一考点的动作:先找到坐标寄存在哪张表。
该背什么:同一份坐标可能有两份(ZASSET 一份、ZEXTATTR 或 EXIF 一份),没有哪一份天然更可信,要靠同地点的其他照片交叉比对;EXIF 里没有 GPS 时就回库读,别以为没有。
代表题:2023G-Q7、2025I-Q40、2025I-Q64、2024I-Q1
K01 找库:数据目录与包名(7 题,出现于 2022、2023、2024、2025)
五年里每一届都至少有一题只问「数据存在哪个库/这个 App 的包名是什么」。考法一直在换外衣:2022 问电子书的书库在哪、2024 问照片数据在哪个库、2025 问小程序式的包名(智能家居、AI 聊天),但动作始终是同一步——先定位目录,再开库。这类题分值好拿,前提是认得包名规律:安卓按包名切目录、iOS 分私有域与 AppDomainGroup 共享域。
该背什么:安卓统一在 /data/<包名>/databases/;iOS 分私有域与 AppDomainGroup 共享域;认得开发者就能猜出包名(Quora → com.quora.app.Experts,小米 → com.xiaomi.smarthome)。
代表题:2022I-Q10、2023G-Q3、2024G-Q12、2025G-Q169
K11 时间戳换算(6 题,出现于 2022、2023、2024、2025)
五年里最「稳定送分又稳定送命」的考点,2021、2022、2023、2024、2025 各届都有。坑一共四个:①格式——Cocoa 以 2001 年为 0 点,忘加 978307200 就错到离谱;②时区——题干写 UTC+8 而工具默认输出 UTC,少加 8 小时(2023 个人赛的「I am already home」就是这题);③反向坑——库里存的已经是本地时间,再 +8 反而多 8 小时(2023 团体赛跑步时间);④单位——安卓常见毫秒级,先除 1000。每一届都要重踩一遍,说明它必须练到条件反射。
该背什么:四条一起背:Cocoa 加 978307200、UTC+8 再加 8 小时、库里已是本地时间就别再加、安卓毫秒先除 1000。每次作答前先回题干确认它要的是 UTC+8 还是 UTC+0。
代表题:2021I-Q33、2023G-Q6、2023I-Q62、2024I-Q2、2024I-Q29
10.6 正在成形:三届与两届的考点
三届的四个考点已经算「稳定」,两届的三个考点则要留意——它们往往是出题人新加的方向,2026 年大概率会接着考。
覆盖 3 届(稳定)
K08 聊天库:库结构与消息定位(9 题,2021、2023、2025):安卓 WhatsApp 双库(wa.db 联系人+msgstore.db 消息)、iOS 单库 ChatStorage.sqlite;精确定位靠 GUID 或关键字
该背什么:三个库名背死:iOS WhatsApp 只有 ChatStorage.sqlite;安卓是 wa.db(联系人)+ msgstore.db(消息);微信是 EnMicroMsg.db(通常加密)。精确定位靠 GUID 或关键字,不靠翻。
代表题:2021G-Q30、2024I-Q42、2024I-Q41、2025I-Q50
K03 相册库:主表与文件身份(7 题,2022、2023、2025):ZASSET 主表、Z_PK 自增主键、ZFILENAME、本地名与原文件名、修改后另存
该背什么:Z_PK 是 Core Data 的自增主键,写入顺序=主键顺序;库里同时存本地文件名与原始(云端)文件名,「原名」问的是后者。
代表题:2025G-Q160、2025G-Q162、2025I-Q38、2025I-Q42
K09 聊天库:联系人与群组(6 题,2023、2024、2025):群组两张表(群信息+会话)、群 ID 里含建群者手机号与建群时间戳、封存/锁定/多设备登录
该背什么:群组这组题的值都藏在「名字」里:群 ID 形如 <建群者手机号>-<建群时间戳>@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 五年趋势:考核面在逐年扩大
- 考核面从 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,五届全勤)
ZIMPORTED*ZRECEIVED* 的命名区别 | ||
sharingd = 隔空投送) | ||
SpringBoard = 截图) | ||
Infltr 拍摄) | ||
② 时间戳换算(K11)的四种坑
③ 聊天库三个考点的问法迁移(K07/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 考点自查表
最后给自己打一次分。逐行问自己:这个考点,我能不能不看笔记说出「去哪个库、读哪个字段、要注意什么」?说不出来的,把编号记下来,回第九章找代表题重做一遍。
✓ 这一章的用法
别把它当知识点汇总来读,当进度表用:15 行全部能说出「怎么查」,手机数据库这类题就不会再丢分了。第九章提供逐题拆解,这一章提供重复度依据,两张表合起来看,就能判断哪些投入是值得的。
十一、速查表:考场上的四张表
这一章是全文的落点。前面八章都在讲「为什么」,这一章只有「去哪查」。
i 这四张表怎么用
表一、表二:按平台列出每个库的路径与核心字段,遇到陌生检材时先对一遍;表三:按题目的问法索引——这是最该打印出来的一张,题目怎么问,你顺着它找到「去哪个库、看哪个字段」;表四:反查表,看到题干里冒出一个字段名,回来查它是哪个库的。
10.1 速查表一:iOS 常考库
| Photos.sqlite | /var/mobile/Media/PhotoData/ | ZASSETZEXTENDEDATTRIBUTES、ZADDITIONALASSETATTRIBUTES、ZGENERICALBUM、ZCLOUDMASTER | ZUUIDZFILENAME、ZUNIFORMTYPEIDENTIFIER、ZDATECREATED、ZADDEDDATE、ZLATITUDE/ZLONGITUDE、ZVIDEOCPDURATIONVALUE、ZEXTENDEDATTRIBUTES |
| ChatStorage.sqlite | /var/mobile/Applications/group.net.whatsapp.WhatsApp.shared/ | ZWAMESSAGEZWAMESSAGEINFO、ZWACHATSESSION、ZWAMEDIAITEM、ZWAGROUPINFO | ZTEXTZFROMJID/ZTOJID、ZMESSAGETYPE、ZMESSAGEINFO、ZMEDIAITEM、ZISFROMME、ZCREATORJID |
| DeviceAgents.sqlite | ZWASMBDEVICEAGENT | ||
| NoteStore.sqlite | /AppDomainGroup-group.com.apple.notes/ | ZICCLOUDSYNCINGOBJECT | |
| CallHistory.storedata | /var/mobile/Library/CallHistoryDB/ | ZCALLRECORDZCALLBPROPERTIES) | ZADDRESSZDATE(Cocoa 时间) |
| AddressBook.sqlitedb | /var/mobile/Library/AddressBook/ | ZABCDRECORD | |
| sms.db | /var/mobile/Library/SMS/ | messagechat | |
| Manifest.db | /var/ | Files | fileIDdomain、relativePath |
| message_N.sqlite | com.tencent.xin/Documents/<32位哈希>/ | aeskey、filekey、m_assetUrlForSystem(GUID) | |
| finder_main.db | .../finder/db/ | finderContactTable3 | followState |
10.2 速查表二:Android 常考库
| msgstore.db | /data/data/com.whatsapp/databases/ | messagechat、jid、message_edit_info、message_ftsv2_content、media | _idchat_row_id、jid_row_id、message_row_id、docid、message_type、text_data |
| wa.db | /data/data/com.whatsapp/databases/ | wa_contactswa_contects,应为笔误) | |
| contacts2.db | /data/data/com.android.providers.contacts/databases/ | contactsraw_contacts、data、mimetypes | mimetypes 才知道某行是电话、邮箱还是姓名 |
| mmssms.db | /data/data/com.android.providers.telephony/databases/ | smsthreads | |
| 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/ |
10.3 速查表三:按题目问法索引(最常用的一张)
做题时从这里开始查。左边是题目里出现的话,右边是你的动作。
ZWAMESSAGE | ZTEXT、ZFROMJID、ZISFROMME | |
message→chat→jid | chat_row_idjid_row_id → jid.user,三级联表 | |
ZWAMESSAGE→ZWAMEDIAITEM | ZMEDIAITEM 的值,去 ZWAMEDIAITEM 按 Z_PK 过滤 | |
ZASSET + ZEXTATTR | 两处经纬度都要看,冲突时用邻近照片轨迹判定 | |
ZEXTENDEDATTRIBUTES | ZCAMERAMAKEZCAMERAMODEL | |
ZADDITIONALASSETATTRIBUTES | ZIMPORTEDBYBUNDLEIDENTIFIER | |
ZASSET | ZVIDEOCPDURATIONVALUE != 0 | |
ZASSET | ZTRASHEDSTATEZTRASHEDDATE、ZHIDDEN | |
ZWAGROUPINFO.ZCREATORJID或解析群 ID 老格式 | <手机号>-<时间戳>@g.us | |
ChatStorage 记录 | ||
ZWACHATSESSION | ||
Media/Profile/ | ||
DeviceAgents.sqlite | ZWASMBDEVICEAGENT | |
ZCALLRECORD | ZADDRESSZDATE 过滤后 COUNT(*) | |
AddressBook.sqlitedb或 JID 的 @ 前半段 | ||
message_edit_info + message_ftsv2_content | message_row_idmessage._id ← docid | |
GROUP BY 该字段 | ||
ZWAMEDIAITEM.ZMEDIALOCALPATH;备份里查 Manifest.db | ||
消息 XML | filekeyaeskey(解密)、md5(校验) | |
XML 里的 m_assetUrlForSystem GUID = 相册库的 ZUUID | ||
finderContactTable3 | followState = 1 | |
SELECT name FROM sqlite_master WHERE type='table'; | ||
PRAGMA table_info(表名); | ||
GROUP BY 字段COUNT(*) |
10.4 速查表四:看到字段名,反查它属于哪个库
题干或选项里直接给你一串字段名时,这张表能帮你秒判方向。
ZASSETZEXTATTR/ZADDASSETATTR | ||
ZUUID | ||
ZMESSAGETYPEZMESSAGEINFO | ||
ZFROMJIDZTOJID | ||
ZMEDIAITEM | ||
ZRECEIPTINFO | ZWAMESSAGEINFO | |
ZCREATORJID | ZWAGROUPINFO | |
ZICCLOUDSYNCINGOBJECT | ||
ZCALLRECORDZADDRESS/ZDATE | ||
chat_row_idjid_row_id/message_row_id | ||
message_ftsv2_contentdocid | 编辑前的原始文本 | |
text_datamessage_type | ||
followStatefinderContactTable3 | ||
m_assetUrlForSystemaeskey/filekey | message_N.sqlite | |
fileIDdomain/relativePath | ||
Z_PRIMARYKEYZ_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 条易错点
.storedata 这类 | SQLite format 3 就是库 | |
-wal/-shm | 三个文件放同一目录再打开 | |
| Cocoa + 978307200 = UNIX | ||
ZASSET 里找相机型号 | ZEXTENDEDATTRIBUTES,顺着外键跳过去 | |
| 对比邻近记录 + EXIF 印证 | ||
JOIN 的 ON 条件 | ||
ambiguous column name。一律写 cus.customer_id 这种全名 | ||
ZEXTATTR 到底是什么) | sqlite_master 对全称,或用导字段脚本搜关键词 | |
COUNT(字段) 数总数 | COUNT(*),数「多少个人」要 COUNT(DISTINCT 字段) | |
GROUP BY 字段 + 抓样本内容现场反推,零记忆且不会错 | ||
十三、要掌握到什么程度:四级能力清单
把「会做手机数据库题」拆成四级。可以拿它给自己打分,看看卡在哪一级,再去看对应章节。
| L1 | 认得出 | sqlite_master 列出表名;能说出这个库属于哪个 App。 | |
| L2 | 找得到 | SELECT ... WHERE ... 把记录捞出来。 | |
| L3 | 接得起 | Z_PK/xxx_row_id 串两三张表;会把题干的时刻换算成时间戳当筛选条件;会 GROUP BY 反查枚举值;会在两个库之间做接力。 | |
| L4 | 辨得清 |
十四、五周练习路线
不用买课,全程只用公开资源 + 你手上的这几篇题解。
| 第 1 周 | sqlite_master,把表名抄一遍;③ 用 2.5 节的脚本导出全库字段清单,存成自己的参考文件 | |
| 第 2 周 | ZASSET 和 ZEXTENDEDATTRIBUTES 的字段对着库抄一遍 | |
| 第 3 周 | ZWAMESSAGE → ZWAMESSAGEINFO → ZWAMEDIAITEM 三段链;③ 安卓侧练 4.6.1 节的五表链,把「编辑前原文」亲手查出来 | |
| 第 4 周 | strftime 把任意给定时刻换成 Cocoa 秒数;③ 复现第 7.4 节四个范式,每个至少独立写一遍,不看答案 | |
| 第 5 周 |
结语
写这一篇的起因,是发现近五年美亚杯的手机题有个很集中的落点:出题人越来越喜欢把答案埋在一个具体字段里——一个 ZVIDEOCPDURATIONVALUE、一个 ZCREATORJID、一个 followState、一个 ZRECEIPTINFO。自动取证工具能帮你走到门口,但最后那一脚得你自己迈。
而这条路其实比想象中窄——常用的库就那十来个,核心的表就那二三十张,真正被反复考的字段不到六十个。本文把它们全部收在了第十一章的四张表里。
所以别再觉得「数据库题要背很多」。你要记的不是几十张表的几百个字段,而是七个动作:判平台、找库、列表名、查字段、定条件、接外键、交叉验证。把这七个动作变成肌肉记忆,剩下的都是体力活。
✓ 最后一句提醒
手机取证里的「答案」,永远以原始记录为准。工具给你的是它算出来的结果,而库给你的是当时真实写下的那串数字。这也是为什么——五年了,这个考点一次都没落过。
敬请各位大佬关注:小谢取证
扫取二维码获取
更多精彩
小谢取证