Analyser le ShimCache : le format binaire Windows 10 et 11

4 min de lecture

TL;DR. Le premier u32 = taille d'en-tête (typiquement 0x300x36). À cet offset, chaque entrée commence par le magic à 4 octets 10ts, suivi d'un en-tête de 12 octets et d'un corps de taille variable : chemin UTF-16LE (préfixé en longueur, sans terminateur nul), mtime FILETIME, et un blob de données final. Le drapeau d'exécution a été supprimé sous Windows 10.

Chaque version de Windows depuis XP livre une mise en page différente du ShimCache. La variante Windows 10 / 11 est la plus rencontrée aujourd'hui par les enquêteurs, et aussi la plus indulgente à analyser — une fois les règles connues. Ce billet parcourt le format de bout en bout.

Disposition binaire d'une entrée ShimCache Windows 10/11

Le blob à analyser

Vous partez de la valeur binaire à :

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

Si vous avez extrait cette valeur de la ruche SYSTEM (voir où le ShimCache est stocké), vous disposez d'un blob unique, typiquement de quelques dizaines à quelques centaines de kilo-octets. Le blob contient un petit en-tête suivi d'une séquence d'entrées, chacune commençant par une valeur magic connue.

L'en-tête

Les quatre premiers octets little-endian du blob donnent l'offset de la première entrée. Sur Windows 10 / 11, c'est généralement 0x30, 0x34, 0x35 ou 0x36, selon le build. Certaines ruches y placent 0x80 — signal d'une mise en page Windows 8.x qui utilise la même signature d'entrée 10ts / 00ts.

Un parser défensif ne code pas en dur la taille d'en-tête ; il lit le premier u32 et valide que l'octet à cet offset commence une entrée reconnue. Si ni 10ts ni 00ts n'apparaissent à l'offset annoncé, scannez vers l'avant pour 10ts et reprenez à partir de là — cela gère les ruches légèrement hors spec que les vrais systèmes produisent parfois.

Une entrée Windows 10 / 11, octet par octet

Chaque entrée moderne commence par la signature de quatre octets 10ts (0x31 0x30 0x74 0x73). La mise en page complète, dans l'ordre, est :

OffsetTailleChamp
0x004Magic — "10ts"
0x044Séquence / inconnu (souvent inutilisé par les parsers)
0x084Taille des données de l'entrée — octets suivant ce champ
0x0C2Taille du chemin en octets (UTF-16LE)
0x0ENOctets du chemin (UTF-16LE, sans terminateur)
0x0E + N8Dernière modification — FILETIME (100 ns depuis 1601-01-01 UTC)
0x16 + N4Taille des données de fin
0x1A + NMDonnées de fin (souvent vides ou petites)

La taille totale de l'entrée est donc 0x0C + entry_data_size. L'entrée suivante débute immédiatement après.

Deux notes pratiques :

  1. Le chemin est en UTF-16LE et n'est pas à terminaison nulle. Utilisez le champ taille de chemin ; ne lisez pas jusqu'à un null.
  2. Le FILETIME est le mtime $STANDARD_INFORMATION du fichier, pas l'heure d'exécution du programme. Le billet sur la preuve d'exécution explique pourquoi cela compte.

Ce qui a été retiré sous Windows 10

Les versions antérieures de Windows empaquetaient un drapeau Executed dans chaque entrée. Sur Windows 10 et 11, ce drapeau a été supprimé entièrement. Il n'y a aucun moyen, dans le cache, de dire si un binaire a réellement tourné — il faut Prefetch, AmCache ou les journaux d'événements pour cela.

En pratique

Un parser robuste :

  1. Valide le magic de quatre octets à l'offset annoncé par l'en-tête.
  2. Itère les entrées en lisant le champ taille de données et en avançant.
  3. Décode le chemin en UTF-16LE.
  4. Convertit le FILETIME au format d'horodatage que préfère votre outillage aval (Unix ms est pratique).
  5. Surface l'étiquette de format OS (Windows 10/11 vs 8.x vs 8.0) pour le filtrage en aval.

Pour une référence opérationnelle, le Shimcache Parser open source implémente exactement l'algorithme ci-dessus en Rust et le livre dans votre navigateur sous forme de WebAssembly — sans serveur, sans upload.

Parsers de référence

Articles connexes