Jump List Parser
Zurück zum Blog

Das Dateiformat AutomaticDestinations-ms erklärt

2026-05-255 Min. Lesezeit

Das Format ist nicht exotisch, sobald man es zweimal gelesen hat. Eine automaticDestinations-ms ist eine OLE-Compound-Datei, derselbe Container, der auch Office-Dokumente vor 2007 enthält. Darin findet sich pro kürzlich verwendetem Element ein Stream, der jeweils einen Standard-Shell-Link enthält, sowie ein einzelner DestList-Stream, der sie ordnet und jedem Eintrag Zeitstempel zuordnet. Das ist die ganze Geschichte. Der Rest dieses Beitrags ist das Felddetail, das man braucht, wenn ein Parser sich verschiebt und man von Hand bestätigen muss, was die Bytes aussagen.

Der äußere Container ist MS-CFB, nicht mehr

Jede .automaticDestinations-ms beginnt mit der MS-CFB-Header-Magic D0 CF 11 E0 A1 B1 1A E1. Die Sektorgröße beträgt bei jeder Jump List, die ich untersucht habe, 512 Byte, was bedeutet, dass die FAT, die Verzeichniskette und der Mini-Stream (für Einträge unter 4096 Byte) sich genauso verhalten wie in einer alten .doc. Wenn Sie bereits einen CFB-Parser haben, richten Sie ihn auf die Datei und Sie sind schon halb fertig.

Was Sie aus dem Verzeichnis lesen, ist eine flache Liste. Es gibt keine verschachtelten Speicher, keine überraschenden Sub-Container. Pro kürzlich verwendetem Ziel ein Stream, plus eine DestList. Stream-Namen sind kleingeschriebene hexadezimale Ganzzahlen: 1, 2, a, 1f und so weiter. Der Stream-Name ist auch die Eintrags-ID. Behalten Sie sie im Auge; sie ist es, was jedes LNK zurück zu einer DestList-Zeile verknüpft.

Nummerierte Streams sind LNK, durch und durch

Jeder nummerierte Stream ist ein wortwörtlicher Shell-Link gemäß [MS-SHLLINK]. Das bedeutet, dass der Parser, den Sie bereits für .lnk-Dateien auf der Festplatte verwenden, sie ohne Modifikation dekodiert: ShellLinkHeader, optionale LinkTargetIDList, LinkInfo (mit VolumeID, LocalBasePath, CommonNetworkRelativeLink), die StringData-Strings (NAME_STRING, RELATIVE_PATH, WORKING_DIR, COMMAND_LINE_ARGUMENTS, ICON_LOCATION) und die ExtraData-Blöcke (TrackerDataBlock, PropertyStoreDataBlock, EnvironmentVariableDataBlock, KnownFolderDataBlock). Das Byte-Layout innerhalb des Streams ist identisch mit einem eigenständigen Shell-Link, die CFB-Schicht fügt nichts darüber hinaus hinzu.

Diese Symmetrie ist es wert, verinnerlicht zu werden. Wenn Sie eines lesen können, können Sie beide lesen.

Der DestList-Stream: 32-Byte-Header, Einträge variabler Länge

DestList ist das, was die Sammlung von LNK-Streams zu einem geordneten, datierten MRU macht. Er beginnt mit einem 32-Byte-Header und enthält dann eine Sequenz von Datensätzen variabler Länge.

DestList header (32 bytes, little-endian)
offset  size  field
0x00    4     version              (0x01..0x04 observed; Win7=1, Win10+=3/4)
0x04    4     number of entries
0x08    4     number of pinned entries
0x0C    4     unused / reserved    (often a float; varies by version)
0x10    4     last entry ID issued
0x14    4     unused / reserved
0x18    4     last revision number
0x1C    4     unused / reserved

Jeder Eintragsdatensatz nach diesem Header trägt die Metadaten, die zur Rekonstruktion benötigt werden, woher das LNK kam:

DestList entry (variable length)
field                       size    notes
checksum / reserved         8       version-dependent
NewVolumeID (GUID)          16      target volume
NewObjectID (GUID)          16      target object (file)
BirthVolumeID (GUID)        16      original volume
BirthObjectID (GUID)        16      original object
NetBIOS hostname            16      ASCII, NUL-padded
entry ID                    4       matches the numbered stream name
reserved                    4
access count                4       IEEE-754 float; sentinel = pinned
last access FILETIME        8       100-ns ticks since 1601-01-01 UTC
entry pin status / reserved 4       layout differs by version
name length (chars)         2       UTF-16LE code units
name                        2*N     UTF-16LE, not NUL-terminated
trailer                     4       only on DestList v3/v4

Die Versionen 3 und 4 (Windows 10 und später) hängen an jeden Eintrag einen kleinen Trailer an und verschieben einige reservierte Felder. Die Feldnamen und Bedeutungen oben bleiben stabil, aber die genauen Offsets verschieben sich zwischen Versionen. Verzweigen Sie auf die version im Header, bevor Sie das Entry-Parsing anfassen. Ältere Parser, die das v1-Layout fest verdrahten, lesen moderne Dateien stillschweigend falsch, die Einträge dekodieren, die Zeitstempel sind verschoben, der Hostname sieht aus wie Müll, und es wird kein Fehler ausgelöst.

Das Access-Count-Feld trägt das Pinned-Flag

Das stolpert die Leute. Der Header sagt "Anzahl der angehefteten Einträge", aber der Pin-Marker pro Eintrag ist in das Access-Count-Feld selbst codiert. Nicht angeheftete Einträge speichern einen normalisierten Float, der Öffnungen zählt. Angeheftete Einträge speichern einen Sentinel, historisch 0xFFFFFFFF, interpretiert als 32-Bit-Float, der als NaN dekodiert wird. Der genaue Sentinel-Wert und die Position eines zusätzlichen Pin-Flags wandern zwischen DestList-Versionen. Behandeln Sie die Zählung als undurchsichtig, bis Sie die Version geprüft haben.

Angeheftete Einträge sind operativ interessant. Sie implizieren Nutzerabsicht, jemand hat mit Rechtsklick "An diese Liste anheften" gewählt, und sie überleben gewöhnliche "Zuletzt verwendete Elemente löschen"-Resets. Wenn eine Jump List bereinigt wurde, aber angeheftete Einträge bleiben, haben Sie immer noch etwas.

Praktische Parsing-Hinweise

  • Endianness. Alles ist Little-Endian, einschließlich der GUIDs (Data1/Data2/Data3 sind Little-Endian; Data4 ist ein Byte-Array). Dieselbe Konvention, die Microsoft überall sonst verwendet.
  • FILETIME. unix_seconds = (filetime / 10_000_000) - 11_644_473_600. Bleiben Sie in UTC. Jump Lists tragen keine Zeitzoneninformation.
  • Hostname. Der 16-Byte NetBIOS-Name ist die Workstation, die die Datei geöffnet hat. In einer Domäne ist dieses Feld ein Geschenk, es korreliert Aktivität über Hosts hinweg, ohne dass die Logs jedes Servers benötigt werden.
  • Entry ID. Die 4-Byte entry ID in jeder DestList-Zeile passt zu einem der Hex-Stream-Namen im CFB-Verzeichnis. Das ist der Join-Schlüssel.
  • Versions-Skew. Win7 schrieb v1. Win8/8.1 führte v3 ein. Win10 und Win11 geben üblicherweise v3 oder v4 aus. Bestätigen Sie den Header zuerst; nehmen Sie nichts an.
  • Reihenfolge. DestList-Zeilen sind auf der Festplatte nicht in MRU-Reihenfolge. Sortieren Sie nach last access FILETIME, wenn Sie die Sicht des Benutzers auf "am aktuellsten" wollen. Sortieren Sie nach entry ID, wenn Sie die Schreibreihenfolge wollen.

Die ursprüngliche Reverse-Engineering-Arbeit auf Byte-Ebene für DestList stammt von Yogesh Khatri und Mike Stevens. Eric Zimmermans JLECmd ist der kanonische Offline-Parser und das de-facto-Schema für die Ausgabe. Kacos2000s Jumplist-Browser PowerShell-Viewer ist nützlich, wenn Sie die rohen Strukturen sehen wollen anstatt einer normalisierten Sicht.

Um an einer echten Datei zu experimentieren, ohne etwas zu installieren, legen Sie eine in den In-Browser-Parser auf der Startseite. Er führt einen zu WebAssembly kompilierten Rust-DestList- und LNK-Dekoder aus, vollständig clientseitig. Für Hintergründe dazu, woher diese Dateien kommen und warum sie für eine Untersuchung wichtig sind, siehe die forensische Einführung.

Weiterführende Literatur

  • Yogesh Khatri und Mike Stevens, "Jumping into the Past", die ursprüngliche DestList-Reverse-Engineering-Arbeit. Immer noch die beste Einzelreferenz für das Feldlayout.
  • Microsofts [MS-CFB]-Spezifikation auf Microsoft Learn, für die Container-Schicht.
  • Microsofts [MS-SHLLINK]-Spezifikation, für die LNK-Nutzlast in jedem nummerierten Stream.
  • Der LNK-Parser und MFT-Parser zur Bestätigung, wenn die Zeitachse wichtig ist.