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.
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 :
| Offset | Taille | Champ |
|---|---|---|
| 0x00 | 4 | Magic, "10ts" |
| 0x04 | 4 | Séquence / inconnu, souvent ignoré |
| 0x08 | 4 | Taille des données de l'entrée, octets suivant ce champ |
| 0x0C | 2 | Taille du chemin en octets (UTF-16LE) |
| 0x0E | N | Octets du chemin, UTF-16LE, sans terminateur |
| 0x0E + N | 8 | Heure de dernière modification, FILETIME (100-ns depuis 1601-01-01 UTC) |
| 0x16 + N | 4 | Taille des données de fin |
| 0x1A + N | M | Donné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 :
- 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.
- Le
FILETIMEest la mtime$STANDARD_INFORMATIONdu 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 :
- Valide la magic de quatre octets à l'offset annoncé par l'en-tête.
- Itère les entrées en lisant le champ de taille des données de l'entrée et en avançant.
- Décode le chemin en UTF-16LE en utilisant le préfixe de longueur.
- Convertit le
FILETIMEdans 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. - Fait remonter l'étiquette du format OS détecté (Windows 10/11 vs 8.x vs 8.0) pour le filtrage downstream.
- Rejoue
SYSTEM.LOG1etSYSTEM.LOG2avant 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
- AppCompatCacheParser d'Eric Zimmerman. implémentation C# étalon-or couvrant toutes les variantes Windows.
- Shim Cache Parser de Mandiant. proof-of-concept Python d'origine, utile comme référence croisée de format.
- Velociraptor
Windows.Registry.AppCompatCache. artefact VQL pour la collecte live. - Windows 10/11 AppCompatCache deep dive sur Ø Security. le write-up public le plus complet de la disposition moderne.