Tools · Windows security and Event Logs · 08 / 27
Event ID 4698: how to detect new scheduled tasks on Windows
Enable the audit setting for scheduled task events, read the task XML inside 4698, and compare against the Task Scheduler operational log.
Task events
4698 means a scheduled task was created, 4699 deleted, 4700 enabled, 4701 disabled, and 4702 updated. They require the Audit Other Object Access Events subcategory. The event includes the full task XML, so you can read the trigger, the action’s command and arguments, and the account it runs as.
The Microsoft-Windows-TaskScheduler/Operational log, which must also be enabled, records registration (106), execution starts (200, 129), and completion (201), which helps you see whether a task actually ran.
auditpol /set /subcategory:"Other Object Access Events" /success:enable wevtutil sl Microsoft-Windows-TaskScheduler/Operational /e:true
What a malicious task looks like
Tasks that run every few minutes or at logon, whose action is powershell, cmd, mshta, rundll32, or a binary in a user profile or ProgramData. Tasks with names copying Windows components but stored in the root folder instead of \Microsoft\Windows. Tasks running as SYSTEM created by a non-administrative workflow.
Software updaters create many legitimate tasks, so compare against a known-good list per image.
Lab: register a task and read its XML
Create a harmless task that runs at logon and writes the date to a file, using schtasks. Then query 4698 and extract the TaskName and the command from the TaskContent XML. Delete the task and find 4699.
Finally, list every task outside \Microsoft\ with its action, which is a good periodic inventory.
schtasks /create /tn "UpdaterCheck" /tr "cmd.exe /c echo %date% >> C:\Users\Public\t.txt" /sc onlogon /ru SYSTEM
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4698; StartTime=(Get-Date).AddHours(-1)} |
% { $x=[xml]$_.ToXml(); $d=@{}; $x.Event.EventData.Data | % { $d[$_.Name]=$_.'#text' }
$t=[xml]$d.TaskContent; [pscustomobject]@{Task=$d.TaskName; By=$d.SubjectUserName; Cmd=$t.Task.Actions.Exec.Command; Args=$t.Task.Actions.Exec.Arguments} }
schtasks /delete /tn "UpdaterCheck" /f
Get-ScheduledTask | Where-Object TaskPath -notlike '\Microsoft\*' | % { [pscustomobject]@{Name=$_.TaskName; Path=$_.TaskPath; Action=($_.Actions.Execute -join ';')} }Response
Export the task XML with schtasks /query /tn name /xml before deleting it. Hash any binary it runs. Look for the same task name on other hosts, since attackers reuse names across a network.
Pair this check with services and Run keys. Tasks are one of several persistence locations, and finding one should prompt a check of the others.
Diagram
From task creation to verdict
- 4698
Task created; XML includes trigger, action, and account.
- Parse
Read Actions.Exec.Command and Arguments.
- Ran?
Check TaskScheduler/Operational 200 and 201.
- Compare
Match against known-good tasks for that image.
Check yourself
1. Which audit subcategory produces 4698?
2. Where in a 4698 event is the command the task runs?
3. Which log shows whether a task actually executed?