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 | T1053.005 Scheduled Task/Job: Scheduled Task |
| Persistence | T1053.005 Scheduled Task/Job: Scheduled Task |
| Privilege Escalation | T1053.005 Scheduled Task/Job: Scheduled Task |
References
Event coverage
| Provider | Event/ActionType | Title |
|---|---|---|
| Sysmon | Event ID 1 | Process creation |
| Security-Auditing | Event ID 4688 | A new process has been created. |
| Defender-DeviceProcessEvents | any | Process activity (any) |
Rule body yaml
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)
References rareScheduledTaskRegistrations (defined above).
Stage 6: invoke
| invoke FileProfile(InitiatingProcessSHA256, 1000)
Stage 7: where
| where not(GlobalPrevalence > 1000 and tolower(InitiatingProcessFileName) !in (lolbins))
References lolbins (defined above).
Exclusions
Top-level NOT(...) conjuncts: predicates this rule actively suppresses.
| Field | Kind | Excluded values |
|---|---|---|
InitiatingProcessFileName | eq | lolbins |
GlobalPrevalence | gt | 1000 |
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.
| Field | Kind | Values |
|---|---|---|
InitiatingProcessFileName | in |
|
ProcessCommandLine | in |
|
ProcessCommandLine | match |
|
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.
| Field | Source |
|---|---|
ProcessCommandLine | extend |