>
av.Amit VijayanCYBERSECURITY & RESEARCHLet’s connect
Back to the library

Cyber Playbooks

Hunting Scheduled Task Persistence: The Foothold That Survives a Reboot

Dark illustration of clockwork gears with a glowing red heartbeat pulse line running beneath them, symbolizing scheduled task persistence

Case file: persistence via Scheduled Tasks

You've contained the initial access. The phishing payload is deleted, the malicious process is dead, the EDR console shows the endpoint "clean." Then, 72 hours later, the same host phones home again. Nothing in your first sweep explains the re-infection — until you look at the Scheduled Task cache and find a job named GoogleUpdateTaskMachineUA that points to a binary in %APPDATA%. That's persistence, and it's one of the most reliable tricks in the attacker playbook because it looks like normal Windows administration.

This hunt is about one pattern: an adversary creating or modifying a Scheduled Task so their payload runs on a schedule — at logon, on a timer, or every time the machine boots. It's MITRE ATT&CK T1053.005 (Scheduled Task/Job: Scheduled Task), and it shows up in ransomware operations, intrusions, and commodity malware alike. The evidence trail is rich — task creation events, process launches of schtasks.exe, and registry writes under the Task Cache — which makes it an ideal hypothesis-driven hunt.

The hypothesis

If an attacker is establishing persistence on Windows endpoints in our environment, then we will find Scheduled Task registrations (Event ID 4698 or schtasks.exe /create executions) where the task action points to an unsigned binary, a script in a user-writable path, or a command line containing download/execution primitives — launched by a process that doesn't normally manage tasks.

Data you'll need

Log sourceTable / indexWhat it gives you
Microsoft Defender for EndpointDeviceProcessEvents, DeviceRegistryEventsProcess launches (schtasks.exe, PowerShell *-ScheduledTask cmdlets) and Task Cache registry writes
Windows Security logSecurityEvent / index=wineventlog, EventCode 4698"A scheduled task was created" audit events with the task XML
SysmonEventCode 1 (process create), 13 (registry set)Full command lines and parent-child process chains

Hunting with KQL

Start broad: any Scheduled Task creation activity in the last 14 days, via schtasks.exe, PowerShell task cmdlets, or the Task Scheduler COM interface.

// Hunt: Scheduled Task creation outside normal admin tooling
let SuspiciousParents = dynamic(["cmd.exe", "powershell.exe", "pwsh.exe",
    "wscript.exe", "cscript.exe", "mshta.exe", "rundll32.exe", "regsvr32.exe"]);
DeviceProcessEvents
| where TimeGenerated > ago(14d)
| where (FileName =~ "schtasks.exe" and ProcessCommandLine has_any ("/create", "/change", "/run"))
    or (FileName in~ ("powershell.exe", "pwsh.exe")
        and ProcessCommandLine has_any ("New-ScheduledTask", "Register-ScheduledTask",
            "Set-ScheduledTask", "schtasks"))
| extend TaskActionSuspicious = ProcessCommandLine has_any (
    "%appdata%", "%temp%", "powershell", "cmd /c", "wscript",
    "http://", "https://", "bitsadmin", "certutil", "-enc", "-EncodedCommand")
| project TimeGenerated, DeviceName, InitiatingProcessFileName,
    InitiatingProcessCommandLine, FileName, ProcessCommandLine,
    InitiatingProcessAccountName, TaskActionSuspicious
| order by TaskActionSuspicious desc, TimeGenerated desc

What this does: the query collects every task registration event and promotes a boolean flag, TaskActionSuspicious, when the command line references user-writable paths, scripting engines, encoded commands, or URLs — all classic markers of a malicious task action. Sorting true-positives to the top keeps the triage queue short.

Back it up with the audit trail. Event ID 4698 fires whenever a scheduled task is created and embeds the task XML, including the <Command> and <Arguments> the task will run:

// Hunt: 4698 task-creation audit events with suspicious actions
SecurityEvent
| where TimeGenerated > ago(14d)
| where EventID == 4698
| extend TaskName = extract(@"TaskName:\s+(\S+)", 1, EventData),
         TaskCommand = extract(@"<Command>(.*?)</Command>", 1, EventData)
| where TaskCommand has_any (@"\AppData\", @"\Temp\", "powershell", "cmd.exe", ".ps1", ".vbs", ".js")
| project TimeGenerated, Computer, Account, TaskName, TaskCommand
Example: what a true-positive result row looks like
TimeGenerated2026-09-18 03:14:22 UTC
DeviceNameWS-FIN-0442
InitiatingProcessFileNamepowershell.exe
ProcessCommandLineschtasks /create /tn "OfficeTelemetry" /tr "cmd /c powershell -w hidden -enc aQBmACgAWwBJAG4AdABQAHQAcgBdADoAOgBTAGkAegBlACAAPQAgADQAKQA=" /sc onlogon /f
TaskActionSuspicioustrue
Why it's maliciousTask name mimics legitimate software, triggers at every logon, and runs a Base64-encoded hidden PowerShell payload — three persistence red flags in one row.

Hunting with Splunk

The same hunt translates cleanly to Splunk. Use the Windows Security log for 4698 creations and Sysmon EventCode 1 for full command-line fidelity:

index=wineventlog EventCode=4698 earliest=-14d
| rex field=_raw "TaskName:\s+(?<task_name>\S+)"
| rex field=_raw "Task Content:\s*(?<task_xml>[\s\S]*)"
| rex field=task_xml "<Command>(?<task_command>.*?)</Command>"
| where match(task_command, "(?i)(appdata|\\\\temp\\\\|powershell|cmd\.exe|\.ps1|\.vbs|\.js|http)")
| table _time, host, user, task_name, task_command

What this does: it pulls every 4698 "scheduled task was created" event, extracts the task name and the command the task will execute from the embedded task XML, and keeps only rows where the command touches user-writable paths, scripting engines, or URLs. Pair it with a Sysmon sweep to catch attackers who bypass the audit log by editing the Task Cache registry directly:

index=sysmon EventCode=13 earliest=-14d
    TargetObject="*\\Microsoft\\Windows NT\\CurrentVersion\\Schedule\\TaskCache\\Tree\\*"
| stats count by host, TargetObject, _time

Example hit: a Sysmon EventCode 1 row on host WS-FIN-0442 — Image: C:\Windows\System32\schtasks.exe, CommandLine: schtasks /create /tn "OfficeTelemetry" /tr "cmd /c powershell -w hidden -enc ...", ParentImage: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe, User: FINANCE\jchen. PowerShell spawning schtasks with an encoded payload at 3 a.m. is not patch management — it's an intruder bolting the door from the inside.

Validating the hit

  1. Read the task XML. On the host (or from the 4698 event's Task Content), inspect C:\Windows\System32\Tasks\<TaskName>. Check the <Actions> node: what binary runs, with what arguments, and on what trigger? Malicious tasks typically use LogonTrigger or short-interval TimeTrigger schedules.
  2. Vet the binary. Hash the executable the task points to, check its signature (Get-AuthenticodeSignature), and submit the hash to your threat intel feeds. An unsigned binary in %APPDATA% or %TEMP% is damning; a signed vendor updater is usually not.
  3. Reconstruct the parent chain. Who created the task? Trace InitiatingProcessFileName back: a task created by services.exe during a software push differs sharply from one created by a Word-launched PowerShell. Correlate with process creation logs ±10 minutes around the 4698 timestamp.
  4. Check for siblings. One malicious task is rarely alone. Search the same host for other recent task creations, new Run registry keys, and new services — attackers layer persistence mechanisms.

Tuning out false positives

  • Software updaters: Google Update, Adobe ARM, and vendor agents register tasks constantly. Whitelist by signed publisher + known task names, not by task name alone (attackers mimic names like GoogleUpdateTaskMachineUA).
  • Configuration management: SCCM/MECM, Intune, and GPO-deployed tasks create tasks at scale. Filter on the creating account (SYSTEM via known management processes) and known task paths like \Microsoft\....
  • Admin maintenance scripts: IT teams schedule log cleanup and backup jobs. Keep an inventory of sanctioned tasks; anything not on the list that runs a script from a user profile deserves a look.

What to do next

A confirmed malicious task means the host is compromised right now — act on that assumption. Isolate the endpoint in EDR, then delete the task (schtasks /delete /tn "<name>" /f) and remove the payload binary. Don't stop at one host: sweep the fleet for the same task name, hash, and command-line pattern, since persistence is often deployed enterprise-wide before ransomware detonation. Reset credentials for the affected user, capture a memory image if your IR process calls for it, and open an incident — persistence is a foothold, and footholds exist to be used.

Next in this series: obfuscated PowerShell and AMSI bypass attempts — hunting the payload that the scheduled task was built to launch.

From the HackInvasion archive

This sample preserves the article’s original text and publication date, restyled for Amit’s personal website. Older material may describe historical tools or techniques.

Read the original article on Blogger

Keep exploring.

All articles