# 03 · COM for Local Privilege Escalation > **Audience / level:** Red team operators and Windows internals students; no COM background assumed, ramping to original-research methodology. **Prerequisites:** basic Windows administration (services, registry, tokens), TCP/IP fundamentals, and the ability to read short C++ snippets. **Estimated reading time:** 120–150 minutes. > After this chapter you will be able to explain the COM impersonation contract and every way it breaks, reproduce the DiagHub and USO weaponization chains, pick the correct potato for any build from Windows 7 to Server 2022, recognize trapped-COM-object and IDispatch primitives, and run a repeatable workflow for finding new COM LPE bugs yourself. **Contents** - [1. Why out-of-process COM servers are LPE targets](#1-why-out-of-process-com-servers-are-lpe-targets) - [2. The impersonation contract](#2-the-impersonation-contract) - [3. How the contract fails: the LPE bug taxonomy](#3-how-the-contract-fails-the-lpe-bug-taxonomy) - [4. Case study: DiagHub (Forshaw 2018)](#4-case-study-diaghub-forshaw-2018) - [5. Case study: USO and UsoDllLoader (itm4n 2019)](#5-case-study-uso-and-usodllloader-itm4n-2019) - [6. DCOM NTLM reflection and the hardening timeline](#6-dcom-ntlm-reflection-and-the-hardening-timeline) - [7. The Potato family](#7-the-potato-family) — [7.1 prerequisites and the three COM roles](#71-common-prerequisites-and-the-three-com-roles), [7.3 RottenPotato](#73-rottenpotato-local-ntlm-reflection-over-dcom-2016), [7.4 JuicyPotato](#74-juicypotato-arbitrary-clsid-reflection-2018), [7.6 RoguePotato](#76-roguepotato-fake-oxid-resolver-and-token-kidnapping-2020), [7.7 PrintSpoofer](#77-printspoofer-pipe-name-normalization-2020), [7.9 JuicyPotatoNG](#79-juicypotatong-type-9-logon-and-an-sspi-hook-2022), [7.10 LocalPotato](#710-localpotato-ntlm-context-swapping-2023), [7.11 classification table](#711-the-rest-of-the-family-classification-table), [7.12 decision tree](#712-potato-decision-tree) - [8. Trapped COM objects (Forshaw, January 2025)](#8-trapped-com-objects-forshaw-january-2025) - [9. Other documented COM LPE vectors](#9-other-documented-com-lpe-vectors) - [10. Research Lab: Finding New COM LPE Bugs](#10-research-lab-finding-new-com-lpe-bugs) - [11. References](#11-references) --- ## 1. Why out-of-process COM servers are LPE targets COM objects are activated through the Service Control Manager / **RPCSS** service. An **out-of-process (OOP) server** is registered under `HKLM\SOFTWARE\Classes\CLSID\{CLSID}` either with a `LocalServer32` value naming an executable, or with an `AppID` that names a Windows service hosting the class. That server process frequently runs as `NT AUTHORITY\SYSTEM`, `LOCAL SERVICE`, or `NETWORK SERVICE` — which is exactly why it is interesting: if an unprivileged user can activate the object and drive one of its methods, the user is steering code that executes inside a privileged process. > **Beginner note:** A **CLSID** (class identifier) is the GUID naming a COM class, e.g. `{42CBFAA7-A4A7-47BB-B422-BD10E9D02700}`. An **AppID** is a second GUID, under `HKLM\SOFTWARE\Classes\AppID\{AppID}`, that groups classes under one activation policy: which identity the server runs as (`RunAs`), which service hosts it, and who may launch or access it. When you audit "COM permissions" for LPE, you audit AppIDs, not CLSIDs. Whether a low-privileged user may even talk to such an object is governed by two SDDL strings stored as values under the AppID key: - **`LaunchPermission`** — who may activate the class. The rights mask includes `ExecuteLocal 0x2` and `ActivateLocal 0x8`. - **`AccessPermission`** — who may call methods on an already-running object. Machine-wide fallbacks for both live at `HKLM\SOFTWARE\Microsoft\Ole\DefaultLaunchPermission` and `DefaultAccessPermission` (editable through DCOMCNFG). > **Beginner note:** An **SDDL** string is the text form of an ACL; each parenthesized ACE is `(type;;rights;;;SID)`. Reading `O:SYG:SYD:(A;;CCDCSW;;;AU)`: the ACE grants rights `CCDCSW` to `AU` — Authenticated Users (`S-1-5-11`). Other principals you will see here: `INTERACTIVE` (`S-1-5-4`), `ALL APPLICATION PACKAGES` (AppContainer apps), and LPAC capability SIDs. Many inbox services grant launch **and** access to `Authenticated Users`, `INTERACTIVE`, or even AppContainer/LPAC principals. The Diagnostics Hub service (section 4) is the canonical example: its launch SDDL contains `(A;;CCDCSW;;;AU)` — Authenticated Users — plus ALL APPLICATION PACKAGES and an LPAC capability SID, meaning both normal users *and* sandboxed apps may activate a SYSTEM object. **Any SYSTEM object with a dangerous method and a permissive AppID is a candidate LPE primitive.** The call path from your code to the privileged method is: ```text Client → Proxy → COM library / RPC runtime → ALPC or TCP/NP transport → Stub → Server object ``` Marshalling across that boundary is NDR ("stubless" proxies generated from type information) or type-library driven (`oleaut32` automation marshalling). Everything about *who the server acts as* during the call relies on one mechanism — the impersonation contract in section 2 — and most of this chapter is a catalogue of how that contract fails. > **Key idea:** The LPE question for any COM class is always the same three-tuple: (1) does the AppID let *me* activate and call it, (2) does the server run as a *higher* identity, (3) does any method touch a resource I control — a file path, a registry key, a DLL name, a process, or an interface pointer. If all three are yes, you have a lead. > **Legal:** Every technique in this chapter escalates privileges on a Windows host. Use only inside an authorized engagement or your own lab. The PoCs below reproduce the original researchers' public work; several are intentionally destructive to the target build (they overwrite System32 files). --- ## 2. The impersonation contract Every COM LPE in this chapter is, at root, a broken promise about *whose token the server uses*. Learn the contract precisely; the bug taxonomy in section 3 is just its failure modes. > **Beginner note:** An **access token** is the kernel object representing a logon session's identity: user SID, group SIDs, privileges. Threads normally run with the process **primary token**. **Impersonation** lets a server thread temporarily wear a *copy* of its client's token (an **impersonation token**) so access checks during a call evaluate against the caller, not the privileged server; `RevertToSelf()` drops it. An **integrity level** (IL) — Untrusted, Low, Medium, High, System — is a mandatory-integrity label inside the token; it gates writes, but as section 6 shows, IL alone is not a security boundary. The client controls what it offers the server at two levels: 1. **Process-wide, once per process:** `CoInitializeSecurity(pSecDesc, cAuthSvc, asAuthSvc, pReserved1, dwAuthnLevel, dwImpLevel, pAuthList, dwCapabilities, pReserved3)` sets the default authentication and impersonation levels for all proxies ([74]). 2. **Per-proxy — the call that matters for exploitation:** `CoSetProxyBlanket(pProxy, dwAuthnSvc, dwAuthzSvc, pServerPrincName, dwAuthnLevel, dwImpLevel, pAuthInfo, dwCapabilities)`, a wrapper for `IClientSecurity::SetBlanket` ([74]). A client can raise (or lower) the impersonation level on any individual interface pointer it holds. The impersonation-level constants the client passes ([77]): | Constant | Value | Meaning | |---|---|---| | `RPC_C_IMP_LEVEL_DEFAULT` | 0 | Inherit process default | | `RPC_C_IMP_LEVEL_ANONYMOUS` | 1 | Token unusable by the server | | `RPC_C_IMP_LEVEL_IDENTIFY` | 2 | Server can identify the client only — **the token cannot be used to access objects** | | `RPC_C_IMP_LEVEL_IMPERSONATE` | 3 | Server may act as the client **on the local machine** | | `RPC_C_IMP_LEVEL_DELEGATE` | 4 | Server may act as the client off-machine (Kerberos only) | Two capability flags decide *which* client token the proxy presents: `EOAC_STATIC_CLOAKING` (0x20) presents the token captured when the blanket was set; `EOAC_DYNAMIC_CLOAKING` (0x40) presents the *current thread* token on each call. Authentication levels matter too: `RPC_C_AUTHN_LEVEL_CONNECT = 2` up through `PKT_INTEGRITY = 5` and `PKT_PRIVACY = 6` — the KB5004442 / CVE-2021-26414 DCOM hardening (section 6) is defined in terms of forcing a minimum of `PKT_INTEGRITY` for remote activation ([78]). **Server side — the correct pattern ([75]).** A security-aware OOP server must verify what it was offered, impersonate, do the dangerous work *as the client*, and revert: ```cpp 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(); ``` The check on the second line is the whole game: without it, a server may happily operate on an identification-level token (and fail mysteriously), or — worse for security — skip impersonation entirely and operate as SYSTEM. Transport equivalents exist for non-COM servers, and the potato family abuses all of them: raw RPC servers use `RpcImpersonateClient()`/`RpcRevertToSelf()`, named-pipe servers use `ImpersonateNamedPipeClient()`/`RevertToSelf()`, and the kernel-side equivalent is `PsImpersonateClient` gated by `SeTokenCanImpersonate`. When a server receives only an IDENTIFY-level token and tries to *use* it (duplicate it to a primary, open objects), the call fails with error **1346 `ERROR_BAD_IMPERSONATION_LEVEL`**. That error is the signature of the identification-token wall the whole potato family ran into after Microsoft's 2018 hardening (section 7). ![Figure 1 — The COM impersonation contract: client blanket, server-side check, impersonate, revert](diagrams/d06-impersonation-contract.png) *How to read this figure: the left lane is the client offering an impersonation level via `CoSetProxyBlanket`; the right lane is the server. The guarded box around `CoImpersonateClient` is the access check most LPE bugs are missing — follow the dashed arrows for the failure modes (no impersonation, late impersonation, identify-level token).* > **Key idea:** Impersonation is *opt-in by the client*. A malicious client that wants a privileged server to act as SYSTEM must either find a server that never impersonates (bug class 1), or find a path where the server impersonates, checks, and then reverts before doing the dangerous operation (bug class 2). Conversely, a client that wants to *capture* a SYSTEM token must offer `RPC_C_IMP_LEVEL_IMPERSONATE` itself — exactly what the USO client in section 5 does. --- ## 3. How the contract fails: the LPE bug taxonomy Six failure modes cover nearly every COM LPE published to date. Memorize this list — it is also the triage checklist for original research in section 10. 1. **No impersonation at all.** The server performs file, registry, or process operations as SYSTEM on attacker-controlled paths without ever calling `CoImpersonateClient()`. This is the classic privileged-file-operation bug class ([68]): the client passes a path, the server opens/moves/writes it with its own token. DiagHub's `AddAgent` (section 4) is the archetype — the client names a DLL inside `C:\Windows\System32` and the SYSTEM service loads it. 2. **Late or short-lived impersonation.** The server impersonates, performs an access *check*, then calls `RevertToSelf()` and performs the *write* as SYSTEM. The check is honest and the operation is still exploitable, because the resource being written can be swapped or linked after the check. The Storage Service's `SvcMoveFileInheritSecurity` — which applies inherited ACEs with `SetNamedSecurityInfo()` after reverting — is the documented instance, and it is stage one of the DiagHub chain (section 4). 3. **Identification-token confusion.** The server (or attacker tooling) ends up holding only an IDENTIFY-level token. Using it fails with error 1346 `ERROR_BAD_IMPERSONATION_LEVEL`. This failure mode is why early RoguePotato experiments captured "useless" tokens, and why Microsoft's post-2018 CLSID hardening deliberately downgrades captured tokens to Identification — a dead token is the *mitigation* (sections 6 and 7). 4. **Cloaking confusion.** Static vs dynamic cloaking picks the wrong token when a server itself makes outbound calls. Under `EOAC_STATIC_CLOAKING` the proxy presents the token captured at blanket-set time even if the thread has since impersonated someone else; under `EOAC_DYNAMIC_CLOAKING` it presents the current thread token. In relay and reflection chains, the wrong choice is the difference between reflecting a SYSTEM authentication and reflecting an anonymous one ([76]). 5. **Cross-session / "interactive user" activation.** An AppID with `RunAs = Interactive User` causes the object to be instantiated in *someone else's* logon session when activated from session 0 or another user's session. Combined with missing client verification in the server, activation from a low session lands code execution in a privileged user's session — the Session Moniker chain behind CVE-2017-0100 (section 6). 6. **Marshalled-by-reference leaks.** Object-oriented remoting marshals returned objects by reference by default: the object stays inside the server and the client receives a remote pointer. If the server hands out an object that was never meant to cross the trust boundary, the client now drives privileged code living inside the server process — the "trapped COM object" bug class of section 8, with a 2025-vintage PPL-injection PoC built entirely on `IDispatch`/`ITypeInfo`. ![Figure 2 — Bug class 1: the missing impersonation call, the most common COM LPE root cause](diagrams/d12-missing-impersonation.png) *How to read this figure: the client's attacker-controlled path enters the server method on the left. The correct flow (top) impersonates before touching the resource; the vulnerable flow (bottom) skips straight to the privileged operation, so the file/registry/process access is made with the server's SYSTEM token.* --- ## 4. Case study: DiagHub (Forshaw 2018) **Since:** Windows 10 (the Windows 7/8.1 analog is the "IE ETW Collector") · **Affects:** Windows 10 builds before 19H1/1903 · **Researcher:** James Forshaw, Project Zero, April 2018, with companion bugs P0 issues 1427 and 1428 ([1], [2]). The **Microsoft (R) Diagnostics Hub Standard Collector Service** ("DiagHub") collects ETW diagnostics on behalf of sandboxed apps (Edge/IE). It runs as **SYSTEM**, shipping as `DiagnosticsHub.StandardCollector.Service.exe` plus `DiagnosticsHub.StandardCollector.Runtime.dll`. It had already been abused by Lokihardt (sandbox escape) and by Forshaw (user to SYSTEM), and CVEs kept landing afterwards: **CVE-2018-0952** (Atredis Partners PoC, [66]) and a later case-sensitivity directory-traversal EoP (irsl PoC, [67]). ### 4.1 The DCOM object - **CLSID `{42CBFAA7-A4A7-47BB-B422-BD10E9D02700}`** ("Diagnostics Hub Standard Collector Service"), DCOM-activatable by normal users *and* by AppContainer/LPAC sandboxes (the `(A;;CCDCSW;;;AU)` AppID of section 1). - Interfaces: `IMarshal`, `IUnknown`, `IStandardCollectorAuthorizationService` (IID `2D2AC45D-03BB-4B8A-8EFE-93EF98217054`), and **`IStandardCollectorService` (IID `0D8AF6B7-EFD5-4F6D-A834-314740AB8CAA`)**; proxy CLSID `4323664B-B884-4929-8377-D2FD097F7BD3`. - `IStandardCollectorService` exposes 8 methods (3 `IUnknown` + 5): `CreateSession`, `GetSession`, `DestroySession`, `DestroySessionAsync`, `AddLifetimeMonitorProcessIdForSession`. Forshaw recovered the names with the OleViewDotNet IPID→VTable workflow plus WinDbg `dqs DiagnosticsHub_StandardCollector_Runtime+0x36C78 L8`, and the signatures via OleViewDotNet's "View Proxy Definition" NDR decompilation — the exact recipe generalized in section 10. ### 4.2 The load-anything primitive The session object's `AddAgent(dllName, guid)` loads a collector "agent" DLL. The simplified server code ([1]): ```cpp 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 file extension**. So: place any file (for example `license.rtf`) whose contents are a valid DLL exporting `DllGetClassObject` into System32, call `CreateSession` then `AddAgent(L"license.rtf", guid)`, and your code runs **as SYSTEM, outside `DllMain` and outside loader lock** (the service calls `DllGetClassObject` explicitly after load). Client skeleton from the blog: ```cpp 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); ``` ### 4.3 The full exploit chain (issue 1428) DiagHub supplies the *loader*; the Storage Service supplies the *write*. The chain ([1], [2]): 1. **StorSvc `SvcMoveFileInheritSecurity`** impersonates the caller, `MoveFileEx()`es a file, **then reverts impersonation** and calls `SetNamedSecurityInfo()` as SYSTEM to apply inherited ACEs — bug class 2 from section 3. Two bugs fall out: the failed-ACL revert path 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`**. Candidates are enumerable with NtObjectManager's `Get-AccessibleFile -AccessRights WriteDac` run against the StorSvc process token. 3. Overwrite `license.rtf` with a DLL implementing `DllGetClassObject`. 4. DiagHub `CreateSession` → `AddAgent("license.rtf", …)` → **DLL loaded as SYSTEM**. 5. The same loader primitive was later reused by the Metasploit module for the **CVE-2019-0841** AppXSvc hardlink EoP, whose credits read "James Forshaw — Code creating hard links and communicating with DiagHub service". > **Beginner note:** A **hard link** is a second directory entry pointing at the same underlying file data. Created through the right system calls, it needs no ownership of the target, so an attacker makes a file they *do* control alias a file SYSTEM owns — then any "set ACL on the controlled path" operation retargets the privileged file. `WRITE_DAC` is the access right to change a file's permissions. ![Figure 3 — The DiagHub chain: StorSvc write primitive into the AddAgent loader](diagrams/d13-diaghub-chain.png) *How to read this figure: read left to right. `SvcMoveFileInheritSecurity` plus a hard link converts a weak ACL operation into `WRITE_DAC` on `license.rtf`; the file is overwritten with a DLL; `CoCreateInstance({42CBFAA7-…})` → `CreateSession` → `AddAgent` → `LoadLibraryExW` executes it as SYSTEM. The mitigation ribbon at the right marks the 19H1 ProcessImageLoadPolicy fix.* ### 4.4 Microsoft's mitigation **Windows 10 19H1 / 1903** hardened DiagHub with a **`ProcessImageLoadPolicy`** restricting the collector to loading **Microsoft-signed binaries only** (first flagged publicly by @x9090 and @decoder_it on Twitter in Jan/Feb 2019). That killed the "load an arbitrary-extension file from System32" trick outright. The USO trick of section 5 was its replacement — and that was hardened in turn in 2020-era Insider builds. > **Key idea:** Microsoft increasingly kills the *weaponization primitive* — loader policy, signing-level checks, KnownDlls — rather than each individual file-write bug. The same pattern reappears as the oleaut32 `VerifyTrust` signing-level check in section 8. When you plan an engagement, assume arbitrary-file-write bugs outlive their public weaponization paths. --- ## 5. Case study: USO and UsoDllLoader (itm4n 2019) **Since:** August 2019, itm4n — "Weaponizing Privileged File Writes with the USO Service", parts 1 and 2 ([9], [10]); tool `UsoDllLoader` ([11]). · **Affects:** mainstream Windows 10 of the era; broken on 2020-era Insider builds and later patched. ### 5.1 The primitive The **Update Session Orchestrator** service (`UsoSvc`, implemented in `C:\Windows\System32\usosvc.dll`, running as SYSTEM) attempts to load a **non-existent DLL, `windowscoredeviceinfo.dll`, from System32 every time an Update Session is created** ([9]). Any privileged-file-write primitive therefore becomes code execution: drop a payload as `C:\Windows\System32\windowscoredeviceinfo.dll`, then trigger a scan — via the built-in undocumented client `usoclient.exe StartScan`, or directly over COM — and the service loads the DLL and calls a specific export, so payload code runs as SYSTEM **outside `DllMain`**, avoiding loader-lock constraints. > **Warning:** This is not a vulnerability by itself. It is a **weaponization bridge** — the successor to DiagHub after 1903 — that converts any arbitrary-write-to-System32 primitive (a potato, a StorSvc bug, a spooler bug) into SYSTEM code execution. itm4n's repo notes (June 2020) that the trick was already broken on then-current Insider builds while still working on mainstream Windows 10; treat it as historical-but-instructive and verify per target build. ### 5.2 The COM interface map Part 2 of the series reconstructs the client side of the orchestrator from scratch ([10]): - **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}`**, whose 5 methods past `IUnknown` are `CreateUpdateSession`, `GetCurrentActiveUpdateSessions`, `LogTaskRunning`, `CreateUxUpdateManager`, `CreateUniversalOrchestrator` (recovered from the `usosvc.dll` VTable). - **`IUsoSessionCommon` IID = `{FCCC288D-B47E-41FA-970C-935EC952F4A4}`** — 68 methods; OleViewDotNet auto-generated the interface code once the IID was known. Another GUID seen in a `QueryInterface` during the work: `C57692F8-8F5F-47CB-9381-34329B40285A`. The reconstructed "StartScan" client flow, verbatim from part 2: ```cpp 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(); ``` Note the `CoSetProxyBlanket(... RPC_C_IMP_LEVEL_IMPERSONATE ...)` line: the client must *offer* impersonation for the privileged server to act on its behalf. That is the section 2 contract observed in the wild, in Microsoft's own client code. ### 5.3 The RE methodology (why this case study matters) The interface above was undocumented. itm4n's workflow, step by step: 1. **Static:** `symchk` to pull PDB symbols; string cross-reference on `"StartScan"` leads to `PerformOperationOnSession()` / `PerformOperationOnManager()` in `usosvc.dll`. 2. **Dynamic:** breakpoint on `_guard_dispatch_icall_fptr` (the CFG indirect-call thunk) immediately after `CoCreateInstance` to catch every proxy dispatch; WinDbg `dqs` on the proxy VTable (`usoapi!IUpdateSessionOrchestratorProxyVtbl`) to dump slot addresses. 3. **Slot matching:** map each client-side `ObjectStublessClientN` slot to its server-side symbol in `usosvc.dll` — that is how `Proc21` was identified as the StartScan trigger. 4. **Tool failure and pivot:** OleViewDotNet failed to expand the object (`ClassFactory cannot supply requested class`), so the interface was recovered by client-side dynamic analysis instead of OVDN introspection. A realistic "the tool fails, you pivot" moment — expect it (section 10). --- ## 6. DCOM NTLM reflection and the hardening timeline This section separates two bugs that are constantly conflated, then lays out the eight-year Microsoft hardening campaign that the entire potato family is a reaction to. > **Key idea:** **CVE-2015-2370 (MS15-076) is the DCOM/DCE-RPC local NTLM reflection bug — 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.** Same subsystem, different mechanism, different year. Do not conflate them; the timeline below keeps them on separate rows. > **Beginner note:** **NTLM reflection** means convincing a privileged process to start an NTLM authentication *to you*, then feeding that authentication back into a *local* server context you control, so the local SSPI handshake completes and yields an impersonation token for the privileged identity. No password is cracked; attacker and target share one machine. ### 6.1 The root bug: CVE-2015-2370 (MS15-076, July 14 2015) Reported by James Forshaw on 2015-04-09 as **"Windows: DCOM DCE/RPC Local NTLM Reflection Elevation of Privilege"** (Project Zero issue 325, [3]) — a follow-on to the unfixed WebDAV NTLM reflection issue 128. **Mechanism.** When a DCOM object reference is marshalled by reference (`OBJREF_STANDARD`), the OBJREF carries RPC **string bindings**: a TowerID plus an address string. Specifying TowerID `NCACN_IP_TCP` and a string of the form `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 and negotiates a **SYSTEM impersonation token locally**. In Forshaw's original PoC the reflected authentication was aimed at 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" — added reflection protections to the RPC runtime (`rpcrt4.dll`). **Weaponization history.** NetSPI's "Trebuchet" ("Exploiting MS15-076 (CVE-2015-2370)", [35]) turned the PoC into arbitrary file write using Forshaw's SyScan'15 symlink/junction tricks — junctions plus `\RPC Control` object-manager symlinks via CreateSymlink. **This bug is the direct ancestor of every potato in section 7.** ### 6.2 CVE-2017-0100 (MS17-012, March 2017) — what it actually is Official title: **"Windows HelpPane Elevation of Privilege Vulnerability"** ([36]) — "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 through 10 1607, Server 2008 R2 through 2016). It is bug class 5 from section 3: `RunAs = Interactive User` plus cross-session activation (the COM Session Moniker) plus missing client verification — Helppane executed attacker content without checking who was asking. Microsoft's fix then *checked the wrong thing*: Forshaw's follow-up report **"Windows: Bad Fix for COM Session Moniker EoP"** became **CVE-2017-0298** ([37]). The HelpPane check compared integrity levels rather than verifying membership of the administrators group; IL is not a security boundary, and UIAccess IL-ratcheting could defeat it. Public text: exploit-db / 0day.today ID 28134. > **Key idea:** Verify *who the caller is* (client blanket, identity, group membership), not *how elevated their token looks*. An integrity level is a label, not an identity. ### 6.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, [15]) | | 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 ([78]) | | 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 | Every row of this table creates the conditions for the next potato: the 1809 OXID changes force RoguePotato's remote-resolver architecture, the INTERACTIVE gating forces JuicyPotatoNG's type-9 logon trick, and the NTLM context-swap fix pushes LocalPotato into its HTTP variant. Section 7 is the offensive mirror image of this table. --- ## 7. The Potato family ### 7.1 Common prerequisites and the three COM roles > **Beginner note:** **`SeImpersonatePrivilege`** ("Impersonate a client after authentication") lets a process legitimately *use* a captured impersonation token — duplicate it and hand it to `CreateProcessWithTokenW`. **`SeAssignPrimaryTokenPrivilege`** ("Replace a process level token") lets a process swap in a new primary token via `CreateProcessAsUser`. Both are held by default by service accounts: IIS AppPool identities, MSSQL service accounts, `LOCAL SERVICE`, `NETWORK SERVICE`, scheduled-task service accounts. That is why every potato is a *service-account* to SYSTEM escalation, not user to SYSTEM. **Token-type math (all potatoes, [30]):** the captured SYSTEM token is an *impersonation* token. To run a process you either duplicate it to a primary token and call `CreateProcessWithTokenW` (needs `SeImpersonatePrivilege`), or call `CreateProcessAsUser` (needs `SeAssignPrimaryTokenPrivilege`). If the captured token is only Identification-level, both paths fail with error 1346 — the taxonomy's failure mode 3. > **Key idea:** **COM appears in this family in exactly three roles.** (a) The **trigger** — a 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, and that attackers fake or redirect. (c) The **callback surface** — attacker-implemented COM objects (fake `IUnknown`/`IRemUnknown2` servers) into which the privileged side calls back, enabling `CoImpersonateClient()`/`RpcImpersonateClient()`. Everything else in these chains — NTLM relay, named-pipe impersonation, MS-RPRN/MS-EFSR coercion, SMB context swapping — is RPC/SMB/SSPI, not COM. Precise attribution matters when you hunt for variants and when you write detections. ### 7.2 Pre-history: Hot Potato and RottenPotatoNG - **Hot Potato** (FoxGlove, January 2016, [29]): NBNS spoofing + WPAD + Windows Update HTTP NTLM to SYSTEM; patched as MS16-075 (Metasploit `windows/local/ms16_075_reflection`). No COM anywhere — included only because the name causes confusion. **RottenPotatoNG** (FoxGlove, [31]): the official C++ port of RottenPotato without the Meterpreter/Incognito dependency; the codebase SweetPotato and CandyPotato later wrapped. ### 7.3 RottenPotato: local NTLM reflection over DCOM (2016) **Since:** FoxGlove Security — @breenmachine & @vvalien1, September 2016 ([28], [31]) · **Affects:** ≤ Windows 10 **1803** / Server ≤ 2016 era; killed by the 1809 OXID changes · **COM role:** trigger + OXID redirect. RottenPotato is CVE-2015-2370's mechanism repackaged for post-MS15-076 builds: instead of reflecting raw RPC, it MITMs the local OXID resolution of a DCOM activation and completes the NTLM handshake against a *local* SSPI server context. #### Mechanics 1. **COM trigger:** `CoGetInstanceFromIStorage(NULL, CLSID_BITS, …)` with the hardcoded **BITS CLSID `{4991D34B-80A1-4291-83B6-3328366B9097}`** and a hand-crafted `IStorage` whose string bindings point at `127.0.0.1:6666`. This forces the BITS COM server (SYSTEM) to speak DCOM/RPC to the attacker's TCP listener. 2. **Byte-level relay (RPC, not COM):** the RPC conversation is relayed to the real OXID resolver on TCP 135 so the activation proceeds normally. 3. **The critical trick (SSPI):** when the NTLMSSP exchange begins, the MITM swaps the Type 2 message's **challenge plus "Reserved" field** — the Reserved field is a reference to the local `SecHandle` — with output of a local `AcquireCredentialsHandle` + `AcceptSecurityContext()` loop. 4. SYSTEM's Type 3 completes the *local* authentication → `ImpersonateSecurityContext()` yields a SYSTEM impersonation token → the original tool hands it to Meterpreter's Incognito to use. 5. Result: 100% reliable, instant, cross-version SYSTEM from any account holding `SeImpersonatePrivilege` — on builds up to 1803. ![Figure 4 — RottenPotato: BITS DCOM trigger into a local NTLM reflection loop](diagrams/d14-rottenpotato.png) *How to read this figure: the BITS server (SYSTEM) is tricked into authenticating to the attacker's local listener via the crafted `IStorage`. The MITM box in the middle splices the NTLM Type 2 Reserved field into a local `AcceptSecurityContext` loop; the output on the right is a SYSTEM impersonation token.* #### Prerequisites | Requirement | Detail | |---|---| | Privileges | `SeImpersonatePrivilege` (or `SeAssignPrimaryTokenPrivilege`) — service accounts | | OS/build | ≤ Windows 10 1803 / Server 2016 era; BITS abusable as trigger CLSID, local TCP listener allowed | #### Proof of concept ```powershell # Original workflow: Meterpreter session running as a service account *(illustrative)* load incognito execute -Hc -f RottenPotato.exe list_tokens -u # SYSTEM impersonation token now available impersonate_token "NT AUTHORITY\\SYSTEM" ``` #### OpSec & detection - Local listener on the hardcoded port 6666 and a byte-relay to TCP 135 on the same host — a service account opening loopback RPC relays is anomalous. Process lineage: a service account (IIS AppPool, MSSQL) spawning `cmd.exe`/payload as SYSTEM is the loudest indicator, shared by every potato. #### Mitigations - The 1809 / Server 2019 rpcss changes (section 6.3) kill the technique outright; minimize service-account privileges so the prerequisite is absent. ### 7.4 JuicyPotato: arbitrary-CLSID reflection (2018) **Since:** ohpe/@Giutro & @decoder_it, August 2018 ([32], [33], [34]) · **Affects:** up to Windows 10 **1803** / Server 2016; broken by the 1809/2019 silent fix · **COM role:** trigger + OXID redirect. "RottenPotatoNG on steroids": the same reflection engine, generalized to **arbitrary CLSID** (`-c`), **arbitrary local COM-listen port** (`-l`), **arbitrary payload program** (`-p`), with `-t *` trying both `CreateProcessWithTokenW` and `CreateProcessAsUser`. No Meterpreter dependency. #### Mechanics 1. The authors brute-forced and enumerated abusable CLSIDs. A usable CLSID must satisfy three criteria: **(1)** instantiable by the current (service) user, **(2)** implements **`IMarshal`**, **(3)** runs as an elevated account. 2. Per-OS CLSID lists were published at `ohpe.it/juicy-potato/CLSID/` — BITS `{4991D34B-80A1-4291-83B6-3328366B9097}`, wuauserv/aucore `{e60687f7-01a1-40aa-86ac-db1cbf673334}`, and others; DiagHub's own CLSID `{42CBFAA7-A4A7-47BB-B422-BD10E9D02700}` appears in the Server 2016 list. 3. The chosen CLSID replaces BITS in the RottenPotato trigger; the rest of the chain (Type 2 Reserved-field swap, local SSPI completion, token duplication) is identical. #### Prerequisites | Requirement | Detail | |---|---| | Privileges | `SeImpersonatePrivilege` and/or `SeAssignPrimaryTokenPrivilege` | | OS/build | ≤ Windows 10 1803 / Server 2016, with a valid CLSID for that exact OS from the published lists | #### Proof of concept ```powershell # *(illustrative — typical published invocation)* JuicyPotato.exe -l 1337 -p C:\Windows\System32\cmd.exe -t * -c "{4991D34B-80A1-4291-83B6-3328366B9097}" # -l local COM listen port, -p program to run, -t * = try both token APIs, -c CLSID ``` #### OpSec & detection - Same lineage indicator as RottenPotato; additionally, activation attempts against unusual service CLSIDs (wuauserv, DiagHub, PrintNotify) from a service-account process are high-fidelity, and repeated DCOM activation failures from one account (wrong-OS CLSID picks) are alertable noise. #### Mitigations - 1809/2019 rpcss hardening (section 6.3). The CLSID-list approach also died because the surviving abusable-CLSID set was hardened so captured tokens become Identification-level, and PrintNotify-style CLSIDs were gated to the INTERACTIVE group. ### 7.5 RogueWinRM: BITS over HTTP (side quest) **Since:** decoder's "From NETWORK SERVICE to SYSTEM" ([19]) + antonioCoco's RogueWinRM ([26]) · **Affects:** client SKUs where WinRM is not listening (the default) · **COM role:** none directly — HTTP — but the BITS trigger is the same service family. When BITS starts, it attempts HTTP NTLM authentication to `127.0.0.1:5985` (WinRM). If WinRM is off — the default on client SKUs — an unprivileged user can bind port 5985, trigger BITS, and capture the SYSTEM HTTP NTLM authentication into a local SSPI context, yielding a SYSTEM token. RogueWinRM ships as one of SweetPotato's methods (7.8), and is the minimal demonstration that the *trigger* and the *capture* are independent problems: swap DCOM for HTTP and the same token math applies. ### 7.6 RoguePotato: fake OXID resolver and token kidnapping (2020) **Since:** @decoder_it & @splinter_code, May 2020 ([14], [24]) · **Affects:** Windows 10 **1809+** / Server **2019+** — exactly where JuicyPotato died · **COM role:** trigger + fake OXID resolver (callback surface); the impersonation and token steps are pipes/SSPI. The comeback after the 1809 kills. The problem: post-1809, remote OXID queries are forced to port 135 and processed as **ANONYMOUS LOGON**, and the old local-resolver trick is dead. RoguePotato's answer: make the privileged client talk to an *attacker-controlled machine's* port 135, redirect that to a **fake OXID resolver**, and steer the client onto a named pipe where impersonation actually works. #### Mechanics 1. **COM trigger (COM):** `CoGetInstanceFromIStorage` with the BITS CLSID `{4991d34b-80a1-4291-83b6-3328366b9097}` (default; `-c` to override) and an `IStorage` whose string bindings point at a **remote attacker IP**. 2. **Redirect:** the attacker box runs `socat tcp-listen:135,reuseaddr,fork tcp::9999`, forwarding to the fake **RogueOxidResolver** RPC server — which can run on the victim on port 9999, or standalone on an 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`. (The first attempt, `ncacn_ip_tcp:localhost[9998]` with an `IRemUnknown2` callback and `RpcImpersonateClient()`, yielded only an **Identification** token — error-1346 dead end. Moving to the named-pipe transport was the fix.) 4. **Path-validation bug (borrowed from PrintSpoofer, credit @itm4n & @jonasLyk):** the `/` in the hostname is normalized into the pipe path, so rpcss connects to the attacker pipe **`\\.\pipe\roguepotato\pipe\epmapper`** — the protocol forces the `\pipe\epmapper` suffix, but the `/pipe/roguepotato` prefix redirects it. 5. **Impersonation (not COM):** `ImpersonateNamedPipeClient()` yields a **NETWORK SERVICE impersonation token** carrying the rpcss logon-session LUID. 6. **Token kidnapping:** rpcss/RpcEptMapper runs as NETWORK SERVICE but *shares a logon session with SYSTEM-token-holding threads* (Forshaw's "Sharing a Logon Session a Little Too Much", [6]). Enumerate handles in the rpcss process, duplicate a SYSTEM token, then `CreateProcessAsUser()`/`CreateProcessWithToken()` (with a Session-0 window-station permission fix) → SYSTEM. ![Figure 5 — RoguePotato: remote-triggered fake OXID resolver and token kidnapping](diagrams/d15-roguepotato.png) *How to read this figure: swimlanes separate what is COM (trigger, `ResolveOxid2` forgery) from what is pipe/SSPI (`ImpersonateNamedPipeClient`, token duplication). Follow the binding string `localhost/pipe/roguepotato[\pipe\epmapper]` — the slash normalization is what lands the SYSTEM-side rpcss on the attacker's pipe.* #### Prerequisites | Requirement | Detail | |---|---| | Privileges | `SeImpersonatePrivilege` | | OS/build | Windows 10 1809+ / Server 2019+ | | Config | Attacker-controlled machine reachable from the victim on TCP 135 (or a local resolver plus port proxy) | #### Proof of concept ```text # Attacker box (redirect 135 back to the victim's fake resolver): socat tcp-listen:135,reuseaddr,fork tcp::9999 # Victim, as the service account *(illustrative — typical published invocation)*: RoguePotato.exe -r -e "C:\Windows\System32\cmd.exe" -l 9999 ``` #### OpSec & detection - Outbound/inbound TCP 135 to a non-domain-controller from a server running a service-account process is the loudest network indicator; `socat` on the attacker side is your own infrastructure. - Local artifacts: the named pipe `\\.\pipe\roguepotato\pipe\epmapper`, handle enumeration inside the rpcss process, and a payload process spawned from a duplicated rpcss token (NETWORK SERVICE context flipping to SYSTEM). #### Mitigations - Block workstation/server egress to untrusted hosts on TCP 135; monitor for unexpected OXID resolution. The token-kidnapping half depends on the shared-logon-session design, not a CVE; least-privilege service accounts reduce impact. ### 7.7 PrintSpoofer: pipe-name normalization (2020) **Since:** itm4n, March 2020 ([12], [13]); coercion primitive from Lee Christensen's SpoolSample ([40]) · **Affects:** Windows 10, Server 2016/2019 (and older with the spooler running) · **COM role:** none — pure RPC coercion plus pipe impersonation; included because it supplies the path-normalization bug RoguePotato reuses. #### Mechanics 1. **Coercion (pure RPC):** MS-RPRN **`RpcRemoteFindFirstPrinterChangeNotificationEx`** forces the **Print Spooler (SYSTEM)** to connect back to an attacker-named notification pipe. 2. **The bug:** 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` even though the real `\pipe\spoolss` endpoint is occupied. 3. **Token half:** `CreateNamedPipe` → client connects → `ImpersonateNamedPipeClient()` → `OpenThreadToken` → `DuplicateTokenEx` (to primary) → `CreateProcessWithTokenW` → SYSTEM. #### Prerequisites | Requirement | Detail | |---|---| | Privileges | `SeImpersonatePrivilege` | | OS/build | Windows 10, Server 2016/2019 (older with spooler); Print Spooler service running | #### Proof of concept ```powershell # From a service-account shell: interactive SYSTEM cmd on the console, or any -c command PrintSpoofer.exe -i -c cmd PrintSpoofer.exe -c "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -enc " ``` #### OpSec & detection - The SYSTEM spooler process connecting to a non-standard named pipe (`\\.\pipe\*\pipe\spoolss`) is the signature; the pipe name is attacker-chosen, so match the pattern, not the string. Also: service account spawning a child as SYSTEM via `CreateProcessWithTokenW`. #### Mitigations - Disable the Print Spooler where printing is not required. > **Warning (patch status, MEDIUM confidence):** PrintSpoofer was **unpatched at publication**; later Windows builds hardened spooler behaviors, and PrintNotify-based alternatives exist for hardened or no-spooler hosts. Treat current effectiveness as **build-dependent — verify on the target build** before relying on it. ### 7.8 SweetPotato: the C# bundle (2020) **Since:** @_EthicalChaos_ / CCob, 2020 ([38], [39]); decoder's companion analysis "We thought they were potatoes but they were beans" ([20]) · **Affects:** "Local Service to SYSTEM … from Windows 7 to Windows 10 / Server 2019" · **COM role:** inherited from Rotten/Juicy (DCOM trigger) plus the named-pipe half from PrintSpoofer. A single C# toolkit bundling the RottenPotato code base, the weaponized JuicyPotato BITS+WinRM approach (credit @decoder_it/@Guitro), RogueWinRM, and PrintSpoofer; it auto-attempts the correct technique for the target (`-c` CLSID, default BITS `{4991D34B-80A1-4291-83B6-3328366B9097}`; `-m` method). Its operational value is one binary for the whole pre-2020 family, executed in memory through your .NET loader of choice. ```powershell SweetPotato.exe -p C:\Windows\System32\cmd.exe -a "whoami > C:\Windows\Temp\sp.txt" # *(illustrative)* ``` > **Note (MEDIUM confidence):** the exact internal method list varies by release version; confirm `-m` options against the version you actually deploy. ### 7.9 JuicyPotatoNG: type-9 logon and an SSPI hook (2022) **Since:** @decoder_it & @splinter_code, September 2022 ([16], [25]); companion piece "The impersonation game" ([17]) · **Affects:** Windows 10 **1809+ / 11**, Server **2019+ / 2022** · **COM role:** trigger + local callback surface with an SSPI hook. The problem solved: post-1809, the surviving abusable CLSIDs require the caller to be **INTERACTIVE**, which service accounts are not. JuicyPotatoNG restores the Juicy workflow on modern builds with two tricks. #### Mechanics 1. **Token enrichment:** `LogonUser()` with **logon type 9 (`NewCredentials`)** — LSASS clones the caller's token and **adds the INTERACTIVE SID** (no impersonation privilege is needed for the clone). The enriched token can activate the INTERACTIVE-gated CLSIDs. 2. **Trigger:** DCOM activation against the default CLSID **`{854A20FB-2D44-457D-992F-EF13785D2B51}`** — PrintNotify, the "Printer Extensions and Notifications" service, SYSTEM, present on Windows 10/11 and Server 2016/2019/2022 — with the local COM server on port **10247**. 3. **Token capture:** an **SSPI hook on `AcceptSecurityContext()`** intercepts the privileged DCOM client's NTLM exchange as it is processed in-process. This avoids `RpcServerUseProtseqEp` (which would hold the port) and, per the authors, relaxes requirements versus `RpcImpersonateClient()`. The approach is conceptually rooted in Forshaw's "Windows Exploitation Tricks: Relaying DCOM Authentication" (October 2021, [5]). 4. Verified PoC output: `[+] authresult success {854A20FB-…};NT AUTHORITY\SYSTEM;Impersonation` — an impersonation-level SYSTEM token, duplicated and passed to the usual `CreateProcessWithTokenW`/`CreateProcessAsUser` pair. #### Prerequisites | Requirement | Detail | |---|---| | Privileges | `SeImpersonatePrivilege`; local credentials for the type-9 `LogonUser` clone | | OS/build | Windows 10 1809+ / 11, Server 2019+ / 2022; PrintNotify CLSID present, port 10247 free | #### Proof of concept ```powershell # *(illustrative — typical published invocation; watch for the authresult success line)* JuicyPotatoNG.exe -t * -p C:\Windows\System32\cmd.exe -a "whoami > C:\Windows\Temp\ng.txt" ``` #### OpSec & detection - `LogonUser` type-9 (`NewCredentials`) events from a service account are unusual and alertable. In-process SSPI hooking means no extra listener service; detection leans on activation of the PrintNotify CLSID by a service account and the standard potato lineage indicator. #### Mitigations - The INTERACTIVE-gating mitigation is what this tool circumvents via LSASS behavior, not a patched bug; Microsoft fixes have targeted individual pieces (CLSID gating, resolver behavior) rather than the pattern — verify per build. ### 7.10 LocalPotato: NTLM context swapping (2023) **Since:** @decoder_it & @splinter_code, February 2023 ([21], [23]); HTTP edition November 2023 ([22]) · **Affects:** builds before the January 2023 patch (**CVE-2023-21746**) for the SMB scenario; HTTP/WebDAV scenario unpatched · **COM role:** trigger only. > **Key idea:** LocalPotato is **not a DCOM bug**. COM/DCOM is used only as the trigger; the vulnerability lives in NTLM local authentication inside LSASS (`msv1_0.dll`), and the write primitive is SMB. Mislabeling it as a COM vulnerability will send your variant hunting to the wrong DLL. #### Mechanics 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", Windows 10/11 only) and **`{A9819296-E5B3-4E67-8226-5E72CE9E1FB7}`** (Universal Print Management Service "McpManagementService", Windows 11 + Server 2022). A fake local OXID resolver (JuicyPotatoNG-style SSPI hooks, default port 10271) returns security bindings containing a **crafted SPN `cifs/127.0.0.1`**, so the privileged DCOM client initiates NTLM with `ISC_REQ_UNVERIFIED_TARGET_NAME` and that SPN. 2. **The bug (NTLM/LSASS):** 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:** the attacker's SMB client is authenticated to `\\127.0.0.1\C$` **as SYSTEM** → arbitrary file read/write (the PoC writes raw SMB2 packets per MS-SMB2). 4. **Weaponization paths:** write `SprintCSP.dll` to System32 and trigger `SvcRebootToFlashingMode` (StorSvc RPC, via BlackArrowSec's RpcClient, released 2023-02-13); or overwrite `printconfig.dll` and instantiate **PrintNotify** (the authors' Server 2022 demo); or an XPS Print Job / NetMan DLL hijack. 5. **Patch:** January 2023 (CVE-2023-21746) changed `SsprHandleChallengeMessage`: if `ISC_REQ_UNVERIFIED_TARGET_NAME` is set and an SPN is present, the SPN is forced to NULL, so SMB anti-reflection denies `cifs/127.0.0.1`. **The HTTP/WebDAV scenario remained unpatched by Microsoft decision** — "LocalPotato HTTP edition" ([22]) — and 0patch covers EOL versions. #### Prerequisites | Requirement | Detail | |---|---| | Privileges | `SeImpersonatePrivilege` (service-account context) | | OS/build | SMB scenario: builds before January 2023 patches; HTTP edition: unpatched at publication. Local SMB server reachable on 127.0.0.1:445; a SYSTEM-authenticating CLSID for the trigger | #### Proof of concept ```powershell # *(illustrative — typical published invocation; writes attacker file to a privileged path)* LocalPotato.exe -i C:\users\public\payload.dll -o C:\Windows\System32\SprintCSP.dll ``` #### OpSec & detection - Loopback SMB sessions to `\\127.0.0.1\C$` authenticating as SYSTEM from a service-account context are the signature; the trigger is a DCOM activation of PrintNotify/AxInstSv/McpManagementService by a service account — same indicator family as JuicyPotatoNG. - Weaponization touches well-known System32 binaries (`SprintCSP.dll`, `printconfig.dll`); file-integrity monitoring catches the write. #### Mitigations - January 2023+ patches close the SMB scenario (CVE-2023-21746). > **Warning (patch status):** the **HTTP/WebDAV edition was left unpatched by Microsoft decision** as of the November 2023 publication; 0patch covers EOL versions. Verify per target build and month before assuming coverage. ### 7.11 The rest of the family: classification table The family is much larger than the eight tools above. Verified existence and classification (per-author claims; mechanism column is the part that matters for variant hunting): | 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 | Win8–11, Server 2012–2022 ([44]) | | **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 2012–2022 ([43]) | | **PrintNotifyPotato** | BeichenDream, late 2022 | PrintNotify CLSID + fake `IUnknown`/pointer-moniker callback; works even with legacy Spooler disabled | **Yes** | Win10/11, Server 2012–2022 ([46]) | | **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 2012–2022 era (pre-EFSR hardening); C# ([41]) | | **SharpEfsPotato** | bugch3ck | EfsRpc + pipe impersonation built from SweetPotato + SharpEfsTrigger | No | similar ([42]) | | **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 | **No — pure RPC coercion** | Win10/11, Server 2022 (verified screenshot build 22621) ([47]) | | **PetitPotato** | wh0amitz | Local PetitPotam (EFSRPC) → pipe | No | — ([48]) | | **RasmanPotato** | crisprss | Coerces **RasMan** service (SYSTEM) via its RPC interface → pipe impersonation | No | Win10, Server 2012–2019 ([49]) | | **BadPotato** | BeichenDream | itm4n's PrintSpoofer ported to C# | No | Win8–10, 2012–2019 ([45]) | | **CandyPotato** | klezVirus | Pure C++ weaponized RottenPotatoNG | Yes (Rotten lineage) | Win10/2016 era ([50]) | | **MultiPotato** | S3cur3Th1sSh1t | Multi-trigger combo | mixed | — ([51]) | | **SigmaPotato** | tylerdotrar, 2023–24 | Modernized GodPotato-fork style; `--revshell`, in-memory `[SigmaPotato]::Main()` via .NET reflection | Yes (DCOM lineage) | Win8–11, 2012–2022 ([52]) | | **GenericPotato** | Micah van Deusen, 2021 | Generic SeImpersonation → SYSTEM via modified SSPI/HTTP named-pipe-less approach | partial | — ([54]) | | **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 ([53]) | | **RemotePotato0** | antonioCoco/SentinelOne, 2021 | Cross-protocol relay RPC→HTTP, "Won't Fix" — **remote** user→DA, not local LPE; boundary case | partial | — ([55]) | | **CertPotato** | SensePost, 2022 | ADCS cert path from virtual/network service accounts | No | — ([56]) | | **LonelyPotato** | decoder, Dec 2017 | RottenPotato standalone (no Meterpreter) | Yes | ≤1803 ([18]) | > **Warning:** **No tool called "SharpPotato" exists.** If a runbook, wiki, or teammate references it, the intended tool is almost certainly SweetPotato (the C# bundle), BadPotato (PrintSpoofer's C# port), or SharpEfsPotato. Do not waste engagement time hunting for a phantom repository. > > Boundary cases: **RemotePotato0** ([55]) is a *remote* cross-protocol relay (RPC→HTTP, rated "Won't Fix", remote user→DA), not a local LPE — file it, and IBM X-Force's DCOM NTLM coercion work RemoteMonologue ([62]), under lateral movement rather than this chapter. For a narrated history of the whole family, see Antonio Cocomazzi's "10 Years of Windows Privilege Escalation with Potatoes" slides (POC2023/Troopers24, [27]). ### 7.12 Potato decision tree ![Figure 6 — Potato lineage 2016 to 2023+, aligned against the Microsoft hardening timeline](diagrams/d16-potato-timeline.png) *How to read this figure: time runs left to right; each potato is color-coded by its mechanism (COM trigger, RPC coercion, NTLM/LSASS). The vertical markers are the hardening events of section 6.3 — notice every new tool appears immediately after a mitigation row.* Text form of the decision tree — run this on every foothold: 1. `whoami /priv` → is `SeImpersonatePrivilege` or `SeAssignPrimaryTokenPrivilege` present? (No → not a potato target; look at sections 4, 8, 9 instead.) 2. OS ≤ Win10 1803 / Server 2016 → **RottenPotatoNG / JuicyPotato** (BITS or a list CLSID for the exact OS). 3. OS ≥ Win10 1809 / Server 2019 → can the victim reach your box on TCP 135 (or can you run a local resolver plus port-proxy)? → **RoguePotato**. Else → interactive-capable or service account? → **JuicyPotatoNG** (LogonUser type-9 trick) or **GodPotato / DCOMPotato / PrintNotifyPotato** (service DCOM callbacks). 4. Spooler up, Win10/2016–2019 → **PrintSpoofer** (C#: **BadPotato**). Spooler down or hardened → **EfsPotato / CoercedPotato** coercion alternatives. 5. Need a file-write primitive on builds ≤ January 2023 → **LocalPotato** (+ StorSvc/PrintNotify weaponization); patched → **LocalPotato HTTP/WebDAV edition**. 6. .NET 4 present and OpSec matters → **SweetPotato / GodPotato (.NET 4 build) / SigmaPotato** for in-memory execution. --- ## 8. Trapped COM objects (Forshaw, January 2025) **Since:** James Forshaw, Project Zero, January 30 2025 — "Windows Bug Class: Accessing Trapped COM Objects with IDispatch" ([4]) · **Affects:** current Windows 10/11 including 24H2 (with caveats below); Microsoft does not treat PPL as a security boundary, so fixes ship late and in new Windows versions. ### 8.1 The bug class > **Beginner note:** Object-oriented remoting (DCOM, .NET Remoting) marshals returned objects **by reference** by default: the object itself stays — is "trapped" — inside the server process, and the client receives a remote pointer. Every method call the client makes then executes *in the server's security context*. If the object was never designed to be exposed across the trust boundary, the client is driving privileged code. > **Beginner note:** **PPL** (Protected Process Light) is Windows' process-protection model: a PPL process can only be opened and injected into by processes signed at an equal or higher protection level (e.g. `PsProtectedSignerWindows-Light`). LSASS and modern AV/EDR processes run protected, so unsigned code inside a PPL process is a significant offensive prize. Three introduction scenarios produce trapped objects: 1. **Unsafe object shared inadvertently — CVE-2019-0555.** WinRT needed an XML document; developers bolted runtime interfaces onto the XML DOM Document v6 COM object assuming no XSLT. A malicious client `QueryInterface`s the legacy `IXMLDOMDocument`, regains XSLT script execution → sandbox escape. 2. **Asynchronous marshaling primitive.** .NET classes that are both serializable and `MarshalByRefObject` (e.g. `FileInfo`/`DirectoryInfo`): send a serialized copy to the server, then read it back — the read is marshaled **by reference**, yielding a trapped live object with which to create files as the server. Implemented in Forshaw's `ExploitRemotingService` tool. 3. **Abusing the platform's object-instantiation plumbing — CVE-2017-0211.** Coerce a path to `CoCreateInstance` with an attacker CLSID and get the object back: Structured Storage exposed across the boundary; `IPropertyBag` → create an arbitrary COM object in the server → XML DOM → XSLT EoP. ### 8.2 The IDispatch / ITypeInfo vector (the new part) Any OOP server exposing **`IDispatch`** must vend type information: the client calls `IDispatch::GetTypeInfo` → **`ITypeInfo`**, which is itself marshaled by reference — trapped in the server. `ITypeInfo::Invoke` is *not* remotable, but **`ITypeInfo::CreateInstance` IS** — and it calls `CoCreateInstance` **inside the server process**. The type info must describe a **CoClass** (it carries a CLSID); reach candidate classes via `ITypeInfo::GetContainingTypeLib` → `ITypeLib` → enumerate classes, including **referenced type libraries** (`stdole` is virtually always referenced). Worked example from the post, using the OleViewDotNet PowerShell module ([4], [8]): ```powershell Get-ComClass -Service # service-hosted COM classes # → 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} $o = New-ComObject -Clsid 72566e27-1abb-4eb3-b4f0-eb431cb1cb32 $lib = Import-ComTypeLib -Object $o Get-ComObjRef $o # proves OOP hosting: shows svchost.exe $lib.Parse(); $lib.ReferencedTypeLibs # → stdole 2.0 {00020430-0000-0000-C000-000000000046} # stdole classes: StdFont {0BE35203-8F91-11CE-9DE3-00AA004BB851}, StdPicture {0BE35204-…} ``` Both stdole classes are registered **InProcServer32 only (`oleaut32.dll`)**, so instantiating one *through the remote type library* plants it inside the service. Navigation: `GetRefTypeOfImplType(0)` → `GetRefTypeInfo` → `GetContainingTypeLib` → `GetTypeInfoOfGuid()` → `CreateInstance()` → `Get-ComObjRef` proves the new object lives in the service process. (StdPicture has an OOP check; StdFont does not. Forshaw found no direct EoP in the font object itself — it is a stepping stone.) **Type-library/proxy substitution → type confusion.** The stub for a 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 view of the types — classic type confusion (Forshaw's earlier NGEN-COM attack overwrote the KnownDlls handle this way). Microsoft's countermeasure now lives in `oleaut32.dll`: **`VerifyTrust`** — when loading a TLB inside a protected process it requires a cached signing level **≥ 12 (Windows)** via `NtGetCachedSigningLevel`/`NtSetCachedSigningLevel`; raw (non-PE) TLBs fail with `STATUS_INVALID_IMAGE_FORMAT`. The same hardening blocks ROT-based OOP type-library references. ### 8.3 The PPL injection chain (flagship PoC) `WaaSRemediationAgent` runs inside **`WaaSMedicSvc` as PPL `PsProtectedSignerWindows-Light`** (`$cls.AppIDEntry.ServiceProtectionLevel = WindowsLight`) — the service family that also gates access to LSASS-level protected processes. The chain ([4]): 1. Set `HKLM\SOFTWARE\Microsoft\.NETFramework\AllowDCOMReflection = 1` (DWORD) — re-enables .NET DCOM reflection disabled since **MS14-009** — and `OnlyUseLatestCLR = 1` to force CLR v4 (otherwise v2 loads). *(Admin is needed for HKLM; per-user HKCU COM/.NET registration can substitute — Forshaw: "the general technique does not [require admin]" given 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 `CreateInstance` → the **CLR loads inside the PPL** and returns `System.Object`. 4. `GetType` → .NET reflection → **`System.Reflection.Assembly::Load(byte[])`** → unsigned code executes inside a PPL-Windows process → instantiate an object in the loaded assembly → run code (LSASS-with-PPL and protected AV/EDR processes are now reachable). Clean up the registry afterwards. 5. **Windows 11 24H2 wrinkle:** the 64-bit `mscorlib.tlb` is a raw TLB and fails the `VerifyTrust` signing level (`STATUS_INVALID_IMAGE_FORMAT`); the **32-bit** `Framework\v4.0.30319\mscorlib.tlb` is PE-packed (MZ), so setting the cached signing level (or swapping the TLB registration path to the 32-bit file) makes the PoC work again. Forshaw did **not** release his PoC (C++; "very similar to my CVE-2014-0257 exploit", [7]). ![Figure 7 — Trapped COM objects: IDispatch to StdFont CreateInstance to CLR inside PPL](diagrams/d17-trapped-objects.png) *How to read this figure: each arrow crosses a trust boundary into the service process. The left chain (`IDispatch` → `ITypeInfo` → stdole → StdFont) is pure COM plumbing; the right chain (`TreatAs` → `System.Object` → `Assembly.Load`) is the .NET payload delivery. The side panel marks where oleaut32 `VerifyTrust` hardening can break the chain on 24H2.* ### 8.4 Third-party PoCs and detection - **ComDotNetExploit** — Mohamed Fakroud's writeup plus PoC for the PPL injection ([59], [60]). - **ForsHops** — IBM X-Force Red's "Fileless lateral movement with trapped COM objects" and tool: `forshops.exe [target] [c:\path\to\assembly]` ([57], [58]). Detection guidance (Samir Bousseaden): watch for a `TreatAs` key appearing on the StdFont CLSID, the `AllowDCOMReflection`/`OnlyUseLatestCLR` values, and a CLR load inside the WaaSMedicSvc svchost; YARA strings include the WaaSRemediation CLSID `{72566E27-…}` and `{34050212-8AEB-416D-AB76-1E45521DB615}`. - **Outflank (Kyle Avery, July 2025)** used LLM-assisted enumeration to find **alternative IDispatch-exposing classes**, avoiding the PPL CLR-load limitation entirely ([61]) — the bug class is a *class*, not one service. Framing for operators: the LPE angle is sandbox/AppContainer escapes and PPL injection rather than plain user→SYSTEM; because Microsoft does not treat PPL as a security boundary, expect these primitives to live long. --- ## 9. Other documented COM LPE vectors 1. **UPnP Device Host + Update Orchestrator chain (NCC Group / Fox-IT, November 2019, [63]).** **CVE-2019-1405** — logical flaw in the UPnP Device Host service's COM objects `UPnPContainerManager`/`UPnPContainerManager64`, implementing the undocumented `IUPnPContainerManager` (IID `6D8FF8D4-730D-11D4-BF42-00B0D0118B56`; methods `ReferenceContainer`, `UnReferenceContainer`, `CreateInstance`, `CreateInstanceWithProgID`, `Shutdown`) → any local user executes commands as **LOCAL SERVICE**. Fixed November 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 UsoSvc` → `sc config UsoSvc binpath= …` → `sc start`), which runs as SYSTEM; fixed October 2019 by stripping Full Control from the SERVICE group. Chained: any local user → SYSTEM on Windows 10 1803–1903. PoC: **COMahawk** (`COMahawk64.exe`). The canonical "service exposes a dangerous COM API to everyone" teaching example, and the writeup's methodology section is a ready-made OleViewDotNet walkthrough. 2. **COM Aggregate Marshaler and type-library bugs (Forshaw, May 2017).** **CVE-2017-0213** (Aggregate Marshaler `CStdMarshal::Finish_RemQIAndUnmarshal2` RQI2 unpack bug; the PoC uses BITS `SetNotifyInterface` → type-library load under impersonation → **scriptlet moniker** → code execution in BITS as SYSTEM; exploit-db 42020; later entered CISA KEV, [65]) and **CVE-2017-0214** (type-library input validation). Pattern: callback interface plus type-library processing equals code execution inside the privileged server. 3. **Session Moniker / interactive-user DCOM (2017).** CVE-2017-0100 plus bad-fix CVE-2017-0298 — full treatment in section 6.2; pattern: `RunAs=Interactive User` + cross-session activation + missing client verification. 4. **WalletService memory corruption over COM — CVE-2020-1362 (Q4n/Haoran Qin + Zhiniang Peng, July 2020, [64]).** The WalletService COM server: `Wallet::WalletCustomProperty::SetGroup` OOB write → `SetLabel` BSTR pointer write-what-where → fake vtable → redirect a virtual call to `dxgi!ATL::CComObject::vector deleting destructor`, whose `LoadLibraryExW` uses a writable global path → load an attacker DLL as SYSTEM; chained with CVE-2020-1361 for the heap leak. Medium IL → SYSTEM, verified on build 18363. Pattern: **DCOM method parameters → memory corruption in the privileged server** — the marshaling layer is the attack surface, not the business logic. 5. **Per-user COM hijack as an LPE vector.** User-writable per-user registrations (`HKCU\Software\Classes\CLSID\{…}\InprocServer32`, `TreatAs`, AppID paths) shadow machine registrations for classes activated *in the user's logon session*. Classic uses are UAC bypass and persistence (bohops, [69], [70]; 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 performs the PPL injection *without* admin (section 8.3). Triage question: "what identity activates user-scope classes on this box?" 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 processes. Leveraged by CVE-2014-0257 (Forshaw's IE sandbox escape, public PoC, [7]), by 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 + a .NET class = unsigned code inside signed processes.** 7. **WinRT brokers.** WinRT is COM underneath: runtime classes, activation via combase, broker processes crossing IL/AppContainer boundaries. Verified in-scope example: **CVE-2019-0555** (XML DOM through WinRT, section 8.1). Brokers are OOP COM servers with sandbox-facing activation permissions — triage them with exactly the section 10 checklist. 8. **DiagHub sibling CVEs.** CVE-2018-0952 ([66]) and the case-sensitivity directory-traversal EoP ([67]) — covered with the case study in section 4; "DCOM service with file-manipulation methods = recurring CVE farm". 9. **Adjacent but NOT COM — do not mislabel.** **Perfusion** (itm4n) is the `RpcEptMapper` registry-key ACL bug (Win7/2008R2/8/2012) — RPC, not COM. **FullPowers** recovers the LOCAL/NETWORK SERVICE default privileges through the task scheduler — not COM. **CVE-2020-1337** is a Print Spooler file-write — RPC. Precision here is not pedantry: it decides which hardening rows of section 6.3 apply, and which fuzzer you point at the target in section 10. --- ## 10. Research Lab: Finding New COM LPE Bugs Everything above was found with the workflow below. The category is not mined out: DiagHub kept producing CVEs for years, the potato family respawns after every mitigation, and the trapped-object class produced a flagship result in January 2025. ### 10.1 Hunting hypothesis The classes of *new* bugs this category still hides: - OOP servers whose methods touch attacker-controlled paths without impersonation, or with check-then-revert gaps (bug classes 1–2) — especially newly shipped inbox services. - Service-hosted classes whose AppID grants AppContainer/LPAC activation — a sandbox-escape pipeline identical to DiagHub's original audience. - Callback-accepting methods (interface pointers as parameters) where the server calls back with `RPC_C_IMP_LEVEL_IMPERSONATE` — the DCOMPotato pattern, almost certainly present in unaudited services. - `IDispatch`-exposing service classes beyond the five default-box candidates (alternative trapped-object entries, as Outflank's LLM-assisted enumeration demonstrated, [61]) and type-library trust / `TreatAs` paths that survive the `VerifyTrust` signing-level hardening. ### 10.2 Enumeration **OleViewDotNet** (Forshaw, [8]; .NET 4; PowerShell module `Install-Module OleViewDotNet` — **PowerShell 5 only, not 7+**): - GUI: `Registry → Local Services` (COM objects hosted in services, filterable), `Registry → CLSIDs by Server`, and per-object **View Launch Permissions / View Access Permissions** (decoded AppID SDDLs). - PowerShell: ```powershell Get-ComClass -Service # service-hosted classes Get-ComClass -Clsid $clsid | % Servers # InProc vs Local servers $cls = Get-ComClass -Clsid $clsid $cls.AppIDEntry # RunAs, service name, SDDLs $cls.AppIDEntry.ServiceProtectionLevel # PPL check (e.g. WindowsLight) # object walk (New-ComObject, Get-ComInterface, Import-ComTypeLib, Get-ComObjRef): section 8.2 ``` **Raw registry:** `HKLM\SOFTWARE\Classes\CLSID`, `…\AppID\{guid}` values `LaunchPermission`, `AccessPermission`, `RunAs`, `Service`, `ServiceProtectionLevel`; machine defaults under `HKLM\SOFTWARE\Microsoft\Ole`; per-user shadow at `HKCU\Software\Classes\CLSID`. **Existing abusable-CLSID corpora:** the per-OS lists at `ohpe.it/juicy-potato/CLSID/` ([33]), the strontic xcyclopedia CLSID library, and the juicy-potato repo README tables ([34]). **RPC/RPCSS surface:** RpcView (endpoint/interface dump), impacket `rpcdump.py`, and the Wireshark DCERPC + DCOM (`IObjectExporter`/`IRemUnknown2`) dissectors for watching activations on the wire. ### 10.3 Triage checklist A target is promising if: 1. OOP server (`LocalServer32` or service-hosted AppID). 2. Runs as SYSTEM (or LOCAL/NETWORK SERVICE for a hop). 3. Launch **and** access ACLs include your principal — Authenticated Users, INTERACTIVE, or AppContainer/LPAC for sandbox escapes. 4. Methods touch filesystem paths, the registry, process creation, `LoadLibrary*`, `SetNamedSecurityInfo`, or token APIs; or accept interface pointers (callback impersonation — the DCOMPotato pattern); or return objects (the trapped-object pattern). 5. Bonus: `IDispatch` present (type-library vector), .NET interop, or `RunAs=Interactive User`. ### 10.4 Static RE checklist (Forshaw's DiagHub recipe, generalized) 1. OleViewDotNet: create an instance → Marshal ⇒ View Properties → capture **PID + IPID + OXID + string bindings + authentication services**. 2. OVDN process view (needs admin and configured symbols: dbghelp from WinDbg plus `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 + L` → symbolized method names (public symbols exist on Microsoft components). 4. OVDN **View Proxy Definition** → decompiles the NDR stubless bytecode → method signatures; the **Structures tab** → marshaled struct layouts → rebuild the interface in C++/C#. 5. In IDA/Ghidra: xref command strings (itm4n's `"StartScan"` technique, section 5.3), map `_guard_dispatch_icall_fptr` indirect calls to VTable slots via dynamic breakpoints, and match client proxy slots (`ObjectStublessClientN`) to server symbols. 6. Hunt the failure modes of section 3: missing or late `CoImpersonateClient`/`RpcImpersonateClient`, early `RevertToSelf`, identify-level acceptance, device-map/symlink handling under impersonation, `RunAs` mismatches, type-library/TLB trust, `TreatAs`, unmarshaled-object safety. ### 10.5 Dynamic harness Minimal C++ client — the same shape as the USO reconstruction (section 5.2), generalized *(illustrative skeleton)*: ```cpp CoInitializeEx(0, COINIT_MULTITHREADED); IUnknown* pUnk = nullptr; CoCreateInstance(clsid, nullptr, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&pUnk)); // Offer impersonation — the contract from section 2: CoSetProxyBlanket(pUnk, RPC_C_AUTHN_DEFAULT, RPC_C_AUTHZ_DEFAULT, COLE_DEFAULT_PRINCIPAL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, EOAC_NONE); // QueryInterface to the target IID, then invoke methods with controlled inputs: // path arguments (junctions, symlinks, device-map paths, odd extensions) and // interface-pointer arguments (your own fake IUnknown/IMarshal implementations) CoUninitialize(); ``` Observe the server side with **Process Monitor** (filter: server PID; watch file/registry/LoadImage operations executing as SYSTEM), API Monitor, and WinDbg breakpoints on `CoImpersonateClient`, `RpcImpersonateClient`, `CreateFileW`, `RegCreateKeyExW`, `LoadLibraryExW`, `SetNamedSecurityInfo`. ETW providers Microsoft-Windows-RPC and COM give call tracing. Use **TokenViewer** (Forshaw) to inspect any captured token — impersonation vs identification is the number-one source of "the exploit didn't work" confusion (error 1346, section 2). ### 10.6 Fuzzing marshal paths - Mutate marshaled NDR buffers against the **NDR interpreter stubs** — the exact layer OVDN decompiles in step 10.4.4. Watch for OOB behavior in server-side unmarshalling (the WalletService class, CVE-2020-1362) and for marshal-by-reference object leaks (section 8). - Prior art: **COMRace** (USENIX Security '22 — data-race detection in COM objects, [71]); **Black Hat EU 2024 "Enhancing Automatic Vulnerability Discovery for Windows RPC/COM in New Ways"** (harness generation, [72]). Poor-man's fuzzer: interface brute-calling via the OVDN PowerShell module (`New-ComObject` + method invocation with mutated arguments), and RPC-aware fuzzing driven from IDA-generated clients. ### 10.7 Patch diffing BinDiff/Ghidra the usual binaries: `rpcss.dll`, `combase.dll`, `ole32.dll`, `oleaut32.dll`, `usosvc.dll`, `usocoreworker`, and the DiagHub runtime. Proven patterns to look for: - New ACL checks inside methods (the CVE-2019-1405 fix). - Service security-descriptor changes (the CVE-2019-1322 fix). - **Silent** binding-builder changes (the 1809 `MakeBinding` → `CreateRemoteBindingToOR` rewrite). - Image-load and signing trust: `ProcessImageLoadPolicy` (19H1); signing-level enforcement (`VerifyTrust` in oleaut32). - NTLM package changes (`SsprHandleChallengeMessage`, CVE-2023-21746 — diff `msv1_0.dll`). A silent diff in any of these is a map of what the previous technique did — and usually of what a variant must now avoid. ### 10.8 From crash or lead to primitive - A missing-impersonation lead becomes a primitive when you can (a) control the resource path and (b) name a target file the server will touch — prove it with ProcMon showing a SYSTEM-integrity `CreateFile` on your path. - A captured token becomes a primitive only at `RPC_C_IMP_LEVEL_IMPERSONATE`; check it in TokenViewer before writing any process-spawning code, and expect error 1346 otherwise. - A marshaling crash becomes a primitive when you control the overwritten offset — the WalletService writeup ([64]) is the model for turning DCOM-parameter corruption into a vtable redirect. - A trapped object becomes a primitive when `Get-ComObjRef` proves it lives in the privileged process and you can reach a `CreateInstance`-capable type info (section 8.2). ### 10.9 Further reading - Windows Bug Class: Accessing Trapped COM Objects with IDispatch ([4]) - Weaponizing Privileged File Writes with the USO Service, parts 1–2 ([9], [10]) - CVE-2019-1405 and CVE-2019-1322 — Elevation to SYSTEM via UPnP Device Host and Update Orchestrator ([63]) - DCOM Turns 30: Revisiting a Legacy Interface in the Modern Threatscape, hack.lu 2025 slides ([73]) --- ## 11. References **Project Zero / Forshaw (primary sources)** - [1] Windows Exploitation Tricks: Exploiting Arbitrary File Writes for Local Elevation of Privilege (DiagHub) — James Forshaw, Project Zero (2018) — https://googleprojectzero.blogspot.com/2018/04/windows-exploitation-tricks-exploiting.html (mirror: https://projectzero.google/2018/04/windows-exploitation-tricks-exploiting.html) - [2] Project Zero issue 1428 (StorSvc `SvcMoveFileInheritSecurity`, full exploit incl. DiagHub loader) — James Forshaw (2018) — https://bugs.chromium.org/p/project-zero/issues/detail?id=1428 - [3] Project Zero issue 325 (DCOM DCE/RPC Local NTLM Reflection → CVE-2015-2370) — James Forshaw (2015) — https://bugs.chromium.org/p/project-zero/issues/detail?id=325 - [4] Windows Bug Class: Accessing Trapped COM Objects with IDispatch — James Forshaw, Project Zero (2025) — https://projectzero.google/2025/01/windows-bug-class-accessing-trapped-com.html (mirror: https://googleprojectzero.blogspot.com/2025/01/windows-bug-class-accessing-trapped-com.html) - [5] Windows Exploitation Tricks: Relaying DCOM Authentication — James Forshaw, Project Zero (2021) — https://googleprojectzero.blogspot.com/2021/10/windows-exploitation-tricks-relaying.html - [6] Sharing a Logon Session a Little Too Much — James Forshaw (2020) — https://www.tiraniddo.dev/2020/04/sharing-logon-session-little-too-much.html - [7] CVE-2014-0257 PoC (.NET DCOM reflection; IE11SandboxEscapes) — James Forshaw — https://github.com/tyranid/IE11SandboxEscapes - [8] OleViewDotNet — James Forshaw — https://github.com/tyranid/oleviewdotnet **itm4n** - [9] Weaponizing Privileged File Writes with the USO Service — Part 1 — itm4n (2019) — https://itm4n.github.io/usodllloader-part1/ - [10] Weaponizing Privileged File Writes with the USO Service — Part 2 — itm4n (2019) — https://itm4n.github.io/usodllloader-part2/ - [11] UsoDllLoader — itm4n — https://github.com/itm4n/UsoDllLoader - [12] PrintSpoofer — Abusing Impersonation Privileges on Windows 10 and Server 2019 — itm4n (2020) — https://itm4n.github.io/printspoofer-abusing-impersonate-privileges/ - [13] PrintSpoofer repository — itm4n — https://github.com/itm4n/PrintSpoofer **decoder.cloud / splinter_code / antonioCoco** - [14] No more JuicyPotato? Old story, welcome RoguePotato! — decoder (2020) — https://decoder.cloud/2020/05/11/no-more-juicypotato-old-story-welcome-roguepotato/ - [15] No more rotten/juicy potato? (the 1809 silent fix) — decoder (2018) — https://decoder.cloud/2018/10/29/no-more-rotten-juicy-potato/ - [16] Giving JuicyPotato a second chance: JuicyPotatoNG — decoder (2022) — https://decoder.cloud/2022/09/21/giving-juicypotato-a-second-chance-juicypotatong/ - [17] The impersonation game (Juicy2 lineage) — decoder (2020) — https://decoder.cloud/2020/05/30/the-impersonation-game/ - [18] The lonely potato — decoder (2017) — https://decoder.cloud/2017/12/23/the-lonely-potato/ - [19] From NETWORK SERVICE to SYSTEM — decoder (2020) — https://decoder.cloud/2020/04/01/from-network-service-to-system/ - [20] We thought they were potatoes but they were beans (SweetPotato analysis) — decoder (2019) — https://decoder.cloud/2019/12/06/we-thought-they-were-potatoes-but-they-were-beans/ - [21] LocalPotato — When Swapping The Context Leads You To SYSTEM — decoder (2023) — https://decoder.cloud/2023/02/13/localpotato-when-swapping-the-context-leads-you-to-system/ - [22] LocalPotato HTTP edition — decoder (2023) — https://decoder.cloud/2023/11/03/localpotato-http-edition/ - [23] LocalPotato repository — decoder — https://github.com/decoder-it/LocalPotato (technical writeup: https://www.localpotato.com/localpotato_html/LocalPotato.html) - [24] RoguePotato repository — antonioCoco — https://github.com/antonioCoco/RoguePotato - [25] JuicyPotatoNG repository — antonioCoco — https://github.com/antonioCoco/JuicyPotatoNG - [26] RogueWinRM — antonioCoco — https://github.com/antonioCoco/RogueWinRM - [27] 10 Years of Windows Privilege Escalation with Potatoes (talk slides) — Antonio Cocomazzi, POC2023/Troopers24 — https://powerofcommunity.net/poc2023/AntonioCocomazzi.pdf (mirror: https://troopers.de/downloads/troopers24/TR24_10_years_of_Windows_Privilege_Escalation_with_Potatoes_CYZBJ3.pdf) **FoxGlove Security / ohpe** - [28] Rotten Potato — Privilege Escalation from Service Accounts to SYSTEM — FoxGlove Security (2016) — https://foxglovesecurity.com/2016/09/26/rotten-potato-privilege-escalation-from-service-accounts-to-system/ - [29] Hot Potato — FoxGlove Security (2016) — https://foxglovesecurity.com/2016/01/16/hot-potato/ - [30] Abusing Token Privileges for Windows Local Privilege Escalation — FoxGlove Security (2017) — https://foxglovesecurity.com/2017/08/25/abusing-token-privileges-for-windows-local-privilege-escalation/ - [31] RottenPotato / RottenPotatoNG repositories — FoxGlove Security — https://github.com/foxglovesec/RottenPotato and https://github.com/foxglovesec/RottenPotatoNG - [32] JuicyPotato (abusing the golden privileges) — ohpe & decoder (2018) — http://ohpe.it/juicy-potato/ - [33] JuicyPotato per-OS CLSID lists — ohpe — http://ohpe.it/juicy-potato/CLSID/ - [34] juicy-potato repository — ohpe — https://github.com/ohpe/juicy-potato **CVE-2015-2370 ecosystem and CVE-2017-0100** - [35] Exploiting MS15-076 (CVE-2015-2370) — the "Trebuchet" weaponization — NetSPI — https://www.netspi.com/blog/technical-blog/web-application-pentesting/exploiting-ms15-076-cve-2015-2370/ - [36] CVE-2017-0100 advisory (Windows HelpPane Elevation of Privilege Vulnerability) — Microsoft MSRC (2017) — https://msrc.microsoft.com/update-guide/vulnerability/CVE-2017-0100 (NVD mirror: https://nvd.nist.gov/vuln/detail/CVE-2017-0100) - [37] Windows: Bad Fix for COM Session Moniker EoP (CVE-2017-0298) — James Forshaw (2017) — mirrored at https://sploitus.com/exploit?id=1337DAY-ID-28134 **Other potatoes and coercion tooling** - [38] SweetPotato — CCob — https://github.com/CCob/SweetPotato - [39] SweetPotato — Local Service to SYSTEM — @_EthicalChaos_ (2020) — https://ethicalchaos.dev/2020/04/13/sweetpotato-local-service-to-system-privesc/ (analysis: https://www.pentestpartners.com/security-blog/sweetpotato-service-to-system/) - [40] SpoolSample (MS-RPRN coercion PoC) — Lee Christensen — https://github.com/leechristensen/SpoolSample - [41] EfsPotato — zcgonvh — https://github.com/zcgonvh/EfsPotato - [42] SharpEfsPotato — bugch3ck — https://github.com/bugch3ck/SharpEfsPotato - [43] DCOMPotato — zcgonvh — https://github.com/zcgonvh/DCOMPotato - [44] GodPotato — BeichenDream — https://github.com/BeichenDream/GodPotato - [45] BadPotato — BeichenDream — https://github.com/BeichenDream/BadPotato - [46] PrintNotifyPotato — BeichenDream — https://github.com/BeichenDream/PrintNotifyPotato - [47] CoercedPotato — Hackvens (@Hack0ura & @Prepouce) — https://github.com/Prepouce/CoercedPotato (writeup, FR: https://blog.hackvens.fr/articles/CoercedPotato.html) - [48] PetitPotato — wh0amitz — https://github.com/wh0amitz/PetitPotato - [49] RasmanPotato — crisprss — https://github.com/crisprss/RasmanPotato - [50] CandyPotato — klezVirus — https://github.com/klezVirus/CandyPotato - [51] MultiPotato — S3cur3Th1sSh1t — https://github.com/S3cur3Th1sSh1t/MultiPotato - [52] SigmaPotato — tylerdotrar — https://github.com/tylerdotrar/SigmaPotato - [53] GhostPotato — Shenanigans Labs (2019) — https://shenaniganslabs.io/2019/11/12/Ghost-Potato.html - [54] GenericPotato (The Power of SeImpersonation) — Micah van Deusen (2021) — https://micahvandeusen.com/the-power-of-seimpersonation/ - [55] RemotePotato0 / Relaying Potatoes — antonioCoco & SentinelOne (2021) — https://www.sentinelone.com/labs/relaying-potatoes-another-unexpected-privilege-escalation-vulnerability-in-windows-rpc-protocol/ - [56] CertPotato — SensePost (2022) — https://sensepost.com/blog/2022/certpotato-using-adcs-to-privesc-from-virtual-and-network-service-accounts-to-local-system/ **Trapped COM object ecosystem** - [57] Fileless lateral movement with trapped COM objects — IBM X-Force Red — https://www.ibm.com/think/news/fileless-lateral-movement-trapped-com-objects - [58] ForsHops — IBM X-Force Red — https://github.com/xforcered/ForsHops - [59] Abusing IDispatch for Trapped COM Object Access & Injecting into PPL Processes — Mohamed Fakroud — https://mohamed-fakroud.gitbook.io/red-teamings-dojo/abusing-idispatch-for-trapped-com-object-access-and-injecting-into-ppl-processes - [60] ComDotNetExploit (PPL injection PoC) — T3nb3w — https://github.com/T3nb3w/ComDotNetExploit - [61] Accelerating Offensive R&D with LLMs (alternative trapped-object classes) — Kyle Avery, Outflank (2025) — https://www.outflank.nl/blog/2025/07/29/accelerating-offensive-research-with-llm/ - [62] RemoteMonologue (DCOM NTLM authentication coercion) — IBM X-Force — https://www.ibm.com/think/x-force/remotemonologue-weaponizing-dcom-ntlm-authentication-coercions **Other COM LPE CVEs, privileged-file-operation abuse, and fuzzing** - [63] CVE-2019-1405 and CVE-2019-1322 — Elevation to SYSTEM via the UPnP Device Host Service and the Update Orchestrator Service — Langlois & Torkington, NCC Group Fox-IT (2019) — https://www.fox-it.com/be-en/cve-2019-1405-and-cve-2019-1322-elevation-to-system-via-the-upnp-device-host-service-and-the-update-orchestrator-service/ - [64] CVE-2020-1362 WalletService writeup — Q4n (Haoran Qin) (2020) — https://github.com/Q4n/CVE-2020-1362 - [65] CVE-2017-0213 exploit (COM Aggregate Marshaler) — exploit-db 42020 — https://www.exploit-db.com/exploits/42020/ - [66] CVE-2018-0952 DiagHub PoC — Atredis Partners — https://github.com/atredispartners/CVE-2018-0952-SystemCollector - [67] DiagHub case-sensitivity directory-traversal EoP — irsl — https://github.com/irsl/microsoft-diaghub-case-sensitivity-eop-cve - [68] Introduction to privileged file operation abuse on Windows — OffSec/Almond — https://offsec.almond.consulting/intro-to-file-operation-abuse-on-Windows.html - [69] Abusing the COM Registry Structure: CLSID, LocalServer32, & InprocServer32 — bohops (2018) — https://bohops.com/2018/06/28/abusing-com-registry-structure-clsid-localserver32-inprocserver32/ - [70] Abusing the COM Registry Structure (Part 2): Loading Techniques for Evasion and Persistence — bohops (2018) — https://bohops.com/2018/08/18/abusing-the-com-registry-structure-part-2-loading-techniques-for-evasion-and-persistence/ - [71] COMRace (USENIX Security '22 talk) — https://www.youtube.com/watch?v=9bBh2YEqVMA - [72] Enhancing Automatic Vulnerability Discovery for Windows RPC/COM in New Ways (Black Hat EU 2024 talk) — https://www.youtube.com/watch?v=VQiQuLo0v58 - [73] DCOM Turns 30: Revisiting a Legacy Interface in the Modern Threatscape (hack.lu 2025 slides) — https://d3lb3.github.io/assets/hacklu_2025.pdf **Microsoft documentation (impersonation contract and hardening)** - [74] CoSetProxyBlanket — Microsoft Learn — https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-cosetproxyblanket - [75] CoImpersonateClient — Microsoft Learn — https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-coimpersonateclient - [76] Cloaking — Microsoft Learn — https://learn.microsoft.com/en-us/windows/win32/com/cloaking - [77] RPC_C_IMP_LEVEL enumeration — Microsoft Learn — https://learn.microsoft.com/en-us/windows/win32/api/wtypesbase/ne-wtypesbase-rpc_imp_level - [78] KB5004442 — Manage changes for Windows DCOM Server Security Feature Bypass (CVE-2021-26414) — Microsoft Support — https://support.microsoft.com/en-us/topic/kb5004442-manage-changes-for-windows-dcom-server-security-feature-bypass-cve-2021-26414-f1400b52-c141-43d2-941e-979ed901b716