Detection rules › Kusto
Suspicious use of CPL file
This query identifies .cpl files being loaded and verifies if the corresponding file is suspicious by looking at the signature and global prevalence.
Known false positives
- Legitimate custom software might create a control panel item that is unsigned or has a low global prevalence.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Stealth |
References
Telemetry coverage
Rule body
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
Stage 0: let
let timeframe = 2*1h;
let default_global_prevalence = 0;
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;
let loadedDlls = DeviceImageLoadEvents <inlined as stages below>;
Stage 1: source
let suspiciousCPLs
Stage 2: source
let loadedDlls
Stage 3: source
DeviceImageLoadEvents
Stage 4: where
where ...
Stage 5: extend
extend SHA1
Stage 6: where
where ActionType =~ "ImageLoaded"
Stage 7: join
join kind=leftouter (suspiciousCPLs) on SHA1
Stage 8: summarize aggregation inside the join branch
summarize by SHA1
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | eq |
| field:"ActionType" kind:eq value:"ImageLoaded" |
FileName | ends_with |
| field:"file_name" kind:ends_with value:".cpl" |
GlobalPrevalence | lt |
| field:"GlobalPrevalence" kind:lt |
Signer | is_null | field:"Signer" kind:is_null |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
ProfileAvailability | eq | Error | excludes:ProfileAvailability field:"ProfileAvailability" value:"Error" |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
SHA1 | summarize |