Reconstruir una línea de tiempo de Jump List cuando alguien intentó ocultarla
La mayoría de los artículos tratan una Jump List como un registro ordenado que se lee de arriba abajo. No lo es. El flujo DestList es una estructura de elementos usados más recientemente, y en el momento en que asumes que el orden del archivo es el orden en que ocurrieron las cosas, tu línea de tiempo está equivocada y ni siquiera lo sabes. Este artículo trata de obtener el orden correcto, y de qué aspecto tiene el artefacto después de que alguien haya estado ahí dentro intentando hacer desaparecer un problema.
Si nunca has parseado uno de estos, empieza por la guía práctica de DFIR y el desglose del formato de archivo automaticDestinations. Aquí se asume que ya sabes qué contiene una fila de DestList.
La trampa del orden
Cada entrada de DestList lleva un FILETIME: la última vez que el shell registró que ese destino fue abierto. Un solo valor. Se sobrescribe en cada acceso. Así que una sola entrada te dice la apertura más reciente y nada sobre las cinco aperturas anteriores. Esto es lo primero que la gente confunde. Ven "último acceso 14:07" y escriben "el usuario abrió el archivo una vez a las 14:07" en el informe. El archivo puede haberse abierto cuarenta veces. Tú solo ves la última.
Lo segundo que confunden es la secuencia. Los flujos LNK numerados (1, 2, 3, etc.) se asignan aproximadamente en orden de creación, pero tampoco son tu línea de tiempo, porque Windows los reutiliza y reescribe. Lo que en realidad quieres son dos ordenamientos independientes, ordenados y luego comparados:
- El FILETIME de último acceso de cada entrada, ordenado de forma ascendente. Este es tu orden de eventos.
- Los valores de ID de entrada de DestList. La cabecera mantiene un contador que el shell incrementa cada vez que emite una nueva entrada. Cada fila almacena el ID que se le asignó. Ese contador solo sube, así que los IDs de entrada son un registro monótono del orden en que los destinos entraron por primera vez en la lista.
Cuando esos dos ordenamientos coinciden, tienes un MRU limpio y puedes confiar en la línea de tiempo. Cuando no coinciden, ocurrió algo interesante, y esa discrepancia es exactamente la señal de la que trata el resto de este artículo.
Reconstruir la secuencia
Parsea a CSV y extrae tres columnas: ID de entrada, tiempo de último acceso y ruta de destino. Con JLECmd:
JLECmd.exe -d "C:\case\AutomaticDestinations" --csv "C:\case\out"
Luego ordena por LastModified (el nombre que JLECmd da al FILETIME de DestList) y conserva EntryNumber al lado. En una lista normal, los IDs de entrada tenderán en la misma dirección que las marcas temporales. Un archivo abierto recientemente se sitúa cerca de la parte superior del MRU y lleva un ID de entrada alto y una marca temporal reciente. Los destinos antiguos y sin tocar llevan IDs bajos y marcas temporales antiguas, y van a la deriva hacia la expulsión.
Ahora compara el contador de la cabecera con tu número de filas. Si la cabecera de DestList dice que el último número de entrada emitido es 61 y tú tienes nueve filas, unas cincuenta y tantas entradas abandonaron esta lista en algún momento. Antes de escribir "se eliminaron pruebas", entiende primero la explicación aburrida: la Jump List automática tiene un tope. El tope está ligado a la opción del shell "número de elementos recientes a mostrar", y cuando la lista está llena se expulsa la entrada menos usada recientemente para hacer hueco. La rotación normal produce exactamente esta diferencia. Una estación de trabajo que abre documentos todo el día mostrará un contador alto y una lista superviviente corta, y eso es simplemente un martes cualquiera.
Lo que no es normal son los huecos en los IDs supervivientes que rompen la lógica MRU, o un contador de cabecera que no se mueve mientras los tiempos de acceso siguen avanzando. Guarda esa idea.
Qué aspecto tiene realmente una Jump List borrada
Los usuarios y los atacantes recurren a los mismos instrumentos burdos. El interruptor de Configuración ("mostrar elementos abiertos recientemente en Inicio, Jump Lists y el Explorador de archivos"), eliminar los archivos por AppID, o ejecutar un limpiador de privacidad. La guía para borrar o eliminar Jump Lists cubre la mecánica. Aquí está el residuo forense que deja cada uno.
Elimina el archivo .automaticDestinations-ms por completo y Windows no lo llora. El siguiente uso interactivo de esa aplicación recrea el archivo desde cero. Así que la ausencia rara vez es ausencia. Lo que encuentras es un archivo cuyos tiempos de creación $STANDARD_INFORMATION y $FILE_NAME en la MFT son sospechosamente recientes, que contiene un puñado de entradas cuyas marcas temporales de acceso se agrupan todas dentro de una ventana estrecha posterior a ese tiempo de creación. Una Jump List de Notepad que afirma que todo el historial del usuario con Notepad empezó el jueves pasado por la tarde, en una máquina aprovisionada hace dos años, te está diciendo cuándo murió la anterior, no cuándo el usuario empezó a usar Notepad.
El interruptor es más astuto porque trunca en lugar de destruir, y el archivo conserva su tiempo de creación original. La señal ahí es un DestList corto y plano: pocas entradas, todas agrupadas y recientes, un contador de ID de entrada que desmiente la historia. Contrasta la propia huella del interruptor en el registro (HKCU\...\Explorer\Advanced\Start_TrackDocs) y el parser de registro te fechará el cambio a través del tiempo de última escritura de la clave.
La eliminación en sí se registra en un lugar que la persona casi nunca piensa en limpiar. Las escrituras al archivo de Jump List pasan por el diario de cambios de NTFS. Extrae el diario USN y verás las banderas de razón DataOverwrite, DataExtend y, con frecuencia, FileDelete/FileCreate contra el nombre .automaticDestinations-ms, con marca temporal. La Jump List puede haber desaparecido y el diario seguir diciendo cuándo se fue.
Manipulación quirúrgica, y por qué es fácil de detectar
La eliminación total es ruidosa. La jugada más silenciosa es abrir el archivo compuesto y eliminar una entrada incómoda dejando el resto intacto. Aquí es donde conocer el formato del contenedor da frutos.
El .automaticDestinations-ms es un archivo compuesto OLE (CFBF, el antiguo formato de almacenamiento estructurado). Tiene una FAT, un directorio de flujos con nombre y holgura de sector como cualquier estructura de asignación. Hay dos formas en que la cirugía sale mal:
Un editor casero elimina el flujo LNK numerado pero no toca DestList. Ahora tienes una fila de DestList cuya entrada referencia un número de flujo que no está presente en el directorio. Un puntero colgante. Parsea los flujos, parsea el DestList, compara los dos conjuntos. Cada entrada de DestList debería resolverse a un flujo; cada flujo (excepto DestList) debería estar reclamado por una entrada. Cualquier cosa huérfana en cualquiera de los dos lados es una inconsistencia estructural que los archivos sanos mantenidos por el shell no producen.
El otro modo de fallo es editar directamente los bytes del DestList. Cada entrada lleva un valor de integridad, y las filas editadas a mano por scripts que no lo recalculan no coincidirán. Windows es laxo a la hora de validarlo en la práctica, así que el archivo falsificado igual carga, que es justo lo que busca el atacante, pero el checksum incorrecto sobrevive como bandera para ti. Trata una discrepancia de checksum como sospechosa, no como concluyente, porque los errores de parser y la desviación de versión también las producen.
Y luego está la recuperación. Como es un archivo compuesto con una FAT, los sectores de un flujo eliminado van a no asignado, no a cero. Se quedan ahí hasta que se reasignan. Tallar la cadena de sectores libres del CFBF puede recuperar un flujo LNK descartado, o lo suficiente de uno como para recuperar la ruta de destino y el GUID de volumen incrustado. No está garantizado, los sectores se reutilizan, pero he recuperado una ruta UNC completa de la holgura de un archivo compuesto sobre un archivo que el usuario creía haber limpiado.
La shadow copy casi siempre gana
Todo lo anterior es interesante y la mayor parte del tiempo no lo necesitas, porque Volume Shadow Copy capturó silenciosamente la Jump List antes de que nadie la tocara. Monta las shadow copies, extrae el mismo .automaticDestinations-ms de cada una y compara los DestLists entre snapshots. La entrada que existe en el snapshot del martes y falta en el del viernes te dice tanto qué se eliminó como la ventana en la que ocurrió. La eliminación selectiva en el volumen en vivo queda derrotada por completo por una copia que el usuario no sabía que existía y no puede alcanzar.
Esto es lo primero que hay que comprobar, no lo último. Empiezo por los entresijos del archivo compuesto porque deberías entenderlos, pero operativamente el orden es: shadow copies, luego diario USN, luego tallado y después análisis a nivel de byte de lo que sobrevivió.
Una línea de tiempo que puedas defender
La reconstrucción solo vale algo si aguanta, así que dos hábitos.
Fija tu reloj. El FILETIME de DestList es UTC y los tiempos de la cabecera LNK son UTC, pero el sistema que estás reconstruyendo puede haber movido su reloj: correcciones NTP, un cambio manual, una VM revertida a un snapshot anterior. Cuando el orden de ID de entrada y el orden de marcas temporales no coinciden, el movimiento del reloj es una explicación más común y más honesta que la falsificación, y la compruebas antes de alegar manipulación. Busca los eventos de cambio de hora en System.evtx (ID de evento 4616 en Security para "se cambió la hora del sistema") con el EVTX parser, y reconcilia.
No te apoyes en un solo artefacto. Una Jump List sitúa un archivo delante de un usuario en un momento. UserAssist confirma que la aplicación realmente se lanzó en una sesión GUI. Prefetch confirma que el proceso se ejecutó. El diario USN confirma que el archivo de destino existía y fue tocado. SRUM confirma que la aplicación estaba en ejecución y moviendo bytes. La Jump List es un hilo fuerte, y es un solo hilo. Los casos que se desmoronan en el interrogatorio son los construidos sobre un único artefacto leído literalmente.
Para una revisión puntual sin sacar el archivo de la estación de trabajo, suéltalo en la página de inicio y decodifica los flujos y el DestList del lado del cliente. Cuando necesites salida por lotes y eventos de línea de tiempo, JLECmd y Plaso siguen siendo las herramientas. La comparación de herramientas tiene el resumen.
Lecturas adicionales
- Yogesh Khatri y Mike Stevens, "Jumping into the Past" - la ingeniería inversa de DestList sobre la que se sostiene todo esto.
- Eric Zimmerman, JLECmd y JumpListExplorer.
- Joachim Metz, libolecf - referencia para el contenedor de archivo compuesto OLE si estás tallando a mano.
- MITRE ATT&CK T1070 Indicator Removal para situar dónde encajan las Jump Lists borradas en el panorama más amplio.