Detection rules › Kusto

Dev-0270 Malicious Powershell usage

Status
available
Severity
high
Time window
6h
Source
github.com/Azure/Azure-Sentinel

DEV-0270 heavily uses powershell to achieve their objective at various stages of their attack. To locate powershell related activity tied to the actor, Microsoft Sentinel customers can run the following query.

MITRE ATT&CK coverage

Event coverage

Rule body kusto

id: 422ca2bf-598b-4872-82bb-5f7e8fa731e7
name: Dev-0270 Malicious Powershell usage
description: | 
  'DEV-0270 heavily uses powershell to achieve their objective at various stages of their attack. To locate powershell related activity tied to the actor, Microsoft Sentinel customers can run the following query.'
severity: High 
requiredDataConnectors:
  - connectorId: SecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: WindowsSecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - DeviceProcessEvents
queryFrequency: 6h 
queryPeriod: 6h 
triggerOperator: gt 
triggerThreshold: 0 
status: Available
tactics: 
  - Exfiltration
  - DefenseEvasion
relevantTechniques:
  -  T1048
  -  T1562
tags:
  - Dev-0270
query: |
  (union isfuzzy=true
  (SecurityEvent
  | where EventID==4688
  | extend FileName=tostring(split(NewProcessName, @'\')[(-1)]),  ProcessCommandLine = CommandLine, InitiatingProcessFileName=ParentProcessName
  | where (FileName =~ "powershell.exe" and ProcessCommandLine has_all("try", "Add-MpPreference", "-ExclusionPath", "ProgramData", "catch")) or (FileName =~ 'powershell.exe' and ProcessCommandLine has_all('Add-PSSnapin', 'Get-Recipient', '-ExpandProperty', 'EmailAddresses', 'SmtpAddress', '-hidetableheaders') )
  | project TimeGenerated, Computer, Account, AccountDomain, ProcessName, ProcessNameFullPath = NewProcessName, InitiatingProcessFileName, EventID, Activity, CommandLine, EventSourceName, Type
  ),
  (DeviceProcessEvents 
  | where (FileName =~ "powershell.exe" and ((ProcessCommandLine has_all("try", "Add-MpPreference", "-ExclusionPath", "ProgramData", "catch"))  or (ProcessCommandLine has_all('Add-PSSnapin', 'Get-Recipient', '-ExpandProperty', 'EmailAddresses', 'SmtpAddress', '-hidetableheaders'))))
  or ( InitiatingProcessFileName =~ 'powershell.exe' and (((InitiatingProcessCommandLine has_all('$file=', 'dllhost.exe', 'Invoke-WebRequest', '-OutFile')) or ((InitiatingProcessCommandLine has_all('$admins=', 'System.Security.Principal.SecurityIdentifier', 'Translate', '-split', 'localgroup', '/add', '$rdp='))))))
  | extend Account = strcat(InitiatingProcessAccountDomain, @'\', InitiatingProcessAccountName),  Computer = DeviceName
  )
  )
  | extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
  | extend AccountName = tostring(split(Account, @'\')[1]), AccountNTDomain = tostring(split(Account, @'\')[0])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: Account
      - identifier: Name
        columnName: AccountName
      - identifier: NTDomain
        columnName: AccountNTDomain
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Computer
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: HostNameDomain
version: 1.0.4
kind: Scheduled

Stages and Predicates

union isfuzzy=true (2 sources)

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

Leg 1: SecurityEvent

SecurityEvent
| where EventID==4688
| extend FileName=tostring(split(NewProcessName, @'\')[(-1)]),  ProcessCommandLine = CommandLine, InitiatingProcessFileName=ParentProcessName
| where (FileName =~ "powershell.exe" and ProcessCommandLine has_all("try", "Add-MpPreference", "-ExclusionPath", "ProgramData", "catch")) or (FileName =~ 'powershell.exe' and ProcessCommandLine has_all('Add-PSSnapin', 'Get-Recipient', '-ExpandProperty', 'EmailAddresses', 'SmtpAddress', '-hidetableheaders') )
| project TimeGenerated, Computer, Account, AccountDomain, ProcessName, ProcessNameFullPath = NewProcessName, InitiatingProcessFileName, EventID, Activity, CommandLine, EventSourceName, Type

Leg 2: DeviceProcessEvents

DeviceProcessEvents 
| where (FileName =~ "powershell.exe" and ((ProcessCommandLine has_all("try", "Add-MpPreference", "-ExclusionPath", "ProgramData", "catch"))  or (ProcessCommandLine has_all('Add-PSSnapin', 'Get-Recipient', '-ExpandProperty', 'EmailAddresses', 'SmtpAddress', '-hidetableheaders'))))
or ( InitiatingProcessFileName =~ 'powershell.exe' and (((InitiatingProcessCommandLine has_all('$file=', 'dllhost.exe', 'Invoke-WebRequest', '-OutFile')) or ((InitiatingProcessCommandLine has_all('$admins=', 'System.Security.Principal.SecurityIdentifier', 'Translate', '-split', 'localgroup', '/add', '$rdp='))))))
| extend Account = strcat(InitiatingProcessAccountDomain, @'\', InitiatingProcessAccountName),  Computer = DeviceName

Applied to the combined result

| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
| extend AccountName = tostring(split(Account, @'\')[1]), AccountNTDomain = tostring(split(Account, @'\')[0])

Indicators

Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.

FieldKindValues
EventIDeq
  • 4688 transforms: cased corpus 313 (splunk 283, kusto 30)
FileNameeq
  • powershell.exe corpus 3 (kusto 3)
InitiatingProcessCommandLinematch
  • $admins=
  • $file=
  • $rdp=
  • -OutFile corpus 2 (sigma 1, kusto 1)
  • -split
  • /add corpus 9 (sigma 5, splunk 2, kusto 2)
  • Invoke-WebRequest corpus 13 (sigma 10, elastic 1, chronicle 1, kusto 1)
  • System.Security.Principal.SecurityIdentifier
  • Translate
  • dllhost.exe corpus 2 (kusto 2)
  • localgroup corpus 2 (sigma 1, kusto 1)
InitiatingProcessFileNameeq
  • powershell.exe corpus 15 (elastic 12, kusto 2, splunk 1)
ProcessCommandLinematch
  • -ExclusionPath corpus 2 (kusto 2)
  • -ExpandProperty corpus 2 (sigma 1, kusto 1)
  • -hidetableheaders corpus 2 (sigma 1, kusto 1)
  • Add-MpPreference corpus 3 (sigma 2, kusto 1)
  • Add-PSSnapin corpus 3 (sigma 2, kusto 1)
  • EmailAddresses corpus 2 (sigma 1, kusto 1)
  • Get-Recipient corpus 2 (sigma 1, kusto 1)
  • ProgramData
  • SmtpAddress corpus 2 (sigma 1, kusto 1)
  • catch
  • try

Output fields

Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.

FieldSource
Accountextend
AccountDomainproject
Activityproject
CommandLineproject
Computerextend
EventIDproject
EventSourceNameproject
InitiatingProcessFileNameproject
ProcessNameproject
ProcessNameFullPathproject
TimeGeneratedproject
Typeproject
DomainIndexextend
HostNameextend
HostNameDomainextend
AccountNTDomainextend
AccountNameextend