COM and DCOM trace their origins to the early iterations of Windows, emerging alongside the resizable windows feature in Windows 2.0.
One of the earliest applications of interprocess communication in Windows was the interprocess clipboard, a fundamental yet effective feature enabling interaction between processes from a user experience standpoint. Introduced in 1987, Dynamic Data Exchange (DDE) facilitated this process, later partially supplanted by Object Linking and Embedding (OLE) 1.0 in 1990. Remarkably, OLE remains around decades later.
As software demands grew, more flexible mechanisms were required to support complex features, such as embedding an Excel spreadsheet within a Word document while maintaining editability.
COM and DCOM trace their origins to the early iterations of Windows, emerging alongside the resizable windows feature in Windows 2.0.
One of the earliest applications of interprocess communication in Windows was the interprocess clipboard, a fundamental yet effective feature enabling interaction between processes from a user experience standpoint. Introduced in 1987, Dynamic Data Exchange (DDE) facilitated this process, later partially supplanted by Object Linking and Embedding (OLE) 1.0 in 1990. Remarkably, OLE remains around decades later.
As software demands grew, more flexible mechanisms were required to support complex features, such as embedding an Excel spreadsheet within a Word document while maintaining editability.
The need for reusable, loosely coupled components led to the adoption of the Component-Based Development (CBD) model—a concept that, while often associated with modern software engineering, originated with COM.
COM (Component Object Model) introduced a flexible architecture allowing developers to interact with objects solely through their interfaces, abstracting away the complexities of implementation. This approach laid the groundwork for numerous technologies built on top of COM, including OLE 2.0, ActiveX, DirectX, Windows Shell, Browser Helper Objects, COM+, and, eventually, DCOM.
A pivotal feature of COM is location transparency— the ability to interact with an object without knowing its physical location. Whether an object resides within the same thread, process, or even on a remote machine, a user can interact with it using only the provided interface.
With the beta release of Windows 95, which introduced native TCP/IP support, Internet Explorer, and the widespread adoption of the World Wide Web, the necessity for distributed software components grew. The core principles of COM were subsequently extended, giving rise to Distributed COM (DCOM), which enabled communication between software components across networks.
However, distributed communication introduced additional challenges that DCOM sought to address:
Keep these concepts in mind, as they will be explored in greater detail later.
Afterwards, multiple protocols were built on top of DCOM such as:
The last element on the list, OPC is still widely used in Industrial technologies, which is why you get a lot of content related to that subject when you look for information on DCOM.
To summarize, DCOM is essentially COM over the network.
Before delving deeper, let’s define some key identifiers frequently encountered when working with COM and DCOM:
Since COM objects are registered in the Windows Registry, they can be easily enumerated using the following command:
PS C:\Users\user> reg query "HKCR\CLSID\"
HKEY_CLASSES_ROOT\CLSID\CLSID
HKEY_CLASSES_ROOT\CLSID\{0000002F-0000-0000-C000-000000000046}
HKEY_CLASSES_ROOT\CLSID\{00000300-0000-0000-C000-000000000046}
HKEY_CLASSES_ROOT\CLSID\{00000301-A8F2-4877-BA0A-FD2B6645FB94}
HKEY_CLASSES_ROOT\CLSID\{00000303-0000-0000-C000-000000000046}
[...]
Then, for each CLSID, you’ll have multiple keys and values needed by the system to instantiate the class. Indeed, two keys give information related to where the class is implemented. From there you have two possibilities:
In the same manner of the CLSIDs, ProgIDs and AppIDs can be listed using the following commands:
PS C:\Users\user> reg query "HKLM\SOFTWARE\Classes\"
[...]
HKEY_LOCAL_MACHINE\SOFTWARE\Classes\AADJ
HKEY_LOCAL_MACHINE\SOFTWARE\Classes\AADJCSP
HKEY_LOCAL_MACHINE\SOFTWARE\Classes\AboveLockAppLaunchCallback
HKEY_LOCAL_MACHINE\SOFTWARE\Classes\AccClientDocMgr.AccClientDocMgr
[...]
PS C:\Users\user> reg query "HKCR\AppID\"
[...]
HKEY_CLASSES_ROOT\AppID\{00021401-0000-0000-C000-000000000046}
HKEY_CLASSES_ROOT\AppID\{000C101C-0000-0000-C000-000000000046}
HKEY_CLASSES_ROOT\AppID\{0010890e-8789-413c-adbc-48f5b511b3af}
[...]
Fortunately, James Forshaw, developed a very convenient tool to enumerate all sort of information on COM: OleView.NET.
This tool allows you to list CLSIDs, ProgIDs, interfaces, scoped permissions, global permissions, type library definitions, etc. In addition to the enumeration features, you can also instantiate classes and perform many more advanced actions.
OleView.net comes with a Powershell Module, which you can install using:
Install-Module OleViewDotNet
Now that we know how to enumerate classes information, we can use this information to instantiate classes.
It’s quite easy to do, as Powershell provides the ability to run .NET code directly by providing different types of identifiers:
$object = [System.Activator]::CreateInstance([type]::GetTypeFromCLSID("49B2791A-B1AE-4C90-9B8E-E860BA07F889"))
$object = new-object -comobject Shell.Application
$object = [System.Activator]::CreateInstance([type]::GetTypeFromProgID("Shell.Application"))
Methods and attributes can then be called directly from the $object variable:
$object.Document.ActiveView.ExecuteShellCommand("cmd",$null,"/c calc.exe","7")
Classes can also be instantiated remotely by specifying an IP address as second parameter:
$object = [System.Activator]::CreateInstance([type]::GetTypeFromProgID("Shell.Application","192.168.0.2"))
We have seen multiples methods to instantiate classes using Powershell but it can also be done using other binaries such as rundll32 with the CLSID:
rundll32.exe C:\Windows\System32\shell32.dll,SHCreateLocalServerRunDll {9aa46009-3ce0-458a-a354-715610a075e6}
rundll32.exe -sta {9aa46009-3ce0-458a-a354-715610a075e6}
Next, scheduled tasks offers another quite interesting persistence method. Here is the format to use for the scheduled task XML file:
<Actions Context="LocalSystem">
<ComHandler>
<ClassId>{E4544ABA-62BF-4C54-AAB2-EC246342626C}</ClassId>
</ComHandler>
</Actions>
A regular user can register a new CLSID in the HKCU hive, pointing to an arbitrary executable, and then instantiate this class through a scheduled task later on.
Using the Windows API, the most common functions to create an object are:
HRESULT CoCreateInstance(
[in] REFCLSID rclsid,
[in] LPUNKNOWN pUnkOuter,
[in] DWORD dwClsContext,
[in] REFIID riid,
[out] LPVOID *ppv
);
HRESULT CoCreateInstanceEx(
[in] REFCLSID Clsid,
[in] IUnknown *punkOuter,
[in] DWORD dwClsCtx,
[in] COSERVERINFO *pServerInfo,
[in] DWORD dwCount,
[in, out] MULTI_QI *pResults
);
HRESULT CoGetClassObject(
[in] REFCLSID rclsid,
[in] DWORD dwClsContext,
[in, optional] LPVOID pvReserved,
[in] REFIID riid,
[out] LPVOID *ppv
);
Finally, you can instantiate classes in the explorer using the following syntax:
explorer "shell:::{D4480A50-BA28-11d1-8E75-00C04FA31A86}"
This trick can help to bypass AppLocker in some cases. The full list of explorer shortcuts can be found here.
Under the hood, this class is instantiated using the rundll32 C:\Windows\System32\shwebsvc.dll,AddNetPlaceRunDll
command. Some other similar classes might be interesting to investigate.
According to Component-based software engineering principles, COM classes expose interfaces which are a collection of functions used to communicate with the client. A COM class can expose multiple interfaces where each interface is identified by an IID, which is a unique GUID.
Among all the available interfaces, one is quite special: IUnknown. Indeed, every single COM class must implement the IUnknown interface and every interface must inherit from IUnknown.
The interface IUnknown exposes the method QueryInterface() which takes an IID as an input and returns an interface pointer after calling AddRef (another method from IUnknown interface to manage reference counting).
HRESULT QueryInterface(
REFIID riid,
void **ppvObject
);
Interfaces are described using the IDL (Interface Definition Language) format:
[object, uuid(673B0E01-4987-11d2-85C0-08001700C57F)]
interface ITest : IUnknown
{
ULONG Test1();
ULONG Test2();
}
Thereby for each interface, the following information are declared:
The IDL file is then used to generate a type library (TLB) and other output files using the MIDL compiler. The TLB file stores information such as properties and methods of a COM or DCOM class in a binary format. This file is parsed at runtime by other applications to determine interfaces, and interfaces methods that can be called on an object. This way, clients and servers built using different languages can communicate by parsing an object type library. We’ll also see that this file can be very useful while looking for interesting methods.
All TLB files are referenced in the registry under the following key (example for the CLSID 91e132a0-0df1-11d2-86cc-444553540000): HKEY_CLASSES_ROOT\CLSID\{91e132a0-0df1-11d2-86cc-444553540000}\TypeLib.
We have seen a bit earlier that classes can be implemented two different ways:
As you can imagine, in-process components are running inside the process that instantiated the object. In most cases, they are defined in DLL files (except if they are instantiated from a remote computer, see DLL Surrogates for details).
You can spot DLLs implementing COM objects by the exported function DllGetClassObject() which takes a CLSID as input parameter and returns a class factory:
HRESULT DllGetClassObject(
[in] REFCLSID rclsid,
[in] REFIID riid,
[out] LPVOID *ppv
);
Object factories allow creating an instance of an object via the IClassFactory::CreateInstance() method:
HRESULT CreateInstance(
[in] IUnknown *pUnkOuter,
[in] REFIID riid,
[out] void **ppvObject
);
And finally you can call methods on the instantiated object.
To sum-up this process:
Out-of-process components are different in most aspects. First, they are running in a different process on your computer (or a remote computer via DCOM). They are implemented in executable binaries (.exe files).
Depending on the location of the server (locally or remote), the protocols and the interfaces involved might change but the overall process is similar. In fact, the ALPC (a protocol used for local RPC communications) protocol is replaced by DCE-RPC, interfaces and methods are also similar (ISystemActivator and IRemoteSCMActivator)
The activation process of this types of component involves a new component: the DCOM activator which is a component of RPCSS. Thus, RPCSS exposes two RPC interfaces:
We will focus here on remote activation since the local activation process was already explained in details by James Forshaw in this excellent talk.
Although remote and local object activation are conceptually similar, interesting mechanisms are involved in this process.
The very first step of object activation is contacting the object resolver to get the string and security bindings from the server. This is done by calling the ServerAlive2() method the IOXIDResolver native RPC interface:
This method returns network and security bindings used to contact the object resolver:
As you can see, the hostname of the machine as well as the IP address of each network interface are included in the response. This behavior was first discovered by Airbus Cybersecurity team and described in their blog post. It can be very useful while looking for jump machines to access restricted subnetworks for instance.
This native RPC interface exposes only one method used for object activation: RemoteActivation. This interface is used for older versions of DCOM but is still present.
You can easily identify this interface on RPCView from the RPCSS.exe process:
Don’t run, we’ll focus on some parameters:
ORPCthis and ORPCthat parameters are structures added by the DCOM protocol to carry additional parameters (Ref) like versioning (we’ve seen before that DCOM supports different versions), causality information or application-specific out-of-band data.
The OBJREF is a structure containing a marshalled COM object sent to the client, and used to create a proxy object that acts as a stand-in for the remote object, allowing to call methods over the network.
The structure content and layout depends on the type of OBJREF used. Four types of OBJREF exist:
The OBJREF structure then depends on the type of OBJREF defined in the flags of the header:
We’ll see after how this OBJREF is used to interact with the final OBJECT in both cases.
Although the interface IActivation exists, and the process being similar, the IRemoteSCMActivator interface is preferred for newer versions of DCOM.
Unlike the IActivation interface, IRemoteSCMActivator splits the activation process in two functions:
HRESULT RemoteGetClassObject(
[in] handle_t rpc,
[in] ORPCTHIS* orpcthis,
[out] ORPCTHAT* orpcthat,
[in, unique] MInterfacePointer* pActProperties,
[out] MInterfacePointer** ppActProperties
);
HRESULT RemoteCreateInstance(
[in] handle_t rpc,
[in] ORPCTHIS* orpcthis,
[out] ORPCTHAT* orpcthat,
[in, unique] MInterfacePointer* pUnkOuter,
[in, unique] MInterfacePointer* pActProperties,
[out] MInterfacePointer** ppActProperties
);
Both functions take activation properties as input and output information to connect back to the object.
The activation properties structure is divided into a header and activation properties. The header contains various information related to the classes you want to instantiate such as the CLSIDs.
The properties structure is as follows:
struct InstantiationInfoData {
CLSID classId; #CLSID to create
DWORD classCtx;
DWORD actvflags; #Activation flags
long fIsSurrogate;
DWORD cIID;
DWORD instFlag;
IID *pIID; #List of interface IDs to query for
DWORD thisSize;
COMVERSION clientCOMVersion;
}
For instance, while instantiating remotely the MMC20.Application class with RemoteGetClassObject(), the following InstantiationInfoData structure containing the CLSID and the IID of the class factory is passed to the server:
The server then sends back a response containing an OBJREF_CUSTOM (always) with the IID 000001a3-0000-0000-c000-000000000046 corresponding to IID_IActivationPropertiesOut, a specific structure containing information regarding the activation of the requested interface.
This was a bit confusing at first, what if I want a standard OBJREF ? Well this OBJREF is not the one you expected, and the response is deeper.
The response structure is split into two parts, the CustomHeader and the Properties, itself divided in two parts:
Remark: From now on, an RPC channel is established between the client and the object resolver on a dynamic RPC port (here 62076/TCP) sent by the server in the String Bindings contained in the ScmReplyInfo structure.
struct PropsOutInfo {
DWORD cIfs;
IID* piid;
HRESULT* phresults;
MInterfacePointer** ppIntfData;
}
Next steps are quite similar to the in-process activation. Indeed, we need to contact the object resolver on the remote host in order to access the class factory object. The IPID of the IRemUnknown2 interface (the object exporter) is the only information needed to contact it.
The IRemUnknown2 interface is analog to the holy IUnknown interface locally, it also the equivalent method to QueryInterface():
HRESULT RemQueryInterface2(
[in] REFIPID ripid,
[in] unsigned short cIids,
[in, size_is(cIids)] IID* iids,
[out, size_is(cIids)] HRESULT* phr,
[out, size_is(cIids)] PMInterfacePointerInternal* ppMIF
);
As you may have already guessed, this method takes as input the IPID of the interface you want to talk to (here IClassFactory we got from the PropertiesOut structure). In this example, we do have a Class Factory, however if your class does not implement a factory interface, the object is then contacted directly.
The IClassFactory interface is then queried via DCE-RPC directly to create the object instance with the method CreateInstance():
HRESULT CreateInstance(
[in] IUnknown *pUnkOuter,
[in] REFIID riid,
[out] void **ppvObject
);
At this time of the research I was looking for a plain OBJREF in Wireshark, then I heard the OBJREF meowing to my ear:
This MEOW string is the OBJREF signature and by saving this binary data to a file, striping the beginning and parsing it with OLEView.NET (Object > Hex Editor > From File), you can see that this is an OBJREF for the IUnknown interface:
We are now ready to call the IUnknown interface from the final object in order to query further interfaces.
In this article, we have explored the different types of components involved in COM/DCOM (Classes, Interfaces) and their identifiers (ProgIDs, CLSIDs, AppIDs, IIDs). We also discussed how to enumerate them using native tools (PowerShell, Windows Registry) or external tools such as OleView.NET.
Then, we described the different ways to instantiate a class.
Next time, we will try to understand the mechanics behind method calling or object enumeration and the permission mechanism.