Potential PowerShell Obfuscation via Backtick-Escaped Variable Expansion

Identifies PowerShell scripts that use backtick-escaped characters inside ${} variable expansion as a form of obfuscation. These methods are designed to evade static analysis and bypass security protections such as the Antimalware Scan Interface (AMSI).

Elastic rule (View on GitHub)

 1[metadata]
 2creation_date = "2025/04/16"
 3integration = ["windows"]
 4maturity = "production"
 5updated_date = "2025/04/16"
 6
 7[rule]
 8author = ["Elastic"]
 9description = """
10Identifies PowerShell scripts that use backtick-escaped characters inside ${} variable expansion as a form of
11obfuscation. These methods are designed to evade static analysis and bypass security protections such as the Antimalware
12Scan Interface (AMSI).
13"""
14from = "now-9m"
15language = "esql"
16license = "Elastic License v2"
17name = "Potential PowerShell Obfuscation via Backtick-Escaped Variable Expansion"
18risk_score = 21
19rule_id = "d43f2b43-02a1-4219-8ce9-10929a32a618"
20setup = """## Setup
21
22The 'PowerShell Script Block Logging' logging policy must be enabled.
23Steps to implement the logging policy with Advanced Audit Configuration:

Computer Configuration > Administrative Templates > Windows PowerShell > Turn on PowerShell Script Block Logging (Enable)

1
2Steps to implement the logging policy via registry:

reg add "hklm\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging" /v EnableScriptBlockLogging /t REG_DWORD /d 1

 1"""
 2severity = "low"
 3tags = [
 4    "Domain: Endpoint",
 5    "OS: Windows",
 6    "Use Case: Threat Detection",
 7    "Tactic: Defense Evasion",
 8    "Data Source: PowerShell Logs",
 9]
10timestamp_override = "event.ingested"
11type = "esql"
12
13query = '''
14FROM logs-windows.powershell_operational* metadata _id, _version, _index
15| WHERE event.code == "4104"
16
17// Look for scripts with more than 500 chars that contain a related keyword
18| EVAL script_len = LENGTH(powershell.file.script_block_text)
19| WHERE script_len > 500
20
21// Replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for
22// The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1
23| EVAL replaced_with_fire = REPLACE(powershell.file.script_block_text, """\$\{(\w++`){2,}\w++\}""", "🔥")
24
25// Count how many patterns were detected by calculating the number of 🔥 characters inserted
26| EVAL count = LENGTH(replaced_with_fire) - LENGTH(REPLACE(replaced_with_fire, "🔥", ""))
27
28// Keep the fields relevant to the query, although this is not needed as the alert is populated using _id
29| KEEP count, replaced_with_fire, powershell.file.script_block_text, powershell.file.script_block_id, file.path, file.name, powershell.sequence, powershell.total, _id, _index, host.name, agent.id, user.id
30| WHERE count >= 1
31'''
32
33
34[[rule.threat]]
35framework = "MITRE ATT&CK"
36[[rule.threat.technique]]
37id = "T1027"
38name = "Obfuscated Files or Information"
39reference = "https://attack.mitre.org/techniques/T1027/"
40
41[[rule.threat.technique]]
42id = "T1140"
43name = "Deobfuscate/Decode Files or Information"
44reference = "https://attack.mitre.org/techniques/T1140/"
45
46
47[rule.threat.tactic]
48id = "TA0005"
49name = "Defense Evasion"
50reference = "https://attack.mitre.org/tactics/TA0005/"
51[[rule.threat]]
52framework = "MITRE ATT&CK"
53[[rule.threat.technique]]
54id = "T1059"
55name = "Command and Scripting Interpreter"
56reference = "https://attack.mitre.org/techniques/T1059/"
57[[rule.threat.technique.subtechnique]]
58id = "T1059.001"
59name = "PowerShell"
60reference = "https://attack.mitre.org/techniques/T1059/001/"
61
62
63
64[rule.threat.tactic]
65id = "TA0002"
66name = "Execution"
67reference = "https://attack.mitre.org/tactics/TA0002/"

Related rules

to-top