Loading

Potential PowerShell Obfuscation via Concatenated Dynamic Command Invocation

Detects PowerShell scripts that builds commands from concatenated string literals inside dynamic invocation constructs like &() or .(). Attackers use concatenated dynamic invocation to obscure execution intent, bypass keyword-based detections, and evade AMSI.

Rule type: esql
Rule indices:

Rule Severity: high
Risk Score: 73
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: 7
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 rule identifies PowerShell script block content where a command is built from concatenated string literals and executed through dynamic invocation using the call operator (&) or dot invocation (.). This technique can hide the true command name (for example, splitting cmdlet, function, alias, or script names into fragments) and is often paired with additional obfuscation to hinder quick 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_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.
  • Establish scope and execution context:

    • Review host.name and host.id to understand where the script ran, and identify whether the host is an admin workstation, server, or user endpoint.
    • Review user.name, user.domain, and user.id to understand who initiated the activity and whether PowerShell use is expected for that identity.
    • Use the alert timestamp to bound the activity window for correlation and scoping.
  • Reconstruct the dynamically invoked command(s):

    • Review powershell.file.script_block_text and use Esql.script_block_tmp to quickly locate the dynamic invocation expression(s) inside the script block.
    • Identify each invocation using &(...) or .(...) where multiple quoted strings are joined with +.
    • Concatenate the quoted string fragments in their observed order to derive the effective command/function/script name being invoked.
    • Use Esql.script_block_pattern_count to prioritize review; multiple dynamic concatenation invocations in the same script block generally indicate stronger intent to obscure execution.
  • Determine whether the operator changes execution semantics:

    • For &(...) (call operator), focus on the command being executed and any arguments passed immediately before/after the invocation.
    • For .(...) (dot invocation), assess whether the script is intended to run in the current scope (for example, to define or modify functions/variables) and whether that scope change is expected for the host and user context.
  • Reassemble full script content when fragmented:

    • Pivot on powershell.file.script_block_id to locate other fragments of the same script block.
    • If powershell.total indicates the content is split across multiple events, use powershell.sequence and powershell.total to reconstruct the full script block in order before making a determination.
  • Identify script origin and persistence opportunities:

    • If file.path, file.directory, or file.name are present, determine whether the script block is associated with an on-disk script and whether its location aligns with approved administrative tooling or known automation paths.
    • Treat unusual user-writable or temporary locations as higher risk, especially when paired with high Esql.script_block_pattern_count.
  • Evaluate obfuscation characteristics and intent:

    • Use powershell.file.script_block_entropy_bits, powershell.file.script_block_surprisal_stdev, powershell.file.script_block_unique_symbols, and powershell.file.script_block_length to assess how atypical the content is compared to known-good scripts in your environment.
    • Review surrounding logic in powershell.file.script_block_text for additional obfuscation patterns (for example, layered string operations, indirect invocation, or hidden payload material) that may not be captured by this specific match.
  • Correlate with adjacent endpoint activity (if available):

    • Pivot using host.id and the alert time window to identify the PowerShell host process, its parent process, and any child processes that indicate follow-on execution.
    • Review network, file, and registry activity on host.id around the same time for signs of payload retrieval, on-disk staging, persistence, or system configuration changes.
    • Review authentication activity associated with user.id around the same time window for anomalous logons, new session sources, or unusual access patterns.
    • Pivot on user.id to identify similar activity across other hosts, which may indicate shared automation, credential reuse, or lateral movement.
  • Capture and operationalize investigation artifacts:

    • Document the reconstructed command strings, notable script fragments, and any referenced file locations (file.path) for escalation and threat hunting.
    • Use those artifacts to search for additional occurrences across the environment, focusing on the same user.id, host.id, and similar powershell.file.script_block_text patterns.
  • Administrative scripts, modules, or internal frameworks that dynamically assemble short command names (cmdlets, functions, aliases) via string concatenation before invoking them for indirection or compatibility.
  • Legitimate automation that uses dot invocation to load or execute helper logic in the current scope, including scripts that intentionally reduce readability for code protection.
  • If the activity is confirmed or strongly suspected to be malicious or unauthorized:

    • Contain the affected host identified by host.id to prevent further execution and potential lateral movement.
    • Preserve evidence, including the full reconstructed powershell.file.script_block_text (using powershell.sequence/powershell.total if needed) and any associated on-disk script referenced by file.path.
  • If an on-disk script is involved (file.path present):

    • Acquire the referenced script file for analysis and validate its provenance.
    • Remove or quarantine the script if it is unauthorized, and assess for additional copies using the same file.name or file.path patterns across hosts.
  • If account misuse is suspected:

    • Scope recent activity for the implicated user.id across hosts, prioritize investigation for privileged accounts, and reset credentials per policy.
    • Review and reduce unnecessary privileges associated with the account, especially if PowerShell access is not required.
  • Eradication and recovery:

    • Identify and remediate follow-on artifacts discovered during scoping (for example, dropped scripts/binaries or persistence mechanisms) using established response procedures.
    • Increase monitoring for recurrence by hunting for similar dynamic concatenation patterns (high Esql.script_block_pattern_count) on the same host.id and user.id.
  • Post-incident hardening:

    • Ensure PowerShell Script Block Logging coverage is consistently enabled and centrally collected for systems where PowerShell use is permitted.
    • Limit PowerShell use to approved users and hosts, and review controls that reduce the impact of dynamic invocation and obfuscation in your environment.
from logs-windows.powershell_operational* metadata _id, _version, _index
| where event.code == "4104" and powershell.file.script_block_text like "*+*"

// 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,
    """[.&]\(\s*(['"][A-Za-z0-9.-]+['"]\s*\+\s*)+['"][A-Za-z0-9.-]+['"]\s*\)""",
    "🔥"
)

// 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_tmp,
    powershell.file.*,
    file.path,
    powershell.sequence,
    powershell.total,
    _id,
    _version,
    _index,
    host.name,
    host.id,
    agent.id,
    user.id

// Filter for scripts that match the pattern at least once
| where Esql.script_block_pattern_count >= 1
		

Framework: MITRE ATT&CK

Framework: MITRE ATT&CK