Loading

Potential PowerShell Obfuscation via Special Character Overuse

Detects PowerShell scripts dominated by whitespace and special characters with low symbol diversity, a profile often produced by formatting or encoding obfuscation. Attackers use symbol-heavy encoding or formatting (for example, SecureString-style blobs or character-level transforms) to hide payloads and evade static analysis and AMSI.

Rule type: esql
Rule indices:

Rule Severity: medium
Risk Score: 47
Runs every:
Searches indices from: now-9m
Maximum alerts per execution: 100
References:

Tags:

  • Domain: Endpoint
  • OS: Windows
  • Use Case: Threat Detection
  • Tactic: Defense Evasion
  • Data Source: PowerShell Logs
  • Resources: Investigation Guide

Version: 8
Rule authors:

  • Elastic

Rule license: Elastic License v2

PowerShell Script Block Logging must be enabled to generate the events used by this rule (e.g., 4104). Setup instructions: https://ela.st/powershell-logging-setup

This rule uses the following fields that require the Windows Integration v3.3.0 and up: powershell.file.script_block_unique_symbols.

Disclaimer: This guide was created by humans with the assistance of generative AI. While its contents have been manually curated to include the most valuable information, always validate assumptions and adjust procedures to match your internal runbooks and incident triage and response policies.

This rule flags PowerShell script block content that is unusually long and dominated by whitespace and a narrow set of special characters. This profile is often associated with formatting or encoding obfuscation where payload logic is transformed into symbol-heavy strings and reconstructed at runtime. Use the steps below to validate execution context, reconstruct full content, determine likely intent, and scope related activity.

  • user.name, user.domain, user.id: Account execution context for correlation, prioritization, and scoping.
  • host.name, host.id: Host execution context for correlation, prioritization, and scoping.
  • file.path, file.directory, file.name: File-origin context when the script block is sourced from an on-disk file.
  • powershell.file.script_block_text: Script block content that matched the detection logic.
  • powershell.file.script_block_id, powershell.sequence, powershell.total: Script block metadata to pivot to other fragments or reconstruct full script content when split across multiple events.
  • Esql.script_block_tmp: Transformed script block where detection patterns replace original content with a marker to support scoring/counting and quickly spot match locations.
  • Esql.script_block_ratio: Proportion of the script block's characters that match the alert's target character set, divided by total script length (0-1).
  • Esql.script_block_pattern_count: Count of matches for the detection pattern(s) observed in the script block content.
  • powershell.file.script_block_entropy_bits: Shannon entropy of the script block. Higher values may indicate obfuscation.
  • powershell.file.script_block_surprisal_stdev: Standard deviation of surprisal across the script block. Low values indicate uniform randomness. High values indicate mixed patterns and variability.
  • powershell.file.script_block_unique_symbols: Count of distinct characters present in the script block.
  • powershell.file.script_block_length: Script block length (size) context.
  • Review alert context and scope:

    • Use @timestamp to identify when the activity occurred and to bound the timeline for correlation.
    • Review host.name and host.id to understand which endpoint produced the script block.
    • Review user.name, user.domain, and user.id to determine whether the account is expected to run PowerShell on this host.
    • If multiple alerts are present, group by host.id and user.id to identify concentrated or repeat activity.
  • Analyze the script block content for obfuscation and intent:

    • Inspect powershell.file.script_block_text to understand what is being executed. Obfuscation commonly presents as large blocks of escaped characters, excessive punctuation, and character-level reassembly.
    • Use Esql.script_block_tmp to quickly locate symbol-dense regions, then interpret the corresponding content in powershell.file.script_block_text.
    • Use Esql.script_block_ratio, powershell.file.script_block_unique_symbols, powershell.file.script_block_entropy_bits, and powershell.file.script_block_surprisal_stdev to gauge how atypical the content is compared to known-good scripts in your environment.
    • Identify deobfuscation and runtime execution patterns such as repeated string replacement, concatenation, [char] casting, -join, formatting operators, reflection, and dynamic invocation (for example, Invoke-Expression or executing decoded strings).
    • Capture any embedded indicators from powershell.file.script_block_text, including URLs, hostnames, IP addresses, file paths, registry paths, or scheduled task/service names.
  • Reconstruct full script content when logged in chunks:

    • If powershell.total indicates multiple fragments, pivot on powershell.file.script_block_id and reassemble the script in powershell.sequence order.
    • Confirm completeness by comparing observed fragments to powershell.total. Missing segments can hide key decode or execution stages.
  • Validate script origin and expected usage:

    • Review file.path, file.directory, and file.name (when present) to determine whether the script originated from disk, a module path, or an unusual location.
    • If the script is file-backed, assess whether the file location and naming are consistent with approved administration and automation practices for the host and user.
  • Scope for related PowerShell activity:

    • Pivot on powershell.file.script_block_hash (when available) to identify repeated executions of the same content across hosts and users.
    • Review additional script blocks on the same host.id and user.id around the alert time for staging behavior (variable setup, decoding routines, or creation of additional script blocks).
    • Use stable substrings from powershell.file.script_block_text (unique function names or strings) to find related executions that may not match this specific obfuscation profile.
  • Correlate with adjacent telemetry to confirm execution chain and impact (if available):

    • Use host.id, user.id, and @timestamp to pivot into process telemetry and determine which process initiated PowerShell and whether the parent process is expected.
    • Review activity on the same host around the alert time for signs of follow-on behavior such as outbound connections, file creation/modification, registry changes, or persistence mechanisms consistent with the recovered script logic.
  • Legitimate automation can embed large protected or serialized values (for example, encrypted configuration blobs or SecureString exports) that appear symbol-heavy.
  • Deployment and configuration tooling may generate templated PowerShell with extensive escaping or large here-strings, especially when embedding JSON/XML or code as data.
  • Authorized security testing may use obfuscation techniques that resemble this behavior.
  • To validate a benign source, confirm the script's provenance and repeatability:
    • Check whether file.path (when present) and powershell.file.script_block_hash consistently map to an approved script, owner, and expected execution pattern.
    • Compare the alerting user.id and host.id against known automation accounts and managed endpoints; unexpected combinations warrant escalation.
  • If malicious or suspicious activity is confirmed:

    • Contain the affected host according to your incident response procedures to prevent additional execution and lateral movement.
    • Preserve evidence for triage and forensics, including powershell.file.script_block_text (and any reconstructed content), powershell.file.script_block_id, powershell.sequence, powershell.total, powershell.file.script_block_hash (if available), file.path (if present), and the execution context (host.name, host.id, user.name, user.domain, user.id, agent.id, @timestamp).
    • Scope the activity by searching for the same powershell.file.script_block_hash and any extracted indicators across the environment.
    • Identify and remediate follow-on actions associated with the script (downloaded payloads, dropped files, persistence changes, or credential access). Apply blocking controls for confirmed indicators where feasible.
    • If the script content indicates credential material handling or unauthorized automation, rotate affected credentials and review account activity for misuse.
  • If the activity is determined to be benign:

    • Document the script owner, purpose, and expected execution context (hosts, users, and schedule), using file.path and powershell.file.script_block_hash (when available) as stable identifiers.
    • Monitor for drift, such as execution by different users/hosts, unexpected file paths, or material changes in the script block content.
from logs-windows.powershell_operational* metadata _id, _version, _index
| where event.code == "4104"

// filter for scripts with low unique symbol counts, which can indicate special character obfuscation
| where powershell.file.script_block_unique_symbols < 50

// replace repeated spaces used for formatting after a new line with a single space to reduce FPs
| eval Esql.script_block_tmp = replace(powershell.file.script_block_text, """\n\s+""", "\n ")

// Look for scripts with more than 1000 chars
| eval Esql.script_block_length = length(Esql.script_block_tmp)
| where Esql.script_block_length > 1000

// replace the patterns we are looking for with the 🔥 emoji to enable counting them
// The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1
| eval Esql.script_block_tmp = replace(
    Esql.script_block_tmp,
    """[\s\$\{\}\+\@\=\(\)\^\\\"~\[\]\?\./%#\`\'\;\-\!\*]""",
    "🔥"
)

// count how many patterns were detected by calculating the number of 🔥 characters inserted
| eval Esql.script_block_count = Esql.script_block_length - length(replace(Esql.script_block_tmp, "🔥", ""))

// Calculate the ratio of special characters to total length
| eval Esql.script_block_ratio = Esql.script_block_count::double / Esql.script_block_length::double

// keep the fields relevant to the query, although this is not needed as the alert is populated using _id
| keep
    Esql.script_block_count,
    Esql.script_block_length,
    Esql.script_block_ratio,
    Esql.script_block_tmp,
    powershell.file.*,
    file.path,
    powershell.sequence,
    powershell.total,
    _id,
    _version,
    _index,
    host.name,
    host.id,
    agent.id,
    user.id

// Filter for scripts with high whitespace and special character ratio
| where Esql.script_block_ratio >= 0.75
		

Framework: MITRE ATT&CK

Framework: MITRE ATT&CK