Loading

Potential PowerShell Obfuscation via String Reordering

Detects PowerShell scripts that uses format placeholders like "{0}{1}" with the -f operator or ::Format to reorder strings at runtime. Attackers use format-based reconstruction to hide commands or payload strings 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: 10
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

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 alert indicates a PowerShell script block used indexed format placeholders (for example, "{0}{1}") with runtime formatting (for example, the -f operator or ::Format) to reorder and reconstruct strings. This technique can hide meaningful strings (commands, URLs, artifact names) from straightforward text inspection and can be used as part of staged execution.

Because the detection requires repeated occurrences of these patterns in a larger script block, triage should focus on (1) execution context (host and user), (2) script origin (file-backed vs. in-memory/interactive), and (3) what strings are being reconstructed and what actions they enable.

  • 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_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.
  • Confirm the execution context and initial scope:

    • Identify the affected endpoint using host.name and host.id. Check for other security alerts or suspicious activity on the same host around the alert time.
    • Identify the account context using user.name, user.domain, and user.id. Prioritize alerts where the user is unexpected for the host role or where the user does not typically run PowerShell.
    • If file.path / file.directory / file.name are present, capture the script location and assess whether the path and filename are expected for that host and user. If file fields are missing, treat the activity as potentially interactive or in-memory and increase emphasis on correlated telemetry.
  • Review the script block content and determine what is being reconstructed:

    • Start with Esql.script_block_tmp to quickly locate where formatting patterns occur, then use powershell.file.script_block_text as the authoritative source for analysis and evidence.
    • Identify how formatting is used:
      • Placeholder-only or placeholder-heavy format strings that primarily consist of {n} tokens.
      • Out-of-order placeholder indexes (for example, {3}{0}{2}{1}) and repeated reordering blocks.
      • Multiple reconstruction stages where formatted output is subsequently reformatted or concatenated.
    • Reconstruct key strings by mapping placeholder indexes to the arguments/fragments used in each formatting operation. Record any reconstructed strings that indicate follow-on behavior (remote addresses, filenames, persistence identifiers, or execution flow).
  • Use the available scoring and statistics fields to guide prioritization:

    • Review Esql.script_block_pattern_count to understand how heavily the script relies on formatting-based reconstruction. Higher counts generally increase suspicion.
    • Review powershell.file.script_block_entropy_bits, powershell.file.script_block_unique_symbols, powershell.file.script_block_surprisal_stdev, and powershell.file.script_block_length to differentiate structured, readable scripts from highly variable or packed content.
    • Treat these values as supporting signals; base the decision primarily on reconstructed strings and correlated activity.
  • Reconstruct full script content when split across multiple events:

    • Pivot on powershell.file.script_block_id to gather all fragments for the same script block.
    • Order fragments using powershell.sequence and confirm completeness using powershell.total before drawing conclusions.
  • Correlate and validate impact using adjacent telemetry available in your environment:

    • Review other PowerShell script blocks from the same host.id and user.id to identify staging, deobfuscation, or follow-on execution.
    • Correlate with endpoint telemetry for the same host and timeframe to understand how PowerShell was started and what occurred next (process ancestry, network activity, file/registry changes, and authentication activity). Use reconstructed strings to focus this correlation.
  • Expand the hunt to assess prevalence:

    • Search for the same or similar content in powershell.file.script_block_text (shared fragments, repeated placeholder patterns) across other hosts.
    • Use Esql.script_block_pattern_count and the script block statistics fields to identify other high-similarity or high-complexity scripts that may represent the same technique.
  • Legitimate automation can use indexed placeholders to build dynamic output, reports, or templated configuration content. Benign usage is more likely when the resulting strings are human-readable and the script has an expected on-disk origin (file.path / file.name) and consistent execution over time.
  • Some internally developed frameworks generate PowerShell dynamically and may include repeated formatting patterns. Validate ownership of the script source and whether execution by the identified user.id on the identified host.id is expected.
  • Localization or templating logic may use indexed placeholders. This is typically associated with readable templates and stable execution patterns rather than multi-stage reconstruction of operational strings.
  • If malicious or suspicious activity is confirmed:

    • Contain the affected endpoint identified by host.name / host.id to prevent further execution and limit lateral movement.
    • Preserve evidence, including powershell.file.script_block_text, powershell.file.script_block_id, powershell.sequence, powershell.total, and the alert enrichment fields (Esql.script_block_tmp, Esql.script_block_pattern_count, and script block statistics).
    • Use reconstructed strings to drive scoping and impact assessment (look for related activity on the same host, and search for the same indicators across other hosts and users).
  • Eradication and recovery:

    • Identify the execution mechanism and remove it (for example, an unexpected script file, a startup trigger, or other persistence identified during correlation).
    • Remove or quarantine related artifacts discovered during analysis and validate that similar activity is not occurring on other endpoints.
  • If the activity is determined to be benign:

    • Document the expected script source (file.path / file.name), the responsible team, and the expected execution context (host.id, user.id) to support faster triage of future alerts.
    • Monitor for deviations from the established baseline (new hosts, new users, or materially different reconstructed strings).
from logs-windows.powershell_operational* metadata _id, _version, _index
| where event.code == "4104" and powershell.file.script_block_text like "*{0}*"

// Filter out smaller scripts that are unlikely to implement obfuscation using the patterns we are looking for
| eval Esql.script_block_length = length(powershell.file.script_block_text)
| where Esql.script_block_length > 500

// 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(
    powershell.file.script_block_text,
    """((\{\d+\}){2,}["']\s?-f|::Format[^\{]+(\{\d+\}){2,})""",
    "🔥"
)

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

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

// Filter for scripts that match the pattern at least five times
| where Esql.script_block_pattern_count >= 5

// Exclude Noisy Patterns

// Icinga Framework
| where not file.directory == "C:\\Program Files\\WindowsPowerShell\\Modules\\icinga-powershell-framework\\cache"
  // ESQL requires this condition, otherwise it only returns matches where file.directory exists.
  or file.directory IS NULL

| where not (powershell.file.script_block_text LIKE "*GitBranchStatus*" AND
    powershell.file.script_block_text LIKE "*$s.BranchBehindStatusSymbol.Text*")
| where not
    // https://wtfbins.wtf/17
    (
        (powershell.file.script_block_text like "*sentinelbreakpoints*" or
         powershell.file.script_block_text like "*:::::\\\\windows\\\\sentinel*")
        and
        (powershell.file.script_block_text like "*$local:Bypassed*" or
         powershell.file.script_block_text like "*origPSExecutionPolicyPreference*")
    )
		

Framework: MITRE ATT&CK

Framework: MITRE ATT&CK