Le format de fichier AutomaticDestinations-ms expliqué
Le format n'a rien d'exotique une fois qu'on l'a lu deux fois. Un automaticDestinations-ms est un OLE Compound File, le même conteneur que celui des documents Office antérieurs à 2007. À l'intérieur, on obtient un flux par élément récemment utilisé, chacun contenant un shell link standard, ainsi qu'un unique flux DestList qui les ordonne et associe des horodatages à chaque entrée. C'est toute l'histoire. Le reste de ce billet est le détail au niveau des champs dont vous aurez besoin lorsqu'un parser se désaligne et que vous devez confirmer à la main ce que disent les octets.
Le conteneur externe est MS-CFB, rien de plus
Chaque .automaticDestinations-ms s'ouvre avec l'en-tête magique MS-CFB D0 CF 11 E0 A1 B1 1A E1. La taille de secteur est de 512 octets pour toutes les Jump Lists que j'ai inspectées, ce qui signifie que la FAT, la chaîne du répertoire et le mini-stream (pour les entrées de moins de 4096 octets) se comportent comme dans un vieux .doc. Si vous avez déjà un parser CFB, dirigez-le sur le fichier et vous êtes à mi-chemin.
Ce que vous lisez du répertoire est une liste plate. Pas de stockages imbriqués, pas de sous-conteneurs surprise. Un flux par cible récemment utilisée, plus une DestList. Les noms de flux sont des entiers hexadécimaux en minuscules : 1, 2, a, 1f, et ainsi de suite. Le nom du flux est aussi l'ID de l'entrée. Gardez-le ; c'est ce qui lie chaque LNK à une ligne de DestList.
Les flux numérotés sont des LNK, de bout en bout
Chaque flux numéroté est un shell link mot pour mot tel que défini par [MS-SHLLINK]. Cela signifie que le parser que vous utilisez déjà pour les fichiers .lnk sur le disque les décode sans modification : ShellLinkHeader, LinkTargetIDList optionnelle, LinkInfo (avec VolumeID, LocalBasePath, CommonNetworkRelativeLink), les chaînes StringData (NAME_STRING, RELATIVE_PATH, WORKING_DIR, COMMAND_LINE_ARGUMENTS, ICON_LOCATION), et les blocs ExtraData (TrackerDataBlock, PropertyStoreDataBlock, EnvironmentVariableDataBlock, KnownFolderDataBlock). La disposition des octets à l'intérieur du flux est identique à celle d'un shell link autonome, la couche CFB n'ajoute rien par-dessus.
Cette symétrie mérite d'être intériorisée. Si vous savez en lire un, vous savez les lire tous les deux.
Le flux DestList : en-tête de 32 octets, entrées de longueur variable
DestList est ce qui transforme l'ensemble des flux LNK en un MRU ordonné et daté. Il commence par un en-tête de 32 octets puis exécute une séquence d'enregistrements de longueur variable.
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
Chaque enregistrement d'entrée après cet en-tête porte les métadonnées nécessaires pour reconstruire d'où venait le LNK :
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
Les versions 3 et 4 (Windows 10 et plus récent) accrochent une petite remorque sur chaque entrée et déplacent quelques champs réservés. Les noms et significations des champs ci-dessus restent stables, mais les offsets exacts varient entre versions. Branchez-vous sur la version de l'en-tête avant de toucher au parsing des entrées. Les anciens parsers qui codent en dur la disposition v1 lisent silencieusement de travers les fichiers modernes, les entrées décodent, les horodatages sont décalés, le nom d'hôte ressemble à du charabia, et aucune erreur n'est levée.
Le champ access-count porte le drapeau d'épinglage
Cela fait trébucher les gens. L'en-tête dit "nombre d'entrées épinglées", mais le marqueur d'épinglage par entrée est encodé dans le champ access-count lui-même. Les entrées non épinglées stockent un float normalisé comptant les ouvertures. Les entrées épinglées stockent une sentinelle, historiquement 0xFFFFFFFF interprété comme un float 32 bits, qui se décode en NaN. La sentinelle exacte et la position de tout drapeau d'épinglage supplémentaire bougent entre versions de DestList. Traitez le comptage comme opaque jusqu'à ce que vous ayez vérifié la version.
Les entrées épinglées sont opérationnellement intéressantes. Elles impliquent une intention de l'utilisateur, quelqu'un a fait un clic droit et choisi "Épingler à cette liste", et elles survivent aux réinitialisations ordinaires de "effacer les éléments récents". Si une Jump List a été élaguée mais que des entrées épinglées subsistent, vous avez encore quelque chose.
Notes pratiques de parsing
- Boutisme. Tout est little-endian, y compris les GUID (
Data1/Data2/Data3sont little-endian ;Data4est un tableau d'octets). La même convention que Microsoft utilise partout ailleurs. - FILETIME.
unix_seconds = (filetime / 10_000_000) - 11_644_473_600. Restez en UTC. Les Jump Lists ne portent aucune information de fuseau horaire. - Hostname. Le nom NetBIOS de 16 octets est la station de travail qui a ouvert le fichier. Sur un domaine, ce champ est un cadeau, il corrèle l'activité entre hôtes sans avoir besoin des journaux de chaque serveur.
- Entry ID. L'
entry IDde 4 octets dans chaque ligne de DestList correspond à l'un des noms de flux hex dans le répertoire CFB. C'est la clé de jointure. - Décalage de version. Win7 écrivait v1. Win8/8.1 a introduit v3. Win10 et Win11 émettent couramment v3 ou v4. Confirmez l'en-tête d'abord ; ne supposez rien.
- Ordre. Les lignes de DestList ne sont pas dans l'ordre MRU sur disque. Triez par
last access FILETIMEquand vous voulez la vue utilisateur du "plus récent". Triez parentry IDsi vous voulez l'ordre d'écriture.
La rétro-ingénierie originale au niveau des octets pour DestList vient de Yogesh Khatri et Mike Stevens. JLECmd d'Eric Zimmerman est le parser hors ligne canonique et le schéma de facto pour la sortie. Le visualiseur PowerShell Jumplist-Browser de Kacos2000 est utile lorsque vous voulez voir les structures brutes plutôt qu'une vue normalisée.
Pour examiner un vrai fichier sans rien installer, déposez-en un dans le parser dans le navigateur sur la page d'accueil. Il exécute un décodeur Rust DestList + LNK compilé en WebAssembly, entièrement côté client. Pour le contexte sur la provenance de ces fichiers et leur importance pour une enquête, voir l'introduction forensique.
Pour aller plus loin
- Yogesh Khatri et Mike Stevens, "Jumping into the Past", l'article original de rétro-ingénierie de DestList. Toujours la meilleure référence unique pour la disposition des champs.
- La spécification [MS-CFB] de Microsoft sur Microsoft Learn, pour la couche conteneur.
- La spécification [MS-SHLLINK] de Microsoft, pour la charge utile LNK à l'intérieur de chaque flux numéroté.
- Le parser LNK et le parser MFT pour corroboration quand la chronologie importe.