Jump List Parser
Volver al blog

El formato de archivo CustomDestinations-ms explicado

2026-05-255 min de lectura

automaticDestinations-ms es lo que Windows escribió sobre el usuario. customDestinations-ms es lo que la aplicación escribió sobre sí misma. Misma carpeta, mismos archivos con nombre de AppID, contenedores completamente diferentes. El lado Automatic es MS-CFB con flujos LNK numerados. El lado Custom es un flujo de bytes plano con categorías y shell links espalda con espalda, sin ningún archivo compuesto a la vista. Si alimentaras uno a un lector OLE esperando el otro, ya lo sabrías.

Por qué existe CustomDestinations

El archivo Automatic responde a "¿qué tocó recientemente el usuario?". El archivo Custom responde a "¿qué quiere la aplicación en su Jump List?". La división es por autor, no por contenido. Un desarrollador que quiere la entrada "Nueva ventana de incógnito" de Chrome, o la tarea "Nuevo documento" de Word, llama a ICustomDestinationList: BeginList, añade instancias IObjectCollection mediante AppendCategory, AppendKnownCategory o AddUserTasks, luego CommitList. La shell serializa eso en %AppData%\Microsoft\Windows\Recent\CustomDestinations\<AppID>.customDestinations-ms.

Contenido típico:

  • Una categoría Tareas, verbos que el desarrollador conectó. "Nueva ventana de incógnito", "Nueva ventana privada", "Redactar correo", "Salir". Estos apuntan al propio ejecutable de la aplicación con argumentos específicos de línea de comandos.
  • Una categoría Known, Frequent o Recent, delegada a la shell. La app dice "muestra aquí la lista estándar"; la shell la rellena.
  • Una o más categorías Custom, nombres UTF-16LE arbitrarios. Los navegadores, IDEs y reproductores multimedia la usan para cosas como "Más visitados", "Proyectos recientes", "Reproducidos recientemente".

Estructura en disco

El archivo se abre con una cabecera fija que lleva un valor mágico, una versión de formato y un recuento de entradas de categoría. Cada entrada de categoría codifica:

  • Un campo type: 0 para una categoría personalizada (con nombre de app), 1 para una categoría conocida como Frequent o Recent, 2 para la categoría Tareas.
  • Para el tipo 0, un nombre UTF-16LE precedido por su longitud en caracteres (no bytes).
  • Un recuento de entradas.
  • Esa cantidad de estructuras LNK serializadas, escritas espalda con espalda.
[ 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 0xBABFFBAB / padding ]

No hay un prefijo de longitud por entrada delante de cada LNK. Parseas el shell link en el sitio y dejas que la estructura LNK te diga dónde termina. Por eso es indispensable un parser LNK que funcione para este formato.

El terminador 0xBABFFBAB es una prueba olfativa tan buena como la cabecera mágica MS-CFB del lado Automatic. Si ves esos cuatro bytes al final del archivo y ninguna cabecera de archivo compuesto al inicio, estás mirando un archivo CustomDestinations.

La carga LNK, byte por byte

Cada destino es un shell link estándar MS-SHLLINK, idéntico en forma a los flujos numerados dentro de un archivo Automatic. Mismo parser, sin casos especiales. Obtienes ShellLinkHeader, LinkTargetIDList opcional, LinkInfo con VolumeID y LocalBasePath, las cadenas StringData (NAME_STRING, RELATIVE_PATH, WORKING_DIR, COMMAND_LINE_ARGUMENTS, ICON_LOCATION) y cualquier bloque ExtraData. El TrackerDataBlock sobrevive aquí también con sus IDs de volumen y máquina y GUIDs droid. La ruta de destino, el directorio de trabajo, los argumentos de línea de comandos, la ubicación del icono y las tres marcas temporales FILETIME vienen todos del interior del LNK.

Ese último punto importa: el contenedor CustomDestinations no duplica marcas temporales ni recuentos de acceso por entrada. No hay DestList. El orden es por posición en el archivo, y las únicas marcas temporales son las que están dentro de los LNKs incrustados.

Elementos anclados y la categoría Tareas

La categoría Tareas (type = 2) contiene verbos definidos por el desarrollador, no documentos del usuario. "Abrir nueva ventana", "Redactar correo", "Iniciar una sesión privada". Estos LNKs casi siempre apuntan al binario de la aplicación con argumentos: chrome.exe --incognito, winword.exe /n, outlook.exe /c ipm.note. Útil para entender qué ofrecía una aplicación; no útil como registro de actividad del usuario.

Las entradas ancladas son diferentes. Se almacenan junto a otras categorías, pero sobreviven a los resets de Jump List y al borrado explícito de elementos recientes. En triaje, una entrada anclada es intención. El usuario hizo clic activamente en "Anclar a esta lista". Trátalo como una señal más fuerte que un acceso MRU transitorio.

Notas prácticas de parseo

  • Lee estrictamente de forma secuencial. No hay un directorio central; los offsets son implícitos en la posición del cursor. Un parseo fallido en el offset N invalida todo lo posterior a N.
  • Sin capa OLE. No intentes abrir el archivo con un lector de Compound File. Trátalo como un flujo de bytes en bruto.
  • Las cadenas UTF-16LE están prefijadas por longitud en caracteres. Multiplica por dos antes de avanzar el cursor.
  • Un footer o relleno final es normal. Tolera una cola corta en lugar de tratarla como corrupción. El marcador 0xBABFFBAB, cuando está presente, es el final.
  • Reutiliza tu parser LNK. La forma está documentada en [MS-SHLLINK] y ha sido reverseada repetidamente: Volatility, libforensics, JLECmd de Eric Zimmerman, Jumplist-Browser de Kacos2000, los lanzamientos de investigación de fox-it. No escribas el tuyo desde cero a menos que lo hagas por diversión.

Por qué esto importa para el triaje

Un archivo customDestinations-ms con una lista de Tareas obsoleta es poco notable, es lo que la aplicación entregó. Un archivo customDestinations-ms cuyos LNKs anclados apuntan a recursos compartidos de red, unidades USB o ejecutables inesperados es interesante. El anclaje sobrevivió porque el usuario lo puso allí. Cruza la AppID con el catálogo de aplicaciones (los archivos LNK en Recent\ y el automaticDestinations-ms correspondiente corroborarán), luego mira los campos LinkInfo y TrackerDataBlock en cada LNK anclado para ruta, nombre de host y GUID droid.

Para ver todo esto en una muestra real sin escribir código, suelta una en el parser de la página de inicio, decodifica ambos tipos de archivo lado a lado.

Lecturas adicionales