Jump List Parser
Retour au blog

Le format de fichier CustomDestinations-ms expliqué

2026-05-254 min de lecture

Contrairement à automaticDestinations-ms, que Windows assemble automatiquement à partir des données MRU par application, un fichier customDestinations-ms est écrit par l'application elle-même via l'API COM ICustomDestinationList. Son rôle est de publier ce que l'application veut explicitement voir dans sa Jump List — "Tasks", éléments épinglés et catégories nommées arbitraires. Le format sur disque est un flux plat et séquentiel de raccourcis shell catégorisés, et non un OLE Compound File : un petit en-tête suivi d'une série de blocs de catégories, chacun contenant un compteur puis autant de structures LNK sérialisées.

Pourquoi CustomDestinations existe

AutomaticDestinations répond à « qu'est-ce que l'utilisateur a touché récemment ? ». CustomDestinations répond à « qu'est-ce que l'application veut proposer ? ». Le développeur appelle BeginList, ajoute des instances IObjectCollection via AppendCategory, AppendKnownCategory ou AddUserTasks, puis finalise avec CommitList. Le shell sérialise le résultat dans %AppData%\Microsoft\Windows\Recent\CustomDestinations\<AppID>.customDestinations-ms.

Le contenu typique inclut une catégorie "Tasks" (verbes définis par l'application tels que « New Incognito Window » ou « New Private Window »), une catégorie known comme Frequent ou Recent que l'application délègue au shell, et une ou plusieurs catégories custom avec des noms UTF-16 arbitraires (les navigateurs, IDE et lecteurs multimédias en font un usage intensif).

Structure sur disque

Le fichier s'ouvre par un en-tête fixe contenant une valeur magique et une version de format, suivi d'un nombre d'entrées de catégorie. Chaque entrée de catégorie encode :

  • un champ type0 pour une catégorie custom (nommée par l'application), 1 pour une catégorie known telle que Frequent ou Recent, et 2 pour la catégorie "Tasks" ;
  • pour les catégories custom, un nom UTF-16LE préfixé par sa longueur en caractères (pas en octets) ;
  • un compteur d'entrées ;
  • ce même nombre de structures LNK sérialisées écrites bout à bout.
[ 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 ]

Il n'y a aucun préfixe de longueur par entrée devant chaque LNK — vous analysez le raccourci shell sur place et laissez la structure LNK indiquer où elle se termine.

La charge utile LNK

Chaque destination est un raccourci shell MS-SHLLINK standard, identique en forme aux flux numérotés trouvés dans AutomaticDestinations. Cela signifie que le même parseur LNK est réutilisé : ShellLinkHeader, LinkTargetIDList optionnel, LinkInfo, les blocs StringData et les éventuels blocs ExtraData (notamment le TrackerDataBlock avec ses identifiants volume/machine et ses GUID droid). Chemin cible, répertoire de travail, arguments de ligne de commande, emplacement d'icône et les trois horodatages FILETIME proviennent tous du LNK lui-même — le conteneur CustomDestinations ne les duplique pas.

Éléments épinglés et catégorie "Tasks"

La catégorie "Tasks" (type 2) contient des verbes de lancement définis par le développeur plutôt que des documents utilisateur — « Open new window », « Compose mail », « Start a private session ». Ces LNK pointent généralement vers l'exécutable de l'application avec des arguments de ligne de commande spécifiques.

Les entrées épinglées sont stockées aux côtés des autres catégories mais survivent aux réinitialisations de Jump List et à la suppression explicite des éléments récents, ce qui en fait un signal de persistance utile en triage : un chemin qu'un utilisateur a activement épinglé implique une intention, et pas seulement une activité MRU accessoire.

Notes pratiques d'analyse

  • Lisez strictement de manière séquentielle. Il n'y a pas de répertoire central ; les offsets sont implicites dans la position du curseur.
  • Il n'y a aucune couche OLE — n'essayez pas d'ouvrir le fichier avec un lecteur Compound File. Traitez-le comme un flux d'octets brut.
  • Les chaînes UTF-16LE sont préfixées en longueur en caractères, donc multipliez par deux avant d'avancer le curseur.
  • Attendez-vous à des octets de footer ou de padding en fin après la dernière catégorie ; tolérez une courte queue plutôt que de la traiter comme une corruption.
  • Réutilisez votre parseur LNK existant tel quel ; le format est documenté par Microsoft dans la documentation développeur du shell et a été rétro-ingénieré par Volatility, libforensics, fox-it et kacos2000.

Si vous avez un fichier d'exemple, essayez-le dans le parseur dans le navigateur — il décode les deux types de fichiers côte à côte.