Skip to content

05 · NTFS & File System

NTFS Forensics: $MFT, USN Journal and $LogFile Explained

Read NTFS like an examiner: MFT record structure, $SI vs $FN timestamps, timestomping checks, USN reason flags, $LogFile, $I30 slack and deleted files.

Published on 12 min read

NTFS keeps far more history than the files a user can see: every file ever created leaves a record, every change is journaled, and deleted names linger in index slack. This guide walks through the metadata files that matter, how an MFT record is built, how to read $SI and $FN timestamps without over-claiming, how to catch timestomping, and how to combine the $MFT, USN journal, $LogFile and $I30 into one defensible file-system timeline.

The NTFS metadata files that matter

Everything on an NTFS volume, including the file system's own bookkeeping, is a file described in the Master File Table. The first records are reserved for system files that the Windows API hides and locks, so you need a raw-disk tool or an image to collect them.

MFT recordFileForensic value
0$MFTOne FILE record per file and directory: names, parents, timestamps, sizes, resident data
1$MFTMirrCopy of the first four MFT records only, used for recovery; not a backup of the table
2$LogFileTransaction log of metadata changes with redo and undo data; short retention
5. (root)Root directory and its $I30 index
6$BitmapOne bit per cluster: allocated or free. Tells you whether a deleted file's clusters are still available
7$BootBoot sector: bytes per sector, sectors per cluster, MFT location, record size
9$Secure$SDS stream holds shared security descriptors (owner SID, ACLs); $SDH and $SII index them
11$ExtendDirectory holding $UsnJrnl, $ObjId, $Quota, $Reparse and others
varies$Extend\$UsnJrnl$J stream: change journal records. $Max stream: journal size parameters

Records 3 ($Volume), 4 ($AttrDef), 8 ($BadClus) and 10 ($UpCase) complete the set. Every NTFS volume has its own copy of all of this, including USB drives and VHDs, so collect per volume.

Anatomy of an MFT record

The FILE header

Each $MFT entry is normally 1,024 bytes (4,096 on some 4Kn disks; $Boot tells you which). In a hex editor a valid record usually starts with FILE0: the FILE signature followed by the update sequence array offset 0x30, which happens to be the ASCII character 0. A record that failed its integrity check carries BAAD instead.

OffsetSizeField
0x004Signature FILE
0x042Update sequence array offset (fixup)
0x088$LogFile sequence number (LSN) of the last change
0x102Sequence number
0x122Hard link count
0x142Offset to the first attribute
0x162Flags: 0x01 in use, 0x02 directory
0x184Used size of the record
0x208Base record reference (non-zero for extension records)
0x2C4Record number (XP and later)

Remember the fixups: the last two bytes of each 512-byte sector in the record are replaced by the update sequence number and must be restored from the array before you read attribute data. Every serious parser does this; hand-carving often forgets.

Attributes

After the header comes a list of attributes, each with a type code, ending with 0xFFFFFFFF. The ones you meet daily are 0x10 $STANDARD_INFORMATION, 0x20 $ATTRIBUTE_LIST, 0x30 $FILE_NAME, 0x80 $DATA, 0x90 $INDEX_ROOT, 0xA0 $INDEX_ALLOCATION and 0xB0 $BITMAP. A file can carry several $FILE_NAME attributes: a long name, a DOS 8.3 short name and one per hard link.

Resident versus non-resident data

When the content is small enough to fit in the record, $DATA is resident: the bytes live inside the MFT entry itself. With 1,024-byte records the practical limit is roughly 700 bytes, depending on the other attributes present. Resident data is gold: small scripts, batch files, configuration fragments and Zone.Identifier streams can be read straight from the $MFT, even for deleted files whose record has not been reused. Larger content is non-resident: the attribute stores data runs (cluster offsets and lengths) pointing into the volume.

Record reuse and the file reference

When a file is deleted, NTFS clears the in-use flag and the record becomes available. The next time the record is allocated, the sequence number increments. Other structures refer to files by a 64-bit file reference: a 48-bit entry number plus a 16-bit sequence number. That pairing is what lets you tell apart "entry 81234, sequence 5" (the current file) from "entry 81234, sequence 4" (whatever lived there before). USN records, $I30 entries, LNK files and Jump Lists all store full references, so a sequence mismatch is a reliable sign that the file they point to is gone.

$STANDARD_INFORMATION versus $FILE_NAME timestamps

Both attributes hold four FILETIME values in UTC at 100-nanosecond resolution: created, modified, MFT record changed and accessed, the MACB set. They are maintained differently, which is the whole point of comparing them. The $SI versus $FN distinction is summarised below.

  • $SI is what Explorer, dir and most tools show. Windows updates it as the file is used, and any process with write access can set it through SetFileTime or NtSetInformationFile.
  • $FN is maintained by the kernel when the name entry is created or changed. There is no documented user-mode API to set it directly.

General behaviour on modern Windows (verify on the target build before relying on any single cell):

Action$SI$FN
CreateAll four set to nowAll four set to now
Modify contentM, C (and often A) updatedUsually unchanged
Rename in the same folderC updatedBehaviour varies by version; often refreshed or unchanged
Move within the volumeC updatedVaries; the new name entry may take current or $SI values
CopyB, C and A new; M inherited from sourceAll set to the copy time
Move to another volumeB new; M inheritedAll set to the move time

Two practical consequences. First, a modified time earlier than the creation time is normal for a copied file and is not tampering. Second, the last access time depends on NtfsDisableLastAccessUpdate, which was disabled by default from Vista to Windows 10 1709 and has been "system managed" since 1803, so record the value before interpreting any A timestamp.

Detecting timestomping

Timestomping (MITRE T1070.006) is almost always done on $SI, because that is what the API exposes. Look for these signs, from strongest to weakest:

  1. $SI created earlier than $FN created. A file cannot be created before its own name entry. MFTECmd flags this as SI<FN.
  2. Zeroed sub-second values. Many tools write whole seconds, leaving .0000000 fractions. Real timestamps almost never end in seven zeros. MFTECmd flags this as uSecZeros.
  3. A BASIC_INFO_CHANGE record in the USN journal for the file with no matching content write, especially shortly after FILE_CREATE.
  4. $LogFile undo data that still holds the previous $SI values, if the change is recent enough.
  5. Inconsistency with other artifacts: a binary "created in 2019" whose MFT entry number sits among files created last week, or which Prefetch, Amcache or the USN journal saw arrive yesterday.

Sophisticated actors can defeat the $FN check, for example by stomping $SI and then moving the file so the new name entry inherits the fake values, or by writing raw to the volume. That is why the USN journal and $LogFile corroboration matters more than any single flag.

# Full $MFT to CSV, then filter the anomaly columns in Timeline Explorer
MFTECmd.exe -f "E:\Triage\C\$MFT" --csv E:\Out --csvf mft.csv

# Dump one record in detail (entry number from the CSV)
MFTECmd.exe -f "E:\Triage\C\$MFT" --de 81234

The USN journal: every change, in order

The USN journal lives in the sparse $Extend\$UsnJrnl:$J stream and appends a record each time a file or directory changes. $Max holds its maximum size and allocation delta. When the journal exceeds its maximum, the oldest records are purged and their clusters read back as zeros, so a multi-gigabyte $J may contain only tens of megabytes of real data at the end.

Reason flags worth knowing

The Reason field is a bit mask that accumulates while a handle is open, then the final record carries CLOSE. Read the combination, not just one bit.

FlagValueTypical meaning
DATA_OVERWRITE0x00000001Content rewritten
DATA_EXTEND0x00000002Content appended, normal for new files
DATA_TRUNCATION0x00000004File shortened or emptied
NAMED_DATA_EXTEND0x00000020Alternate data stream written, for example Zone.Identifier
FILE_CREATE0x00000100File or directory created
FILE_DELETE0x00000200File or directory deleted
SECURITY_CHANGE0x00000800ACL or owner changed
RENAME_OLD_NAME0x00001000Name before a rename or move
RENAME_NEW_NAME0x00002000Name after a rename or move
BASIC_INFO_CHANGE0x00008000Attributes or timestamps changed
STREAM_CHANGE0x00200000A named stream added or removed
CLOSE0x80000000Final record for this handle

Rebuilding full paths

Each record stores only the file name plus the parent's file reference. To get a full path, resolve the parent reference against the $MFT, recursively up to the root. If the parent's entry was reused (sequence mismatch), the path is ambiguous and a good parser marks it as such rather than guessing. Resolving against the $MFT collected at the same time as $J is essential.

# Parse $J and resolve parent paths using the $MFT from the same volume
MFTECmd.exe -f "E:\Triage\C\$Extend\$J" -m "E:\Triage\C\$MFT" --csv E:\Out --csvf usn.csv

Typical patterns: a download shows FILE_CREATE, DATA_EXTEND, then NAMED_DATA_EXTEND for the Zone.Identifier stream; an archive extraction shows dozens of FILE_CREATE under one parent within a second; ransomware shows paired RENAME_OLD_NAME and RENAME_NEW_NAME with a new extension across thousands of files.

$LogFile: the last minutes, with before and after values

The $LogFile is the NTFS transaction log, typically 64 MB on modern volumes. Before changing metadata, NTFS writes a record with a redo operation (how to reapply the change) and an undo operation (how to roll it back), often containing the raw bytes before and after. That means $LogFile can show the original $SI timestamps of a stomped file, the names in an index entry that was just removed, or resident content that has since been overwritten.

The catch is retention. It is a circular buffer covering metadata changes, and on a busy system drive it may hold minutes to a few hours of activity. Collect it early, and treat it as a precision tool for events close to acquisition time, not a long-term history. Windows 8 and later use Log File Service version 2.0 while the volume is mounted, which some older parsers cannot read.

$I30 index slack: names that outlive their records

Every directory stores a sorted index of its children in $I30 attributes: $INDEX_ROOT inside the MFT record for small folders, plus $INDEX_ALLOCATION INDX records once it grows. Each entry contains a copy of the child's $FILE_NAME, with name, sizes and four timestamps. When entries are removed or the B-tree is rebalanced, old entries often survive in the unused tail of the INDX record. Parsing that $I30 slack can reveal files deleted from a staging folder even after their MFT records were reused. This is a form of file slack specific to directory indexes.

# Parse the $I30 of a directory exported with a raw-NTFS tool
MFTECmd.exe -f "E:\Triage\ProgramData_I30" --csv E:\Out --csvf i30.csv

Alternate data streams and Zone.Identifier

An alternate data stream is simply a named $DATA attribute. Windows uses one for the Mark of the Web: file.exe:Zone.Identifier stores ZoneId=3 for Internet downloads and, on Windows 10 and later with most browsers, ReferrerUrl and HostUrl. Because the stream is tiny, it is almost always resident, so MFTECmd prints its contents directly in the CSV. See the Zone.Identifier cheat sheet for field details.

Attackers also hide payloads in ADS (type evil.exe > notes.txt:hidden.exe). They show up as extra $DATA attributes in the $MFT and as NAMED_DATA_EXTEND or STREAM_CHANGE in the USN journal.

# Live triage: list streams on files in a folder
Get-ChildItem C:\Users\Public -Recurse -File | Get-Item -Stream * |
  Where-Object Stream -ne ':$DATA' | Select-Object FileName, Stream, Length

Deleted file recovery versus carving

These are different techniques with different evidential weight.

  • Metadata-based recovery uses an unallocated MFT record that still has its data runs. If $Bitmap shows those clusters as free and nothing has reused them, you get the file with its original name, path and timestamps. Tools: The Sleuth Kit (icat), Velociraptor Windows.NTFS.Recover, most commercial suites.
  • File carving ignores the file system and scans unallocated space for headers and footers. It recovers content without names or timestamps and fragments badly on large files. Use it when metadata is gone, and say clearly in the report that carved content has no file-system provenance.

On SSDs with TRIM, freed clusters are often zeroed quickly, so expect less from both approaches. Volume shadow copies are frequently the better source for older versions of a file.

Tools

  • MFTECmd (Eric Zimmerman) parses $MFT, $J, $Boot, $SDS and $I30, with anomaly columns for timestomping and resident ADS output. Source and releases: github.com/EricZimmerman/MFTECmd.
  • $LogFile parsers: LogFileParser (Joakim Schicht), NTFS Log Tracker and dfir_ntfs decode redo and undo operations.
  • Velociraptor: Windows.NTFS.MFT, Windows.Forensics.Usn and Windows.NTFS.I30 work live or offline.
  • Our browser-based parsers, which run client-side so the evidence never leaves your machine: MFT Parser, USN Parser and LogFile Parser.
  • Plaso ingests $MFT and $J into a super timeline; see building a super timeline with Plaso.

Common mistakes

  • Treating any $SI/$FN difference as tampering. $FN routinely lags. Only $SI created earlier than $FN created, plus corroboration, supports a timestomping finding.
  • Parsing $J against the wrong $MFT. Paths resolved against a later or earlier $MFT will be wrong wherever entries were reused.
  • Ignoring sequence numbers. Entry 81234 today is not necessarily the file a LNK or USN record referenced last month.
  • Collecting too late. $LogFile and the end of $J are overwritten by normal activity, including your own triage tools running on the system.
  • Forgetting shadow copies. Older $MFT and $J versions often sit in VSS and can extend your timeline by weeks.
  • Reading access times without checking NtfsDisableLastAccessUpdate.

Workflow

  1. Collect $MFT, $J, $Max, $LogFile, $Secure:$SDS and $Boot from every NTFS volume, plus the same from shadow copies. See acquisition and triage with KAPE and Velociraptor.
  2. Parse $MFT to CSV. Record the NTFS version details, record size and NtfsDisableLastAccessUpdate.
  3. Parse $J with -m pointing to the same volume's $MFT. Note the first and last USN timestamps to know your coverage window.
  4. Pivot on the incident window: filter USN for FILE_CREATE, RENAME_*, FILE_DELETE and BASIC_INFO_CHANGE under suspicious parents (\ProgramData\, \Users\Public\, \Windows\Temp\, user Downloads).
  5. For every suspicious file, check the $MFT row: SI<FN, uSecZeros, resident Zone.Identifier, owner via SecurityId and $Secure:$SDS.
  6. Parse $LogFile if the activity is recent, and $I30 of staging directories to find deleted names.
  7. Recover deleted files by metadata where possible; carve only as a fallback, and label carved results.
  8. Merge everything into a single timeline and correlate with execution artifacts such as Prefetch and event logs. When the file system shows deliberate cleanup, continue with detecting Windows anti-forensics.

Area 05

NTFS & File System

The artifact cheat sheets behind this guide: location by Windows version, what each one proves and the tools to parse it.

Related articles