Detection rules › Kusto

Detect Rare scheduled task created

Author
Robbe Van den Daele
Source
github.com/HybridBrothers/Hunting-Queries-Detection-Rules

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

References

Event coverage

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.

FieldKindExcluded values
InitiatingProcessFileNameeqlolbins
GlobalPrevalencegt1000

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
InitiatingProcessFileNamein
  • lolbins transforms: tolower, cased
ProcessCommandLinein
  • rareScheduledTaskRegistrations transforms: cased
ProcessCommandLinematch
  • /create corpus 9 (sigma 6, splunk 2, kusto 1)
  • schtasks corpus 6 (sigma 5, kusto 1)

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
ProcessCommandLineextend