#!/usr/bin/env python3
"""Agent for hunting suspicious scheduled tasks on Windows endpoints."""

import json
import argparse
import re
import xml.etree.ElementTree as ET
from datetime import datetime

try:
    import Evtx.Evtx as evtx
except ImportError:
    evtx = None


SUSPICIOUS_ACTIONS = [
    r"powershell", r"pwsh", r"cmd\.exe.*/c", r"wscript", r"cscript",
    r"mshta", r"rundll32", r"regsvr32", r"certutil",
    r"bitsadmin", r"msiexec.*http",
]

SUSPICIOUS_PATHS = [
    r"\\temp\\", r"\\tmp\\", r"\\appdata\\", r"\\downloads\\",
    r"\\public\\", r"\\programdata\\", r"\\users\\.*\\desktop\\",
    r"c:\\windows\\temp",
]

LEGITIMATE_TASK_PREFIXES = [
    "\\Microsoft\\Windows\\", "\\Microsoft\\Office\\",
    "\\Microsoft\\EdgeUpdate\\",
]


def parse_schtasks_csv(csv_path):
    """Parse output of schtasks /query /fo CSV /v."""
    import csv
    tasks = []
    with open(csv_path, newline="", encoding="utf-8-sig") as f:
        reader = csv.DictReader(f)
        for row in reader:
            tasks.append({
                "hostname": row.get("HostName", ""),
                "task_name": row.get("TaskName", ""),
                "status": row.get("Status", ""),
                "task_to_run": row.get("Task To Run", ""),
                "run_as_user": row.get("Run As User", ""),
                "schedule_type": row.get("Schedule Type", ""),
                "author": row.get("Author", ""),
            })
    return tasks


def analyze_tasks(tasks):
    """Analyze scheduled tasks for suspicious properties."""
    findings = []
    for task in tasks:
        name = task.get("task_name", "")
        action = task.get("task_to_run", "")
        run_as = task.get("run_as_user", "")
        is_legit_prefix = any(name.startswith(p) for p in LEGITIMATE_TASK_PREFIXES)
        risk_score = 0
        reasons = []
        for pattern in SUSPICIOUS_ACTIONS:
            if re.search(pattern, action, re.IGNORECASE):
                risk_score += 30
                reasons.append(f"suspicious_action:{pattern}")
        for pattern in SUSPICIOUS_PATHS:
            if re.search(pattern, action, re.IGNORECASE):
                risk_score += 25
                reasons.append(f"suspicious_path:{pattern}")
        if run_as and "SYSTEM" in run_as.upper():
            risk_score += 15
            reasons.append("runs_as_system")
        if not is_legit_prefix and name.count("\\") <= 1:
            risk_score += 10
            reasons.append("non_standard_location")
        if re.search(r"(http|https|ftp)://", action, re.IGNORECASE):
            risk_score += 40
            reasons.append("network_url_in_action")
        if re.search(r"-enc\s|encodedcommand", action, re.IGNORECASE):
            risk_score += 35
            reasons.append("encoded_command")
        if risk_score > 0:
            severity = "CRITICAL" if risk_score >= 60 else "HIGH" if risk_score >= 30 else "MEDIUM"
            findings.append({
                "task_name": name,
                "action": action[:500],
                "run_as_user": run_as,
                "risk_score": risk_score,
                "severity": severity,
                "reasons": reasons,
                "hostname": task.get("hostname", ""),
            })
    return sorted(findings, key=lambda x: x["risk_score"], reverse=True)


def hunt_evtx_4698(evtx_path):
    """Hunt Event ID 4698 (scheduled task creation) in EVTX."""
    if evtx is None:
        return []
    findings = []
    with evtx.Evtx(evtx_path) as log:
        for record in log.records():
            xml_str = record.xml()
            try:
                root = ET.fromstring(xml_str)
                ns = {"ns": "http://schemas.microsoft.com/win/2004/08/events/event"}
                eid_el = root.find(".//ns:EventID", ns)
                if eid_el is None or eid_el.text != "4698":
                    continue
                data = {}
                for d in root.findall(".//ns:Data", ns):
                    data[d.get("Name", "")] = d.text or ""
                task_name = data.get("TaskName", "")
                task_content = data.get("TaskContent", "")
                is_suspicious = any(
                    re.search(p, task_content, re.IGNORECASE) for p in SUSPICIOUS_ACTIONS)
                if is_suspicious:
                    findings.append({
                        "timestamp": record.timestamp().isoformat(),
                        "task_name": task_name,
                        "user": data.get("SubjectUserName", ""),
                        "task_content_preview": task_content[:500],
                        "severity": "HIGH",
                    })
            except ET.ParseError:
                continue
    return findings


def generate_sigma_rule():
    """Generate Sigma rule for suspicious scheduled task creation."""
    return {
        "title": "Suspicious Scheduled Task Created",
        "id": "e4db2c6a-3f1b-4c8d-9e2a-7b5c4d6e8f0a",
        "status": "production",
        "level": "high",
        "logsource": {"product": "windows", "service": "security"},
        "detection": {
            "selection": {"EventID": 4698},
            "filter_legit": {"TaskName|startswith": ["\\Microsoft\\Windows\\", "\\Microsoft\\Office\\"]},
            "suspicious_content": {
                "TaskContent|contains": ["powershell", "cmd /c", "wscript", "mshta",
                                          "\\Temp\\", "\\AppData\\", "http://", "https://"],
            },
            "condition": "selection and not filter_legit and suspicious_content",
        },
        "tags": ["attack.persistence", "attack.execution", "attack.t1053.005"],
    }


def main():
    parser = argparse.ArgumentParser(description="Suspicious Scheduled Tasks Hunter")
    parser.add_argument("--csv", help="schtasks CSV export")
    parser.add_argument("--evtx", help="Security EVTX log file")
    parser.add_argument("--output", default="schtask_hunt_report.json")
    parser.add_argument("--action", choices=["analyze", "hunt_evtx", "sigma", "full"],
                        default="full")
    args = parser.parse_args()

    report = {"generated_at": datetime.utcnow().isoformat(), "findings": {}}

    if args.action in ("analyze", "full") and args.csv:
        tasks = parse_schtasks_csv(args.csv)
        findings = analyze_tasks(tasks)
        report["findings"]["task_analysis"] = findings
        print(f"[+] Suspicious tasks: {len(findings)} / {len(tasks)} total")

    if args.action in ("hunt_evtx", "full") and args.evtx:
        findings = hunt_evtx_4698(args.evtx)
        report["findings"]["evtx_4698"] = findings
        print(f"[+] EVTX 4698 suspicious: {len(findings)}")

    if args.action in ("sigma", "full"):
        report["findings"]["sigma_rule"] = generate_sigma_rule()
        print("[+] Sigma rule generated")

    with open(args.output, "w") as f:
        json.dump(report, f, indent=2, default=str)
    print(f"[+] Report saved to {args.output}")


if __name__ == "__main__":
    main()
