threat hunting

Detecting Suspicious Powershell Execution

Detect suspicious PowerShell execution patterns including encoded commands, download cradles, AMSI bypass attempts, and constrained language mode evasion.

amsiexecutionmitre-attackpowershellproactive-detectiont1059threat-hunting
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

When to Use

  • When proactively hunting for indicators of detecting suspicious powershell execution 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
T1059.001 PowerShell
T1059.003 Windows Command Shell
T1562.001 Disable or Modify Tools

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: Base64 encoded PowerShell command launched by macro document
  2. Scenario 2: IEX download cradle fetching payload from C2 server
  3. Scenario 3: AMSI bypass via reflection patching before payload execution
  4. Scenario 4: PowerShell Empire agent communicating with C2

Output Format

Hunt ID: TH-DETECT-[DATE]-[SEQ]
Technique: T1059.001
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.md2.5 KB

API Reference: Suspicious PowerShell Execution Detection

Windows PowerShell Event Logs

Event IDs

Event ID Log Description
4104 PowerShell/Operational Script block logging
4103 PowerShell/Operational Module logging
800 PowerShell Pipeline execution details
400 PowerShell Engine lifecycle (start)
403 PowerShell Engine lifecycle (stop)

Script Block Logging Query

Get-WinEvent -FilterHashtable @{
    LogName = 'Microsoft-Windows-PowerShell/Operational'
    Id = 4104
} -MaxEvents 100

Event 4104 Properties

Index Field Description
0 MessageNumber Block sequence number
1 MessageTotal Total blocks in script
2 ScriptBlockText Actual script content
3 ScriptBlockId Unique script ID
4 Path Script file path

Suspicious PowerShell Patterns

Execution Policy Bypass

powershell -ExecutionPolicy Bypass -File script.ps1
powershell -ep bypass -nop -w hidden -enc <base64>

Common Obfuscation Techniques

Technique Example
Concatenation "Inv"+"oke-Ex"+"pression"
Variable substitution ${Invoke-Expression}
Encoded commands -enc SQBuAHYAbwBrAGUALQA...
Char array [char[]]@(73,69,88) -join ''

Sigma Detection Rules

Suspicious PowerShell Command Line

title: Suspicious PowerShell Invocation
logsource:
    product: windows
    category: process_creation
detection:
    selection:
        CommandLine|contains:
            - '-enc'
            - '-EncodedCommand'
            - 'FromBase64String'
            - 'DownloadString'
            - 'Invoke-Expression'
    condition: selection
level: high

AMSI (Antimalware Scan Interface)

AMSI Scan Functions

HRESULT AmsiScanBuffer(
    HAMSICONTEXT amsiContext,
    PVOID buffer,
    ULONG length,
    LPCWSTR contentName,
    HAMSISESSION amsiSession,
    AMSI_RESULT *result
);

AMSI Results

Value Meaning
0 Clean
1 Not Detected
16384 Blocked by admin
32768 Detected (malware)

Microsoft Defender ATP API

Advanced Hunting Query

POST https://api.security.microsoft.com/api/advancedqueries/run
Authorization: Bearer {token}
 
{
  "Query": "DeviceProcessEvents | where FileName == 'powershell.exe' | where ProcessCommandLine has_any('encodedcommand','downloadstring','invoke-expression') | project Timestamp, DeviceName, ProcessCommandLine | take 100"
}
standards.md1.6 KB

Standards and References - Detecting Suspicious Powershell Execution

MITRE ATT&CK Mappings

Technique Name Description
T1059.001 PowerShell See attack.mitre.org/techniques/T1059/001
T1059.003 Windows Command Shell See attack.mitre.org/techniques/T1059/003
T1562.001 Disable or Modify Tools See attack.mitre.org/techniques/T1562/001

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.9 KB

Detailed Hunting Workflow - Detecting Suspicious Powershell Execution

Phase 1: Data Collection and Querying

Splunk SPL Query

index=sysmon EventCode=1 Image="*\\powershell.exe"
| where match(CommandLine, "(?i)(-enc|-encodedcommand|-w hidden|-nop|iex|invoke-expression|downloadstring|webclient|bypass)")
| table _time Computer User CommandLine ParentImage

KQL Query (Microsoft Defender for Endpoint)

DeviceProcessEvents
| where FileName =~ "powershell.exe"
| where ProcessCommandLine has_any ("-enc","-encodedcommand","-w hidden","iex","downloadstring","bypass")
| project Timestamp, DeviceName, AccountName, ProcessCommandLine, InitiatingProcessFileName

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.py5.9 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Agent for detecting suspicious PowerShell execution patterns."""

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


SUSPICIOUS_CMDLETS = [
    "Invoke-Expression", "IEX", "Invoke-WebRequest", "Invoke-RestMethod",
    "Start-Process", "New-Object Net.WebClient", "DownloadString",
    "DownloadFile", "System.Reflection.Assembly", "FromBase64String",
    "Invoke-Mimikatz", "Invoke-Shellcode", "Invoke-DllInjection",
    "Invoke-ReflectivePEInjection", "Get-Keystrokes", "Get-GPPPassword",
    "Invoke-CredentialInjection", "Invoke-TokenManipulation",
    "Add-Exfiltration", "Get-TimedScreenshot",
]

OBFUSCATION_PATTERNS = [
    (r'\-[eE][nN][cC]\s', "Encoded command (-enc)"),
    (r'[Ff][Rr][Oo][Mm][Bb][Aa][Ss][Ee]64', "Base64 decoding"),
    (r'\$\{[^}]+\}', "Variable obfuscation ${...}"),
    (r"'[^']*'\s*\+\s*'[^']*'", "String concatenation obfuscation"),
    (r'\-[Ww]indow[Ss]tyle\s+[Hh]idden', "Hidden window execution"),
    (r'\-[Nn]o[Pp]rofile', "NoProfile flag"),
    (r'\-[Ee]xecution[Pp]olicy\s+[Bb]ypass', "Execution policy bypass"),
    (r'[Ss]et-[Mm]pPreference.*-[Dd]isable', "Defender bypass attempt"),
    (r'[Aa][Mm][Ss][Ii]', "AMSI reference"),
]


def parse_script_block_logs():
    """Parse PowerShell script block logging events (Event ID 4104)."""
    events = []
    if sys.platform != "win32":
        return events
    ps_cmd = (
        "Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-PowerShell/Operational';"
        "Id=4104} -MaxEvents 200 | Select-Object TimeCreated,"
        "@{N='ScriptBlock';E={$_.Properties[2].Value}},"
        "@{N='Path';E={$_.Properties[4].Value}} | ConvertTo-Json -Depth 3"
    )
    try:
        result = subprocess.check_output(
            ["powershell", "-NoProfile", "-Command", ps_cmd],
            text=True, errors="replace", timeout=30
        )
        data = json.loads(result) if result.strip() else []
        return data if isinstance(data, list) else [data]
    except (subprocess.SubprocessError, json.JSONDecodeError):
        return []


def analyze_script_content(script_text):
    """Analyze a PowerShell script for suspicious patterns."""
    findings = []
    if not script_text:
        return findings

    for cmdlet in SUSPICIOUS_CMDLETS:
        if cmdlet.lower() in script_text.lower():
            findings.append({"type": "suspicious_cmdlet", "cmdlet": cmdlet})

    for pattern, desc in OBFUSCATION_PATTERNS:
        if re.search(pattern, script_text):
            findings.append({"type": "obfuscation", "pattern": desc})

    b64_match = re.findall(r'[A-Za-z0-9+/]{40,}={0,2}', script_text)
    for b64 in b64_match[:3]:
        try:
            import base64
            decoded = base64.b64decode(b64).decode("utf-8", errors="replace")
            if any(c.lower() in decoded.lower() for c in SUSPICIOUS_CMDLETS[:10]):
                findings.append({"type": "encoded_payload", "preview": decoded[:100]})
        except Exception:
            pass

    return findings


def analyze_log_file(log_path):
    """Analyze a text file containing PowerShell commands."""
    findings = []
    try:
        with open(log_path, "r", errors="replace") as f:
            content = f.read()
        results = analyze_script_content(content)
        if results:
            findings.append({
                "file": log_path,
                "indicators": results,
                "indicator_count": len(results),
            })
    except FileNotFoundError:
        print(f"[!] File not found: {log_path}")
    return findings


def main():
    parser = argparse.ArgumentParser(
        description="Detect suspicious PowerShell execution patterns"
    )
    parser.add_argument("--event-logs", action="store_true",
                        help="Parse Windows PowerShell event logs")
    parser.add_argument("--script", help="Analyze a PowerShell script file")
    parser.add_argument("--log-dir", help="Directory of PS log files to scan")
    parser.add_argument("--output", "-o", help="Output JSON report")
    parser.add_argument("--verbose", "-v", action="store_true")
    args = parser.parse_args()

    print("[*] Suspicious PowerShell Execution Detection Agent")
    report = {"timestamp": datetime.now(timezone.utc).isoformat(), "findings": []}

    if args.event_logs:
        events = parse_script_block_logs()
        for evt in events:
            script = evt.get("ScriptBlock", "")
            indicators = analyze_script_content(script)
            if indicators:
                report["findings"].append({
                    "source": "event_log",
                    "time": evt.get("TimeCreated", ""),
                    "path": evt.get("Path", ""),
                    "indicators": indicators,
                    "preview": script[:200] if args.verbose else "",
                })
        print(f"[*] Analyzed {len(events)} script block events")

    if args.script:
        findings = analyze_log_file(args.script)
        report["findings"].extend(findings)

    if args.log_dir and os.path.isdir(args.log_dir):
        for root, _, files in os.walk(args.log_dir):
            for f in files:
                if f.lower().endswith((".ps1", ".psm1", ".psd1", ".log", ".txt")):
                    findings = analyze_log_file(os.path.join(root, f))
                    report["findings"].extend(findings)

    report["total_suspicious"] = len(report["findings"])
    report["risk_level"] = (
        "CRITICAL" if len(report["findings"]) >= 10
        else "HIGH" if len(report["findings"]) >= 5
        else "MEDIUM" if report["findings"]
        else "LOW"
    )
    print(f"[*] Suspicious findings: {len(report['findings'])}")

    if args.output:
        with open(args.output, "w") as f:
            json.dump(report, f, indent=2)
        print(f"[*] Report saved to {args.output}")
    else:
        print(json.dumps(report, indent=2))


if __name__ == "__main__":
    main()
process.py3.7 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Suspicious PowerShell Detection - Analyzes logs for T1059.001 indicators."""

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

DETECTION_PATTERNS = [
    r'-enc',
    r'-encodedcommand',
    r'-w hidden',
    r'-nop',
    r'iex',
    r'invoke-expression',
    r'downloadstring',
    r'webclient',
    r'bypass',
    r'Net\\.WebClient',
    r'bitstransfer',
    r'Start-BitsTransfer',
]

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_event(event):
    cmd = event.get("CommandLine", event.get("command_line", event.get("ProcessCommandLine", "")))
    content = event.get("Task_Content", event.get("Parameters", event.get("RawEventData", "")))
    search_text = f"{cmd} {content}"
    risk = 0
    indicators = []
    for pattern in DETECTION_PATTERNS:
        if re.search(pattern, search_text, re.IGNORECASE):
            risk += 25
            indicators.append(f"Pattern match: {pattern}")
    if not indicators:
        return None
    risk = min(risk, 100)
    return {
        "technique": "T1059.001",
        "command_line": cmd[:500] if cmd else content[:500],
        "hostname": event.get("Computer", event.get("DeviceName", event.get("hostname", "unknown"))),
        "user": event.get("User", event.get("AccountName", event.get("UserId", "unknown"))),
        "timestamp": event.get("_time", event.get("timestamp", event.get("UtcTime", event.get("Timestamp", "")))),
        "risk_score": risk,
        "risk_level": "CRITICAL" if risk >= 75 else "HIGH" if risk >= 50 else "MEDIUM" if risk >= 25 else "LOW",
        "indicators": indicators,
    }

def run_hunt(input_path, output_dir):
    print(f"[*] Suspicious PowerShell Hunt - {datetime.datetime.now().isoformat()}")
    events = parse_logs(input_path)
    findings = [f for f in (analyze_event(e) for e in events) if f]
    Path(output_dir).mkdir(parents=True, exist_ok=True)
    slug = "detecting_suspicious"
    with open(Path(output_dir) / f"{slug}_findings.json", "w", encoding="utf-8") as f:
        json.dump({"hunt_id": f"TH-{datetime.date.today()}", "total_events": len(events), "findings": findings}, f, indent=2)
    with open(Path(output_dir) / "hunt_report.md", "w", encoding="utf-8") as f:
        f.write(f"# Suspicious PowerShell Hunt Report\n\n")
        f.write(f"**Date**: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
        f.write(f"**Findings**: {len(findings)}\n\n")
        for finding in sorted(findings, key=lambda x: x["risk_score"], reverse=True)[:20]:
            f.write(f"### [{finding['risk_level']}] {finding['technique']}\n")
            f.write(f"- **Host**: {finding['hostname']}\n")
            f.write(f"- **Indicators**: {', '.join(finding['indicators'])}\n\n")
    print(f"[+] {len(findings)} findings written to {output_dir}")

def main():
    p = argparse.ArgumentParser(description="Suspicious PowerShell 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="./detecting_suspi_output")
    sp.add_parser("queries")
    args = p.parse_args()
    if args.cmd == "hunt": run_hunt(args.input, args.output)
    elif args.cmd == "queries":
        print("=== Detection Queries ===")
        print("See references/workflows.md for platform-specific queries")
    else: p.print_help()

if __name__ == "__main__": main()

Assets 1

template.mdtext/markdown · 2.6 KB
Keep exploring