NTFS 的 Update Sequence Number 日志,是我在任何 Windows 主机上想知道"什么时候动了哪些磁盘内容"时最先伸手去拿的痕迹。自 Vista 起,Windows 出厂的每一个 NTFS 卷默认就启用它;几乎没人禁用它,几乎也没人在出事之前去看它。这种不对称,正是它有用的原因。
卷上每一次文件创建、删除、重命名、截断、属性切换和关闭,都会向 $UsnJrnl:$J 追加一条记录。记录很小(每条几十字节),文件本身是固定大小的环形缓冲区,因此一台繁忙的机器大约会保留最近数百万次操作。默认 32 MB 配额的桌面机上是几天;1 GB 大小的文件服务器上是几周。
日志真正存在的位置
日志不是一个普通文件。它是一个名为 $J 的备用数据流,附在卷根目录下的元数据文件 \$Extend\$UsnJrnl 上。两个新手常被绊倒的细节:
- 在资源管理器里的拖放,或
xcopy、robocopy,会悄无声息地复制空的默认数据流,你最终得到一个零字节的文件。请使用懂 ADS 的工具:FTK Imager、X-Ways、The Sleuth Kit 的icat、RawCopy.exe、PowerForensics。配套文章 从磁盘镜像中提取$J会逐一介绍。 - 同一元数据文件上还有一个兄弟数据流
$Max,只包含大小/配置元数据。如果误抓$Max,你拿不到任何有用的东西。一定要选$J。
文件本身是稀疏的。NTFS 在磁盘上预留了已配置的最大大小,但把前面未使用的区域留作零字节。当你从镜像中雕刻 $J 时,前几百兆字节经常全是零,然后才到达第一条真正的记录。任何像样的解析器(usnrs、PoorBillionaire 的 USN-Journal-Parser、MFTECmd、本页的 WebAssembly 解析器)都会自动跳过前面的零。
记录结构 (USN_RECORD_V2)
每条记录都是一个 USN_RECORD_V2 结构。Microsoft 在 winioctl.h 中记录了布局;逐字段的摘要如下:
| 字段 | 字节 | 含义 |
|---|---|---|
| RecordLength | 4 | 包含文件名在内的记录总大小 |
| Major / Minor version | 2 + 2 | 实际中见到的都是 2.0 |
| FileReferenceNumber | 8 | 文件的 MFT 表项 + 序列号 |
| ParentFileReferenceNumber | 8 | 父目录的 MFT 表项 + 序列号 |
| USN | 8 | 该记录在日志中的位置 |
| Timestamp | 8 | Windows FILETIME (自 1601-01-01 起的 100ns 计数) |
| Reason | 4 | 描述发生何种变更的位掩码 |
| SourceInfo | 4 | 提示信息 (例如 DATA_MANAGEMENT 表示由操作系统发起的变更) |
| SecurityId | 4 | 指向 $Secure:$SII 的索引 |
| FileAttributes | 4 | 标准 NTFS 属性 |
| FilenameLength / Offset | 2 + 2 | 记录内 UTF-16 文件名的位置 |
| Filename | n | UTF-16 LE,无空终止符 |
Reason 位掩码是真正撑起这个数据结构的字段。它是叠加的、并非互斥:同一条记录可以同时携带 FileCreate | DataExtend | Close。一个文件典型的生命周期形如 FileCreate | DataExtend → DataExtend | Close → BasicInfoChange | Close → FileDelete | Close。事后重建这个序列,正是讲清楚文件到底发生了什么的方式。reason 码文章 是逐比特的字段参考。
新读者常常低估的两个字段:
FileReferenceNumber是你的连接键。按它对记录分组,你就拿到了一个文件横跨重命名、属性变更和删除的完整故事。注意:高 16 位是序列号 — 被删除的文件和后续分配到同一 MFT 表项的新文件共享表项,但序列号不同。ParentFileReferenceNumber是记录中唯一的路径信息。文件名只是末端叶子。要拿到完整路径,需要一个解析过的$MFT来沿父链走到根。永远把两者一起取走。
为什么在 DFIR 中物有所值
几乎每一次响应都会碰到的两个原因。
第一,日志能跨过删除而留存。即便文件从命名空间消失、其 MFT 表项已被复用,它的创建、增长、重命名和删除记录仍留在 $J 中,直到环形缓冲区把它覆盖。被清空回收站的文件也一样 — 回收站的 $I 文件被清掉,但日志仍记得。当问题是"这个文件曾经存在过吗",日志大多数时候比 MFT 表项和回收站元数据活得更久。
第二,代价低且自描述。一个 100 MB 的 $J 通常包含五百到一千万条记录,跨越数天到数周。每条记录都带着自己的时间戳、reason、文件引用和父引用。几秒就能跑完解析器,在任意字段上转视角。同样代价下没有别的 Windows 痕迹能做到。
它不会告诉你的是"谁"。任何记录里都没有用户、进程、命令行。要把行为人关联起来,需要按时间戳与 Security.evtx 的 4663(对象访问,几乎肯定未配置 SACL)、Sysmon 事件 11(文件创建)、或 Sysmon 事件 1 的进程树关联起来。日志回答"什么"和"何时",其他日志回答"谁"。
在痕迹栈中的位置
我使用的心智模型,从"当前状态"到"历史记录"排列:
$MFT— 当前状态索引。卷上所有文件和目录,包括一些近期删除的表项。每个属性 4 个时间戳,无历史。$UsnJrnl:$J— 按操作的历史记录,持续数天到数周。本文所讲的对象。$LogFile— NTFS 的崩溃恢复事务日志。数分钟到数小时的原始 before/after 镜像。USN journal vs MFT vs LogFile 一文拆解了何时选哪个。
把日志和 Prefetch、AmCache、Shimcache、LNK 文件 和 jump lists 一起看,通常能精确到分钟级地还原出运行了什么、打开了什么、什么动了磁盘。
接下来读什么
如果你从未打开过日志,最快建立感觉的方法是从一台测试机上抓一份,连同对应的 $MFT 一起拖入本页顶部的解析器。按 FileCreate 过滤,按 Windows 写入的顺序读这个文件。本系列后续文章会深入 reason 标志、提取流程,以及日志针对勒索软件、外泄、timestomping、删除文件取证所暴露的模式。
延伸阅读
- Microsoft Learn — Change Journals 与 USN_RECORD_V2 参考。
- Airbus CERT 的 usnrs — 现存最干净的开源
USN_RECORD_V2解析器,也是本站 WebAssembly 模块的底层。 - Brian Carrier, File System Forensic Analysis — 至今仍未被取代的 NTFS 内部结构书面级权威。