EXPLORE
← Back to Explore
elasticmediumTTP

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.

MITRE ATT&CK

defense-evasionexecution

Detection Query

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

Author

Elastic

Created

2025/04/16

Data Sources

PowerShell Logs

Tags

Domain: EndpointOS: WindowsUse Case: Threat DetectionTactic: Defense EvasionData Source: PowerShell LogsResources: Investigation Guide
Raw Content
[metadata]
creation_date = "2025/04/16"
integration = ["windows"]
maturity = "production"
updated_date = "2026/03/24"

[rule]
author = ["Elastic"]
description = """
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.
"""
from = "now-9m"
language = "esql"
license = "Elastic License v2"
name = "Potential PowerShell Obfuscation via Special Character Overuse"
note = """## Triage and analysis

> **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.

### Investigating Potential PowerShell Obfuscation via Special Character Overuse

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.

#### Key alert fields to review

- `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.

#### Possible investigation steps

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

### False positive analysis

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

### Response and remediation

- 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.
"""
risk_score = 47
rule_id = "6ddb6c33-00ce-4acd-832a-24b251512023"
setup = """## Setup

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`.
"""
severity = "medium"
tags = [
    "Domain: Endpoint",
    "OS: Windows",
    "Use Case: Threat Detection",
    "Tactic: Defense Evasion",
    "Data Source: PowerShell Logs",
    "Resources: Investigation Guide",
]
timestamp_override = "event.ingested"
type = "esql"

query = '''
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
'''


[[rule.threat]]
framework = "MITRE ATT&CK"

[[rule.threat.technique]]
id = "T1027"
name = "Obfuscated Files or Information"
reference = "https://attack.mitre.org/techniques/T1027/"

[[rule.threat.technique.subtechnique]]
id = "T1027.010"
name = "Command Obfuscation"
reference = "https://attack.mitre.org/techniques/T1027/010/"

[[rule.threat.technique]]
id = "T1140"
name = "Deobfuscate/Decode Files or Information"
reference = "https://attack.mitre.org/techniques/T1140/"

[rule.threat.tactic]
id = "TA0005"
name = "Defense Evasion"
reference = "https://attack.mitre.org/tactics/TA0005/"

[[rule.threat]]
framework = "MITRE ATT&CK"

[[rule.threat.technique]]
id = "T1059"
name = "Command and Scripting Interpreter"
reference = "https://attack.mitre.org/techniques/T1059/"

[[rule.threat.technique.subtechnique]]
id = "T1059.001"
name = "PowerShell"
reference = "https://attack.mitre.org/techniques/T1059/001/"

[rule.threat.tactic]
id = "TA0002"
name = "Execution"
reference = "https://attack.mitre.org/tactics/TA0002/"
[rule.investigation_fields]
field_names = [
    "@timestamp",
    "user.name",
    "user.id",
    "user.domain",
    "powershell.file.script_block_text",
    "powershell.file.script_block_id",
    "powershell.sequence",
    "powershell.total",
    "file.path",
    "file.directory",
    "file.name",
    "process.pid",
    "host.name",
    "host.id",
    "powershell.file.script_block_length"
]