Analizzare lo ShimCache: il formato binario Windows 10 e 11

4 min di lettura

TL;DR. Il primo u32 = dimensione dell'header (tipicamente 0x300x36). A quell'offset ogni voce inizia con la firma a 4 byte 10ts, seguita da un header di 12 byte e da un corpo di lunghezza variabile: percorso UTF-16LE (con prefisso di lunghezza, non terminato da null), mtime FILETIME e un blob di dati finale. Il flag di esecuzione è stato rimosso in Windows 10.

Ogni versione di Windows dal XP ha portato un layout di ShimCache diverso. La variante Windows 10 / 11 è quella che oggi gli investigatori incontrano più spesso, e anche la più clemente da analizzare — una volta che si conoscono le regole. Questo articolo percorre il formato dall'inizio alla fine.

Layout binario di una voce ShimCache di Windows 10/11

Il blob da analizzare

Si parte dal valore binario in:

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

Se hai estratto quel valore dalla hive SYSTEM (vedi dove è memorizzato lo ShimCache), ciò che hai è un singolo blob, tipicamente da decine a centinaia di kilobyte. Il blob contiene una piccola intestazione seguita da una sequenza di voci, ciascuna delle quali inizia con un valore magic noto.

L'intestazione

I primi quattro byte little-endian del blob forniscono l'offset della prima voce. Su Windows 10 / 11 è tipicamente 0x30, 0x34, 0x35 o 0x36, a seconda della build. Alcune hive mettono qui 0x80 — segnale di un layout Windows 8.x che usa la stessa firma di voce 10ts / 00ts.

Un parser difensivo non codifica fissa la dimensione dell'intestazione; legge il primo u32 e valida che il byte a quell'offset sia l'inizio di una voce riconosciuta. Se né 10ts00ts compaiono all'offset dichiarato, scansiona in avanti cercando 10ts e riprendi da lì — questo gestisce hive leggermente fuori specifica che i sistemi reali a volte producono.

Una voce Windows 10 / 11, byte per byte

Ogni voce moderna inizia con la firma di quattro byte 10ts (0x31 0x30 0x74 0x73). Il layout completo, in ordine, è:

OffsetDimensioneCampo
0x004Magic — "10ts"
0x044Sequenza / sconosciuto (spesso ignorato dai parser)
0x084Dimensione dati voce — byte dopo questo campo
0x0C2Dimensione del percorso in byte (UTF-16LE)
0x0ENByte del percorso (UTF-16LE, senza terminatore)
0x0E + N8Ultima modifica — FILETIME (100 ns dal 1601-01-01 UTC)
0x16 + N4Dimensione dei dati finali
0x1A + NMDati finali (di solito vuoti o piccoli)

La dimensione totale della voce è quindi 0x0C + entry_data_size. La voce successiva inizia immediatamente dopo.

Due note pratiche:

  1. Il percorso è UTF-16LE e non è terminato da zero. Usa il campo della dimensione del percorso; non leggere fino a un null.
  2. Il FILETIME è il mtime $STANDARD_INFORMATION del file, non l'ora di esecuzione del programma. Il post sulla prova di esecuzione spiega perché è importante.

Cosa è stato rimosso in Windows 10

Le versioni precedenti di Windows impacchettavano un flag Executed all'interno di ogni voce. In Windows 10 e 11 è stato rimosso completamente. Non c'è modo, nella cache, di dire se un binario sia stato eseguito davvero — servono Prefetch, AmCache o log eventi per quello.

In pratica

Un parser robusto:

  1. Valida il magic di quattro byte all'offset annunciato dall'intestazione.
  2. Itera le voci leggendo il campo dimensione dati e avanzando.
  3. Decodifica il percorso come UTF-16LE.
  4. Converte il FILETIME nel formato di timestamp che preferisce il tuo tooling a valle (Unix ms è comodo).
  5. Espone l'etichetta del formato OS (Windows 10/11 vs 8.x vs 8.0) per il filtraggio a valle.

Se vuoi un riferimento funzionante, lo Shimcache Parser open source implementa esattamente l'algoritmo qui sopra in Rust e lo invia al tuo browser come WebAssembly — niente server, niente upload.

Parser di riferimento

Articoli correlati