Skip to content

NTFS $LogFile Redo/Undo Operations Explained

The NTFS log record header byte by byte, the 38 redo/undo opcodes, which ones matter in forensics, and how create, delete, rename and timestamp changes look.

Published on 8 min read

TL;DR. An NTFS log record is an LFS header (LSN, previous LSN, transaction ID…) followed by an NTFS header: redo opcode, undo opcode, where the redo and undo bytes are, which open attribute they target, and where inside it. Thirty-eight opcodes exist (0x00–0x25), but a handful carry most of the forensic value: InitializeFileRecordSegment, DeallocateFileRecordSegment, CreateAttribute / DeleteAttribute, UpdateResidentValue, and the four Add/DeleteIndexEntry* operations. Everything else is bookkeeping you still need to resolve targets and group records.

If $LogFile feels opaque, it is because a single user action — "rename this file" — becomes several records, each of which only makes sense with the right context: which attribute is open under which index, which cluster size the volume uses, what the FILE record looked like before. This article is the decoder ring. The broader picture is in the complete $LogFile guide.

Two headers per record

Every log record starts with the LFS record header, followed by the client data. For the NTFS client, the client data starts with its own header. Offsets from the Linux ntfs3 fslog.c:

LFS record header (0x30 bytes)

OffsetSizeField
0x008This LSN
0x088Client previous LSN (same transaction)
0x108Client undo-next LSN
0x184Client data length
0x1C4Client ID
0x204Record type: 1 = client record, 2 = client restart
0x244Transaction ID
0x282Flags (0x1 = record continues on the next page)

NTFS log record header (start of client data)

OffsetSizeFieldUse
0x002Redo operationWhat to do
0x022Undo operationHow to reverse it
0x04 / 0x062 + 2Redo offset / lengthWhere the redo bytes are
0x08 / 0x0A2 + 2Undo offset / lengthWhere the undo bytes are
0x0C2Target attributeIndex into the open attribute table
0x0E2LCNs to followNumber of cluster numbers at 0x20
0x102Record offsetOffset of the attribute inside the FILE record (or of the entry in an index buffer)
0x122Attribute offsetOffset of the change inside that attribute
0x142Cluster block offsetIn 512-byte units, inside the target cluster
0x188Target VCNVirtual cluster in the target attribute
0x208 × nTarget LCNsPhysical clusters

Record type 2 records are checkpoints (client restart areas), not changes. The restart area and LSN article covers them.

Finding the MFT entry a record touches

Records do not say "MFT entry 322". They say "attribute #N in the open attribute table, VCN v, cluster block offset c". When the target attribute is $MFT:$DATA, the entry is:

entry = (VCN × cluster_size + cluster_block_offset × 512) / mft_record_size

Two volume parameters are needed. A parser can take them from the boot sector or, as the in-browser $LogFile parser does, calibrate them from the log itself: InitializeFileRecordSegment records contain a full FILE record image whose header states its own record number, so the combination of cluster size and record size that maps most of them correctly wins.

The open attribute table is rebuilt from OpenNonresidentAttribute records and from the OpenAttributeTableDump and AttributeNamesDump written at each checkpoint. Its entry layout differs between NTFS client versions (0x28 vs 0x2C bytes), which is one of the places parsers can disagree.

The 38 operations, grouped

The names below are those of ntfs3's enum NTFS_LOG_OPERATION, which the parser also uses.

CodeOperationGroupForensic value
0x00Noop—Undo side of many redo-only records
0x01CompensationLogRecordTransactionWritten during rollback
0x02InitializeFileRecordSegmentFILE recordHigh: full FILE record image of a new or reused entry
0x03DeallocateFileRecordSegmentFILE recordHigh: entry freed (deletion)
0x04WriteEndOfFileRecordSegmentFILE recordLow
0x05CreateAttributeAttributeHigh: new attribute, e.g. $FILE_NAME or resident $DATA
0x06DeleteAttributeAttributeHigh: attribute removed, e.g. old $FILE_NAME on rename
0x07UpdateResidentValueAttributeHigh: $STANDARD_INFORMATION times, small resident values
0x08UpdateNonresidentValueAttributeMedium: index buffers, $UsnJrnl writes, other streams
0x09UpdateMappingPairsAttributeMedium: data runs (file growth)
0x0ADeleteDirtyClustersAttributeLow
0x0BSetNewAttributeSizesAttributeMedium: size changes
0x0C / 0x0DAdd / DeleteIndexEntryRootIndexHigh: directory entries in $INDEX_ROOT
0x0E / 0x0FAdd / DeleteIndexEntryAllocationIndexHigh: directory entries in $INDEX_ALLOCATION
0x10WriteEndOfIndexBufferIndexLow
0x11 / 0x12SetIndexEntryVcnRoot / AllocationIndexLow (B-tree plumbing)
0x13 / 0x14UpdateFileNameRoot / AllocationIndexMedium: duplicated $FILE_NAME info in $I30
0x15 / 0x16Set / ClearBitsInNonresidentBitMapBitmapLow alone: allocation of clusters or MFT entries
0x17HotFix—Rare
0x18EndTopLevelActionTransactionStructure
0x19–0x1BPrepare / Commit / ForgetTransactionTransactionTransaction boundaries
0x1COpenNonresidentAttributeTableNeeded to resolve targets
0x1D–0x20OpenAttributeTable / AttributeNames / DirtyPageTable / TransactionTable dumpsCheckpointNeeded to resolve targets
0x21 / 0x22UpdateRecordDataRoot / AllocationIndexMedium: non-$I30 indexes ($ObjId, $Quota, $Secure)
0x23 / 0x24UpdateRelativeDataInIndex / 2IndexLow
0x25ZeroEndOfFileRecordFILE recordLow

Undo is usually the inverse of redo: AddIndexEntryRoot is undone by DeleteIndexEntryRoot, CreateAttribute by DeleteAttribute, SetBits… by ClearBits…, and UpdateResidentValue by another UpdateResidentValue carrying the old bytes. Operations whose rollback needs nothing, such as initialising a fresh FILE record, often have Noop as undo.

What common actions look like

These are the patterns a parser looks for. Real sequences contain more records (bitmaps, sizes, security, USN writes) and the exact order can vary by Windows version.

File creation

  • InitializeFileRecordSegment on the new entry. Redo = complete FILE record image: header (sequence number, flags), $STANDARD_INFORMATION, $FILE_NAME (name, parent reference, four times), possibly a small $DATA.
  • AddIndexEntryRoot or AddIndexEntryAllocation in the parent directory's $I30 index. The index entry embeds a $FILE_NAME copy.

Deletion

  • DeleteIndexEntryRoot / DeleteIndexEntryAllocation removing the name from the parent (the removed entry is in the undo bytes, sometimes in redo).
  • DeallocateFileRecordSegment on the entry, with ClearBitsInNonresidentBitMap on the MFT bitmap.

Rename or move

  • dfir.ru documents a rename as DeleteIndexEntryRoot, DeleteAttribute (old $FILE_NAME), CreateAttribute (new $FILE_NAME), AddIndexEntryRoot (How the $LogFile works?). A move is the same with a different parent in the added entry. For large directories the …Allocation variants appear instead of …Root.

Timestamp change

  • UpdateResidentValue targeting the $STANDARD_INFORMATION attribute (the first attribute of a FILE record, at offset 0x38 in NTFS 3.1 records). Redo holds the new bytes, undo the old ones. The update can be partial — dfir.ru notes an update may start at the M timestamp — so a parser must use the attribute offset to know which of the four times it is reading. Timestomping detection builds on this.

Small file content

  • A resident $DATA attribute created with CreateAttribute or included in the FILE record image can carry the first bytes of a small file. Later edits are a different story: LogFileParser's documentation states that on modern Windows the content of resident data changes is generally not stored, only the fact that a change happened. See recovering deleted-file evidence.

Grouping records into actions

Two approaches:

  1. By transaction. Use the transaction ID and previous-LSN chain, bounded by ForgetTransaction or commit records. Most faithful, harder to get right across checkpoints.
  2. By MFT entry and proximity. Take records that touch the same entry within a window of LSNs. Simpler and robust to damaged pages, but two unrelated actions on the same file close together can merge.

The in-browser parser currently uses the second approach (a ±256-record window per MFT entry) and exposes every raw record so you can check the grouping. Transaction-based grouping is on its roadmap; LogFileParser and NTFS Log Tracker should be used to confirm.

Reading the bytes yourself

When an event matters, open the raw record: check the redo/undo opcodes, target attribute (for example #322:$STANDARD_INFORMATION or $MFT:$DATA), record offset, attribute offset and the hex. A 32-byte undo at attribute offset 0 of $STANDARD_INFORMATION holds four little-endian FILETIME values: created, modified, MFT changed, accessed.

Frequently asked questions

What are redo and undo operations in the $LogFile?

Each NTFS log record describes one metadata change twice: the redo operation and bytes say how to apply the change again, the undo operation and bytes say how to reverse it. Recovery replays redo for committed transactions and applies undo for uncommitted ones.

Which $LogFile operations show a file being created or deleted?

A creation usually shows InitializeFileRecordSegment on the new FILE record plus AddIndexEntryRoot or AddIndexEntryAllocation in the parent directory. A deletion shows DeleteIndexEntryRoot or DeleteIndexEntryAllocation followed by DeallocateFileRecordSegment.

Further reading

Related articles