每条 MFT 记录都把四个时间戳存放在两个独立属性里。也就是每条记录八个数字。它们并非总是同步。这两组之间的关系,以及它们崩坏的地方,是 NTFS 时间分析的基础。如果你只学一个 MFT 取证的细节,就学这个。
NTFS 记录的四个时刻
$STANDARD_INFORMATION(SI,属性类型 0x10)与 $FILE_NAME(FN,属性类型 0x30)都为同样四个事件记录时间戳:
- Created:文件首次被写入卷的时刻。
- Modified:文件内容最后变化的时刻。
- Accessed:文件最后被读取的时刻。
- MFT-modified:记录本身最后被更新的时刻(任何属性的改动,不仅是数据)。
时间戳以 64 位 Windows FILETIME 格式(自 1601-01-01 UTC 起的 100 纳秒计数)保存。粒度为 100 纳秒。位数足够,自然事件会在低位留下噪声。
为什么会有两组
$STANDARD_INFORMATION 是用户态看到的那组时间戳。Win32 API 的 SetFileTime 写入的就是这里。任何会触碰时间戳的常规文件操作都会更新 SI:写、读(取决于访问时间设置)、重命名、属性变更。
$FILE_NAME 是早期 NT 出于 POSIX 子系统兼容性引入的,并因历史原因保留下来。它仅在名称变化时更新:创建(首次设置名称)、重命名(名称变化)、跨目录移动(在 NTFS 意义上也是重命名)。此后只要文件保持那个名字,FN 就保持不变。
实际效果是:SI 时间戳一直在动,FN 时间戳保持稳定。正是这种不对称使得 timestomping 可被检测。
Timestomping 都做了什么
修改时间戳的工具通常会调用 SetFileTime。它写入 SI。不动 FN。timestomping 之后:
- SI 显示攻击者想要的任何值。常常被回拨数年(把刚投放的工具伪装成系统文件),或者被向前调整(模糊真实活动时间)。
- FN 仍然反映真实创建时间,可能与 SI 相差很大。
这就是教科书式的 timestomping 签名:一个文件 SI created 在 2018 年,FN created 在六分钟前。
取证时要留意两种模式:
- SI created 早于 FN created。 这在自然条件下不可能。文件不可能在被命名之前存在。任何 SI created 早于 FN created 的记录都被动过。
- 明显未被重命名过的文件上,SI modified 与 FN modified 相差甚远。 FN modified 只在重命名时变化;若 SI 显示 2017 而 FN 显示昨天,中间也没有重命名记录,那就是有问题。
双重重命名诡计
知道 SI/FN 不一致会被发现的操作员会用一种规避手段:先重命名文件(强制 NTFS 更新 FN),然后对 SI 与 FN 同时调用 SetFileTime,再改回原名。这样两个属性都显示回拨过的值。
这能击败 SI/FN 比较。它击败不了:
- USN journal。 每次重命名都会写下
RENAME_OLD_NAME/RENAME_NEW_NAME这一对。该诡计所需的两次重命名都会出现在 journal 中。journal 里那两次的时间戳是真实时间,而不是被伪造的 FN 值。 $LogFile。 重命名前后的事务记录就在日志里。- VSS 快照。 较旧的快照里 FN 是 stomp 之前的值。对同一记录把当前 MFT 与快照中的 MFT 做 diff 即可。
要把双重重命名做得无声很难。但在成熟红队工具集中仍很常见。
亚秒级粒度:暴露偷懒工具的线索
NTFS 用 100 纳秒粒度存时间戳。原生 Windows 操作会在低位留下噪声。写入时间戳时操作系统不会把亚秒部分清零;内核时钟返回什么就写什么。
多数 timestomping 工具会先把时间四舍五入到秒再写入。结果就是以 .0000000 UTC 结尾的时间戳。单个这样的时间戳没什么稀奇(偶尔的自然事件确实也会舍入)。但跨多个文件整齐排列的 .0000000 后缀就是一种指纹。
这个很好查。把你 MFT 解析中的 created、modified、accessed、MFT-modified 四列拿出来,过滤出 SI 四个时间戳全以 .0000000 结尾的记录,再与自然 Windows 活动的基线比较。对比立刻就显出来了。
Windows 自身对时间戳做了什么
要使用 SI/FN 对比,你得知道 Windows 自己会对这四个字段做什么。备忘单:
- SI created:在创建时写入。一些安装器在"创建"(即解压)文件时也会更新它。
- SI modified:每次写数据时写入。
SetFileTime会更新。仅修改元数据不会更新。 - SI accessed:Windows 7+ 默认禁用(用
fsutil behavior set disablelastaccess 0启用)。即便禁用,该字段也偶尔会被某些操作(备份软件、EDR 扫描)更新,因此作为"最后读取"信号在任何方向上都不可靠。 - SI MFT-modified:任何属性变更(包括重命名、ACL 变更、ADS 创建、数据写入)都会写入。
- FN created:在名称被首次设置时写入(创建、创建硬链接、重命名)。
- FN modified、FN accessed、FN MFT-modified:在名称被设置时写入,之后基本稳定。
干净安装下,FN 的四个时间戳通常相等(它们都是在分配名称的同一瞬间设置的)。看到 FN 四个一致而 SI 四个发散,那是正常的。
Windows Update 与被打补丁的二进制
Windows Update 会更新被打补丁文件的 SI 时间戳。一次累积更新后,\Windows\System32\ 里有一半的文件 SI modified 都在几分钟之内,而 FN 自上一个 service pack 起就没动过。这是正常情况。把它当作基线模式,而不是异常活动。
对补丁窗口要与 CBS.log 与 setupapi.dev.log 交叉比对。如果 SI 说 svchost.exe 在 03:14 被修改,而 CBS 日志在 03:14 显示了一次组件安装,那就是 Windows Update,不是攻击者。
我对 timestomping 的筛查方式
我对一份新鲜的 MFT 抽取会跑的筛查:
- 拉出所有 SI created < FN created 的记录。逐条人工复核。
- 拉出 SI modified 比 FN modified 早超过 30 天,且文件位于用户可写目录的所有记录。人工复核。
- 拉出 SI 四个时间戳全以
.0000000结尾、且文件不是 Windows 系统二进制的所有记录。人工复核。 - 将当前 MFT 与最近的 VSS 快照做 diff。把 SI 时间戳向后回退的记录高亮。人工复核。
这套流程能抓到野外大多数 timestomping。使用双重重命名或 VSS 删除的高级操作员需要 journal 与事件日志层来检测,但上面四项检查仍然能通过其他工件(USN journal 里的重命名、事件日志里的 VSS 删除)让他们浮出水面。
延伸阅读
- David Cowen,Forensic Lunch 中关于 timestomping 的内容。多年来的从业者案例研究。
- SANS,Windows Time Rules cheatsheet。一个对每个 Windows 操作如何影响每个时间戳的快速参考。
- linux-ntfs 项目的
$STANDARD_INFORMATION与$FILE_NAME笔记。字段级布局参考。