TL;DR. Erstes u32 = Header-Größe, typischerweise 0x30 bis 0x36. An diesem Offset beginnt jeder Eintrag mit der vier Byte langen Magic 10ts, gefolgt von einem 12-Byte-Header und einem Body variabler Länge: UTF-16LE-Pfad (längenpräfixiert, nicht null-terminiert), FILETIME-mtime und ein abschließender Daten-Blob. Das Ausführungs-Flag wurde in Windows 10 entfernt.
Jedes Windows-Release seit XP hat ein anderes AppCompatCache-Layout ausgeliefert. Die Windows-10/11-Variante ist die, die Ermittler an den meisten Tagen sehen, und sie ist am verzeihendsten, wenn man weiß, wo die Fallen sind. Das ist der Walkthrough, den ich mir gewünscht hätte, als ich zum ersten Mal versuchte, einen Parser von Hand zu rollen.
Der Blob, den du parsest
Beginne mit dem binären Wert unter:
HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\AppCompatCache\AppCompatCache
Extrahiere ihn aus dem SYSTEM-Hive (siehe wo der ShimCache gespeichert ist). Was du bekommst, ist ein einzelner Blob, typischerweise zehn bis hunderte Kilobyte. Kleiner Header, dann eine Folge von Einträgen, jeder mit einer bekannten Magic beginnend.
Der Header
Die ersten vier Little-Endian-Bytes des Blobs sind der Offset des ersten Eintrags. Unter Windows 10/11 ist das je nach Build üblicherweise 0x30, 0x34, 0x35 oder 0x36. Manche Hives tragen hier 0x80, was ein Windows-8.x-Layout signalisiert, das immer noch die Eintragssignatur 10ts / 00ts verwendet.
Hardcode die Header-Größe nicht. Lies das erste u32 und validiere, dass das Byte an diesem Offset der Start eines erkannten Eintrags ist. Wenn am angekündigten Offset weder 10ts noch 00ts auftaucht, scanne nach 10ts vorwärts und nimm ab da auf. Reale Hives liefern leicht von der Spezifikation abweichende Daten, und ein Parser, der beim ersten Byte-Mismatch aussteigt, ist ein Parser, der Fälle verliert.
Ein Windows-10/11-Eintrag, Byte für Byte
Jeder moderne Eintrag beginnt mit der vier Byte langen Signatur 10ts (0x31 0x30 0x74 0x73). Vollständiges Layout:
| Offset | Größe | Feld |
|---|---|---|
| 0x00 | 4 | Magic, "10ts" |
| 0x04 | 4 | Sequenz / unbekannt, oft ignoriert |
| 0x08 | 4 | Eintragsdatengröße, Bytes nach diesem Feld |
| 0x0C | 2 | Pfadgröße in Bytes (UTF-16LE) |
| 0x0E | N | Pfad-Bytes, UTF-16LE, kein Terminator |
| 0x0E + N | 8 | Last-Modified-Zeit, FILETIME (100-ns seit 1601-01-01 UTC) |
| 0x16 + N | 4 | Trailing-Datengröße |
| 0x1A + N | M | Trailing-Daten, meist leer oder klein |
Die Gesamteintragsgröße ist 0x0C + entry_data_size. Der nächste Eintrag beginnt direkt danach.
Zwei praktische Hinweise:
- Der Pfad ist UTF-16LE und nicht null-terminiert. Nutze das Pfadgrößenfeld. Lies nicht, bis du eine Null triffst, du frisst sonst den Header des nächsten Eintrags.
- Die
FILETIMEist die$STANDARD_INFORMATION-mtime der Datei. Nicht die Zeit, zu der das Programm lief. Nicht die Zeit, zu der der Eintrag eingefügt wurde. Die mtime der Datei, direkt zur Untersuchungszeit aus NTFS kopiert. Der Proof-of-Execution-Post deckt ab, warum das wichtig ist.
Was in Windows 10 entfernt wurde
Frühere Windows-Versionen packten ein Executed-Flag in jeden Eintrag. Win7- und 8.x-Analysten lernten, es zu lesen. Windows 10 entfernte es. Windows 11 brachte es nicht zurück. Es gibt keinen Weg im Cache, festzustellen, ob eine Binärdatei tatsächlich lief. Du brauchst Prefetch, AmCache oder EVTX / Sysmon.
Für die Legacy-Feld-Layouts (XP bis 8.1) siehe Legacy-ShimCache-Formate.
Parsen in der Praxis
Ein Parser, der das Review übersteht:
- Validiert die vier Byte lange Magic am durch den Header angekündigten Offset.
- Iteriert Einträge, indem er das Eintragsdatengrößenfeld liest und vorrückt.
- Dekodiert den Pfad als UTF-16LE mit dem Längen-Präfix.
- Konvertiert die
FILETIMEin das Zeitstempelformat, das deine Downstream-Werkzeuge bevorzugen. Unix-Millisekunden sind bequem. ISO 8601 UTC ist das, was Berichte tatsächlich wollen. - Bringt das erkannte OS-Format-Label (Windows 10/11 vs. 8.x vs. 8.0) zur Anzeige für Downstream-Filterung.
- Spielt
SYSTEM.LOG1undSYSTEM.LOG2vor dem Lesen des Werts ab. Wenn dein Parser das nicht tut, liest er einen veralteten Hive.
Der quelloffene Shimcache Parser implementiert die Schritte 1 bis 5 in Rust und liefert sie als WebAssembly an deinen Browser. Er liest eine einzelne Hive-Datei und spielt keine Transaktionslogs ab, also schick einen dirty Hive vorher durch rla.exe oder Registry Explorer. Kein Server, kein Upload. Nützlich, wenn der SYSTEM-Hive aus einer regulierten Umgebung kam.
Weiterführende Lektüre
- Eric Zimmermans AppCompatCacheParser. C#-Goldstandard-Implementierung, die jede Windows-Variante abdeckt.
- Mandiants Shim Cache Parser. ursprünglicher Python-Proof-of-Concept, nützlich als Format-Quervergleich.
- Velociraptor
Windows.Registry.AppCompatCache. VQL-Artefakt für Live-Erfassung. - Windows 10/11 AppCompatCache deep dive auf Ø Security. das gründlichste öffentliche Write-up des modernen Layouts.