Detection rules › Kusto

Suspicious use of CPL file

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

This query identifies .cpl files being loaded and verifies if the corresponding file is suspicious by looking at the signature and global prevalence.

MITRE ATT&CK coverage

References

Event coverage

ProviderEvent/ActionTypeTitle
SysmonEvent ID 7Image loaded
Defender-DeviceImageLoadEventsImageLoadedImage loaded

Rule body kusto

let timeframe = 2*1h;
let default_global_prevalence = 0;
let suspiciousCPLs = DeviceImageLoadEvents
    | where ingestion_time() >= ago(timeframe)
    // Begin environment-specific filter.
    // End environment-specific filter.
    | where FileName endswith ".cpl"
    | summarize by SHA1
    // FileProfile is case-sensitive and works on lower-case hashes.
    | extend SHA1=tolower(SHA1)
    | invoke FileProfile(SHA1, 1000)
    | where not(ProfileAvailability =~ "Error")
    // Begin environment-specific filter.
    // End environment-specific filter.
    | where ((isempty(Signer) or not(IsCertificateValid==1)) and coalesce(GlobalPrevalence,default_global_prevalence) < 100) or coalesce(GlobalPrevalence,default_global_prevalence) < 50;
let loadedDlls=DeviceImageLoadEvents
    | where ingestion_time() >= ago(timeframe)
    // FileProfile is case-sensitive and works on lower-case hashes.
    | extend SHA1=tolower(SHA1)
    | where SHA1 in~ ((suspiciousCPLs|project SHA1)) and ActionType =~ "ImageLoaded"
    // Begin environment-specific filter.
    // End environment-specific filter.
    ;
loadedDlls
    | join kind=leftouter suspiciousCPLs on SHA1
    // Begin environment-specific filter.
    // End environment-specific filter.

Stages and Predicates

Parameters

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

Let binding: suspiciousCPLs

let suspiciousCPLs = DeviceImageLoadEvents
    | where ingestion_time() >= ago(timeframe)
    | where FileName endswith ".cpl"
    | summarize by SHA1
    | extend SHA1=tolower(SHA1)
    | invoke FileProfile(SHA1, 1000)
    | where not(ProfileAvailability =~ "Error")
    | where ((isempty(Signer) or not(IsCertificateValid==1)) and coalesce(GlobalPrevalence,default_global_prevalence) < 100) or coalesce(GlobalPrevalence,default_global_prevalence) < 50;

Derived from timeframe, default_global_prevalence.

The stages below define let loadedDlls (the rule's main pipeline source).

Stage 1: source

DeviceImageLoadEvents

Stage 2: where

| where ingestion_time() >= ago(timeframe)

Stage 3: extend

| extend SHA1=tolower(SHA1)

Stage 4: where

| where SHA1 in~ ((suspiciousCPLs|project SHA1)) and ActionType =~ "ImageLoaded"

The stages below run on loadedDlls (the outer pipeline).

Stage 5: join

loadedDlls
| join kind=leftouter suspiciousCPLs on SHA1

Stage 6: summarize

summarize by SHA1

Exclusions

Top-level NOT(...) conjuncts: predicates this rule actively suppresses.

FieldKindExcluded values
ProfileAvailabilityeqError

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
ActionTypeeq
  • ImageLoaded
FileNameends_with
  • .cpl
GlobalPrevalencelt
  • 100 transforms: coalesce_default:0 corpus 4 (kusto 4)
  • 50 transforms: coalesce_default:0
Signeris_null
  • (no value, null check)

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
SHA1summarize