Detection rules › Kusto
Persistence Via Scheduled Tasks
This query identifies binaries that run as a scheduled task, by looking at the parent process command line. Of the identified binaries running as scheduled tasks it finds suspicious binaries by looking at the file signature and global prevalence.
Known false positives
- Some legitimate software also uses scheduled tasks, for example, for downloading periodic updates. If the software is unsigned or has a low global prevalence this might cause false positives.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Execution | |
| Persistence | |
| Privilege Escalation |
References
Telemetry coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | DeviceProcessEvents action ProcessCreated: Process created |
Rule body
let timeframe = 2*1d;
let default_global_prevalence = 0;
// Time to look back for same scheduled binary.
let lookback= 7d;
let ScheduledBinaries = (
DeviceProcessEvents
| where Timestamp >= ago(lookback)
| where ActionType =~ "ProcessCreated"
| where InitiatingProcessCommandLine startswith "svchost.exe -k netsvcs -p" and InitiatingProcessCommandLine contains "Schedule" // First argument after -p is censored with ** so can't look for the actual command line
);
let NewScheduledBinaries=(
ScheduledBinaries
| where Timestamp >= ago(lookback)
| summarize FirstSeen=min(Timestamp),LastSeen=max(Timestamp) by DeviceId, SHA1
| where LastSeen >= ago(timeframe)
| where FirstSeen >= ago(timeframe)
);
let NewScheduledBinaryExecution=(
ScheduledBinaries
| where ingestion_time() >= ago(timeframe)
| lookup kind=inner NewScheduledBinaries on DeviceId, SHA1
);
NewScheduledBinaryExecution
| summarize MachineCount=dcount(DeviceId) by SHA1
// Find the max 1000 least used binaries.
| top 1000 by MachineCount asc
// FileProfile is case-sensitive and works on lower-case hashes.
| extend SHA1=tolower(SHA1)
| invoke FileProfile(SHA1,1000)
| where not(ProfileAvailability =~ "Error")
| where coalesce(GlobalPrevalence,default_global_prevalence) < 100
| join NewScheduledBinaryExecution on SHA1
| summarize arg_max(Timestamp, *), Devices=make_set(DeviceName), MachineCount=dcount(DeviceName) by SHA1 // Gives the last execution with all details per SHA1.
// Begin environment-specific filter.
// End environment-specific filter.
Stages and Predicates
Parameters
let timeframe = 2*1d;
let default_global_prevalence = 0;
let lookback = 7d;
let ScheduledBinaries and let NewScheduledBinaryExecution are inlined into the numbered stages below.
Let binding: NewScheduledBinaries
let NewScheduledBinaries = (
ScheduledBinaries
| where Timestamp >= ago(lookback)
| summarize FirstSeen=min(Timestamp),LastSeen=max(Timestamp) by DeviceId, SHA1
| where LastSeen >= ago(timeframe)
| where FirstSeen >= ago(timeframe)
);
Stage 1: source
let ScheduledBinaries
Stage 2: source
let NewScheduledBinaries
Stage 3: source
let NewScheduledBinaryExecution
Stage 4: source
DeviceProcessEvents
Stage 5: where
where Timestamp >= ago(604800s)
Stage 6: where
where ActionType =~ "ProcessCreated"
Stage 7: where
where InitiatingProcessCommandLine contains "Schedule" and InitiatingProcessCommandLine startswith "svchost.exe -k netsvcs -p"
Stage 8: where
where ...
Stage 9: kusto:lookup
lookup kind=inner (NewScheduledBinaries) on DeviceId, SHA1
Stage 10: summarize
summarize MachineCount by SHA1
Stage 11: top
top
Stage 12: extend
extend SHA1
Stage 13: invoke
invoke
Stage 14: where
where not (ProfileAvailability =~ "Error")
Stage 15: where
where GlobalPrevalence < 100
Stage 16: join
join (NewScheduledBinaryExecution) on SHA1
Stage 17: summarize
summarize Devices, MachineCount by SHA1
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
ProfileAvailability | eq | Error | excludes:ProfileAvailability field:"ProfileAvailability" value:"Error" |
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | eq |
| field:"ActionType" kind:eq value:"ProcessCreated" |
GlobalPrevalence | lt |
| field:"GlobalPrevalence" kind:lt value:"100" |
InitiatingProcessCommandLine | contains |
| field:"ParentCommandLine" kind:contains value:"Schedule" |
InitiatingProcessCommandLine | starts_with |
| field:"ParentCommandLine" kind:starts_with value:"svchost.exe -k netsvcs -p" |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Devices | summarize |
MachineCount | summarize |
SHA1 | summarize |