Las operaciones redo/undo del $LogFile de NTFS, explicadas
La cabecera de un registro del diario NTFS byte a byte, los 38 opcodes redo/undo, cuáles importan en forense y cómo se ven creación, borrado y renombrado.
TL;DR. Un registro del diario de NTFS es una cabecera LFS (LSN, LSN anterior, id. de transacción…) seguida de una cabecera NTFS: opcode redo, opcode undo, dónde están los bytes redo y undo, a qué atributo abierto se refieren y en qué punto de él. Existen treinta y ocho opcodes (0x00–0x25), pero unos pocos concentran casi todo el valor forense: InitializeFileRecordSegment, DeallocateFileRecordSegment, CreateAttribute / DeleteAttribute, UpdateResidentValue y las cuatro operaciones Add/DeleteIndexEntry*. Todo lo demás es contabilidad interna que, aun así, necesitas para resolver los destinos y agrupar los registros.
Si $LogFile te parece opaco es porque una sola acción del usuario —«renombrar este archivo»— se convierte en varios registros, y cada uno solo tiene sentido con el contexto adecuado: qué atributo está abierto con qué índice, qué tamaño de clúster usa el volumen, cómo era el registro FILE antes. Este artículo es la tabla de descifrado. La visión de conjunto está en Análisis forense del $LogFile de NTFS: la guía completa.
Dos cabeceras por registro
Todo registro del diario empieza con la cabecera de registro LFS, seguida de los datos del cliente. En el caso del cliente NTFS, esos datos empiezan con su propia cabecera. Desplazamientos tomados del fslog.c de ntfs3 de Linux:
Cabecera de registro LFS (0x30 bytes)
| Desplazamiento | Tamaño | Campo |
|---|---|---|
0x00 | 8 | LSN del registro |
0x08 | 8 | LSN anterior del cliente (misma transacción) |
0x10 | 8 | LSN undo siguiente del cliente |
0x18 | 4 | Longitud de los datos del cliente |
0x1C | 4 | Id. del cliente |
0x20 | 4 | Tipo de registro: 1 = registro de cliente, 2 = reinicio del cliente |
0x24 | 4 | Id. de transacción |
0x28 | 2 | Flags (0x1 = el registro continúa en la página siguiente) |
Cabecera del registro NTFS (inicio de los datos del cliente)
| Desplazamiento | Tamaño | Campo | Uso |
|---|---|---|---|
0x00 | 2 | Operación redo | Qué hacer |
0x02 | 2 | Operación undo | Cómo revertirlo |
0x04 / 0x06 | 2 + 2 | Desplazamiento / longitud redo | Dónde están los bytes redo |
0x08 / 0x0A | 2 + 2 | Desplazamiento / longitud undo | Dónde están los bytes undo |
0x0C | 2 | Atributo destino | Índice en la tabla de atributos abiertos |
0x0E | 2 | LCN que siguen | Número de números de clúster en 0x20 |
0x10 | 2 | Desplazamiento de registro | Desplazamiento del atributo dentro del registro FILE (o de la entrada dentro de un búfer de índice) |
0x12 | 2 | Desplazamiento de atributo | Desplazamiento del cambio dentro de ese atributo |
0x14 | 2 | Desplazamiento de bloque de clúster | En unidades de 512 bytes, dentro del clúster destino |
0x18 | 8 | VCN destino | Clúster virtual en el atributo destino |
0x20 | 8 × n | LCN destino | Clústeres físicos |
Los registros de tipo 2 son checkpoints (áreas de reinicio del cliente), no cambios. Los trata el artículo $LogFile: el área de reinicio y los LSN, explicados.
Encontrar la entrada de la MFT que toca un registro
Los registros no dicen «entrada 322 de la MFT». Dicen «atributo n.º N de la tabla de atributos abiertos, VCN v, desplazamiento de bloque de clúster c». Cuando el atributo destino es $MFT:$DATA, la entrada es:
entry = (VCN × cluster_size + cluster_block_offset × 512) / mft_record_size
Hacen falta dos parámetros del volumen. Un analizador puede tomarlos del sector de arranque o, como hace el analizador de $LogFile en el navegador, calibrarlos a partir del propio diario: los registros InitializeFileRecordSegment contienen una imagen completa de un registro FILE cuya cabecera indica su propio número de registro, así que gana la combinación de tamaño de clúster y tamaño de registro que ubica correctamente la mayoría de ellos.
La tabla de atributos abiertos se reconstruye a partir de los registros OpenNonresidentAttribute y de los OpenAttributeTableDump y AttributeNamesDump escritos en cada checkpoint. La estructura de sus entradas varía según la versión del cliente NTFS (0x28 frente a 0x2C bytes), y ese es uno de los puntos en los que los analizadores pueden discrepar.
Las 38 operaciones, agrupadas
Los nombres siguientes son los del enum NTFS_LOG_OPERATION de ntfs3, que también usa el analizador.
| Código | Operación | Grupo | Valor forense |
|---|---|---|---|
0x00 | Noop | — | Parte undo de muchos registros que solo tienen redo |
0x01 | CompensationLogRecord | Transacción | Se escribe durante una reversión |
0x02 | InitializeFileRecordSegment | Registro FILE | Alto: imagen completa del registro FILE de una entrada nueva o reutilizada |
0x03 | DeallocateFileRecordSegment | Registro FILE | Alto: entrada liberada (borrado) |
0x04 | WriteEndOfFileRecordSegment | Registro FILE | Bajo |
0x05 | CreateAttribute | Atributo | Alto: atributo nuevo, p. ej. $FILE_NAME o $DATA residente |
0x06 | DeleteAttribute | Atributo | Alto: atributo eliminado, p. ej. el $FILE_NAME antiguo en un renombrado |
0x07 | UpdateResidentValue | Atributo | Alto: horas de $STANDARD_INFORMATION, valores residentes pequeños |
0x08 | UpdateNonresidentValue | Atributo | Medio: búferes de índice, escrituras en $UsnJrnl, otros flujos |
0x09 | UpdateMappingPairs | Atributo | Medio: data runs (crecimiento del archivo) |
0x0A | DeleteDirtyClusters | Atributo | Bajo |
0x0B | SetNewAttributeSizes | Atributo | Medio: cambios de tamaño |
0x0C / 0x0D | Add / DeleteIndexEntryRoot | Índice | Alto: entradas de carpeta en $INDEX_ROOT |
0x0E / 0x0F | Add / DeleteIndexEntryAllocation | Índice | Alto: entradas de carpeta en $INDEX_ALLOCATION |
0x10 | WriteEndOfIndexBuffer | Índice | Bajo |
0x11 / 0x12 | SetIndexEntryVcnRoot / Allocation | Índice | Bajo (fontanería del árbol B) |
0x13 / 0x14 | UpdateFileNameRoot / Allocation | Índice | Medio: información de $FILE_NAME duplicada en $I30 |
0x15 / 0x16 | Set / ClearBitsInNonresidentBitMap | Bitmap | Bajo por sí solo: asignación de clústeres o de entradas de la MFT |
0x17 | HotFix | — | Raro |
0x18 | EndTopLevelAction | Transacción | Estructura |
0x19–0x1B | Prepare / Commit / ForgetTransaction | Transacción | Límites de las transacciones |
0x1C | OpenNonresidentAttribute | Tabla | Necesario para resolver los destinos |
0x1D–0x20 | Volcados OpenAttributeTable / AttributeNames / DirtyPageTable / TransactionTable | Checkpoint | Necesarios para resolver los destinos |
0x21 / 0x22 | UpdateRecordDataRoot / Allocation | Índice | Medio: índices distintos de $I30 ($ObjId, $Quota, $Secure) |
0x23 / 0x24 | UpdateRelativeDataInIndex / 2 | Índice | Bajo |
0x25 | ZeroEndOfFileRecord | Registro FILE | Bajo |
El undo suele ser el inverso del redo: AddIndexEntryRoot se deshace con DeleteIndexEntryRoot, CreateAttribute con DeleteAttribute, SetBits… con ClearBits…, y UpdateResidentValue con otro UpdateResidentValue que lleva los bytes antiguos. Las operaciones cuya reversión no necesita nada, como inicializar un registro FILE nuevo, suelen tener Noop como undo.
Cómo se ven las acciones habituales
Estos son los patrones que busca un analizador. Las secuencias reales contienen más registros (bitmaps, tamaños, seguridad, escrituras USN) y el orden exacto puede variar según la versión de Windows.
Creación de un archivo
InitializeFileRecordSegmentsobre la nueva entrada. Redo = imagen completa del registro FILE: cabecera (número de secuencia, flags),$STANDARD_INFORMATION,$FILE_NAME(nombre, referencia del padre, cuatro horas) y quizá un$DATApequeño.AddIndexEntryRootoAddIndexEntryAllocationen el índice$I30de la carpeta padre. La entrada de índice incluye una copia de$FILE_NAME.
Borrado
DeleteIndexEntryRoot/DeleteIndexEntryAllocation, que quitan el nombre de la carpeta padre (la entrada eliminada está en los bytes undo, a veces en los redo).DeallocateFileRecordSegmentsobre la entrada, conClearBitsInNonresidentBitMapsobre el bitmap de la MFT.
Renombrado o movimiento
- dfir.ru documenta un renombrado como
DeleteIndexEntryRoot,DeleteAttribute($FILE_NAMEantiguo),CreateAttribute($FILE_NAMEnuevo),AddIndexEntryRoot(How the $LogFile works?). Un movimiento es lo mismo con otra carpeta padre en la entrada añadida. En carpetas grandes aparecen las variantes…Allocationen lugar de…Root.
Cambio de marca de tiempo
UpdateResidentValuesobre el atributo$STANDARD_INFORMATION(el primer atributo de un registro FILE, en el desplazamiento0x38en los registros de NTFS 3.1). El redo contiene los bytes nuevos; el undo, los antiguos. La actualización puede ser parcial —dfir.ru señala que puede empezar en la marca M—, así que un analizador debe usar el desplazamiento de atributo para saber cuál de las cuatro horas está leyendo. Detectar timestomping con el $LogFile de NTFS se basa en esto.
Contenido de archivos pequeños
- Un atributo
$DATAresidente creado conCreateAttributeo incluido en la imagen del registro FILE puede llevar los primeros bytes de un archivo pequeño. Las ediciones posteriores son otra historia: la documentación de LogFileParser indica que, en Windows moderno, el contenido de los cambios en datos residentes no suele guardarse, solo el hecho de que hubo un cambio. Consulta $LogFile: recuperar evidencias de archivos borrados.
Agrupar registros en acciones
Dos enfoques:
- Por transacción. Usar el id. de transacción y la cadena de LSN anteriores, delimitada por registros
ForgetTransactiono de confirmación. Es el más fiel, pero más difícil de hacer bien a través de los checkpoints. - Por entrada de la MFT y proximidad. Tomar los registros que tocan la misma entrada dentro de una ventana de LSN. Es más sencillo y resiste mejor las páginas dañadas, pero dos acciones sin relación sobre el mismo archivo, muy seguidas, pueden fusionarse.
El analizador en el navegador usa por ahora el segundo enfoque (una ventana de ±256 registros por entrada de la MFT) y expone cada registro en bruto para que puedas comprobar la agrupación. La agrupación por transacciones está en su hoja de ruta; conviene usar LogFileParser y NTFS Log Tracker para confirmar.
Leer los bytes uno mismo
Cuando un evento importa, abre el registro en bruto: comprueba los opcodes redo/undo, el atributo destino (por ejemplo #322:$STANDARD_INFORMATION o $MFT:$DATA), el desplazamiento de registro, el desplazamiento de atributo y el hexadecimal. Un undo de 32 bytes en el desplazamiento de atributo 0 de $STANDARD_INFORMATION contiene cuatro valores FILETIME en little-endian: creación, modificación, cambio MFT y acceso.
Preguntas frecuentes
¿Qué son las operaciones redo y undo del $LogFile?
Cada registro del diario de NTFS describe un cambio de metadatos dos veces: la operación y los bytes redo indican cómo volver a aplicar el cambio, y la operación y los bytes undo, cómo revertirlo. La recuperación reaplica redo en las transacciones confirmadas y aplica undo en las no confirmadas.
¿Qué operaciones del $LogFile muestran la creación o el borrado de un archivo?
Una creación suele mostrar InitializeFileRecordSegment sobre el nuevo registro FILE más AddIndexEntryRoot o AddIndexEntryAllocation en la carpeta padre. Un borrado muestra DeleteIndexEntryRoot o DeleteIndexEntryAllocation seguido de DeallocateFileRecordSegment.
Para saber más
- Kernel de Linux,
fs/ntfs3/fslog.c: definiciones de las estructuras y una implementación completa de la reproducción del diario. - Joakim Schicht, readme de LogFileParser: qué operaciones decodifica y qué contienen.
- Maxim Suhanov, How the $LogFile works?