Detection rules › Kusto

Process Injection From Untrusted Process

Group by
DeviceId, InitiatingProcessFolderPath, InitiatingProcessSHA1
Author
FalconForce
Source
github.com/FalconForceTeam/FalconFriday

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

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 used in Stages 2, 8

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.

FieldKindExcluded valuesSearch
InitiatingProcessFolderPathstarts_withFolderPathexcludes:InitiatingProcessFolderPath field:"InitiatingProcessFolderPath" value:"FolderPath"
InitiatingProcessSHA1is_null(no value, null check)excludes:InitiatingProcessSHA1
ProfileAvailabilityeqErrorexcludes:ProfileAvailability field:"ProfileAvailability" value:"Error"

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
ActionTypein
  • CreateRemoteThreadApiCall
  • NtAllocateVirtualMemoryRemoteApiCall
  • NtMapViewOfSectionRemoteApiCall
  • QueueUserApcRemoteApiCall
  • SetThreadContextRemoteApiCall
field:"ActionType" kind:in
GlobalPrevalencelt
  • 200 transforms: coalesce_default:0
  • 500 transforms: coalesce_default:0
field:"GlobalPrevalence" kind:lt
ProcessIdcross_field_compare
  • InitiatingProcessId transforms: op:ne
field:"process_id" kind:cross_field_compare value:"InitiatingProcessId"
Signeris_null
  • (no value, null check)
field:"Signer" kind:is_null

Output fields

These fields are emitted when the rule matches.

FieldSource
DeviceIdsummarize
InitiatingProcessFolderPathsummarize
InjectionTargetssummarize
InjectionCommandLineextend
InjectionSourceextend