Files
2026-07-18 21:50:11 +05:30

62 KiB
Raw Permalink Blame History

R3 — COM for Local Privilege Escalation (research brief)

Scope: Local privilege escalation via COM/DCOM only. Important correction (verified): The DCOM/DCE-RPC local NTLM reflection bug is CVE-2015-2370 (MS15-076, Project Zero issue 325). CVE-2017-0100 (MS17-012) is a different DCOM LPE — the HelpPane.exe interactive-user client-verification bug from Forshaw's COM Session Moniker research. Do not conflate.


1. WHY OUT-OF-PROCESS COM SERVERS ARE LPE TARGETS

1.1 The structural problem

  • COM objects are activated through the SCM/RPCSS service. An out-of-process (OOP) server is registered via HKLM\SOFTWARE\Classes\CLSID\{CLSID} with LocalServer32 or with an AppID that names a Windows service. The server process frequently runs as NT AUTHORITY\SYSTEM, LOCAL SERVICE, or NETWORK SERVICE.
  • Whether a low-privileged user may even talk to such an object is governed by two SDDL strings stored under HKLM\SOFTWARE\Classes\AppID\{AppID}:
    • LaunchPermission — who may activate (rights mask includes ExecuteLocal 0x2, ActivateLocal 0x8).
    • AccessPermission — who may call methods on a running object.
    • Machine-wide defaults: HKLM\SOFTWARE\Microsoft\Ole\DefaultLaunchPermission / DefaultAccessPermission (editable via DCOMCNFG).
  • Many inbox services grant launch+access to Authenticated Users (S-1-5-11), INTERACTIVE (S-1-5-4), or even AppContainer/LPAC principals. Example (DiagHub): launch SDDL contains (A;;CCDCSW;;;AU) — Authenticated Users — plus ALL APPLICATION PACKAGES and an LPAC capability SID. Any such SYSTEM object with a dangerous method is a candidate LPE primitive.
  • The client-server call path is: Client → Proxy → COM library/RPC runtime → ALPC or TCP/NP transport → Stub → Server object. Marshalling is NDR (type-less "stubless" proxies) or type-library driven (oleaut32). All security context transfer relies on the impersonation contract below.

1.2 The impersonation contract (precision)

Client side — two levels:

  1. Process-wide, once: CoInitializeSecurity(pSecDesc, cAuthSvc, asAuthSvc, pReserved1, dwAuthnLevel, dwImpLevel, pAuthList, dwCapabilities, pReserved3).
  2. Per-proxy (what matters for exploitation): CoSetProxyBlanket(pProxy, dwAuthnSvc, dwAuthzSvc, pServerPrincName, dwAuthnLevel, dwImpLevel, pAuthInfo, dwCapabilities) — a wrapper for IClientSecurity::SetBlanket.

Key constants (verified):

Constant Value Meaning
RPC_C_IMP_LEVEL_DEFAULT 0
RPC_C_IMP_LEVEL_ANONYMOUS 1 token unusable
RPC_C_IMP_LEVEL_IDENTIFY 2 server can identify client only — token cannot be used to access objects
RPC_C_IMP_LEVEL_IMPERSONATE 3 server may act as client locally
RPC_C_IMP_LEVEL_DELEGATE 4 act as client off-machine (Kerberos only)
EOAC_STATIC_CLOAKING 0x20 proxy uses the token captured at blanket-set time
EOAC_DYNAMIC_CLOAKING 0x40 proxy uses the current thread token per call

Auth levels: RPC_C_AUTHN_LEVEL_CONNECT=2PKT_INTEGRITY=5, PKT_PRIVACY=6 (matters for the KB5004442 / CVE-2021-26414 DCOM hardening).

Server side — the correct pattern:

DWORD impLevel; DWORD authnSvc, authzSvc, authnLevel, caps; RPC_AUTHZ_HANDLE privs;
CoQueryClientBlanket(&authnSvc, &authzSvc, NULL, &authnLevel, &impLevel, &privs, &caps);
if (impLevel < RPC_C_IMP_LEVEL_IMPERSONATE) return E_ACCESSDENIED;   // often missing!
HRESULT hr = CoImpersonateClient();   // thread token := client's token
/* ... privileged operation performed AS THE CLIENT ... */
CoRevertToSelf();

Transport equivalents: RPC servers use RpcImpersonateClient()/RpcRevertToSelf(); named-pipe servers use ImpersonateNamedPipeClient()/RevertToSelf(); kernel-side equivalent is PsImpersonateClient gated by SeTokenCanImpersonate.

1.3 How the contract fails (the LPE bug taxonomy)

  1. No impersonation at all — server does file/registry/process ops as SYSTEM on attacker-controlled paths (the classic privileged-file-operation bug class).
  2. Late/short-lived impersonation — server impersonates, checks access, then RevertToSelf() and performs the write as SYSTEM (the StorSvc SvcMoveFileInheritSecurity bug used in the DiagHub chain).
  3. Identification-token confusion — server gets only an IDENTIFY token; use of it fails with error 1346 ERROR_BAD_IMPERSONATION_LEVEL. This is why early RoguePotato experiments got "useless" tokens and why Microsoft's CLSID hardening downgraded tokens to Identification.
  4. Cloaking confusion — static vs dynamic cloaking picks the wrong token when the server itself makes outbound calls (relevant to relay/reflection chains).
  5. Cross-session / "interactive user" activation — AppID RunAs = Interactive User objects instantiated from session 0 or another user's session (Session Moniker chain, CVE-2017-0100).
  6. Marshalled-by-reference leaks — server returns live objects that were never meant to cross the boundary (trapped objects).

2. CASE STUDY — DIAGHUB (Forshaw, Project Zero, April 2018)

Source: "Windows Exploitation Tricks: Exploiting Arbitrary File Writes for Local Elevation of Privilege" — googleprojectzero.blogspot.com/2018/04/windows-exploitation-tricks-exploiting.html (mirror: projectzero.google). Companion bugs: P0 issues 1427 & 1428.

2.1 The service

  • Microsoft (R) Diagnostics Hub Standard Collector Service ("DiagHub"), introduced in Windows 10 (Win7/8.1 analog: "IE ETW Collector"). Collects ETW diagnostics on behalf of sandboxed apps (Edge/IE). Runs as SYSTEM. Ships in DiagnosticsHub.StandardCollector.Service.exe + DiagnosticsHub.StandardCollector.Runtime.dll.
  • History of abuse: previously exploited by Lokihardt (sandbox escape) and Forshaw (user→SYSTEM). Later CVEs kept landing: CVE-2018-0952 (Atredis Partners PoC, github.com/atredispartners/CVE-2018-0952-SystemCollector), and a later case-sensitivity directory-traversal EoP (irsl PoC, github.com/irsl/microsoft-diaghub-case-sensitivity-eop-cve).

2.2 The DCOM object (exact IDs)

  • CLSID: {42CBFAA7-A4A7-47BB-B422-BD10E9D02700} ("Diagnostics Hub Standard Collector Service"), DCOM-activatable by normal users and AppContainer/LPAC sandboxes.
  • Interfaces: IMarshal, IUnknown, IStandardCollectorAuthorizationService (IID 2D2AC45D-03BB-4B8A-8EFE-93EF98217054), IStandardCollectorService (IID 0D8AF6B7-EFD5-4F6D-A834-314740AB8CAA), proxy CLSID 4323664B-B884-4929-8377-D2FD097F7BD3.
  • IStandardCollectorService has 8 methods (3 IUnknown + 5): CreateSession, GetSession, DestroySession, DestroySessionAsync, AddLifetimeMonitorProcessIdForSession (names recovered via OleViewDotNet IPID→VTable workflow + WinDbg dqs DiagnosticsHub_StandardCollector_Runtime+0x36C78 L8; signatures via OVDN "View Proxy Definition" NDR decompilation).

2.3 The load-anything primitive

ICollectionSession::AddAgent(dllName, guid) — simplified server code:

void EtwCollectionSession::AddAgent(LPWCSTR dll_path, REFGUID guid) {
    WCHAR valid_path[MAX_PATH];
    if (!GetValidAgentPath(dll_path, valid_path)) return E_INVALID_AGENT_PATH;
    HMODULE mod = LoadLibraryExW(valid_path, nullptr, LOAD_WITH_ALTERED_SEARCH_PATH);
    dll_get_class_obj = GetProcAddress(hModule, "DllGetClassObject");
    return dll_get_class_obj(guid);
}

GetValidAgentPath constrains the load to C:\Windows\System32 but not to any extension. So: write any file (e.g. license.rtf) whose contents are a valid DLL exporting DllGetClassObject into System32 → call CreateSession then AddAgent(L"license.rtf", guid) → code runs as SYSTEM, outside DllMain/loader lock. Client skeleton (from the blog):

CoCreateInstance(CLSID_CollectorService, nullptr, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&service));
SessionConfiguration config = {}; config.version = 1;
config.monitor_pid = GetCurrentProcessId(); CoCreateGuid(&config.guid);
config.path = SysAllocString(L"C:\\Dummy");
service->CreateSession(&config, nullptr, &session);
session->AddAgent(L"dummy.dll", agent_guid);

2.4 Full exploit chain (issue 1428 — arbitrary file write → SYSTEM)

  1. Storage Service (StorSvc) RPC method SvcMoveFileInheritSecurity impersonates the caller, MoveFileEx()s a file, then reverts impersonation and calls SetNamedSecurityInfo() as SYSTEM to apply inherited ACEs. Two bugs: failed-ACL revert allows arbitrary file creation (issue 1427); and via hard links, SetNamedSecurityInfo applies attacker-chosen ACEs to any file SYSTEM can WRITE_DAC (issue 1428).
  2. Pick a non-TrustedInstaller-owned, non-critical System32 file — the blog uses C:\Windows\System32\license.rtf (enumerate candidates with NtObjectManager's Get-AccessibleFile -AccessRights WriteDac run against the StorSvc process token).
  3. Overwrite license.rtf with a DLL implementing DllGetClassObject.
  4. DiagHub CreateSessionAddAgent("license.rtf", …)DLL loaded as SYSTEM.
  5. (Same primitive was later used by the Metasploit module for CVE-2019-0841 AppXSvc hardlink EoP, which credits "James Forshaw — Code creating hard links and communicating with DiagHub service".)

2.5 Microsoft's mitigation

  • Windows 10 19H1 / 1903: DiagHub hardened with a ProcessImageLoadPolicy restricting the collector to loading Microsoft-signed binaries only (first flagged publicly by @x9090 and @decoder_it on Twitter, Jan/Feb 2019). Killed the "load arbitrary extension from System32" trick. The USO trick was its replacement — then that was hardened too (2020-era Insider builds).
  • Lesson: MS increasingly kills the weaponization primitive (loader policy, signing-level checks, KnownDlls) rather than each file-write bug — see also the oleaut32 VerifyTrust signing-level check.

3. CASE STUDY — USO / UPDATE ORCHESTRATOR (itm4n, Aug 2019)

Sources: "Weaponizing Privileged File Writes with the USO Service — Part 1" (itm4n.github.io/usodllloader-part1/, 2019-08-17) and "Part 2" (itm4n.github.io/usodllloader-part2/, 2019-08-19). Tool: github.com/itm4n/UsoDllLoader.

3.1 The primitive

  • Update Session Orchestrator service (UsoSvc, C:\Windows\System32\usosvc.dll, runs as SYSTEM) attempts to load a non-existent DLL windowscoredeviceinfo.dll from System32 every time an Update Session is created.
  • Any privileged-file-write primitive → drop payload as C:\Windows\System32\windowscoredeviceinfo.dll → trigger a scan via the built-in undocumented client usoclient.exe StartScan (or directly via COM) → code as SYSTEM outside DllMain (the service calls a specific export after load; also avoids loader-lock constraints).
  • Not a vulnerability by itself — a weaponization bridge, the successor to DiagHub after 1903. itm4n's repo notes (2020-06): trick broken on then-current Insider builds; still worked on mainstream Win10 at that time; later patched.

3.2 The COM interface map (exact IDs)

  • CLSID UpdateSessionOrchestrator = {B91D5831-B1BD-4608-8198-D72E155020F7}; AppID {E7299E79-75E5-47BB-A03D-6D319FB7F886}; server usosvc.dll.
  • CoCreateInstance requests IID IUpdateSessionOrchestrator = {07F3AFAC-7C8A-4CE7-A5E0-3D24EE8A77E0}; its 5 methods past IUnknown: CreateUpdateSession, GetCurrentActiveUpdateSessions, LogTaskRunning, CreateUxUpdateManager, CreateUniversalOrchestrator (recovered from usosvc.dll VTable).
  • IUsoSessionCommon IID = {FCCC288D-B47E-41FA-970C-935EC952F4A4} — 68 methods; OleViewDotNet auto-generated the interface code. Another GUID seen in a QueryInterface: C57692F8-8F5F-47CB-9381-34329B40285A.
  • Reconstructed "StartScan" client flow (verbatim from part 2):
CoInitializeEx(0, COINIT_MULTITHREADED);
CoCreateInstance(CLSID_UpdateSessionOrchestrator, nullptr, CLSCTX_LOCAL_SERVER,
                 IID_PPV_ARGS(&updateSessionOrchestrator));
updateSessionOrchestrator->LogTaskRunning(L"StartScan");
updateSessionOrchestrator->CreateUpdateSession(1, &IID_IUsoSessionCommon, &usoSessionCommon);
CoSetProxyBlanket(usoSessionCommon, RPC_C_AUTHN_DEFAULT, RPC_C_AUTHZ_DEFAULT,
                  COLE_DEFAULT_PRINCIPAL, RPC_C_AUTHN_LEVEL_DEFAULT,
                  RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, NULL);
usoSessionCommon->Proc21(0, 0, L"ScanTriggerUsoClient");   // VTable[21] = StartScan trigger
CoUninitialize();

3.3 RE methodology demonstrated

Static: symchk for PDBs → string xref "StartScan"PerformOperationOnSession()/PerformOperationOnManager(). Dynamic: breakpoint on _guard_dispatch_icall_fptr after CoCreateInstance → WinDbg dqs on proxy VTable (usoapi!IUpdateSessionOrchestratorProxyVtbl) → match ObjectStublessClientN slots to server-side symbols in usosvc.dll. OleViewDotNet failed to expand the object (ClassFactory cannot supply requested class) — worked around by client-side dynamic analysis; a realistic "tool fails, pivot" moment. Note the CoSetProxyBlanket(... RPC_C_IMP_LEVEL_IMPERSONATE ...) call — the client must offer impersonation; this is the contract in the wild.


4. DCOM NTLM REFLECTION — THE BUG CLASS, THE REAL CVE, AND THE HARDENING TIMELINE

4.1 The root bug: CVE-2015-2370 (MS15-076, July 14 2015) — Project Zero issue 325

  • Reported by James Forshaw 2015-04-09: "Windows: DCOM DCE/RPC Local NTLM Reflection Elevation of Privilege" (bugs.chromium.org/p/project-zero/issues/detail?id=325; follow-on to unfixed WebDAV NTLM reflection issue 128).
  • Mechanism: when a DCOM object reference is marshalled by reference (OBJREF_STANDARD), the OBJREF carries RPC string bindings (TowerID + address string). Specifying TowerID NCACN_IP_TCP and a string host[port] makes the object resolver (rpcss, acting for the privileged server) open a TCP connection to an arbitrary local port and perform NTLM authentication per the security bindings. A local attacker relays that SYSTEM NTLM exchange back into a local AcceptSecurityContext() loop → negotiates a SYSTEM impersonation token locally (or, in Forshaw's original PoC, reflects the auth to the local DCOM activation service to activate CLSID_Package {F20DA720-C02F-11CE-927B-0800095AE340} as SYSTEM; a variant wrote a file to C:\Windows (2)).
  • Fix: MS15-076 — "the authentication implementation in the RPC subsystem … does not prevent DCE/RPC connection reflection" — Microsoft added reflection protections in the RPC runtime (rpcrt4.dll).
  • Weaponization history: NetSPI's "Trebuchet" (blog: "Exploiting MS15-076 (CVE-2015-2370)") turned the PoC into arbitrary file write using Forshaw's SyScan'15 symlink/junction tricks (junction + \RPC Control object-manager symlinks via CreateSymlink). This bug is the direct ancestor of every potato.

4.2 CVE-2017-0100 (MS17-012, March 2017) — what it actually is

  • Official title: "Windows HelpPane Elevation of Privilege Vulnerability" — "a DCOM object in Helppane.exe, configured to run as the interactive user, fails to properly authenticate the client." Local users gain privileges via a crafted application (Windows 7 SP1 → 10 1607, Server 2008 R2 → 2016).
  • Context: part of Forshaw's COM Session Moniker EoP research (session-moniker activation of an interactive-user DCOM class from another session; Helppane then executed attacker content with insufficient client verification). Microsoft's fix checked the wrong thing — Forshaw's follow-up report "Windows: Bad Fix for COM Session Moniker EoP" became CVE-2017-0298 (the HelpPane check compared integrity levels rather than verifying admin group; IL is not a security boundary; UIAccess IL-ratcheting could defeat it). Public text: exploit-db/0day.today ID 28134.
  • Teaching point: verify who the caller is (client blanket/identity) not how elevated their token looks.

4.3 Microsoft's DCOM hardening timeline

Date Change Impact
Jul 2015 MS15-076 / CVE-2015-2370 — RPC runtime blocks DCE/RPC connection reflection Kills raw reflection; potato family moves to local token negotiation
Mar 2017 MS17-012 / CVE-2017-0100 (then CVE-2017-0298 bad-fix fix) Session Moniker chain closed
May 2017 CVE-2017-0213 (COM Aggregate Marshaler, CStdMarshal::Finish_RemQIAndUnmarshal2 RQI2 unpack bug) & CVE-2017-0214 (type-library load validation) patched — both Forshaw; PoC: BITS SetNotifyInterface → load attacker type lib under impersonation → scriptlet moniker → code exec in BITS as SYSTEM. 0213 later entered CISA KEV Type-library attack surface shrinks
Oct 2018 (RS5) Silent rpcss fix (Win10 1809 / Server 2019): OXID binding no longer built via string concat host[port]; new CreateRemoteBindingToOR() builds bindings via RpcStringBindingCompose()host[port][135] fails parsing; also remote OXID queries forced to port 135 only and processed as ANONYMOUS LOGON; abusable-CLSID set hardened so captured tokens become Identification tokens; PrintNotify-style CLSIDs gated to INTERACTIVE group Kills Rotten/JuicyPotato (decoder's post "No more rotten/juicy potato?", 2018-10-29)
2019 (19H1/1903) DiagHub ProcessImageLoadPolicy (MS-signed loads only) Kills DiagHub weaponization
Jun 2021+ KB5004442 / CVE-2021-26414 — DCOM server hardening: minimum RPC_C_AUTHN_LEVEL_PKT_INTEGRITY enforced for remote DCOM activation (phased) Mostly a remote DCOM mitigation
Jan 2023 CVE-2023-21746 — NTLM local-auth context-swap fix in msv1_0.dll (SsprHandleChallengeMessage: if ISC_REQ_UNVERIFIED_TARGET_NAME set and SPN present → SPN forced NULL → SMB anti-reflection on cifs/127.0.0.1 fires) Kills LocalPotato SMB scenario

5. THE POTATO FAMILY — MECHANISMS, REQUIREMENTS, BUILD RANGES, AND EXACTLY WHAT IS COM

Common prerequisites (all potatoes): attacker code runs as an account holding SeImpersonatePrivilege and/or SeAssignPrimaryTokenPrivilege (IIS AppPool, MSSQL service, LOCAL/NETWORK SERVICE, scheduled-task service accounts). Token type math: CreateProcessWithTokenW (needs impersonate-capable primary token dup), CreateProcessAsUser (needs SeAssignPrimaryTokenPrivilege). COM appears in this family in exactly three roles: (a) the trigger — DCOM activation (CoGetInstanceFromIStorage with a crafted IStorage) that forces a privileged COM server to authenticate to the attacker; (b) the OXID resolver — rpcss's IObjectExporter::ResolveOxid2 interface (UUID 99FCFEC4-5260-101B-BBCB-00AA0021347A) that locates objects; (c) the callback surface — attacker-implemented COM objects (e.g. fake IUnknown/IRemUnknown2 server) into which the privileged side calls back, enabling CoImpersonateClient()/RpcImpersonateClient(). Everything else — NTLM relay, named-pipe impersonation, MS-RPRN/MS-EFSR coercion, SMB context swapping — is RPC/SMB/SSPI, not COM.

5.0 Pre-history (one line each)

  • Hot Potato (FoxGlove, Jan 2016): NBNS spoofing + WPAD + Windows Update HTTP NTLM → SYSTEM; MS16-075 (Metasploit windows/local/ms16_075_reflection). No COM.
  • RottenPotatoNG (FoxGlove): official C++ port of Rotten without Meterpreter/Incognito dependency.

5.1 RottenPotato (FoxGlove Security — @breenmachine & @vvalien1, Sep 2016)

  • COM part: CoGetInstanceFromIStorage(NULL, CLSID_BITS, …) with hardcoded BITS CLSID {4991D34B-80A1-4291-83B6-3328366B9097} and a hand-crafted IStorage whose string bindings point at 127.0.0.1:6666 → forces the BITS COM server (SYSTEM) to speak DCOM/RPC to the attacker's TCP listener.
  • Non-COM part: byte-level relay of the RPC conversation to the real OXID resolver on TCP 135; when the NTLMSSP exchange begins, MITM swaps the Type 2 challenge + "Reserved" field (the Reserved field is a reference to the local SecHandle — the critical trick) with output of local AcquireCredentialsHandle + AcceptSecurityContext(); SYSTEM's Type 3 completes the local authentication → ImpersonateSecurityContext() → SYSTEM impersonation token → Meterpreter Incognito to use it.
  • Works: ≤ Windows 10 1803 / Server ≤ 2016 era (killed by the 1809 OXID changes). 100% reliable, instant, cross-version at the time.
  • Refs: foxglovesecurity.com/2016/09/26/rotten-potato-privilege-escalation-from-service-accounts-to-system/; github.com/foxglovesec/RottenPotato.

5.2 JuicyPotato (ohpe/@Giutro & @decoder_it, Aug 2018)

  • "RottenPotatoNG on steroids": arbitrary CLSID (-c), arbitrary local COM-listen port (-l), arbitrary program (-p), -t * tries both CreateProcessWithTokenW and CreateProcessAsUser. No Meterpreter.
  • Key research: brute-forced/enumerated abusable CLSIDs — a usable CLSID must (1) be instantiable by the current (service) user, (2) implement IMarshal, (3) run as an elevated account. Per-OS CLSID lists published: ohpe.it/juicy-potato/CLSID/ (e.g., BITS {4991D34B…}, wuauserv/aucore {e60687f7-01a1-40aa-86ac-db1cbf673334} etc.). DiagHub's own CLSID {42CBFAA7-A4A7-47BB-B422-BD10E9D02700} appears in the Server 2016 list.
  • Works: up to Win10 1803 / Server 2016; broken by 1809/2019 silent fix.
  • Refs: ohpe.it/juicy-potato/; github.com/ohpe/juicy-potato; decoder.cloud/2018/10/29/no-more-rotten-juicy-potato/.

5.3 RogueWinRM (side quest; part of SweetPotato)

BITS, when started, attempts HTTP NTLM auth to 127.0.0.1:5985 (WinRM) — if WinRM is off (default on clients), an unprivileged user can bind 5985 and capture SYSTEM HTTP NTLM → token. github.com/antonioCoco/RogueWinRM. Not COM itself (HTTP), but BITS trigger is the same service family. Ref: decoder.cloud "From NETWORK SERVICE to SYSTEM" + RogueWinRM repo.

5.4 RoguePotato (@decoder_it & @splinter_code, May 2020)

The comeback after the 1809 kills. Verified from the primary source (decoder.cloud/2020/05/11/no-more-juicypotato-old-story-welcome-roguepotato/):

  1. COM trigger (COM): CoGetInstanceFromIStorage with BITS CLSID {4991d34b-80a1-4291-83b6-3328366b9097} (default; -c to override), IStorage string bindings pointing at a remote attacker IP — because post-1809 the OXID resolver is only reachable on port 135 and remote OXID queries authenticate as ANONYMOUS LOGON (useless directly).
  2. Redirect: attacker box runs socat tcp-listen:135,reuseaddr,fork tcp:<victim>:9999 → forwards to the fake RogueOxidResolver RPC server (can run on victim on port 9999, or standalone on attacker Windows box).
  3. Fake OXID resolver (COM): implements IObjectExporter — answers ServerAlive2 with RPC_S_OK, and forges ResolveOxid2 to return a DUALSTRINGARRAY with TowerID ncacn_np and endpoint string localhost/pipe/roguepotato[\pipe\epmapper] plus security bindings naming NT AUTHORITY\NETWORK SERVICE. (First attempt with ncacn_ip_tcp:localhost[9998] + IRemUnknown2 + RpcImpersonateClient() yielded only an Identification token — dead end; the named-pipe transport was the fix.)
  4. Path-validation bug (borrowed from PrintSpoofer, credit @itm4n & @jonasLyk): / in the hostname is normalized into the pipe path → rpcss connects to attacker pipe \\.\pipe\roguepotato\pipe\epmapper (protocol forces the \pipe\epmapper suffix; the /pipe/roguepotato prefix redirects it).
  5. Impersonation (not COM): ImpersonateNamedPipeClient()NETWORK SERVICE impersonation token with the rpcss logon-session LUID.
  6. Token kidnapping: because rpcss/RpcEptMapper (NETWORK SERVICE) shares a logon session with SYSTEM-token-holding threads (per Forshaw's "Sharing a Logon Session a Little Too Much", tiraniddo.dev/2020/04/sharing-logon-session-little-too-much.html), enumerate handles in the rpcss process, duplicate a SYSTEM token, CreateProcessAsUser()/CreateProcessWithToken() (with Session-0 window-station permission fix) → SYSTEM.
  • Requirements: SeImpersonatePrivilege; attacker-controlled machine reachable on TCP 135 from victim (or local resolver + port proxy); Works: Win10 1809+ / Server 2019+ (exactly where Juicy died).

5.5 PrintSpoofer (itm4n, Mar 2020)

  • Coercion (pure RPC, not COM): MS-RPRN RpcRemoteFindFirstPrinterChangeNotificationEx (SpoolSample-style trigger; original coercion PoC: github.com/leechristensen/SpoolSample) forces the Print Spooler (SYSTEM) to connect to a notification pipe.
  • The bug: the printer-name validation accepts \\HOSTNAME/pipe/foo, which is later normalized to \\HOSTNAME\pipe\foo\pipe\spoolss — redirecting the SYSTEM spooler into the attacker's pipe server \\.\pipe\foo\pipe\spoolss despite \pipe\spoolss being occupied.
  • Token half (not COM): CreateNamedPipe → client connects → ImpersonateNamedPipeClient()OpenThreadTokenDuplicateTokenEx (primary) → CreateProcessWithTokenW → SYSTEM.
  • Requirements: SeImpersonatePrivilege; spooler running. Works: Win10, Server 2016/2019 (and older with spooler); Patch status: unpatched at publication; treat as build-dependent today — later Windows builds hardened spooler behaviors and PrintNotify-based alternatives exist for hardened/no-spooler hosts (flag for verification per target build; medium confidence).

5.6 SweetPotato (@EthicalChaos / CCob, 2020)

  • C# unified toolkit: "Local Service to SYSTEM privilege escalation from Windows 7 to Windows 10 / Server 2019" — bundles the RottenPotato code base, the weaponized JuicyPotato BITS+WinRM approach (credit @decoder_it/@Guitro), RogueWinRM, and PrintSpoofer; auto-attempts the correct technique (-c CLSID default BITS {4991D34B-…}, -m method). COM usage = inherited from Rotten/Juicy (DCOM trigger) + named-pipe half from PrintSpoofer.
  • Refs: github.com/CCob/SweetPotato; ethicalchaos.dev/2020/04/13/sweetpotato-local-service-to-system-privesc/; pentestpartners.com/security-blog/sweetpotato-service-to-system/; decoder's companion analysis "We thought they were potatoes but they were beans" (decoder.cloud/2019/12/06/).

5.7 JuicyPotatoNG (@decoder_it & @splinter_code, Sep 2022)

  • Blog: decoder.cloud/2022/09/21/giving-juicypotato-a-second-chance-juicypotatong/; repo github.com/antonioCoco/JuicyPotatoNG. Default CLSID {854A20FB-2D44-457D-992F-EF13785D2B51} (PrintNotify — "Printer Extensions and Notifications" service, SYSTEM; present Win10/11 + Server 2016/2019/2022), default COM-server port 10247.
  • Problem solved: post-1809, surviving CLSIDs require the caller to be INTERACTIVE, which service accounts are not.
  • Trick 1 (token enrichment): LogonUser() with logon type 9 (NewCredentials) — LSASS clones the caller's token and adds the INTERACTIVE SID (no impersonation privilege needed for the clone) → the enriched token can activate the INTERACTIVE-gated CLSIDs.
  • Trick 2 (token capture): local COM server on 10247 + SSPI hook on AcceptSecurityContext() to intercept the privileged DCOM client's NTLM exchange as it is processed in-process — avoids RpcServerUseProtseqEp (which would hold the port) and, per the authors, relaxes requirements vs RpcImpersonateClient(). Conceptually rooted in Forshaw's Kerberos-DCOM relay work ("Windows Exploitation Tricks: Relaying DCOM Authentication", Oct 2021).
  • Works: Win10 1809+ / Win11, Server 2019+ / 2022 (i.e., post-Juicy world). Verified PoC output: [+] authresult success {854A20FB-…};NT AUTHORITY\SYSTEM;Impersonation.
  • Also see decoder's companion piece "The impersonation game" (decoder.cloud/2020/05/30/, a.k.a. "Juicy2" lineage).

5.8 LocalPotato (@decoder_it & @splinter_code, Feb 2023) — CVE-2023-21746

Precision — what is COM vs not: COM/DCOM is used only as the trigger; the vulnerability is in NTLM local authentication inside LSASS (msv1_0.dll); the write primitive is SMB. It is NOT a DCOM bug.

  1. Trigger (COM): CoGetInstanceFromIStorage with a SYSTEM-authenticating CLSID — default {854A20FB-2D44-457D-992F-EF13785D2B51} (PrintNotify); alternatives: {90F18417-F0F1-484E-9D3C-59DCEEE5DBD8} (ActiveX Installer Service "AxInstSv", Win10/11 only) and {A9819296-E5B3-4E67-8226-5E72CE9E1FB7} (Universal Print Management Service "McpManagementService", Win11 + Server 2022). The fake local OXID resolver (JuicyPotatoNG-style SSPI hooks, port 10271 default) returns security bindings containing a crafted SPN cifs/127.0.0.1, causing the privileged DCOM client to initiate NTLM with ISC_REQ_UNVERIFIED_TARGET_NAME and that SPN.
  2. The bug (NTLM/LSASS, not COM): in NTLM local authentication, Type 2 messages carry a security-context handle referencing an in-memory server context. The attacker runs a rogue client against the local SMB server (127.0.0.1:445) in parallel, and swaps the context handles between the two flows — LSASS binds the privileged (SYSTEM) identity to the attacker's SMB session ("Context Swapping").
  3. Result: attacker's SMB client is authenticated to \\127.0.0.1\C$ as SYSTEM → arbitrary file read/write (PoC writes raw SMB2 packets per MS-SMB2).
  4. Weaponization: write SprintCSP.dll to System32 + trigger SvcRebootToFlashingMode (StorSvc RPC, BlackArrowSec's RpcClient — released 2023-02-13), or overwrite printconfig.dll and instantiate PrintNotify (the authors' Server 2022 demo), or XPS Print Job / NetMan DLL hijack.
  5. Patch: Jan 2023 (CVE-2023-21746): SsprHandleChallengeMessage — if ISC_REQ_UNVERIFIED_TARGET_NAME set and SPN present → SPN nulled → SMB anti-reflection denies cifs/127.0.0.1. HTTP/WebDAV scenario remained unpatched (Microsoft decision): "LocalPotato HTTP edition" (decoder.cloud/2023/11/03/localpotato-http-edition/). 0patch covers EOL versions.

5.9 The rest of the family — verified existence & classification

Tool Author/Year Mechanism COM in chain? Works on
GodPotato BeichenDream, Dec 2022 Novel DCOM trigger; author: "some defects in rpcss when dealing with OXID"; needs only SeImpersonatePrivilege; .NET 2/3.5/4 builds Yes (trigger) — service-DCOM-object activation + impersonation of the DCOM callback Win811, Server 20122022
DCOMPotato zcgonvh "Some Service DCOM Object and SeImpersonatePrivilege abuse" — variants PrinterNotifyPotato.exe, McpManagementPotato.exe; attacker-supplied IUnknown callback → CoImpersonateClient() in its QueryInterface impersonates the SYSTEM caller (these objects default to RPC_C_IMP_LEVEL_IMPERSONATE) Yes (trigger + callback impersonation) Win10/11, Server 20122022
PrintNotifyPotato BeichenDream, late 2022 PrintNotify CLSID + fake IUnknown/pointer-moniker callback; works even with legacy Spooler disabled Yes Win10/11, Server 20122022
EfsPotato zcgonvh, 2021 MS-EFSR (EfsRpcOpenFileRaw/EfsRpcEncryptFileSrv PetitPotam-style) coercion of SYSTEM to attacker pipe \\.\pipe\lsarpc (alt: efsrpc/samr/lsass/netlogon) + named-pipe impersonation; CVE-2021-36942 patch bypass notes by @xassiz No — pure RPC coercion + pipe impersonation Servers 20122022 era (pre-EFSR hardening); C#
SharpEfsPotato bugch3ck EfsRpc + pipe impersonation built from SweetPotato + SharpEfsTrigger No similar
CoercedPotato @Hack0ura & @Prepouce (Hackvens), 2023 Automated multi-coercion: tries MS-RPRN (RpcRemoteFindFirstPrinterChangeNotification[Ex]) then MS-EFSR functions (14 variants) against local pipe servers \\.\pipe\coerced\pipe\spoolss / …\srvsvc + impersonation; French writeup blog.hackvens.fr/articles/CoercedPotato.html No — pure RPC coercion Win10/11, Server 2022 (verified screenshot build 22621)
PetitPotato wh0amitz Local PetitPotam (EFSRPC) → pipe No
RasmanPotato crisprss Coerces RasMan service (SYSTEM) via its RPC interface → pipe impersonation No Win10, Server 20122019
BadPotato BeichenDream itm4n's PrintSpoofer ported to C# No Win810, 20122019
CandyPotato klezVirus Pure C++ weaponized RottenPotatoNG Yes (Rotten lineage) Win10/2016 era
MultiPotato S3cur3Th1sSh1t Multi-trigger combo mixed
SigmaPotato tylerdotrar, 202324 Modernized GodPotato-fork style; --revshell, in-memory [SigmaPotato]::Main() via .NET reflection Yes (DCOM lineage) Win811, 20122022
GenericPotato Micah van Deusen, 2021 Generic SeImpersonation → SYSTEM via modified SSPI/HTTP named-pipe-less approach (micahvandeusen.com/the-power-of-seimpersonation/) partial
GhostPotato Shenanigans Labs, Nov 2019 MS08-068-style SMB relay bypass via NTLM challenge-cache (300 s) with modified impacket No unpatched-at-the-time SMB relay
RemotePotato0 antonioCoco/SentinelOne, 2021 Cross-protocol relay RPC→HTTP, "Won't Fix" — remote user→DA, not local LPE; boundary case partial
CertPotato SensePost, 2022 ADCS cert path from virtual/network service accounts No
LonelyPotato decoder, Dec 2017 RottenPotato standalone (no Meterpreter) Yes ≤1803
"SharpPotato" No well-known tool by this exact name exists (likely confusion with SweetPotato/BadPotato/SharpEfsPotato). State this to preempt questions.

5.10 Potato decision tree (text form)

  1. whoami /priv → SeImpersonatePrivilege or SeAssignPrimaryTokenPrivilege present? (No → not a potato target.)
  2. OS ≤ Win10 1803 / Server 2016 → RottenPotatoNG / JuicyPotato (BITS or list CLSID).
  3. OS ≥ Win10 1809 / Server 2019 → can victim reach your box on TCP 135 (or local port-proxy)? → RoguePotato. Else → interactive-capable or service account? → JuicyPotatoNG (LogonUser type-9 trick) / GodPotato / DCOMPotato / PrintNotifyPotato (service DCOM callbacks).
  4. Spooler up, Win10/2016-2019 → PrintSpoofer (C#: BadPotato). Spooler down/hardened → EfsPotato / CoercedPotato coercion alternatives.
  5. Need file-write primitive on ≤ Jan-2023 builds → LocalPotato (+ StorSvc/PrintNotify weaponization); patched → LocalPotato HTTP/WebDAV edition.
  6. .NET 4 present → SweetPotato / GodPotato-NET4 / SigmaPotato for opsec-friendly in-memory execution.

6. TRAPPED COM OBJECTS (Forshaw, Project Zero, Jan 30 2025)

Source: "Windows Bug Class: Accessing Trapped COM Objects with IDispatch" — projectzero.google/2025/01/windows-bug-class-accessing-trapped-com.html (mirror googleprojectzero.blogspot.com). (Post is dated 2025-Jan-30.)

6.1 The bug class

Object-oriented remoting (DCOM, .NET Remoting) marshals returned objects by reference by default — the object stays ("is trapped") in the server process while the client gets a remote pointer. If the object is not safe to expose across the boundary, the client now drives privileged code in the server. Three introduction scenarios:

  1. Unsafe object shared inadvertentlyCVE-2019-0555: WinRT needed an XML document; devs bolted runtime interfaces onto the XML DOM Document v6 COM object assuming no XSLT → malicious client QueryInterfaces the legacy IXMLDOMDocument → XSLT script execution → sandbox escape.
  2. Asynchronous marshaling primitive — .NET classes both serializable and MarshalByRefObject (e.g., FileInfo/DirectoryInfo): send serialized copy to server, read it back → marshaled by reference → trapped live object → create files as server (implemented in Forshaw's ExploitRemotingService tool).
  3. Abusing the platform's object-instantiation plumbing — coerce a path to CoCreateInstance with attacker CLSID and get the object back: CVE-2017-0211 (Structured Storage exposed across boundary; IPropertyBag → create arbitrary COM object in server → XML DOM → XSLT EoP).

6.2 The IDispatch / type-library vector (the new part)

  • Any OOP server exposing IDispatch must vend type info: client calls IDispatch::GetTypeInfoITypeInfo, itself marshaled by reference → trapped in server.
  • ITypeInfo::Invoke is not remotable, but ITypeInfo::CreateInstance IS — and it calls CoCreateInstance in the server process. The type info must describe a CoClass (carries a CLSID); reach it via ITypeInfo::GetContainingTypeLibITypeLib → enumerate classes → including referenced type libraries (stdole is virtually always referenced).
  • Worked example (verbatim methodology from the post, OleViewDotNet PowerShell module):
    • Get-ComClass -Service → enumerate interfaces → filter IsDispatch → 5 candidates on a default box: WaaSRemediation {72566E27-1ABB-4EB3-B4F0-EB431CB1CB32}, SearchGatheringManager {9E175B68-F52A-11D8-B9A5-505054503030}, SearchGathererNotification {9E175B6D-…}, AutomaticUpdates {BFE18E9C-6D87-4450-B37C-E02F0B373803}, Microsoft.SyncShare.SyncShareFactoryClass {DA1C0281-456B-4F14-A46D-8ED2E21A866F}.
    • New-ComObject -Clsid 72566e27-…Import-ComTypeLib → verify OOP with Get-ComObjRef (shows svchost.exe) → $lib.Parse()ReferencedTypeLibsstdole 2.0 {00020430-0000-0000-C000-000000000046} → classes StdFont {0BE35203-8F91-11CE-9DE3-00AA004BB851} and StdPicture {0BE35204-…} — both registered InProcServer32 only (oleaut32.dll) → instantiating one via the remote type library plants it inside the service: navigate GetRefTypeOfImplType(0)GetRefTypeInfoGetContainingTypeLibGetTypeInfoOfGuid(<StdFont CLSID>)CreateInstance()Get-ComObjRef proves object lives in the service process. (StdPicture has an OOP check; StdFont doesn't. Forshaw found no direct EoP in the font object.)
  • Type-library/proxy substitution → type confusion (why substitution works): the stub for the remoted interface is generated from the type library; control the TLB (registration swap or referencing an OOP TLB) and you control the server-side stub's type view — classic type confusion (his earlier NGEN-COM attack overwrote the KnownDlls handle this way). Microsoft's countermeasure now in oleaut32.dll: VerifyTrust — when loading a TLB inside a protected process, requires cached signing level ≥ 12 (Windows) via NtGetCachedSigningLevel/NtSetCachedSigningLevel; raw (non-PE) TLBs fail with STATUS_INVALID_IMAGE_FORMAT. This same hardening blocks ROT-based OOP type-library references.

6.3 PPL injection chain (the flagship PoC)

WaaSRemediationAgent runs in WaaSMedicSvc as PPL PsProtectedSignerWindows-Light ($cls.AppIDEntry.ServiceProtectionLevel = WindowsLight) — the service that also gates access to LSASS-level protected processes. Chain:

  1. Set HKLM\SOFTWARE\Microsoft\.NETFramework\AllowDCOMReflection = 1 (DWORD) — re-enables .NET DCOM reflection disabled since MS14-009; also OnlyUseLatestCLR = 1 to force CLR v4 (else v2 is loaded). (Admin needed for HKLM; but per-user HKCU COM/.NET registration can substitute — Forshaw: "the general technique does not [require admin]" if you find any suitable IDispatch-exposing server.)
  2. TreatAs hijack: point HKLM\SOFTWARE\Classes\CLSID\{0BE35203-8F91-11CE-9DE3-00AA004BB851}\TreatAs (StdFont) at the .NET System.Object COM class.
  3. Create WaaSRemediationAgent OOP → walk IDispatch→stdole→StdFont CreateInstanceCLR loads inside the PPL and returns System.Object.
  4. GetType → .NET reflection → System.Reflection.Assembly::Load(byte[]) → unsigned code executes in PPL-Windows → instantiate an object in the loaded assembly → run code (LSASS-with-PPL, protected AV/EDR processes now reachable). Cleanup registry.
  5. Windows 11 24H2 wrinkle: 64-bit mscorlib.tlb is a raw TLB → fails VerifyTrust signing-level (STATUS_INVALID_IMAGE_FORMAT); the 32-bit Framework\v4.0.30319\mscorlib.tlb is PE-packed (MZ) → set cached signing level (or swap TLB registration path to the 32-bit file) → PoC works again. Forshaw did not release his PoC (C++; "very similar to my CVE-2014-0257 exploit" — github.com/tyranid/IE11SandboxEscapes).
  • Public third-party PoCs: Mohamed Fakroud's writeup + github.com/T3nb3w/ComDotNetExploit (PPL injection); IBM X-Force Red "Fileless lateral movement with trapped COM objects" + github.com/xforcered/ForsHops (forshops.exe [target] [c:\path\to\assembly]; detection: TreatAs key on StdFont CLSID, AllowDCOMReflection/OnlyUseLatestCLR values, CLR load inside WaaSMedicSvc svchost — Samir Bousseaden's guidance; YARA strings include WaaSRemediation CLSID {72566E27-…} and {34050212-8AEB-416D-AB76-1E45521DB615}); Outflank (Kyle Avery, Jul 2025) used LLM-assisted enumeration to find alternative IDispatch classes that avoid the PPL CLR-load limitation entirely (outflank.nl/blog/2025/07/29/accelerating-offensive-research-with-llm/).
  • Framing: LPE angle = sandbox/AppContainer escapes and PPL injection (Microsoft does not treat PPL as a security boundary → fixes ship late, in new Windows versions).

7. OTHER DOCUMENTED COM LPE VECTORS (verified)

  1. UPnP Device Host + Update Orchestrator chain (NCC Group / Fox-IT, Nov 2019):
    • CVE-2019-1405 — logical flaw in UPnP Device Host service COM objects UPnPContainerManager/UPnPContainerManager64 implementing undocumented IUPnPContainerManager (IID 6D8FF8D4-730D-11D4-BF42-00B0D0118B56; methods ReferenceContainer, UnReferenceContainer, CreateInstance, CreateInstanceWithProgID, Shutdown) → any local user executes commands as LOCAL SERVICE. Fixed Nov 2019 by adding Administrators-only access checks to the dangerous methods.
    • CVE-2019-1322 — service misconfiguration: members of the SERVICE group could reconfigure UsoSvc (sc stop UsoSvcsc config UsoSvc binpath= …sc start) running as SYSTEM. Fixed Oct 2019 by stripping Full Control from the SERVICE group.
    • Chained: any local user → SYSTEM on Win10 18031903. PoC: COMahawk (COMahawk64.exe). Ref: fox-it.com writeup ("CVE-2019-1405 and CVE-2019-1322 Elevation to SYSTEM via the UPnP Device Host Service and the Update Orchestrator Service", Langlois & Torkington). The canonical "service exposes dangerous COM API to everyone" teaching example — and the methodology section of that post is a ready-made OleViewDotNet walkthrough.
  2. COM Aggregate Marshaler & type-library bugs (Forshaw, May 2017): CVE-2017-0213 (Aggregate Marshaler Finish_RemQIAndUnmarshal2; PoC uses BITS SetNotifyInterface → type library load under impersonation → scriptlet moniker → code exec in BITS as SYSTEM; exploit-db 42020; later CISA KEV) and CVE-2017-0214 (type-library input validation).
  3. Session Moniker / interactive-user DCOM (2017): CVE-2017-0100 + bad-fix CVE-2017-0298. Bug pattern: RunAs=Interactive User + cross-session activation + missing client verification.
  4. WalletService memory corruption over COM — CVE-2020-1362 (Q4n/Haoran Qin + Zhiniang Peng, Jul 2020): WalletService COM server (Wallet::WalletCustomProperty::SetGroup OOB write → SetLabel BSTR pointer write-what-where → fake vtable → redirect virtual call to dxgi!ATL::CComObject<CDXGIAdapter>::vector deleting destructor whose LoadLibraryExW uses a writable global path → load attacker DLL as SYSTEM; chained with CVE-2020-1361 heap leak). Medium IL → SYSTEM, verified on 18363. Writeup: github.com/Q4n/CVE-2020-1362. Pattern: DCOM method parameters → memory corruption in privileged server.
  5. Per-user COM hijack into privileged processes (HKCU\Software\Classes\CLSID{…}\InprocServer32 / TreatAs / AppID paths): user-writable per-user COM registrations shadow machine registrations for classes activated in the user's logon session. Classic uses are UAC bypass & persistence (bohops "Abusing the COM Registry Structure" parts 12; MITRE ATT&CK T1122 Component Object Model Hijacking), but it becomes LPE/PPL-relevant when a higher-privileged in-session process (or an IDispatch trapped-object server) activates the class — Forshaw explicitly notes the per-user variant of the StdFont TreatAs hijack for PPL injection without admin. Guidance: present as "check what identity activates user-scope classes."
  6. .NET COM interop into privileged processes: any registered .NET COM class loads the CLR (mscoree) into the activating process — including SYSTEM services and PPL. Leveraged by CVE-2014-0257 (Forshaw IE sandbox escape, public PoC), the trapped-object chain, and historically by .NET DCOM reflection (System.Type over DCOM → arbitrary reflection; fixed by MS14-009, re-enablable via AllowDCOMReflection; OnlyUseLatestCLR selects CLR v4). Pattern: registry values + TreatAs + .NET class = unsigned code in signed processes.
  7. WinRT broker / runtime-class bugs: WinRT is COM underneath (runtime classes, activation via combase, broker processes crossing IL/AppContainer boundaries). Verified example in-scope: CVE-2019-0555 (XML DOM through WinRT). Guidance: brokers = OOP COM servers with sandbox-facing activation permissions — same triage.
  8. DiagHub sibling CVEs: CVE-2018-0952 (Atredis PoC) and the later DiagHub case-sensitivity directory-traversal EoP (irsl PoC) — reinforce "DCOM service with file-manipulation methods = recurring CVE farm."
  9. Adjacent (flag as not COM, avoid mislabeling): Perfusion (itm4n) is the RpcEptMapper registry-key ACL bug (Win7/2008R2/8/2012) — RPC, not COM; FullPowers recovers LOCAL/NETWORK SERVICE default privileges (task scheduler, not COM); CVE-2020-1337 Print Spooler file-write — RPC.

8. RESEARCH METHODOLOGY — FINDING NEW COM LPE BUGS

8.1 Enumeration

  • OleViewDotNet (github.com/tyranid/oleviewdotnet; Forshaw, .NET 4; PS module: Install-Module OleViewDotNetPowerShell 5 only, not 7+):
    • GUI: Registry → Local Services (COM objects hosted in services, with filter), Registry → CLSIDs by Server, object node → View Launch Permissions / View Access Permissions (decoded AppID SDDLs).
    • PowerShell: Get-ComClass -Service, Get-ComInterface -Class $_, New-ComObject -Clsid …, Import-ComTypeLib -Object …, Get-ComObjRef (proves OOP + host PID/name), Get-ComClass -Clsid ….Servers (InProc vs Local), .AppIDEntry.ServiceProtectionLevel (PPL check), .AppIDEntry (RunAs, service name).
  • Raw registry: HKLM\SOFTWARE\Classes\CLSID, …\AppID\{guid} values LaunchPermission, AccessPermission, RunAs, Service, ServiceProtectionLevel; machine defaults under HKLM\SOFTWARE\Microsoft\Ole. Per-user shadow: HKCU\Software\Classes\CLSID.
  • Existing abusable-CLSID corpora: ohpe.it/juicy-potato/CLSID/ (per-OS), strontic xcyclopedia CLSID library, juicy-potato repo README tables.
  • RPC/RPCSS surface: RpcView (endpoint/interface dump), impacket rpcdump.py, Wireshark DCERPC + DCOM (IObjectExporter/IRemUnknown2) dissectors.

8.2 Target triage checklist

  1. OOP server (LocalServer32 or service-hosted AppID). 2. Runs as SYSTEM (or LOCAL/NETWORK SERVICE for hops). 3. Launch+access ACLs include your principal (Authenticated Users / INTERACTIVE / AppContainer-LPAC for sandbox escapes). 4. Methods that touch: filesystem paths, registry, process creation, LoadLibrary*, SetNamedSecurityInfo, token APIs, or that accept interface pointers (callback impersonation, DCOMPotato pattern) or return objects (trapped-object pattern). 5. Bonus: IDispatch present (type-library vector), .NET interop, RunAs=Interactive User.

8.3 Static RE checklist (Forshaw's DiagHub recipe, generalized)

  1. OleViewDotNet: create instance → Marshal ⇒ View Properties → capture PID + IPID + OXID + string bindings + authn services.
  2. OVDN process view (needs admin + configured symbols: dbghelp from WinDbg + srv*https://msdl.microsoft.com/download/symbols) → IPID entry → interface pointer + server-side VTable address (e.g., DiagnosticsHub_StandardCollector_Runtime+0x36C78).
  3. WinDbg attach: dqs <module>+<offset> L<nMethods> → symbolized method names (public symbols on MS components).
  4. OVDN View Proxy Definition → decompiles NDR stubless bytecode → method signatures; Structures tab → marshaled struct layouts → rebuild the interface in C++/C#.
  5. In IDA/Ghidra: xref command strings (itm4n's "StartScan" technique), map _guard_dispatch_icall_fptr indirect calls to VTable slots via dynamic breakpoints; match client proxy slots (ObjectStublessClientN) to server symbols.
  6. Hunt the failure modes: missing/late CoImpersonateClient/RpcImpersonateClient, early RevertToSelf, identify-level acceptance, device-map/symlink handling under impersonation, RunAs mismatches, type-library/TLB trust, TreatAs, unmarshaled-object safety.

8.4 Dynamic harness

Minimal C++ client: CoInitializeExCoCreateInstance(CLSCTX_LOCAL_SERVER)CoSetProxyBlanket(…, RPC_C_IMP_LEVEL_IMPERSONATE, …) → invoke methods with controlled inputs; log server-side behavior with Process Monitor (filter: server PID; file/reg/LoadImage ops as SYSTEM), API Monitor, WinDbg breakpoints on CoImpersonateClient, RpcImpersonateClient, CreateFileW, RegCreateKeyExW, LoadLibraryExW, SetNamedSecurityInfo; ETW providers Microsoft-Windows-RPC / COM for call tracing; TokenViewer (Forshaw) to inspect captured tokens (type: impersonation vs identification — the #1 source of "exploit didn't work" confusion).

8.5 Fuzzing marshal paths

  • Mutate marshaled NDR buffers against the NDR interpreter stubs (the exact layer OVDN decompiles); watch for OOB in server-side unmarshalling (WalletService class) and for marshal-by-reference object leaks.
  • Academic/industrial: COMRace (USENIX Security '22 — data-race detection in COM objects); Black Hat EU 2024 "Enhancing Automatic Vulnerability Discovery for Windows RPC/COM in New Ways" (harness-generation talk); RPC-aware fuzzing driven from IDA-generated clients; interface brute-calling via OVDN PowerShell (New-ComObject + method invocation) as a poor-man's fuzzer.

8.6 Patch diffing

BinDiff/Ghidra rpcss.dll, combase.dll, ole32.dll, oleaut32.dll, usosvc.dll, usocoreworker, DiagHub runtime. What to look for (proven patterns): new ACL checks in methods (CVE-2019-1405 fix), service-SD changes (CVE-2019-1322 fix), silent binding-builder changes (1809 MakeBindingCreateRemoteBindingToOR), image-load policy (ProcessImageLoadPolicy, 19H1), signing-level enforcement (VerifyTrust in oleaut32), NTLM package changes (SsprHandleChallengeMessage, CVE-2023-21746, diff msv1_0.dll).


9. DIAGRAM SPECS (covered by DIAGRAM_SPEC d12d17)

  1. Impersonation Contract sequence diagram (d06/d12).
  2. DiagHub chain (d13): StorSvc SvcMoveFileInheritSecurity + hard link → WRITE_DAC on license.rtf → overwrite → CoCreateInstance({42CBFAA7-…}) → CreateSession → AddAgent → LoadLibraryExW as SYSTEM; mitigation ribbon: 19H1 ProcessImageLoadPolicy.
  3. RoguePotato chain (d15): swimlane with COM lanes (14) vs pipe/token (57).
  4. Potato decision tree (d16 timeline + text tree): color-code COM trigger vs RPC coercion vs NTLM/LSASS; build ranges per branch.
  5. Trapped COM object flow (d17): IDispatch→ITypeInfo→stdole→StdFont CreateInstance→TreatAs→System.Object in PPL→Assembly.Load; VerifyTrust side panel.
  6. DCOM OBJREF anatomy (d04): the exact bytes every potato forges.

10. REFERENCES

Project Zero / Forshaw (primary):

itm4n:

decoder.cloud / splinter_code:

FoxGlove / ohpe:

CVE-2015-2370 ecosystem / CVE-2017-0100:

Other potatoes / coercion:

Trapped COM ecosystem:

Other COM LPE CVEs:

Microsoft docs (impersonation contract):


Research confidence & loose ends:

  • HIGH confidence: all CLSIDs/IIDs quoted (verified against primary sources or multiple corroborating sources), DiagHub/USO/Trapped-COM workflows (read in full from primary blogs), potato mechanisms and build ranges, CVE mappings (2015-2370, 2017-0100, 2017-0298, 2017-0213/0214, 2019-0555, 2019-1405/1322, 2020-1362, 2023-21746).
  • MEDIUM confidence (marked in-line where used): PrintSpoofer's current per-build patch status (was unpatched at release; later spooler hardening varies — "verify on target build"); SweetPotato's exact internal method list per release version; GodPotato's internal description (author says "rpcss OXID defects," some third parties mischaracterize it as pure named-pipe — use the DCOM-callback family description shared with DCOMPotato/PrintNotifyPotato).
  • The two GPZ blogspot URLs that failed automated fetch (DiagHub blog, PrintSpoofer blog) were verified via their projectzero.google mirror / extensive secondary sources respectively; all technical claims quoted above come from the full primary text where accessible.