Parsear el ShimCache: el formato binario de Windows 10 y 11

5 min de lectura

TL;DR. Primer u32 = tamaño de la cabecera, típicamente 0x30 a 0x36. En ese offset cada entrada empieza con la magic de cuatro bytes 10ts, seguida de una cabecera de 12 bytes y un cuerpo de longitud variable: ruta UTF-16LE (prefijada en longitud, no terminada en null), mtime FILETIME, y un blob de datos finales. El flag de ejecución se eliminó en Windows 10.

Cada release de Windows desde XP ha enviado un diseño distinto del AppCompatCache. La variante Windows 10/11 es la que los investigadores ven la mayoría de los días, y es la más indulgente una vez que sabes dónde están las trampas. Este es el recorrido que ojalá hubiera tenido cuando intenté rodar a mano un parser por primera vez.

Diseño binario de la entrada del ShimCache de Windows 10/11

El blob que estás parseando

Empieza por el valor binario en:

HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\AppCompatCache\AppCompatCache

Extráelo del hive SYSTEM (mira dónde se almacena el ShimCache). Lo que obtienes es un único blob, típicamente de decenas a cientos de kilobytes. Cabecera pequeña, luego una secuencia de entradas, cada una empezando con una magic conocida.

La cabecera

Los primeros cuatro bytes en little-endian del blob son el offset de la primera entrada. En Windows 10/11 es comúnmente 0x30, 0x34, 0x35 o 0x36, dependiendo del build. Algunos hives llevan 0x80 aquí, lo que señala un diseño Windows 8.x que sigue usando la firma de entrada 10ts / 00ts.

No hardcodees el tamaño de la cabecera. Lee el primer u32 y valida que el byte en ese offset es el inicio de una entrada reconocida. Si en el offset anunciado no aparece ni 10ts ni 00ts, escanea hacia adelante por 10ts y arranca desde ahí. Los hives reales traen datos ligeramente fuera de spec, y un parser que se rinde al primer byte que no cuadra es un parser que pierde casos.

Una entrada Windows 10/11, byte a byte

Cada entrada moderna empieza con la firma de cuatro bytes 10ts (0x31 0x30 0x74 0x73). Diseño completo:

OffsetTamañoCampo
0x004Magic, "10ts"
0x044Secuencia / desconocido, a menudo ignorado
0x084Tamaño de datos de la entrada, bytes que siguen a este campo
0x0C2Tamaño de la ruta en bytes (UTF-16LE)
0x0ENBytes de la ruta, UTF-16LE, sin terminador
0x0E + N8Hora de última modificación, FILETIME (100-ns desde 1601-01-01 UTC)
0x16 + N4Tamaño de datos finales
0x1A + NMDatos finales, normalmente vacíos o pequeños

El tamaño total de la entrada es 0x0C + entry_data_size. La siguiente entrada empieza inmediatamente después.

Dos notas prácticas:

  1. La ruta es UTF-16LE y no está terminada en null. Usa el campo de tamaño de ruta. No leas hasta encontrar un cero, te comerás la cabecera de la siguiente entrada.
  2. El FILETIME es la mtime $STANDARD_INFORMATION del archivo. No el tiempo en que se ejecutó el programa. No el tiempo en que se insertó la entrada. La mtime del archivo, copiada directamente de NTFS en el momento del examen. El post de prueba de ejecución cubre por qué importa.

Lo que se eliminó en Windows 10

Las versiones anteriores de Windows empaquetaban un flag Executed dentro de cada entrada. Los analistas de Win7 y 8.x aprendieron a leerlo. Windows 10 lo eliminó. Windows 11 no lo volvió a poner. No hay forma dentro del cache de saber si un binario realmente corrió. Necesitas Prefetch, AmCache, o EVTX / Sysmon.

Para los diseños de campos legacy (XP hasta 8.1), mira formatos legacy del ShimCache.

Parsing en la práctica

Un parser que aguanta la revisión:

  1. Valida la magic de cuatro bytes en el offset anunciado por la cabecera.
  2. Itera entradas leyendo el campo de tamaño de datos de la entrada y avanzando.
  3. Decodifica la ruta como UTF-16LE usando el prefijo de longitud.
  4. Convierte el FILETIME al formato de timestamp que prefiera tu tooling downstream. Milisegundos Unix son cómodos. ISO 8601 UTC es lo que los informes realmente quieren.
  5. Saca a la luz la etiqueta de formato OS detectada (Windows 10/11 vs 8.x vs 8.0) para filtrado downstream.
  6. Reproduce SYSTEM.LOG1 y SYSTEM.LOG2 antes de leer el valor. Si tu parser no lo hace, está leyendo un hive obsoleto.

El Shimcache Parser open source implementa los pasos 1 a 5 en Rust y los entrega a tu navegador como WebAssembly. Lee un único archivo de hive y no reproduce los logs de transacciones, así que pasa antes un hive sucio por rla.exe o Registry Explorer. Sin servidor, sin subida. Útil cuando el hive SYSTEM viene de un entorno regulado.

Lectura adicional

Artículos relacionados