Jump List Parser
Zurück zum Blog

Das Dateiformat automaticDestinations-ms erklärt

2026-05-253 Min. Lesezeit

Eine automaticDestinations-ms-Datei ist eine OLE Compound File (CFB / MS-CFB). Jeder nummerierte Stream enthält eine Shell-Verknüpfung (LNK) für ein zuletzt verwendetes Element, und ein einzelner DestList-Stream liefert Reihenfolge, Zeitstempel und den ursprünglichen Hostnamen.

Der äußere Container: OLE Compound File

Die Datei folgt dem Microsoft Compound File Binary Format (MS-CFB), demselben Structured-Storage-Container, der hinter Office-Dokumenten vor 2007 steckt. Intern ist die Datei in Sektoren fester Größe unterteilt (512 Bytes bei den meisten Jump Lists), die über eine FAT verkettet sind, mit einem Mini-Stream für Einträge kleiner als 4096 Bytes. Ein Directory an einem bekannten Sektorindex listet die benannten Streams auf ("Storages" und "Streams" in der CFB-Terminologie).

Für eine Jump List interessiert nur das Directory-Listing. Sie sehen einen flachen Satz von Streams: einen pro zuletzt verwendetem Element, benannt mit einer hexadezimalen Zahl, plus einen einzigen DestList-Stream. Es gibt keine verschachtelten Storages.

Nummerierte Streams: ein LNK pro Eintrag

Jeder Stream-Name ist ein kleingeschriebener Hex-Integer — 1, 2, a, 1f und so weiter — und enthält eine standardmäßige Windows-Shell-Verknüpfung, wie in [MS-SHLLINK] dokumentiert. Das bedeutet: derselbe LNK-Parser, den Sie bereits für .lnk-Dateien auf der Festplatte einsetzen, dekodiert sie: ShellLinkHeader, optionale LinkTargetIDList, LinkInfo, die StringData-Blöcke (NAME_STRING, RELATIVE_PATH, WORKING_DIR, COMMAND_LINE_ARGUMENTS, ICON_LOCATION) und die ExtraData-Blöcke (TrackerDataBlock, PropertyStoreDataBlock und Verwandte).

Der Stream-Name ist die Entry-ID und stellt die Verknüpfung jedes LNK zurück zu einer Zeile in der DestList her.

Der DestList-Stream

DestList ist der Index, der den Beutel von LNKs in eine geordnete, datierte Liste zuletzt verwendeter Elemente verwandelt. Er beginnt mit einem 32-Byte- Header, gefolgt von einer Folge von Eintragsdatensä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 folgende Eintrag trägt — in dieser Reihenfolge — die Metadaten, die nötig sind, um zu rekonstruieren, woher der LNK stammt:

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) fügen pro Eintrag einen kleinen Trailer hinzu und passen einige reservierte Felder an; die Namen und Zwecke der obigen Felder sind stabil, aber die genauen Offsets verschieben sich leicht zwischen den Versionen. Verzweigen Sie immer anhand der Header-version, bevor Sie Einträge parsen.

Angeheftete vs. nicht angeheftete Einträge

Der Header gibt die Anzahl der angehefteten Einträge an, aber der Pin-Marker pro Eintrag ist im Access-Count-Feld kodiert. Nicht angeheftete Elemente speichern einen normalisierten Float, der angibt, wie oft der Benutzer das Ziel geöffnet hat. Angeheftete Elemente speichern einen Sentinel-Wert — historisch 0xFFFFFFFF, als 32-Bit-Float interpretiert (NaN) — um zu kennzeichnen, dass der Eintrag vom Benutzer angeheftet wurde und der Zähler nicht aussagekräftig ist. Der genaue Sentinel und die Position eines zusätzlichen Pin-Flags variieren zwischen DestList-Versionen, behandeln Sie den Zähler daher als opak, bis Sie die Version geprüft haben.

Praktische Parsing-Hinweise

  • Endianness. Alles ist Little-Endian, auch GUIDs (Data1/Data2/Data3 sind Little-Endian; Data4 ist ein Byte-Array).
  • FILETIME. Umrechnung mit unix_seconds = (filetime / 10_000_000) - 11_644_473_600. In UTC belassen; Jump Lists zeichnen keine Zeitzone auf.
  • Hostname. Der 16-Byte-NetBIOS-Name ist die Workstation, die die Datei geöffnet hat. In einer Domäne ist er unschätzbar wertvoll, um Aktivitäten über mehrere Hosts hinweg zu korrelieren.
  • Entry ID. Vergleichen Sie die 4-Byte-entry ID aus jeder DestList-Zeile mit den Hex-Stream-Namen im CFB-Directory; das ist Ihr Join-Key.
  • Versionsabweichungen. Windows 7 erzeugte DestList v1. Windows 8/8.1 führte v3 ein; Windows 10 und 11 emittieren üblicherweise v3 oder v4. Ältere Parser, die ein v1-Layout annehmen, lesen moderne Dateien falsch — lesen Sie immer zuerst den Header.
  • Reihenfolge. Nehmen Sie nicht an, dass die DestList-Zeilen in MRU-Reihenfolge vorliegen. Sortieren Sie nach last access FILETIME (oder nach last entry ID minus Offset, je nachdem was Sie benötigen).

Um eine reale Datei zu untersuchen, ohne Code zu schreiben, legen Sie eine im In-Browser-Parser ab — er führt einen zu WebAssembly kompilierten Rust- DestList- und LNK-Parser aus, vollständig clientseitig. Für Hintergründe dazu, woher diese Dateien stammen und warum sie für eine Untersuchung wichtig sind, siehe die forensische Einführung.