Detection rules › Kusto
Process Injection From Untrusted Process
This query searches for processes performing remote process injection via multiple API calls related to process injection. It filters out programs that inject into their own process or into a process from the same directory. It then finds suspicious processes based on the global prevalence.
Known false positives
- Some legitimate software uses process injection, for example, when performing debugging.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Execution | |
| Privilege Escalation | |
| Stealth |
References
Telemetry coverage
Rule body
let timeframe = 2*1h;
let default_global_prevalence = 0;
let AllProcessInjectionEvents = materialize(
DeviceEvents
| where ingestion_time() >= ago(timeframe)
| where ActionType in~ ("QueueUserApcRemoteApiCall","NtAllocateVirtualMemoryRemoteApiCall", "CreateRemoteThreadApiCall", "SetThreadContextRemoteApiCall", "NtMapViewOfSectionRemoteApiCall") and ProcessId != InitiatingProcessId
| extend InitiatingProcessSHA1=tolower(InitiatingProcessSHA1)
| where not(InitiatingProcessFolderPath startswith FolderPath) // Exclude injection into processes in the same directory.
);
let SuspiciousProcessInjectionEvents = (
AllProcessInjectionEvents
| where not(isempty(InitiatingProcessSHA1)) // Only with a valid SHA1.
| summarize MachineCount=dcount(DeviceId) by InitiatingProcessSHA1
// Take 1000 of the most unique hashes, as files with high prevalence are very likely to be legitimately signed.
| top 1000 by MachineCount asc
| invoke FileProfile(InitiatingProcessSHA1, 1000)
| where not(ProfileAvailability =~ "Error")
| where coalesce(GlobalPrevalence,default_global_prevalence) < 200 or ((isempty(Signer) or not(IsCertificateValid)) and coalesce(GlobalPrevalence,default_global_prevalence) < 500)
);
AllProcessInjectionEvents
| lookup kind=inner SuspiciousProcessInjectionEvents on InitiatingProcessSHA1
// Work around the Defender limitation where FolderPath for CreateRemoteThreadApiCall does not contain FileName where it does for other events.
| extend InjectionTarget=strcat(FolderPath,@"\",FileName)
// Begin environment-specific filter.
// End environment-specific filter.
| summarize arg_min(Timestamp, *), InjectionTargets=make_set(InjectionTarget) by DeviceId, InitiatingProcessFolderPath // Show only the first invocation per device.
| extend InjectionSource=InitiatingProcessFolderPath, InjectionCommandLine=InitiatingProcessCommandLine
| project-reorder Timestamp, InjectionSource, InjectionCommandLine, InjectionTargets
Stages and Predicates
Parameters
let timeframe = 2*1h;
let default_global_prevalence = 0;
let AllProcessInjectionEvents is inlined into the numbered stages below.
Let binding: SuspiciousProcessInjectionEvents
let SuspiciousProcessInjectionEvents = (
AllProcessInjectionEvents
| where not(isempty(InitiatingProcessSHA1))
| summarize MachineCount=dcount(DeviceId) by InitiatingProcessSHA1
| top 1000 by MachineCount asc
| invoke FileProfile(InitiatingProcessSHA1, 1000)
| where not(ProfileAvailability =~ "Error")
| where coalesce(GlobalPrevalence,default_global_prevalence) < 200 or ((isempty(Signer) or not(IsCertificateValid)) and coalesce(GlobalPrevalence,default_global_prevalence) < 500)
);
Stage 1: source
let AllProcessInjectionEvents
Stage 2: source
let SuspiciousProcessInjectionEvents
Stage 3: source
DeviceEvents
Stage 4: where
where ...
Stage 5: where
where ActionType in~ ("CreateRemoteThreadApiCall", "NtAllocateVirtualMemoryRemoteApiCall", "NtMapViewOfSectionRemoteApiCall", "QueueUserApcRemoteApiCall", "SetThreadContextRemoteApiCall") and ProcessId != InitiatingProcessId
Stage 6: extend
extend InitiatingProcessSHA1
Stage 7: where
where not (InitiatingProcessFolderPath startswith "FolderPath")
Stage 8: kusto:lookup
lookup kind=inner (SuspiciousProcessInjectionEvents) on InitiatingProcessSHA1
Stage 9: extend
extend InjectionTarget
Stage 10: summarize
summarize InjectionTargets by DeviceId, InitiatingProcessFolderPath
Stage 11: extend
extend InjectionCommandLine, InjectionSource
Stage 12: project-reorder
project-reorder
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
InitiatingProcessFolderPath | starts_with | FolderPath | excludes:InitiatingProcessFolderPath field:"InitiatingProcessFolderPath" value:"FolderPath" |
InitiatingProcessSHA1 | is_null | excludes:InitiatingProcessSHA1 | |
ProfileAvailability | eq | Error | excludes:ProfileAvailability field:"ProfileAvailability" value:"Error" |
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | in |
| field:"ActionType" kind:in |
GlobalPrevalence | lt |
| field:"GlobalPrevalence" kind:lt |
ProcessId | cross_field_compare |
| field:"process_id" kind:cross_field_compare value:"InitiatingProcessId" |
Signer | is_null | field:"Signer" kind:is_null |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DeviceId | summarize |
InitiatingProcessFolderPath | summarize |
InjectionTargets | summarize |
InjectionCommandLine | extend |
InjectionSource | extend |