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

4 min de lectura

TL;DR. El primer u32 = tamaño de cabecera (típicamente 0x300x36). En ese offset cada entrada empieza con el magic de 4 bytes 10ts, seguido de una cabecera de 12 bytes y un cuerpo de tamaño variable: ruta UTF-16LE (con prefijo de longitud, no terminada en nulo), mtime FILETIME y un blob de datos final. El flag de ejecución se eliminó en Windows 10.

Cada versión de Windows desde XP ha enviado un diseño distinto del ShimCache. La variante Windows 10 / 11 es la que más ven hoy los investigadores y también la más indulgente de analizar — una vez conoces las reglas. Este artículo recorre el formato de principio a fin.

Diseño binario de una entrada ShimCache de Windows 10/11

El blob que vas a analizar

Empiezas con el valor binario en:

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

Si has extraído ese valor de la colmena SYSTEM (mira dónde se almacena el ShimCache), lo que tienes es un único blob, normalmente de decenas a cientos de kilobytes. El blob contiene una pequeña cabecera seguida de una secuencia de entradas, cada una comenzando con un valor magic conocido.

La cabecera

Los primeros cuatro bytes little-endian del blob dan el offset de la primera entrada. En Windows 10 / 11 suele ser 0x30, 0x34, 0x35 o 0x36, según el build. Algunas colmenas ponen 0x80 aquí — señal de un diseño Windows 8.x que usa la misma firma de entrada 10ts / 00ts.

Un parser defensivo no codifica la cabecera por defecto; lee el primer u32 y valida que el byte en ese offset comienza una entrada reconocida. Si ni 10ts ni 00ts aparecen en el offset declarado, escanea hacia adelante buscando 10ts y retoma allí — esto maneja colmenas ligeramente fuera de especificación que los sistemas reales producen a veces.

Una entrada de Windows 10 / 11, byte por byte

Cada entrada moderna comienza con la firma de cuatro bytes 10ts (0x31 0x30 0x74 0x73). El diseño completo, en orden, es:

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

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

Dos notas prácticas:

  1. La ruta es UTF-16LE y no lleva terminación nula. Usa el campo de tamaño de ruta; no leas hasta encontrar un null.
  2. El FILETIME es el mtime $STANDARD_INFORMATION del archivo, no el momento en que se ejecutó el programa. El artículo sobre prueba de ejecución explica por qué importa.

Qué se eliminó en Windows 10

Las versiones anteriores de Windows empaquetaban un flag Executed dentro de cada entrada. En Windows 10 y 11 se eliminó por completo. No hay forma en el caché de decir si un binario realmente se ejecutó — necesitas Prefetch, AmCache o registros de eventos para eso.

En la práctica

Un parser robusto:

  1. Valida el magic de cuatro bytes en el offset anunciado por la cabecera.
  2. Itera entradas leyendo el campo de tamaño y avanzando.
  3. Decodifica la ruta como UTF-16LE.
  4. Convierte el FILETIME al formato de marca temporal que prefiera tu herramienta aguas abajo (Unix ms es conveniente).
  5. Expone la etiqueta de formato del SO (Windows 10/11 vs 8.x vs 8.0) para filtrado posterior.

Si quieres una referencia funcional, el Shimcache Parser open source implementa exactamente el algoritmo de arriba en Rust y lo envía a tu navegador como WebAssembly — sin servidor, sin subida.

Parsers de referencia

Artículos relacionados