TaskCache-Forensik: Tree, Tasks und DynamicInfo
Was der TaskCache-Schlüssel im SOFTWARE-Hive zu jeder geplanten Aufgabe speichert: Tree mit Id/Index/SD, Tasks-Werte, DynamicInfo, Actions, Triggers und Hash.
Kurz gesagt. TaskCache besteht aus zwei Hälften. Tree\<path> ordnet einem Aufgabennamen seine GUID (Id), eine Kategorie (Index) und eine Sicherheitsbeschreibung (SD) zu. Tasks\{GUID} speichert Path, URI, Author, Date, Hash, Actions, Triggers und DynamicInfo, dessen FILETIMEs Erstellung, letzte Ausführung und letzte erfolgreiche Ausführung in UTC liefern. Die Binärformate wurden von der Community per Reverse Engineering erschlossen: Vertrauen Sie den gut belegten Feldern und prüfen Sie den Rest gegen andere Quellen.
Dies ist der Registry-Schwerpunkt der Serie, die mit dem Leitfaden zur Forensik geplanter Aufgaben beginnt. Die wichtigsten Quellen sind die Analyse von cyber.wtf (Windows 10 1909, gegengeprüft mit Windows 7) und winreg-kb von libyal. Microsoft dokumentiert diese Werte nicht.
Aufbau des Schlüssels
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 spiegelt die Ordnerstruktur von C:\Windows\System32\Tasks. Tasks ist flach, mit einem Schlüssel pro Aufgaben-GUID. Die vier Kategorieschlüssel enthalten je einen Unterschlüssel pro Aufgaben-GUID dieser Kategorie.
Tree: Namen, Kategorien, Berechtigungen
| Wert | Typ | Bedeutung |
|---|---|---|
Id | String | Die Aufgaben-GUID, z. B. {8A1B...}, verweist auf Tasks\{GUID} |
Index | DWORD | Kategorie. cyber.wtf ordnet zu: 1 Boot, 2 Logon, 3 Plain, 4 Maintenance; Ordner haben keinen Index |
SD | Binary | Selbstrelative Sicherheitsbeschreibung, die den Zugriff auf die Aufgabe regelt |
Drei Punkte sollten Sie bei jedem Tree-Eintrag prüfen:
- Ist
SDvorhanden? Ein Aufgabenschlüssel ohneSDist fürschtasksund die Konsole unsichtbar. Das ist die Versteckmethode von Tarrask (Microsoft, 2022). - Verweigert die dekodierte SD den Lesezugriff? Binary Defense hat gezeigt, dass eine gültige Beschreibung, die allen das Lesen verweigert, eine Aufgabe genauso gut versteckt und dabei unauffällig aussieht (Binary Defense). Lesen Sie die SDDL, statt nur zu prüfen, ob der Wert existiert.
- Ist
Indexplausibel? MITRE führt das Ändern des WertsIndexals weitere Versteckmethode auf (T1053.005). Ein Wert 0 bei einer Aufgabe oder einIndex, der nicht zu dem Kategorieschlüssel passt, unter dem die GUID auftaucht, verdient eine Notiz.
Die Last-Write-Zeit des Tree-Schlüssels ist eine FILETIME in UTC, die sich bei jeder Änderung eines Werts in diesem Schlüssel aktualisiert. Bei einer normalen Aufgabe liegt sie nahe am Registrierungszeitpunkt. Liegt sie deutlich nach der Erstellungszeit aus DynamicInfo, hat etwas den Schlüssel neu geschrieben; bei einer Aufgabe im Tarrask-Stil kann das der Moment sein, in dem SD gelöscht wurde. Das ist eine Schlussfolgerung, kein direkter Nachweis, und so sollten Sie es im Bericht auch formulieren.
Tasks{GUID}: die Aufgabendaten
| Wert | Bedeutung |
|---|---|
Path | Aufgabenpfad, z. B. \Microsoft\Windows\Defrag\ScheduledDefrag |
URI | Identisch mit dem XML-Element URI, sofern vorhanden |
Author, Description, Source, Version, Documentation | Kopien der Felder aus RegistrationInfo |
Date | Registrierungsdatum als String, im selben zeitzonenlosen Format wie in der XML |
Hash | Integritäts-Hash der XML-Datei, siehe unten |
Schema | Schemaversion als Zahl |
SecurityDescriptor | Optionale SDDL aus der XML |
Actions | Binäre Serialisierung der Aktionen |
Triggers | Binäre Serialisierung von Triggern, Principal und Einstellungen |
DynamicInfo | Binärer Datensatz zum Ausführungsstatus |
Nicht jeder Wert existiert bei jeder Aufgabe. cyber.wtf merkt an, dass offenbar nur Actions und Triggers nötig sind, damit die Aufgabe in der Konsole erscheint.
DynamicInfo
DynamicInfo ist der Grund, TaskCache immer auszuwerten. Das Layout nach cyber.wtf, stimmig mit den Größenangaben in winreg-kb (28 Bytes unter Vista/7, 36 Bytes ab Windows 8):
| Offset | Größe | Feld |
|---|---|---|
| 0 | 4 | Version / Magic, typischerweise 3 |
| 4 | 8 | FILETIME: erstellt (Registrierung oder letzte Aktualisierung) |
| 12 | 8 | FILETIME: letzte Ausführung |
| 20 | 4 | Aufgabenstatus |
| 24 | 4 | Letzter Ergebniscode (HRESULT, z. B. 0x80070002 Datei nicht gefunden) |
| 28 | 8 | FILETIME: letzte erfolgreiche Ausführung (nur ab Windows 8) |
winreg-kb bezeichnet die ersten beiden Zeitstempel als "unknown" und vermutet "last registered or update time?" sowie "launch time?"; cyber.wtf benennt sie wie oben. In der Praxis verschiebt sich der Wert "erstellt", wenn eine Aufgabe neu registriert wird. Lesen Sie ihn daher als letzte Registrierung, nicht als erstmalige Erstellung. Der letzte Ergebniscode ist oft das nützlichste Feld: Eine Persistenzaufgabe, die erfolgreich gelaufen ist, zeigt 0x0; eine, deren Binärdatei entfernt wurde, zeigt ein HRESULT für "Datei nicht gefunden".
Alle drei FILETIMEs sind UTC. Damit sind sie der Anker für das zeitzonenlose XML-Feld Date, wie in Aufbau der Aufgaben-XML erklärt.
Der Actions-Blob
Der Wert Actions beginnt mit einem Versions-Word, danach folgt der String des Principal-Kontexts und dann ein Datensatz pro Aktion, erkennbar an einem Magic-Wert (cyber.wtf):
| Magic | Aktion |
|---|---|
0x6666 | Exec: Befehl, Argumente, Arbeitsverzeichnis |
0x7777 | ComHandler: CLSID und Daten |
0x8888 | SendEmail |
0x9999 | ShowMessage |
Strings sind UTF-16 mit Längenpräfix. Fehlt die XML-Datei, erfahren Sie über diesen Blob trotzdem, was die Aufgabe ausgeführt hat. Vergleichen Sie ihn mit der XML, wenn beide vorhanden sind: Ein Angreifer, der die Registry direkt bearbeitet, kann die Aktion ändern, ohne die Datei anzufassen, oder umgekehrt.
Der Triggers-Blob
Triggers beginnt mit einem Header (Version, dann ein "Job Bucket" mit Flags, einer CRC32, der Principal-ID, Benutzerinformationen und optionalen Einstellungen), gefolgt von Trigger-Datensätzen verschiedener Typen, jeweils mit eigenem Magic-Wert und 8-Byte-Ausrichtung. Das Header-Layout ist recht gut verstanden; die einzelnen Trigger-Datensätze unterscheiden sich je nach Windows-Version und sind weniger gesichert. Ein Parser kann den Header und den Principal (das Konto, unter dem die Aufgabe läuft) zuverlässig auslesen. Behandeln Sie aus dem Blob dekodierte Trigger-Details als Best-Effort und bevorzugen Sie die XML, wenn sie vorliegt.
Hash
Über den Wert Hash erkennt der Dienst, ob eine XML-Datei außerhalb seiner Kontrolle verändert wurde. winreg-kb verzeichnet ihn als SHA-256, auf Systemen vor dem Update KB2305420 als CRC32. Auf aktuellen Windows-10- und Windows-11-Systemen ist er nach Beobachtung der SHA-256 des XML-Dateiinhalts ohne die zwei Byte lange UTF-16-BOM.
Ihn neu zu berechnen ist eine günstige Integritätsprüfung:
- Übereinstimmung: Die XML auf dem Datenträger entspricht dem, was der Dienst registriert hat.
- Abweichung: Die Datei wurde nach der Registrierung bearbeitet, oder der Registry-Eintrag, oder die Datei wurde von anderswo wiederhergestellt. In jedem Fall widersprechen sich die beiden Kopien: Lesen Sie beide.
- Keine XML: Die Datei wurde gelöscht; die Registry ist die einzige verbliebene Definition.
TaskCache mit den XML-Dateien korrelieren
Ordnen Sie jedem Tree-Schlüssel die XML-Datei mit demselben relativen Pfad unter System32\Tasks zu. Listen Sie dann auf:
| Situation | Typische Erklärung | Vorgehen |
|---|---|---|
| XML und TaskCache, Hash stimmt | Normal | Triage anhand des Inhalts |
| Nur TaskCache (keine XML) | XML von Hand oder durch ein Bereinigungstool gelöscht; die Aufgabe läuft womöglich weiter | Hohe Priorität: Actions dekodieren |
| Nur XML (kein TaskCache) | Datei abgelegt, aber nie registriert, Registry bereinigt oder Hive in inkonsistentem Zustand | Hive-Zustand und Ereignisprotokolle prüfen |
| Tree ohne SD | Versteckte Aufgabe (Tarrask-Stil) | Hohe Priorität |
| Hash-Abweichung | Datei oder Registry nach der Registrierung geändert | Beide Definitionen vergleichen |
Ein Vorbehalt, bevor Sie etwas als "fehlend" einstufen: Wurde der SOFTWARE-Hive im laufenden Betrieb kopiert, ohne seine Transaktionsprotokolle einzuspielen, können jüngste Registry-Änderungen fehlen. Siehe Speicherorte und Sicherung geplanter Aufgaben.
Der Scheduled Tasks Parser führt diese Korrelation durch, berechnet den Hash neu, dekodiert die SD zu SDDL, DynamicInfo, Actions und den Triggers-Header und markiert Aufgaben, die nur in der Registry, nur auf dem Datenträger oder ohne SD existieren. Für umfangreichere Registry-Analysen deckt der Registry Parser den Rest des Hives ab.
FAQ
Was ist der Registry-Schlüssel TaskCache?
Unter HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache hält der Aufgabenplanungsdienst seine Kopie jeder registrierten Aufgabe vor: Namen und Sicherheitsbeschreibungen unter Tree, Aufgabendaten unter Tasks\{GUID} und Kategorielisten unter Boot, Logon, Plain und Maintenance.
Welche Zeitstempel enthält DynamicInfo?
Ab Windows 8 umfasst der Wert meist 36 Bytes: ein Versions-DWORD, eine FILETIME, die üblicherweise als Erstellungs- oder Registrierungszeit der Aufgabe gedeutet wird, eine FILETIME der letzten Ausführung, ein DWORD für den Aufgabenstatus, einen letzten Ergebniscode und eine FILETIME der letzten erfolgreichen Ausführung. Das Layout stammt aus Community-Recherchen, nicht aus Microsoft-Dokumentation.
Was ist der Wert Hash in TaskCache\Tasks?
Ein Integritäts-Hash der Aufgaben-XML. Auf aktuellen Systemen ist er nach Beobachtung ein SHA-256 der XML-Datei ohne ihre zwei Byte lange Byte Order Mark; ältere Systeme verwendeten CRC32. Weicht er von der XML auf dem Datenträger ab, wurde eine der beiden Kopien nach der Registrierung verändert.