Den ShimCache parsen: Das Binärformat von Windows 10 und 11

4 Min. Lesezeit

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.

Windows-10/11-ShimCache-Binär-Eintragslayout

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:

OffsetGrößeFeld
0x004Magic, "10ts"
0x044Sequenz / unbekannt, oft ignoriert
0x084Eintragsdatengröße, Bytes nach diesem Feld
0x0C2Pfadgröße in Bytes (UTF-16LE)
0x0ENPfad-Bytes, UTF-16LE, kein Terminator
0x0E + N8Last-Modified-Zeit, FILETIME (100-ns seit 1601-01-01 UTC)
0x16 + N4Trailing-Datengröße
0x1A + NMTrailing-Daten, meist leer oder klein

Die Gesamteintragsgröße ist 0x0C + entry_data_size. Der nächste Eintrag beginnt direkt danach.

Zwei praktische Hinweise:

  1. 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.
  2. Die FILETIME ist 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:

  1. Validiert die vier Byte lange Magic am durch den Header angekündigten Offset.
  2. Iteriert Einträge, indem er das Eintragsdatengrößenfeld liest und vorrückt.
  3. Dekodiert den Pfad als UTF-16LE mit dem Längen-Präfix.
  4. Konvertiert die FILETIME in das Zeitstempelformat, das deine Downstream-Werkzeuge bevorzugen. Unix-Millisekunden sind bequem. ISO 8601 UTC ist das, was Berichte tatsächlich wollen.
  5. Bringt das erkannte OS-Format-Label (Windows 10/11 vs. 8.x vs. 8.0) zur Anzeige für Downstream-Filterung.
  6. Spielt SYSTEM.LOG1 und SYSTEM.LOG2 vor 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

Verwandte Artikel