Potential PowerShell Obfuscation via Concatenated Dynamic Command Invocation
editPotential PowerShell Obfuscation via Concatenated Dynamic Command Invocation
editDetects 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: None
Severity: high
Risk score: 73
Runs every: 5m
Searches indices from: now-9m (Date Math format, see also Additional look-back time)
Maximum alerts per execution: 100
References: None
Tags:
- Domain: Endpoint
- OS: Windows
- Use Case: Threat Detection
- Tactic: Defense Evasion
- Data Source: PowerShell Logs
- Resources: Investigation Guide
Version: 11
Rule authors:
- Elastic
Rule license: Elastic License v2
Investigation guide
editTriage 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, andpowershell.file.script_block_id. -
Hint: use full-alert
Esql.script_block_tmponly 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, andpowershell.total; order fragments and record sourceprocess.pidwhen recovered. !{investigate{"description":"","label":"Script block fragments for the same script","providers":[[{"excluded":false,"field":"powershell.file.script_block_id","queryType":"phrase","value":"{{powershell.file.script_block_id}}","valueType":"string"},{"excluded":false,"field":"host.id","queryType":"phrase","value":"{{host.id}}","valueType":"string"}]],"relativeFrom":"now-1h","relativeTo":"now"}} - 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-eventuser.name, source-eventuser.domain, andhost.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.pathafter 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.pidplus same-host process-start telemetry for recoveredprocess.entity_id,process.command_line, andprocess.parent.executable. -
Hint: if endpoint process telemetry is unavailable, keep later pivots bounded to
host.idplususer.idoruser.namein the alert window rather than assuming process scope. !{investigate{"description":"","label":"Process events for the PowerShell instance","providers":[[{"excluded":false,"field":"process.pid","queryType":"phrase","value":"{{process.pid}}","valueType":"string"},{"excluded":false,"field":"host.id","queryType":"phrase","value":"{{host.id}}","valueType":"string"},{"excluded":false,"field":"event.category","queryType":"phrase","value":"process","valueType":"string"}]],"relativeFrom":"now-1h","relativeTo":"now"}} - 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 reconstructedpowershell.file.script_block_text; comparepowershell.file.script_block_lengthagainstEsql.script_block_pattern_countto 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, anddestination.ip. -
Hint: missing file, DNS, or network telemetry is unresolved, not benign; if
process.entity_idwas not recovered, scope only byhost.idplususer.idoruser.namein the alert window. !{investigate{"description":"","label":"Child process activity from the PowerShell instance","providers":[[{"excluded":false,"field":"process.parent.pid","queryType":"phrase","value":"{{process.pid}}","valueType":"string"},{"excluded":false,"field":"host.id","queryType":"phrase","value":"{{host.id}}","valueType":"string"},{"excluded":false,"field":"event.category","queryType":"phrase","value":"process","valueType":"string"},{"excluded":false,"field":"event.type","queryType":"phrase","value":"start","valueType":"string"}]],"relativeFrom":"now-1h","relativeTo":"now"}} !{investigate{"description":"","label":"File, network, and DNS events for the PowerShell PID","providers":[[{"excluded":false,"field":"event.category","queryType":"phrase","value":"file","valueType":"string"},{"excluded":false,"field":"host.id","queryType":"phrase","value":"{{host.id}}","valueType":"string"},{"excluded":false,"field":"process.pid","queryType":"phrase","value":"{{process.pid}}","valueType":"string"}],[{"excluded":false,"field":"event.category","queryType":"phrase","value":"network","valueType":"string"},{"excluded":false,"field":"host.id","queryType":"phrase","value":"{{host.id}}","valueType":"string"},{"excluded":false,"field":"process.pid","queryType":"phrase","value":"{{process.pid}}","valueType":"string"}],[{"excluded":false,"field":"event.category","queryType":"phrase","value":"dns","valueType":"string"},{"excluded":false,"field":"host.id","queryType":"phrase","value":"{{host.id}}","valueType":"string"},{"excluded":false,"field":"process.pid","queryType":"phrase","value":"{{process.pid}}","valueType":"string"}]],"relativeFrom":"now-1h","relativeTo":"now"}} !{investigate{"description":"","label":"Script block events for the PowerShell PID","providers":[[{"excluded":false,"field":"event.code","queryType":"phrase","value":"4104","valueType":"string"},{"excluded":false,"field":"host.id","queryType":"phrase","value":"{{host.id}}","valueType":"string"},{"excluded":false,"field":"process.pid","queryType":"phrase","value":"{{process.pid}}","valueType":"string"}]],"relativeFrom":"now-1h","relativeTo":"now"}} - 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.idin the last 48 hours, prioritizing repeated obfuscated PowerShell, the same resolved token, or script path. !{investigate{"description":"","label":"Alerts associated with the user","providers":[[{"excluded":false,"field":"event.kind","queryType":"phrase","value":"signal","valueType":"string"},{"excluded":false,"field":"user.id","queryType":"phrase","value":"{{user.id}}","valueType":"string"}]],"relativeFrom":"now-48h/h","relativeTo":"now"}} -
Hint: if the user view is sparse or shared, pivot to the same
host.idin the last 48 hours. !{investigate{"description":"","label":"Alerts associated with the host","providers":[[{"excluded":false,"field":"event.kind","queryType":"phrase","value":"signal","valueType":"string"},{"excluded":false,"field":"host.id","queryType":"phrase","value":"{{host.id}}","valueType":"string"}]],"relativeFrom":"now-48h/h","relativeTo":"now"}} - 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.pathor stable helper path, recovered parent executable, dot-sourced location, anduser.idplushost.idall align with one recognized workflow, with child process, file, DNS, and network effects contained to it. If external records are unavailable, require the samefile.pathor 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 relevanthost.idoruser.idscope. Avoid exceptions onEsql.script_block_pattern_count,Esql.script_block_tmp,user.name, orpowershell.file.script_block_textalone.
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, anduser.idplushost.idscope. 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.
Setup
editSetup
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
Rule query
editfrom 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&CKTM
-
Tactic:
- Name: Defense Evasion
- ID: TA0005
- Reference URL: https://attack.mitre.org/tactics/TA0005/
-
Technique:
- Name: Obfuscated Files or Information
- ID: T1027
- Reference URL: https://attack.mitre.org/techniques/T1027/
-
Sub-technique:
- Name: Command Obfuscation
- ID: T1027.010
- Reference URL: https://attack.mitre.org/techniques/T1027/010/
-
Technique:
- Name: Deobfuscate/Decode Files or Information
- ID: T1140
- Reference URL: https://attack.mitre.org/techniques/T1140/
-
Tactic:
- Name: Execution
- ID: TA0002
- Reference URL: https://attack.mitre.org/tactics/TA0002/
-
Technique:
- Name: Command and Scripting Interpreter
- ID: T1059
- Reference URL: https://attack.mitre.org/techniques/T1059/
-
Sub-technique:
- Name: PowerShell
- ID: T1059.001
- Reference URL: https://attack.mitre.org/techniques/T1059/001/