Detection rules › Kusto

Powershell Empire Cmdlets Executed in Command Line

Status
available
Severity
medium
Time window
12h
Source
github.com/Azure/Azure-Sentinel

This query identifies use of PowerShell Empire's cmdlets within the command line data of the PowerShell process, indicating potential use of the post-exploitation tool.

MITRE ATT&CK coverage

TacticTechniques
Execution
Persistence
Privilege Escalation
Stealth
Credential Access
Discovery
Lateral Movement
Collection
Command & Control
Exfiltration

Telemetry coverage

Rule body

id: ef88eb96-861c-43a0-ab16-f3835a97c928
name: Powershell Empire Cmdlets Executed in Command Line
description: |
  'This query identifies use of PowerShell Empire's cmdlets within the command line data of the PowerShell process, indicating potential use of the post-exploitation tool.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: SecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: WindowsSecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: WindowsSecurityEvents
    dataTypes:
      - SecurityEvents
  - connectorId: WindowsForwardedEvents
    dataTypes:
      - WindowsEvent
queryFrequency: 12h
queryPeriod: 12h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Collection
  - CommandAndControl
  - CredentialAccess
  - DefenseEvasion
  - Discovery
  - Execution
  - Exfiltration
  - LateralMovement
  - Persistence
  - PrivilegeEscalation
relevantTechniques:
  - T1548.002
  - T1134
  - T1134.002
  - T1134.005
  - T1087.001
  - T1087.002
  - T1557.001
  - T1071.001
  - T1560
  - T1547.001
  - T1547.005
  - T1547.009
  - T1217
  - T1115
  - T1059
  - T1059.001
  - T1059.003
  - T1136.001
  - T1136.002
  - T1543.003
  - T1555.003
  - T1484.001
  - T1482
  - T1114.001
  - T1573.002
  - T1546.008
  - T1041
  - T1567.001
  - T1567.002
  - T1068
  - T1210
  - T1083
  - T1615
  - T1574.001
  - T1574.004
  - T1574.007
  - T1574.008
  - T1574.009
  - T1070.006
  - T1105
  - T1056.001
  - T1056.004
  - T1106
  - T1046
  - T1135
  - T1040
  - T1027
  - T1003.001
  - T1057
  - T1055
  - T1021.003
  - T1021.004
  - T1053.005
  - T1113
  - T1518.001
  - T1558.002
  - T1558.003
  - T1082
  - T1016
  - T1049
  - T1569.002
  - T1127.001
  - T1552.001
  - T1552.004
  - T1550.002
  - T1125
  - T1102.002
  - T1047
query: |
  let regexEmpire = tostring(toscalar(externaldata(cmdlets:string)[@"https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/Sample%20Data/Feeds/EmpireCommandString.txt"] with (format="txt")));
  (union isfuzzy=true
   (SecurityEvent
  | where EventID == 4688
  //consider filtering on filename if perf issues occur
  //where FileName in~ ("powershell.exe","powershell_ise.exe","pwsh.exe")
  | where not(ParentProcessName has_any ('gc_worker.exe', 'gc_service.exe'))
  | where CommandLine has "-encodedCommand"
  | parse kind=regex flags=i CommandLine with * "-EncodedCommand " encodedCommand
  | extend encodedCommand = iff(encodedCommand has " ", tostring(split(encodedCommand, " ")[0]), encodedCommand)
  // Note: currently the base64_decode_tostring function is limited to supporting UTF8
  | extend decodedCommand = translate('\0','', base64_decode_tostring(substring(encodedCommand, 0, strlen(encodedCommand) -  (strlen(encodedCommand) %8)))), encodedCommand, CommandLine , strlen(encodedCommand)
  | extend EfectiveCommand = iff(isnotempty(encodedCommand), decodedCommand, CommandLine)
  | where EfectiveCommand matches regex regexEmpire
  | project timestamp = TimeGenerated, Computer, SubjectUserName, SubjectDomainName, FileName = Process, EfectiveCommand, decodedCommand, encodedCommand, CommandLine, ParentProcessName
  | extend HostName = split(Computer, '.', 0)[0], DnsDomain = strcat_array(array_slice(split(Computer, '.'), 1, -1), '.')
  ),
  (WindowsEvent
  | where EventID == 4688
  | where EventData has_any ("-encodedCommand", "powershell.exe","powershell_ise.exe","pwsh.exe")
  | where not(EventData has_any ('gc_worker.exe', 'gc_service.exe'))
  //consider filtering on filename if perf issues occur
  //extend NewProcessName = tostring(EventData.NewProcessName)
  //extend Process=tostring(split(NewProcessName, '\\')[-1])
  //FileName = Process
  //where FileName in~ ("powershell.exe","powershell_ise.exe","pwsh.exe")
  | extend ParentProcessName = tostring(EventData.ParentProcessName)
  | where not(ParentProcessName has_any ('gc_worker.exe', 'gc_service.exe'))
  | extend CommandLine = tostring(EventData.CommandLine)
  | where CommandLine has "-encodedCommand"
  | parse kind=regex flags=i CommandLine with * "-EncodedCommand " encodedCommand
  | extend encodedCommand = iff(encodedCommand has " ", tostring(split(encodedCommand, " ")[0]), encodedCommand)
  // Note: currently the base64_decode_tostring function is limited to supporting UTF8
  | extend decodedCommand = translate('\0','', base64_decode_tostring(substring(encodedCommand, 0, strlen(encodedCommand) -  (strlen(encodedCommand) %8)))), encodedCommand, CommandLine , strlen(encodedCommand)
  | extend EfectiveCommand = iff(isnotempty(encodedCommand), decodedCommand, CommandLine)
  | where EfectiveCommand matches regex regexEmpire
  | extend SubjectUserName = tostring(EventData.SubjectUserName)
  | extend SubjectDomainName = tostring(EventData.SubjectDomainName)
  | extend NewProcessName = tostring(EventData.NewProcessName)
  | extend Process=tostring(split(NewProcessName, '\\')[-1])
  | project timestamp = TimeGenerated, Computer, SubjectUserName, SubjectDomainName, FileName = Process, EfectiveCommand, decodedCommand, encodedCommand, CommandLine, ParentProcessName
  | extend HostName = split(Computer, '.', 0)[0], DnsDomain = strcat_array(array_slice(split(Computer, '.'), 1, -1), '.')
  ))
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: SubjectUserName
      - identifier: NTDomain
        columnName: SubjectDomainName
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: DnsDomain
version: 1.3.1
kind: Scheduled

Stages and Predicates

Let binding: regexEmpire

let regexEmpire = tostring(toscalar(externaldata(cmdlets:string)[@"https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/Sample%20Data/Feeds/EmpireCommandString.txt"] with (format="txt")));

union isfuzzy=true (2 sources)

Each leg below queries one source; the rule matches if any leg does. Sources: SecurityEvent, WindowsEvent

Leg 1: SecurityEvent

SecurityEvent
| where EventID == 4688
| where not(ParentProcessName has_any ('gc_worker.exe', 'gc_service.exe'))
| where CommandLine has "-encodedCommand"
| parse kind=regex flags=i CommandLine with * "-EncodedCommand " encodedCommand
| extend encodedCommand = iff(encodedCommand has " ", tostring(split(encodedCommand, " ")[0]), encodedCommand)
| extend decodedCommand = translate('\0','', base64_decode_tostring(substring(encodedCommand, 0, strlen(encodedCommand) -  (strlen(encodedCommand) %8)))), encodedCommand, CommandLine , strlen(encodedCommand)
| extend EfectiveCommand = iff(isnotempty(encodedCommand), decodedCommand, CommandLine)
| where EfectiveCommand matches regex regexEmpire
| project timestamp = TimeGenerated, Computer, SubjectUserName, SubjectDomainName, FileName = Process, EfectiveCommand, decodedCommand, encodedCommand, CommandLine, ParentProcessName
| extend HostName = split(Computer, '.', 0)[0], DnsDomain = strcat_array(array_slice(split(Computer, '.'), 1, -1), '.')

Leg 2: WindowsEvent

WindowsEvent
| where EventID == 4688
| where EventData has_any ("-encodedCommand", "powershell.exe","powershell_ise.exe","pwsh.exe")
| where not(EventData has_any ('gc_worker.exe', 'gc_service.exe'))
| extend ParentProcessName = tostring(EventData.ParentProcessName)
| where not(ParentProcessName has_any ('gc_worker.exe', 'gc_service.exe'))
| extend CommandLine = tostring(EventData.CommandLine)
| where CommandLine has "-encodedCommand"
| parse kind=regex flags=i CommandLine with * "-EncodedCommand " encodedCommand
| extend encodedCommand = iff(encodedCommand has " ", tostring(split(encodedCommand, " ")[0]), encodedCommand)
| extend decodedCommand = translate('\0','', base64_decode_tostring(substring(encodedCommand, 0, strlen(encodedCommand) -  (strlen(encodedCommand) %8)))), encodedCommand, CommandLine , strlen(encodedCommand)
| extend EfectiveCommand = iff(isnotempty(encodedCommand), decodedCommand, CommandLine)
| where EfectiveCommand matches regex regexEmpire
| extend SubjectUserName = tostring(EventData.SubjectUserName)
| extend SubjectDomainName = tostring(EventData.SubjectDomainName)
| extend NewProcessName = tostring(EventData.NewProcessName)
| extend Process=tostring(split(NewProcessName, '\\')[-1])
| project timestamp = TimeGenerated, Computer, SubjectUserName, SubjectDomainName, FileName = Process, EfectiveCommand, decodedCommand, encodedCommand, CommandLine, ParentProcessName
| extend HostName = split(Computer, '.', 0)[0], DnsDomain = strcat_array(array_slice(split(Computer, '.'), 1, -1), '.')

Exclusions

The rule actively suppresses these predicates.

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
CommandLinematch
  • -encodedCommand transforms: term corpus 4 (elastic 2, sigma 1, kusto 1)
field:"CommandLine" kind:match value:"-encodedCommand"
EfectiveCommandregex_match
  • regexEmpire
field:"EfectiveCommand" kind:regex_match value:"regexEmpire"
EventDatamatch
  • -encodedCommand transforms: term
  • powershell.exe transforms: term
  • powershell_ise.exe transforms: term
  • pwsh.exe transforms: term
field:"EventData" kind:match
EventIDeq
  • 4688 corpus 317 (splunk 283, kusto 33, elastic 1)
field:"EventID" kind:eq value:"4688"

Output fields

These fields are emitted when the rule matches.

FieldSource
CommandLineproject
Computerproject
EfectiveCommandproject
FileNameproject
ParentProcessNameproject
SubjectDomainNameproject
SubjectUserNameproject
decodedCommandproject
encodedCommandproject
timestampproject
DnsDomainextend
HostNameextend