Playing around COM objects - PART 1
Component Object Model
Last updated
Component Object Model
Last updated
COM is a platform-independent, distributed, object-oriented system for creating binary software components that can interact. COM is the foundation technology for Microsoft's OLE (Object Linking and Embedding)(compound documents) and ActiveX (Internet-enabled components) technologies.
In other word, COM was originally created to enable Microsoft Office applications to communicate and exchange data between documents such embedding an Excel chart inside a Word document or PowerPoint presentation <---This ability called OLE.
COM objects can be created with a variety of programming languages. Object-oriented languages, such as C++ and the challenge for me was how I could interact with COM objects using GOLANG via Win32 API functions devoted for this.😎
As well as COM designed to promote software interoperability; that is, to allow two or more applications or “components” to easily interact with one another, even if they were written by different vendors at different times, in different programming languages, or if they are running on different machines and different operating systems.
First of all, what is an object? An object is an instantiation of some class. At a generic level, a “class” is the definition of a set of related attributes and methods grouped together for a specified purpose. The purpose is generally to provide some service to “things” outside the object, namely clients that want to make use of those services.
A client never has direct access to the COM object in its entirety. Instead, clients always access the object through clearly defined contracts: the interfaces that the object supports.
A COM object exposes its features through an interface, which is a collection of member functions.
An interface is basically an abstract class, containing only pure virtual functions and has no data members.
Therefore, an abstract class is a class that contain only pure virtual declaration of functions (the " = 0 " indicates the purity)) and has no data members:
An abstract class is a class that cannot be used to create objects; however, it can be subclassed.
So, the following line of code is not valid and it will throw an error as an object cannot be created for the abstract class:
The interface class can only be approached by using a pointer to the virtual table that exposes the methods in the interface. Interface does not come by itself, it usually comes with an inherited class that implements the exposed method in the interface. Such a class that implements the interface exposed methods is often called a co-class(derived class). Here is an example of a co-class:
Code Explanation: Here, IClassA is the base class, and as it has a pure virtual function (func1), it has become an abstract class. ClassB is derived from the parent class IClassA. The func1 is defined in the derived class.
We can use a global method to create an instance of the co-class or we can use a static method as well. The technique of using a method that creates an instance of a co-class and returning a pointer to its interface is often called Class Factoring. Here is the global create instance method:
In the main function, we try to create a pointer of base class type, it will be created successfully, and we can point it to the derived class. This pointer can be used to call the derived class function.
A COM class is identified by using a unique 128-bit Class ID (CLSID) that associates a class with a particular deployment in the file system, which for Windows is a DLL or EXE. A CLSID is a GUID, which means that no other class has the same CLSID.(Strongly-typed)
Also we can use a programmatic identifier (ProgID) which is a registry entry that can be associated with a CLSID. Like the CLSID, the ProgID identifies a class but with less precision because it is not guaranteed to be globally unique.(it's human readable and are locally scoped)
Interfaces are strongly typed. Every interface has its own unique interface identifier, named an IID, which eliminates collisions that could occur with human-readable names. The IID is a globally unique identifier (GUID). The name of an interface is always prefixed with an ''I" by convention.
COM Clients only interact with pointers to interfaces as we described before: When a client has access to an object, it has nothing more than a pointer through which it can access the functions in the interface. The pointer is opaque, meaning that it hides all aspects of internal implementation. In COM, the client can only call functions of the interface to which it has a pointer.
It is convenient to use a simple presentation to show the relation between a COM object and its interfaces :
Now, when a client want to interact with this COM object he must use a pointer to the targeted interface:
In order to enumerate COM objects through a number of different views (e.g. by CLSID, by ProgID, by server executable), enumerate interfaces on the object and then create an instance and invoke methods we will use the famous tool OleViewDotNet made by James Forshaw and the tool COMView.
Let's try to analyze this CLSID that have the following GUID :
13709620-C279-11CE-A49E-444553540000,
In COM, an object can support multiple interfaces as depicted on the above picture: IDispatch, IObjectSafety, IObjectSafety, IShellDispatch{1,2,3,4,5} and there are 2 interfaces that should demands a little special attention : IUnknown & IClassFactory.
As we said previously an interface is strongly-typed which mean it has an only unique identifier called IID:
IUnkown is a fundamental interface in COM that contains basic operations of not only all objects, but all interfaces as well. An object is not considered a COM object unless it implements this interface.
All interfaces in COM are polymorphic with IUknown:
So if you look at the first 3 functions in any interfaces you will see QueryInterface, AddRef, and Release:
In other words, IUnknown is the base interface from which all other interfaces inherit.
Before continuing, I would like to highlight something we've discussed it before which is this expression "COM Client only interact with a pointer to interfaces'', this pointer is a pointer to an array of pointers to the objects's implementations of the interface member functions:
By convention the pointer to the interface function table is called the pVtbl pointer. The table itself is generally referred to with the name vtbl for “virtual function table.” :
As you know, we create instances of a COM object using:
The interface specifications.
The CLSID declaration in the COM class.
We do not directly use the COM class to create an instance of a type of COM object.
Instead, an intermediate object called a class object is used to create instances of a COM object.
A class object is a specialized type of COM object that knows how to create a specific type of COM object. There is a one-to-one relationship between a class object and a COM object.
Every type of class object knows how to create only one type of COM object. For example, if two COM objects, called O1 and 02, are implemented within a COM server, two class objects must also be implemented, one that knows how to create 01 and one that knows how to create 02.
A class object can be considered of as a "creator" object. Its only goal is to create instances of a COM object.
Clients obtain a pointer to a class object's interface by asking the COM subsystem for a specific class object that can create the COM object they want. Using this interface pointer, clients have the class object create one or more instances of their associated COM object.
The COM specification defines a COM object creation interface called IClassFactory:
Class objects that implement
IClassFactory
as their object creation interface are called class factories.
Given a CLSID the client must now create an object of that class in order to make use of its services. It does so using two steps:
1. Obtain the “class factory” for the CLSID.
2. Ask the class factory to instantiate an object of the class
3. Returning an interface pointer to the client.
The IClassFactory interface is implemented by COM servers on a “class factory” object for the purpose of creating new objects of a particular class.
Now that we grasp what a class factory we can examine how a client obtains the class factory.
This COM library function do whatever is necessary to obtain a class factory object for the given CLSID and return one of that class factory's interface pointers to the client.After that the client may calls IClassFactory::CreateInstance to instantiate objects of the class.
Let's spot the most important parameters :
[in] rclsid
The CLSID associated with the data and code that you will use to create the objects.
[in] dwClsContext
The context in which the executable code is to be run. To enable a remote activation, include CLSCTX_REMOTE_SERVER. For more information on the context values and their use, see the CLSCTX enumeration.
[in] riid
Reference to the identifier of the interface, which will be supplied in ppv on successful return. This interface will be used to communicate with the class object. Typically this value is IID_IClassFactory, although other values such as IID_IClassFactory2 which supports a form of licensing are allowed.
[out] ppv
The address of pointer variable that receives the interface pointer requested in riid. Upon successful return, *ppv contains the requested interface pointer.
This function return S_OK if location and connection to the specified class object was successful.
Now, we will try to instantiate an object of the class {13709620-C279-11CE-A49E-444553540000} using OleViewDotNet which seems for me very abstract :
In order to understand what's under the hood I decided to hook class factoring routine and practice what Mr.Un1k0d3r teach us, so let’s take a more insightful look through WinDBG at how the calls are chained together. If we ask OLE32 about all the CoGet* and CreateI* used by OleViewDotNet :
As you can notice above OleViewDotNet don't use Ol32.dll!CoGetClassObject, and you may encounter CoCreateInstance which is simply a wrapper function for CoGetClassObject and IClassFactory.
By googling little a bit I found this precious information mentioned on MSDN documentation that said:
You should not call DllGetClassObject directly. When an object is defined in a DLL, CoGetClassObject calls the CoLoadLibrary function to load the DLL(in our cae this dll is shell32.dll), which, in turn, calls DllGetClassObject.
We can also verify the existence of this function through WinDBG :
So we place a breakpoint at SHELL32!DllGetClassObject and let it run until we hit it:
We can so confirm that OleViewDotNet is using SHELL32!DllGetClassObject for instantiating an object of the class with CLSID : {13709620-C279-11CE-A49E-444553540000}
To gain a full picture of what's under the hood, we can use the nice (Trace and watch utility)‘wt -l 2’ WinDBG command to gain a two-level-depth hierarchical function call :
From the above result I deduced that SHELL32!DllGetClassObject is calling combase!DllGetClassObject thus I added an other breakpoint to confirm that result :
Now I have a clear idea how I will craft my hooker 😄
Below is a simplified diagram that aims to visualize the flow of events before and after SHELL32!DllGetClassObject is hooked :
Based on my debugging analysis when OleViewDotNet want to instantiate an object of the class mainly it jumps to SHELL32!DllGetClassObject then executing an other jump to combase!DllGetClassObject :
Before moving to after hooking section let me show you the DllGetClassObject definition based on MSDN documentation :
If you have already noticed that CoGetClassObject parameters look like DllGetClassObject.
OleViewDotNet calls shell32!DllGetClassObject like before hooking.
OleViewDotNet looks up shell32!DllGetClassObject address but here is the magic, we patched dynamically this address to a malicious address that point on a rogue comhook!HookedDllGetClassObject function:
Take a look on line 7 and 8, the address of HookedDllGetClassObject is 0x698413fa, and if we disassemble it we will land into our malicious code and for poc purpose, comhook!HookedDllGetClassObject intercepts the rclsid parameter and execute pop up message holding the CLSID of the COM class :
3. comhook!HookedDllGetClassObject at the end call combase!DllGetClassObject routine at line 39 and if we can confirm that by adding a breakpoint at the address 0x69841478:
Frankly, I included this exercise in my research first to get familiar with known win32 API functions used to create COM objects and also simulate what Anti-malware and EDR software often utilize to intercept suspicious calls by injecting their dlls into processes and make some checks.
In our context, I inject comhook.dll into OleViewDotNet that allows me to understand what's under the hood in the class factoring phase of any COM object creation.
Let me share the snippet code of my comhook.dll and explain the most relevant part of it :
The C programming language provides a keyword called typedef, which you can use to give a type a new name.
I will explain the tricky part, roughly the idea is to patch the address of shell32DllGetClassObject to an address that will allow us to jump into HookedDllGetClassObject.
+ The local variable addr hold the address of shell32DllGetClassObject.
+ The local variable dwHooked hold the address of HookedDllGetClassObject which is 0x698413fa which is represented in 4 bytes.
Now, we want that the patch point us in an address that perform the following instruction:
Based on the above result our asm code is represented in 7 opcodes, thus we fix dwSize to 7 and we allocate memory for the CHAR* variable patch by this instruction :
Keep in mind that when pushing stuff on the stack there is endianness format that should be take on consideration while storing 0x698413fa on that stack.
The following figure explain what's happened in the tricky part
Finally, we will patch the address of shell32DllGetClassObject by the address of patch variable through this instruction : memcpy(addr, patch, dwSize);
The purpose of this section will be the implementation of those tasks in C language:
1. Obtain the “class factory” for our targeted CLSID. --> through DllGetClassObject.
2. Ask the class factory to instantiate an object of the class --> through CreateInstance method.
3. Get ShellExecute ID.
4. Initialize parameters to be used on Invoke method.
5. Pop the calc.
Goal : Reach what OleViewDotNet did through C language.
COM is based on a binary interoperability standard, rather than a language interoperability standard. Any language supporting “structure” or “record” types containing double-indirected access to a table of function pointers is suitable.
That being said, COM can declare interface declarations for both C++ and C . The C++ definition of an interface, which in general is of the form:
Did you know that a struct can store a pointer to some function?
then the corresponding C declaration of that interface looks like :
What we've done above is to recreate a C++ class, using plain C. The ISomeInterface struct is really a C++ class. A C++ class is really nothing more than a struct whose first member is always a pointer to its VTable (an array of function pointers) -- an array that contains pointers to all the functions inside of that class. The first argument passed to an object's function is a pointer to the object (struct) itself. (This is referred to as the hidden "this
" pointer.)
Before a program can use any COM object, it must initialize COM, which is done by calling the function CoInitialize. This need be done only once, so a good place to do it is at the very start of the program.
The above is a macro. A #define
in one of the Microsoft include files allows your compiler to compile the above into a 16 byte array.
Next, the program calls DllGetClassObject to get a pointer to shell32.dll's IClassFactory
object. Note that we pass the CLSID
object's GUID as the first argument. We also pass a pointer to our variable icf
which is where a pointer to the IClassFactory
will be returned to us, if all goes well:
Now, If we compile the above code and run it, we will get a match between the resulted offset and the one shown in OleViewDotNet which prove that we have now a pointer to the VTable of IClassFactory
:
Once we have the IClassFactory
object, we can call its CreateInstance
function to get a IDispatch object. Note how we use the IClassFactory
to call its CreateInstance
function. We get the function via IClassFactory
's VTable. Also note that we pass the IClassFactory
pointer as the first argument.
Note that we pass IDispatch's VTable GUID as the third argument. And for the fourth argument, we pass a pointer to our variable id
which is where a pointer to an IDispatch's object will be returned to us, if all goes well:
Again compile and run :
IDispatch interface usage : Exposes objects, methods and properties to programming tools and other applications that support Automation.
Once we have the IDispatch
object, we can call its GetIDsOfNames function to get the COM dispatch identifier (DISPID) of ShellExecute .
Note that we pass the IDispatch
pointer as the first argument. We pass a reference of IID_NULL as the second argument(Reserved for future use. Must be IID_NULL.), for the third argument, we pass ShellExecute as WCHAR to be mapped, for the fourth argument the count of the names to be mapped(1 in our case), for the fifth argument the locale context in which to interpret the names. And last argument we pass the address of dispid which is the id value of ShellExecute that will be returned to us, if all goes well:
Again compile and run :
According to MSDN, Invoke goal is to provide access to properties and methods exposed by an object and this is what we need to pop up the calc :
In our situation, note that we pass the IDispatch
pointer as the 1 arg. Then,we pass the dispid id of ShellExecute as the 2 arg. We pass a reference of IID_NULL as the 3 arg(Reserved for future use. Must be IID_NULL.), for the 4 arg the locale context in which to interpret the names, for the 5 arg flags describing the context of the Invoke call (in our case we pass DISPATCH_METHOD).
The 6 arg pointer to a DISPPARAMS structure containing an array of arguments passed ShellExecute , an array of argument DISPIDs for named arguments, and counts for the number of elements in the arrays.
VARIANTARG describes arguments passed within DISPPARAMS, and VARIANT to specify variant data that cannot be passed by reference.
In our case, we need only to set up 2 elements to reach our goal :
Before continuing let me highlight some stuff regarding BSTR(Basic string or binary string):
A BSTR (Basic string or binary string) is a string data type that is used by COM, Automation, and Interop functions. Use the BSTR data type in all interfaces that will be accessed from script.
A BSTR is a composite data type that consists of a length prefix, a data string, and a terminator. For more details check : https://docs.microsoft.com/en-us/previous-versions/windows/desktop/automat/bstr
At this stage, we prepared all the necessary ingredients to pop the calc using the invoke method; for the rest of the arguments that I did not mention just take a look on: https://docs.microsoft.com/en-us/windows/win32/api/oaidl/nf-oaidl-idispatch-invoke
So next, we call the IDispatch
's Release and IClassFactory
's Release
functions . Once we do this, our id and icf
variables no longer contains a valid pointer to anything. It's garbage now. We call aslo SysFreeString
to deallocates a string allocated previously.
Finally, we must call CoUninitialize
to allow COM to clean up some internal stuff. This needs to be done once only, so it's best to do it at the end of our program (but only if CoInitialize
succeeded).
As you notice the blog was long, but I truly share with you all the things I learned regarding the internal of COM which represent a fundamental pillar for Windows as OS. (COM is a big ocean of course I did not cover all things like single thread apartment, proxy/stub, security aspects, I promise my next parts will be about that).
Since I’m very obsessed with Windows especially when I’m facing in security community like James FORSHAW talks regarding COM, also Matt Nelson’s blog that explain lateral movements through DCOM… as well as some AVs that expose misconfigure COM object that led to LPE and self-bypass a poc was done by Denis Skvortcov.
What I mentioned in the previous paragraph, combined with the fact that one day I tried to implement WMI using golang, but I failed that represents the reasons why I wrote this blog since I knew nothing about COM, this is how it works, and now we need to learn theory and basic stuff, also slow down and seek to deeply understand the topic. This patience will be fruitful since you can now explore and develop stuff with creativity.
Final Note: I am not a Windows Internal expert I'm just a learner, If you think I said anything incorrect anywhere, feel free to reach out to me and correct me, I would highly appreciate that. And finally, thank you very much for taking your time to read this post.