﻿---
title: Potential PowerShell Obfuscation via Concatenated Dynamic Command Invocation
description: Detects PowerShell scripts that builds commands from concatenated string literals inside dynamic invocation constructs like &() or .(). Attackers use...
url: https://www.elastic.co/docs/reference/security/prebuilt-rules/rules/windows/defense_evasion_posh_obfuscation_concat_dynamic
products:
  - Elastic Security
---

# 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
- Noise: Low
- Performance: Normal
- Profile: Recommended
- Threat: Script-Based Execution
- Rule Type: ES|QL
- Platform: Windows

**Version**: 12
**Rule authors**:
- Elastic

**Rule license**: Elastic License v2

## 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](https://ela.st/powershell-logging-setup)

## Investigation guide


## Triage and analysis


### Investigating Potential PowerShell Obfuscation via Concatenated Dynamic Command Invocation


#### Possible investigation steps

- What did the alert preserve about the concatenated dynamic invocation?
  - Focus: `Esql.script_block_pattern_count`, `powershell.file.script_block_text`, and `powershell.file.script_block_id`.
  - Hint: use full-alert `Esql.script_block_tmp` only when you need the match-local slice.
  - Implication: escalate faster when multiple call-operator or dot-sourced matches sit near download, reflection, credential, persistence, or execution logic; lower suspicion only when one short match resolves to a transparent helper and source recovery supports the same recognized workflow.
- Can you reconstruct the full source 4104 script block before interpreting context?
  - Why: PowerShell can split large script blocks, and this ES|QL alert keeps summary fields that do not replace source-event recovery.
  - Focus: query PowerShell Operational source events with `host.id`, `powershell.file.script_block_id`, `powershell.sequence`, and `powershell.total`; order fragments and record source `process.pid` when recovered. $investigate_2
  - Implication: incomplete fragments are unresolved, not benign; escalation is stronger when reconstruction exposes hidden stages, fileless delivery, or missing execution context.
- What command or script does the concatenation resolve to, and does the operator expand impact?
  - Focus: reconstructed `powershell.file.script_block_text`, surrounding variable assignments, and call-operator versus dot-sourcing use.
  - Implication: escalate when the resolved token hides invocation or LOLBin logic that the surrounding code then executes; lower suspicion when reconstruction leaves one readable helper inside a recognized module or compatibility wrapper.
- Does the source event show a file-backed or fileless origin that fits this user and host?
  - Focus: recovered `file.path`, `user.id`, source-event `user.name`, source-event `user.domain`, and `host.id`.
  - Implication: escalate when the script is fileless or sourced from temp, downloads, profiles, shares, or another user-writable path under an unexpected identity; lower suspicion when the file path and user-host pairing match the same recognized admin module or compatibility workflow.
  - Hint: absent `file.path` after source recovery means interactive, pasted, or memory-only activity; require stronger corroboration before closure.
- Can you recover the PowerShell process launch chain?
  - Focus: source `process.pid` plus same-host process-start telemetry for recovered `process.entity_id`, `process.command_line`, and `process.parent.executable`.
  - Hint: if endpoint process telemetry is unavailable, keep later pivots bounded to `host.id` plus `user.id` or `user.name` in the alert window rather than assuming process scope. $investigate_3
  - Implication: escalate when PowerShell is launched by Office, a browser, an archive extractor, a LOLBin, an unexpected service, or a remote session; lower suspicion when the launch chain matches the same recognized management tool or scheduled task already supported by source evidence.
- Does the reconstructed script show layered obfuscation or payload-delivery logic beyond concatenation?
  - Focus: `powershell.file.script_block_entropy_bits`, `powershell.file.script_block_surprisal_stdev`, `powershell.file.script_block_length`, and reconstructed `powershell.file.script_block_text`; compare `powershell.file.script_block_length` against `Esql.script_block_pattern_count` to detect dead-code inflation around few match sites.
  - Implication: escalate when concatenation sits beside encoding, reflection, decoder routines, download strings, hidden payload material, dead-code padding, or Get-Command wildcard resolution.
- Did the recovered process or host-window activity retrieve, stage, or execute follow-on content?
  - Focus: child starts from recovered `process.entity_id`, same-PID 4104 blocks, and file, DNS, or connection side effects: `file.path`, `dns.question.name`, and `destination.ip`.
  - Hint: missing file, DNS, or network telemetry is unresolved, not benign; if `process.entity_id` was not recovered, scope only by `host.id` plus `user.id` or `user.name` in the alert window. $investigate_4 $investigate_5 $investigate_6
  - Implication: escalate when the same process chain spawns shells, writes scripts or binaries, or reaches rare external destinations.
- If local findings remain suspicious or unresolved, does related alert history change scope?
  - Focus: related alerts for the same `user.id` in the last 48 hours, prioritizing repeated obfuscated PowerShell, the same resolved token, or script path. $investigate_0
  - Hint: if the user view is sparse or shared, pivot to the same `host.id` in the last 48 hours. $investigate_1
  - Implication: broaden response when repeated obfuscation, AMSI tampering, encoded commands, download, credential-access, or persistence alerts cluster on the same user or host; keep scope local when the alert is isolated and local evidence resolves to one recognized workflow.
- Escalate on intentionally hidden PowerShell execution across match details, reconstruction, origin, launch chain, layered obfuscation, or follow-on activity; close only when recovered script, resolved token, origin, user-host context, launch chain, and side-effect telemetry align with one recognized workflow; preserve artifacts and escalate when reconstruction, process recovery, or file/network visibility stays incomplete.


### False positive analysis

- Internal compatibility wrappers, module loaders, code-protected vendor scripts, or administrative scripts may concatenate or dot-source helper names. Confirm recovered `powershell.file.script_block_text`, resolved token, `file.path` or stable helper path, recovered parent executable, dot-sourced location, and `user.id` plus `host.id` all align with one recognized workflow, with child process, file, DNS, and network effects contained to it. If external records are unavailable, require the same `file.path` or helper path, resolved token, parent executable, and user-host pairing to recur across prior alerts from this rule.
- Before creating an exception, anchor it on stable `file.path`, resolved token, recovered parent executable, and relevant `host.id` or `user.id` scope. Avoid exceptions on `Esql.script_block_pattern_count`, `Esql.script_block_tmp`, `user.name`, or `powershell.file.script_block_text` alone.


### Response and remediation

- If confirmed benign, reverse any temporary containment and document the evidence that proved one recognized workflow: resolved token, recovered `file.path`, launch chain, and `user.id` plus `host.id` scope. Create an exception only after the same pattern recurs consistently.
- If suspicious but unconfirmed, preserve the alert, reconstructed script fragments, recovered process identifiers, launch chain, staged file paths, DNS names, destination IPs, and case timeline before containment or cleanup.
- Apply reversible containment first: heightened monitoring, temporary outbound restrictions, or PowerShell restrictions on the affected `host.id`. Escalate to host isolation only when launch-chain or follow-on evidence indicates likely payload execution, lateral movement, or active command-and-control.
- If confirmed malicious, isolate the endpoint or contain the account based on the identity, launch-chain, file, and network evidence. Before suspending or terminating PowerShell, record the recovered process entity ID, command line, parent chain, resolved token, reconstructed script fragments, and staged file or network indicators.
- Review related hosts and users for the same resolved token, stable file path, parent executable, and destination indicators before removing artifacts so scoping completes before evidence is destroyed.
- Remove only the unauthorized scripts, dropped payloads, and persistence artifacts identified during the investigation, then remediate the delivery path or administrative-control gap that allowed obfuscated PowerShell execution.
- Post-incident hardening: retain Script Block Logging and endpoint process/file/network telemetry, restrict PowerShell where it is not required, and document the resolved token, script path, launch chain, and side-effect pattern that distinguished benign workflow from abuse.


## Rule Query

```esql
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,
    process.pid,
    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
- Tactic:
  - Name: Defense Evasion
  - Id: TA0005
  - Reference URL: [[https://attack.mitre.org/tactics/TA0005/](https://attack.mitre.org/tactics/TA0005/)](https://attack.mitre.org/tactics/TA0005/)
- Technique:
  - Name: Obfuscated Files or Information
  - Id: T1027
  - Reference URL: [[https://attack.mitre.org/techniques/T1027/](https://attack.mitre.org/techniques/T1027/)](https://attack.mitre.org/techniques/T1027/)
- Sub Technique:
  - Name: Command Obfuscation
  - Id: T1027.010
  - Reference URL: [[https://attack.mitre.org/techniques/T1027/010/](https://attack.mitre.org/techniques/T1027/010/)](https://attack.mitre.org/techniques/T1027/010/)
- Technique:
  - Name: Deobfuscate/Decode Files or Information
  - Id: T1140
  - Reference URL: [[https://attack.mitre.org/techniques/T1140/](https://attack.mitre.org/techniques/T1140/)](https://attack.mitre.org/techniques/T1140/)

**Framework:** MITRE ATT&CK
- Tactic:
  - Name: Execution
  - Id: TA0002
  - Reference URL: [[https://attack.mitre.org/tactics/TA0002/](https://attack.mitre.org/tactics/TA0002/)](https://attack.mitre.org/tactics/TA0002/)
- Technique:
  - Name: Command and Scripting Interpreter
  - Id: T1059
  - Reference URL: [[https://attack.mitre.org/techniques/T1059/](https://attack.mitre.org/techniques/T1059/)](https://attack.mitre.org/techniques/T1059/)
- Sub Technique:
  - Name: PowerShell
  - Id: T1059.001
  - Reference URL: [[https://attack.mitre.org/techniques/T1059/001/](https://attack.mitre.org/techniques/T1059/001/)](https://attack.mitre.org/techniques/T1059/001/)