El formato de archivo AutomaticDestinations-ms explicado
El formato no es exótico una vez que lo has leído dos veces. Un automaticDestinations-ms es un OLE Compound File, el mismo contenedor que aloja los documentos de Office anteriores a 2007. Dentro de él, obtienes un flujo por cada elemento usado recientemente, cada uno con un shell link estándar, además de un único flujo DestList que los ordena y fija marcas temporales a cada entrada. Esa es toda la historia. El resto de este artículo es el detalle a nivel de campo que necesitarás cuando un parser se desalinee y tengas que confirmar a mano lo que dicen los bytes.
El contenedor exterior es MS-CFB, nada más
Cada .automaticDestinations-ms se abre con la cabecera mágica MS-CFB D0 CF 11 E0 A1 B1 1A E1. El tamaño del sector es de 512 bytes para todas las Jump Lists que he inspeccionado, lo que significa que la FAT, la cadena del directorio y el mini-stream (para entradas de menos de 4096 bytes) se comportan como lo harían en un viejo .doc. Si ya tienes un parser CFB, apúntalo al archivo y estarás a medio camino.
Lo que lees del directorio es una lista plana. No hay almacenamientos anidados ni sub-contenedores sorpresa. Un flujo por cada destino usado recientemente, más una DestList. Los nombres de los flujos son enteros hexadecimales en minúsculas: 1, 2, a, 1f, y así sucesivamente. El nombre del flujo es también el ID de la entrada. Recuérdalo; es lo que une cada LNK con una fila de DestList.
Los flujos numerados son LNK, hasta el fondo
Cada flujo numerado es un shell link palabra por palabra tal como lo define [MS-SHLLINK]. Eso significa que el parser que ya usas para los archivos .lnk en disco los decodifica sin modificación: ShellLinkHeader, LinkTargetIDList opcional, LinkInfo (con VolumeID, LocalBasePath, CommonNetworkRelativeLink), las cadenas StringData (NAME_STRING, RELATIVE_PATH, WORKING_DIR, COMMAND_LINE_ARGUMENTS, ICON_LOCATION) y los bloques ExtraData (TrackerDataBlock, PropertyStoreDataBlock, EnvironmentVariableDataBlock, KnownFolderDataBlock). La disposición de bytes dentro del flujo es idéntica a la de un shell link independiente, la capa CFB no añade nada por encima.
Esa simetría merece ser interiorizada. Si puedes leer uno, puedes leer ambos.
El flujo DestList: cabecera de 32 bytes, entradas de longitud variable
DestList es lo que convierte el conjunto de flujos LNK en un MRU ordenado y datado. Comienza con una cabecera de 32 bytes y luego ejecuta una secuencia de registros 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 registro de entrada después de esa cabecera lleva los metadatos necesarios para reconstruir de dónde vino 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 a cada entrada y mueven algunos campos reservados. Los nombres y significados de los campos arriba se mantienen estables, pero los offsets exactos cambian entre versiones. Bifurca según la version de la cabecera antes de tocar el parseo de entradas. Los parsers más antiguos que codifican el layout v1 leen mal los archivos modernos en silencio, las entradas decodifican, las marcas temporales están desplazadas, el nombre de host parece basura y no se eleva ningún error.
El campo de recuento de accesos lleva la marca de anclado
Esto hace tropezar a la gente. La cabecera dice "número de entradas ancladas", pero el marcador de anclado por entrada está codificado en el propio campo de recuento de accesos. Las entradas no ancladas almacenan un float normalizado que cuenta aperturas. Las entradas ancladas almacenan un centinela, históricamente 0xFFFFFFFF interpretado como un float de 32 bits, que se decodifica como NaN. El centinela exacto y la posición de cualquier flag adicional de anclado se mueven entre versiones de DestList. Trata el recuento como opaco hasta que hayas comprobado la versión.
Las entradas ancladas son operativamente interesantes. Implican intención del usuario, alguien hizo clic derecho y eligió "Anclar a esta lista", y sobreviven a los resets ordinarios de "borrar elementos recientes". Si una Jump List ha sido podada pero quedan entradas ancladas, todavía tienes algo.
Notas prácticas de parseo
- Endianness. Todo es little-endian, incluyendo los GUID (
Data1/Data2/Data3son little-endian;Data4es un array de bytes). La misma convención que Microsoft usa en todas partes. - FILETIME.
unix_seconds = (filetime / 10_000_000) - 11_644_473_600. Mantente en UTC. Las Jump Lists no llevan información de zona horaria. - Hostname. El nombre NetBIOS de 16 bytes es la estación de trabajo que abrió el archivo. En un dominio ese campo es un regalo, correlaciona actividad entre hosts sin necesitar los registros de cada servidor.
- Entry ID. El
entry IDde 4 bytes en cada fila de DestList coincide con uno de los nombres de flujo hex en el directorio CFB. Esa es la clave de unión. - Desviación de versión. Win7 escribió v1. Win8/8.1 introdujo v3. Win10 y Win11 suelen emitir v3 o v4. Confirma primero la cabecera; no asumas.
- Ordenación. Las filas de DestList no están en orden MRU en disco. Ordena por
last access FILETIMEcuando quieras la vista del usuario de "más reciente". Ordena porentry IDsi quieres el orden de escritura.
La ingeniería inversa original a nivel de byte para DestList vino de Yogesh Khatri y Mike Stevens. JLECmd de Eric Zimmerman es el parser offline canónico y el esquema de facto para la salida. El visor PowerShell Jumplist-Browser de Kacos2000 es útil cuando quieres ver las estructuras crudas en lugar de una vista normalizada.
Para experimentar con un archivo real sin instalar nada, suelta uno en el parser del navegador de la página de inicio. Ejecuta un decodificador Rust de DestList + LNK compilado a WebAssembly, completamente del lado del cliente. Para el contexto sobre de dónde vienen estos archivos y por qué importan para una investigación, consulta la introducción forense.
Lecturas adicionales
- Yogesh Khatri y Mike Stevens, "Jumping into the Past", el artículo original de ingeniería inversa de DestList. Sigue siendo la mejor referencia única para el layout de campos.
- La especificación [MS-CFB] de Microsoft en Microsoft Learn, para la capa del contenedor.
- La especificación [MS-SHLLINK] de Microsoft, para la carga útil LNK dentro de cada flujo numerado.
- El LNK parser y el MFT parser para corroboración cuando la línea de tiempo importa.