MFTECmd and MFT Explorer · NTFS metadata reference
bub.im · cheatsheet
MFTECmd and MFT Explorer · NTFS metadata reference
Eric Zimmerman’s pair for the NTFS Master File Table: MFTECmd turns $MFT, $J, $LogFile, $Boot, $SDS and $I30 into CSV, JSON or bodyfile; MFT Explorer opens a raw $MFT as a browsable tree. Enough to snapshot a volume, find what was deleted, and know what to recover.
Setup
Both tools are free, portable zips, no installer. Pick the build that matches the .NET runtime on the box, or ship the runtime with the tool on a locked-down machine.
| Item | Where | Notes |
|---|---|---|
| MFTECmd | ericzimmerman.github.io, net6 and net9 folders | Console parser. Single MFTECmd.exe plus a Maps folder is all you need. Runs on Linux and macOS through dotnet MFTECmd.dll. |
| MFT Explorer | same page, GUI section | WinForms GUI, Windows only. Needs the .NET desktop runtime that matches the zip.gui |
| Timeline Explorer | same page | CSV viewer built for the output of every EZ tool: column filters, grouping, conditional colouring. The fastest way to read a large MFTECmd CSV.companion |
| Get-ZimmermanTools.ps1 | same page | PowerShell fetcher that downloads or updates the whole suite into one folder. Use -NetVersion 9 to pick the build. |
| Runtime | .NET 6 or .NET 9 desktop runtime | MFTECmd works with the console runtime; MFT Explorer and Timeline Explorer need the desktop runtime of the same major version. |
| Privileges | Administrator | Required to read C:\$MFT, $J or volume shadow copies directly from a live system. Parsing a copied $MFT file needs nothing special. |
# fetch the suite into C:\Tools\EZ (net9 builds), re-run later to update PS> Set-ExecutionPolicy -Scope Process Bypass PS> .\Get-ZimmermanTools.ps1 -Dest C:\Tools\EZ -NetVersion 9 # direct links, if you only want the pair # https://download.ericzimmermanstools.com/net9/MFTECmd.zip # https://download.ericzimmermanstools.com/net9/MFTExplorer.zip # verify the parser runs and see its help PS> C:\Tools\EZ\net9\MFTECmd.exe
Inputs MFTECmd understands
The file type is detected from content, not from the name. Each artifact answers a different question.
| Artifact | Live path | What it tells you |
|---|---|---|
| $MFT | C:\$MFT | One record per file and directory on the volume, including records of deleted files until they are reused. Names, parent paths, sizes, eight timestamps, flags. The snapshot. |
| $J | C:\$Extend\$UsnJrnl:$J | USN change journal: a log of create, rename, overwrite and delete events with timestamps and reason flags. Survives MFT record reuse, so it shows what happened and when. |
| $LogFile | C:\$LogFile | NTFS transaction log. Low-level, short retention (hours to a few days on a busy volume). Useful for very recent changes. |
| $Boot | C:\$Boot | Volume geometry: cluster size, MFT start cluster, volume serial. Needed to turn data runs into byte offsets. |
| $SDS | C:\$Secure:$SDS | Security descriptors. Resolve the SecurityId column of the $MFT CSV to owners and ACLs. |
| $I30 | <dir>:$I30:$INDEX_ALLOCATION | Per-directory index. Slack entries keep names of files deleted from that directory even after the MFT record is reused.since 1.0 |
| Shadow copies | --vss on a live drive | Processes the same artifact from every volume shadow copy as well, giving older snapshots for free. |
Switches
Single-letter options take one dash, long options take two. An output option (--csv, --json or --body) is required unless you are dumping a single entry.
| Switch | Takes | Effect |
|---|---|---|
| -f | file | Artifact to process. Required. Live paths such as C:\$MFT work because the tool reads locked files directly. |
| -m | $MFT file | Companion $MFT when -f points to a $J. Resolves parent paths in the journal output instead of leaving them as entry numbers.use it |
| –csv | directory | Write CSV results here. The file name is timestamped unless --csvf is given. |
| –csvf | file name | Override the default CSV name. |
| –json, –jsonf | directory, file name | Same as the CSV pair, JSON lines output. |
| –body, –bodyf | directory, file name | Bodyfile format for mactime or Plaso. Requires --bdl. |
| –bdl | drive letter | Drive letter to prefix bodyfile paths with. Letter only, no colon. |
| –blf | flag | Use LF line endings in the bodyfile instead of CRLF. |
| –de | entry or entry-seq | Dump every attribute of one record to the console. Decimal or hex, for example 5, 624-5, 0x270-0x5. No output switch needed. |
| –fls | flag | With --de on a directory record, list the directory contents instead. |
| –ds | security id | Dump one security descriptor from $SDS. |
| –dd, –do | directory, offset | Export the raw 1024-byte FILE record found at --do into --dd. Get offsets from --de. |
| –dt | format string | Timestamp format. Default yyyy-MM-dd HH:mm:ss.fffffff. Output is always UTC. |
| –sn | flag | Include DOS 8.3 file name attributes as rows. Off by default, since they duplicate every long name. |
| –fl | flag | Condensed file listing: fewer columns, one row per file. Needs --csv. The right shape for a snapshot you will diff later.snapshot |
| –at | flag | Always emit the 0x30 ($FILE_NAME) timestamps, not only when they differ from 0x10. |
| –vss | flag | Process the artifact in every volume shadow copy of the drive given in -f as well. One output per copy. |
| –dedupe | flag | Skip shadow copy files identical to the live one, by SHA-1. Pair with --vss. |
| –debug, –trace | flag | Progressively noisier logging when a parse fails. |
Recipes
All from an elevated prompt. Write output to a different drive than the one you are examining when deleted data matters.
# full $MFT parse from the live system drive PS> MFTECmd.exe -f C:\$MFT --csv D:\mft --csvf C_mft.csv # condensed listing, the shape to keep as a snapshot PS> MFTECmd.exe -f C:\$MFT --csv D:\mft --csvf C_mft_list.csv --fl # USN journal with paths resolved through the $MFT PS> MFTECmd.exe -f 'C:\$Extend\$UsnJrnl:$J' -m C:\$MFT --csv D:\mft --csvf C_usn.csv # the same, plus every volume shadow copy, skipping duplicates PS> MFTECmd.exe -f C:\$MFT --csv D:\mft --vss --dedupe # one record in full: attributes, timestamps, data runs, resident data PS> MFTECmd.exe -f C:\$MFT --de 51234 PS> MFTECmd.exe -f C:\$MFT --de 51234-7 # list the contents of a directory record PS> MFTECmd.exe -f C:\$MFT --de 5 --fls # bodyfile for a timeline in mactime or Plaso PS> MFTECmd.exe -f C:\$MFT --body D:\mft --bdl C --blf # names of files deleted from one directory, from its index slack PS> MFTECmd.exe -f 'C:\Users\LP\Downloads:$I30:$INDEX_ALLOCATION' --csv D:\mft # owners and ACLs PS> MFTECmd.exe -f 'C:\$Secure:$SDS' --csv D:\mft # volume geometry (cluster size, MFT start) PS> MFTECmd.exe -f C:\$Boot --csv D:\mft
$UsnJrnl:$J, $Secure:$SDS, :$I30:$INDEX_ALLOCATION) go in single quotes in PowerShell so that $J is not expanded as a variable. C:\$MFT works unquoted only because $MFT is an empty variable; quote it too if in doubt.$MFT CSV columns
One row per $FILE_NAME attribute, so a file with a long and a short name can appear twice unless --sn is off (the default). Paths are relative to the volume root and start with .\.
| Column | Meaning |
|---|---|
| EntryNumber, SequenceNumber | Record index in the MFT and how many times that slot has been reused. Together they form the file reference. Entry 5 is the volume root. |
| InUse | True for a live file, False for a record of a deleted file that has not been reused yet. This column is the deleted-file filter.key |
| ParentEntryNumber, ParentSequenceNumber | Reference of the containing directory. If the parent sequence no longer matches the live parent record, the path shown is reconstructed and may be stale. |
| ParentPath, FileName, Extension | Directory, name and lower-cased extension. Join them to get the full path. |
| FileSize | Logical size in bytes of the unnamed $DATA stream. Zero for directories. |
| ReferenceCount | Number of hard links. |
| ReparseTarget | Target of a symlink, junction or other reparse point. |
| IsDirectory, HasAds, IsAds | Directory flag, and whether the record owns or is an alternate data stream. ADS rows carry name:stream in FileName. |
| SI<FN | True when a $STANDARD_INFORMATION time is earlier than the matching $FILE_NAME time, a classic timestomping hint. |
| uSecZeros | True when a timestamp has all sub-second digits at zero, another timestomping hint. |
| Copied | True when the created time is later than the modified time, the pattern a copy operation leaves behind. |
| SiFlags | File attribute flags from $STANDARD_INFORMATION: Archive, Hidden, System, Compressed, Encrypted, Sparse, ReparsePoint and so on. |
| NameType | Posix, Windows, Dos, or DosWindows (one attribute serving both). |
| Created0x10, LastModified0x10, LastRecordChange0x10, LastAccess0x10 | The four $STANDARD_INFORMATION timestamps, the ones Explorer shows and user-mode APIs can change. |
| Created0x30, LastModified0x30, LastRecordChange0x30, LastAccess0x30 | The four $FILE_NAME timestamps, kernel maintained and much harder to forge. Empty unless they differ from 0x10 or --at is set. |
| UpdateSequenceNumber | Last USN that touched the record. Lets you jump from an $MFT row to the matching $J events. |
| LogfileSequenceNumber | Last $LogFile LSN for the record. |
| SecurityId | Index into $SDS. Resolve with --ds or a $SDS parse. |
| ObjectIdFileDroid, LoggedUtilStream, ZoneIdContents | Object id GUID, logged utility stream (EFS marker), and the content of the Zone.Identifier ADS, which carries the download URL for files saved by browsers. |
$J CSV columns and reasons
One row per journal event. The same file produces many rows as its reason flags accumulate; the row carrying Close is the final state of that burst.
| Column | Meaning |
|---|---|
| Name, Extension | File name at the time of the event. A rename produces an old-name row and a new-name row. |
| EntryNumber, SequenceNumber | MFT reference of the file. Join these to the $MFT CSV to confirm whether the record is still live. |
| ParentEntryNumber, ParentSequenceNumber, ParentPath | Containing directory. ParentPath is only filled when -m pointed at the matching $MFT. |
| UpdateSequenceNumber | The USN, a byte offset into the journal, strictly increasing. Sort on this for true event order. |
| UpdateTimestamp | When the event was recorded, UTC. |
| UpdateReasons | Pipe-separated reason flags, see the next table. |
| FileAttributes | Attribute flags at event time: Archive, Directory, Hidden and so on. |
| OffsetToData, SourceFile | Where in the journal the record sits and which input file it came from (relevant with --vss). |
| Reason | Read it as |
|---|---|
| FileCreate | New file or directory. |
| FileDelete | Deleted. With Close in the same row, the delete completed.loss marker |
| RenameOldName, RenameNewName | The two halves of a rename or a move within the volume. Recycle Bin deletes show as a rename into $Recycle.Bin as $R.... |
| DataOverwrite, DataExtend, DataTruncation | Content changed in place, grew, or was cut. Truncation followed by Overwrite is the typical save pattern. |
| BasicInfoChange | Attributes or timestamps changed. The reason behind explicit timestamp edits. |
| SecurityChange | ACL or owner changed. |
| HardLinkChange, ReparsePointChange, StreamChange, NamedDataOverwrite | Links, reparse data, or an alternate data stream changed. |
| Close | Handle closed. Marks the end of the burst of flags for one operation. |
Reading the signals
| Question | Look at | Interpretation |
|---|---|---|
| Was it deleted? | InUse = False in $MFT, or FileDelete in $J | $MFT shows what is still recoverable by name; $J shows every delete even when the record is gone. |
| Can the content still be read? | --de output: resident data or data runs | Resident $DATA (roughly under 700 bytes) is inside the record and comes out with --de. Non-resident data lives in clusters listed as data runs; on an SSD with TRIM those are usually zeroed within minutes.ssd |
| Recycle Bin or hard delete? | $J rename into $Recycle.Bin versus plain FileDelete | Explorer deletes rename into the bin and create an $I metadata file. Storage Sense, Shift + Del, scripts and services delete outright. |
| When did it disappear? | UpdateTimestamp on the delete row | $MFT keeps no deletion time. Only the journal has it. |
| Where did it come from? | ZoneIdContents | HostUrl and ReferrerUrl of a downloaded file. Enough to download it again. |
| Timestomped? | SI<FN, uSecZeros, 0x30 columns | Tools that edit timestamps change $STANDARD_INFORMATION only; $FILE_NAME keeps the truth. |
| Copied or moved? | Copied, and ParentPath change in $J | A copy gets a new Created time later than Modified. A move keeps the record and only rewrites the parent. |
| Who owned it? | SecurityId + $SDS parse | Join on the id to get owner SID and DACL. |
MFT Explorer
The GUI reads a raw $MFT file (not a CSV) and renders the volume as a tree with deleted entries included, plus a detail pane for every record. It uses the same parsing library as MFTECmd.
| Task | How |
|---|---|
| Open a dump | File, Load MFT, pick a copied $MFT. Loading rebuilds the whole tree in memory; a system drive with a million records takes minutes and well over a gigabyte of RAM.slow |
| Browse the tree | Left pane is the directory tree starting at the root (entry 5). Expand a folder to list files; the grid shows name, size, entry and sequence numbers, flags and timestamps. |
| Spot deleted items | Deleted records stay in the tree under their last known parent and are marked (strike-through or a flag column, depending on version). Filter the grid on InUse to isolate them. |
| Inspect a record | Select a row and the lower pane lists every attribute: $STANDARD_INFORMATION, each $FILE_NAME, $DATA with resident content or data runs, $INDEX_ROOT, security id, object id. |
| See the raw bytes | Hex view of the 1024-byte FILE record with the selected attribute highlighted. Useful to confirm what --de prints. |
| Search | Grid column filters (type into the filter row) and a find box for names. Wildcards work in filters; searching the entire tree is a scan, so be patient. |
| Export | Grid rows export to CSV or Excel from the context menu. For a full-volume export MFTECmd is faster. |
| Timestamps | Displayed in UTC by default, with an option to switch the display time zone. The CSV from MFTECmd stays UTC regardless. |
Snapshot and diff workflow
An inventory of the file system taken on a schedule, so that after a loss you know exactly what to look for. Keep the output off the volume it describes.
# Snapshot-Mft.ps1: condensed $MFT listing, dated, on another drive $stamp = Get-Date -Format 'yyyy-MM-dd' $out = 'D:\Snapshots' New-Item -ItemType Directory -Force $out | Out-Null & 'C:\Tools\EZ\net9\MFTECmd.exe' -f 'C:\$MFT' --csv $out --csvf "C_$stamp.csv" --fl # register it weekly as SYSTEM (run once, elevated) PS> $a = New-ScheduledTaskAction -Execute powershell.exe -Argument '-NoProfile -ExecutionPolicy Bypass -File C:\Tools\Snapshot-Mft.ps1' PS> $t = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Sunday -At 03:00 PS> Register-ScheduledTask -TaskName 'MFT snapshot' -Action $a -Trigger $t -User SYSTEM -RunLevel Highest
# Diff-Mft.ps1: files present in the older snapshot and missing from the newer one param([string]$Old, [string]$New, [string]$Scope = '.\Users\LP\Downloads') $key = { "$($_.ParentPath)\$($_.FileName)" } $a = Import-Csv $Old | ? { $_.InUse -eq 'True' -and $_.ParentPath -like "$Scope*" } | % $key $b = Import-Csv $New | ? { $_.InUse -eq 'True' -and $_.ParentPath -like "$Scope*" } | % $key Compare-Object $a $b | ? SideIndicator -eq '<=' | Select -Expand InputObject # usage PS> .\Diff-Mft.ps1 -Old D:\Snapshots\C_2026-09-28.csv -New D:\Snapshots\C_2026-10-05.csv # after a loss: what the current MFT still remembers as deleted under Downloads PS> MFTECmd.exe -f 'C:\$MFT' --csv D:\mft --csvf now.csv PS> Import-Csv D:\mft\now.csv | ? { $_.InUse -eq 'False' -and $_.ParentPath -like '.\Users\LP\Downloads*' } | Select ParentPath, FileName, FileSize, LastModified0x10 | Sort FileName # and when it was deleted, from the journal PS> MFTECmd.exe -f 'C:\$Extend\$UsnJrnl:$J' -m 'C:\$MFT' --csv D:\mft --csvf usn.csv PS> Import-Csv D:\mft\usn.csv | ? { $_.UpdateReasons -match 'FileDelete' -and $_.ParentPath -like '.\Users\LP\Downloads*' } | Select UpdateTimestamp, Name, ParentPath | Sort UpdateTimestamp
| Layer | Tool | Role in the plan |
|---|---|---|
| Inventory | MFTECmd --fl on a schedule | Knows every path and size as of each snapshot. Cheap, seconds per run. |
| History | $J parse at loss time | Tells you when and how each file vanished, and whether it went through the Recycle Bin. |
| Candidates | $MFT parse at loss time, InUse = False | Names the records still intact enough for an undelete tool to target by name. |
| Content | File History, OneDrive, shadow copies | The only layers that actually hold bytes. The metadata layers above tell you what to ask them for. |
Gotchas
| Where | Behaviour |
|---|---|
| Record reuse | A deleted record survives only until NTFS hands its slot to a new file. On a busy system drive that can be hours. Dump the $MFT first, think later. |
| Journal size | $J is a ring buffer, typically 32 MB on a system volume, which covers days to a few weeks. fsutil usn queryjournal C: shows the current size; fsutil usn createjournal m=0x8000000 a=0x800000 C: raises it to 128 MB. |
| Sparse $J | The journal file is sparse, and a copied one can look huge on disk. MFTECmd skips the empty region, so parse time is driven by the live part only. |
| Paths | Output paths start with .\ and never carry a drive letter. Add it yourself when joining against other data, or use the bodyfile with --bdl. |
| Timestamps | Everything is UTC. Convert at the display layer (Timeline Explorer can) rather than in the data. |
| Two rows per file | A long name plus a DOS name produces two $FILE_NAME attributes. With --sn on you will see both; dedupe on EntryNumber and SequenceNumber. |
| Orphans | A deleted file whose parent directory was also deleted and reused shows a reconstructed or PathUnknown parent. $I30 slack of the former parent, if still present, may name it correctly. |
| Defender and EDR | Reading $MFT and $J directly is a common forensic action and some EDR products alert on it. On a managed machine, run it with the service desk in the loop. |
| BitLocker | Parse from inside the booted system or from an unlocked volume. A raw image of a locked volume is ciphertext. |
| MFT Explorer memory | Loading a multi-gigabyte $MFT can exhaust RAM on a laptop. Use MFTECmd for the volume and MFT Explorer for a smaller volume or for drilling into specific records. |
0 comments