Detection rules › Kusto

PowerShell without powershell.exe

Group by
InitiatingProcessSHA1
Author
FalconForce
Source
github.com/FalconForceTeam/FalconFriday

This query detects the use of PowerShell through "system.management.automation.dll" which is invoked by a process with a low global prevalence (i.e., fairly unique binary).

Known false positives

  • Many legitimate programs will use the "system.management.automation.dll". This might lead to false-positives that require filtering.

MITRE ATT&CK coverage

Telemetry coverage

Rule body

let timeframe = 2*1h;
let default_global_prevalence = 0;
let suspiciousProcs = materialize(
DeviceImageLoadEvents
| where FileName =~ "system.management.automation.dll" or FileName =~ "system.management.automation.ni.dll" and not(isempty(InitiatingProcessSHA1))
| summarize count() by InitiatingProcessSHA1
// FileProfile is case-sensitive and works on lower-case hashes.
| extend InitiatingProcessSHA1=tolower(InitiatingProcessSHA1)
| invoke FileProfile(InitiatingProcessSHA1, 1000)
| where not(ProfileAvailability =~ "Error")
| where coalesce(IsRootSignerMicrosoft, false) == false or  coalesce(IsCertificateValid, false) == false
| where (IsCertificateValid and coalesce(GlobalPrevalence,default_global_prevalence) < 200) or (not(coalesce(IsCertificateValid, false)) and coalesce(GlobalPrevalence,default_global_prevalence) < 500));
DeviceImageLoadEvents
| where ingestion_time() >= ago(timeframe)
// FileProfile is case-sensitive and works on lower-case hashes.
| extend InitiatingProcessSHA1=tolower(InitiatingProcessSHA1)
| where InitiatingProcessSHA1 in~ ((suspiciousProcs | project InitiatingProcessSHA1)) and FileName startswith "System.Management.Automation"
| join kind=inner suspiciousProcs on InitiatingProcessSHA1
// Begin environment-specific filter.
// End environment-specific filter.

Stages and Predicates

Parameters

let timeframe = 2*1h;
let default_global_prevalence = 0;

Let binding: suspiciousProcs used in Stages 1, 6

let suspiciousProcs = materialize(
DeviceImageLoadEvents
| where FileName =~ "system.management.automation.dll" or FileName =~ "system.management.automation.ni.dll" and not(isempty(InitiatingProcessSHA1))
| summarize count() by InitiatingProcessSHA1
| extend InitiatingProcessSHA1=tolower(InitiatingProcessSHA1)
| invoke FileProfile(InitiatingProcessSHA1, 1000)
| where not(ProfileAvailability =~ "Error")
| where coalesce(IsRootSignerMicrosoft, false) == false or  coalesce(IsCertificateValid, false) == false
| where (IsCertificateValid and coalesce(GlobalPrevalence,default_global_prevalence) < 200) or (not(coalesce(IsCertificateValid, false)) and coalesce(GlobalPrevalence,default_global_prevalence) < 500));

Stage 1: source

let suspiciousProcs

Stage 2: source

DeviceImageLoadEvents

Stage 3: where

where ...

Stage 4: extend

extend InitiatingProcessSHA1

Stage 5: where

where FileName startswith "System.Management.Automation"

Stage 6: join

join kind=inner (suspiciousProcs) on InitiatingProcessSHA1

Stage 7: summarize aggregation inside the join branch

summarize by InitiatingProcessSHA1

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
ProfileAvailabilityeqErrorexcludes:ProfileAvailability field:"ProfileAvailability" value:"Error"

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
FileNameeq
  • system.management.automation.dll
  • system.management.automation.ni.dll
field:"file_name" kind:eq
FileNamestarts_with
  • System.Management.Automation
field:"file_name" kind:starts_with value:"System.Management.Automation"
GlobalPrevalencelt
  • 200 transforms: coalesce_default:0
  • 500 transforms: coalesce_default:0
field:"GlobalPrevalence" kind:lt
IsCertificateValideq
  • false transforms: coalesce_default:false
field:"IsCertificateValid" kind:eq value:"false"
IsRootSignerMicrosofteq
  • false transforms: coalesce_default:false
field:"IsRootSignerMicrosoft" kind:eq value:"false"

Output fields

These fields are emitted when the rule matches.

FieldSource
InitiatingProcessSHA1summarize