threat hunting

Hunting For Scheduled Task Persistence

Hunt for adversary persistence via Windows Scheduled Tasks by analyzing task creation events, suspicious task actions, and unusual scheduling patterns.

mitre-attackpersistenceproactive-detectionscheduled-taskst1053threat-hunting
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

When to Use

  • When proactively hunting for indicators of hunting for scheduled task persistence in the environment
  • After threat intelligence indicates active campaigns using these techniques
  • During incident response to scope compromise related to these techniques
  • When EDR or SIEM alerts trigger on related indicators
  • During periodic security assessments and purple team exercises

Prerequisites

  • EDR platform with process and network telemetry (CrowdStrike, MDE, SentinelOne)
  • SIEM with relevant log data ingested (Splunk, Elastic, Sentinel)
  • Sysmon deployed with comprehensive configuration
  • Windows Security Event Log forwarding enabled
  • Threat intelligence feeds for IOC correlation

Workflow

  1. Formulate Hypothesis: Define a testable hypothesis based on threat intelligence or ATT&CK gap analysis.
  2. Identify Data Sources: Determine which logs and telemetry are needed to validate or refute the hypothesis.
  3. Execute Queries: Run detection queries against SIEM and EDR platforms to collect relevant events.
  4. Analyze Results: Examine query results for anomalies, correlating across multiple data sources.
  5. Validate Findings: Distinguish true positives from false positives through contextual analysis.
  6. Correlate Activity: Link findings to broader attack chains and threat actor TTPs.
  7. Document and Report: Record findings, update detection rules, and recommend response actions.

Key Concepts

Concept Description
T1053.005 Scheduled Task
T1053.003 Cron
T1053.002 At

Tools & Systems

Tool Purpose
CrowdStrike Falcon EDR telemetry and threat detection
Microsoft Defender for Endpoint Advanced hunting with KQL
Splunk Enterprise SIEM log analysis with SPL queries
Elastic Security Detection rules and investigation timeline
Sysmon Detailed Windows event monitoring
Velociraptor Endpoint artifact collection and hunting
Sigma Rules Cross-platform detection rule format

Common Scenarios

  1. Scenario 1: Cobalt Strike persistence via schtasks creating periodic beacon
  2. Scenario 2: Ransomware scheduled task for re-execution after reboot
  3. Scenario 3: APT encoded PowerShell task running every 30 minutes
  4. Scenario 4: Insider task to periodically copy sensitive files

Output Format

Hunt ID: TH-HUNTIN-[DATE]-[SEQ]
Technique: T1053.005
Host: [Hostname]
User: [Account context]
Evidence: [Log entries, process trees, network data]
Risk Level: [Critical/High/Medium/Low]
Confidence: [High/Medium/Low]
Recommended Action: [Containment, investigation, monitoring]
Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 3

api-reference.md1.5 KB

API Reference — Hunting for Scheduled Task Persistence

Libraries Used

  • subprocess: Execute schtasks /query and schtasks /query /xml for task enumeration
  • csv: Parse schtasks CSV output for structured task analysis
  • python-evtx (Evtx): Parse Security EVTX for Event ID 4698 (Task Created)

CLI Interface

python agent.py enumerate                    # List and risk-score all tasks
python agent.py events --evtx-file <path>    # Scan EVTX for task creation events
python agent.py export --task-name <name>    # Export task XML definition

Core Functions

enumerate_tasks()

Runs schtasks /query /fo CSV /v and classifies each task as high/medium/low risk.

Returns: dict with total_tasks, high_risk, medium_risk, suspicious_tasks, non_vendor_tasks.

scan_event_log_4698(evtx_file)

Parses Windows Security EVTX for Event ID 4698 (Scheduled Task Created).

Parameters:

Name Type Description
evtx_file str Path to Security .evtx log file

export_task_xml(task_name)

Exports a task's full XML definition using schtasks /query /tn <name> /xml.

Risk Classification

Risk Criteria
High Action matches suspicious patterns (powershell -enc, certutil, temp paths)
Medium Non-vendor task (not under \Microsoft\, \Google\, etc.)
Low Known vendor task prefix

Dependencies

pip install python-evtx  # Optional, for EVTX parsing
standards.md1.5 KB

Standards and References - Hunting For Scheduled Task Persistence

MITRE ATT&CK Mappings

Technique Name Description
T1053.005 Scheduled Task See attack.mitre.org/techniques/T1053/005
T1053.003 Cron See attack.mitre.org/techniques/T1053/003
T1053.002 At See attack.mitre.org/techniques/T1053/002

Detection Data Sources

Source Event ID Purpose
Sysmon 1 Process creation with command line
Sysmon 3 Network connection initiated
Sysmon 7 Image loaded (DLL)
Sysmon 10 Process access (LSASS)
Sysmon 11 File creation
Sysmon 12/13 Registry create/set
Sysmon 22 DNS query
Sysmon 25 Process tampering
Windows Security 4624 Successful logon
Windows Security 4625 Failed logon
Windows Security 4648 Explicit credential logon
Windows Security 4672 Special privileges assigned
Windows Security 4688 Process creation
Windows Security 4697 Service installed
Windows Security 4698 Scheduled task created
Windows Security 4769 Kerberos TGS requested
Windows Security 5140 Network share accessed

References

workflows.md2.8 KB

Detailed Hunting Workflow - Hunting For Scheduled Task Persistence

Phase 1: Data Collection and Querying

Splunk SPL Query

index=wineventlog (EventCode=4698 OR EventCode=106)
| where match(Task_Content, "(?i)(powershell|cmd|wscript|mshta|http|encoded)")
| table _time Computer User Task_Name Task_Content

KQL Query (Microsoft Defender for Endpoint)

DeviceEvents
| where ActionType == "ScheduledTaskCreated"
| where AdditionalFields has_any ("powershell","cmd","wscript","http")
| project Timestamp, DeviceName, AccountName, AdditionalFields

Phase 2: Baseline and Anomaly Detection

Step 2.1 - Establish Normal Behavior Baseline

  • Collect 30 days of historical data for the targeted technique
  • Document expected patterns, frequencies, and legitimate use cases
  • Identify known false positive sources and document exceptions
  • Build statistical baseline (mean, standard deviation) for key metrics

Step 2.2 - Identify Anomalies

  • Compare current activity against the 30-day baseline
  • Flag events exceeding 3 standard deviations from normal
  • Prioritize anomalies by risk score and potential business impact
  • Cross-reference with threat intelligence for known IOCs

Phase 3: Investigation and Correlation

Step 3.1 - Deep Dive Analysis

  • For each anomaly, collect full process tree context
  • Correlate with network activity, file operations, and authentication events
  • Check binary signatures, file hashes, and certificate validity
  • Review user account context and access patterns

Step 3.2 - Attack Chain Reconstruction

  • Map findings to MITRE ATT&CK kill chain stages
  • Identify initial access vector if applicable
  • Trace lateral movement and privilege escalation paths
  • Determine data access and potential exfiltration

Phase 4: Validation and Response

Step 4.1 - True/False Positive Determination

  • Verify findings with system owners and IT operations
  • Check change management records for authorized activities
  • Validate user context (authorized actions vs. compromised account)
  • Document determination rationale for each finding

Step 4.2 - Response Actions

  • For confirmed threats: initiate incident response procedures
  • For detection gaps: create or update detection rules
  • For false positives: tune existing rules and update exclusions
  • Update threat hunting playbook with lessons learned

Phase 5: Documentation and Reporting

Step 5.1 - Hunt Report

  • Summarize hypothesis, methodology, and findings
  • Include all queries executed and their results
  • Document IOCs discovered and detection rules created
  • Provide recommendations for security improvements

Step 5.2 - Knowledge Base Update

  • Add findings to threat intelligence platform
  • Update MITRE ATT&CK coverage heatmap
  • Share detection rules via Sigma format
  • Schedule follow-up hunts for related techniques

Scripts 2

agent.py4.8 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Agent for hunting scheduled task persistence mechanisms (T1053.005)."""

import json
import argparse
import subprocess
import re
import csv
from io import StringIO
from datetime import datetime

SUSPICIOUS_TASK_PATTERNS = [
    r"powershell.*-enc", r"powershell.*downloadstring", r"powershell.*iex",
    r"cmd\.exe\s+/c", r"mshta\.exe", r"rundll32\.exe", r"regsvr32\.exe",
    r"certutil.*-decode", r"bitsadmin.*transfer",
    r"wscript\.exe", r"cscript\.exe",
    r"\\temp\\", r"\\tmp\\", r"\\appdata\\local\\temp",
    r"\\users\\public\\", r"\\programdata\\",
    r"base64", r"http://", r"https://.*\.exe",
]

LEGITIMATE_TASK_PREFIXES = [
    r"\\Microsoft\\", r"\\Adobe\\", r"\\Google\\", r"\\Apple\\",
    r"\\Mozilla\\", r"\\Intel\\", r"\\NVIDIA\\",
]


def enumerate_tasks():
    """Enumerate all scheduled tasks and flag suspicious ones."""
    try:
        proc = subprocess.run(
            ["schtasks", "/query", "/fo", "CSV", "/v"],
            capture_output=True, text=True, timeout=60
        )
    except (subprocess.TimeoutExpired, FileNotFoundError) as e:
        return {"error": str(e)}
    findings = []
    reader = csv.DictReader(StringIO(proc.stdout))
    for row in reader:
        task_name = row.get("TaskName", "")
        action = row.get("Task To Run", "")
        author = row.get("Author", "")
        schedule = row.get("Schedule Type", "")
        status = row.get("Status", "")
        is_legit = any(re.search(p, task_name, re.I) for p in LEGITIMATE_TASK_PREFIXES)
        is_suspicious = any(re.search(p, action, re.I) for p in SUSPICIOUS_TASK_PATTERNS)
        risk = "high" if is_suspicious else ("low" if is_legit else "medium")
        findings.append({
            "task_name": task_name,
            "action": action[:500],
            "author": author,
            "schedule": schedule,
            "status": status,
            "last_run": row.get("Last Run Time", ""),
            "next_run": row.get("Next Run Time", ""),
            "run_as_user": row.get("Run As User", ""),
            "risk": risk,
            "suspicious_match": is_suspicious,
        })
    suspicious = [f for f in findings if f["risk"] in ("high", "medium")]
    return {
        "timestamp": datetime.utcnow().isoformat(),
        "total_tasks": len(findings),
        "high_risk": sum(1 for f in findings if f["risk"] == "high"),
        "medium_risk": sum(1 for f in findings if f["risk"] == "medium"),
        "suspicious_tasks": [f for f in findings if f["suspicious_match"]],
        "non_vendor_tasks": [f for f in findings if f["risk"] == "medium"],
    }


def scan_event_log_4698(evtx_file):
    """Parse Security EVTX for Event ID 4698 (Scheduled Task Created)."""
    try:
        import Evtx.Evtx as evtx_lib
    except ImportError:
        return {"error": "python-evtx not installed"}
    findings = []
    with evtx_lib.Evtx(evtx_file) as log:
        for record in log.records():
            xml = record.xml()
            if "<EventID>4698</EventID>" not in xml:
                continue
            suspicious = any(re.search(p, xml, re.I) for p in SUSPICIOUS_TASK_PATTERNS)
            findings.append({
                "record_id": record.record_num(),
                "suspicious": suspicious,
                "xml_snippet": xml[:1000],
            })
    return {
        "file": evtx_file,
        "task_creation_events": len(findings),
        "suspicious_events": sum(1 for f in findings if f["suspicious"]),
        "findings": findings[:200],
    }


def export_task_xml(task_name):
    """Export a specific scheduled task's XML configuration for analysis."""
    try:
        proc = subprocess.run(
            ["schtasks", "/query", "/tn", task_name, "/xml"],
            capture_output=True, text=True, timeout=10
        )
        if proc.returncode == 0:
            return {"task_name": task_name, "xml": proc.stdout}
        return {"error": proc.stderr.strip()}
    except (subprocess.TimeoutExpired, FileNotFoundError) as e:
        return {"error": str(e)}


def main():
    parser = argparse.ArgumentParser(description="Hunt for scheduled task persistence")
    sub = parser.add_subparsers(dest="command")
    sub.add_parser("enumerate", help="Enumerate and risk-score scheduled tasks")
    e = sub.add_parser("events", help="Scan Security EVTX for task creation events")
    e.add_argument("--evtx-file", required=True)
    x = sub.add_parser("export", help="Export task XML for analysis")
    x.add_argument("--task-name", required=True)
    args = parser.parse_args()
    if args.command == "enumerate":
        result = enumerate_tasks()
    elif args.command == "events":
        result = scan_event_log_4698(args.evtx_file)
    elif args.command == "export":
        result = export_task_xml(args.task_name)
    else:
        parser.print_help()
        return
    print(json.dumps(result, indent=2, default=str))


if __name__ == "__main__":
    main()
process.py3.5 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Scheduled Task Persistence Detection - Analyzes Windows task creation events for suspicious persistence indicators."""

import json, csv, argparse, datetime, re
from collections import defaultdict
from pathlib import Path

SUSPICIOUS_TASK_PATTERNS = {
    "commands": [
        r"powershell", r"cmd\.exe", r"wscript", r"cscript", r"mshta",
        r"certutil", r"bitsadmin", r"rundll32", r"regsvr32",
    ],
    "arguments": [
        r"-enc", r"-encodedcommand", r"iex", r"downloadstring",
        r"http[s]?://", r"bypass", r"hidden", r"base64",
    ],
    "paths": [
        r"\\temp\\", r"\\appdata\\", r"\\programdata\\",
        r"\\public\\", r"\\downloads\\",
    ],
}

def parse_logs(path):
    p = Path(path)
    if p.suffix == ".json":
        with open(p, encoding="utf-8") as f:
            data = json.load(f)
            return data if isinstance(data, list) else data.get("events", [])
    elif p.suffix == ".csv":
        with open(p, encoding="utf-8-sig") as f:
            return [dict(r) for r in csv.DictReader(f)]
    return []

def analyze_task(event):
    eid = event.get("EventCode", event.get("EventID", event.get("event_id", "")))
    if str(eid) not in ("4698", "106"):
        return None
    task_name = event.get("Task_Name", event.get("TaskName", ""))
    task_content = event.get("Task_Content", event.get("TaskContent", event.get("command_line", "")))
    host = event.get("Computer", event.get("hostname", "unknown"))
    user = event.get("User", event.get("AccountName", "unknown"))
    ts = event.get("_time", event.get("timestamp", event.get("UtcTime", "")))

    risk = 20
    indicators = []
    for cat, patterns in SUSPICIOUS_TASK_PATTERNS.items():
        for pattern in patterns:
            if re.search(pattern, task_content, re.IGNORECASE):
                risk += 15
                indicators.append(f"Suspicious {cat}: {pattern}")
    if not indicators:
        return None
    risk = min(risk, 100)
    return {
        "technique": "T1053.005",
        "task_name": task_name,
        "task_content": task_content[:500],
        "hostname": host, "user": user, "timestamp": ts,
        "risk_score": risk,
        "risk_level": "CRITICAL" if risk >= 70 else "HIGH" if risk >= 50 else "MEDIUM" if risk >= 30 else "LOW",
        "indicators": indicators,
    }

def run_hunt(input_path, output_dir):
    print(f"[*] Scheduled Task Hunt - {datetime.datetime.now().isoformat()}")
    events = parse_logs(input_path)
    findings = [f for f in (analyze_task(e) for e in events) if f]
    Path(output_dir).mkdir(parents=True, exist_ok=True)
    with open(Path(output_dir) / "schtask_findings.json", "w", encoding="utf-8") as f:
        json.dump({"hunt_id": f"TH-SCHTASK-{datetime.date.today()}", "findings": findings}, f, indent=2)
    print(f"[+] {len(findings)} findings written to {output_dir}")

def main():
    p = argparse.ArgumentParser(description="Scheduled Task Persistence Detection")
    sp = p.add_subparsers(dest="cmd")
    h = sp.add_parser("hunt")
    h.add_argument("--input", "-i", required=True)
    h.add_argument("--output", "-o", default="./schtask_output")
    sp.add_parser("queries")
    args = p.parse_args()
    if args.cmd == "hunt": run_hunt(args.input, args.output)
    elif args.cmd == "queries":
        print("=== Splunk ===")
        print('''index=wineventlog (EventCode=4698 OR EventCode=106)
| where match(Task_Content, "(?i)(powershell|cmd|wscript|mshta|http|encoded)")
| table _time Computer User Task_Name Task_Content''')
    else: p.print_help()

if __name__ == "__main__": main()

Assets 1

template.mdtext/markdown · 2.6 KB
Keep exploring