USN Reason 代码 — 在位之间阅读

逐字段解读 USN_RECORD 的 reason 位掩码,以及每种组合所揭示的磁盘上文件的生命周期。

作者: 阅读约 2 分钟

只要花时间读 USN 日志,reason 位掩码就会像 EVTX 的 EventID 一样变成肌肉记忆。它是 NTFS 在每条 USN_RECORD_V2 上设置的 32 位字段,用来概括文件刚刚发生了什么变化。位是叠加的、而非互斥 — 这是第一件要内化的事:一条记录可以同时携带 FileCreate | DataExtend | Close,而该组合的意义不同于其中任何一位单独的意义。

下面是我贴在显示器旁便笺上的字段参考。

你实际遇到顺序排列的位

标志Hex实际含义
DataOverwrite0x00000001主数据流的某段被就地覆盖
DataExtend0x00000002主数据流变大
DataTruncation0x00000004主数据流变小
NamedDataOverwrite / Extend / Truncation0x10 / 0x20 / 0x40同上三个,但在备用数据流上
FileCreate0x00000100创建了新文件或目录
FileDelete0x00000200文件从命名空间中被解开
EaChange0x00000400扩展属性变化
SecurityChange0x00000800ACL/所有者变化
RenameOldName0x00001000重命名的"之前"半
RenameNewName0x00002000重命名的"之后"半
IndexableChange0x00004000可索引标志切换
BasicInfoChange0x00008000时间戳、属性或压缩标志被重写
HardLinkChange0x00010000添加或移除了硬链接
CompressionChange0x00020000NTFS 压缩切换
EncryptionChange0x00040000EFS 状态变化
ObjectIdChange0x00080000对象 ID 被设置或清除
ReparsePointChange0x00100000reparse point 被添加/更改/移除
StreamChange0x00200000备用数据流被添加、重命名或删除
Close0x80000000产生此次变更的句柄已被释放

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.docxreport.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 上、周围没有 DataOverwriteDataExtendFileCreate 或重命名的孤立 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 或卷影副本。

一个站得住脚的阅读食谱

冷启动一份解析过的日志时:

  1. 先按 FileCreate 过滤。窗口里真正新增的是什么?寻找用户可写目录中的路径(\AppData\Local\Temp\\Users\Public\、非已知厂商子目录的 \ProgramData\\PerfLogs\)。
  2. 切换到 RenameNewName | Close,按新扩展名或新父目录分组。Save-by-rename、云同步移动、勒索软件加后缀都浮上来。
  3. 按分钟分桶 DataExtend。大批量写入 — 备份、加密、外泄归档增长 — 会在一个原本安静的基线之上呈现持续尖峰。
  4. 先读带 Close 的记录。把中间记录当作辅助证据,而不是独立事件。
  5. 一旦锁定候选文件,把同一 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