Hunting queries — UAC bypass via auto-elevated COM (T1548.002)
CLSIDs referenced throughout:
| CLSID | Object | Interface | Provider DLL |
|---|---|---|---|
{3E5FC7F9-9A51-4367-9063-A120244FBEC7} |
CMSTPLUA | ICMLuaUtil | cmlua.dll |
{D2E7041B-2927-42FB-8E9F-7CE93B6DC937} |
ColorDataProxy | ICMLuaUtil | colorui.dll |
Expected process tree when the technique fires:
svchost.exe (-k DcomLaunch)
└── dllhost.exe /Processid:{3E5FC7F9-9A51-4367-9063-A120244FBEC7} <- High integrity, no consent.exe
└── <payload>.exe / cmd.exe / powershell.exe <- inherits High integrity
The absence of consent.exe immediately before the high-integrity child is the
discriminator between this and a legitimate user-approved elevation.
Microsoft Defender for Endpoint — Advanced Hunting (KQL)
1. Surrogate launch for an auto-elevating CLSID
let elevCLSIDs = dynamic([
"3e5fc7f9-9a51-4367-9063-a120244fbec7",
"d2e7041b-2927-42fb-8e9f-7ce93b6dc937"
]);
DeviceProcessEvents
| where FileName =~ "dllhost.exe"
| extend clsid = tolower(extract(@"/Processid:\{?([0-9a-fA-F\-]{36})\}?", 1, ProcessCommandLine))
| where clsid in (elevCLSIDs)
| project Timestamp, DeviceName, AccountName, clsid, ProcessCommandLine,
ProcessIntegrityLevel, InitiatingProcessFileName, ProcessId
| order by Timestamp desc
2. Payload — elevated child of the surrogate
DeviceProcessEvents
| where InitiatingProcessFileName =~ "dllhost.exe"
| where InitiatingProcessCommandLine has_any (
"3E5FC7F9-9A51-4367-9063-A120244FBEC7",
"D2E7041B-2927-42FB-8E9F-7CE93B6DC937")
| project Timestamp, DeviceName, AccountName,
FileName, ProcessCommandLine, ProcessIntegrityLevel,
InitiatingProcessCommandLine, SHA256
| order by Timestamp desc
3. Silent elevation — high integrity with no preceding consent.exe
Strong behavioural hunt that generalises past any single CLSID.
let window = 30s;
let elevated =
DeviceProcessEvents
| where ProcessIntegrityLevel in ("High", "System")
| where InitiatingProcessIntegrityLevel == "Medium"
| project Timestamp, DeviceId, DeviceName, AccountName, FileName,
ProcessCommandLine, InitiatingProcessFileName,
InitiatingProcessCommandLine, ProcessId;
let consents =
DeviceProcessEvents
| where FileName =~ "consent.exe"
| project DeviceId, ConsentTime = Timestamp;
elevated
| join kind=leftouter consents on DeviceId
// Count the consent prompts that fall in the window for THIS process, then keep
// the processes that had none. Filtering row-by-row instead would let a process
// through on the strength of some other, unrelated consent event on the device.
| summarize ConsentInWindow = countif(ConsentTime between ((Timestamp - window) .. Timestamp))
by DeviceId, ProcessId, Timestamp, DeviceName, AccountName, FileName,
ProcessCommandLine, InitiatingProcessFileName, InitiatingProcessCommandLine
| where ConsentInWindow == 0
| project Timestamp, DeviceName, AccountName, FileName, ProcessCommandLine,
InitiatingProcessFileName, InitiatingProcessCommandLine
| order by Timestamp desc
4. Moniker string in script content
DeviceEvents
| where ActionType == "PowerShellCommand"
| where AdditionalFields has "Elevation:Administrator!new:"
| project Timestamp, DeviceName, AccountName, InitiatingProcessCommandLine, AdditionalFields
Splunk (Sysmon)
Surrogate launch + payload, correlated by parent GUID
index=windows sourcetype=XmlWinEventLog:Microsoft-Windows-Sysmon/Operational
EventCode=1
(
(Image="*\\dllhost.exe" CommandLine="*3E5FC7F9-9A51-4367-9063-A120244FBEC7*")
OR
(ParentImage="*\\dllhost.exe" ParentCommandLine="*3E5FC7F9-9A51-4367-9063-A120244FBEC7*")
)
| eval stage=if(match(Image, "dllhost\.exe$"), "surrogate_launch", "elevated_payload")
| table _time host User stage Image CommandLine ParentImage ParentCommandLine IntegrityLevel ProcessGuid ParentProcessGuid
| sort - _time
Elevated process with no consent.exe in the preceding 30 seconds
index=windows sourcetype=XmlWinEventLog:Microsoft-Windows-Sysmon/Operational EventCode=1
(IntegrityLevel="High" OR IntegrityLevel="System")
| eval is_consent=if(match(Image, "consent\.exe$"), 1, 0)
| sort 0 host _time
| streamstats time_window=30s max(is_consent) as consent_seen by host
| where is_consent=0 AND consent_seen=0
| search NOT Image IN ("*\\services.exe","*\\svchost.exe","*\\wininit.exe","*\\lsass.exe","*\\MsMpEng.exe")
| table _time host User Image CommandLine ParentImage IntegrityLevel
Elastic — EQL sequence
Ties the surrogate launch to its elevated child in one signal.
sequence by host.id with maxspan=1m
[ process where event.type == "start"
and process.name == "dllhost.exe"
and process.command_line : (
"*3E5FC7F9-9A51-4367-9063-A120244FBEC7*",
"*D2E7041B-2927-42FB-8E9F-7CE93B6DC937*") ] by process.entity_id
[ process where event.type == "start" ] by process.parent.entity_id
Windows Event Log / 4688 fallback
If Sysmon is not deployed, enable Audit Process Creation plus
Include command line in process creation events, then:
Event ID 4688
New Process Name ends with \dllhost.exe
Process Command Line contains 3E5FC7F9-9A51-4367-9063-A120244FBEC7
and the follow-on event where Creator Process Name is that dllhost.exe.
Note that 4688 does not carry integrity level — pair with 4624 logon
elevation data or accept the reduced fidelity.
Sysmon configuration — required telemetry
Minimal additions to capture everything the rules above need.
<Sysmon schemaversion="4.90">
<EventFiltering>
<!-- EID 1: keep every COM surrogate launch and everything it spawns -->
<RuleGroup name="ProcCreate-COMSurrogate" groupRelation="or">
<ProcessCreate onmatch="include">
<CommandLine condition="contains">3E5FC7F9-9A51-4367-9063-A120244FBEC7</CommandLine>
<CommandLine condition="contains">D2E7041B-2927-42FB-8E9F-7CE93B6DC937</CommandLine>
<ParentImage condition="image">dllhost.exe</ParentImage>
<Image condition="image">consent.exe</Image>
</ProcessCreate>
</RuleGroup>
<!-- EID 7: provider DLL mapping. Exclude the normal surrogate to cut volume. -->
<RuleGroup name="ImageLoad-ICMLuaUtil" groupRelation="or">
<ImageLoad onmatch="include">
<ImageLoaded condition="end with">\cmlua.dll</ImageLoaded>
<ImageLoaded condition="end with">\colorui.dll</ImageLoaded>
</ImageLoad>
<ImageLoad onmatch="exclude">
<Image condition="image">cmstp.exe</Image>
</ImageLoad>
</RuleGroup>
<!-- EID 10: token theft / handle abuse often accompanies elevation attempts -->
<RuleGroup name="ProcessAccess-Elevation" groupRelation="or">
<ProcessAccess onmatch="include">
<TargetImage condition="image">dllhost.exe</TargetImage>
<GrantedAccess condition="is">0x1F0FFF</GrantedAccess>
</ProcessAccess>
</RuleGroup>
</EventFiltering>
</Sysmon>
Also enable, via GPO or registry:
- PowerShell Script Block Logging (EID 4104) — carries the moniker string for script-based variants.
- Audit Process Creation with command line inclusion — 4688 fallback.
Tuning and validation notes
Baseline before deploying. Query 30 days of dllhost.exe command lines and
build an allow-list of CLSIDs your environment legitimately surrogates. On most
fleets the two CLSIDs above never appear, which makes rules 1 and 2 near
zero-noise. Environments that genuinely deploy Connection Manager profiles via
cmstp.exe are the main exception.
Validate with vetted tooling, not custom code. Atomic Red Team’s T1548.002 atomics are sandboxed, documented, and safe to run in a lab. Use those to confirm the rules fire rather than authoring a bypass yourself.
Expect CLSID rotation. The auto-elevate CLSID list is long and attackers swap objects freely. Rule 3 in the Sigma file and KQL query 3 are the durable detections — they key on the structure of the bypass (medium-integrity parent, high-integrity child, no consent prompt) rather than a specific GUID. Weight your alerting toward those and treat the CLSID lists as fast-but-brittle signatures.
Blind spot to be aware of. If the payload is loaded in-process rather than
executed via ShellExec — for example by writing a registry value through
ICMLuaUtil::SetRegistryStringValue and waiting for another process to consume it —
there is no elevated child process to catch. Cover that path with registry-write
monitoring on the keys the technique targets, and with the EID 7 provider-load rule.
References
- MITRE ATT&CK T1548.002 — https://attack.mitre.org/techniques/T1548/002/
- Atomic Red Team T1548.002 — https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1548.002/T1548.002.md