关键词:FSEvents、fseventsd、DocumentRevisions、Time Machine、Backups.backupdb、APFS 快照、废纸篓
难度:进阶
前置知识:macOS 目录结构(见01)、APFS 快照(见文件系统分析)
一、概述
macOS 上删掉的东西通常还有痕迹可追,只是这三个机制各补一块,单拎出一个都不够用。
最容易被误解的是 FSEvents。fseventsd 每当卷上发生创建、修改、重命名、删除,就往该卷的数据库里追加一条记录,形如「卷 disk3s5 在 2024-09-05 14:22:25 有 3 个文件被创建」。看后半句——记录里没有文件名。disk3s5 是卷标识,后面跟的是目录路径和事件计数,不是文件清单。要落到具体文件上,只能把事件和同一时刻的其它证据对齐。资料里把 FSEvents 讲成「能列出所有被删文件」的,源头上就是漏了这条。
现场还有一个坑:.DocumentRevisions-V100 不是 FSEvents 数据库。它属于「文件版本」功能,保存 iWork、Office 等应用开启版本管理后产生的内容快照。FSEvents 数据库在 .fseventsd,两个目录名长得像,机制毫无关系。
第三块是 Time Machine,也是三块里价值最高的:它是 macOS 上唯一能合法恢复「已删除文件历史版本」的机制(连同 APFS 本地快照)。Windows 没有等价的系统级历史版本机制,做跨平台取证时这个差别得先摆出来。
这三个工件在流程里的位置不一样,得分开看。FSEvents 是分析环节的目录级变化证据,上游是镜像接入,下游接时间线构建,它给的粒度是「某时刻某目录有若干变化」,文件名得从别处补。APFS 本地快照 属于证据来源而不是分析步骤,它随主卷镜像一起取得,拿到它不需要额外动作。Time Machine 备份盘 则是独立扣押对象,它不在主机镜像里,备份盘必须单独扣押——委托范围里没写这一项,到现场才发现有备份盘,程序合法性上会出问题。这个区分直接影响排期:2.6 那张对比表里的「取证可得性」一行,就是决定前面工作怎么排的。
落到具体物件上,形态是这样的:
| 形态 |
位置 |
关键结构 |
| FSEvents 数据库 |
<卷>/.fseventsd/ |
每卷一个目录,内含多个编号文件;记录是聚合后的目录级事件,含时间与变化类型标志 |
| 事件内容 |
同上 |
只有目录路径、变化计数、事件类型、时间戳,没有文件名 |
| 保留机制 |
由 fseventsd 的保留策略控制 |
时间跨度随系统活动量变化,不能假设覆盖整个时间窗 |
| 文件版本 |
/.DocumentRevisions-V100/ |
不是 FSEvents,是应用开启版本管理后的内容快照 |
| Time Machine 备份集 |
/Volumes/<备份盘>/Backups.backupdb/<主机名>/<日期>.backupset/ |
增量备份集,含 000.dmg 等稀疏映像与 Info.plist 元数据;Latest 软链指向最新集 |
| APFS 本地快照 |
与卷同一容器内 |
与卷同生共死,跨容器不可用;不是完整副本,只存变化的块 |
| 废纸篓 |
~/.Trash、/Volumes/<卷>/.Trashes/<uid> |
文件保留原时间戳,是时间线的好锚点 |
这张表里有三条判定要点必须先立住:FSEvents 记录没有文件名、.DocumentRevisions-V100 不是 FSEvents、APFS 快照与 Time Machine 备份是两种不同的证据来源。
据此能回答的是这些:某个目录在某个时刻发生过若干次文件变化、变化类型(创建 / 修改 / 重命名 / 删除)、某个文件的旧版本内容(若在备份集或本地快照里)、以及某个时间点某文件是否已存在。
回答不了的,以及六个必须先认清的边界。
FSEvents 说不出是哪些文件。 它给的是目录与计数,想知道具体文件名必须靠交叉补全——Spotlight 索引、废纸篓残留、备份集都要用上。
「移到废纸篓」与「永久删除」无法区分。 两者都会产生删除事件,没有独立的「清倒废纸篓」记录。这个区分能力不存在,报告里不能靠它下结论。
FSEvents 的覆盖时段不可假设。 保留跨度随系统活动量变化,超出保留期的部分在镜像里就是不存在。
跨容器的历史版本。 APFS 本地快照与卷同生共死,卷坏了它也没了;只有 Time Machine 备份集能跨容器,但必须单独扣押。
文件内容的变化过程。 FSEvents 记的是「变了」,不是「从什么变成了什么」。
加密卷内的内容。 FileVault 加密的卷上,时间机器备份集里的内容同样受保护,不解密就只能得到存在性与元数据。
读到这里你应该已经知道:APFS 容器的快照机制与卷结构,否则无法区分本地快照与外部备份(见APFS 容器与快照);macOS 目录结构与 ~/Library 分层(见macOS 取证工件地图);时间戳的语义与时区处理,FSEvents 事件时间与文件时间戳的基准必须统一;以及文件系统的版本控制与 inode 关系,否则解释不了「为什么废纸篓里的文件保留了原时间戳」。
还有一条纪律:清楚证据来源必须写清——来自主镜像的快照与来自外部备份盘的备份集,可采性与法律效力不同。
二、核心原理
2.1 FSEvents 数据库的位置
| 路径 |
适用 |
说明 |
/System/Volumes/Data/.fseventsd |
Big Sur+ |
数据卷上的 FSEvents 数据库,实测属主 root:wheel,权限 drwx------ |
/Volumes/<卷名>/.fseventsd |
10.10–10.15 |
非系统卷上的索引 |
/var/db/dyld/ |
10.9 及以前 |
旧路径,已废弃,与 FSEvents 无关 |
实测 ls -ld 输出 drwx------@ 127 root wheel 4064(127 个条目 = 大量按目录哈希的数据库文件)。
这个权限直接构成取证上的硬约束:现场普通用户读不到,必须 sudo;离线挂载同样需要 root,而且 apfs-fuse 会不会暴露这个目录取决于实现。
它不是易失数据(在磁盘上,随卷一起 dd),但解析需要专用工具。
2.2 FSEvents 记录了什么(以及没记录什么)
这是本篇最重要的技术边界。
| 记录 |
不记录 |
| 卷标识、目录 inode 路径 |
★ 文件名 |
| 事件的类型(创建/修改/删除/重命名) |
文件内容 |
| 事件发生的时间 |
操作者身份 |
| 同一目录下的变更计数 |
单个文件的完整路径 |
记录形态(简化示意,非真实二进制格式):disk3s5 /Users/chenhao/Downloads/ 2024-09-05 14:22:25 3 files created
| 目标 |
FSEvents 能否直接给出 |
| "某目录在 X 时刻有文件被删除" |
★ 能(数量与时间) |
| "某次批量删除的规模" |
★ 能 |
| "被删除的文件叫什么名字" |
不能 |
| "谁删除的" |
不能 |
| "删除的文件去了哪里" |
不能(可与废纸篓交叉) |
报告写法约束:FSEvents 只能支撑"在 T 时刻、D 目录发生了 N 次变更事件"这类结论。任何写出具体文件名的 FSEvents 结论,都必须有其他证据补足文件名来源,并说明补足过程。
2.3 解析 FSEvents 的工具现实
| 工具 |
实际能力 |
适用性 |
fs_usage |
★ 是实时系统调用监视器(man fs_usage:「report system calls … related to filesystem activity in real-time」),需 root 实时运行 |
不是 FSEvents 解析器。只能现场监听当前会话,看不到历史 |
| FseventsParser 类工具 |
解析 .fseventsd 下的 fs_YYYYMMDD 记录文件 |
离线首选,但输出的是目录级事件 |
fseventer 类工具 |
社区工具,部分支持导出 |
兼容性需实测 |
直接 strings |
只能得到目录路径片段 |
只能作为线索 |
必须澄清一点:不少资料把 fs_usage 列在"FSEvents 解析工具"里,这是错的。
fs_usage 监听的是当前实时系统调用,不读取 .fseventsd 里的历史记录。在已关机或已镜像的检材上它完全无效。
FSEvents 的历史数据只能用专用解析器读取。
这条得写进报告局限性:
FSEvents 解析工具均为第三方实现,不同版本对 APFS 与新版 .fseventsd 格式的兼容性差异较大。某目录的 FSEvents 事件若无法解析,结论只能写"因解析工具不支持该格式,未能获取",不能写"无相关操作"。
2.4 .DocumentRevisions-V100:文件版本,不是 FSEvents
| 维度 |
FSEvents(.fseventsd) |
文件版本(.DocumentRevisions-V100) |
| 内容 |
目录级变更事件 |
文件内容的历史版本 |
| 粒度 |
目录 + 计数 + 时间 |
完整文件内容快照 |
| 触发 |
系统自动,任何文件操作 |
仅应用主动开启版本管理时 |
| 位置 |
/System/Volumes/Data/.fseventsd |
/System/Volumes/Data/.DocumentRevisions-V100 |
| 权限 |
drwx------ root |
实测普通用户 Operation not permitted |
ls -d /System/Volumes/Data/.DocumentRevisions-V100
# → 存在(Big Sur+ 数据卷根下)
这三个场景下它才有价值:
| 场景 |
价值 |
| iWork / Office 文档被修改多次 |
★ 可拿到被覆盖前的旧版本内容 |
| 文件被"另存为"后原文件删除 |
旧版本可能仍留存 |
| 内部文件被外部改写 |
内部版本与磁盘版本可对比 |
限制:只有开启了版本管理的文件才有内容快照,普通文件(尤其 Office 之外的格式)通常没有。
没有 .DocumentRevisions 记录,绝不等于文件没有被改过。
2.5 Time Machine:结构与位置
Time Machine 是 macOS 的独立备份系统。
/Volumes/<备份盘名>/Backups.backupdb/
└── <主机名>/
├── <年>-<月>-<日>-<时分>.backupset/ ← 增量备份集
│ ├── 000.dmg / 001.dmg ... ← 稀疏映像
│ └── Info.plist ← 该备份集的元数据
└── Latest ← 指向最新备份集的符号链接
tmutil destinationinfo # 目标卷信息
tmutil listbackups # 列出所有备份集
tmutil latestbackup # 最新备份时间
tmutil listlocalsnapshots / # 本地 APFS 快照
tmutil calculatedrift / # 备份链是否可延续
实测本机 tmutil destinationinfo 返回 No destinations configured.(未配置备份目标)——这本身就是一种证据状态说明。
取证价值:
| 能拿到 |
说明 |
| 已删除文件的历史版本 |
★ macOS 独有优势,Windows 无等价系统机制 |
| 某个时间点的完整目录快照 |
可做"点时刻"存在性证明 |
| 备份覆盖的时间范围 |
可确定"某时段数据已被备份覆盖" |
| 备份链是否完整 |
calculatedrift 可判断有无缺口 |
2.6 APFS 快照 vs Time Machine:必须分清
这是很多资料混为一谈的地方,也是本篇的第二个重点。
| 维度 |
APFS 本地快照 |
Time Machine |
| 位置 |
同一个 APFS 容器内(系统自动维护) |
外部磁盘 / 网络目标 |
| 数量 |
macOS 自动保留最近若干个(通常按空间与时间策略) |
由用户配置的备份频率决定 |
| 目的 |
便于恢复误删、支持系统更新回滚 |
灾难恢复 |
| 跨容器可用 |
否(与卷同生共死) |
是 |
| 取证可得性 |
★ 随容器镜像一起取得 |
必须单独扣押备份盘 |
| 是否完整副本 |
否(仅变化的块) |
是(逻辑完整,底层为增量) |
tmutil listlocalsnapshots /
# → com.apple.TimeMachine.2024-09-05-142225
# → com.apple.TimeMachine.2024-09-06-030015
# ↑ APFS 本地快照:与目标卷同一个容器,随镜像一起取得
报告里必须写清取得来源。
"该文件的历史版本来自 DigiForensics.dd 中的 APFS 本地快照"和"来自外部备份盘的 Time Machine 备份集"是两种完全不同的证据来源,可采性、可复核性与法律效力都不同。混写会被质疑。
2.7 废纸篓
| 位置 |
归属 |
~/.Trash |
当前用户 |
/Volumes/<卷>/.Trashes/<uid> |
外接卷上的用户废纸篓,uid 对应用户 |
/Volumes/<卷>/.Trashes/ |
卷级空间 |
取证要点:
- 废纸篓里的文件保留原文件的完整时间戳(btime/mtime 不变),是时间线的好锚点
- "移到废纸篓"与"永久删除"是两个动作,前者是可恢复的中间状态
这三条里第三条最容易被问:清倒废纸篓会不会留痕?不一定。
APFS 上删除后目录项被释放,.fseventsd 会有删除事件,但没有"清倒废纸篓"这一独立记录——所以不能通过 FSEvents 区分"移到废纸篓"和"直接永久删除"。
三、操作步骤
3.1 第 1 步:确认 FSEvents 数据库存在与权限
# 现场(需要 root)/离线(镜像挂载后)同一组命令
ls -ld /System/Volumes/Data/.fseventsd # 或 /mnt/mac-data/.fseventsd
sudo ls /System/Volumes/Data/.fseventsd | head -20 # 形如 fs_20240905xxxxxx
# 用专用解析器提取 "时间 + 卷 + 目录 + 事件数"
./FseventsParser /mnt/mac-data/.fseventsd > /evidence/fsevents.csv 2>/dev/null
# → 2024-09-05T14:22:25,disk3s5,/Users/chenhao/Downloads,3
# 2024-09-05T15:03:44,disk3s5,/Users/chenhao/Desktop,12
若 .fseventsd 目录不存在:说明该卷未启用 FSEvents,或从未被挂载过。
这本身就是设备使用强度的证据。
| 解析后必须做的事 |
原因 |
| 记录解析工具名与版本 |
不同版本兼容性差异大,复核人需知道用了什么 |
| 记录"未能解析"的文件数 |
缺失必须声明,不能当作"无操作" |
| 与其他证据做时间对齐 |
FSEvents 只有时间与目录,需补文件名 |
3.3 第 3 步:与废纸篓、Spotlight 交叉补全文件名
# FSEvents 说 Downloads 在 14:22:25 有 3 次创建事件
# → 用 Quarantine + Spotlight 找同一时刻落地的文件
awk -F, '$1 ~ /2024-09-05T14:22:2/ {print $3}' /evidence/fsevents.csv
# → /Users/chenhao/Downloads
# 该目录当前内容与时间戳
ls -la /mnt/mac-data/Users/chenhao/Downloads/
stat -f 'btime=%SB mtime=%Sm %N' -t '%Y-%m-%d %H:%M:%S' \
/mnt/mac-data/Users/chenhao/Downloads/*
# 同时段 Quarantine(见第 5 篇)
find /mnt/mac-data/Users/chenhao/Downloads -type f -print0 | while IFS= read -r -d '' f; do
v=$(xattr -p com.apple.quarantine "$f" 2>/dev/null) && [ -n "$v" ] && echo "$f $v"
done
交叉补全的逻辑:FSEvents 给出"何时、何地、多少个文件",Quarantine 与 Spotlight 给出"哪些文件、何时下载、从哪来"。
两者时间吻合即构成完整证据链。但这一步是"吻合",不是"证明"——报告里写"时间吻合,与 FSEvents 事件计数一