# analyzing-powershell-empire-artifacts

Detect PowerShell Empire post-exploitation framework artifacts in Windows Script Block Logging (Event ID 4104) and Module Logging (Event ID 4103), including the default launcher string, Base64-encoded WebClient/FromBase64String payloads, known module invocations (Invoke-Mimikatz, Invoke-Kerberoast), and staging URL patterns. Use when hunting for or confirming Empire C2 activity in Windows event logs.

- **Kind:** skill
- **Source:** https://github.com/mukul975/Anthropic-Cybersecurity-Skills
- **Page:** https://forefy.com/skills/c1a0dcef-df34-411f-b227-f1907229ef2f
- **API (JSON + files):** https://forefy.com/api/asr/c1a0dcef-df34-411f-b227-f1907229ef2f

---

## LICENSE

```

```

## SKILL.md

---
name: analyzing-powershell-empire-artifacts
description: Detect PowerShell Empire post-exploitation framework artifacts in Windows Script Block Logging (Event ID 4104) and Module Logging (Event ID 4103), including the default launcher string, Base64-encoded WebClient/FromBase64String payloads, known module invocations (Invoke-Mimikatz, Invoke-Kerberoast), and staging URL patterns. Use when hunting for or confirming Empire C2 activity in Windows event logs.
domain: cybersecurity
subdomain: threat-hunting
tags:
- PowerShell-Empire
- threat-hunting
- Script-Block-Logging
- base64
- stager
- C2
- MITRE-ATT&CK
- T1059.001
- forensics
version: '1.0'
author: mahipal
license: Apache-2.0
d3fend_techniques:
- Executable Denylisting
- Execution Isolation
- File Metadata Consistency Validation
- Content Format Conversion
- File Content Analysis
nist_ai_rmf:
- GOVERN-1.1
- MEASURE-2.7
- MANAGE-3.1
nist_csf:
- DE.CM-01
- DE.AE-02
- DE.AE-07
- ID.RA-05
mitre_attack:
- T1059.001
- T1071.001
- T1003.001
- T1558.003
- T1027.010
---

# Analyzing PowerShell Empire Artifacts

## Overview

PowerShell Empire is a post-exploitation framework consisting of listeners, stagers, and agents. Its artifacts leave detectable traces in Windows event logs, particularly PowerShell Script Block Logging (Event ID 4104) and Module Logging (Event ID 4103). This skill analyzes event logs for Empire's default launcher string (`powershell -noP -sta -w 1 -enc`), Base64 encoded payloads containing `System.Net.WebClient` and `FromBase64String`, known module invocations (Invoke-Mimikatz, Invoke-Kerberoast, Invoke-TokenManipulation), and staging URL patterns.


## When to Use

- When investigating security incidents that require analyzing powershell empire artifacts
- 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 access to Windows Event Log or exported EVTX files
- PowerShell Script Block Logging (Event ID 4104) enabled via Group Policy
- Module Logging (Event ID 4103) enabled for comprehensive coverage

## Key Detection Patterns

1. **Default launcher** — `powershell -noP -sta -w 1 -enc` followed by Base64 blob
2. **Stager indicators** — `System.Net.WebClient`, `DownloadData`, `DownloadString`, `FromBase64String`
3. **Module signatures** — Invoke-Mimikatz, Invoke-Kerberoast, Invoke-TokenManipulation, Invoke-PSInject, Invoke-DCOM
4. **User agent strings** — default Empire user agents in HTTP listener configuration
5. **Staging URLs** — `/login/process.php`, `/admin/get.php` and similar default URI patterns

## Output

JSON report with matched IOCs, decoded Base64 payloads, timeline of suspicious events, MITRE ATT&CK technique mappings, and severity scores.

## references

```

```

## references/api-reference.md

# PowerShell Empire Artifact Detection Reference

## Enable Script Block Logging (GPO)

```
Computer Configuration > Administrative Templates > Windows Components >
Windows PowerShell > Turn on PowerShell Script Block Logging: Enabled
```

Registry: `HKLM\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging`
- `EnableScriptBlockLogging` = 1

## Enable Module Logging (GPO)

```
Computer Configuration > Administrative Templates > Windows Components >
Windows PowerShell > Turn on Module Logging: Enabled
Module Names: *
```

## Key Event IDs

| Event ID | Log | Description |
|----------|-----|-------------|
| 4104 | Microsoft-Windows-PowerShell/Operational | Script Block Logging — captures executed script text |
| 4103 | Microsoft-Windows-PowerShell/Operational | Module Logging — captures pipeline execution details |
| 4688 | Security | Process Creation — captures command line arguments |
| 800 | Windows PowerShell | Pipeline execution (legacy) |

## Default Empire Launcher Pattern

```
powershell -noP -sta -w 1 -enc <Base64-payload>
```

### Launcher Flags

| Flag | Meaning |
|------|---------|
| `-noP` | No profile — skips PowerShell profile scripts |
| `-sta` | Single-threaded apartment |
| `-w 1` | Window style hidden |
| `-enc` | Encoded command (Base64 UTF-16LE) |

## Empire Stager IOC Patterns

| Pattern | Context |
|---------|---------|
| `System.Net.WebClient` | Downloads stager payload from listener |
| `.DownloadString()` | Fetches PowerShell script from C2 |
| `.DownloadData()` | Fetches binary data from C2 |
| `[System.Convert]::FromBase64String` | Decodes embedded payload |
| `IEX()` / `Invoke-Expression` | Executes downloaded script |
| `New-Object System.Net.WebClient` | Creates web client for download |

## Empire Module Signatures

| Module | MITRE | Description |
|--------|-------|-------------|
| `Invoke-Mimikatz` | T1003.001 | Credential dumping via Mimikatz |
| `Invoke-Kerberoast` | T1558.003 | Service ticket requests for offline cracking |
| `Invoke-TokenManipulation` | T1134 | Access token manipulation |
| `Invoke-PSInject` | T1055.012 | Process hollowing injection |
| `Invoke-DCOM` | T1021.003 | Lateral movement via DCOM |
| `Invoke-SMBExec` | T1021.002 | SMB-based lateral movement |
| `Invoke-WMIExec` | T1047 | WMI-based execution |
| `Invoke-RunAs` | T1134.002 | Create process with alternate token |
| `Invoke-SessionGopher` | T1552.001 | Extract saved session credentials |
| `Install-SSP` | T1547.005 | Security Support Provider persistence |
| `New-GPOImmediateTask` | T1484.001 | GPO abuse for execution |

## Default Empire Staging URIs

```
/login/process.php
/admin/get.php
/admin/news.php
/news.php
/login/process.jsp
```

## Splunk Detection Query

```spl
index=wineventlog source="WinEventLog:Microsoft-Windows-PowerShell/Operational" EventCode=4104
| where match(ScriptBlockText, "(?i)system\.net\.webclient") AND match(ScriptBlockText, "(?i)frombase64string")
| stats count by Computer, UserID, ScriptBlockText
```

## Elastic KQL Detection

```
event.code: "4104" AND powershell.file.script_block_text: (*System.Net.WebClient* AND *FromBase64String*)
```

## MITRE ATT&CK Mapping

- **T1059.001** — Command and Scripting Interpreter: PowerShell
- **T1071.001** — Application Layer Protocol: Web Protocols
- **T1027** — Obfuscated Files or Information
- **T1105** — Ingress Tool Transfer

## scripts

```

```

## scripts/agent.py

```python
#!/usr/bin/env python3
"""Detect PowerShell Empire framework artifacts in Windows event logs."""

import argparse
import base64
import json
import re
import subprocess
import sys
from datetime import datetime, timezone


EMPIRE_LAUNCHER_PATTERN = re.compile(
    r"powershell\s+-noP\s+-sta\s+-w\s+1\s+-enc\s+", re.IGNORECASE
)

EMPIRE_STAGER_PATTERNS = [
    re.compile(r"System\.Net\.WebClient", re.IGNORECASE),
    re.compile(r"\.DownloadString\(", re.IGNORECASE),
    re.compile(r"\.DownloadData\(", re.IGNORECASE),
    re.compile(r"FromBase64String", re.IGNORECASE),
    re.compile(r"IEX\s*\(", re.IGNORECASE),
    re.compile(r"Invoke-Expression", re.IGNORECASE),
    re.compile(r"New-Object\s+System\.Net\.WebClient", re.IGNORECASE),
    re.compile(r"\[System\.Convert\]::FromBase64String", re.IGNORECASE),
]

EMPIRE_MODULE_SIGNATURES = [
    "Invoke-Mimikatz",
    "Invoke-Kerberoast",
    "Invoke-TokenManipulation",
    "Invoke-PSInject",
    "Invoke-DCOM",
    "Invoke-RunAs",
    "Invoke-PSRemoting",
    "Invoke-SessionGopher",
    "Invoke-ReflectivePEInjection",
    "Install-SSP",
    "New-GPOImmediateTask",
    "Get-Keystrokes",
    "Get-Screenshot",
    "Get-ClipboardContents",
    "Invoke-Portscan",
    "Invoke-SMBExec",
    "Invoke-WMIExec",
]

EMPIRE_DEFAULT_URIS = [
    "/login/process.php",
    "/admin/get.php",
    "/admin/news.php",
    "/news.php",
    "/login/process.jsp",
]

EMPIRE_DEFAULT_USER_AGENTS = [
    "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko",
    "Mozilla/5.0 (compatible, MSIE 11, Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko",
]


def query_event_log(event_id, log_name="Microsoft-Windows-PowerShell/Operational", max_events=1000):
    """Query Windows Event Log for specific event ID using wevtutil."""
    cmd = [
        "wevtutil", "qe", log_name,
        "/q:*[System[(EventID={})]]".format(event_id),
        "/c:{}".format(max_events),
        "/f:xml", "/rd:true"
    ]
    try:
        proc = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
        return proc.stdout
    except FileNotFoundError:
        return ""
    except subprocess.TimeoutExpired:
        return ""


def parse_script_block_events(xml_data):
    """Parse Event ID 4104 Script Block Logging events from XML."""
    events = []
    if not xml_data:
        return events

    blocks = re.split(r"<Event\s+xmlns=", xml_data)
    for block in blocks[1:]:
        time_match = re.search(r"<TimeCreated\s+SystemTime='([^']+)'", block)
        computer_match = re.search(r"<Computer>([^<]+)</Computer>", block)
        script_match = re.search(r"<Data Name='ScriptBlockText'>([^<]+)</Data>", block)
        sid_match = re.search(r"<Security\s+UserID='([^']+)'", block)

        if script_match:
            events.append({
                "timestamp": time_match.group(1) if time_match else "",
                "computer": computer_match.group(1) if computer_match else "",
                "user_sid": sid_match.group(1) if sid_match else "",
                "script_block": script_match.group(1),
                "event_id": 4104
            })
    return events


def decode_base64_payload(encoded_string):
    """Attempt to decode Base64 encoded PowerShell payload."""
    try:
        decoded_bytes = base64.b64decode(encoded_string)
        decoded = decoded_bytes.decode("utf-16-le", errors="replace")
        return decoded
    except Exception:
        try:
            decoded_bytes = base64.b64decode(encoded_string)
            return decoded_bytes.decode("utf-8", errors="replace")
        except Exception:
            return None


def analyze_script_block(script_text):
    """Analyze a script block for Empire indicators."""
    findings = []

    # Check for default launcher pattern
    if EMPIRE_LAUNCHER_PATTERN.search(script_text):
        findings.append({
            "indicator": "empire_default_launcher",
            "severity": "Critical",
            "description": "Default Empire launcher pattern detected: 'powershell -noP -sta -w 1 -enc'",
            "mitre": "T1059.001"
        })
        # Try to extract and decode the Base64 payload
        b64_match = re.search(r"-enc\s+([A-Za-z0-9+/=]+)", script_text)
        if b64_match:
            decoded = decode_base64_payload(b64_match.group(1))
            if decoded:
                findings[-1]["decoded_payload_preview"] = decoded[:500]

    # Check for stager patterns
    matched_stagers = []
    for pattern in EMPIRE_STAGER_PATTERNS:
        if pattern.search(script_text):
            matched_stagers.append(pattern.pattern)
    if len(matched_stagers) >= 2:
        findings.append({
            "indicator": "empire_stager_patterns",
            "severity": "High",
            "description": f"Multiple Empire stager patterns detected: {', '.join(matched_stagers[:5])}",
            "matched_count": len(matched_stagers),
            "mitre": "T1059.001"
        })

    # Check for known Empire module signatures
    for module in EMPIRE_MODULE_SIGNATURES:
        if module.lower() in script_text.lower():
            findings.append({
                "indicator": "empire_module",
                "severity": "Critical",
                "module_name": module,
                "description": f"Empire post-exploitation module detected: {module}",
                "mitre": "T1059.001"
            })

    # Check for Empire default URIs
    for uri in EMPIRE_DEFAULT_URIS:
        if uri in script_text:
            findings.append({
                "indicator": "empire_staging_uri",
                "severity": "High",
                "uri": uri,
                "description": f"Default Empire staging URI detected: {uri}",
                "mitre": "T1071.001"
            })

    # Check for Empire user agents
    for ua in EMPIRE_DEFAULT_USER_AGENTS:
        if ua in script_text:
            findings.append({
                "indicator": "empire_default_useragent",
                "severity": "Medium",
                "user_agent": ua,
                "description": "Default Empire HTTP listener user agent detected",
                "mitre": "T1071.001"
            })

    # Check for encoded command patterns
    b64_blocks = re.findall(r"[A-Za-z0-9+/]{100,}={0,2}", script_text)
    for b64 in b64_blocks[:5]:
        decoded = decode_base64_payload(b64)
        if decoded and any(p.search(decoded) for p in EMPIRE_STAGER_PATTERNS):
            findings.append({
                "indicator": "encoded_empire_payload",
                "severity": "Critical",
                "description": "Base64 encoded payload contains Empire stager patterns",
                "decoded_preview": decoded[:300],
                "mitre": "T1027"
            })

    return findings


def scan_event_logs(max_events=1000):
    """Scan PowerShell event logs for Empire artifacts."""
    results = {
        "scan_time": datetime.now(timezone.utc).isoformat(),
        "events_analyzed": 0,
        "suspicious_events": [],
        "summary": {
            "total_findings": 0,
            "critical": 0,
            "high": 0,
            "medium": 0
        }
    }

    # Query Event ID 4104 (Script Block Logging)
    xml_data = query_event_log(4104, "Microsoft-Windows-PowerShell/Operational", max_events)
    events = parse_script_block_events(xml_data)
    results["events_analyzed"] = len(events)

    for event in events:
        findings = analyze_script_block(event["script_block"])
        if findings:
            results["suspicious_events"].append({
                "timestamp": event["timestamp"],
                "computer": event["computer"],
                "user_sid": event["user_sid"],
                "findings": findings,
                "script_preview": event["script_block"][:200]
            })
            for f in findings:
                results["summary"]["total_findings"] += 1
                sev = f.get("severity", "").lower()
                if sev in results["summary"]:
                    results["summary"][sev] += 1

    # Also check Event ID 4103 (Module Logging)
    xml_4103 = query_event_log(4103, "Microsoft-Windows-PowerShell/Operational", max_events)
    events_4103 = parse_script_block_events(xml_4103)
    for event in events_4103:
        for module in EMPIRE_MODULE_SIGNATURES:
            if module.lower() in event.get("script_block", "").lower():
                results["suspicious_events"].append({
                    "timestamp": event["timestamp"],
                    "computer": event["computer"],
                    "event_id": 4103,
                    "findings": [{
                        "indicator": "empire_module_in_module_log",
                        "severity": "Critical",
                        "module_name": module,
                        "mitre": "T1059.001"
                    }]
                })
                results["summary"]["total_findings"] += 1
                results["summary"]["critical"] += 1

    return results


def analyze_script_file(filepath):
    """Analyze a PowerShell script file or exported log for Empire artifacts."""
    with open(filepath, "r", encoding="utf-8", errors="replace") as f:
        content = f.read()

    findings = analyze_script_block(content)
    return {
        "file": filepath,
        "scan_time": datetime.now(timezone.utc).isoformat(),
        "findings": findings,
        "finding_count": len(findings)
    }


def main():
    parser = argparse.ArgumentParser(
        description="Detect PowerShell Empire artifacts in event logs and scripts"
    )
    subparsers = parser.add_subparsers(dest="command", help="Analysis mode")

    scan_parser = subparsers.add_parser("scan-logs", help="Scan Windows event logs for Empire IOCs")
    scan_parser.add_argument("--max-events", type=int, default=1000, help="Max events to query (default: 1000)")

    file_parser = subparsers.add_parser("analyze-file", help="Analyze a PowerShell script or log file")
    file_parser.add_argument("file", help="Path to script or log file")

    decode_parser = subparsers.add_parser("decode", help="Decode Base64 encoded PowerShell payload")
    decode_parser.add_argument("payload", help="Base64 encoded string")

    args = parser.parse_args()

    if args.command == "scan-logs":
        result = scan_event_logs(args.max_events)
    elif args.command == "analyze-file":
        result = analyze_script_file(args.file)
    elif args.command == "decode":
        decoded = decode_base64_payload(args.payload)
        result = {
            "encoded": args.payload[:100] + "..." if len(args.payload) > 100 else args.payload,
            "decoded": decoded,
            "empire_indicators": analyze_script_block(decoded) if decoded else []
        }
    else:
        parser.print_help()
        sys.exit(0)

    print(json.dumps(result, indent=2, default=str))


if __name__ == "__main__":
    main()
```

