只要花时间读 USN 日志,reason 位掩码就会像 EVTX 的 EventID 一样变成肌肉记忆。它是 NTFS 在每条 USN_RECORD_V2 上设置的 32 位字段,用来概括文件刚刚发生了什么变化。位是叠加的、而非互斥 — 这是第一件要内化的事:一条记录可以同时携带 FileCreate | DataExtend | Close,而该组合的意义不同于其中任何一位单独的意义。
下面是我贴在显示器旁便笺上的字段参考。
你实际遇到顺序排列的位
| 标志 | Hex | 实际含义 |
|---|---|---|
DataOverwrite | 0x00000001 | 主数据流的某段被就地覆盖 |
DataExtend | 0x00000002 | 主数据流变大 |
DataTruncation | 0x00000004 | 主数据流变小 |
NamedDataOverwrite / Extend / Truncation | 0x10 / 0x20 / 0x40 | 同上三个,但在备用数据流上 |
FileCreate | 0x00000100 | 创建了新文件或目录 |
FileDelete | 0x00000200 | 文件从命名空间中被解开 |
EaChange | 0x00000400 | 扩展属性变化 |
SecurityChange | 0x00000800 | ACL/所有者变化 |
RenameOldName | 0x00001000 | 重命名的"之前"半 |
RenameNewName | 0x00002000 | 重命名的"之后"半 |
IndexableChange | 0x00004000 | 可索引标志切换 |
BasicInfoChange | 0x00008000 | 时间戳、属性或压缩标志被重写 |
HardLinkChange | 0x00010000 | 添加或移除了硬链接 |
CompressionChange | 0x00020000 | NTFS 压缩切换 |
EncryptionChange | 0x00040000 | EFS 状态变化 |
ObjectIdChange | 0x00080000 | 对象 ID 被设置或清除 |
ReparsePointChange | 0x00100000 | reparse point 被添加/更改/移除 |
StreamChange | 0x00200000 | 备用数据流被添加、重命名或删除 |
Close | 0x80000000 | 产生此次变更的句柄已被释放 |
Close 位值得单开一段,因为读错它会让你的事件计数偏差一个数量级。NTFS 会把同一句柄下的连续操作合并,刷盘时发出中间记录,在句柄消失后发出一条带 Close 的最终摘要记录。中间记录是真实的,但会重复计数。在分诊解析过的日志时,先按带 Close 的记录过滤,把操作去重到每个句柄一行。只在需要次句柄粒度时才拉非 Close 记录。
频率高到值得背下来的模式
有一些 reason 组合在调查中出现频率高到你不再逐字符读,而是当作"单词"读。
应用写入新文件。 FileCreate | DataExtend,然后随着文件增长是更多 DataExtend | Close 记录,最后以 BasicInfoChange | Close 收尾。最后这条是文件关闭时被打上 mtime。
跨目录重命名。 共享同一 FileReferenceNumber 的两条记录:RenameOldName | Close 后跟 RenameNewName | Close。两条之间父引用不同 — 这个差就是你还原移动的方法。如果只按其中一个重命名位过滤,就会丢掉一半故事。
原子 save-by-rename。 Office、VS Code、Notepad++、带备份的 vim,几乎所有现代编辑器。你会看到对临时文件(~$report.docx、report.docx.tmp、.swp 等)的 FileCreate,一个或多个 DataExtend 记录,对原文件的 FileDelete | Close,以及把临时文件指向原文件名的 RenameNewName | Close。整段序列发生在同一个墙钟秒内。
勒索软件先加密再重命名。 对每个文件覆盖数 MB 的持续 DataOverwrite 记录,后跟带有统一后缀或固定长度随机扩展名的 RenameOldName / RenameNewName 对。DataOverwrite 速率与重命名上的同扩展名聚类,是诊断的两半。勒索软件检测一文 是配套实操。
杀毒软件隔离。 同一 MFT 表项上最近 FileCreate 之后紧跟一条 FileDelete | Close,中间常常因 AV 把文件移入受限目录而出现一条 SecurityChange。即便二进制本身已不在,日志也保留 AV 触碰过文件的证据。
云同步上传。 RenameNewName | Close,新父目录是 \Users\<u>\OneDrive\、\Dropbox\ 或 \Google Drive\,旧父目录是用户配置目录。同步代理自身在同步文件夹内文件上的 DataExtend | Close 记录通常对应下载;上传往往看起来像是"重命名进入同步文件夹"。
Timestomping。 同一 FileReferenceNumber 上、周围没有 DataOverwrite、DataExtend、FileCreate 或重命名的孤立 BasicInfoChange | Close。合法 touch 会与触发时间戳更新的写入配对。timestomping 文章 详细展开了这一点。
数据流注入 / ADS 滥用。 单独的 StreamChange 通常表示浏览器下载的 Zone.Identifier(与原文件的 FileCreate 配对)。在 Downloads\ 之外、特别是签名系统二进制上,同一 FileReferenceNumber 反复出现 StreamChange 记录值得展开看看。
这些位永远不会告诉你的东西
Reason 字段关乎文件,而不是行为主体。没有用户、PID、命令行,也没有完整性级别。把行为主体挂到操作上,总意味着要去别处取:Security.evtx 4663(对象访问,仅当配置了 SACL 时 — 而通常并未配置)、Sysmon 事件 11(文件创建)和事件 1(带命令行的进程创建),或者在恰当时刻把二进制落到磁盘上的 进程执行证据 与 Shimcache 条目。
这些位也不告诉你内容。日志知道某段区域被覆盖,但不知道之前那里是什么字节。要拿到那些,需要 $LogFile 的 before-image 或卷影副本。
一个站得住脚的阅读食谱
冷启动一份解析过的日志时:
- 先按
FileCreate过滤。窗口里真正新增的是什么?寻找用户可写目录中的路径(\AppData\Local\Temp\、\Users\Public\、非已知厂商子目录的\ProgramData\、\PerfLogs\)。 - 切换到
RenameNewName | Close,按新扩展名或新父目录分组。Save-by-rename、云同步移动、勒索软件加后缀都浮上来。 - 按分钟分桶
DataExtend。大批量写入 — 备份、加密、外泄归档增长 — 会在一个原本安静的基线之上呈现持续尖峰。 - 先读带
Close的记录。把中间记录当作辅助证据,而不是独立事件。 - 一旦锁定候选文件,把同一
FileReferenceNumber上的全部记录分到一组,按时间顺序读完整生命周期。这个序列基本就是要写进报告的部分。
本页解析器直接公开了按 reason 过滤,因此步骤 1-3 只是几下点击。步骤 5 要按 FileReferenceNumber 再按 USN 排序。
延伸阅读
- Microsoft Learn — USN_RECORD_V2 参考 直接覆盖每一位的定义。
- Eric Zimmerman 的 MFTECmd — 它的
$J解析器把 reason 位掩码导出为可在电子表格中透视的扁平 CSV。 - Airbus CERT 的 usnrs — 开源 Rust 实现;内化位布局的最短路径是读
src/reason.rs。