← 返回博客

NTFS 上文件被删除时,到底什么留了下来

· 阅读 2 分钟

我能对一位新人 NTFS 取证检查员说的最有用的一句话是:删除不是擦除。它是一次 flag 改动和一次索引更新。文件的 MFT 记录原地不动。数据簇在 $Bitmap 中被标记为空闲。在有别的东西来要这块空间之前,什么都不会被覆盖。

人们抽象上知道这一点。我反复看到的错误是:假设在每台机器上生存窗口都很宽裕。并非如此。在繁忙的文件服务器上,槽位几分钟内就会被重用。在周末没人用的工作站上,被删除的记录能留一个月。在为某物是否还可恢复下定论之前,你必须先弄清楚自己在看的是哪种主机。

记录里还剩什么

被删除的 MFT 记录在结构上和在用的没有差别。IN_USE 位(偏移 0x16 的 flags 字段的第 0 位)被清掉。其余内容,除非该记录已被认领,就是 NTFS 最后一次写入时的样子:

  • $STANDARD_INFORMATION(属性类型 0x10):SI 中的四个时间戳、DOS flags、安全描述符引用、所有者 ID、USN 指针。时间戳完整地越过删除事件保留下来。请注意你看到的 SI 是删除瞬间文件的样子,而不是它最初被创建时的样子(Windows 在文件存活期间持续更新 SI)。
  • $FILE_NAME0x30):每个硬链接一条,加上在 disable8dot3 关闭的卷上的 8.3 短名。父目录引用是文件曾经所在目录的 MFT 记录号。即使该目录后来被删除,这个引用依然成立,这正是 fls 与 MFTECmd 能重建已删除目录层级中路径的方式。
  • $DATA0x80):对常驻文件(数据少于约 700 字节),字节本身就在记录内。对非常驻文件,runlist 指向曾经构成该文件的簇。那些簇不再被标记为已分配,但也没有被清零。
  • $ATTRIBUTE_LIST0x20)若存在,会指向文件使用过的扩展记录。这些扩展记录本身也是已删除状态,但在被重新认领之前依然可解析。

序列号是让"已删除文件分析"可辩护的关键。每条记录都有一个 16 位的序列号,每次槽位被重用时增加。一条指向记录 12345 序列 3 的 USN journal 条目或 $LogFile 引用,在槽位现在已是序列 4 之后,依然指向序列 3。这种不匹配正告诉你:自那以后槽位已被重用过。

槽位真正消失的时刻

两个事件各自独立地终结可恢复性:

MFT 记录槽位被重用。 NTFS 在此没有一个固定的分配策略。实际上,当新文件需要记录时,驱动程序会查看 $MFT:$BITMAP 找出编号最低的空闲记录并使用它。被删除的记录大致按顺序被回收。在高负载卷上这一顺序推进得很快。MFT 自身只会增长,从不缩小,因此远高于当前高水位的旧已删除记录可能存留数年。

数据簇被覆盖。 与记录独立。簇在 $Bitmap 中被标记为空闲,因此任何分配器都可能认领它们。在刚被删除的文件上,要让恢复变得无望,需要两件事都发生。我见过记录存留但簇早就没了(只能恢复元数据与常驻数据),也见过反过来(记录被重用但簇仍保留原始字节,可以做 carving)。

二者都没有干净的计时器。"NTFS 上的已删除文件能保留多久"这个问题,诚实的答案与"吧台上的空位能保留多久"一样:直到有人坐上去。

journals 增加了什么

MFT 给你看现在。USN journal 给你看动词。一条 FILE_DELETE 的 USN 记录会写出文件名、父目录以及删除的时间戳。即使 MFT 槽位之后被重用了,USN 条目仍然保留旧名以及原始的记录/序列对。把两者结合,你会得到类似这样的东西:

2026-04-12T13:08:11Z USN FILE_DELETE | CLOSE  rec=44231 seq=7 path=\Users\bob\Documents\secrets.zip
2026-04-12T13:08:11Z MFT rec=44231 IN_USE=0 seq=7 (still readable, deleted)
2026-04-12T13:08:11Z MFT FN parent=12, name=secrets.zip

即便文件其他线索都没了,这也是对"这个文件在删除时是什么样子"的可重构答案。$LogFile 在两侧几秒内补充事务级别的细节:删除被一对 DeleteAttributeDeallocateFileRecordSegment 框住,你通常能识别出周围的操作。

如果 VSS 当时启用并且存在删除前的快照,快照中的 $MFT 对同一个记录/序列仍然显示 IN_USE=1,并且数据簇通过 copy-on-write 被保留下来。提取细节见 Volume Shadow Copy 与 $MFT

大家常搞错的几种情况

安全删除工具。 SDelete 在默认模式下会覆盖数据簇,但完全不动 MFT 记录。你会得到一个有完整 $FILE_NAME、完整 $STANDARD_INFORMATION 的已删除记录,$DATA 指向已被清零的簇。名称和时间戳完全保留。期待 SDelete 比实际更彻底的用户会在这里栽跟头。

通过硬链接删除文件。 删除一个硬链接并不会删除文件本身;该链接对应的 $FILE_NAME 属性被移除,硬链接计数下降。只有当最后一个链接被移除时,文件才真正被删除。如果你看到一条 hardlink_count 为零、没有任何 $FILE_NAME 残留的已删除 MFT 记录,那这个文件是真删了。如果你看到的记录硬链接计数不为零且缺失 $FILE_NAME,那么删除是部分的。

删除前被重命名的文件。 一些 dropper 会在删除前立即把暂存文件改成无害名字,希望残存的 $FILE_NAME 看起来不起眼。USN journal 保留 RENAME_OLD_NAMERENAME_NEW_NAME 这一对,因此你可以从那里还原原始名称。仅看 MFT 只能看到最终名称。

回收站不是删除。 被送进 回收站 的文件会被重命名并移到 \$Recycle.Bin\<SID>\。MFT 记录保持 IN_USE=1,文件以 $R<ID> 名字完整存在。配套的 $I<ID> 记录原始路径。清空回收站后再走常规删除流程。

善于读取已删除记录的工具

做取证我会用以下之一:

  • MFTECmd--de 1(drop entries),或者干脆输出未过滤结果在下游过滤。它会标注常驻数据并给出序列号。
  • omerbenamram/mft_dump-o json。序列号、常驻 flag、完整属性列表。便于喂给脚本筛出 IN_USE=0seq 与 USN 引用匹配的记录。
  • Sleuth Kit 的 fls -m -r 输出包含已删除项的 bodyfile。古典方法,至今仍可用。icat -r 在非常驻文件的簇仍完整时可以恢复数据。
  • analyzeMFT 如果你特别想要一个纯 Python 选项且不在意速度。

本站的 浏览器解析器 一键就能把过滤条件设为 IN_USE=0,并在表格中内联显示常驻数据。如果卷重要、文件小、槽位未被重用,你常常能在表格里直接看到字节。

仅凭 MFT 仍然回答不了的问题

MFT 告诉你某条记录存在并已被删除。它不告诉你是谁删的。要回答这个,需要在 SACL 已配置的情况下查 Security 4663(对象访问),或者 Sysmon Event ID 23(FileDelete),它会记录用户和进程。多数主机上这两者都没开。这种情况只能依赖佐证:删除工具的 Prefetch 命中、在相应时间运行的陌生二进制的 Amcache 记录、同一时间窗的 recent file cache 条目。

MFT 是地板。在它之上把故事的其余部分搭起来。

延伸阅读

  • Brian Carrier,File System Forensic Analysis。关于 NTFS 删除语义的章节至今仍是权威。
  • Sleuth Kit 的 fls 文档,介绍包含已删除项的规范 bodyfile 输出。
  • Microsoft,Master File Table。NTFS 在 MS 一侧对所保证与不保证内容的参考。

外部资源