Análisis forense de TaskCache: Tree, Tasks y DynamicInfo
Qué guarda la clave TaskCache de SOFTWARE para cada tarea programada: Id/Index/SD de Tree, valores de Tasks, DynamicInfo, blobs Actions y Triggers, y el Hash.
Resumen. TaskCache tiene dos mitades. Tree\<path> asocia el nombre de una tarea a su GUID (Id), una categoría (Index) y un descriptor de seguridad (SD). Tasks\{GUID} guarda Path, URI, Author, Date, Hash, Actions, Triggers y DynamicInfo, cuyos FILETIME dan la creación, la última ejecución y la última ejecución correcta en UTC. Las estructuras binarias se conocen por ingeniería inversa de la comunidad: confíe en los campos bien corroborados y contraste el resto.
Este es el análisis en profundidad del registro dentro de la serie que empieza con la guía de análisis forense de tareas programadas. Las fuentes principales son el análisis de cyber.wtf (Windows 10 1909, contrastado con Windows 7) y el winreg-kb de libyal. Microsoft no documenta estos valores.
Estructura de la clave
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache
├── Tree\<folder>\<task name> Id, Index, SD
├── Tasks\{GUID} Path, URI, Author, Date, Hash, Actions, Triggers, DynamicInfo, ...
├── Boot\{GUID}
├── Logon\{GUID}
├── Plain\{GUID}
└── Maintenance\{GUID}
Tree reproduce la estructura de carpetas de C:\Windows\System32\Tasks. Tasks es plana, con una clave por GUID de tarea. Las cuatro claves de categoría contienen una subclave por cada GUID de tarea de esa categoría.
Tree: nombres, categorías y permisos
| Valor | Tipo | Significado |
|---|---|---|
Id | Cadena | El GUID de la tarea, p. ej. {8A1B...}, que apunta a Tasks\{GUID} |
Index | DWORD | Categoría. cyber.wtf asigna 1 Boot, 2 Logon, 3 Plain, 4 Maintenance; las carpetas no tienen Index |
SD | Binario | Descriptor de seguridad autorrelativo que controla el acceso a la tarea |
Hay tres cosas que comprobar en cada entrada de Tree:
- ¿Existe
SD? Una clave de tarea sinSDes invisible paraschtasksy para la consola. Es la técnica de ocultación de Tarrask (Microsoft, 2022). - ¿El SD decodificado deniega la lectura? Binary Defense demostró que un descriptor válido que deniega la lectura a todo el mundo oculta una tarea igual de bien y parece normal (Binary Defense). Lea el SDDL, no se limite a comprobar que existe.
- ¿Es plausible
Index? MITRE recoge la alteración del valorIndexcomo otro método de ocultación (T1053.005). Un valor 0 en una tarea, o unIndexque no concuerda con la clave de categoría bajo la que aparece el GUID, merece una anotación.
La hora de última escritura de la clave Tree es un FILETIME en UTC que se actualiza cuando cambia cualquier valor de esa clave. En una tarea normal queda cerca del registro. Si es muy posterior a la hora de creación de DynamicInfo, algo reescribió la clave; en una tarea al estilo Tarrask, puede ser el momento en que se borró el SD. Es una inferencia, no un registro directo, así que formúlela como tal en el informe.
Tasks{GUID}: los datos de la tarea
| Valor | Significado |
|---|---|
Path | Ruta de la tarea, p. ej. \Microsoft\Windows\Defrag\ScheduledDefrag |
URI | Igual que el URI del XML, cuando existe |
Author, Description, Source, Version, Documentation | Copias de los campos de RegistrationInfo |
Date | Cadena con la fecha de registro, en el mismo formato sin zona horaria que el XML |
Hash | Hash de integridad del archivo XML, véase más abajo |
Schema | Versión del esquema como número |
SecurityDescriptor | SDDL opcional procedente del XML |
Actions | Serialización binaria de las acciones |
Triggers | Serialización binaria de desencadenadores, principal y configuración |
DynamicInfo | Registro binario del estado de ejecución |
No todos los valores existen en todas las tareas. cyber.wtf señala que solo Actions y Triggers parecen imprescindibles para que la tarea aparezca en la consola.
DynamicInfo
DynamicInfo es el motivo para analizar siempre TaskCache. Esta es la estructura que describe cyber.wtf, coherente con las notas de tamaño de winreg-kb (28 bytes en Vista/7, 36 bytes en Windows 8 y posteriores):
| Desplazamiento | Tamaño | Campo |
|---|---|---|
| 0 | 4 | Versión / magic, normalmente 3 |
| 4 | 8 | FILETIME: creación (registro o última actualización) |
| 12 | 8 | FILETIME: última ejecución |
| 20 | 4 | Estado de la tarea |
| 24 | 4 | Código del último resultado (HRESULT, p. ej. 0x80070002, archivo no encontrado) |
| 28 | 8 | FILETIME: última ejecución correcta (solo Windows 8+) |
winreg-kb etiqueta las dos primeras marcas de tiempo como «unknown» y sugiere «last registered or update time?» y «launch time?»; cyber.wtf les da los nombres de arriba. En la práctica, el valor de «creación» cambia cuando la tarea se vuelve a registrar, así que interprételo como último registro y no como la creación original. El código del último resultado suele ser el campo más útil: una tarea de persistencia que se ha ejecutado bien muestra 0x0; una cuyo binario se eliminó muestra un HRESULT de archivo no encontrado.
Los tres FILETIME están en UTC. Eso los convierte en el ancla para el Date sin zona horaria del XML, como se explica en anatomía del XML de las tareas.
Blob Actions
El valor Actions empieza con una palabra de versión, sigue con la cadena del contexto del principal y después contiene un registro por acción, identificado por un valor magic (cyber.wtf):
| Magic | Acción |
|---|---|
0x6666 | Exec: comando, argumentos, directorio de trabajo |
0x7777 | ComHandler: CLSID y datos |
0x8888 | SendEmail |
0x9999 | ShowMessage |
Las cadenas son UTF-16 precedidas de su longitud. Cuando el archivo XML ha desaparecido, este blob es la forma de saber qué ejecutaba la tarea. Compárelo con el XML cuando existan ambos: un atacante que edita el registro directamente puede cambiar la acción sin tocar el archivo, o al revés.
Blob Triggers
Triggers empieza con una cabecera (versión y luego un «job bucket» con flags, un CRC32, el identificador del principal, información de usuario y configuración opcional), seguida de registros de desencadenadores de distintos tipos, cada uno con su propio magic y alineación a 8 bytes. La estructura de la cabecera se conoce razonablemente bien; los registros de cada desencadenador varían según la versión de Windows y están menos asentados. Un parser puede extraer de forma fiable la cabecera y el principal (la cuenta con la que se ejecuta la tarea); trate los detalles de desencadenadores decodificados del blob como aproximados y prefiera el XML cuando lo tenga.
Hash
El valor Hash permite al servicio detectar que un archivo XML ha cambiado fuera de su control. winreg-kb lo registra como SHA-256, o CRC32 en sistemas anteriores a la actualización KB2305420. En sistemas Windows 10 y 11 actuales se observa que es el SHA-256 del contenido del archivo XML sin su BOM UTF-16 de dos bytes.
Recalcularlo proporciona una comprobación de integridad barata:
- Coincide: el XML en disco es el que registró el servicio.
- No coincide: el archivo se editó después del registro, o se editó la entrada del registro, o el archivo se restauró desde otro sitio. En cualquier caso, las dos copias discrepan: lea ambas.
- No hay XML: el archivo se borró; el registro es la única definición que queda.
Correlacionar TaskCache con los archivos XML
Empareje cada clave de Tree con el archivo XML de la misma ruta relativa bajo System32\Tasks. Después, clasifique:
| Situación | Explicación habitual | Qué hacer |
|---|---|---|
| XML y TaskCache, el hash coincide | Normal | Triaje según el contenido |
| Solo TaskCache (sin XML) | XML borrado a mano o por una herramienta de limpieza; la tarea puede seguir ejecutándose | Prioridad alta: decodificar Actions |
| Solo XML (sin TaskCache) | Archivo depositado pero nunca registrado, registro limpiado o hive sucio | Revisar el estado del hive y los registros de eventos |
| Tree sin SD | Tarea oculta (al estilo Tarrask) | Prioridad alta |
| Hash no coincide | Archivo o registro modificados después del registro | Comparar las dos definiciones |
Una advertencia antes de dar algo por «ausente»: si el hive SOFTWARE se copió en caliente sin aplicar sus registros de transacciones, pueden faltar cambios recientes del registro. Consulte ubicaciones y adquisición de las tareas programadas.
El Scheduled Tasks Parser realiza esta correlación, recalcula el hash, decodifica el SD a SDDL, DynamicInfo, Actions y la cabecera de Triggers, y marca las tareas que solo están en el registro, las que solo están en disco y las que no tienen SD. Para un trabajo más amplio sobre el registro, el Registry parser cubre el resto del hive.
Preguntas frecuentes
¿Qué es la clave de registro TaskCache?
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache es donde el servicio Programador de tareas guarda su copia de cada tarea registrada: nombres y descriptores de seguridad bajo Tree, datos de la tarea bajo Tasks\{GUID} y listas por categoría bajo Boot, Logon, Plain y Maintenance.
¿Qué marcas de tiempo contiene DynamicInfo?
En Windows 8 y posteriores suele ocupar 36 bytes: un DWORD de versión, un FILETIME que se interpreta habitualmente como hora de creación o de registro de la tarea, un FILETIME de última ejecución, un DWORD de estado de la tarea, un código del último resultado y un FILETIME de última ejecución correcta. La estructura procede de la investigación de la comunidad, no de documentación de Microsoft.
¿Qué es el valor Hash de TaskCache\Tasks?
Un hash de integridad del XML de la tarea. En sistemas actuales se observa que es el SHA-256 del archivo XML sin su marca de orden de bytes de dos bytes; los sistemas más antiguos usaban CRC32. Si no coincide con el XML en disco, uno de los dos cambió después del registro.