threat hunting

Hunting for Unusual Service Installations

Detect suspicious Windows service installations (MITRE ATT&CK T1543.003) by parsing System event logs for Event ID 7045, analyzing service binary paths, and identifying indicators of persistence mechanisms.

event-7045persistenceservice-installationsysmont1543.003threat-huntingwindows-services
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

Overview

Attackers frequently install malicious Windows services for persistence and privilege escalation (MITRE ATT&CK T1543.003 — Create or Modify System Process: Windows Service). Event ID 7045 in the System event log records every new service installation. This skill parses .evtx log files to extract service installation events, flags suspicious binary paths (temp directories, PowerShell, cmd.exe, encoded commands), and correlates with known attack patterns.

When to Use

  • When investigating security incidents that require hunting for unusual service installations
  • When building detection rules or threat hunting queries for this domain
  • When SOC analysts need structured procedures for this analysis type
  • When validating security monitoring coverage for related attack techniques

Prerequisites

  • Python 3.9+ with python-evtx, lxml
  • Windows System event log (.evtx) files
  • Access to live System event log (optional, for real-time monitoring)
  • Sysmon logs for enhanced process tracking (optional)

Steps

  1. Parse System.evtx for Event ID 7045 (new service installed)
  2. Extract service name, binary path, service type, and account
  3. Flag services with suspicious binary paths (temp dirs, encoded commands)
  4. Detect PowerShell-based service creation patterns
  5. Identify services running as LocalSystem with unusual paths
  6. Cross-reference with known legitimate service baselines
  7. Generate threat hunting report with MITRE ATT&CK T1543.003 mapping

Expected Output

  • JSON report listing all new service installations with risk scores, suspicious indicators, and remediation recommendations
  • Timeline of service installation events with binary path analysis
Source materials

References and resources

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

References 1

api-reference.md2.6 KB

API Reference — Hunting for Unusual Service Installations

Libraries Used

  • python-evtx: Parse Windows .evtx event log files
  • lxml: XML parsing of EVTX record XML
  • re: Regex matching for suspicious binary path patterns
  • collections.Counter: Statistics aggregation

CLI Interface

python agent.py System.evtx parse
python agent.py System.evtx hunt
python agent.py System.evtx stats
python agent.py System.evtx full

Core Functions

parse_evtx_events(evtx_path) — Extract Event ID 7045 records

Opens .evtx file with evtx.Evtx(), iterates records, parses XML with lxml. Extracts: ServiceName, ImagePath, ServiceType, StartType, AccountName, timestamp. Namespace: http://schemas.microsoft.com/win/2004/08/events/event

analyze_service_path(image_path) — Binary path risk analysis

Matches against 17 suspicious patterns (temp dirs, PowerShell, encoded commands, LOLBins, download patterns). Checks against 5 legitimate path prefixes. Scoring: +20 for non-standard path, +15 per suspicious indicator. Max 100.

hunt_suspicious_services(evtx_path) — Main hunting engine

Combines parsing and analysis. Extra +20 risk for LocalSystem account with non-standard binary path. Results sorted by risk score descending.

generate_statistics(results) — Summary statistics

Counts risk distribution, top indicators, service account usage.

full_hunt(evtx_path) — Comprehensive threat hunt report

Suspicious Path Patterns

Pattern Indicator
\temp\, \tmp\ temp_directory
\appdata\ appdata_directory
\users\public\ public_user_directory
powershell.exe powershell_execution
cmd.exe /c cmd_execution
-enc, -encodedcommand encoded_command
downloadstring, webclient download_pattern
invoke-expression, iex invoke_expression
mshta, regsvr32, rundll32 lolbin_execution

Legitimate Service Path Prefixes

  • C:\Windows\System32\
  • C:\Windows\SysWOW64\
  • C:\Program Files\
  • C:\Program Files (x86)\
  • C:\Windows\Microsoft.NET\

Event ID 7045 Fields

Field Description
ServiceName Display name of installed service
ImagePath Binary path and arguments
ServiceType user mode service, kernel driver, etc.
StartType auto start, demand start, boot start
AccountName Service account (LocalSystem, etc.)

MITRE ATT&CK Mapping

  • T1543.003 — Create or Modify System Process: Windows Service
  • Tactics: Persistence, Privilege Escalation

Dependencies

  • python-evtx >= 0.7.4
  • lxml >= 4.9.0

Scripts 1

agent.py7.6 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Agent for hunting suspicious Windows service installations (T1543.003) via Event ID 7045."""

import json
import re
import argparse
from datetime import datetime
from collections import Counter

try:
    from lxml import etree
except ImportError:
    etree = None

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

SUSPICIOUS_PATH_PATTERNS = [
    (r"\\temp\\", "temp_directory"),
    (r"\\tmp\\", "temp_directory"),
    (r"\\appdata\\", "appdata_directory"),
    (r"\\users\\public\\", "public_user_directory"),
    (r"\\programdata\\[^\\]+\.[^\\]+$", "loose_file_in_programdata"),
    (r"powershell\.exe", "powershell_execution"),
    (r"cmd\.exe\s*/c", "cmd_execution"),
    (r"-enc\s+", "encoded_command"),
    (r"-encodedcommand", "encoded_command"),
    (r"frombase64string", "base64_decode"),
    (r"downloadstring|downloadfile|webclient", "download_pattern"),
    (r"invoke-expression|iex\s", "invoke_expression"),
    (r"\\windows\\temp\\", "windows_temp"),
    (r"mshta\.exe|regsvr32\.exe|rundll32\.exe|msiexec\.exe", "lolbin_execution"),
    (r"sc\.exe\s+create", "sc_create_chain"),
    (r"\\\$recycle\.bin\\", "recycle_bin"),
    (r"\\perflog", "perflog_abuse"),
]

LEGITIMATE_SERVICE_PATHS = [
    r"^\"?C:\\Windows\\System32\\",
    r"^\"?C:\\Windows\\SysWOW64\\",
    r"^\"?C:\\Program Files\\",
    r"^\"?C:\\Program Files \(x86\)\\",
    r"^\"?C:\\Windows\\Microsoft\.NET\\",
]


def parse_evtx_events(evtx_path):
    """Parse System.evtx and extract Event ID 7045 records."""
    if not evtx:
        raise RuntimeError("python-evtx not installed: pip install python-evtx")
    events = []
    ns = {"e": "http://schemas.microsoft.com/win/2004/08/events/event"}
    with evtx.Evtx(evtx_path) as log:
        for record in log.records():
            try:
                xml = record.xml()
                root = etree.fromstring(xml.encode("utf-8") if isinstance(xml, str) else xml)
                event_id_el = root.find(".//e:System/e:EventID", ns)
                if event_id_el is None or event_id_el.text != "7045":
                    continue
                system = root.find(".//e:System", ns)
                event_data = root.find(".//e:EventData", ns)
                data_fields = {}
                if event_data is not None:
                    for data in event_data.findall("e:Data", ns):
                        name = data.get("Name", "")
                        data_fields[name] = data.text or ""
                time_el = system.find("e:TimeCreated", ns)
                timestamp = time_el.get("SystemTime", "") if time_el is not None else ""
                computer_el = system.find("e:Computer", ns)
                computer = computer_el.text if computer_el is not None else ""
                events.append({
                    "timestamp": timestamp,
                    "event_id": 7045,
                    "computer": computer,
                    "service_name": data_fields.get("ServiceName", ""),
                    "image_path": data_fields.get("ImagePath", ""),
                    "service_type": data_fields.get("ServiceType", ""),
                    "start_type": data_fields.get("StartType", ""),
                    "account_name": data_fields.get("AccountName", ""),
                })
            except Exception:
                continue
    return events


def analyze_service_path(image_path):
    """Analyze a service binary path for suspicious indicators."""
    findings = []
    path_lower = image_path.lower()
    for pattern, indicator in SUSPICIOUS_PATH_PATTERNS:
        if re.search(pattern, path_lower, re.IGNORECASE):
            findings.append(indicator)
    is_legitimate = any(re.match(p, image_path, re.IGNORECASE) for p in LEGITIMATE_SERVICE_PATHS)
    risk_score = 0
    if not is_legitimate:
        risk_score += 20
    risk_score += len(findings) * 15
    risk_score = min(risk_score, 100)
    return {
        "suspicious_indicators": findings,
        "legitimate_path": is_legitimate,
        "risk_score": risk_score,
        "risk_level": "CRITICAL" if risk_score >= 70 else "HIGH" if risk_score >= 50 else "MEDIUM" if risk_score >= 20 else "LOW",
    }


def hunt_suspicious_services(evtx_path):
    """Main hunting function: parse events and analyze each service."""
    events = parse_evtx_events(evtx_path)
    results = []
    for event in events:
        analysis = analyze_service_path(event.get("image_path", ""))
        entry = {**event, **analysis}
        if entry.get("account_name", "").lower() == "localsystem" and not analysis["legitimate_path"]:
            entry["risk_score"] = min(entry["risk_score"] + 20, 100)
            entry["risk_level"] = "CRITICAL" if entry["risk_score"] >= 70 else entry["risk_level"]
            entry["suspicious_indicators"].append("localsystem_nonstandard_path")
        results.append(entry)
    results.sort(key=lambda x: x.get("risk_score", 0), reverse=True)
    return results


def generate_statistics(results):
    """Generate summary statistics from hunting results."""
    total = len(results)
    risk_counts = Counter(r.get("risk_level", "LOW") for r in results)
    indicator_counts = Counter()
    account_counts = Counter()
    for r in results:
        for ind in r.get("suspicious_indicators", []):
            indicator_counts[ind] += 1
        account_counts[r.get("account_name", "Unknown")] += 1
    return {
        "total_service_installations": total,
        "risk_distribution": dict(risk_counts),
        "top_indicators": dict(indicator_counts.most_common(10)),
        "service_accounts": dict(account_counts.most_common(10)),
        "critical_count": risk_counts.get("CRITICAL", 0),
        "high_count": risk_counts.get("HIGH", 0),
    }


def full_hunt(evtx_path):
    """Run comprehensive service installation threat hunt."""
    results = hunt_suspicious_services(evtx_path)
    stats = generate_statistics(results)
    suspicious = [r for r in results if r.get("risk_score", 0) >= 20]
    return {
        "hunt_type": "Unusual Service Installation (T1543.003)",
        "timestamp": datetime.utcnow().isoformat(),
        "evtx_file": evtx_path,
        "statistics": stats,
        "suspicious_services": suspicious[:30],
        "mitre_technique": {
            "id": "T1543.003",
            "name": "Create or Modify System Process: Windows Service",
            "tactic": "Persistence, Privilege Escalation",
        },
        "recommendation": "Investigate CRITICAL and HIGH services. Verify binary hashes against known-good baselines." if stats["critical_count"] + stats["high_count"] > 0
            else "No high-risk service installations detected.",
    }


def main():
    parser = argparse.ArgumentParser(description="Service Installation Threat Hunting Agent (T1543.003)")
    parser.add_argument("evtx", help="Path to System.evtx file")
    sub = parser.add_subparsers(dest="command")
    sub.add_parser("parse", help="Parse and list all Event ID 7045 records")
    sub.add_parser("hunt", help="Hunt for suspicious service installations")
    sub.add_parser("stats", help="Generate hunting statistics")
    sub.add_parser("full", help="Full threat hunt report")
    args = parser.parse_args()

    if args.command == "parse":
        result = parse_evtx_events(args.evtx)
    elif args.command == "hunt":
        result = hunt_suspicious_services(args.evtx)
    elif args.command == "stats":
        results = hunt_suspicious_services(args.evtx)
        result = generate_statistics(results)
    elif args.command == "full" or args.command is None:
        result = full_hunt(args.evtx)
    else:
        parser.print_help()
        return
    print(json.dumps(result, indent=2, default=str))


if __name__ == "__main__":
    main()
Keep exploring