Skip to content

Les opérations redo/undo du $LogFile NTFS expliquées

L'en-tête d'enregistrement du journal NTFS octet par octet, les 38 opcodes redo/undo, ceux qui comptent en forensique, et à quoi ressemble chaque action.

Publié le 9 min de lecture

TL;DR. Un enregistrement du journal NTFS, c'est un en-tête LFS (LSN, LSN précédent, ID de transaction…) suivi d'un en-tête NTFS : opcode redo, opcode undo, emplacement des octets redo et undo, attribut ouvert visé, et position à l'intérieur de celui-ci. Il existe trente-huit opcodes (0x00–0x25), mais une poignée porte l'essentiel de la valeur forensique : InitializeFileRecordSegment, DeallocateFileRecordSegment, CreateAttribute / DeleteAttribute, UpdateResidentValue et les quatre opérations Add/DeleteIndexEntry*. Le reste est de l'intendance, dont vous avez tout de même besoin pour résoudre les cibles et regrouper les enregistrements.

Si le $LogFile paraît opaque, c'est qu'une seule action utilisateur — « renommer ce fichier » — produit plusieurs enregistrements, dont chacun n'a de sens qu'avec le bon contexte : quel attribut est ouvert sous quel index, quelle taille de cluster utilise le volume, à quoi ressemblait l'enregistrement FILE avant. Cet article sert de grille de décodage. La vue d'ensemble se trouve dans le guide complet du $LogFile.

Deux en-têtes par enregistrement

Chaque enregistrement commence par l'en-tête d'enregistrement LFS, suivi des données client. Pour le client NTFS, ces données commencent par leur propre en-tête. Offsets tirés du fslog.c de ntfs3 sous Linux :

En-tête d'enregistrement LFS (0x30 octets)

OffsetTailleChamp
0x008LSN de l'enregistrement
0x088LSN précédent du client (même transaction)
0x108LSN undo suivant du client
0x184Longueur des données client
0x1C4ID du client
0x204Type d'enregistrement : 1 = enregistrement client, 2 = redémarrage client
0x244ID de transaction
0x282Drapeaux (0x1 = l'enregistrement continue sur la page suivante)

En-tête d'enregistrement NTFS (début des données client)

OffsetTailleChampUsage
0x002Opération redoCe qu'il faut faire
0x022Opération undoComment l'annuler
0x04 / 0x062 + 2Offset / longueur redoOù se trouvent les octets redo
0x08 / 0x0A2 + 2Offset / longueur undoOù se trouvent les octets undo
0x0C2Attribut cibleIndex dans la table des attributs ouverts
0x0E2Nombre de LCNNombre de numéros de cluster à 0x20
0x102Offset d'enregistrementOffset de l'attribut dans l'enregistrement FILE (ou de l'entrée dans un tampon d'index)
0x122Offset d'attributOffset du changement dans cet attribut
0x142Offset de bloc de clusterEn unités de 512 octets, dans le cluster cible
0x188VCN cibleCluster virtuel dans l'attribut cible
0x208 × nLCN ciblesClusters physiques

Les enregistrements de type 2 sont des points de contrôle (zones de redémarrage client), pas des changements. L'article Zone de redémarrage et LSN les couvre.

Retrouver l'entrée MFT visée par un enregistrement

Les enregistrements ne disent pas « entrée MFT 322 ». Ils disent « attribut n° N de la table des attributs ouverts, VCN v, offset de bloc de cluster c ». Quand l'attribut cible est $MFT:$DATA, l'entrée vaut :

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

Il faut donc deux paramètres du volume. Un parseur peut les lire dans le secteur d'amorçage ou, comme le fait le parseur de $LogFile dans le navigateur, les calibrer à partir du journal lui-même : les enregistrements InitializeFileRecordSegment contiennent une image complète d'enregistrement FILE dont l'en-tête indique son propre numéro, et la combinaison taille de cluster / taille d'enregistrement qui les place correctement le plus souvent l'emporte.

La table des attributs ouverts se reconstruit à partir des enregistrements OpenNonresidentAttribute et des OpenAttributeTableDump et AttributeNamesDump écrits à chaque point de contrôle. La structure de ses entrées diffère selon la version du client NTFS (0x28 ou 0x2C octets), et c'est l'un des points où les parseurs peuvent diverger.

Les 38 opérations, par famille

Les noms ci-dessous sont ceux de l'enum NTFS_LOG_OPERATION de ntfs3, que le parseur reprend.

CodeOpérationFamilleValeur forensique
0x00Noop—Côté undo de nombreux enregistrements sans annulation
0x01CompensationLogRecordTransactionÉcrit pendant une annulation
0x02InitializeFileRecordSegmentEnregistrement FILEÉlevée : image complète d'une entrée nouvelle ou réutilisée
0x03DeallocateFileRecordSegmentEnregistrement FILEÉlevée : entrée libérée (suppression)
0x04WriteEndOfFileRecordSegmentEnregistrement FILEFaible
0x05CreateAttributeAttributÉlevée : nouvel attribut, par ex. $FILE_NAME ou $DATA résident
0x06DeleteAttributeAttributÉlevée : attribut retiré, par ex. l'ancien $FILE_NAME lors d'un renommage
0x07UpdateResidentValueAttributÉlevée : horodatages $STANDARD_INFORMATION, petites valeurs résidentes
0x08UpdateNonresidentValueAttributMoyenne : tampons d'index, écritures $UsnJrnl, autres flux
0x09UpdateMappingPairsAttributMoyenne : runs de données (croissance du fichier)
0x0ADeleteDirtyClustersAttributFaible
0x0BSetNewAttributeSizesAttributMoyenne : changements de taille
0x0C / 0x0DAdd / DeleteIndexEntryRootIndexÉlevée : entrées de répertoire dans $INDEX_ROOT
0x0E / 0x0FAdd / DeleteIndexEntryAllocationIndexÉlevée : entrées de répertoire dans $INDEX_ALLOCATION
0x10WriteEndOfIndexBufferIndexFaible
0x11 / 0x12SetIndexEntryVcnRoot / AllocationIndexFaible (mécanique du B-tree)
0x13 / 0x14UpdateFileNameRoot / AllocationIndexMoyenne : infos $FILE_NAME dupliquées dans $I30
0x15 / 0x16Set / ClearBitsInNonresidentBitMapBitmapFaible isolément : allocation de clusters ou d'entrées MFT
0x17HotFix—Rare
0x18EndTopLevelActionTransactionStructure
0x19–0x1BPrepare / Commit / ForgetTransactionTransactionBornes de transaction
0x1COpenNonresidentAttributeTableNécessaire pour résoudre les cibles
0x1D–0x20Dumps OpenAttributeTable / AttributeNames / DirtyPageTable / TransactionTablePoint de contrôleNécessaires pour résoudre les cibles
0x21 / 0x22UpdateRecordDataRoot / AllocationIndexMoyenne : index hors $I30 ($ObjId, $Quota, $Secure)
0x23 / 0x24UpdateRelativeDataInIndex / 2IndexFaible
0x25ZeroEndOfFileRecordEnregistrement FILEFaible

L'undo est généralement l'inverse du redo : AddIndexEntryRoot s'annule par DeleteIndexEntryRoot, CreateAttribute par DeleteAttribute, SetBits… par ClearBits…, et UpdateResidentValue par un autre UpdateResidentValue qui transporte les anciens octets. Les opérations dont l'annulation ne demande rien, comme l'initialisation d'un enregistrement FILE neuf, ont souvent Noop comme undo.

À quoi ressemblent les actions courantes

Ce sont les motifs que recherche un parseur. Les séquences réelles contiennent davantage d'enregistrements (bitmaps, tailles, sécurité, écritures USN) et l'ordre exact peut varier selon la version de Windows.

Création de fichier

  • InitializeFileRecordSegment sur la nouvelle entrée. Redo = image complète de l'enregistrement FILE : en-tête (numéro de séquence, drapeaux), $STANDARD_INFORMATION, $FILE_NAME (nom, référence du parent, quatre horodatages), éventuellement un petit $DATA.
  • AddIndexEntryRoot ou AddIndexEntryAllocation dans l'index $I30 du répertoire parent. L'entrée d'index embarque une copie de $FILE_NAME.

Suppression

  • DeleteIndexEntryRoot / DeleteIndexEntryAllocation qui retire le nom du parent (l'entrée retirée se trouve dans les octets undo, parfois dans les octets redo).
  • DeallocateFileRecordSegment sur l'entrée, avec ClearBitsInNonresidentBitMap sur le bitmap de la MFT.

Renommage ou déplacement

  • dfir.ru décrit un renommage comme DeleteIndexEntryRoot, DeleteAttribute (ancien $FILE_NAME), CreateAttribute (nouveau $FILE_NAME), AddIndexEntryRoot (How the $LogFile works?). Un déplacement est identique, avec un autre parent dans l'entrée ajoutée. Pour les gros répertoires, ce sont les variantes …Allocation qui apparaissent à la place de …Root.

Modification d'horodatage

  • UpdateResidentValue visant l'attribut $STANDARD_INFORMATION (premier attribut d'un enregistrement FILE, à l'offset 0x38 dans les enregistrements NTFS 3.1). Le redo contient les nouveaux octets, l'undo les anciens. La mise à jour peut être partielle — dfir.ru signale qu'elle peut commencer à l'horodatage M — si bien qu'un parseur doit utiliser l'offset d'attribut pour savoir lequel des quatre horodatages il lit. La détection du timestomping repose sur ce point.

Contenu des petits fichiers

  • Un attribut $DATA résident créé par CreateAttribute ou inclus dans l'image de l'enregistrement FILE peut contenir les premiers octets d'un petit fichier. Les modifications ultérieures sont une autre histoire : la documentation de LogFileParser indique que, sur les Windows récents, le contenu des modifications de données résidentes n'est généralement pas stocké, seulement le fait qu'un changement a eu lieu. Voir Retrouver les traces de fichiers supprimés.

Regrouper les enregistrements en actions

Deux approches :

  1. Par transaction. Utiliser l'ID de transaction et la chaîne des LSN précédents, bornée par les enregistrements ForgetTransaction ou de validation. La plus fidèle, mais plus délicate à réussir d'un point de contrôle à l'autre.
  2. Par entrée MFT et proximité. Prendre les enregistrements qui touchent la même entrée dans une fenêtre de LSN. Plus simple et robuste face aux pages endommagées, mais deux actions sans rapport sur le même fichier, rapprochées dans le temps, peuvent fusionner.

Le parseur dans le navigateur utilise actuellement la seconde approche (une fenêtre de ±256 enregistrements par entrée MFT) et expose chaque enregistrement brut pour que vous puissiez vérifier le regroupement. Le regroupement par transaction est prévu ; utilisez LogFileParser et NTFS Log Tracker pour confirmer.

Lire les octets vous-même

Quand un événement compte, ouvrez l'enregistrement brut : contrôlez les opcodes redo/undo, l'attribut cible (par exemple #322:$STANDARD_INFORMATION ou $MFT:$DATA), l'offset d'enregistrement, l'offset d'attribut et l'hexadécimal. Un undo de 32 octets à l'offset d'attribut 0 de $STANDARD_INFORMATION contient quatre valeurs FILETIME en petit-boutiste : création, modification, modification de l'entrée MFT, dernier accès.

Questions fréquentes

Que sont les opérations redo et undo du $LogFile ?

Chaque enregistrement du journal NTFS décrit un changement de métadonnées deux fois : l'opération et les octets redo disent comment réappliquer le changement, l'opération et les octets undo comment l'annuler. La reprise rejoue le redo des transactions validées et applique l'undo des transactions non validées.

Quelles opérations du $LogFile montrent la création ou la suppression d'un fichier ?

Une création montre en général InitializeFileRecordSegment sur le nouvel enregistrement FILE, plus AddIndexEntryRoot ou AddIndexEntryAllocation dans le répertoire parent. Une suppression montre DeleteIndexEntryRoot ou DeleteIndexEntryAllocation suivi de DeallocateFileRecordSegment.

Pour aller plus loin

Articles liés

Articles liés