Volver a todos los artículos

La mayoría de la gente que trabaja con Prefetch nunca mira los bytes. Lanzan un parser, leen una tabla y siguen. Eso suele estar bien. No está bien cuando el parser se equivoca, y la única manera de saber si tu parser se equivoca es entender qué se supone que debe hacer.

El formato Prefetch de Windows ha cambiado cinco o seis veces en veinte años. Cada cambio rompió al menos un parser popular en su momento. Si estás leyendo un archivo .pf de un host Win11 con una herramienta cuya última actualización fue en 2019, esto debería importarte.

SCCA: el magic y la forma

Todo archivo Prefetch legado comienza con la cadena ASCII SCCA en el offset 0x04. Los cuatro bytes anteriores son el campo de versión, little-endian. Eso es lo primero que comprueba cualquier parser, y la versión es la puerta de entrada a todo lo demás.

Los valores conocidos:

  • 0x11 (decimal 17) para Windows XP y 2003
  • 0x17 (23) para Vista y Windows 7
  • 0x1A (26) para Windows 8 y 8.1
  • 0x1E (30) para Windows 10 (era sin comprimir y las primeras builds con MAM comprimido)
  • 0x1F (31) para builds posteriores de Windows 10/11. Trátalo como "Win10+ reciente" en lugar de atado a una build específica.

Si los primeros cuatro bytes del archivo no son SCCA ni la firma MAM (volveremos a esto en un momento), no tienes un archivo Prefetch. Tienes otra cosa con extensión .pf, y el caso que más he visto es cuando alguien copió un archivo por SMB con una herramienta consciente del encoding que lo destrozó. Vuelve a adquirir.

Tras la versión y el magic, viene un pequeño bloque de cabecera: tamaño total del archivo, nombre del ejecutable codificado en UTF-16LE (hasta 30 caracteres, rellenado con nulos) y un hash de ruta (también cubierto por separado en detalle). El nombre de ejecutable en la cabecera debe coincidir con el prefijo del nombre del archivo. Cuando no, estás mirando un archivo renombrado, manipulación manual o, más raramente, un binario cuyo campo de nombre interno superaba los 29 caracteres y fue truncado.

Secciones A, B, C, D y el resto

Dentro del archivo, Prefetch está organizado en secciones cuyo propósito se ha mantenido aproximadamente igual entre versiones, aunque hayan cambiado los offsets y los anchos de campo:

  • Section A es el array de métricas de archivos. Una entrada por archivo que tocó el binario durante la ventana de traza. Cada entrada tiene una hora de inicio, una duración, un offset de cadena de nombre de archivo dentro de Section C y una referencia de archivo a la MFT (esta última solo en versiones más nuevas).
  • Section B es el array de cadenas de traza. Son los datos de secuencia de fallas de página que al SO le interesan de verdad. Desde el punto de vista forense, es mayoritariamente ruido; rara vez la parsearás explícitamente.
  • Section C son las cadenas de nombre de archivo. Rutas UTF-16LE, una tras otra, referenciadas por offset desde Section A. Esto se convierte en la "lista de carga de archivos" al imprimir un informe.
  • Section D es el array de información de volumen. Una entrada por volumen que tocó el binario. Cada entrada tiene una ruta de volumen (típicamente \DEVICE\HARDDISKVOLUMEn), una hora de creación, un número de serie y un índice a la tabla de cadenas de directorios.

Tras Section D suelen encontrarse las cadenas de directorios y, en versiones más nuevas, metadatos adicionales que varían según la build. Las ocho marcas temporales de ejecución viven en la cabecera en Win8+, con un layout ligeramente distinto al de Win7 (que solo tenía una). El contador de ejecuciones se sitúa junto a ellas.

Las dos cosas que se mueven entre versiones son los offsets de cada sección (registrados en la cabecera) y el tamaño de las entradas individuales (la entrada del array de métricas de archivos creció de 20 bytes en XP/7 a 32 bytes en Win10 para acomodar la referencia MFT). Un parser que codifica "Section A empieza en offset 0x98" en lugar de leer el offset desde la cabecera fallará silenciosamente con la siguiente release del SO. Aún hay varios circulando por GitHub.

MAM: el wrapper de compresión de Win10

A partir de algún momento alrededor de Windows 10 1607, Microsoft envolvió los archivos Prefetch en compresión MAM. El archivo ya no empieza con SCCA. Empieza con MAM\x04 (el byte 0x04 es el identificador del algoritmo, Xpress Huffman). Los siguientes cuatro bytes son el tamaño sin comprimir, little-endian. Todo lo demás es el flujo comprimido en Xpress Huffman que contiene lo que de otro modo sería el archivo SCCA plano.

Si descomprimes correctamente, recuperas exactamente la estructura SCCA descrita arriba, versión 0x1E o 0x1F. El formato en el cable es el mismo; solo cambió el envoltorio.

La API relevante de Windows es RtlDecompressBufferEx con COMPRESSION_FORMAT_XPRESS_HUFF. El PECmd de Eric Zimmerman invoca la API de Windows en hosts Windows y usa una implementación gestionada de Xpress Huffman cuando se ejecuta en Linux. Varios parsers Python distribuyen un decodificador Xpress Huffman en Python puro, lento pero correcto. Los que no manejan MAM imprimirán "no es un archivo Prefetch" o, peor, soltarán basura y saldrán con código cero.

Confirma siempre el soporte MAM de tu parser pasándole un archivo Prefetch de Win10 bien conocido (cualquier archivo de C:\Windows\Prefetch\ en un host actual Win10/11 vale) y comprobando que el contador de ejecuciones y los tiempos coinciden con lo que reporta PECmd.

Qué cambió entre Win10 v30 y Win11 v31

Menos de lo que sugiere el salto de número de versión, sobre el papel. El layout interno es casi idéntico. Lo que cambió en la práctica:

  • La forma en que el hash de la ruta incorpora contexto (argumentos de línea de comandos, entorno "App-V", nombre completo del paquete en apps UWP) se amplió. El mismo EXE en dos sandboxes distintas produce dos archivos .pf diferentes, donde en builds anteriores podría colisionar.
  • Los tiempos de traza Prefetch se volvieron más granulares. En v31 a veces ves resolución por debajo del segundo que las versiones anteriores redondeaban.
  • El comportamiento de expiración cambió. El anillo de ocho marcas temporales está documentado como FIFO de tamaño fijo, pero Win10 1709+ recorta entradas antiguas agresivamente durante mantenimiento ocioso. v31 recorta aún más agresivamente en sistemas con SSD.

Un parser que maneja v30 suele manejar v31 sin modificación, porque la estructura apenas se movió. Lo que se mueve bajo la estructura es lo que el propio Windows escribe dentro, y eso no se arregla en un parser.

El array de métricas de archivos: qué mirar

Si solo tienes tiempo de mirar una parte de un archivo Prefetch a mano, mira Section A.

Cada entrada te dice: cuándo en la ventana de traza de diez segundos se tocó el archivo por primera vez, cuánto duró el contacto y la ruta al archivo. En versiones más nuevas, el número de referencia MFT te da el ID del archivo, que es oro cuando la ruta ya no existe. Puedes cruzar la referencia de archivo contra la MFT viva y recuperar la entrada original aunque la ruta haya sido reutilizada por un archivo distinto.

Un patrón específico que recordar: un .pf cuya lista de carga contiene referencias que se resuelven a entradas MFT marcadas como borradas en la MFT viva significa que esos archivos estaban en disco cuando corrió el binario y han sido eliminados desde entonces. Combina con el USN journal y puedes reconstruir el directorio en el momento de la ejecución.

Qué parsers manejan qué versiones

Notas operativas, vigentes al escribir esto:

  • PECmd de Eric Zimmerman es la referencia. SCCA v17, v23, v26, v30, v31 y variantes MAM de v30/v31 se manejan correctamente. Es la herramienta offline a la que recurro por defecto.
  • libscca (Joachim Metz) es la biblioteca en C bajo varias otras herramientas. Sigue el formato de cerca y es contra lo que construiría si escribiera mi propio parser hoy.
  • Windows-Prefetch-Parser (PoorBillionaire) es el parser Python que la mayoría encuentra primero en GitHub. Va bien con archivos previos a Win10. No maneja Prefetch comprimido con MAM de fábrica y produce silenciosamente salida incorrecta en hosts Win10/11 a menos que parchees un descompresor. Lee el README con cuidado y comprueba la fecha.
  • El parser de este sitio maneja archivos SCCA y empaquetados en MAM íntegramente en el navegador. Útil cuando quieres echar un vistazo a uno o dos archivos sin levantar herramientas. Para trabajo masivo, usa PECmd.

Si tienes un parser cuyo nombre no está en esa lista y cuyo último commit tiene más de dos años, pruébalo contra una muestra conocida antes de fiarte de su salida.

Por qué esto importa al escribir un informe

El campo de versión es lo primero que compruebas. El wrapper MAM es lo segundo. Los offsets de sección, leídos desde la cabecera, son lo tercero. Acierta los tres y podrás parsear cualquier archivo Prefetch de cualquier Windows en producción, desde XP hasta actual. Falla alguno y tus herramientas te darán un número, pero el número estará mal, y no sabrás que está mal hasta que alguien más cuidadoso que tú mire los bytes crudos durante un contrainterrogatorio.

He sido esa persona más cuidadosa, en el lado equivocado del caso, más de una vez.

Lecturas adicionales