Detection rules › Kusto
PowerShell Encoded Command Execution (Living off the Land)
Detects PowerShell or pwsh.exe launched with encoded command flags (-EncodedCommand, -enc, -ec, -enco). Attackers encode commands in Base64 to obfuscate malicious payloads and bypass script-based controls. Risk is elevated when a download cradle pattern is also detected. Tune AllowlistedParents for known-safe management tools such as SCCM.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Execution | |
| Stealth |
Telemetry coverage
Rule body
id: 7b2f4d1a-9c3e-4f72-8b1d-3e6a9f2c4b8d
name: PowerShell Encoded Command Execution (Living off the Land)
description: |
Detects PowerShell or pwsh.exe launched with encoded command flags
(-EncodedCommand, -enc, -ec, -enco). Attackers encode commands in Base64
to obfuscate malicious payloads and bypass script-based controls.
Risk is elevated when a download cradle pattern is also detected.
Tune AllowlistedParents for known-safe management tools such as SCCM.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: MicrosoftThreatProtection
dataTypes:
- DeviceProcessEvents
- connectorId: SecurityEvents
dataTypes:
- SecurityEvent
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Execution
- DefenseEvasion
relevantTechniques:
- T1059.001
- T1027
query: |
let Lookback = 1h;
let EncodedFlagRegex = @'(?i)(^|\s)-(encodedcommand|enc|ec|enco)\b';
let AllowlistedParents = dynamic([
"SqlServer.exe", "ManagementStudio.exe", "devenv.exe",
"ccmexec.exe", "SMSAgent.exe"
]);
let DownloadCradlePatterns = dynamic([
"Net.WebClient", "WebRequest", "DownloadString", "DownloadFile",
"IEX", "Invoke-Expression", "Invoke-RestMethod", "Start-BitsTransfer"
]);
let MDE_Results =
DeviceProcessEvents
| where Timestamp >= ago(Lookback)
| where FileName in~ ("powershell.exe", "pwsh.exe")
| where ProcessCommandLine matches regex EncodedFlagRegex
| where not(InitiatingProcessFileName has_any (AllowlistedParents))
| extend
HasDownloadCradle = ProcessCommandLine has_any (DownloadCradlePatterns),
AccountName = InitiatingProcessAccountName,
AccountDomain = InitiatingProcessAccountDomain,
HostName = DeviceName,
EventTime = Timestamp
| project EventTime, HostName, AccountName, AccountDomain,
ProcessCommandLine, InitiatingProcessFileName, HasDownloadCradle;
let SecEvent_Results =
SecurityEvent
| where TimeGenerated >= ago(Lookback)
| where EventID == 4688
| where NewProcessName endswith "\\powershell.exe"
or NewProcessName endswith "\\pwsh.exe"
| where CommandLine matches regex EncodedFlagRegex
| where not(ParentProcessName has_any (AllowlistedParents))
| extend
HasDownloadCradle = CommandLine has_any (DownloadCradlePatterns),
AccountName = SubjectUserName,
AccountDomain = SubjectDomainName,
HostName = Computer,
EventTime = TimeGenerated,
ProcessCommandLine = CommandLine,
InitiatingProcessFileName = ParentProcessName
| project EventTime, HostName, AccountName, AccountDomain,
ProcessCommandLine, InitiatingProcessFileName, HasDownloadCradle;
union MDE_Results, SecEvent_Results
| extend RiskScore = iif(HasDownloadCradle, 2, 1)
| sort by RiskScore desc, EventTime desc
entityMappings:
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: HostName
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountName
- identifier: NTDomain
columnName: AccountDomain
- entityType: Process
fieldMappings:
- identifier: CommandLine
columnName: ProcessCommandLine
version: 1.0.4
kind: Scheduled
Stages and Predicates
Stage 0: let
let Lookback = 1h;
let EncodedFlagRegex = @'(?i)(^|\s)-(encodedcommand|enc|ec|enco)\b';
let AllowlistedParents = dynamic([
"SqlServer.exe", "ManagementStudio.exe", "devenv.exe",
"ccmexec.exe", "SMSAgent.exe"
]);
let DownloadCradlePatterns = dynamic([
"Net.WebClient", "WebRequest", "DownloadString", "DownloadFile",
"IEX", "Invoke-Expression", "Invoke-RestMethod", "Start-BitsTransfer"
]);
let MDE_Results = DeviceProcessEvents <inlined as stages below>;
let SecEvent_Results = SecurityEvent <inlined as stages below>;
Stage 1: source
let MDE_Results
Stage 2: source
let SecEvent_Results
Stage 3: union
union of 2 branches
Stage 4: source
DeviceProcessEvents
Stage 5: where
where Timestamp >= ago(3600s)
Stage 6: where
where FileName in~ ("powershell.exe", "pwsh.exe")
Stage 7: where
where ProcessCommandLine matches regex @"(?i)(^|\s)-(encodedcommand|enc|ec|enco)\b"
Stage 8: where
where not ((InitiatingProcessFileName contains "SqlServer.exe" or InitiatingProcessFileName contains "ManagementStudio.exe" or InitiatingProcessFileName contains "devenv.exe" or InitiatingProcessFileName contains "ccmexec.exe" or InitiatingProcessFileName contains "SMSAgent.exe"))
Stage 9: extend
extend AccountDomain, AccountName, EventTime, HasDownloadCradle, HostName
Stage 10: project
project AccountDomain, AccountName, EventTime, HasDownloadCradle, HostName, InitiatingProcessFileName, ProcessCommandLine
Stage 11: source
SecurityEvent
Stage 12: where
where TimeGenerated >= ago(3600s)
Stage 13: where
where EventID == 4688
Stage 14: where
where (NewProcessName endswith @"\powershell.exe" or NewProcessName endswith @"\pwsh.exe")
Stage 15: where
where CommandLine matches regex @"(?i)(^|\s)-(encodedcommand|enc|ec|enco)\b"
Stage 16: where
where not ((ParentProcessName contains "SqlServer.exe" or ParentProcessName contains "ManagementStudio.exe" or ParentProcessName contains "devenv.exe" or ParentProcessName contains "ccmexec.exe" or ParentProcessName contains "SMSAgent.exe"))
Stage 17: extend
extend AccountDomain, AccountName, EventTime, HasDownloadCradle, HostName, InitiatingProcessFileName, ProcessCommandLine
Stage 18: project
project AccountDomain, AccountName, EventTime, HasDownloadCradle, HostName, InitiatingProcessFileName, ProcessCommandLine
Stage 19: extend
extend RiskScore
Stage 20: sort
sort by EventTime, RiskScore
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
CommandLine | regex_match |
| field:"CommandLine" kind:regex_match value:"(?i)(^|\s)-(encodedcommand|enc|ec|enco)\b" |
EventID | eq |
| field:"EventID" kind:eq value:"4688" |
FileName | in |
| field:"file_name" kind:in |
NewProcessName | ends_with |
| field:"Image" kind:ends_with |
ProcessCommandLine | regex_match |
| field:"CommandLine" kind:regex_match value:"(?i)(^|\s)-(encodedcommand|enc|ec|enco)\b" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
InitiatingProcessFileName | match | SqlServer.exe, ManagementStudio.exe, devenv.exe, ccmexec.exe, SMSAgent.exe | excludes:InitiatingProcessFileName |
ParentProcessName | match | SqlServer.exe, ManagementStudio.exe, devenv.exe, ccmexec.exe, SMSAgent.exe | excludes:ParentProcessName |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountDomain | project |
AccountName | project |
EventTime | project |
HasDownloadCradle | project |
HostName | project |
InitiatingProcessFileName | project |
ProcessCommandLine | project |
RiskScore | extend |