Detection rules › Kusto

COM Event System Loading New DLL

Severity
medium
Time window
1d
Group by
Image, ParentImage
Author
Shain
Source
github.com/Azure/Azure-Sentinel

This query uses Sysmon Image Load (Event ID 7) and Process Create (Event ID 1) data to look for COM Event System being used to load a newly seen DLL.

MITRE ATT&CK coverage

TacticTechniques
Privilege Escalation

Telemetry coverage

Rule body

id: 02f6c2e5-219d-4426-a0bf-ad67abc63d53
name: COM Event System Loading New DLL
description: |
  'This query uses Sysmon Image Load (Event ID 7) and Process Create (Event ID 1) data to look for COM Event System being used to load a newly seen DLL.'
severity: Medium
requiredDataConnectors:
  - connectorId: SecurityEvents
    dataTypes:
      - SecurityEvents
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - PrivilegeEscalation
relevantTechniques:
  - T1543
query: |
  let lookback_start = 7d;
  let lookback_end = 1d;
  let timedelta = 5s;
  // Get a list of previously seen DLLs being loaded
  let known_dlls = (Event
  | where TimeGenerated between(ago(lookback_start)..ago(lookback_end))
  | where EventID == 7
  | extend EvData = parse_xml(EventData)
  | extend EventDetail = EvData.DataItem.EventData.Data
  | extend LoadedItems = parse_json(tostring(parse_json(tostring(EvData.DataItem)).EventData)).["Data"]
  | mv-expand LoadedItems
  | where tostring(LoadedItems.["@Name"]) =~ "ImageLoaded"
  | extend DLL = tostring(LoadedItems.["#text"])
  | summarize by DLL);
  // Get Image Load events related to svchost.exe
  Event
  | where Source =~ "Microsoft-Windows-Sysmon"
  // Image Load Event in Sysmon
  | where EventID == 7
  | extend EvData = parse_xml(EventData)
  | extend EventDetail = EvData.DataItem.EventData.Data
  | extend Images = parse_json(tostring(parse_json(tostring(EvData.DataItem)).EventData)).["Data"]
  | mv-expand Images
  // Parse out executing process
  | where tostring(Images.["@Name"]) =~ "Image"
  | extend Image = tostring(Images.["#text"])
  | where Image endswith "\\svchost.exe"
  // Parse out loaded DLLs
  | extend LoadedItems = parse_json(tostring(parse_json(tostring(EvData.DataItem)).EventData)).["Data"]
  | mv-expand LoadedItems
  | where tostring(LoadedItems.["@Name"]) =~ "ImageLoaded"
  | extend DLL = tostring(LoadedItems.["#text"])
  | extend Image = tostring(Image)
  | extend ImageLoadTime = TimeGenerated
  // Join with processes with a command line related to COM Event System
  | join kind = inner(Event
  | where Source =~ "Microsoft-Windows-Sysmon"
  // Sysmon process execution events
  | where EventID == 1
  | extend RenderedDescription = tostring(split(RenderedDescription, ":")[0])
  | extend EventData = parse_xml(EventData).DataItem.EventData.Data
  | mv-expand bagexpansion=array EventData
  | evaluate bag_unpack(EventData)
  | extend Key = tostring(column_ifexists('@Name', "")), Value = column_ifexists('#text', "")
  | evaluate pivot(Key, any(Value), TimeGenerated, Source, EventLog, Computer, EventLevel, EventLevelName, EventID, UserName, RenderedDescription, MG, ManagementGroupName, Type, _ResourceId)
  | extend ParentImage = tostring(column_ifexists("ParentImage", "NotAvailable"))
  // Command line related to COM Event System
  | where ParentImage endswith "\\svchost.exe"
  //| where ParentCommandLine has_all (" -k LocalService"," -p"," -s EventSystem")
  | extend ProcessExecutionTime = TimeGenerated) on $left.Image == $right.ParentImage
  // Check timespan between DLL load and process creation
  | extend delta =  ProcessExecutionTime - ImageLoadTime
  | where ImageLoadTime <= ProcessExecutionTime and delta <= timedelta
  // Filter to only newly seen DLLs
  | where DLL !in (known_dlls)
  | extend ParentCommandLine = tostring(column_ifexists("ParentCommandLine", "NotAvailable"))
  | project-reorder ImageLoadTime, ProcessExecutionTime , Image, ParentCommandLine, DLL
  | extend Hashes = tostring(column_ifexists("Hashes", "NotAvailable, NotAvailable"))
  | extend Hashes = split(Hashes, ",")
  | mv-apply Hashes on (summarize FileHashes = make_bag(pack(tostring(split(Hashes, "=")[0]), tostring(split(Hashes, "=")[1]))))
  | extend SHA1 = tostring(FileHashes.SHA1)
  | extend HashAlgo = "SHA1"
  | extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
  | extend Name = tostring(split(UserName, "\\")[1]), NTDomain = tostring(split(UserName, "\\")[0])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: UserName
      - identifier: Name
        columnName: Name
      - identifier: NTDomain
        columnName: NTDomain
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Computer
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: HostNameDomain
  - entityType: FileHash
    fieldMappings:
      - identifier: Value
        columnName: SHA1
      - identifier: Algorithm
        columnName: HashAlgo
version: 1.0.4
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Shain
    support:
        tier: Community
    categories:
        domains: [ "Security - Others" ]

Stages and Predicates

Parameters

let lookback_start = 7d;
let lookback_end = 1d;
let timedelta = 5s;

Let binding: known_dlls used in Stage 16

let known_dlls = (Event
| where TimeGenerated between(ago(lookback_start)..ago(lookback_end))
| where EventID == 7
| extend EvData = parse_xml(EventData)
| extend EventDetail = EvData.DataItem.EventData.Data
| extend LoadedItems = parse_json(tostring(parse_json(tostring(EvData.DataItem)).EventData)).["Data"]
| mv-expand LoadedItems
| where tostring(LoadedItems.["@Name"]) =~ "ImageLoaded"
| extend DLL = tostring(LoadedItems.["#text"])
| summarize by DLL);

Stage 1: source

Event

Stage 2: where

where Source =~ "Microsoft-Windows-Sysmon"

Stage 3: where

where EventID == 7

Stage 4: extend (3 consecutive steps)

extend EvData, EventDetail, Images

Stage 5: mv-expand

mv-expand Images

Stage 6: where

where @Name =~ "Image"

Stage 7: extend

extend Image

Stage 8: where

where Image endswith @"\svchost.exe"

Stage 9: extend

extend LoadedItems

Stage 10: mv-expand

mv-expand LoadedItems

Stage 11: where

where @Name =~ "ImageLoaded"

Stage 12: extend (3 consecutive steps)

extend DLL, Image, ImageLoadTime

Stage 13: join

join kind=inner (Event) on Image, ParentImage

Stage 14: extend

extend delta

Stage 15: where

where ImageLoadTime <= ProcessExecutionTime and delta <= "5s"

Stage 16: where

where not (DLL =~ "known_dlls")

Stage 17: extend

extend ParentCommandLine

Stage 18: project-reorder

project-reorder

Stage 19: extend

extend Hashes

Stage 20: extend

extend Hashes

Stage 21: kusto:mv-apply

kusto:mv-apply

Stage 22: extend (5 consecutive steps)

extend DomainIndex, HashAlgo, HostName, HostNameDomain, NTDomain, Name, SHA1

Stage 23: summarize aggregation inside the mv-apply branch

summarize

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
DLLeqknown_dllsexcludes:DLL field:"DLL" value:"known_dlls"

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
@Nameeq
  • Image transforms: tostring
  • ImageLoaded transforms: tostring
field:"@Name" kind:eq
EventIDeq
  • 1 corpus 241 (splunk 225, kusto 15, elastic 1)
  • 7 corpus 41 (splunk 39, elastic 1, kusto 1)
field:"EventID" kind:eq
Imageends_with
  • \svchost.exe corpus 23 (sigma 22, kusto 1)
field:"Image" kind:ends_with value:"\svchost.exe"
ImageLoadTimecross_field_compare
  • ProcessExecutionTime transforms: op:le
field:"ImageLoadTime" kind:cross_field_compare value:"ProcessExecutionTime"
ParentImageends_with
  • \svchost.exe corpus 15 (sigma 14, kusto 1)
field:"ParentImage" kind:ends_with value:"\svchost.exe"
deltale
  • 5s
field:"delta" kind:le value:"5s"