Parser le ShimCache : le format binaire de Windows 10 et 11

5 min de lecture

TL;DR. Premier u32 = taille de l'en-tête, généralement 0x30 à 0x36. À cet offset, chaque entrée commence par la magic de quatre octets 10ts, suivie d'un en-tête de 12 octets et d'un corps à longueur variable : chemin UTF-16LE (préfixé par longueur, pas terminé par null), mtime FILETIME, et un blob de données de fin. Le flag d'exécution a été retiré dans Windows 10.

Chaque release Windows depuis XP a embarqué une disposition AppCompatCache différente. La variante Windows 10/11 est celle que les enquêteurs voient la plupart des jours, et c'est la plus indulgente une fois que tu sais où vivent les pièges. Voici le parcours que j'aurais aimé avoir quand j'ai essayé pour la première fois de coder un parser à la main.

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

Le blob que tu parses

Commence par la valeur binaire à :

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

Extrais-la du hive SYSTEM (voir où le ShimCache est stocké). Ce que tu obtiens, c'est un seul blob, typiquement de dizaines à centaines de kilo-octets. Petit en-tête, puis une séquence d'entrées, chacune commençant par une magic connue.

L'en-tête

Les quatre premiers octets little-endian du blob sont 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. Certains hives portent 0x80 ici, ce qui signale une disposition Windows 8.x qui utilise toujours la signature d'entrée 10ts / 00ts.

Ne hardcode pas la taille de l'en-tête. Lis le premier u32 et valide que l'octet à cet offset est le début d'une entrée reconnue. Si ni 10ts ni 00ts n'apparaît à l'offset annoncé, scanne vers l'avant pour 10ts et reprends à partir de là. Les hives réels embarquent des données légèrement hors spec, et un parser qui abandonne au premier mismatch d'octet est un parser qui perd des dossiers.

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). Disposition complète :

OffsetTailleChamp
0x004Magic, "10ts"
0x044Séquence / inconnu, souvent ignoré
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 + N8Heure de dernière modification, FILETIME (100-ns depuis 1601-01-01 UTC)
0x16 + N4Taille des données de fin
0x1A + NMDonnées de fin, généralement vides ou petites

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

Deux notes pratiques :

  1. Le chemin est en UTF-16LE et n'est pas terminé par null. Utilise le champ de taille du chemin. Ne lis pas jusqu'à rencontrer un zéro, tu manges l'en-tête de l'entrée suivante.
  2. Le FILETIME est la mtime $STANDARD_INFORMATION du fichier. Pas l'heure à laquelle le programme a tourné. Pas l'heure à laquelle l'entrée a été insérée. La mtime du fichier, copiée directement de NTFS au moment de l'examen. Le billet preuve d'exécution couvre pourquoi ça compte.

Ce qui a été retiré dans Windows 10

Les versions antérieures de Windows empaquetaient un flag Executed dans chaque entrée. Les analystes Win7 et 8.x ont appris à le lire. Windows 10 l'a retiré. Windows 11 ne l'a pas remis. Il n'y a aucun moyen, à l'intérieur du cache, de savoir si un binaire a réellement tourné. Tu as besoin de Prefetch, AmCache, ou EVTX / Sysmon.

Pour les dispositions de champs legacy (XP à 8.1), voir formats legacy du ShimCache.

Le parsing en pratique

Un parser qui survit à la revue :

  1. Valide la magic de quatre octets à l'offset annoncé par l'en-tête.
  2. Itère les entrées en lisant le champ de taille des données de l'entrée et en avançant.
  3. Décode le chemin en UTF-16LE en utilisant le préfixe de longueur.
  4. Convertit le FILETIME dans le format d'horodatage que ton outillage downstream préfère. Les millisecondes Unix sont pratiques. ISO 8601 UTC est ce que les rapports veulent réellement.
  5. Fait remonter l'étiquette du format OS détecté (Windows 10/11 vs 8.x vs 8.0) pour le filtrage downstream.
  6. Rejoue SYSTEM.LOG1 et SYSTEM.LOG2 avant de lire la valeur. Si ton parser ne le fait pas, il lit un hive périmé.

Le Shimcache Parser open source implémente les étapes 1 à 5 en Rust et les livre à ton navigateur en WebAssembly. Il lit un seul fichier de hive et ne rejoue pas les logs de transaction, donc passe d'abord un hive dirty dans rla.exe ou Registry Explorer. Pas de serveur, pas de téléversement. Utile quand le hive SYSTEM vient d'un environnement réglementé.

Lectures complémentaires

Articles liés