The first time I built a Windows timeline from $MFT, I emitted eight rows per record, sorted by timestamp, opened it in Excel and felt like I had reconstructed reality. I had not. I had a sorted log of NTFS metadata changes. That is a useful thing. It is not, by itself, an investigation.
This post is what I wish someone had told me about MFT timelining before I shipped my first report.
What an MFT timeline actually is
Every active and deleted MFT record carries eight timestamps that you can extract reliably: four in $STANDARD_INFORMATION (SI) and four in $FILE_NAME (FN). Created, modified, accessed, and MFT-modified, in both attributes. Walk the table, emit one row per non-null timestamp, sort by time, and you have a timeline of when NTFS wrote particular bytes of particular records. That is the floor.
The ceiling is a supertimeline that fuses MFT with the USN journal, $LogFile, Prefetch, Sysmon, Shimcache, Amcache, registry hives, browser history and SRUM. The MFT is the backbone because it is the densest and the hardest for an attacker to fake completely. Everything else gets aligned against it.
The layout I actually use
Forget mactime as a destination format. Use it as an intermediate. The shape that holds up in court is one row per event with provenance you can defend:
timestamp_utc | source | attribute | mft_record | seq | path | event
2026-05-15T10:23:01.123Z | MFT | SI | 12345 | 3 | /Users/alice/notes.txt | created
2026-05-15T10:23:01.123Z | MFT | FN | 12345 | 3 | /Users/alice/notes.txt | name_created
2026-05-15T10:24:18.456Z | MFT | SI | 12345 | 3 | /Users/alice/notes.txt | modified
2026-05-15T10:24:18.501Z | USN | - | 12345 | 3 | /Users/alice/notes.txt | DATA_OVERWRITE | CLOSE
source and the record/sequence pair are the columns analysts skip and then regret skipping. The sequence number is what tells you whether two events that share a record number refer to the same incarnation of a file or to a deleted predecessor whose slot was reused. Without it your timeline silently conflates them.
MFTECmd's CSV output is the closest off-the-shelf layout to this. Pipe its rows through a thin script that adds source=MFT and emits one row per timestamp, and you have a working corpus.
The timestamps in order of how much they lie
SI moves on essentially every operation Windows does to a file. Reads update accessed if NTFS feels like it (Windows 7+ defaults to not updating accessed for performance, unless fsutil behavior set disablelastaccess 0 is set). Writes update modified and MFT-modified. Renames update MFT-modified. Timestomping with SetFileTime updates whichever of the four the attacker pointed it at.
FN is updated on rename, hard link creation, and on the initial creation of the file. After that, FN largely sits. The granularity of FN updates by Windows is also coarser; many files end on .0000000 legitimately if FN was set on creation and never touched again.
In practice:
- FN created is the most trustworthy "this file first appeared in this directory" signal you have from the MFT alone.
- SI created is the easiest to forge. Treat it as a hint, not a fact.
- SI modified is the workhorse. Combined with the matching USN
DATA_OVERWRITEit tells you the file's bytes actually changed at that instant. - SI accessed on Windows 7+ defaults to disabled. If you see it changing, either an admin re-enabled it, the volume is on a Server SKU, or something is mounting volumes and walking files (backup software, AV scans, EDR introspection).
Sub-second granularity is the tell. NTFS keeps timestamps in 100-nanosecond ticks. Native Windows operations leave noise in the bottom digits. Timestomping tools like SetMACE and the well-known timestomp.exe round to the second. A column of .0000000 suffixes lined up neatly is a fingerprint.
Merging with the USN journal
The MFT shows you the present and a frozen history of metadata. The USN journal shows you the verbs. Pair them and a single line in your timeline becomes a sentence.
The way I merge: parse $UsnJrnl:$J separately, emit one row per record with source=USN, then sort the combined stream by timestamp. The USN's reason codes (FILE_CREATE, DATA_OVERWRITE, RENAME_OLD_NAME, RENAME_NEW_NAME, FILE_DELETE, CLOSE) give you the operation; the MFT supplies the resulting state. A USN RENAME_OLD_NAME immediately followed by RENAME_NEW_NAME at the same USN reveals the rename. Without the journal, an MFT diff between two snapshots might tell you the file moved; it cannot tell you the order.
One pitfall: USN timestamps and MFT SI timestamps drift by milliseconds for the same event. Do not over-index on the alignment. Sort to the second and use the USN reason code as the tiebreaker.
What ruins MFT timelines
Out-of-order acquisition. If you collect MFT and USN ten minutes apart on a live system, the journal kept going. Pair them from the same point in time, ideally from a VSS snapshot you triggered yourself.
Forgetting the fixup array. Every parser worth using handles this, but if you are rolling your own (please do not, unless you are learning), reading raw 1,024-byte chunks gives you garbage at offsets 510 and 1022 of every record. Apply fixups first.
Conflating record numbers across sequence boundaries. Record 12345 sequence 3 is not the same file as record 12345 sequence 5. The slot was reused. If your timeline groups by record number alone, you will conflate a deleted file with the one that took its slot.
Trusting SI on system binaries. Windows Update touches SI on patched files. A modified SI on C:\Windows\System32\svchost.exe is almost always a CBS install, not an intrusion. Cross-reference with setupapi.dev.log and the CBS.log before you make claims.
Treating the timeline as the conclusion. It is the input to analysis. You still need to know what each event means in the context of the host, the user, and the case.
A workflow that holds up
- Acquire
$MFT,$UsnJrnl:$J,$LogFile, and any available VSS snapshots' copies of the same. Hash everything (SHA-256) immediately. - Parse MFT and USN with MFTECmd or
mft_dumpplus a USN parser. Verify the parsers ran without warnings. - Emit normalized rows: one per timestamp for MFT, one per record for USN, with explicit
source,mft_record,seq, andpath. - Sort by timestamp into a single stream. Do not deduplicate yet; near-duplicates carry signal.
- Pivot to the period of interest. Pull a window two hours before and two hours after the suspected event.
- Layer in the rest: Prefetch, Amcache, Sysmon 1/11/15, LNK and jump list artifacts, Recycle Bin
$Irecords, SRUM for network and process resource usage. - Look for impossible orderings. SI created before FN created. A
DATA_OVERWRITEUSN reason with no corresponding SI modified change in the MFT. TwoFILE_CREATEevents at the same record number with the same sequence (the parser is broken).
That last step is where MFT timelines earn their keep. They expose impossibilities. Every impossibility is either a parser bug or an attacker artifact, and both are worth chasing.
What MFT cannot tell you
It cannot tell you which process touched a file. It cannot tell you which user. It cannot tell you what was inside a file larger than a few hundred bytes that has been deleted and partially overwritten. For those, you need the surrounding artifacts: 4688 with command line audit (or Sysmon 1), the security log's logon ID chain, and on a good day a RAM dump that caught the process still resident.
The MFT is the spine. The other artifacts are the muscles. Neither walks without the other.
Further reading
- Eric Zimmerman, MFTECmd. The canonical CSV layout and the parser most IR teams standardize on.
- SANS, Windows Forensic Analysis Poster. Folds MFT events into the broader timeline correctly.
- Harlan Carvey, Investigating Windows Systems. The chapters on timeline construction are still the practical reference.