How to Analyze an NTFS $LogFile, Step by Step
A practical walkthrough: load $LogFile and $MFT in a browser parser, read the log header, triage flagged events, check raw redo/undo bytes and export results.
TL;DR. Collect $LogFile and the $MFT of the same volume. Drop both into the in-browser $LogFile parser. Read the header line first (version, clean or dirty, LSN range, warnings). Then triage the events view with Flagged only, open each finding's detail panel to see where its time and path come from, jump to the raw records to check the redo/undo bytes, and export to CSV/JSON. The flags are heuristics; the raw records are the evidence. Confirm anything that goes into a report with a second tool.
This walkthrough uses the parser's built-in sample: a synthetic LFS 2.0 log and $MFT from a fictional intrusion on a workstation called FIN-WKS-07. Click Try a sample on the tool page to follow along. All names, times and content in the sample are invented.
Step 1 — Collect the two files together
The parser can read a $LogFile alone, but paths will be partial. Log records name files by MFT entry; the name only appears in the log when the file is created, renamed or deleted. The $MFT fills in everything else.
Without the $MFT, the sample's tools.zip resolves to [MFT #61]\svc_backup\Downloads\tools.zip (the Users folder is not named anywhere in the surviving log). With it, the path is \Users\svc_backup\Downloads\tools.zip.
Collection commands for KAPE, Velociraptor, FTK Imager, RawCopy and icat are in how to acquire the NTFS $LogFile.
Step 2 — Load the files
Drop the files, choose a folder, or drop a ZIP triage collection (KAPE and Velociraptor layouts work as-is). Each $LogFile is paired with the $MFT found in the same folder, so keep one folder per volume.
What happens next stays in the browser tab: the parser is Rust compiled to WebAssembly, running in a Web Worker. The $MFT is streamed in 16 MiB chunks into a name index, so multi-gigabyte MFTs work without loading the whole file into memory. There is no upload endpoint.
Files the parser cannot use are listed with a reason rather than dropped silently: a copy that starts with zeros (probably locked when copied), a $UsnJrnl:$J (not parsed by this tool — use the USN journal parser), or a $MFT with no $LogFile next to it.
Step 3 — Read the header line before the events
For the sample, the header line reads, in substance:
| Field | Sample value | What it tells you |
|---|---|---|
| Log version | LFS 2.0 | Windows 8 or later, volume mounted at capture (1.1 vs 2.0) |
| Size | 256 KiB | Unusually small; real system volumes are usually around 64 MiB |
| Record pages | 5 | How many circular pages hold records |
| LSN range | 115720 → 117880 | Oldest and newest record found (LSNs explained) |
| Dismount | not cleanly dismounted | Live capture or crash |
| Tail / fast pages | 1 page read from the fast-page area | The newest page existed only there — typical of a live capture |
Also read the parser warnings box. A truncated copy, pages that failed the update sequence check, or a CHKD restart page all change how much you can trust the result.
Step 4 — Triage the events view
The summary tiles give counts per type. On the sample: 14 creations, 2 deletions, 2 renames/moves, 6 $STANDARD_INFORMATION changes, 2 resident data writes, and 1 timestomping hint.
Then narrow down:
- Event type: Created, Deleted, Renamed / moved, Timestamps changed (
$SI), Resident data written. - Flagged only: keeps events with at least one flag.
- Search: path, name, content, LSN or date.
The five flags and what triggers them:
| Flag | Trigger |
|---|---|
| Deletion | Event is a delete |
| Rename / move | Event is a rename or move |
| Timestamp change (possible timestomping) | Created time rewritten, a value moved backwards, a whole-second value, or $SI created earlier than $FN created |
| User-writable folder | Path under a user profile's AppData/Downloads/Desktop/Documents, Users\Public, ProgramData, Windows\Temp, $Recycle.Bin or PerfLogs |
| Executable | Name ends in .exe, .dll, .ps1, .bat, .js, .hta and similar |
On the sample, Flagged only plus a sort by LSN surfaces the story in a dozen rows: m64.exe created in Downloads\tools, moved to \ProgramData\Intel, its timestamps rewritten; rc.tmp dropped in \Users\Public and renamed rclone.exe; creds.txt created on the Desktop and deleted.
Step 5 — Open the detail panel
Clicking an event opens its details. Three fields deserve attention every time:
- Time from. NTFS logs no clock time. The parser dates each event from data inside the records —
$FILE_NAMEcreated (creations),$FILE_NAMEchanged (renames), the new$SIvalue (timestamp changes), or the last known time before a deletion. Events with none of these borrow the time of the nearest dated event and are marked ≈. The sample'sm64.exetimestamp change is one of them: its new values point to 2019, so the parser refuses to use them as the time of the change. - Path from. Names seen in the log, the
$MFT, partial (an ancestor unknown) or unknown. - Why it is flagged. For
m64.exe: the creation time was rewritten, a timestamp moved backwards, a whole-second value, and$SIcreated earlier than$FNcreated. The panel shows$SIbefore (2026-09-14 10:06:52.3551871) and after (2019-03-19 07:14:22.0000000).
For how to judge those reasons, see detecting timestomping with the $LogFile.
Step 6 — Verify against the raw records
Every event links to its log records (Show these log records). The records view lists each record with LSN, transaction ID, redo and undo operations, target attribute, and target MFT entry or file. Its detail panel adds previous LSN, undo-next LSN, record type, target VCN, the record / attribute / cluster-block offsets, file offset, whether the record spans pages, and hex dumps of the redo and undo bytes.
For the m64.exe timestamp change you should see an UpdateResidentValue / UpdateResidentValue pair on $STANDARD_INFORMATION, with the new values in the redo bytes and the old ones in the undo bytes. For a rename, a DeleteIndexEntry* followed by an AddIndexEntry* for the same entry. What each opcode means is covered in redo/undo operations explained.
This step is what makes a finding defensible. An event is the parser's interpretation; a record with its bytes and offsets is what you can show someone else and what a second tool should reproduce.
Step 7 — Export and correlate
Both views export to CSV (with formula-injection protection) or JSON. Times can be shown in UTC or local time, with 100 ns precision. Export what you filtered, not the whole log, and keep the LSNs: they are the stable key for citing a record.
Then correlate:
$UsnJrnl:$Jgives datedRENAME_*,FILE_CREATE,FILE_DELETEandBASIC_INFO_CHANGErecords (USN journal parser).- Prefetch shows whether
m64.exeorrclone.exeran (Prefetch parser); the sample even logs the creation ofM64.EXE-1C9E54B7.pf. - LNK files and Jump Lists show what was opened (LNK parser); the sample's
Payroll_2026.xlsx.lnkinRecentis a lead for that.
Step 8 — Confirm with a second tool
The parser has been validated against synthetic logs written from the published format (Linux ntfs3, libfsntfs, dfir.ru's description), not yet against a corpus of real Windows logs. Event grouping uses a window of records per MFT entry rather than transactions. For anything that ends up in a report, re-run the log through LogFileParser or NTFS Log Tracker and compare the records by LSN. See $LogFile parsers compared.
Common mistakes
- Parsing a
$LogFilewith an$MFTfrom another day: MFT entries get reused, names go wrong. - Reading "≈" times as facts. They are neighbours' times.
- Treating a timestomping flag as proof. A whole-second value can come from an archive extractor; a backwards move can come from a legitimate restore.
- Stopping at the events view. Some changes are not rebuilt as events; the raw records view shows everything the log still holds.