Skip to content

Redo- und Undo-Operationen im NTFS-$LogFile erklärt

Der NTFS-Log-Header Byte für Byte, die 38 Redo-/Undo-Opcodes, welche forensisch zählen und wie Anlegen, Löschen, Umbenennen und Zeitstempeländerungen aussehen.

Veröffentlicht am 8 Min. Lesezeit

TL;DR. Ein NTFS-Logeintrag besteht aus einem LFS-Header (LSN, vorherige LSN, Transaktions-ID…) und einem darauf folgenden NTFS-Header: Redo-Opcode, Undo-Opcode, wo die Redo- und Undo-Bytes liegen, auf welches geöffnete Attribut sie zielen und an welcher Stelle darin. Es gibt 38 Opcodes (0x00–0x25), aber eine Handvoll trägt den Großteil des forensischen Werts: InitializeFileRecordSegment, DeallocateFileRecordSegment, CreateAttribute / DeleteAttribute, UpdateResidentValue und die vier Operationen Add/DeleteIndexEntry*. Alles andere ist Verwaltungsarbeit, die Sie trotzdem brauchen, um Ziele aufzulösen und Einträge zu gruppieren.

Wenn Ihnen das $LogFile undurchschaubar vorkommt, liegt das daran, dass aus einer einzigen Benutzeraktion — „diese Datei umbenennen“ — mehrere Einträge werden, die jeweils nur mit dem richtigen Kontext Sinn ergeben: welches Attribut unter welchem Index geöffnet ist, welche Clustergröße das Volume nutzt, wie der FILE-Record vorher aussah. Dieser Artikel ist der Schlüssel zum Entziffern. Das Gesamtbild steht in NTFS-$LogFile-Forensik: der vollständige Leitfaden.

Zwei Header pro Eintrag

Jeder Logeintrag beginnt mit dem LFS-Eintragsheader, gefolgt von den Client-Daten. Beim NTFS-Client beginnen die Client-Daten mit einem eigenen Header. Offsets aus fslog.c von Linux ntfs3:

LFS-Eintragsheader (0x30 Bytes)

OffsetGrößeFeld
0x008Diese LSN
0x088Vorherige LSN des Clients (gleiche Transaktion)
0x108Undo-Next-LSN des Clients
0x184Länge der Client-Daten
0x1C4Client-ID
0x204Eintragstyp: 1 = Client-Eintrag, 2 = Client-Restart
0x244Transaktions-ID
0x282Flags (0x1 = Eintrag geht auf der nächsten Seite weiter)

NTFS-Log-Header (Beginn der Client-Daten)

OffsetGrößeFeldZweck
0x002Redo-OperationWas zu tun ist
0x022Undo-OperationWie es rückgängig gemacht wird
0x04 / 0x062 + 2Redo-Offset / -LängeWo die Redo-Bytes liegen
0x08 / 0x0A2 + 2Undo-Offset / -LängeWo die Undo-Bytes liegen
0x0C2ZielattributIndex in die Open Attribute Table
0x0E2Anzahl folgender LCNsAnzahl der Clusternummern ab 0x20
0x102Eintrags-OffsetOffset des Attributs im FILE-Record (oder des Eintrags in einem Indexpuffer)
0x122Attribut-OffsetOffset der Änderung innerhalb dieses Attributs
0x142Cluster-Block-OffsetIn Einheiten von 512 Bytes, innerhalb des Zielclusters
0x188Ziel-VCNVirtueller Cluster im Zielattribut
0x208 × nZiel-LCNsPhysische Cluster

Einträge vom Typ 2 sind Checkpoints (Client-Restart-Bereiche), keine Änderungen. Sie werden in Restart Area und LSN im $LogFile erklärt behandelt.

Den MFT-Eintrag finden, den ein Logeintrag betrifft

Einträge sagen nicht „MFT-Eintrag 322“. Sie sagen „Attribut #N in der Open Attribute Table, VCN v, Cluster-Block-Offset c“. Ist das Zielattribut $MFT:$DATA, ergibt sich der Eintrag so:

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

Dafür werden zwei Volume-Parameter benötigt. Ein Parser kann sie aus dem Bootsektor entnehmen oder, wie der $LogFile-Parser im Browser, aus dem Log selbst kalibrieren: Einträge vom Typ InitializeFileRecordSegment enthalten ein vollständiges Abbild eines FILE-Records, dessen Header die eigene Eintragsnummer angibt. Es gewinnt die Kombination aus Clustergröße und Eintragsgröße, die die meisten davon korrekt abbildet.

Die Open Attribute Table wird aus OpenNonresidentAttribute-Einträgen sowie aus den bei jedem Checkpoint geschriebenen OpenAttributeTableDump und AttributeNamesDump wiederaufgebaut. Das Layout ihrer Einträge unterscheidet sich zwischen den Versionen des NTFS-Clients (0x28 vs. 0x2C Bytes) — eine der Stellen, an denen Parser zu unterschiedlichen Ergebnissen kommen können.

Die 38 Operationen, gruppiert

Die Namen unten sind die aus enum NTFS_LOG_OPERATION von ntfs3, die auch der Parser verwendet.

CodeOperationGruppeForensischer Wert
0x00Noop—Undo-Seite vieler reiner Redo-Einträge
0x01CompensationLogRecordTransaktionWird beim Zurückrollen geschrieben
0x02InitializeFileRecordSegmentFILE-RecordHoch: vollständiges Abbild des FILE-Records eines neuen oder wiederverwendeten Eintrags
0x03DeallocateFileRecordSegmentFILE-RecordHoch: Eintrag freigegeben (Löschung)
0x04WriteEndOfFileRecordSegmentFILE-RecordGering
0x05CreateAttributeAttributHoch: neues Attribut, z. B. $FILE_NAME oder residentes $DATA
0x06DeleteAttributeAttributHoch: Attribut entfernt, z. B. altes $FILE_NAME bei Umbenennung
0x07UpdateResidentValueAttributHoch: Zeiten in $STANDARD_INFORMATION, kleine residente Werte
0x08UpdateNonresidentValueAttributMittel: Indexpuffer, Schreibvorgänge in $UsnJrnl, andere Streams
0x09UpdateMappingPairsAttributMittel: Data Runs (Datei wächst)
0x0ADeleteDirtyClustersAttributGering
0x0BSetNewAttributeSizesAttributMittel: Größenänderungen
0x0C / 0x0DAdd / DeleteIndexEntryRootIndexHoch: Verzeichniseinträge in $INDEX_ROOT
0x0E / 0x0FAdd / DeleteIndexEntryAllocationIndexHoch: Verzeichniseinträge in $INDEX_ALLOCATION
0x10WriteEndOfIndexBufferIndexGering
0x11 / 0x12SetIndexEntryVcnRoot / AllocationIndexGering (B-Baum-Mechanik)
0x13 / 0x14UpdateFileNameRoot / AllocationIndexMittel: duplizierte $FILE_NAME-Informationen in $I30
0x15 / 0x16Set / ClearBitsInNonresidentBitMapBitmapAllein gering: Belegung von Clustern oder MFT-Einträgen
0x17HotFix—Selten
0x18EndTopLevelActionTransaktionStruktur
0x19–0x1BPrepare / Commit / ForgetTransactionTransaktionTransaktionsgrenzen
0x1COpenNonresidentAttributeTabelleNötig, um Ziele aufzulösen
0x1D–0x20Dumps von OpenAttributeTable / AttributeNames / DirtyPageTable / TransactionTableCheckpointNötig, um Ziele aufzulösen
0x21 / 0x22UpdateRecordDataRoot / AllocationIndexMittel: Indizes außer $I30 ($ObjId, $Quota, $Secure)
0x23 / 0x24UpdateRelativeDataInIndex / 2IndexGering
0x25ZeroEndOfFileRecordFILE-RecordGering

Undo ist meist die Umkehrung von Redo: AddIndexEntryRoot wird durch DeleteIndexEntryRoot rückgängig gemacht, CreateAttribute durch DeleteAttribute, SetBits… durch ClearBits… und UpdateResidentValue durch ein weiteres UpdateResidentValue mit den alten Bytes. Operationen, deren Rücknahme nichts erfordert, etwa das Initialisieren eines frischen FILE-Records, haben als Undo oft Noop.

Wie typische Aktionen aussehen

Das sind die Muster, nach denen ein Parser sucht. Echte Abfolgen enthalten mehr Einträge (Bitmaps, Größen, Sicherheit, USN-Schreibvorgänge), und die genaue Reihenfolge kann je nach Windows-Version variieren.

Datei anlegen

  • InitializeFileRecordSegment auf dem neuen Eintrag. Redo = vollständiges Abbild des FILE-Records: Header (Sequenznummer, Flags), $STANDARD_INFORMATION, $FILE_NAME (Name, Referenz auf den übergeordneten Ordner, vier Zeiten), eventuell ein kleines $DATA.
  • AddIndexEntryRoot oder AddIndexEntryAllocation im $I30-Index des übergeordneten Verzeichnisses. Der Indexeintrag enthält eine Kopie von $FILE_NAME.

Löschen

  • DeleteIndexEntryRoot / DeleteIndexEntryAllocation entfernt den Namen aus dem übergeordneten Ordner (der entfernte Eintrag steht in den Undo-Bytes, manchmal auch im Redo).
  • DeallocateFileRecordSegment auf dem Eintrag, dazu ClearBitsInNonresidentBitMap auf der MFT-Bitmap.

Umbenennen oder Verschieben

  • dfir.ru dokumentiert eine Umbenennung als DeleteIndexEntryRoot, DeleteAttribute (altes $FILE_NAME), CreateAttribute (neues $FILE_NAME), AddIndexEntryRoot (How the $LogFile works?). Eine Verschiebung sieht genauso aus, nur mit einem anderen übergeordneten Ordner im hinzugefügten Eintrag. Bei großen Verzeichnissen erscheinen statt der …Root-Varianten die …Allocation-Varianten.

Zeitstempeländerung

  • UpdateResidentValue auf das Attribut $STANDARD_INFORMATION (das erste Attribut eines FILE-Records, bei Offset 0x38 in NTFS-3.1-Records). Redo enthält die neuen Bytes, Undo die alten. Die Aktualisierung kann partiell sein — dfir.ru weist darauf hin, dass sie beim M-Zeitstempel beginnen kann —, daher muss ein Parser den Attribut-Offset nutzen, um zu wissen, welche der vier Zeiten er liest. Darauf baut Timestomping mit dem NTFS-$LogFile erkennen auf.

Inhalt kleiner Dateien

  • Ein residentes $DATA-Attribut, das mit CreateAttribute angelegt wird oder im Abbild des FILE-Records enthalten ist, kann die ersten Bytes einer kleinen Datei tragen. Spätere Bearbeitungen sind eine andere Geschichte: Laut Dokumentation von LogFileParser wird unter modernem Windows der Inhalt von Änderungen an residenten Daten im Allgemeinen nicht gespeichert, nur die Tatsache, dass eine Änderung stattfand. Siehe Spuren gelöschter Dateien im $LogFile finden.

Einträge zu Aktionen gruppieren

Zwei Ansätze:

  1. Nach Transaktion. Transaktions-ID und die Kette der vorherigen LSNs nutzen, begrenzt durch ForgetTransaction- oder Commit-Einträge. Am genauesten, aber über Checkpoints hinweg schwerer richtig umzusetzen.
  2. Nach MFT-Eintrag und Nähe. Einträge nehmen, die denselben Eintrag innerhalb eines LSN-Fensters betreffen. Einfacher und robust gegenüber beschädigten Seiten, aber zwei voneinander unabhängige Aktionen an derselben Datei kurz hintereinander können verschmelzen.

Der Parser im Browser nutzt derzeit den zweiten Ansatz (ein Fenster von ±256 Einträgen pro MFT-Eintrag) und zeigt jeden Roheintrag, damit Sie die Gruppierung prüfen können. Gruppierung nach Transaktionen steht auf seiner Roadmap; zur Bestätigung sollten LogFileParser und NTFS Log Tracker herangezogen werden.

Die Bytes selbst lesen

Wenn es auf ein Ereignis ankommt, öffnen Sie den Roheintrag: Prüfen Sie Redo-/Undo-Opcodes, Zielattribut (zum Beispiel #322:$STANDARD_INFORMATION oder $MFT:$DATA), Eintrags-Offset, Attribut-Offset und den Hexdump. Ein 32 Byte langes Undo bei Attribut-Offset 0 von $STANDARD_INFORMATION enthält vier FILETIME-Werte im Little-Endian-Format: Erstellung, Änderung, MFT-Änderung, Zugriff.

Häufige Fragen

Was sind Redo- und Undo-Operationen im $LogFile?

Jeder NTFS-Logeintrag beschreibt eine Metadatenänderung zweimal: Redo-Operation und Redo-Bytes sagen, wie die Änderung erneut angewendet wird, Undo-Operation und Undo-Bytes, wie sie rückgängig gemacht wird. Bei der Wiederherstellung wird Redo für abgeschlossene Transaktionen erneut abgespielt und Undo für nicht abgeschlossene angewendet.

Welche $LogFile-Operationen zeigen, dass eine Datei angelegt oder gelöscht wurde?

Beim Anlegen sieht man meist InitializeFileRecordSegment auf dem neuen FILE-Record plus AddIndexEntryRoot oder AddIndexEntryAllocation im übergeordneten Verzeichnis. Beim Löschen DeleteIndexEntryRoot oder DeleteIndexEntryAllocation, gefolgt von DeallocateFileRecordSegment.

Weiterführende Literatur

Verwandte Artikel

Verwandte Artikel