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
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.
| Field | Kind | Excluded values |
|---|---|---|
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 |
|