Jump List Parser
Volver al blog

El formato de archivo AutomaticDestinations-ms explicado

2026-05-254 min de lectura

Un archivo automaticDestinations-ms es un OLE Compound File (CFB / MS-CFB). Cada flujo numerado contiene un shell link (LNK) de un elemento usado recientemente, y un único flujo DestList aporta el orden, las marcas de tiempo y el nombre de host de origen.

El contenedor externo: OLE Compound File

El archivo sigue el formato Microsoft Compound File Binary (MS-CFB), el mismo contenedor de almacenamiento estructurado que hay detrás de los documentos de Office anteriores a 2007. Internamente, el archivo se divide en sectores de tamaño fijo (512 bytes para la mayoría de jump lists), enhebrados a través de una FAT, con un mini-stream para entradas menores de 4096 bytes. Un directorio en un índice de sector conocido enumera los flujos con nombre ("storages" y "streams" en la terminología CFB).

Para una Jump List solo importa el listado del directorio. Verás un conjunto plano de flujos: uno por elemento reciente, nombrados con un número hexadecimal, más un único flujo DestList. No hay storages anidados.

Flujos numerados: un LNK por entrada

Cada nombre de flujo numerado es un entero hexadecimal en minúsculas — 1, 2, a, 1f, etc. — y contiene un shell link estándar de Windows, tal y como se documenta en [MS-SHLLINK]. Eso significa que el mismo parser LNK que ya usas para los archivos .lnk en disco lo decodificará: ShellLinkHeader, LinkTargetIDList opcional, LinkInfo, los bloques StringData (NAME_STRING, RELATIVE_PATH, WORKING_DIR, COMMAND_LINE_ARGUMENTS, ICON_LOCATION) y los bloques ExtraData (TrackerDataBlock, PropertyStoreDataBlock y compañía).

El nombre del flujo es el ID de la entrada, y es lo que enlaza cada LNK con una fila del DestList.

El flujo DestList

DestList es el índice que convierte el conjunto de LNKs en una lista de uso reciente ordenada y fechada. Empieza con una cabecera de 32 bytes seguida de una secuencia de registros de entrada de longitud 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

Cada entrada que sigue lleva — en orden — los metadatos necesarios para reconstruir de dónde procede el 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

Las versiones 3 y 4 (Windows 10 y posteriores) añaden un pequeño trailer por entrada y ajustan algunos campos reservados; los nombres y propósitos de los campos anteriores son estables, pero los desplazamientos exactos cambian ligeramente entre versiones. Ramifica siempre según version de la cabecera antes de procesar las entradas.

Entradas ancladas frente a no ancladas

La cabecera indica el número de entradas ancladas, pero el marcador de anclaje por entrada se codifica en el campo de número de accesos. Los elementos no anclados almacenan un float normalizado que representa con qué frecuencia el usuario abrió el destino. Los elementos anclados almacenan un valor centinela — históricamente 0xFFFFFFFF interpretado como un float de 32 bits (NaN) — para marcar que la entrada está anclada por el usuario y que el recuento no es significativo. El centinela exacto y la posición de cualquier flag adicional de anclaje varía entre versiones de DestList, así que trata el recuento como opaco hasta haber comprobado la versión.

Notas prácticas de parseo

  • Endianness. Todo es little-endian, incluidos los GUIDs (Data1/Data2 /Data3 son little-endian; Data4 es un array de bytes).
  • FILETIME. Convierte con unix_seconds = (filetime / 10_000_000) - 11_644_473_600. Mantenlos en UTC; las jump lists no registran zona horaria.
  • Hostname. El nombre NetBIOS de 16 bytes es la estación de trabajo que abrió el archivo. En un dominio resulta inestimable para correlacionar actividad entre equipos.
  • Entry ID. Empareja el entry ID de 4 bytes de cada fila de DestList con los nombres de flujo hexadecimales del directorio CFB; esa es tu clave de unión.
  • Desfase de versión. Windows 7 producía DestList v1. Windows 8/8.1 introdujo v3; Windows 10 y 11 emiten habitualmente v3 o v4. Los parsers antiguos que asumen el layout v1 leerán mal los archivos modernos: lee siempre primero la cabecera.
  • Ordenación. No asumas que las filas de DestList están en orden MRU. Ordena por last access FILETIME (o por last entry ID menos desplazamiento, según lo que necesites).

Para inspeccionar un archivo real sin escribir código, suéltalo en el parser en el navegador — ejecuta un parser de DestList + LNK en Rust compilado a WebAssembly, enteramente del lado del cliente. Para conocer el origen de estos archivos y por qué importan en una investigación, consulta la introducción forense.