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.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Execution | T1106 Native API |
| Privilege Escalation | T1055.002 Process Injection: Portable Executable Injection |
| Stealth | T1055.002 Process Injection: Portable Executable Injection |
References
Event coverage
| Provider | Event/ActionType | Title |
|---|---|---|
| Sysmon | Event ID 8 | CreateRemoteThread |
| Defender-DeviceEvents | CreateRemoteThreadApiCall | CreateRemoteThread API call |
| Defender-DeviceEvents | NtAllocateVirtualMemoryRemoteApiCall | Remote virtual memory allocation (NtAllocateVirtualMemory) |
| Defender-DeviceEvents | NtMapViewOfSectionRemoteApiCall | Remote section map (NtMapViewOfSection) |
| Defender-DeviceEvents | QueueUserApcRemoteApiCall | Remote APC queued (QueueUserApc) |
| Defender-DeviceEvents | SetThreadContextRemoteApiCall | Remote thread context change (SetThreadContext) |
| Threat-Intelligence | Event ID 1 | Remote Virtual Memory Allocation |
| Threat-Intelligence | Event ID 3 | Remote Section Map |
| Threat-Intelligence | Event ID 4 | Remote APC Queue |
| Threat-Intelligence | Event ID 5 | Remote Thread Context Change |
Rule body kusto
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 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)
);
Derived from default_global_prevalence, AllProcessInjectionEvents.
The stages below define let AllProcessInjectionEvents (the rule's main pipeline source).
Stage 1: source
DeviceEvents
Stage 2: where
| where ingestion_time() >= ago(timeframe)
Stage 3: where
| where ActionType in~ ("QueueUserApcRemoteApiCall","NtAllocateVirtualMemoryRemoteApiCall", "CreateRemoteThreadApiCall", "SetThreadContextRemoteApiCall", "NtMapViewOfSectionRemoteApiCall") and ProcessId != InitiatingProcessId
Stage 4: extend
| extend InitiatingProcessSHA1=tolower(InitiatingProcessSHA1)
Stage 5: where
| where not(InitiatingProcessFolderPath startswith FolderPath)
The stages below run on AllProcessInjectionEvents (the outer pipeline).
Stage 6: kusto:lookup
AllProcessInjectionEvents
| lookup kind=inner SuspiciousProcessInjectionEvents on InitiatingProcessSHA1
Stage 7: extend
| extend InjectionTarget=strcat(FolderPath,@"\",FileName)
Stage 8: summarize
| summarize arg_min(Timestamp, *), InjectionTargets=make_set(InjectionTarget) by DeviceId, InitiatingProcessFolderPath
Stage 9: extend
| extend InjectionSource=InitiatingProcessFolderPath, InjectionCommandLine=InitiatingProcessCommandLine
Stage 10: project-reorder
| project-reorder Timestamp, InjectionSource, InjectionCommandLine, InjectionTargets
Exclusions
Top-level NOT(...) conjuncts: predicates this rule actively suppresses.
| Field | Kind | Excluded values |
|---|---|---|
InitiatingProcessFolderPath | starts_with | FolderPath |
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 |
|---|---|---|
ActionType | in |
|
ProcessId | ne |
|
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 |
|---|---|
DeviceId | summarize |
InitiatingProcessFolderPath | summarize |
InjectionTargets | summarize |
InjectionCommandLine | extend |
InjectionSource | extend |