Detection rules › Kusto

DCOM Lateral Movement

Status
available
Severity
medium
Time window
1h
Group by
DCOMCmdLine, DCOMHostPID, DCOMPID, DeviceId, DeviceName, InitiatingProcessId, InitiatingProcessParentId, LastBootTime, LocalIP, RemoteIP, TimestampNetwEventExact, TimestampProcEventExact
Source
github.com/Azure/Azure-Sentinel

This detection looks for cases of close-time proximity between incoming network traffic on RPC/TCP, followed by the creation of a DCOM object, followed by the creation of a child process of the DCOM object. The query first identifies incoming network traffic over RPC/TCP, followed by the creation of a DCOM object (process) within 2 seconds, followed by the creation of a child process of this DCOM object.

MITRE ATT&CK coverage

Telemetry coverage

Rule body

id: d58035ff-0bac-4c61-a7f4-f58939ff9764
name: DCOM Lateral Movement
description: |
  This detection looks for cases of close-time proximity between incoming network traffic on RPC/TCP, followed by the creation of a DCOM object, followed by the creation of a child process of the DCOM object. 
  The query first identifies incoming network traffic over RPC/TCP, followed by the creation of a DCOM object (process) within 2 seconds, followed by the creation of a child process of this DCOM object. 
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - DeviceProcessEvents
      - DeviceNetworkEvents
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - LateralMovement
relevantTechniques:
  - T1021.003
query: |
  let allowListedExecs = dynamic(["TiWorker.exe", "dllhost.exe", "backgroundTaskHost.exe", "mobsync.exe", "WmiPrvSE.exe", "RuntimeBroker.exe", "smartscreen.exe", "SppExtComObj.Exe", "usocoreworker.exe", "browser_broker.exe", "ssoncom.exe"]);
  let rpcNetwEvents = materialize(DeviceNetworkEvents
  | where InitiatingProcessFileName =~ "svchost.exe" and InitiatingProcessCommandLine has "rpcss" and LocalPort == 135
  | where LocalIP !~ RemoteIP and ActionType != "ListeningConnectionCreated"
  | project TimestampNetwEvent=bin(Timestamp, 5s),TimestampNetwEventExact=Timestamp, DeviceId, DeviceName, RPCHostID=InitiatingProcessId, RPCHostFileName=InitiatingProcessFileName, LocalIP, RemoteIP);
  let dcomProcEvents = materialize (DeviceProcessEvents
  | where InitiatingProcessFileName =~ "svchost.exe" and InitiatingProcessCommandLine has "dcomlaunch"
  | project TimestampProcEvent=bin(Timestamp, 5s),TimestampProcEventExact=Timestamp, DeviceId, DeviceName, DCOMHostPID=InitiatingProcessId, DCOMHostFileName=InitiatingProcessFileName, DCOMPID=ProcessId, DCOMFileName=FileName, DCOMCmdLine=ProcessCommandLine);
  let lastBootTime = materialize(DeviceProcessEvents
  | where FileName =~ "services.exe"
  | summarize LastBootTime=max(Timestamp) by DeviceId);
  let RemoteDcomProcs = materialize(rpcNetwEvents
  | join kind=inner dcomProcEvents on DeviceId 
  | join kind=leftouter lastBootTime on DeviceId
  | where TimestampProcEvent > LastBootTime+5m // Ignore first 5 min after boot.
  // Avoiding < 2 since if the time between netw and proc creation is negative, they can't be related. Network event must come first. 
  | where datetime_diff("second", TimestampProcEventExact, TimestampNetwEventExact) between (0 .. 2) 
  // Allow-listing some usual suspects which create lot of noise. This is dangerous though...huge gap for bypass. 
  | where DCOMFileName  !in~ (allowListedExecs));
  RemoteDcomProcs
  | join kind=inner hint.strategy=broadcast (
      DeviceProcessEvents 
      | where InitiatingProcessParentFileName =~ "svchost.exe" and InitiatingProcessFileName in ((RemoteDcomProcs | project DCOMFileName))) 
  on $left.DCOMHostPID == $right.InitiatingProcessParentId, DeviceId, $left.DCOMPID == $right.InitiatingProcessId
  | where InitiatingProcessParentFileName =~ "svchost.exe" and InitiatingProcessFileName =~ DCOMFileName 
  // Allow-listing the magic of Defender.
  | where FileName !in~ ("csc.exe") 
  | summarize make_set(ProcessCommandLine) by TimestampNetwEventExact, TimestampProcEventExact, DeviceId, DeviceName, InitiatingProcessId, LocalIP, RemoteIP, LastBootTime, DCOMCmdLine
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: DeviceName
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: RemoteIP 
  - entityType: Process
    fieldMappings:
      - identifier: CommandLine
        columnName: DCOMCmdLine
version: 1.0.0
kind: Scheduled

Stages and Predicates

let rpcNetwEvents and let RemoteDcomProcs are inlined into the numbered stages below.

Let binding: allowListedExecs

let allowListedExecs = dynamic(["TiWorker.exe", "dllhost.exe", "backgroundTaskHost.exe", "mobsync.exe", "WmiPrvSE.exe", "RuntimeBroker.exe", "smartscreen.exe", "SppExtComObj.Exe", "usocoreworker.exe", "browser_broker.exe", "ssoncom.exe"]);

Let binding: dcomProcEvents used in Stages 2, 9

let dcomProcEvents = materialize (DeviceProcessEvents
| where InitiatingProcessFileName =~ "svchost.exe" and InitiatingProcessCommandLine has "dcomlaunch"
| project TimestampProcEvent=bin(Timestamp, 5s),TimestampProcEventExact=Timestamp, DeviceId, DeviceName, DCOMHostPID=InitiatingProcessId, DCOMHostFileName=InitiatingProcessFileName, DCOMPID=ProcessId, DCOMFileName=FileName, DCOMCmdLine=ProcessCommandLine);

Let binding: lastBootTime used in Stages 3, 10

let lastBootTime = materialize(DeviceProcessEvents
| where FileName =~ "services.exe"
| summarize LastBootTime=max(Timestamp) by DeviceId);

Stage 1: source

let rpcNetwEvents

Stage 2: source

let dcomProcEvents

Stage 3: source

let lastBootTime

Stage 4: source

let RemoteDcomProcs

Stage 5: source

DeviceNetworkEvents

Stage 6: where

where InitiatingProcessCommandLine contains "rpcss" and InitiatingProcessFileName =~ "svchost.exe" and LocalPort == 135

Stage 7: where

where ActionType !~ "ListeningConnectionCreated" and LocalIP != RemoteIP

Stage 8: project

project DeviceId, DeviceName, LocalIP, RPCHostFileName, RPCHostID, RemoteIP, TimestampNetwEvent, TimestampNetwEventExact

Stage 9: join

join kind=inner (dcomProcEvents) on DeviceId

Stage 10: join

join kind=leftouter (lastBootTime) on DeviceId

Stage 11: where TimestampProcEvent - LastBootTime > 5m

where TimestampProcEvent - LastBootTime > 5m

Stage 12: where TimestampProcEventExact - TimestampNetwEventExact >= 0s AND TimestampProcEventExact - TimestampNetwEventExact <= 2s

where TimestampProcEventExact - TimestampNetwEventExact >= 0s AND TimestampProcEventExact - TimestampNetwEventExact <= 2s

Stage 13: where

where not (DCOMFileName in~ ("RuntimeBroker.exe", "SppExtComObj.Exe", "TiWorker.exe", "WmiPrvSE.exe", "backgroundTaskHost.exe", "browser_broker.exe", "dllhost.exe", "mobsync.exe", "smartscreen.exe", "ssoncom.exe", "usocoreworker.exe"))

Stage 14: join

join kind=inner (DeviceProcessEvents) on DCOMHostPID, InitiatingProcessParentId, DeviceId, DCOMPID, InitiatingProcessId

Stage 15: where

where InitiatingProcessFileName =~ DCOMFileName and InitiatingProcessParentFileName =~ "svchost.exe"

Stage 16: where

where not (FileName =~ "csc.exe")

Stage 17: summarize

summarize by TimestampNetwEventExact, TimestampProcEventExact, DeviceId, DeviceName, InitiatingProcessId, LocalIP, RemoteIP, LastBootTime, DCOMCmdLine

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
DCOMFileNameinRuntimeBroker.exe, SppExtComObj.Exe, TiWorker.exe, WmiPrvSE.exe, backgroundTaskHost.exe, browser_broker.exe, dllhost.exe, mobsync.exe, smartscreen.exe, ssoncom.exe, usocoreworker.exeexcludes:DCOMFileName
FileNameeqcsc.exeexcludes:FileName field:"FileName" value:"csc.exe"

Indicators

These rows show field, operator, and value matches.

Output fields

These fields are emitted when the rule matches.

FieldSource
DCOMCmdLinesummarize
DeviceIdsummarize
DeviceNamesummarize
InitiatingProcessIdsummarize
LastBootTimesummarize
LocalIPsummarize
RemoteIPsummarize
TimestampNetwEventExactsummarize
TimestampProcEventExactsummarize