← Back to blog

Spotting timestomping and anti-forensics in the USN journal

Attackers who edit MFT timestamps can't hide from the change journal. How $STANDARD_INFORMATION vs $FILE_NAME mismatches and unexpected BasicInfoChange records expose anti-forensic activity.

5 min read

Sophisticated operators don't just delete the evidence — they try to camouflage what they leave behind. The most common technique is timestomping: editing a file's NTFS timestamps so it looks innocent (or aged out of the investigation window). It used to work. With the USN journal turned on, it leaves clear signal.

This post walks through what timestomping does to the disk, how the journal exposes it, and what other anti-forensic moves you can catch with the same artefact.

A quick refresher on NTFS timestamps

Every MFT entry carries timestamps in two attributes:

  • $STANDARD_INFORMATION (SI): the timestamps user-space tools and most APIs can change. These are what dir, Explorer and Get-ChildItem show. Format documented in the Microsoft NTFS reference.
  • $FILE_NAME (FN): the timestamps NTFS sets internally on each $FILE_NAME attribute. There's typically one per name (the file may have multiple names if hard-linked). These are much harder to change — they require kernel-level access or specialised tools.

Timestomping tools (SetMACE, timestomp.exe from the historical Metasploit anti-forensics module, several custom implants) generally target the SI timestamps. Some now target FN too. Either way, they touch the MFT — and that touch lights up the journal.

What the journal records for a timestamp edit

When SI timestamps are written, NTFS emits a BasicInfoChange | Close record. That record is the smoking gun, because:

  • A legitimate "touch" produces a BasicInfoChange | Close together with a DataExtend or DataOverwrite from the actual data write that triggered the timestamp update.
  • A timestomp produces a bare BasicInfoChange | Close with no preceding write on the same FileReferenceNumber.

When you see BasicInfoChange | Close with no DataOverwrite, DataExtend or FileCreate in the same handle session, you're looking at metadata manipulation — almost always either timestomping or attribute changes (read-only/hidden flags).

The classic comparison: SI vs FN

The most authoritative timestomping detection compares each file's $STANDARD_INFORMATION timestamps against its $FILE_NAME timestamps. Mandiant's investigators have written about this for years; the original Mandiant timestomping white paper is the historical reference, and Brian Carrier covers it in File System Forensic Analysis.

The journal complements the comparison rather than replacing it:

  • The MFT comparison tells you that timestomping happened at some point. It can't tell you when.
  • The journal BasicInfoChange | Close records tell you exactly when the SI write occurred, and which process-context surrounded it (paired with Security.evtx 4663).

A timestomped file therefore looks like this in your data:

  1. MFT comparison flags SI < FN (or SI in the future of FN, etc.).
  2. Journal has a BasicInfoChange | Close at, say, 2026-04-12 03:08:14 with no surrounding writes.
  3. That timestamp is what you put in your report.

$LogFile and the journal disagree about MFT writes

The journal records writes at the per-file level; $LogFile records the underlying MFT transactions. A timestomp produces:

  • One MFT write transaction in $LogFile (modifying $STANDARD_INFORMATION of the target entry).
  • One BasicInfoChange | Close in the journal.

If you find a $LogFile MFT-update transaction with no matching journal BasicInfoChange | Close, the operator may have deleted the journal record — possible via fsutil usn deletejournal followed by createjournal, which leaves its own traces.

Other anti-forensic moves the journal catches

Disabling the journal

fsutil usn deletejournal /D /N C: removes the journal. After it runs:

  • The journal file is rebuilt empty. Records from before the call are gone.
  • The MFT entry of $UsnJrnl is freshly written — $LogFile records the transaction.
  • The Security.evtx if SACLs were in place records a Sensitive Privilege Use for SeRestorePrivilege or SeManageVolumePrivilege.

The journal can't tell you what it used to contain, but the act of disabling it is itself a strong signal of anti-forensics. See Microsoft's fsutil usn reference for the management commands and their privilege requirements.

Alternate data streams

ADS hiding is older than I am, and the journal records it cleanly: an StreamChange reason bit fires when an alternate data stream is added or removed. Filter StreamChange and you'll surface every ADS create/modify event on the volume. False positives include legitimate Zone.Identifier streams from browser downloads — those produce StreamChange events too, paired with the original file's FileCreate.

Hard-link tricks

HardLinkChange fires when hard links are added or removed. Operators sometimes use hard links to make a file accessible via two parents — useful for evading file-path-based controls. Track HardLinkChange records and pivot on FileReferenceNumber to see all parents.

Reparse-point abuse

Reparse points (junctions, symbolic links, mount points) can redirect a path to elsewhere. ReparsePointChange lights up when one is added or modified. Look for reparse points in $Recycle.Bin, \Users\Default, or other "shouldn't be touched" locations.

What you can't catch this way

A short list of anti-forensic moves that bypass the journal entirely:

  • Booting from external media and rewriting the disk — never touched the live OS.
  • Userland file rewrites that preserve the same content size (overwrite at the same offset, no SI update). These produce DataOverwrite records but lose the original content. The journal sees the write, not what was there before.
  • Disabling the journal before the operator does anything else. The journal can only record what happened while it was on.

For these, you need either $LogFile, shadow copies, or off-host telemetry. The journal is one strong witness, not the only one.

A practical scan

The fastest screen for timestomping using a parsed journal:

  1. Filter for records with reason exactly BasicInfoChange | Close.
  2. For each, look at the prior 5 minutes of records on the same FileReferenceNumber. If there's no DataOverwrite, DataExtend, FileCreate or rename, flag it.
  3. For flagged entries, compute the SI-vs-FN delta in the MFT. Sort by largest delta.

Top of the list is usually your timestomping. The remaining entries are operations like Explorer toggling "Hidden" or scripting that touches attributes — context-dependent, but rarely interesting to a typical investigation.