Jump List Parser
Zurück zum Blog

Das Dateiformat customDestinations-ms erklärt

2026-05-253 Min. Lesezeit

Im Gegensatz zu automaticDestinations-ms, das Windows automatisch aus den pro Anwendung geführten MRU-Daten zusammenstellt, wird eine customDestinations-ms-Datei von der Anwendung selbst über die COM-API ICustomDestinationList geschrieben. Ihr Zweck ist es, das zu veröffentlichen, was die App explizit auf ihrer Jump List anzeigen möchte — "Tasks", angeheftete Elemente und beliebige benannte Kategorien. Das On-Disk-Format ist ein flacher, sequentieller Strom kategorisierter Shell-Links und keine OLE-Compound-Datei: ein kleiner Header, gefolgt von einer Reihe von Kategorieblöcken, die jeweils einen Zähler und anschließend ebenso viele serialisierte LNK-Strukturen enthalten.

Warum es CustomDestinations gibt

AutomaticDestinations beantwortet die Frage "Was hat der Benutzer zuletzt angefasst?". CustomDestinations beantwortet "Was möchte die Anwendung anbieten?". Der Entwickler ruft BeginList auf, hängt IObjectCollection- Instanzen über AppendCategory, AppendKnownCategory oder AddUserTasks an und schließt mit CommitList ab. Die Shell serialisiert das Ergebnis nach %AppData%\Microsoft\Windows\Recent\CustomDestinations\<AppID>.customDestinations-ms.

Typische Inhalte sind eine "Tasks"-Kategorie (von der App definierte Verben wie "New Incognito Window" oder "New Private Window"), eine bekannte Kategorie wie Frequent oder Recent, die die App an die Shell delegiert, und eine oder mehrere benutzerdefinierte Kategorien mit beliebigen UTF-16-Namen (Browser, IDEs und Mediaplayer nutzen das ausgiebig).

On-Disk-Struktur

Die Datei beginnt mit einem festen Header, der einen Magic Value und eine Formatversion enthält, gefolgt von einem Zähler der Kategorieeinträge. Jeder Kategorieeintrag kodiert:

  • ein type-Feld — 0 für eine benutzerdefinierte (von der App benannte) Kategorie, 1 für eine bekannte Kategorie wie Frequent oder Recent und 2 für die "Tasks"-Kategorie;
  • bei benutzerdefinierten Kategorien einen UTF-16LE-Namen, dem seine Länge in Zeichen (nicht Bytes) vorangestellt ist;
  • einen entry_count;
  • ebenso viele serialisierte LNK-Strukturen, direkt aneinandergereiht.
[ header: magic + version + category_count ]
repeat category_count times:
    [ type (0=custom | 1=known | 2=tasks) ]
    [ if type==0: name_len_chars + UTF-16LE name ]
    [ entry_count ]
    [ LNK #1 ][ LNK #2 ] ... [ LNK #entry_count ]
[ trailing footer / padding ]

Vor jedem LNK steht kein eintragsspezifisches Längenpräfix — der Shell-Link wird in situ geparst, und die LNK-Struktur selbst gibt an, wo sie endet.

Der LNK-Payload

Jedes Ziel ist ein standardkonformer MS-SHLLINK- Shell-Link, in der Form identisch mit den nummerierten Streams in AutomaticDestinations. Das heißt, derselbe LNK-Parser wird wiederverwendet: ShellLinkHeader, optionale LinkTargetIDList, LinkInfo, die StringData-Blöcke und etwaige ExtraData-Blöcke (insbesondere der TrackerDataBlock mit seinen Volume-/Machine-IDs und Droid-GUIDs). Zielpfad, Arbeitsverzeichnis, Kommandozeilenargumente, Icon-Pfad und die drei FILETIME-Zeitstempel stammen alle aus dem LNK selbst — der CustomDestinations-Container dupliziert sie nicht.

Angeheftete Elemente und die "Tasks"-Kategorie

Die "Tasks"-Kategorie (type 2) enthält vom Entwickler definierte Start-Verben statt Benutzerdokumenten — "Open new window", "Compose mail", "Start a private session". Diese LNKs zeigen typischerweise auf die Anwendungs-EXE mit spezifischen Kommandozeilenargumenten.

Angeheftete Einträge werden neben anderen Kategorien gespeichert, überleben aber Jump-List-Resets und das explizite Löschen zuletzt verwendeter Elemente, was sie bei der Triage zu einem nützlichen Persistenzsignal macht: Ein Pfad, den ein Benutzer aktiv angeheftet hat, impliziert Absicht und nicht nur beiläufige MRU-Aktivität.

Praktische Parsing-Hinweise

  • Strikt sequentiell lesen. Es gibt kein zentrales Verzeichnis; Offsets sind implizit über die Cursor-Position gegeben.
  • Es gibt keine OLE-Schicht — nicht versuchen, die Datei mit einem Compound-File-Reader zu öffnen. Sie ist ein roher Byte-Strom.
  • UTF-16LE-Strings sind längenpräfixiert in Zeichen, daher vor dem Vorrücken des Cursors mit zwei multiplizieren.
  • Mit abschließenden Footer-Bytes oder Padding nach der letzten Kategorie rechnen; einen kurzen Tail tolerieren, statt ihn als Korruption zu werten.
  • Den vorhandenen LNK-Parser unverändert wiederverwenden; das Format ist von Microsoft im Rahmen der Shell-Entwicklerdokumentation beschrieben und wurde von Volatility, libforensics, fox-it und kacos2000 reverse-engineered.

Wenn Sie eine Beispieldatei haben, probieren Sie sie im In-Browser-Parser aus — er dekodiert beide Dateitypen nebeneinander.