El formato de archivo CustomDestinations-ms explicado
A diferencia de automaticDestinations-ms, que Windows ensambla automáticamente
a partir de los datos MRU por aplicación, un archivo customDestinations-ms lo
escribe la propia aplicación mediante la API COM
ICustomDestinationList.
Su propósito es publicar lo que la aplicación quiere mostrar explícitamente en
su Jump List: "Tasks", elementos anclados y categorías arbitrarias con nombre.
El formato en disco es un flujo plano y secuencial de shell links categorizados,
no un OLE Compound File: una pequeña cabecera seguida de una serie de bloques de
categoría, cada uno con un contador y a continuación esa misma cantidad de
estructuras LNK serializadas.
Por qué existen las CustomDestinations
AutomaticDestinations responde a "¿qué tocó el usuario recientemente?".
CustomDestinations responde a "¿qué quiere ofrecer la aplicación?". El
desarrollador llama a BeginList, añade instancias de IObjectCollection con
AppendCategory, AppendKnownCategory o AddUserTasks, y finaliza con
CommitList. El shell serializa el resultado en
%AppData%\Microsoft\Windows\Recent\CustomDestinations\<AppID>.customDestinations-ms.
El contenido habitual incluye una categoría "Tasks" (verbos definidos por la aplicación como "New Incognito Window" o "New Private Window"), una categoría conocida como Frequent o Recent que la aplicación delega al shell, y una o varias categorías personalizadas con nombres UTF-16 arbitrarios (navegadores, IDEs y reproductores multimedia hacen un uso intensivo de esto).
Estructura en disco
El archivo abre con una cabecera fija que contiene un valor mágico y una versión de formato, seguido de un contador de entradas de categoría. Cada entrada de categoría codifica:
- un campo type:
0para una categoría personalizada (con nombre de la aplicación),1para una categoría conocida como Frequent o Recent, y2para la categoría "Tasks"; - para categorías personalizadas, un nombre UTF-16LE precedido de su longitud en caracteres (no en bytes);
- un entry count;
- esa misma cantidad de estructuras LNK serializadas escritas una tras otra.
[ 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 ]
No hay un prefijo de longitud por entrada delante de cada LNK: se analiza el shell link en su sitio y se deja que la propia estructura LNK indique dónde termina.
La carga LNK
Cada destino es un shell link MS-SHLLINK estándar, idéntico en forma a los flujos numerados que se encuentran dentro de AutomaticDestinations. Es decir, se reutiliza el mismo parser de LNK: ShellLinkHeader, el LinkTargetIDList opcional, LinkInfo, los bloques StringData y cualquier bloque ExtraData (en particular el TrackerDataBlock con sus IDs de volumen y máquina y sus droid GUIDs). La ruta de destino, el directorio de trabajo, los argumentos de línea de comandos, la ubicación del icono y las tres marcas de tiempo FILETIME provienen todos del propio LNK: el contenedor CustomDestinations no los duplica.
Elementos anclados y la categoría "Tasks"
La categoría "Tasks" (type 2) contiene verbos de lanzamiento definidos por
el desarrollador en lugar de documentos del usuario: "Open new window",
"Compose mail", "Start a private session". Estos LNK suelen apuntar al
ejecutable de la aplicación con argumentos de línea de comandos específicos.
Las entradas ancladas se almacenan junto a otras categorías, pero sobreviven a los reinicios de la Jump List y al borrado explícito de elementos recientes, lo que las convierte en una señal de persistencia útil durante el triage: una ruta que un usuario ancló activamente implica intención, no una mera actividad MRU incidental.
Notas prácticas de parseo
- Lee estrictamente de forma secuencial. No hay un directorio central; los desplazamientos son implícitos en la posición del cursor.
- No hay 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 en longitud por caracteres, así que multiplica por dos antes de avanzar el cursor.
- Espera bytes de footer o relleno al final después de la última categoría; tolera una cola corta en lugar de tratarla como corrupción.
- Reutiliza tu parser de LNK existente sin cambios; el formato está documentado por Microsoft como parte de la documentación para desarrolladores del shell y ha sido objeto de ingeniería inversa por Volatility, libforensics, fox-it y kacos2000.
Si tienes un archivo de muestra, pruébalo en el parser en el navegador: decodifica ambos tipos de archivo en paralelo.