Dynamic IEX Reconstruction via Method String Access

edit
A newer version is available. Check out the latest documentation.

Dynamic IEX Reconstruction via Method String Access

edit

Identifies PowerShell scripts that reconstruct the IEX (Invoke-Expression) command by accessing and indexing the string representation of method references. This obfuscation technique uses constructs like ''.IndexOf.ToString() to expose method metadata as a string, then extracts specific characters through indexed access and joins them to form IEX, bypassing static keyword detection and evading defenses such as AMSI.

Rule type: esql

Rule indices: None

Severity: low

Risk score: 21

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

Version: 2

Rule authors:

  • Elastic

Rule license: Elastic License v2

Setup

edit

Setup

The PowerShell Script Block Logging logging policy must be enabled. Steps to implement the logging policy with Advanced Audit Configuration:

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

Steps to implement the logging policy via registry:

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

Rule query

edit
FROM logs-windows.powershell_operational* metadata _id, _version, _index
| WHERE event.code == "4104"

// Look for scripts with more than 500 chars that contain a related keyword
| EVAL script_len = LENGTH(powershell.file.script_block_text)
| WHERE script_len > 500

// Replace string format expressions with 🔥 to enable counting the occurrence of the patterns we are looking for
// The emoji is used because it's unlikely to appear in scripts and has a consistent character length of 1
| EVAL replaced_with_fire = REPLACE(powershell.file.script_block_text, """(?i)['"]['"].(Insert|Normalize|Chars|SubString|Remove|LastIndexOfAny|LastIndexOf|IsNormalized|IndexOfAny|IndexOf)[^\[]+\[\d+,\d+,\d+\]""", "🔥")

// Count how many patterns were detected by calculating the number of 🔥 characters inserted
| EVAL count = LENGTH(replaced_with_fire) - LENGTH(REPLACE(replaced_with_fire, "🔥", ""))

// Keep the fields relevant to the query, although this is not needed as the alert is populated using _id
| KEEP count, replaced_with_fire, powershell.file.script_block_text, powershell.file.script_block_id, file.path, powershell.sequence, powershell.total, _id, _index, host.name, agent.id, user.id
| WHERE count >= 1

Framework: MITRE ATT&CKTM