Potential PowerShell Obfuscation via Backtick-Escaped Variable Expansion
editPotential PowerShell Obfuscation via Backtick-Escaped Variable Expansion
editDetects PowerShell scripts that use backtick-escaped characters inside ${} variable expansion (multiple backticks between word characters) to reconstruct strings at runtime. Attackers use variable-expansion obfuscation to split keywords, hide commands, and evade static analysis and 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 Backtick-Escaped Variable Expansion
Possible investigation steps
- Did you reconstruct the complete alerting script block and confirm the escaped-variable pattern?
-
Focus: alert-local
Esql.script_block_pattern_count,Esql.script_block_tmp,Esql.script_block_length, and reconstructedpowershell.file.script_block_text. !{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"}} -
Hint: query PowerShell Operational events from logs-windows.powershell_operational*, then reconstruct with
powershell.file.script_block_id+powershell.sequence+powershell.totalon the samehost.id; confirm fragment count before judging intent. - Implication: escalate when the complete block repeats ${} backtick expansion across command, variable, or string-building logic; lower suspicion when one isolated escaped token sits inside readable build or template code. Missing fragments keep the alert unresolved, not benign.
- What execution-critical text appears when the backticks inside ${} are removed?
-
Focus: reconstructed
powershell.file.script_block_text, alert-localEsql.script_block_tmp, and the command, variable, or string tokens exposed by removing the backticks. - Implication: escalate when escaped expansions hide cmdlets, invocation operators, download strings, encoded payloads, AMSI or logging bypass names, or variables that feed execution; lower suspicion when they only protect literal template placeholders and no decoded token changes execution.
- Does the script origin and user-host context fit one bounded generation workflow?
-
Focus:
file.path,file.name,user.id,host.name, andhost.id. -
Implication: escalate when
file.pathis absent for a long obfuscated block, the path is user-writable, temporary, or delivery-oriented, or the account-host pair does not fit script generation or deployment; lower suspicion only when origin, account, host, and decoded content match one recognized build, packaging, updater, or test workflow. - If process telemetry is available, how was the PowerShell instance launched?
-
Focus: alert-preserved
process.pid, plus recoveredprocess.executable,process.command_line,process.parent.executable, andprocess.parent.command_line. -
Hint: recover the matching process via
host.id + process.pid; around alert@timestamp, prefer the closest start event for thathost.idif PID reuse creates multiple matches. !{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 the chain starts from a browser, document process, archive, remote tooling, scheduled task, non-PowerShell host process using System.Management.Automation, or encoded/in-memory command path that does not fit the script purpose; lower suspicion when executable, parent, and command line match the same recognized build, packaging, updater, or test workflow. Missing endpoint process telemetry keeps lineage unresolved, not benign.
- Does the decoded content request a second execution stage?
-
Focus: decoded execution, download, credential, policy, persistence, or payload-staging commands in
powershell.file.script_block_text. -
Hint: after process recovery via
host.id + process.pid, review child events whereprocess.parent.entity_idequals the recovered PowerShellprocess.entity_id; usehost.idplusprocess.parent.pidas a weaker tight-window fallback. !{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"}} - Implication: escalate when hidden tokens feed execution operators, downloaded content, child processes, credential access, policy tampering, persistence, or payload staging; lower suspicion when decoded actions stay inside the same recognized generation or update task and no second execution path appears. Missing endpoint process telemetry leaves child-process correlation unresolved, not benign.
- If local evidence remains suspicious or unresolved, does this escaped-variable pattern appear elsewhere?
-
Focus: related alerts for
user.idandhost.idin the last 48 hours, decoded token fragments frompowershell.file.script_block_text, and the samefile.pathwhen present. -
Hint: start with related alerts for the same
user.id; if sparse, pivot to the samehost.id. !{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"}} !{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 scope when the same escaped-variable technique or decoded execution strings appear on unrelated hosts, users, or source paths; keep local when recurrence stays inside the same confirmed workflow and local evidence is otherwise clean.
- Escalate on strong unauthorized execution evidence from decoded tokens, fileless or unusual origin, launch chain, second-stage behavior, or repeated scope; close only when telemetry and any needed owner or change confirmation explain every suspicious token as one recognized build, packaging, updater, or test workflow; preserve and escalate when fragments, endpoint process telemetry, or workflow proof are missing for a suspicious script.
False positive analysis
-
Code generation, packaging, build, updater, bootstrap, or test harness workflows can emit backtick-escaped ${} sequences while producing PowerShell text. Confirm only when reconstructed
powershell.file.script_block_textis limited to template, packaging, installer, updater, or test logic;file.pathor its absence fits that source; any recovered launch chain supports the same tool; anduser.idplushost.idmatch the same operating scope. If change records are unavailable, require the same file origin, decoded token family, and user-host scope across prior alerts from this rule. - Treat one benign-looking token as insufficient for closure. Do not close when decoded content contains execution, download, defense-evasion, credential, or persistence logic that the named workflow does not require.
-
Before creating an exception, anchor it to stable indexed fields such as
user.id,host.id, andfile.pathorfile.name, plus the stable decoded token family and recovered parent context when available. Do not useEsql.script_block_pattern_countorEsql.script_block_tmpin exceptions because they are alert-local summaries, not exception-safe fields.
Response and remediation
-
If confirmed benign, document the evidence that explained the alert first: reconstructed script intent, file origin or fileless source,
user.id,host.id, and the recovered launch context when available. Then reverse any temporary containment and create a narrow exception only after the same workflow pattern is stable across prior alerts. -
If suspicious but unconfirmed, preserve the alert, reconstructed
powershell.file.script_block_text,powershell.file.script_block_id, ordered 4104 fragments, file origin, host-user scope, recovered launch context when available, and decoded indicators before containment. Apply reversible containment such as heightened monitoring or host isolation only if the host role can tolerate it, then escalate before deleting artifacts or resetting credentials. - If confirmed malicious, preserve the same script, source-event, process, and decoded-indicator evidence before destructive action. Isolate the host when the evidence shows unauthorized execution and host criticality allows it, record the recovered process identifier before termination, block confirmed malicious decoded indicators, and remove only the scripts, payloads, startup items, policy changes, or persistence artifacts identified during the investigation. Reset credentials only when the investigation shows account misuse beyond local script execution.
-
Post-incident hardening: retain PowerShell script-block logging, keep endpoint process telemetry sufficient for
host.id + process.pidrecovery, restrict recurring script generation to recognized signed tooling and service scopes, and document the confirmed benign workflow or malicious decoded token family for future triage.
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"
// 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, """\$\{(\w++`){2,}\w++\}""", "🔥")
// 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.name,
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/