Detection rules › Kusto

COM Event System Loading New DLL

Severity
medium
Time window
1d
Group by
DLL, 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 EscalationT1543 Create or Modify System Process

Event coverage

ProviderEventTitle
SysmonEvent ID 1Process creation
SysmonEvent ID 7Image loaded

Rule body kusto

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

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);

Derived from lookback_start, lookback_end.

Stage 1: source

let known_dlls

Stage 2: source

Event

Stage 3: where

| where Source =~ "Microsoft-Windows-Sysmon"

Stage 4: where

| where EventID == 7

Stage 5: extend (3 consecutive steps)

| extend EvData = parse_xml(EventData)
| extend EventDetail = EvData.DataItem.EventData.Data
| extend Images = parse_json(tostring(parse_json(tostring(EvData.DataItem)).EventData)).["Data"]

Stage 6: mv-expand

| mv-expand Images

Stage 7: where

| where tostring(Images.["@Name"]) =~ "Image"

Stage 8: extend

| extend Image = tostring(Images.["#text"])

Stage 9: where

| where Image endswith "\\svchost.exe"

Stage 10: extend

| extend LoadedItems = parse_json(tostring(parse_json(tostring(EvData.DataItem)).EventData)).["Data"]

Stage 11: mv-expand

| mv-expand LoadedItems

Stage 12: where

| where tostring(LoadedItems.["@Name"]) =~ "ImageLoaded"

Stage 13: extend (3 consecutive steps)

| extend DLL = tostring(LoadedItems.["#text"])
| extend Image = tostring(Image)
| extend ImageLoadTime = TimeGenerated

Stage 14: join

| join kind = inner(Event
| where Source =~ "Microsoft-Windows-Sysmon"
| 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"))
| where ParentImage endswith "\\svchost.exe"
| extend ProcessExecutionTime = TimeGenerated) on $left.Image == $right.ParentImage

Stage 15: extend

| extend delta =  ProcessExecutionTime - ImageLoadTime

Stage 16: where

| where ImageLoadTime <= ProcessExecutionTime and delta <= timedelta

Stage 17: where

| where DLL !in (known_dlls)

References known_dlls (defined above).

Stage 18: extend

| extend ParentCommandLine = tostring(column_ifexists("ParentCommandLine", "NotAvailable"))

Stage 19: project-reorder

| project-reorder ImageLoadTime, ProcessExecutionTime , Image, ParentCommandLine, DLL

Stage 20: extend

| extend Hashes = tostring(column_ifexists("Hashes", "NotAvailable, NotAvailable"))

Stage 21: extend

| extend Hashes = split(Hashes, ",")

Stage 22: kusto:mv-apply

| mv-apply Hashes on (summarize FileHashes = make_bag(pack(tostring(split(Hashes, "=")[0]), tostring(split(Hashes, "=")[1]))))

Stage 23: extend (5 consecutive steps)

| 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])

Stage 24: summarize

summarize

Exclusions

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

FieldKindExcluded values
DLLeqknown_dlls

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
@Nameeq
  • Image transforms: tostring
  • ImageLoaded transforms: tostring
EventIDeq
  • 1 transforms: cased corpus 237 (splunk 224, kusto 13)
  • 7 transforms: cased corpus 39 (splunk 38, kusto 1)
Imageends_with
  • \\svchost.exe
ImageLoadTimele
  • ProcessExecutionTime transforms: cased
ParentImageends_with
  • \\svchost.exe
deltale
  • 5s transforms: cased