Detection rules › Kusto

Hijack Execution Flow - DLL Side-Loading

Status
available
Severity
medium
Time window
1h
Source
github.com/Azure/Azure-Sentinel

This detection tries to identify all DLLs loaded by "high integrity" processes and cross-checks the DLL paths against FileCreate/FileModify events of the same DLL by a medium integrity process. Of course, we need to do some magic to filter out false positives as much as possible. So any FileCreate/FileModify done by "NT Authoriy\System" and the "RID 500" users aren't interesting. Also, we only want to see the FileCreate/FileModify actions which are performed with a default or limited token elevation. If done with a full elevated token, the user is apparently admin already.

MITRE ATT&CK coverage

Event coverage

Rule body kusto

id: 3084b487-fad6-4000-9544-6085b9657290
name: Hijack Execution Flow - DLL Side-Loading
description: |
  This detection tries to identify all DLLs loaded by "high integrity" processes and cross-checks the DLL paths against FileCreate/FileModify events of the same DLL by a medium integrity process.
  Of course, we need to do some magic to filter out false positives as much as possible. So any FileCreate/FileModify done by "NT Authoriy\System" and the "RID 500" users aren't interesting.
  Also, we only want to see the FileCreate/FileModify actions which are performed with a default or limited token elevation. If done with a full elevated token, the user is apparently admin already.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - DeviceFileEvents
      - DeviceImageLoadEvents
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
  - PrivilegeEscalation
  - DefenseEvasion
relevantTechniques:
  - T1574.002
query: |
  let imls = materialize(
      DeviceImageLoadEvents
      | where InitiatingProcessIntegrityLevel in ("High", "System") and FileName !endswith ".exe"
      | project FolderPath=tolower(FolderPath), InitiatingProcessFileName, InitiatingProcessIntegrityLevel, DeviceId, DeviceName
      | distinct FolderPath, InitiatingProcessFileName, InitiatingProcessIntegrityLevel, DeviceId, DeviceName
  );
  imls
  | join (
      DeviceFileEvents
      | where FolderPath in~ ((imls | project FolderPath)) and ActionType in ("FileCreated", "FileModified") and
      InitiatingProcessIntegrityLevel !in ("High", "System", "") and InitiatingProcessAccountSid != "S-1-5-18" and
      InitiatingProcessTokenElevation in ("TokenElevationTypeDefault", "TokenElevationTypeLimited") and InitiatingProcessAccountSid !endswith "-500"
      | extend FolderPath=tolower(FolderPath)
  ) on FolderPath, DeviceId, DeviceName
  | project-away FolderPath1
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: DeviceName
  - entityType: Account
    fieldMappings:
      - identifier: Sid
        columnName: InitiatingProcessAccountSid
      - identifier: Name
        columnName: InitiatingProcessAccountName
      - identifier: NTDomain
        columnName: InitiatingProcessAccountDomain
  - entityType: Process
    fieldMappings:
      - identifier: CommandLine
        columnName: InitiatingProcessCommandLine
version: 1.0.1
kind: Scheduled

Stages and Predicates

Stage 0: let

let imls = materialize(<inlined as stages below>);

The stages below define let imls (the rule's main pipeline source).

Stage 1: source

DeviceImageLoadEvents

Stage 2: where

| where InitiatingProcessIntegrityLevel in ("High", "System") and FileName !endswith ".exe"

Stage 3: project

| project FolderPath=tolower(FolderPath), InitiatingProcessFileName, InitiatingProcessIntegrityLevel, DeviceId, DeviceName

Stage 4: distinct

| distinct FolderPath, InitiatingProcessFileName, InitiatingProcessIntegrityLevel, DeviceId, DeviceName

The stages below run on imls (the outer pipeline).

Stage 5: join

imls
| join (
    DeviceFileEvents
    | where FolderPath in~ ((imls | project FolderPath)) and ActionType in ("FileCreated", "FileModified") and
    InitiatingProcessIntegrityLevel !in ("High", "System", "") and InitiatingProcessAccountSid != "S-1-5-18" and
    InitiatingProcessTokenElevation in ("TokenElevationTypeDefault", "TokenElevationTypeLimited") and InitiatingProcessAccountSid !endswith "-500"
    | extend FolderPath=tolower(FolderPath)
) on FolderPath, DeviceId, DeviceName

Stage 6: project-away

| project-away FolderPath1

Exclusions

Top-level NOT(...) conjuncts: predicates this rule actively suppresses.

FieldKindExcluded values
FileNameends_with.exe
InitiatingProcessAccountSidends_with-500
InitiatingProcessIntegrityLevelinHigh, System

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
ActionTypein
  • FileCreated transforms: cased corpus 8 (kusto 8)
  • FileModified transforms: cased
InitiatingProcessAccountSidne
  • S-1-5-18 transforms: cased
InitiatingProcessIntegrityLevelin
  • High transforms: cased corpus 21 (sigma 17, kusto 3, splunk 1)
  • System transforms: cased corpus 29 (sigma 22, splunk 4, elastic 3)
InitiatingProcessTokenElevationin
  • TokenElevationTypeDefault transforms: cased
  • TokenElevationTypeLimited transforms: cased

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
DeviceIdproject
DeviceNameproject
FolderPathproject
InitiatingProcessFileNameproject
InitiatingProcessIntegrityLevelproject