Logical mapping of the 1024-byte FILE record, its attribute headers, and the four most forensically-loaded attributes: $STANDARD_INFORMATION, $FILE_NAME, $DATA, and $ATTRIBUTE_LIST.
$MFT carrying the timestamps, sizes, parent pointer, and a list of attributes that describe the actual data. For an investigator, it is the closest thing NTFS has to a full filesystem ledger.
$MFT — is the spine of NTFS. Every file, every directory, and even the volume's metadata files live as fixed-size FILE records (1024 bytes on standard volumes, 4096 bytes on some large-cluster formats) inside this table. A FILE record begins with the magic bytes F I L E (0x46 0x49 0x4C 0x45), followed by a header, then a stream of attributes that describe everything Windows knows about that file: when it was created, where its data lives on disk, who owns it, what's in its name, what reparse points it carries.
$SI, one in $FN), and links every file to its parent directory by MFT reference. Click the icon at any time to minimize or re-open this card.
C:\$MFT · one fixed-size FILE record per file / directory$STANDARD_INFORMATION, $FILE_NAME, $DATA, end sentinel, slack.An MFT entry is always exactly 1024 bytes on standard NTFS volumes (4096 on large-cluster formats). The first 48 bytes are a kernel-managed FILE Record Header (NTFS 3.1) ending at 0x2F, followed by an Update Sequence Array (USA) at 0x30–0x37 (USN + 2 fixups + 2 B align). The first attribute begins at offset 0x38 (per the header's First Attribute Offset field) and a stream of typed, 8-byte-aligned attributes follows in canonical order: $STANDARD_INFORMATION → $FILE_NAME → $DATA → … until a 0xFFFFFFFF sentinel marks end-of-attributes. Everything past that point is record slack. The bar below maps that 1024 B slot — drawn to true byte proportions on a wide canvas so every band stays readable. Scroll horizontally to traverse the full record; click any band to jump to its byte-level tab.
"FILE" magic, sequence number, hardlink count, in-use / directory flags, and the offset of the first attribute.SetFileTime() writes to — and the one timestomping tools target.FileReferenceNumber
↔
1 MFT record : N USN events
C:\$Extend\$UsnJrnl:$J · one sparse stream for the whole volumeUSN_RECORD_V2 entries (FRN, parent FRN, reason flags, FILETIME, filename).This is a completely separate file from $MFT. The MFT box above shows you the current state of a file (what it looks like right now); this box shows you the history — every FILE_CREATE, rename, attribute write, and security change, with a precise FILETIME on each event. The two are joined on FRN: a BASIC_INFO_CHANGE reason always corresponds to a write into the $STANDARD_INFORMATION band above; RENAME_OLD_NAME / RENAME_NEW_NAME always corresponds to a rewrite of the $FILE_NAME band above.
\$Extend\$UsnJrnl:$J · not inside the FILE recordUSN_RECORD_V2 entries that records every change made to the files described above. Where the MFT shows you the current state, the USN journal shows you the history of state transitions — with a precise FILETIME on every event.FILE_CREATE, every rename, every BASIC_INFO_CHANGE (timestamp / attribute write), every SECURITY_CHANGE, every CLOSE — so investigators can reconstruct exactly how a file got into its current shape.USN_RECORD carries a FileReferenceNumber (the same MFT entry/sequence pair embedded in the FILE Header above) and a ParentFileReferenceNumber (the same pair stored inside $FILE_NAME). FRN is the universal pivot — one MFT record ↔ many USN events.fsutil usn createjournal. Older entries are continuously discarded as the sparse stream's allocated region wraps.Select any field in the map to reveal a deep forensic dive.
NTFS does not store files in a directory tree the way FAT does. Every file and every directory on an NTFS volume is just a row in a giant database called the Master File Table. Even the MFT itself is a row in the MFT (record 0, $MFT). Even the root directory is a row (record 5, "."). Even the volume label, the security descriptor pool, and the bad-cluster list are rows.
Each row is a 1024-byte FILE record beginning with the ASCII magic FILE. The record header is followed by a stream of attributes — small typed structures that describe one facet of the file each. Three attributes appear in nearly every record an investigator cares about: $STANDARD_INFORMATION (timestamps + flags), $FILE_NAME (the filename + parent reference + a second timestamp set), and $DATA (the actual file content, either resident inside the record or pointed at by runlist extents on disk).
When the attributes for one file no longer fit in 1024 bytes — large directories, files with hundreds of alternate data streams, or heavily fragmented files — NTFS spills them into extension records tracked by a fourth attribute, $ATTRIBUTE_LIST. Crow-Eye's MFT_Claw parser walks every record sequentially, dispatches by attribute type, and reconstructs full paths by chasing each $FILE_NAME entry's parent reference back to record 5.
At volume format time, NTFS reserves an MFT Zone — by default about 12.5% of the volume — for the MFT to grow into without fragmenting. The first 16 records (0–15) are reserved for NTFS metadata files. Records 16 and beyond hold user files and directories.
The lifecycle of a single FILE record:
$Bitmap attribute on the MFT itself) and writes a fresh FILE header with the in-use flag set.$STANDARD_INFORMATION (four FILETIMEs, file-attribute flags) and $FILE_NAME (parent reference, name, second FILETIME set) immediately. If the file has data, a $DATA attribute follows — resident if the data fits in the remaining bytes of the record, non-resident (with cluster runlists) if it doesn't.$LogFile Sequence Number (LSN) and updates the $SI "entry changed" timestamp. The on-disk update is fixed up at write time by the Update Sequence Array (USA) — a 2-byte signature is interleaved into every 512-byte sector so torn writes can be detected.Every $FILE_NAME attribute embeds its parent directory as an 8-byte MFT reference: the lower 48 bits are the parent's MFT entry number, the upper 16 bits are the parent's sequence number. By chasing parent references record-by-record back to entry 5 (the root), Crow-Eye reconstructs full paths even for deleted files whose parent directory still exists.
Unlike many Windows artifacts, the on-disk MFT format has changed surprisingly little since 2001. Crow-Eye's parser handles the current format (NTFS 3.1) which has been stable from Windows XP through Windows 11.
FILE.0x0001 bit in the resident-attribute flags). FILE record layout is unchanged.$SECURITY_DESCRIPTOR attribute is added as a per-file ACL container. This is the last revision sometimes called "NTFS 4" by old documentation; the on-disk version byte is still 1.2.$STANDARD_INFORMATION grows from 48 to 72 bytes to hold owner_id, security_id, quota_charged, and usn.1.2 PB max volume size, optional 4096-byte FILE records on huge volumes, and transparent compression / deduplication on top, but the on-disk MFT record layout remains v3.1.0x46 0x49 0x4C 0x45 at offset 0. Carving anchor.$MFT, $LogFile, etc.).0x10 ($SI) to 0x100 ($LOGGED_UTILITY_STREAM).$SI + four in $FN. The pair is the key to detecting timestomping.Reserved by NTFS at format time. Every NTFS volume has these records, every time.
| Rec # | Filename | Role |
|---|---|---|
| 0 | $MFT | The MFT itself — its own DATA attribute is the table. |
| 1 | $MFTMirr | Mirror of the first 4 records — recovery insurance. |
| 2 | $LogFile | Transaction log used by NTFS recovery (LSN source). |
| 3 | $Volume | Volume name, NTFS version, dirty bit. |
| 4 | $AttrDef | Definitions of every attribute type known to this volume. |
| 5 | . (root) | Root directory. All path reconstruction terminates here. |
| 6 | $Bitmap | Cluster allocation bitmap for the whole volume. |
| 7 | $Boot | Maps the boot sector(s) as a file. First-stage bootloader lives here. |
| 8 | $BadClus | Sparse map of bad clusters marked by chkdsk. |
| 9 | $Secure | Shared security descriptor pool (since NTFS 3.0). |
| 10 | $UpCase | Unicode uppercase mapping table for case-insensitive lookup. |
| 11 | $Extend | Container directory for $Quota, $ObjId, $Reparse, $UsnJrnl. |
| 12–15 | reserved | Reserved for future Microsoft use; usually zero / unused. |
Every attribute begins with a 4-byte type code. Green rows are deep-parsed by Crow-Eye (timestamps decoded, paths extracted, GUIDs formatted). The remaining rows are still recognised and counted, but their content is opaque.
| Type | Name | Purpose |
|---|---|---|
| 0x10 | $STANDARD_INFORMATION | 4 timestamps, file-attr flags, owner_id, security_id, USN. |
| 0x20 | $ATTRIBUTE_LIST | Pointer to extension records when attributes overflow. |
| 0x30 | $FILE_NAME | Filename + parent ref + 2nd timestamp set. Can appear multiple times. |
| 0x40 | $OBJECT_ID | 128-bit globally-unique ID assigned to the file. Persisted as data_type='ObjectID'. |
| 0x50 | $SECURITY_DESCRIPTOR | Per-file ACL. Mostly replaced by $Secure shared pool in 3.0+. |
| 0x60 | $VOLUME_NAME | Only on record 3 ($Volume) — the volume label. Persisted as data_type='VolumeName'. |
| 0x70 | $VOLUME_INFORMATION | NTFS version + dirty bit + flags. Persisted as data_type='VolumeInfo'. |
| 0x80 | $DATA | The file content. Unnamed = default stream; named = ADS (now correctly distinguished). |
| 0x90 | $INDEX_ROOT | Root B+tree node for directories. Persisted as data_type='IndexRoot'. |
| 0xA0 | $INDEX_ALLOCATION | Non-resident B+tree leaf clusters for large directories. |
| 0xB0 | $BITMAP | Allocation bitmap for indexes / the MFT itself. |
| 0xC0 | $REPARSE_POINT | Symlinks, mount points, dedup, AppExecLinks. Tag + target path extracted. data_type='Reparse:<Type>'. |
| 0xD0 | $EA_INFORMATION | Length info for OS/2-style extended attributes. data_type='EaInfo'. |
| 0xE0 | $EA | OS/2 extended attribute data, walked entry-by-entry. data_type='EA'. |
| 0xF0 | $PROPERTY_SET | Obsolete; never used in modern Windows. |
| 0x100 | $LOGGED_UTILITY_STREAM | EFS ($EFS) and TxF ($TXF_DATA) streams. data_type='LoggedUtilStream'. |
mft_data_attributesRather than alter the database schema, the enhanced parser packs the newly-parsed NTFS attributes into the existing mft_data_attributes table using a typed data_type convention. Existing consumers that filter on data_type IN ('Default','ADS','Zone.Identifier',…) are unaffected; new analyses can pivot on the additional types below.
| data_type | attribute_name | size | Meaning |
|---|---|---|---|
| Default | '' (empty) | stream bytes | The file's default unnamed $DATA stream. |
| ADS | ADS stream name | stream bytes | Generic named $DATA (Alternate Data Stream). |
| Zone.Identifier | Zone.Identifier | stream bytes | Mark-of-the-Web ADS — proves origin URL of downloaded files. |
| Thumbnail / Encrypted | name | stream bytes | Other well-known ADS variants. |
| ObjectID | GUID {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} | 16+ | Decoded $OBJECT_ID primary GUID. |
| Reparse:Symlink | substitute path | tag (0xA000000C) | $REPARSE_POINT tag 0xA000000C — Windows symbolic link. |
| Reparse:MountPoint | substitute path | tag (0xA0000003) | NTFS junction or volume mount. |
| Reparse:AppExecLink | target exe path | tag (0x8000001B) | UWP execution alias (e.g. winget.exe in WindowsApps). |
| Reparse:Dedup | (empty) | tag (0x80000013) | Data Deduplication chunk-store reference. |
| VolumeName | volume label | bytes | From record 3 ($Volume). |
| VolumeInfo | NTFS 3.1 flags=0x... | flag bitmask | NTFS major.minor version + dirty bit. |
| EaInfo | count=N packed=X unpacked=Y | packed_ea_size | Summary of OS/2 extended attributes attached to the file. |
| EA | comma-joined EA names | ea count | Walked $EA entry list. Common: $KERNEL.PURGE.*. |
| IndexRoot | index stream name ($I30, $O, …) | attr size | Directory B+tree root or other index. Multiple rows per record. |
| LoggedUtilStream | stream name ($EFS, $TXF_DATA) | attr size | EFS-encrypted files or transactional NTFS streams. |
| Hardlinks | hardlinks=N | N | Only emitted when hardlink count > 1 — surfaces hard-linked DLLs. |
| ExtensionOf | base_rec=N | N | This record is an extension of base record N (via $ATTRIBUTE_LIST). |
The schema is the same 4-table layout introduced with the original parser (mft_records, mft_standard_info, mft_file_names, mft_data_attributes). Existing databases gain richer rows on next parse; no migration needed.
| Offset | Size | Field Name | Forensic Meaning & Value |
|---|
| Mask | Flag | Forensic Meaning |
|---|---|---|
| 0x0001 | Read-only | Common but trivially flippable; rarely indicative. |
| 0x0002 | Hidden | Often set on system / staged adversary payloads. |
| 0x0004 | System | Combined with Hidden = classic dropper hiding pattern. |
| 0x0020 | Archive | Set by writes; cleared by backup utilities. |
| 0x0040 | Device | Internal NTFS use. |
| 0x0080 | Normal | "No other attributes set" — mutually exclusive with everything. |
| 0x0100 | Temporary | Marked for deletion at close; suspicious if persisting. |
| 0x0200 | Sparse | Sparse file (only allocated where non-zero). |
| 0x0400 | Reparse Point | File has a $REPARSE_POINT attribute — symlink / mount / dedup. |
| 0x0800 | Compressed | NTFS LZ77 compression on the data stream. |
| 0x1000 | Offline | Stored remotely; HSM dehydration in progress. |
| 0x2000 | Not Indexed | Excluded from content indexing. |
| 0x4000 | Encrypted | EFS-encrypted data stream. |
A single file can have multiple $FILE_NAME attributes — one per namespace. Crow-Eye prefers the non-DOS name when picking the primary filename.
| Value | Namespace | Description |
|---|---|---|
| 0 | POSIX | Case-sensitive, allows any Unicode except / and NUL. |
| 1 | Win32 | Case-insensitive; the long filename most users see. |
| 2 | DOS (8.3) | Legacy short name (PROGRA~1 style). Auto-generated. |
| 3 | Win32 & DOS | Single name compatible with both spaces (e.g. readme.txt). |
Crow-Eye recognises these explicitly. The tag is the first 4 bytes of the reparse data.
| Tag | Type | Investigative Note |
|---|---|---|
| 0xA0000003 | Mount Point | Junction or volume mount — expands to a different MFT. |
| 0xA000000C | Symbolic Link | True Windows symlink (admin-only creation by default). |
| 0x80000013 | Dedup | Data Deduplication chunk store reference. |
| 0x8000001B | AppExecLink | UWP app execution alias (e.g. winget.exe in WindowsApps). |
| Bit | Meaning | Forensic Use |
|---|---|---|
| 0x0001 | IN_USE | Set when the record is allocated to a live file. Cleared on delete — the rest of the record persists. |
| 0x0002 | IS_DIRECTORY | Record describes a directory (uses $INDEX_ROOT / $INDEX_ALLOCATION). |
| 0x0004 | IS_EXTENSION | Record is a continuation of another record's attribute list. |
| 0x0008 | SPECIAL_INDEX | Special index present (rare; view-index style). |
How sophisticated adversaries try to defeat MFT-based evidence — and how an investigator catches each play:
| Technique | Indicator in the MFT | Counter-detection |
|---|---|---|
| Timestomping ($SI only) | The four $STANDARD_INFORMATION timestamps are rewritten to backdate a dropped binary, but the four $FILE_NAME timestamps still reflect creation reality. |
Cross-check $SI vs $FN. The kernel updates $FN only on rename/move — if $SI says "2019" but $FN says "yesterday", the file was timestomped. Crow-Eye exposes both sets in mft_standard_info and mft_file_names. |
| Alternate Data Stream payloads | A binary blob hidden in a named $DATA stream on an innocent-looking host file (notes.txt:evil.exe). Default browsing tools never show ADS. |
Crow-Eye flags every record with has_ads = 1 and counts ADS in ads_count. The named streams are itemised in mft_data_attributes with data_type IN ('ADS','Zone.Identifier','ObjectID','Thumbnail'). |
| FILE record zeroing | An adversary wipes the entire 1024-byte record after deleting the file, removing the in-use flag and the metadata. | Hard to defeat directly — but the parent directory's $INDEX_ROOT / $INDEX_ALLOCATION may still hold the filename. $MFTMirr (record 1) preserves the first 4 records even if $MFT is tampered with. |
| Slack-space residue | The allocated MFT clusters are larger than the logical $MFT size — the trailing slack contains old FILE records from previous allocations. |
Crow-Eye's scan_slack_space_records() walks past the logical EOF and recovers every valid FILE-signature record in slack. These are flagged separately and can reveal deleted-then-overwritten activity. |
| Extension-record fragmentation | Heavy ADS abuse or huge directories force the file's attributes to spill into extension records linked by $ATTRIBUTE_LIST. A naive parser sees only the base record and misses half the evidence. |
Crow-Eye's AttributeListParser reads every entry of $ATTRIBUTE_LIST, lists the extension record numbers, and records them in MFTRecord.extension_records so the analyst can follow the chain. |
| Sequence-number reuse | An adversary deletes a file, then creates one in the same MFT slot — references to the old file (in $FILE_NAME parent pointers, USN journal, prefetch) now resolve to a totally different binary. |
The 16-bit sequence number in every MFT reference increments on reuse. Compare the sequence number in the reference against the live record's sequence number — a mismatch proves slot reuse. |
| USA / fixup forgery | Attempts to forge a FILE record on disk fail to update the Update Sequence Array correctly — the 2-byte sector signatures at offsets 0x1FE, 0x3FE don't match the USN at the top of the USA. |
Validate the USA: the last 2 bytes of every 512-byte sector inside the record should equal the first 2 bytes of the USA (the USN). Mismatches are evidence of crafted records or torn writes. |
Crow-Eye reads $MFT records across the volume — comparing $STANDARD_INFORMATION vs $FILE_NAME timestamps to flag timestomping, and recovering resident data, alternate data streams, and parent-child paths.
Download Crow-Eye