← Volver al blog

Datos residentes: archivos pequeños que viven dentro de la MFT

· 6 min de lectura

Cada archivo NTFS tiene un atributo $DATA (tipo 0x80) que contiene su contenido. Para la mayoría de archivos, $DATA es no residente: la cabecera del atributo lleva una runlist que apunta a cluster runs en otra parte del volumen. Para archivos pequeños, los bytes están inline en el propio registro MFT. Ese segundo caso es uno de los artefactos más útiles en la forense NTFS, y es el que veo olvidar a los analistas una y otra vez.

Qué tan pequeño es lo bastante pequeño

Un registro MFT mide 1.024 bytes. De ahí restas la cabecera de 56 bytes, el array de fixup, $STANDARD_INFORMATION (alrededor de 72 bytes), al menos un $FILE_NAME (variable, alrededor de 90 bytes para un nombre típico), padding y el centinela de fin de atributos. Lo que queda es el presupuesto utilizable para $DATA residente y cualquier otro atributo residente.

En la práctica, el umbral es de aproximadamente 700 bytes de datos. Algunas referencias dicen hasta ~750 bytes para archivos con nombres muy cortos y sin otros atributos residentes; algunas bajan (~580 bytes) para archivos con nombres más largos o atributos adicionales. El umbral no es una constante; depende de qué más haya en el registro.

Archivos que caben, y que veo constantemente:

  • Pequeños archivos de texto: notas, listas de tareas, borradores.
  • Pequeños archivos de configuración: .ini, .cfg, .json pequeños, .xml pequeños.
  • One-liners de PowerShell y scripts cortos.
  • Pequeños archivos .lnk de acceso directo (la mayoría de los LNK son lo bastante pequeños; mira forense de LNK para lo que codifican).
  • Snippets de exportación de registro y archivos .reg.
  • Pequeños archivos .bat y .cmd.
  • Archivos de certificado en formato PEM que son cortos.
  • Archivos vacíos y placeholders de cero bytes. Los "datos" son cero bytes y $DATA es trivialmente residente.
  • Muchos atributos $INDEX_ROOT para directorios pequeños.

Por qué esto importa

Un atributo $DATA residente puede recuperarse sin leer el resto del disco. Si tienes una copia de $MFT, ya tienes:

  • El contenido completo de pequeños archivos de texto.
  • Mucha información $INDEX_ROOT para directorios (las entradas del directorio, listadas inline).
  • Alternate data streams cortos (atributos $DATA con nombre y contenido pequeño).
  • Reparse points y destinos de symlinks (la ruta de destino vive en $REPARSE_POINT, que también suele ser residente).

Esa es una cantidad sorprendente de evidencia dentro de un único extracto MFT de 200 MB a 2 GB. Para archivos pequeños eliminados, los bytes residentes sobreviven aunque el área de datos haya sido sobrescrita, porque nunca se tocó el área de datos; los bytes vivían en la MFT.

He recuperado:

  • Un loader de PowerShell de 320 bytes de un registro eliminado cuyos clusters de datos nunca existieron.
  • Un .lnk de 480 bytes apuntando al directorio de staging del atacante, eliminado tres semanas antes de la adquisición.
  • Un .config de 700 bytes de un servicio que había sido desinstalado. El directorio se había ido; el registro MFT estaba en el slot 412.000 intacto.

La MFT era el único lugar donde esos archivos seguían existiendo.

Residente no es estable

Cuando un archivo residente crece más allá del espacio libre del registro, NTFS lo convierte a no residente. Los datos se mueven a clusters y $DATA se convierte en una runlist. La conversión se registra en $LogFile mientras el cambio ocurre.

Lo contrario puede pasar técnicamente (un archivo encogido por debajo del umbral podría volverse residente de nuevo), pero Windows rara vez lo hace voluntariamente. Una vez que $DATA es no residente, tiende a quedarse así. Si encuentras un registro con $DATA residente para un archivo que sabes que fue más grande, eso es inusual y vale la pena investigar; sugiere manipulación deliberada o un camino de código poco común que encogió el archivo en el sitio.

Detectar datos residentes mientras parseas

Cada parser MFT expone una bandera "residente" en la cabecera del atributo $DATA. La salida CSV de MFTECmd la tiene como columna booleana. El JSON de mft_dump tiene header.is_resident. libmft la expone en el objeto atributo. istat del Sleuth Kit la muestra como parte del listado de atributos.

El parser en navegador de este sitio filtra por IN_USE=0 con $DATA residente en dos clics. Ese filtro es el primero que ejecuto en cualquier MFT extraída donde importe la recuperación de archivos pequeños.

Cómo se ven los bytes

Para un $DATA residente, la cabecera del atributo va seguida de una cabecera residente de 16 bytes (tamaño del contenido, offset del contenido, bandera indexada, padding) y luego los bytes crudos. Los bytes son exactamente lo que estaba en disco en el archivo: texto en cualquier codificación que el archivo usara, contenido binario en su forma nativa. Sin transformación.

Para un pequeño archivo de texto en UTF-8, puedes copiar los bytes fuera y leer el archivo en cualquier editor. Para contenido binario (un pequeño PE, un .pyc compilado, un objeto serializado), los bytes son igualmente usables; trátalos como un archivo extraído del volumen.

Los límites

La mayoría de los archivos no son residentes. En una instalación normal de Windows, mucho menos del 5% de los archivos de usuario son residentes. El resto es todo Office, cada archivo de caché del navegador, cada programa en \Program Files\, cada descarga. La recuperación residente es un nicho de alto valor, no el modo por defecto.

El umbral no es exacto. No supongas "los archivos de menos de 700 bytes son recuperables". El umbral real depende de los otros atributos del registro. Prueba en la MFT específica que estás analizando.

Residente no sobrevive a la compresión o cifrado de un archivo lo bastante grande. Los archivos cifrados con EFS usan $LOGGED_UTILITY_STREAM para los metadatos de cifrado y $DATA es el cifrado. Si el cifrado es lo bastante pequeño para ser residente, tienes los bytes cifrados; aún necesitas la clave EFS para leerlos.

Un flujo que usa esto

La próxima vez que tengas un caso de recuperación de archivo eliminado y el archivo que te importa sea pequeño:

  1. Adquiere $MFT del host (o de una instantánea VSS, si tienes una reciente).
  2. Parséala. Saca cada registro donde IN_USE=0 y el $DATA principal sea residente.
  3. Filtra por regex de $FILE_NAME si sabes el nombre. Filtra por referencia padre si sabes el directorio.
  4. Inspecciona los bytes residentes de $DATA inline. El archivo está ahí mismo.

Cinco minutos de trabajo para una recuperación que el carving por firma habría tardado horas en hacer (y probablemente habría fallado de todos modos para archivos pequeños fragmentados).

Lecturas adicionales

  • Notas sobre atributos residentes del proyecto linux-ntfs. Explicación clara del layout de la cabecera residente.
  • Microsoft, Algoritmo de asignación NTFS. Referencia oficial para cómo se toma la decisión residente/no residente.
  • istat del Sleuth Kit para inspeccionar registros individuales y ver la bandera residente en contexto.

Recursos externos