Windows File System Internals (NTFS)

By Debraj Basak·Apr 25, 2025 · Updated Aug 1, 2026·14 min readWindows Internals

Every file you touch on a modern Windows box – every executable, every registry hive backing file, every prefetch artifact – lives inside a relational database masquerading as a file system. NTFS doesn’t just store bytes in clusters; it wraps every object in a rich metadata record, supports multiple data streams per file, journals its own mutations, and enforces ACLs at the driver level. If you’ve ever wondered why forensic timelines have four timestamps per file, or how malware hides a full PE inside a text file with no visible size change, the answer is always the same: MFT attributes.

Objective: Understand the on-disk architecture of NTFS – volume layout, MFT record format, attribute types (resident vs. non-resident), data runs, Alternate Data Streams, journals, and reparse points – and learn how these features are abused for defence evasion and how to detect that abuse.


1. NTFS Architecture Overview

NTFS (New Technology File System) has shipped as the default Windows file system since Windows NT 3.1. The current on-disk version is NTFS 3.1, unchanged since Windows XP – what changed between OS releases is the driver, not the format.

An NTFS volume is divided into fixed-size clusters (typically 4 KB on volumes ≤ 16 TB). The first sector contains the BIOS Parameter Block inside $Boot, storing bytes-per-sector, sectors-per-cluster, total sectors, and the LCN (Logical Cluster Number) of the MFT start. Everything else – every file, directory, and piece of internal bookkeeping – is a record in the Master File Table.

FeatureNTFSFAT32exFAT
Max file size16 TB (practical)4 GB16 EB (theoretical)
JournalingYes ($LogFile)NoNo
ACL supportFull DACL/SACLNoPartial (via EA)
Alternate Data StreamsYesNoNo
Hard links / ReparseYesNoNo
Compression / EFSPer-fileNoNo

2. The Master File Table

The MFT is the heart of NTFS. Every file, directory, and internal metadata structure occupies at least one MFT record (also called a file record segment). The MFT itself is a file – entry 0, named $MFT – so it describes itself recursively.

Each MFT record is 1,024 bytes. The first 42 bytes are a fixed header; the remaining space holds a variable-length chain of attributes. When a file’s attributes outgrow a single 1 KB record, NTFS allocates extension records and links them via an $ATTRIBUTE_LIST (type 0x20).

MFT Addressing

Each entry gets a 48-bit file number (sequential index starting at 0) and a 16-bit sequence number that increments every time the entry is reallocated. Together they form the 64-bit MFT_SEGMENT_REFERENCE – the stable “file reference number” that directory indexes, $LogFile entries, and USN records all use to point at files. The sequence number is the reason a stale handle can’t accidentally reference a reused MFT slot.


Hierarchy diagram showing an NTFS volume branching into its key metadata files ($Boot, $MFT, $LogFile, $Bitmap), with $MFT containing 1024-byte records that hold a chain of typed attributes
Every NTFS object lives as a record in the MFT; internal metadata files like $Boot and $LogFile are themselves MFT entries.

3. MFT Record Header Dissection

Every record begins with the FILE_RECORD_SEGMENT_HEADER. I spent an unreasonable amount of time the first time I parsed these by hand because I forgot the update-sequence fixups – the raw bytes on disk don’t match what the NTFS driver sees until you undo the fixup array. Here’s the layout:

OffsetSizeFieldNotes
0x004SignatureMagic bytes FILE (0x46494C45)
0x042UpdateSequenceArrayOffsetOffset to fixup array
0x062UpdateSequenceArraySizeSize in words (USN + array)
0x088LogFileSequenceNumberLSN – ties record to $LogFile
0x102SequenceNumberIncremented on reallocation
0x122ReferenceCountHard link count
0x142FirstAttributeOffsetByte offset to first attribute
0x162Flags0x01 = in use, 0x02 = directory
0x184BytesInUseActual used bytes
0x1C4BytesAllocatedAllocated size (usually 1024)
0x208BaseFileRecordSegmentMFT_SEGMENT_REFERENCE of base record (0 if this is the base)
0x282NextAttributeIdNext instance number to assign

Update sequences exist to detect torn writes. The NTFS driver replaces the last two bytes of each 512-byte sector within the record with a sequence value, storing the originals in the fixup array. On read, it verifies the sentinel matches and restores the original bytes. If the check fails, the record is corrupt.

You can dump a raw MFT record with FSCTL_GET_NTFS_FILE_RECORD:

#include <windows.h>
#include <stdio.h>

int main(void) {
    HANDLE hVol = CreateFileW(L"\\\\.\\C:", GENERIC_READ,
        FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
    if (hVol == INVALID_HANDLE_VALUE) return 1;

    /* Request MFT record for file number 0 ($MFT itself) */
    NTFS_FILE_RECORD_INPUT_BUFFER in = { .FileReferenceNumber.QuadPart = 0 };
    BYTE outBuf[sizeof(NTFS_FILE_RECORD_OUTPUT_BUFFER) + 1024];
    DWORD ret;

    BOOL ok = DeviceIoControl(hVol, FSCTL_GET_NTFS_FILE_RECORD,
        &in, sizeof(in), outBuf, sizeof(outBuf), &ret, NULL);

    if (ok) {
        NTFS_FILE_RECORD_OUTPUT_BUFFER *out = (void*)outBuf;
        BYTE *rec = out->FileRecordBuffer;
        printf("Signature: %.4s\n", rec);                    /* "FILE" */
        printf("Flags:     0x%04X\n", *(WORD*)(rec + 0x16)); /* in-use + dir */
        printf("FirstAttr: 0x%04X\n", *(WORD*)(rec + 0x14));
    }
    CloseHandle(hVol);
    return 0;
}

Run this elevated – \\.\C: requires admin rights for direct volume access.


4. NTFS Attribute Architecture

Attributes are the building blocks of every MFT record. Each attribute has a header (always resident inside the record) and a body that is either resident or non-resident.

Resident: the body sits immediately after the header inside the 1 KB record. Small files (roughly < 700 bytes of content) store their entire $DATA right in the MFT – zero extra cluster allocations, one disk read for metadata and content.

Non-resident: the body lives in external clusters. The header contains a run list (VCN-to-LCN mapping) describing where those clusters are.

Attribute Header Fields

FieldResidentNon-Resident
TypeCode (4 bytes)
RecordLength (4 bytes)
FormCode (1 byte)0x000x01
NameLength / NameOffset
Flags / Instance
ValueLength / ValueOffset
LowestVcn / HighestVcn
MappingPairsOffset
AllocatedLength / FileSize / ValidDataLength

Standard Attribute Types

The complete set is defined in $AttrDef (MFT entry 4):

Type CodeNameTypical ResidencePurpose
0x10$STANDARD_INFORMATIONResidentTimestamps (MACB), DOS flags, owner, USN
0x20$ATTRIBUTE_LISTResidentOverflow pointer to extension records
0x30$FILE_NAMEResidentUnicode name, parent ref, duplicate timestamps
0x40$OBJECT_IDResidentGUID for distributed link tracking
0x50$SECURITY_DESCRIPTORResidentLegacy; NTFS 3.0+ stores ACLs in $Secure:$SDS
0x60$VOLUME_NAMEResidentVolume label (on $Volume only)
0x70$VOLUME_INFORMATIONResidentNTFS version, dirty flag
0x80$DATAEitherFile content; unnamed = default stream, named = ADS
0x90$INDEX_ROOTResidentB-tree root for directory index
0xA0$INDEX_ALLOCATIONNon-residentExternal B-tree nodes
0xB0$BITMAPEitherAllocation bitmap (index or MFT)
0xC0$REPARSE_POINTResidentSymlinks, mount points, OneDrive stubs
0xD0$EA_INFORMATIONResidentExtended Attribute metadata
0xE0$EAEitherExtended Attribute data

A forensic detail that trips people up: $STANDARD_INFORMATION timestamps are trivially settable from user mode (SetFileTime), but $FILE_NAME timestamps are only updated by the NTFS driver during renames or hard-link operations. When an attacker timestomps a file, $STANDARD_INFORMATION changes but $FILE_NAME doesn’t – that delta is the classic timestomp detection signal.


Flow diagram of an MFT record showing the fixed header followed by a chain of typed attributes: $STANDARD_INFORMATION, $FILE_NAME, and $DATA as either resident content or a non-resident run list, terminated by an end marker
Attributes chain sequentially inside the 1 KB MFT record; $DATA is resident for tiny files and switches to an external run list once content exceeds roughly 700 bytes.

5. Data Runs and Non-Resident Layout

When a $DATA attribute is non-resident, its header contains a run list that maps Virtual Cluster Numbers (VCNs, zero-based offsets within the attribute) to Logical Cluster Numbers (LCNs, physical offsets on the volume).

Each entry in the run list is encoded compactly. The first byte is split into two nibbles: the low nibble gives the byte-width of the run-length field, and the high nibble gives the byte-width of the LCN offset field (which is signed / delta-encoded from the previous run’s starting LCN). The list is terminated by a 0x00 byte.

For sparse files, a run entry with a zero-length LCN field means “no clusters allocated here – return zeroes.” NTFS 3.0+ uses this for sparse attributes, avoiding disk allocation for regions that are entirely null.

Compressed attributes use a 16-cluster compression unit. Each unit is either stored compressed (fewer than 16 clusters on disk) or uncompressed. The run list interleaves real and sparse runs to encode this.


6. NTFS Metadata Files

MFT entries 0-15 are reserved for internal metadata. These aren’t hidden through any special mechanism – they’re just files with the Hidden and System DOS flags set.

EntryNameWhat It Holds
0$MFTThe MFT itself (self-referential)
1$MFTMirrMirror of first 4 MFT records – recovery fallback
2$LogFileRedo/undo transaction journal
3$VolumeVolume name, NTFS version, dirty flag
4$AttrDefAttribute type definitions
5.Root directory (\)
6$BitmapCluster allocation bitmap (unnamed $DATA attribute)
7$BootVBR + BPB; also the boot sector
8$BadClusBad-cluster list; the named stream $Bad contains the entries
9$SecureCentralized security descriptors ($SDS stream)
10$UpCaseUnicode uppercase mapping table
11$ExtendDirectory containing $UsnJrnl, $Quota, $Reparse, $ObjId

You can query volume-level MFT stats programmatically:

HANDLE hVol = CreateFileW(L"\\\\.\\C:", GENERIC_READ,
    FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
NTFS_VOLUME_DATA_BUFFER vd;
DWORD ret;
DeviceIoControl(hVol, FSCTL_GET_NTFS_VOLUME_DATA, NULL, 0,
    &vd, sizeof(vd), &ret, NULL);
printf("MFT Start LCN : %lld\n", vd.MftStartLcn.QuadPart);
printf("Bytes/Cluster  : %u\n",  vd.BytesPerCluster);
printf("Bytes/MFT Rec  : %u\n",  vd.BytesPerFileRecordSegment);
printf("Total Clusters : %lld\n", vd.TotalClusters.QuadPart);
CloseHandle(hVol);

7. Alternate Data Streams In Depth

Every NTFS file has at least one $DATA attribute – the unnamed default stream (what type file.txt reads). Additional named $DATA attributes are Alternate Data Streams. They’re stored as extra attributes in the same MFT record (or in extension records if the record fills up), making them invisible to dir, Explorer’s size column, and most legacy tooling.

Creating and Reading ADS

Win32 uses colon-separated path syntax:

/* Write to a named stream */
HANDLE h = CreateFileA("C:\\temp\\legit.txt:payload",
    GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
const char *data = "hidden content";
WriteFile(h, data, (DWORD)strlen(data), NULL, NULL);
CloseHandle(h);

Enumerating Streams

WIN32_FIND_STREAM_DATA fsd;
HANDLE hFind = FindFirstStreamW(L"C:\\temp\\legit.txt", FindStreamInfoStandard, &fsd, 0);
if (hFind != INVALID_HANDLE_VALUE) {
    do {
        wprintf(L"  Stream: %-30s  Size: %lld\n",
            fsd.cStreamName, fsd.StreamSize.QuadPart);
    } while (FindNextStreamW(hFind, &fsd));
    FindClose(hFind);
}

From PowerShell:

Get-Item -Path C:\temp\legit.txt -Stream * |
    Select-Object Stream, Length

Zone.Identifier and MOTW

When a browser or download manager saves a file, it writes a Zone.Identifier ADS containing the Mark-of-the-Web:

[ZoneTransfer]
ZoneId=3
ReferenceUrl=https://example.com
HostUrl=https://example.com/file.exe

SmartScreen, Office Protected View, and PowerShell’s execution policy all key off this stream. Stripping it (Remove-Item -Path file.exe -Stream Zone.Identifier) bypasses those trust checks – mapped to T1553.005.


Graph diagram showing a single MFT record for legit.txt with three $DATA attributes: the unnamed default stream, a hidden named ADS carrying a payload executed by LOLBins, and the Zone.Identifier MOTW stream, with Sysmon Event 15 as the detection node
All named $DATA streams share one MFT record with the visible file, making ADS payloads invisible to Explorer and dir while Sysmon Event 15 catches their creation.

8. NTFS Journals

$LogFile (Transaction Journal)

MFT entry 2. Records redo and undo operations for every metadata change before the change is committed. If the system crashes, the NTFS driver replays the redo log (and backs out incomplete transactions with undo) on the next mount. This protects structural integrity, not file content.

$UsnJrnl (Change Journal)

Located at $Extend\$UsnJrnl, with two named streams: $J (the journal data, append-only) and $Max (configuration – max size and allocation delta). Each entry is a USN_RECORD_V2:

FieldTypePurpose
RecordLengthDWORDTotal record size
MajorVersionWORD2 for V2 records
FileReferenceNumberDWORDLONGMFT_SEGMENT_REFERENCE of the file
ParentFileReferenceNumberDWORDLONGParent directory reference
UsnUSN (LONGLONG)Byte offset in $J
TimeStampLARGE_INTEGERWhen the change occurred
ReasonDWORDBitmask – USN_REASON_FILE_CREATE, _DATA_EXTEND, _RENAME_NEW_NAME, etc.
FileNameWCHAR[]Variable-length filename

Query it from the command line:

fsutil usn queryjournal C:
fsutil usn readjournal C: csv > usn_dump.csv

Or programmatically with DeviceIoControl + FSCTL_READ_USN_JOURNAL – pass a READ_USN_JOURNAL_DATA_V0 struct and iterate USN_RECORD_V2 entries from the output buffer.


9. Reparse Points

The $REPARSE_POINT attribute (type 0xC0) causes the I/O Manager to hand the open request to a registered filter driver identified by the reparse tag. The data buffer follows the REPARSE_DATA_BUFFER / REPARSE_GUID_DATA_BUFFER layout.

TagValueUse
IO_REPARSE_TAG_MOUNT_POINT0xA0000003Volume mount points (junctions)
IO_REPARSE_TAG_SYMLINK0xA000000CSymbolic links (mklink)
IO_REPARSE_TAG_CLOUD0x9000001AOneDrive placeholders
IO_REPARSE_TAG_APPEXECLINK0x8000001BUWP app execution aliases

Abuse scenario: an attacker creates a junction (mklink /J) pointing a trusted directory to an attacker-controlled path, redirecting DLL search-order loads. This is a well-known privilege-escalation primitive in installer-based races.


10. Common Attacker Techniques

TechniqueDescription
ADS payload hidingStore a full PE or script in a named stream; host file shows no size change. Valak, WastedLocker, and BitPaymer all use this in the wild.
ADS execution via LOLBinswmic process call create "host.txt:evil.exe", wscript host.txt:evil.vbs, rundll32 advpack.dll,RegisterOCX host.txt:evil.dll
MOTW strippingDelete Zone.Identifier to bypass SmartScreen / Protected View
TimestompingModify $STANDARD_INFORMATION timestamps via SetFileTime; $FILE_NAME timestamps stay original
USN journal deletionfsutil usn deletejournal /D C: wipes the change journal, destroying forensic timeline
Symbolic link / junction hijackRedirect trusted paths to attacker-controlled locations for DLL hijacking or privilege escalation
EA-based data hidingStore payloads in Extended Attributes ($EA); rarely used by legitimate apps, so often overlooked by tooling

Flow diagram illustrating an attacker's NTFS abuse lifecycle: from initial access through ADS payload drop, MOTW stripping, $STANDARD_INFORMATION timestomping, and USN journal deletion, with detection opportunities at the $FILE_NAME timestamp delta and Sysmon Event 15
Attackers chain NTFS-native features – ADS, timestomping, and journal deletion – to hide presence, with the $SI/$FN timestamp gap and Sysmon Event 15 as the primary detection seams.

11. Defensive Strategies & Detection

Sysmon Events

Event IDNameWhat It Catches
15FileCreateStreamHashPrimary ADS detection – fires on named stream creation with SHA256 of content
11FileCreateBaseline file creation; correlate with Event 15 for ADS attachment
1ProcessCreateLOLBin execution with filename:stream in CommandLine
2FileCreationTimeChangedTimestomp detection ($STANDARD_INFORMATION modified)

The Sysmon driver registers as a minifilter attached to volumes, intercepting file operations before the file system processes them – this is why it sees ADS creation that dir misses.

Sigma Rules

title: Alternate Data Stream Creation (Non-MOTW)
logsource:
  product: windows
  service: sysmon
detection:
  selection:
    EventID: 15
  filter_legitimate:
    TargetFilename|endswith:
      - ':Zone.Identifier'
      - ':AFP_AfpInfo'
      - ':encryptable'
  condition: selection and not filter_legitimate
fields:
  - TargetFilename
  - Contents
  - Image
level: medium
title: LOLBin Executing from Alternate Data Stream
logsource:
  product: windows
  service: sysmon
detection:
  selection:
    EventID: 1
    Image|endswith:
      - '\wmic.exe'
      - '\rundll32.exe'
      - '\wscript.exe'
      - '\cscript.exe'
    CommandLine|re: '\\w+\\.\\w+:\\w+'
  condition: selection
level: high

Windows Audit Policy

Enable object-access auditing to catch ADS reads and writes:

auditpol /set /subcategory:"File System" /success:enable /failure:enable

This generates Event 4663 (object accessed) when SACLs are configured on target directories. Event 4656 fires on handle request, 4660 on deletion.

ETW Providers

ProviderGUIDUse
Microsoft-Windows-Kernel-File{EDD08927-9CC4-4E65-B970-C2560FB5C289}Kernel-level file I/O tracing
Microsoft-Windows-Ntfs{3FF37A1C-A68D-4D6E-8C9B-F79E8B16C482}NTFS driver internal events

Hardening

  • Application control: AppLocker / WDAC rules blocking execution from paths containing : in the command line.
  • Restrict LOLBins: Block wmic.exe, rundll32.exe, mshta.exe for non-admin users where business need doesn’t exist.
  • Monitor EA writes: fsutil file queryEA <path> – Extended Attributes are rarely used by legitimate modern applications; any EA write in a sensitive directory warrants investigation.
  • Preserve journals: Alert on fsutil usn deletejournal execution (Sysmon Event 1 with CommandLine match).

12. MITRE ATT&CK Mapping

TechniqueMITRE IDTacticDetection
ADS payload hiding / EA abuseT1564.004Defence EvasionSysmon Event 15, streams.exe, Get-Item -Stream *
MOTW strippingT1553.005Defence EvasionSysmon Event 15 (deletion of Zone.Identifier)
TimestompingT1070.006Defence Evasion$SI vs. $FN timestamp delta; Sysmon Event 2
USN journal deletionT1070Defence EvasionSysmon Event 1 – fsutil usn deletejournal
Directory / file enumerationT1083DiscoveryBaseline process → file-access patterns
Data from local system (ADS read)T1005CollectionObject-access audit (Event 4663)

13. Tools for NTFS Analysis

ToolPurposeLink
NtfsInfo (Sysinternals)Volume stats, cluster size, MFT layoutlearn.microsoft.com
streams.exe (Sysinternals)Recursive ADS enumerationlearn.microsoft.com
MFTECmd (Eric Zimmerman)Parse raw $MFT, $UsnJrnl, $LogFile into CSV/JSONericzimmerman.github.io
analyzeMFT.pyPython MFT parser for cross-platform forensicsgithub.com/dkovar
FTK ImagerBrowse NTFS structures, extract ADS, image volumesexterro.com
WinHex / 010 EditorRaw sector viewing, binary templates for MFT recordsx-ways.net / sweetscape.com
fsutilBuilt-in – query volumes, USN journal, reparse, streams, EA(ships with Windows)
Volatility 3 windows.filescanMemory-forensics recovery of FILE_OBJECT and MFT artefactsvolatilityfoundation.org
WinDbgKernel debugging – !ntfs, dt nt!_FILE_RECORD_SEGMENT_HEADERlearn.microsoft.com

Summary

  • NTFS is a metadata-driven file system where every object – files, directories, even the MFT itself – is a database record built from typed attributes. Understanding the 1 KB MFT record layout and its attribute chain is foundational to both forensics and offence.
  • The resident vs. non-resident split, data-run encoding, and update-sequence fixups are the mechanical details that matter when you’re parsing raw disk or writing detection logic.
  • Alternate Data Streams are the single most abused NTFS feature for defence evasion – real-world malware families (Valak, WastedLocker, BitPaymer) store and execute payloads from named streams. Detect with Sysmon Event 15 and recursive stream enumeration.
  • $UsnJrnl and $LogFile are goldmines for forensic timelines – and deletion targets for adversaries. Protect and monitor them.
  • Timestomping exploits the gap between easily-settable $STANDARD_INFORMATION and kernel-controlled $FILE_NAME timestamps. That delta is your detection signal.

Related Tutorials

References

Get new drops in your inbox

Windows internals, exploit dev, and red-team write-ups - no spam, unsubscribe anytime.