Component Object Model (COM) has been a cornerstone of Microsoft Windows development since the early 1990s and is still very prevalent in modern Windows operating systems and applications. The reliance on COM components and extensive feature development through the years has created a generous attack surface. In February 2025, James Forshaw (@tiraniddo) of Google Project Zero released a blog post detailing a novel approach for abusing Distributed COM (DCOM) remoting technology where trapped COM objects can be used to execute .NET managed code in the context of a server-side DCOM process. Forshaw highlights several use cases for privilege escalation and Protected Process Light (PPL) bypass.
Based on Forshaw’s research, Mohamed Fakroud (@T3nb3w) published an implementation of the technique to bypass PPL protections in early March 2025. Jimmy Bayne (@bohops) and I conducted similar research in February 2025, which has led us to develop a proof-of-concept fileless lateral movement technique by abusing trapped COM objects.
COM is a binary interface standard and a middleware service tier that allows for the exposure of distinct, modular components to interact with each other and with applications, regardless of the underlying programming language. For instance, COM objects developed in C++ can easily interface with a .NET application, enabling developers to integrate diverse software modules effectively. DCOM is a remoting technology that enables COM clients to communicate with COM servers via inter-process communication (IPC) or remote procedure calls (RPC). Many Windows services implement DCOM components that are locally or remotely accessible.
COM classes are typically registered and contained within the Windows Registry. A client program interacts with a COM server by creating an instance of the COM class, known as a COM object. This object provides a pointer to a standardized interface. The client uses this pointer to access the object's methods and properties, facilitating communication and functionality between the client and server.
COM objects are often research targets for assessing vulnerability exposure and discovering abusable features. A trapped COM object is a bug class in which a COM client instantiates a COM class in an out-of-process DCOM server, where the client controls the COM object via a marshaled-by-reference object pointer. Depending on the condition, this control vector may present security-related logic flaws.
Forshaw’s blog describes a PPL bypass use case where the IDispatch interface, as exposed in the WaaSRemediation COM class, is manipulated for trapped COM object abuse and .NET code execution. WaaSRemediation is implemented in the WaaSMedicSvc service, which executes as a protected svchost.exe process in the context of NT AUTHORITY\SYSTEM. Forshaw’s excellent walkthrough was the basis for our applied research and development of a proof-of-concept fileless lateral movement technique.
Our research journey began by exploring the WaaSRemediation COM class that supports the IDispatch interface. This interface allows clients to perform late binding. Normally, COM clients have the interface and type definitions for the objects they are using defined at compile time. Instead, late binding permits the client to discover and call methods upon the object at runtime. IDispatch includes the GetTypeInfo method, which returns an ITypeInfo interface. ITypeInfo has methods that can be used to discover type information for the object implementing it.
If a COM class uses a type library, it can be queried by the client via ITypeLib (obtained from ITypeInfo-> GetContainingTypeLib) to retrieve type information. Additionally, type libraries may also reference other type libraries for additional type information.
According to Forshaw’s blog post, WaaSRemediation references the type library WaaSRemediationLib, which in turn references stdole (OLE Automation). WaaSRemediationLib utilizes two COM classes from that library, StdFont and StdPicture. By performing COM Hijacking on the StdFont object via modifying its TreatAs registry key, the class will point to another COM class of our choosing, such as System.Object in the .NET Framework. Of note, Forshaw points out that StdPicture is not viable as this object performs a check for out-of-process instantiation, so we kept our focus on using StdFont.
.NET objects are interesting to us because of System.Object’s GetType method. Through GetType, we can perform .NET reflection to eventually access Assembly.Load. While System.Object was chosen, this type happens to be the root of the type hierarchy in .NET. Therefore, any .NET COM object could be used.
With the initial stage set, there were two other DWORD values under HKLM\Software\Microsoft\.NetFramework key required to make our perceived use case a reality:
Upon confirming that the latest version of the CLR and .NET could be loaded in our initial testing efforts, we knew we were on the right track.
Shifting our attention to focus on remote programmatic aspects, we first used Remote Registry to manipulate the .NetFramework registry key values and hijack the StdFont object on the target machine. Next, we swapped CoCreateInstance for CoCreateInstanceEx to instantiate the WaaSRemediation COM object on the remote target and get a pointer to the IDispatch interface.
With a pointer to IDispatch, we call the GetTypeInfo member method to get a pointer to the ITypeInfo interface, which is trapped in the server. Member methods called thereafter occur server-side. After identifying the contained type library reference of interest (stdole) and deriving the subsequent class object reference of interest (StdFont), we eventually used the “remotable” CreateInstance method on the ITypeInfo interface to redirect the StdFont object link flow (via prior TreatAs manipulation) to instantiate System.Object.
Since AllowDCOMReflection is properly set, we can then perform .NET reflection over DCOM to access Assembly.Load to load a .NET assembly into the COM server. Since we’re using Assembly.Load over DCOM, this lateral movement technique is completely fileless as the assembly byte transfer is handled by the DCOM remoting magic. For an in-depth explanation of this technical flow from object instantiation to reflection, please refer to the following diagram:
Our first and primary issue was calling Assembly.Load_3, via IDispatch->Invoke. Invoke passes an object array of arguments to the target function, and Load_3 is the overload of Assembly.Load that takes a single byte array. Thus, we needed to wrap the SAFEARRAY of bytes within another SAFEARRAY of VARIANTs – initially, we kept trying to pass a single SAFEARRAY of bytes.
Another issue was finding the proper Assembly.Load overload. Helper functions were taken from Forshaw’s CVE-2014-0257 code, which included the GetStaticMethod function. This function used .NET reflection over DCOM to find a static method given a type pointer, the method name and its parameter count. Assembly.Load has two static overloads that take a single argument; as such, we ended up using a hacky solution. We noticed the third instance of Load with a single argument was our right pick.
One of the biggest drawbacks we observed with this technique was that the beacon spawned would have its lifetime limited to the COM client; in this case, the application lifetime of our weaponization binary “ForsHops.exe” (elegantly named, of course). So, if ForsHops.exe cleaned up its COM references or exited, so would the beacon that was running under the remote machine’s svchost.exe. We tried different solutions, such as having our .NET assembly indefinitely hang its main thread, execute shellcode in another thread and have ForsHops.exe leave the exploit thread hanging, but nothing was elegant.
In its current state, ForsHops.exe runs until the beacon exits, at which point it removes its registry operations. There are opportunities for improvement, but we’ll leave that as an exercise for the reader.
The detection guidance proposed by Samir Bousseaden (@SBousseaden) after Mohamed Fakroud published their implementation also applies to this lateral movement technique:
Furthermore, we recommend implementing the following additional controls:
Additionally, leverage the following proof-of-concept YARA rule to detect the standard ForsHops.exe executable:
rule Detect_Standard_ForsHops_PE_By_Hash
Our implementation slightly extends the COM abuse explained in Forshaw’s blog by leveraging trapped COM objects for lateral movement rather than local execution for PPL bypass. Therefore, it is still susceptible to the same detections as implementations performing local execution.
You can find the ForsHops.exe proof-of-concept lateral movement code here.
A special thank you to Dwight Hohnstein (@djhohnstein) and Sanjiv Kawa (@sanjivkawa) for giving feedback on this research and providing blog post content review.
Learn how to navigate the challenges and tap into the resilience of generative AI in cybersecurity.
Understand the latest threats and strengthen your cloud defenses with the IBM X-Force Cloud Threat Landscape Report.
Find out how data security helps protect digital information from unauthorized access, corruption or theft throughout its entire lifecycle.
A cyberattack is an intentional effort to steal, expose, alter, disable or destroy data, applications or other assets through unauthorized access.