UsnJrnl 与文件操作时间线

NTFS 上有一个绝大多数用户不知道、却对取证极其重要的元数据文件:$UsnJrnl。它是 NTFS 的变更日志,默默记录该卷上每一次文件创建、删除、重命名、写入、加解密操作——并且在文件被删除、目录被清空之后,记录依然保留。

最后更新 2026-10-09版本 v1.0维护 DigiForensics查看历史 0

关键词:$UsnJrnl、USN Change Journal、USN_RECORD_V2、MFT 条目引用、文件创建与重命名、覆盖式循环、时间线重建 难度:进阶 前置知识:NTFS 元数据与 MFT 解析、FILETIME 时间基准、位运算

一、概述

NTFS 上有一个绝大多数用户不知道、却对取证极其重要的元数据文件:$UsnJrnl。它是 NTFS 的变更日志,默默记录该卷上每一次文件创建、删除、重命名、写入、加解密操作——并且在文件被删除、目录被清空之后,记录依然保留。

这解决了取证中最难的一个问题。文件删掉就没了,MFT 条目被标记为未分配,目录项不复存在,常规手段无从下手。但 USN 日志在另一个数据流中独立记录了「某个 MFT 条目引用在某个时间被创建、被重命名成什么名字、被删除」。把 MFT 条目引用与 USN 记录结合,即使文件实体早已消失,仍能重建出完整的文件操作时间线。

还有一个直接的价值:重命名产生两条记录(旧名 RENAME_OLD_NAME 与新名 RENAME_NEW_NAME,共享同一个 FileReferenceNumber),这让文件名伪装可以被彻底揭露——攻击者把 payload.exe 改成 setup.exe,两条记录都在;按同一引用号排序即得完整改名链,无论文件当前叫什么、甚至已经删除。

日志本身不产出结论,它的价值在于把别的工件串起来。上游依赖 NTFS 结构的理解:FileReferenceNumber 的低 48 位就是 MFT 条目号,这是 USN 与 MFT 之间的唯一桥梁;没有这个对应,USN 只是一堆孤立记录,基础见NTFS 结构与 MFT 解析。

动手之前要先确认三件事:日志是否启用(fsutil usn queryjournal,必须现场确认)、当前解析的是哪个卷(每个 NTFS 卷有独立的日志,系统盘、U 盘、数据盘互不相同)、以及覆盖边界在哪(用 USN 序号判断,不能假设头部是起点)。三者都不是可选项,少了上游那个桥梁,后面全是空转。下游两处同样明确:改名链是文件名伪装的揭露手段;USN 时间线与执行证据合并后,能区分「程序启动过」与「文件被改过」的先后关系。

落盘形态如下:

形态 位置 关键结构
USN 数据流 $Extend\$UsnJrnl:$J 实际数据流;日志本体在 NTFS 的扩展元数据区
控制流 $Extend\$UsnJrnl:$Max、$Min 分别保存最大分配大小与最小保留大小
日志标识 $UsnJrnl 控制流 首个与下一个 USN、最低与最大有效 USN
记录结构 USN_RECORD_V2 小端序;含记录长度、FileReferenceNumber、ParentFileReferenceNumber、文件名、Reason 位标志
重命名对 两条记录共享 FileReferenceNumber,ParentFileReferenceNumber 不同 一条 RENAME_OLD_NAME、一条 RENAME_NEW_NAME
与 MFT 的桥 FileReferenceNumber 低 48 位 = MFT 条目号 USN → MFT 关联分析的唯一依据
状态查询 fsutil usn queryjournal <盘符>(活系统) 输出日志标识、首/下一个 USN、最低/最大有效 USN、最大尺寸、分配增量

三个判定要点:每个卷独立日志、覆盖后头部不是起点、fsutil 显示日志不存在或已被清除时,任何解析工具的「无输出」都不能当作「无文件操作」的证据。

据此能回答的是:某个 MFT 条目经历过哪些文件操作、每条操作的时间、它的完整改名链(无论当前叫什么、是否已删除)、以及某次删除或创建发生在什么时刻。

回答不了的部分有七条,前三条最常被写漏:

  • 只看元数据变更,不记读操作。 文件被读、被打开通常不产生记录(只读访问一般不写日志)。它证明「做过什么操作」,不证明「看过什么内容」。
  • 「无输出」不等于「无文件操作」。 日志是环形覆盖的,装满后从头覆写;被 fsutil usn deletejournal 清除过、被组策略禁用、被镜像工具排除的卷都可能没有日志。这个判断必须以 fsutil 的实际输出为准。
  • 不能假设头部是起点。 日志达到 $Max 后新记录从最早的位置开始覆盖,因此日志中总会同时存在「较旧但可能仍完整的时间窗」与「刚写入的最新记录」两段,必须结合 USN 序号判断覆盖边界。
  • 当前的文件名与状态。 USN 记录的是操作历史,「这个文件现在还在不在」要查 MFT 与目录。
  • 操作的主体。 记录里没有用户名,要落到人需结合事件日志与用户归属。
  • 数据内容。 它记元数据变更,不记内容写入了什么。
  • deletejournal 的不可逆性。 清除日志是不可逆操作,一旦做出就无法恢复,涉及该操作时必须先记录日志的现状与 USN 序号区间。

往下读之前先确认你已经掌握:NTFS 的 MFT 结构与条目号概念(这是 USN 与 MFT 关联的前提)、小端序与位运算(USN_RECORD_V2 的 Reason 位标志必须逐位判读)、NTFS 的备用数据流与 $Extend 机制($UsnJrnl:$J 的定位依赖它)、以及 FILETIME 时间基准。同时清楚不可逆操作的纪律:deletejournal 会永久销毁日志,执行前必须完成固定与记录。

二、核心原理

2.1 位置与存储模型

USN 日志位于 NTFS 的扩展元数据流中:

$Extend\$UsnJrnl:$J

$UsnJrnl:$J 就是实际数据流,$Max 与 $Min 是控制流,分别保存最大分配大小与最小保留大小。

每个 NTFS 卷有独立的日志——系统盘、U 盘、数据盘的日志互不相同,分析时必须明确当前解析的是哪个卷。

覆盖规则:当日志达到 $Max 指定的最大尺寸后,新记录从最早的位置开始覆盖。因此日志中总会同时存在「较旧但可能仍完整的时间窗」与「刚刚写入的最新记录」两段,解析时必须结合 USN 序号判断覆盖边界,不能假设头部是起点。

2.2 启用状态必须现场确认

Vista 及之后的 Windows 客户端通常默认启用 USN Journal,但启用状态、最大尺寸、是否被 fsutil usn deletejournal 清除过,都因系统配置、镜像工具、组策略与攻击行为而异。永远以实际检材为准,在活体系统上直接查询:

fsutil usn queryjournal C:

输出包含日志标识、首个与下一个 USN、最低与最大有效 USN、最大尺寸与分配增量。如果该命令显示日志不存在或已被清除,任何解析工具的「无输出」都不能当作「无文件操作」的证据。

2.3 USN_RECORD_V2 记录结构

每条记录的字段布局如下(小端序):

偏移 长度 字段 说明
0 4 RecordLength 本记录总长度,用于遍历到下一条
4 2 MajorVersion 主版本号,V2 为 2;不同系统可能见到 3 或 4
6 2 MinorVersion 次版本号
8 8 FileReferenceNumber 64 位文件 ID(低 48 位为 MFT 条目号,高 16 位为序列号)
16 8 ParentFileReferenceNumber 父目录的 64 位文件 ID
24 8 Usn 更新序列号,卷内单调递增
32 8 TimeStamp FILETIME(1601 起 100 纳秒)
40 4 Reason 变更原因位标志组合
44 4 SourceInfo 来源信息(数据管理、复制等系统侧来源)
48 4 SecurityId 安全标识,与登录会话关联
52 4 FileAttributes 文件属性
56 2 FileNameLength 文件名长度(字节)
58 2 FileNameOffset 文件名在记录中的偏移
60 可变 FileName UTF-16LE 文件名

MajorVersion 为 2 时结构即上表;版本 3 与 4 在前部增加了额外的 64 位字段(如 n/a 的安全 ID 扩展),字段偏移随之改变。

解析器必须按 MajorVersion 分支处理。用 V2 布局硬解 V3/V4,会得到完全错位的时间与文件名。

2.4 Reason 位标志(实测值)

Reason 是位组合,同一条记录可以同时含多个标志。创建文件通常同时含 FILE_CREATE 与 CLOSE:

位值 名称 含义
0x00000001 DATA_OVERWRITE 默认数据流被覆写
0x00000002 DATA_EXTEND 默认数据流被追加
0x00000004 DATA_TRUNCATION 默认数据流被截断
0x00000010 NAMED_DATA_OVERWRITE 命名数据流(ADS)被覆写
0x00000020 NAMED_DATA_EXTEND 命名数据流被追加
0x00000040 NAMED_DATA_TRUNCATION 命名数据流被截断
0x00000100 FILE_CREATE 文件/目录首次创建
0x00000200 FILE_DELETE 文件/目录被删除
0x00000400 EA_CHANGE 扩展属性变更
0x00000800 SECURITY_CHANGE 访问权限变更
0x00001000 RENAME_OLD_NAME 重命名,记录中为旧名
0x00002000 RENAME_NEW_NAME 重命名,记录中为新名
0x00004000 INDEXABLE_CHANGE 索引状态变更
0x00008000 BASIC_INFO_CHANGE 属性或时间戳变更
0x00010000 HARD_LINK_CHANGE 硬链接增删
0x00020000 COMPRESSION_CHANGE 压缩状态变更
0x00040000 ENCRYPTION_CHANGE 加密状态变更(BitLocker/EFS 挂载可见)
0x00080000 OBJECT_ID_CHANGE 对象 ID 变更
0x00100000 REPARSE_POINT_CHANGE 重解析点变更
0x00200000 STREAM_CHANGE 命名流增删改名
0x00400000 TRANSACTED_CHANGE TxF 事务内的修改,非事务修改不会带此位
0x00800000 INTEGRITY_CHANGE 完整性流变更(Windows 8 起的文件级校验元数据)
0x80000000 CLOSE 文件/目录被关闭

其中最具取证价值的是三个组合:

  • FILE_CREATE + CLOSE:文件被创建。
  • RENAME_OLD_NAME + RENAME_NEW_NAME:同一 FileReferenceNumber 的两条记录构成一次重命名的完整前后状态。
  • FILE_DELETE + CLOSE:文件被删除。这是「已删除文件仍可证明存在」的关键。

2.5 重命名产生两条记录

重命名或移动会生成两条 USN 记录,共享同一个 FileReferenceNumber,但 ParentFileReferenceNumber 不同:一条记录旧父目录与旧名(RENAME_OLD_NAME),一条记录新父目录与新名(RENAME_NEW_NAME)。

这个特性使得文件名伪装可以被彻底揭露。

攻击者把 payload.exe 改名为 setup.exe,日志中会同时留下两条记录。把同一 FileReferenceNumber 的记录按时间排序,即可得到完整的改名链,无论文件当前叫什么、甚至已经删除。

2.6 与 MFT 条目引用结合

FileReferenceNumber 的低 48 位就是 MFT 条目号。这意味着 USN 日志可以作为MFT 条目号的索引,对 MFT 做关联分析:

  1. 从 USN 日志取出所有相关的 64 位文件 ID。
  2. 在 MFT 中定位对应条目,读取其当前(可能已删除)的时间戳与属性。
  3. 两者时间线合并,形成「该文件经历过的全部操作」。

这一组合是 MFT 恢复(carving)能否成功的关键前提。只知道文件名而不知道条目号,就无法验证 MFT 中恢复的记录是否真的是那个文件。

三、操作步骤

3.1 提取 $UsnJrnl

$UsnJrnl 是NTFS 元数据文件(位于 $Extend 目录),不会出现在普通目录遍历结果里,必须先用 fls -m 列出元数据文件、拿到它的元数据地址,再用 icat 按地址导出:

mmls /work/DigiForensics.dd
fsstat -o 2048 /work/DigiForensics.dd

# 关键:-m 列出元数据文件,$UsnJrnl 只在这里出现
fls -o 2048 -m /work/DigiForensics.dd | grep -i 'UsnJrnl'
# 输出形如:r/r  <meta-addr>  $UsnJrnl  33554432  ...  $Extend
#        <meta-addr> 即后续 icat 使用的地址,形如 <inod>-<addr>-<seq>

# 用上一步实际读到的地址导出(不要照抄示例中的数字)
ADDR=$(fls -o 2048 -m /work/DigiForensics.dd | grep -i '\$UsnJrnl$' | awk '{print $2}' | head -1)
istat -o 2048 /work/DigiForensics.dd "$ADDR"      # 确认是 $UsnJrnl 再导出
icat -o 2048 /work/DigiForensics.dd "$ADDR" > /evidence/usn/UsnJrnl.bin
注意点 说明
必须 -m 普通 fls 不列元数据文件,会漏掉 $UsnJrnl
地址随镜像而变 <inod>-<addr>-<seq> 形式由卷与 MFT 状态决定,必须现场读取,不可硬编码
不要用 5-128-3 这类照抄的地址 那是某一份检材的元数据地址,换镜像即无效
输出大小 TSK 按数据run list 读取,输出是已分配的实际数据,通常远小于 $Max 声明值;不要按 $Max 预留磁盘
$J vs $Max/$Min 日志记录在 $J 流;$Max/$Min 是控制流。icat 取默认流即日志内容,$Max 用于确认日志容量

支持数据流语法的工具可用 $UsnJrnl:$J 显式指定日志流、$Max/$Min 指定控制流。不同工具支持程度不同,以所用工具的实际文档为准;使用前务必用 istat 确认目标确实是 $UsnJrnl。

这一步不做,后面读到的可能只是个空壳。

3.2 确认日志状态

在活体系统上先查状态,这一步不能跳:

fsutil usn queryjournal C:

关键看三项:是否启用、最大尺寸、以及「最大有效 USN」与「下一个 USN」的差值——差值大说明有大量新记录尚未或正在写入。同时记录 Usn Journal ID,它是判断日志是否被删除后重建的重要依据。

3.3 解析并导出

用专门的 USN 解析器导出为结构化格式(CSV/TSV/JSON),字段应至少包含文件名、父目录文件 ID、文件名 ID、时间戳、原因位、属性与 USN 序号。

解析后立刻做三件事。

  1. 检查 MajorVersion 分布,确认检材是 V2 还是 V3/V4。
  2. 按 USN 序号排序,识别覆盖边界(序号出现回退处即为被覆写的位置)。
  3. 统计 Reason 位组合分布,了解该卷上最常见的操作类型。

3.4 与 MFT 关联构建时间线

核心是把 USN 记录与 MFT 条目对齐: