Detection rules › Kusto
Detect Rare scheduled task created
Persistence via Scheduled Tasks is a well-known technique used by adversaries to make sure their malware programs keep running an the compromised device. With this detection rule, you can search for scheduled tasks being created by processes that did not performed this before. > [!WARNING] > This detection rule is the base for the detection. You will need to add environment specific finetuning in order to limit the BP detections on legitimate processes
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Execution | |
| Persistence | |
| Privilege Escalation |
References
Telemetry coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | DeviceProcessEvents action any: Process activity |
Rule body
let lolbins = toscalar(externaldata(FileName:string, Description:string, Author:string, Date:datetime, Command:string, CommandDescription:string, CommandUsecase:string, CommandCategory:string)
["https://lolbas-project.github.io/api/lolbas.csv"] with(format="csv", ignoreFirstRecord=true)
| extend FileName = tolower(FileName)
| summarize make_set(FileName)
);
// Setting up the rare ones.
let rareScheduledTaskRegistrations = toscalar(
DeviceProcessEvents
| where Timestamp > ago(14d)
| where ProcessCommandLine has_all ("schtasks", "/create")
| summarize count() by ProcessCommandLine
| where count_ < 5
| summarize make_set(ProcessCommandLine)
);
DeviceProcessEvents
| where Timestamp > ago(1d)
| where ProcessCommandLine has_all ("schtasks", "/create")
| extend ProcessCommandLine = replace_regex(ProcessCommandLine, @"C:\\Users\\[^\\]+", "C:\\Users\\<USERNAME>")
// Only take the ones in the last day we find rare within the organization.
| where ProcessCommandLine in (rareScheduledTaskRegistrations)
| invoke FileProfile(InitiatingProcessSHA256, 1000)
| where not(GlobalPrevalence > 1000 and tolower(InitiatingProcessFileName) !in (lolbins))
Stages and Predicates
Let binding: lolbins
let lolbins = toscalar(externaldata(FileName:string, Description:string, Author:string, Date:datetime, Command:string, CommandDescription:string, CommandUsecase:string, CommandCategory:string)
["https://lolbas-project.github.io/api/lolbas.csv"] with(format="csv", ignoreFirstRecord=true)
| extend FileName = tolower(FileName)
| summarize make_set(FileName)
);
Let binding: rareScheduledTaskRegistrations
let rareScheduledTaskRegistrations = toscalar(
DeviceProcessEvents
| where Timestamp > ago(14d)
| where ProcessCommandLine has_all ("schtasks", "/create")
| summarize count() by ProcessCommandLine
| where count_ < 5
| summarize make_set(ProcessCommandLine)
);
Stage 1: source
DeviceProcessEvents
Stage 2: where
| where Timestamp > ago(1d)
Stage 3: where
| where ProcessCommandLine has_all ("schtasks", "/create")
Stage 4: extend
| extend ProcessCommandLine = replace_regex(ProcessCommandLine, @"C:\\Users\\[^\\]+", "C:\\Users\\<USERNAME>")
Stage 5: where
| where ProcessCommandLine in (rareScheduledTaskRegistrations)
Stage 6: invoke
| invoke FileProfile(InitiatingProcessSHA256, 1000)
Stage 7: where
| where not(GlobalPrevalence > 1000 and tolower(InitiatingProcessFileName) !in (lolbins))
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
InitiatingProcessFileName | eq | lolbins | excludes:InitiatingProcessFileName field:"InitiatingProcessFileName" value:"lolbins" |
GlobalPrevalence | gt | 1000 | excludes:GlobalPrevalence field:"GlobalPrevalence" value:"1000" |
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
InitiatingProcessFileName | in |
| field:"parent_process_name" kind:in value:"lolbins" |
ProcessCommandLine | in |
| field:"CommandLine" kind:in value:"rareScheduledTaskRegistrations" |
ProcessCommandLine | match |
| field:"CommandLine" kind:match |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ProcessCommandLine | extend |