Detection rules › Kusto

Process executed from binary hidden in Base64 encoded file

Status
available
Severity
medium
Time window
1d
Group by
Account, AccountDomain, CommandLine, Computer, FileName, ParentProcessName
Source
github.com/Azure/Azure-Sentinel

Encoding malicious software is a technique used to obfuscate files from detection. The first CommandLine component is looking for Python decoding base64. The second CommandLine component is looking for Bash/sh command line base64 decoding. The third one is looking for Ruby decoding base64.

MITRE ATT&CK coverage

Telemetry coverage

Rule body

id: d6190dde-8fd2-456a-ac5b-0a32400b0464
name: Process executed from binary hidden in Base64 encoded file
description: |
  'Encoding malicious software is a technique used to obfuscate files from detection. 
  The first CommandLine component is looking for Python decoding base64. 
  The second CommandLine component is looking for Bash/sh command line base64 decoding.
  The third one is looking for Ruby decoding base64.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: SecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: WindowsSecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: WindowsSecurityEvents
    dataTypes: 
      - SecurityEvents 
  - connectorId: WindowsForwardedEvents
    dataTypes: 
      - WindowsEvent 
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Execution
  - DefenseEvasion
relevantTechniques:
  - T1059
  - T1027
  - T1140
query: |
  let ProcessCreationEvents=(union isfuzzy=true
  (SecurityEvent
  | where EventID==4688
  | where isnotempty(CommandLine)
  | project TimeGenerated, Computer, Account = SubjectUserName, AccountDomain = SubjectDomainName, FileName = Process, CommandLine, ParentProcessName
  ),
  (WindowsEvent
  | where EventID==4688
  | where EventData has_any (".decode('base64')", "base64 --decode", ".decode64(" )
  | extend CommandLine = tostring(EventData.CommandLine)
  | where isnotempty(CommandLine)
  | extend SubjectUserName = tostring(EventData.SubjectUserName)
  | extend SubjectDomainName = tostring(EventData.SubjectDomainName)
  | extend NewProcessName = tostring(EventData.NewProcessName)
  | extend FileName=tostring(split(NewProcessName, '\\')[-1])
  | extend ParentProcessName = tostring(EventData.ParentProcessName)
  | project TimeGenerated, Computer, Account = SubjectUserName, AccountDomain = SubjectDomainName, CommandLine, ParentProcessName
  ));
  ProcessCreationEvents 
  | where CommandLine contains ".decode('base64')"
          or CommandLine contains "base64 --decode"
          or CommandLine contains ".decode64(" 
  | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), CountToday = count() by Computer, Account, AccountDomain, FileName, CommandLine, ParentProcessName
  | extend HostName = iif(Computer has '.',substring(Computer,0,indexof(Computer,'.')),Computer) , DnsDomain = iif(Computer has '.',substring(Computer,indexof(Computer,'.')+1),'')
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: Account
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Computer
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: DnsDomain
version: 1.1.3
kind: Scheduled

Stages and Predicates

Stage 0: let

let ProcessCreationEvents = (union <inlined as stages below>;

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 isnotempty(CommandLine)
| project TimeGenerated, Computer, Account = SubjectUserName, AccountDomain = SubjectDomainName, FileName = Process, CommandLine, ParentProcessName

Leg 2: WindowsEvent

WindowsEvent
| where EventID==4688
| where EventData has_any (".decode('base64')", "base64 --decode", ".decode64(" )
| extend CommandLine = tostring(EventData.CommandLine)
| where isnotempty(CommandLine)
| extend SubjectUserName = tostring(EventData.SubjectUserName)
| extend SubjectDomainName = tostring(EventData.SubjectDomainName)
| extend NewProcessName = tostring(EventData.NewProcessName)
| extend FileName=tostring(split(NewProcessName, '\\')[-1])
| extend ParentProcessName = tostring(EventData.ParentProcessName)
| project TimeGenerated, Computer, Account = SubjectUserName, AccountDomain = SubjectDomainName, CommandLine, ParentProcessName

Applied to the combined result

| where CommandLine contains ".decode('base64')"
        or CommandLine contains "base64 --decode"
        or CommandLine contains ".decode64(" | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), CountToday = count() by Computer, Account, AccountDomain, FileName, CommandLine, ParentProcessName | extend HostName = iif(Computer has '.',substring(Computer,0,indexof(Computer,'.')),Computer) , DnsDomain = iif(Computer has '.',substring(Computer,indexof(Computer,'.')+1),'')

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
CommandLinecontains
  • .decode('base64') corpus 2 (kusto 2)
  • .decode64( corpus 2 (kusto 2)
  • base64 --decode corpus 4 (elastic 2, kusto 2)
field:"CommandLine" kind:contains
CommandLineis_not_null
  • (no value, null check)
field:"CommandLine" kind:is_not_null
EventDatamatch
  • .decode('base64') transforms: term
  • .decode64( transforms: term
  • base64 --decode 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
Accountsummarize
AccountDomainsummarize
CommandLinesummarize
Computersummarize
CountTodaysummarize
EndTimeUtcsummarize
FileNamesummarize
ParentProcessNamesummarize
StartTimeUtcsummarize
DnsDomainextend
HostNameextend