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.
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:
| Offset | Tamaño | Campo |
|---|---|---|
| 0x00 | 4 | Magic, "10ts" |
| 0x04 | 4 | Secuencia / desconocido, a menudo ignorado |
| 0x08 | 4 | Tamaño de datos de la entrada, bytes que siguen a este campo |
| 0x0C | 2 | Tamaño de la ruta en bytes (UTF-16LE) |
| 0x0E | N | Bytes de la ruta, UTF-16LE, sin terminador |
| 0x0E + N | 8 | Hora de última modificación, FILETIME (100-ns desde 1601-01-01 UTC) |
| 0x16 + N | 4 | Tamaño de datos finales |
| 0x1A + N | M | Datos 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:
- 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.
- El
FILETIMEes la mtime$STANDARD_INFORMATIONdel 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:
- Valida la magic de cuatro bytes en el offset anunciado por la cabecera.
- Itera entradas leyendo el campo de tamaño de datos de la entrada y avanzando.
- Decodifica la ruta como UTF-16LE usando el prefijo de longitud.
- Convierte el
FILETIMEal formato de timestamp que prefiera tu tooling downstream. Milisegundos Unix son cómodos. ISO 8601 UTC es lo que los informes realmente quieren. - Saca a la luz la etiqueta de formato OS detectada (Windows 10/11 vs 8.x vs 8.0) para filtrado downstream.
- Reproduce
SYSTEM.LOG1ySYSTEM.LOG2antes 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
- AppCompatCacheParser de Eric Zimmerman. implementación C# estándar de oro que cubre todas las variantes de Windows.
- Shim Cache Parser de Mandiant. proof-of-concept Python original, útil como referencia cruzada de formato.
- Velociraptor
Windows.Registry.AppCompatCache. artefacto VQL para recolección en vivo. - Windows 10/11 AppCompatCache deep dive en Ø Security. el write-up público más exhaustivo del diseño moderno.