The Scheduled Task Was The Persistence
The malware was gone.
The file had been deleted. The suspicious PowerShell window had closed. Nothing new appeared in the alerts queue, and the endpoint looked quiet again.
Then the same command ran the next morning at 8:00AM.
Agent Foskett was not looking for the original script anymore. He was looking for the thing that kept bringing it back.

Briefing summary
Persistence is how an attacker survives cleanup, reboot and user logoff. A scheduled task can relaunch a script, binary or command long after the original activity has disappeared from view.
The scheduled task was not noise
What Defender XDR can show
Find scheduled task creation
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
DeviceEvents
| where Timestamp > ago(30d)
| where ActionType has_any ("ScheduledTaskCreated", "ScheduledTaskUpdated")
or InitiatingProcessCommandLine has_any ("schtasks", "Schedule.Service")
| project
Timestamp,
DeviceName,
ActionType,
InitiatingProcessAccountName,
InitiatingProcessFileName,
InitiatingProcessCommandLine,
AdditionalFields
| order by Timestamp desc
The command line told the story
Review suspicious scheduled task command lines
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
DeviceProcessEvents
| where Timestamp > ago(30d)
| where FileName in~ ("schtasks.exe", "powershell.exe", "pwsh.exe", "cmd.exe")
| where ProcessCommandLine has_any (
"schtasks",
"powershell",
"pwsh",
"-enc",
"-EncodedCommand",
"DownloadString",
"Invoke-WebRequest",
"AppData",
"Temp",
"ProgramData"
)
| project
Timestamp,
DeviceName,
AccountName,
InitiatingProcessFileName,
FileName,
ProcessCommandLine
| order by Timestamp desc
Persistence often follows execution
Look for files written before the task
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
DeviceFileEvents
| where Timestamp > ago(30d)
| where ActionType in~ ("FileCreated", "FileModified", "FileRenamed")
| where FolderPath has_any ("\AppData\", "\Temp\", "\ProgramData\", "\Startup\")
| where FileName has_any (".ps1", ".vbs", ".js", ".bat", ".cmd", ".exe", ".dll", ".lnk")
| project
Timestamp,
DeviceName,
ActionType,
FolderPath,
FileName,
SHA256,
InitiatingProcessFileName,
InitiatingProcessCommandLine
| order by Timestamp desc
Check what the task launched
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
DeviceProcessEvents
| where Timestamp > ago(30d)
| where InitiatingProcessFileName in~ ("taskeng.exe", "taskhostw.exe", "svchost.exe", "schtasks.exe")
| where FileName in~ ("powershell.exe", "pwsh.exe", "cmd.exe", "wscript.exe", "cscript.exe", "mshta.exe", "rundll32.exe", "regsvr32.exe")
| project
Timestamp,
DeviceName,
AccountName,
InitiatingProcessFileName,
InitiatingProcessCommandLine,
FileName,
ProcessCommandLine
| order by Timestamp desc
What investigators should ask
Build the persistence timeline
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
let InvestigationDevice = "DEVICE-NAME-HERE";
let StartTime = ago(1d);
union isfuzzy=true
(
DeviceEvents
| where Timestamp > StartTime
| where DeviceName =~ InvestigationDevice
| project Timestamp, DeviceName, EvidenceType="DeviceEvent", ActionType, Detail=tostring(AdditionalFields), InitiatingProcessFileName, InitiatingProcessCommandLine
),
(
DeviceProcessEvents
| where Timestamp > StartTime
| where DeviceName =~ InvestigationDevice
| project Timestamp, DeviceName, EvidenceType="Process", ActionType, Detail=ProcessCommandLine, InitiatingProcessFileName, InitiatingProcessCommandLine
),
(
DeviceFileEvents
| where Timestamp > StartTime
| where DeviceName =~ InvestigationDevice
| project Timestamp, DeviceName, EvidenceType="File", ActionType, Detail=FolderPath, InitiatingProcessFileName, InitiatingProcessCommandLine
),
(
DeviceRegistryEvents
| where Timestamp > StartTime
| where DeviceName =~ InvestigationDevice
| project Timestamp, DeviceName, EvidenceType="Registry", ActionType, Detail=strcat(RegistryKey, " ", RegistryValueName, " ", RegistryValueData), InitiatingProcessFileName, InitiatingProcessCommandLine
)
| order by Timestamp asc
The task made the compromise durable
Related investigations
Final thought
The Scheduled Task Was The Persistence
This Agent Foskett investigation explains how scheduled task persistence can relaunch suspicious PowerShell, scripts or binaries after cleanup, reboot or user logon.
Microsoft Defender XDR Scheduled Task Investigation
DeviceEvents, DeviceProcessEvents, DeviceFileEvents and DeviceRegistryEvents can help defenders reconstruct scheduled task creation, task-triggered process execution, suspicious file writes and endpoint persistence.
KQL Hunting For Scheduled Task Persistence
Defenders can use KQL to hunt for schtasks.exe, Schedule.Service, PowerShell persistence, AppData payloads, taskhostw.exe, taskeng.exe and attacker behaviour in Microsoft Defender XDR.
