Detection rules › Kusto
COM Event System Loading New DLL
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
| Tactic | Techniques |
|---|---|
| Privilege Escalation | T1543 Create or Modify System Process |
Event coverage
| Provider | Event | Title |
|---|---|---|
| Sysmon | Event ID 1 | Process creation |
| Sysmon | Event ID 7 | Image 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
Stage 0: let
let lookback_start = 7d;
let lookback_end = 1d;
let timedelta = 5s;
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 time_window=518400s
let known_dlls
Stage 2: source
Event
Stage 3: where
| where Source =~ "Microsoft-Windows-Sysmon"
Stage 4: where
| where EventID == 7
Stage 5: extend
| extend EvData = parse_xml(EventData)
Stage 6: extend
| extend EventDetail = EvData.DataItem.EventData.Data
Stage 7: extend
| extend Images = parse_json(tostring(parse_json(tostring(EvData.DataItem)).EventData)).["Data"]
Stage 8: mv-expand
| mv-expand Images
Stage 9: where
| where tostring(Images.["@Name"]) =~ "Image"
Stage 10: extend
| extend Image = tostring(Images.["#text"])
Stage 11: where
| where Image endswith "\\svchost.exe"
Stage 12: extend
| extend LoadedItems = parse_json(tostring(parse_json(tostring(EvData.DataItem)).EventData)).["Data"]
Stage 13: mv-expand
| mv-expand LoadedItems
Stage 14: where
| where tostring(LoadedItems.["@Name"]) =~ "ImageLoaded"
Stage 15: extend
| extend DLL = tostring(LoadedItems.["#text"])
Stage 16: extend
| extend Image = tostring(Image)
Stage 17: extend
| extend ImageLoadTime = TimeGenerated
Stage 18: 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 19: extend
| extend delta = ProcessExecutionTime - ImageLoadTime
Stage 20: where
| where ImageLoadTime <= ProcessExecutionTime and delta <= timedelta
Stage 21: where
| where DLL !in (known_dlls)
Stage 22: extend
| extend ParentCommandLine = tostring(column_ifexists("ParentCommandLine", "NotAvailable"))
Stage 23: project-reorder
| project-reorder ImageLoadTime, ProcessExecutionTime , Image, ParentCommandLine, DLL
Stage 24: extend
| extend Hashes = tostring(column_ifexists("Hashes", "NotAvailable, NotAvailable"))
Stage 25: extend
| extend Hashes = split(Hashes, ",")
Stage 26: kusto:mv-apply
| mv-apply Hashes on (summarize FileHashes = make_bag(pack(tostring(split(Hashes, "=")[0]), tostring(split(Hashes, "=")[1]))))
Stage 27: extend
| extend SHA1 = tostring(FileHashes.SHA1)
Stage 28: extend
| extend HashAlgo = "SHA1"
Stage 29: extend
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
Stage 30: extend
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
Stage 31: extend
| extend Name = tostring(split(UserName, "\\")[1]), NTDomain = tostring(split(UserName, "\\")[0])
Stage 32: summarize
summarize
Exclusions
Top-level NOT(...) conjuncts: predicates this rule actively suppresses.
| Stage | Field | Kind | Excluded values |
|---|---|---|---|
| 21 | DLL | eq | known_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.
| Field | Kind | Values |
|---|---|---|
@Name | eq |
|
EventID | eq |
|
Image | ends_with |
|
ImageLoadTime | le |
|
ParentImage | ends_with |
|
delta | le |
|