Playing around COM objects - PART 1

Component Object Model

Introduction

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.

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.

COM objects and Interfaces

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:

class IClassA
{
public:
   virtual void func1() = 0;
};

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:

IClassA* pNewInstance = new IClassA;

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:

class ClassB: public IClassA
{
public:
   virtual void func1() { // implementation here }
};

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:

IClassA* CreateInstance()
{
    return new ClassB;
}

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.

int main() {
//IClassA b;   ---------- > this line will throw an error
IClassA* b = CreateInstance(); //---------- > pointer can be created, so this line is correct
b -> func1();
}

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.

Object Presentation

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:

1) IUnknown interface

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.” :

2) IClassFactory interface

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.

Class 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.

Ol32.dll!CoGetClassObject

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.

HRESULT CoGetClassObject(
  [in]           REFCLSID rclsid,
  [in]           DWORD    dwClsContext,
  [in, optional] LPVOID   pvReserved,
  [in]           REFIID   riid,
  [out]          LPVOID   *ppv
);

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 :

Hooking class factoring

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 :

0:008> x OLE32!CoGet*
00007ffb`2ffd4790 ole32!CoGetInterceptor (struct _GUID *, struct IUnknown *, struct _GUID *, void **)
00007ffb`2ff8e180 ole32!CoGetObject (wchar_t *, struct tagBIND_OPTS *, struct _GUID *, void **)
00007ffb`2ffc3830 ole32!CoGetSystemWow64DirectoryW (wchar_t *, unsigned int)
00007ffb`2ffa3350 ole32!CoGetInterceptorFromTypeInfo (struct _GUID *, struct IUnknown *, struct ITypeInfo *, struct _GUID *, void **)
00007ffb`2ff91070 ole32!CoGetInterceptorForOle32 (struct _GUID *, struct IUnknown *, struct _GUID *, void **)
0:008> x OLE32!CreateI*
00007ffb`2ffc38e0 ole32!CreateILockBytesOnHGlobalStub (void *, int, struct ILockBytes **)
00007ffb`2ff84c30 ole32!CreateItemMoniker (wchar_t *, wchar_t *, struct IMoniker **)

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 :

0:000> x 
00007ffb`3011e3f0 shell32!DllGetClassObject (void)

So we place a breakpoint at SHELL32!DllGetClassObject and let it run until we hit it:

0:000> bp SHELL32!DllGetClassObject
0:000> g
Breakpoint 0 hit
shell32!DllGetClassObject:
00007ffb`3011e3f0 4053            push    rbx

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 :

0:007> bp SHELL32!DllGetClassObject
0:007> g
Breakpoint 0 hit
shell32!DllGetClassObject:
00007ffb`3011e3f0 4053            push    rbx
0:000> wt -l 2
Tracing shell32!DllGetClassObject to return address 00007ffb`30df50cb
  491     0 [  0] shell32!DllGetClassObject
    6     0 [  1]   shell32!_security_check_cookie
  498     6 [  0] shell32!DllGetClassObject

504 instructions were executed in 503 events (0 from other threads)

Function Name                               Invocations MinInst MaxInst AvgInst
shell32!DllGetClassObject                             1     498     498     498
shell32!_security_check_cookie                        1       6       6       6

0 system calls were executed

combase!CClassCache::CDllPathEntry::GetClassObject+0x29 [inlined in combase!CClassCache::CDllPathEntry::DllGetClassObject+0x6b]:
00007ffb`30df50cb f605ae182a0002  test    byte ptr [combase!Microsoft_Windows_COM_PerfEnableBits (00007ffb`31096980)],2 ds:00007ffb`31096980=00

From the above result I deduced that SHELL32!DllGetClassObject is calling combase!DllGetClassObject thus I added an other breakpoint to confirm that result :

0:009> bl
     0 e Disable Clear  00007ffb`3011e3f0     0001 (0001)  0:**** shell32!DllGetClassObject
     1 e Disable Clear  00007ffb`30e1ff90     0001 (0001)  0:**** combase!DllGetClassObject
0:009> g
Breakpoint 0 hit
shell32!DllGetClassObject:
00007ffb`3011e3f0 4053            push    rbx
0:000> g
Breakpoint 1 hit
combase!DllGetClassObject:
00007ffb`30e1ff90 48895c2408      mov     qword ptr [rsp+8],rbx ss:000000cf`9a6fc730=ffffffffff4441ce

Below is a simplified diagram that aims to visualize the flow of events before and after SHELL32!DllGetClassObject is hooked :

Before hooking

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 :

HRESULT DllGetClassObject(
  [in]  REFCLSID rclsid,//The CLSID that will associate the correct data and code.
  [in]  REFIID   riid, //Usually, this is IID_IClassFactory, a reference to the identifier of the interface that the caller is to use to communicate with the class object.
  [out] LPVOID   *ppv //The address of a pointer variable that receives the interface pointer requested in riid. 
);

If you have already noticed that CoGetClassObject parameters look like DllGetClassObject.

After hooking

  1. OleViewDotNet calls shell32!DllGetClassObject like before hooking.

  2. 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:

0:004> g
Breakpoint 0 hit
shell32!DllGetClassObject:
00007fff`1897e490 b8fa138469      mov     eax,offset comhook!HookedDllGetClassObject (00000000`698413fa)
0:000> u 00007fff`1897e490
shell32!DllGetClassObject:
00007fff`1897e490 b8fa138469      mov     eax,offset comhook!HookedDllGetClassObject (00000000`698413fa)
00007fff`1897e495 ffe0            jmp     rax
00007fff`1897e497 56              push    rsi
00007fff`1897e498 4157            push    r15
00007fff`1897e49a 4881ecb0020000  sub     rsp,2B0h
00007fff`1897e4a1 488b05d06f6600  mov     rax,qword ptr [shell32!_security_cookie (00007fff`18fe5478)]
00007fff`1897e4a8 4833c4          xor     rax,rsp
00007fff`1897e4ab 4889842490020000 mov     qword ptr [rsp+290h],rax

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 :

0:000> uf 00000000`698413fa
comhook!HookedDllGetClassObject:
00000000`698413fa 55              push    rbp
00000000`698413fb 4889e5          mov     rbp,rsp
00000000`698413fe 4883ec40        sub     rsp,40h
00000000`69841402 48894d10        mov     qword ptr [rbp+10h],rcx
00000000`69841406 48895518        mov     qword ptr [rbp+18h],rdx
00000000`6984140a 4c894520        mov     qword ptr [rbp+20h],r8
00000000`6984140e ba00010000      mov     edx,100h
00000000`69841413 b940000000      mov     ecx,40h
00000000`69841418 488b05057e0000  mov     rax,qword ptr [comhook!rdgco+0x190c (00000000`69849224)]
00000000`6984141f ffd0            call    rax
00000000`69841421 488945f8        mov     qword ptr [rbp-8],rax
00000000`69841425 488d45e8        lea     rax,[rbp-18h]
00000000`69841429 4889c2          mov     rdx,rax
00000000`6984142c 488b4d10        mov     rcx,qword ptr [rbp+10h]
00000000`69841430 488b05fd7e0000  mov     rax,qword ptr [comhook!rdgco+0x1a1c (00000000`69849334)]
00000000`69841437 ffd0            call    rax
00000000`69841439 488b55e8        mov     rdx,qword ptr [rbp-18h]
00000000`6984143d 488b45f8        mov     rax,qword ptr [rbp-8]
00000000`69841441 4989d1          mov     r9,rdx
00000000`69841444 4c8d05b52b0000  lea     r8,[comhook!Hook+0x2b5f (00000000`69844000)]
00000000`6984144b baff000000      mov     edx,0FFh
00000000`69841450 4889c1          mov     rcx,rax
00000000`69841453 e858ffffff      call    comhook+0x13b0 (00000000`698413b0)
00000000`69841458 488b45f8        mov     rax,qword ptr [rbp-8]
00000000`6984145c 41b901000000    mov     r9d,1
00000000`69841462 4c8d05c62b0000  lea     r8,[comhook!Hook+0x2b8e (00000000`6984402f)]
00000000`69841469 4889c2          mov     rdx,rax
00000000`6984146c b900000000      mov     ecx,0
00000000`69841471 488b05cc7e0000  mov     rax,qword ptr [comhook!rdgco+0x1a2c (00000000`69849344)]
00000000`69841478 ffd0            call    rax
00000000`6984147a 488d0597640000  lea     rax,[comhook!rdgco (00000000`69847918)]
00000000`69841481 488b00          mov     rax,qword ptr [rax]
00000000`69841484 488b4d20        mov     rcx,qword ptr [rbp+20h]
00000000`69841488 488b5518        mov     rdx,qword ptr [rbp+18h]
00000000`6984148c 4989c8          mov     r8,rcx
00000000`6984148f 488b4d10        mov     rcx,qword ptr [rbp+10h]
00000000`69841493 ffd0            call    rax
00000000`69841495 8945f4 uf       mov     dword ptr [rbp-0Ch],eax
00000000`69841498 8b45f4          mov     eax,dword ptr [rbp-0Ch]
00000000`6984149b 4883c440        add     rsp,40h
00000000`6984149f 5d              pop     rbp
00000000`698414a0 c3              ret

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:

0:000> g
Breakpoint 2 hit
comhook!HookedDllGetClassObject+0x99:
00000000`69841493 ffd0            call    rax {combase!DllGetClassObject (00007fff`1a4cff90)}

comhook.dll-x64

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 :

Global variables :

BOOL hooked = FALSE;
typedef BOOL (WINAPI * DLLMAIN)( HINSTANCE, DWORD, LPVOID );
typedef HRESULT(*RealDllGetClassObject)(REFCLSID, REFIID, LPVOID*);
RealDllGetClassObject rdgco;
FARPROC shell32DllGetClassObject;

The C programming language provides a keyword called typedef, which you can use to give a type a new name.

HookedDllGetClassObject()

HRESULT HookedDllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv){
    
    CHAR *log = (CHAR*)GlobalAlloc(GPTR, 256);
    HRESULT hr;
    LPOLESTR os;
    StringFromCLSID(rclsid, &os);
    snprintf(log, 255, "HookedDllGetClassObject Called ARGS CLSID %ws\n", os);
    MessageBox(0,log,"Message from hooker",1);
    hr = rdgco(rclsid, riid, ppv);
    return hr;
}

Hook()

VOID Hook()
{
     if(!hooked) {
        //Get the address of shell32!DllGetClassObject
        shell32DllGetClassObject =  GetProcAddress(LoadLibrary("shell32.dll"), "DllGetClassObject");
        //Get the address of combase!DllGetClassObject
        rdgco =  (RealDllGetClassObject)GetProcAddress(LoadLibrary("combase.dll"), "DllGetClassObject");
	   
        DWORD dwSize = 7;
        DWORD dwOld = 0;
        //Allocates the specified number of bytes from the heap.
        CHAR *patch = (CHAR*)GlobalAlloc(GPTR, dwSize);
        //Doing casting stuff
        CHAR *addr = (CHAR*)shell32DllGetClassObject;
        long long longHookedDllGetClassObject = (long long)HookedDllGetClassObject;
        DWORD dwHooked = (DWORD)longHookedDllGetClassObject;
        //Changes the protection of shell32DllGetClassObject addresss to be able to patch it.
        VirtualProtect((VOID*)shell32DllGetClassObject, dwSize, PAGE_EXECUTE_READWRITE, &dwOld);
        
        //The tricky part
        DWORD i = 0;
        DWORD position = 1;
        patch[0] = 0xb8;
        for(i; i < 4; i++) {
            CHAR current = dwHooked;
            patch[position++] = current;
            dwHooked >>= 8;
        }
        patch[5] = 0xff;
        patch[6] = 0xe0;
        
        //patch the address of shell32DllGetClassObject to point into HookedDllGetClassObject        
        memcpy(addr, patch, dwSize);
        VirtualProtect((VOID*)shell32DllGetClassObject, dwSize, PAGE_EXECUTE_READ, &dwOld);
    }
 
    hooked = TRUE;
}

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 :

CHAR *patch = (CHAR*)GlobalAlloc(GPTR, dwSize);

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

COM via C(not C++)

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.

C vs C++

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:

interface ISomeInterface{
	virtual RET_T  MemberFunction(ARG1_T arg1, ARG2_T arg2 /*, etc */);
	[Other member functions]
	...
	};

Did you know that a struct can store a pointer to some function?

then the corresponding C declaration of that interface looks like :

typedef struct ISomeInterface
	{
	ISomeInterfaceVtbl *  pVtbl;
	} ISomeInterface;

typedef struct ISomeInterfaceVtbl ISomeInterfaceVtbl;
struct ISomeInterfaceVtbl
	{
	RET_T (*MemberFunction)(ISomeInterface * this, ARG1_T arg1,
		ARG2_T arg2 /*, etc */);
	[Other member functions]
	} ;

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

1. Obtain the “class factory” for our targeted CLSID. --> through DllGetClassObject

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.

DEFINE_GUID(clsid, 0x13709620, 0xc279, 0x11ce, 0xa4, 0x9e, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00);

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:

#include <windows.h>
#include <stdio.h>
#include <initguid.h>
#include <stdint.h>
//gcc .\comfunc.c -o com.exe -lole32 -luuid -loleaut32

//The CLSID {13709620-C279-11CE-A49E-4445535400} associated with the data and code that we will use to create the object.
DEFINE_GUID(clsid, 0x13709620, 0xc279, 0x11ce, 0xa4, 0x9e, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00);

 int main(char argc, char **argv){
    
    LPOLESTR clsidstr = NULL;
    StringFromCLSID(&clsid, &clsidstr);
    printf("Our targeted CLSID is %ls\n", clsidstr);
    HRESULT hr;
    hr = CoInitialize(NULL);

    FARPROC DllGetClassObject = GetProcAddress(LoadLibrary("shell32.dll"), "DllGetClassObject");
    printf("DllGetClassObject is at 0x%p\n\n", DllGetClassObject);
    
    IClassFactory *icf = NULL;
    // Get shell32.DLL's IClassFactory
    hr = DllGetClassObject(&clsid, &IID_IClassFactory, (void **)&icf);

    if(hr != S_OK) {
            printf("DllGetClassObject failed to do something. Error %d HRESULT 0x%08x\n", GetLastError(), (unsigned int)hr);

            CoUninitialize();
            ExitProcess(0);          
    }
   //For debugging purposes 
    HMODULE shell32address = GetModuleHandle("shell32.dll");
    printf("shell32.dll address is :%p \tIClassFactory's Vtable address is:%p \n", shell32address,icf->lpVtbl);
    uint64_t val = (uint64_t)icf->lpVtbl - (uint64_t)shell32address;
    printf("The offset is 0x%p - 0x%p  = 0x%llx", icf->lpVtbl, shell32address, val);
    return 0;
 }

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 :

2. Ask the class factory to instantiate an object of the class through CreateInstance method

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:

 // Create an IDispatch object
   IDispatch *id = NULL;
   hr = icf->lpVtbl->CreateInstance(icf, NULL, &IID_IDispatch, (void **)&id);
    if(hr != S_OK) {
            printf("CreateInstance failed to do something. Error %d HRESULT 0x%08x\n", GetLastError(), (unsigned int)hr);

            CoUninitialize();
            ExitProcess(0);          
    }
    printf("[+]IDispatch's Vtable address is:%p \n",id->lpVtbl);
   uint64_t val1 = (uint64_t)id->lpVtbl - (uint64_t)shell32address;
   printf("The offset IDispatch is 0x%p - 0x%p  = 0x%llx\n", id->lpVtbl , shell32address , val1);

Again compile and run :

IDispatch interface usage : Exposes objects, methods and properties to programming tools and other applications that support Automation.

3. Get ShellExecute ID

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:

// get function ID
   WCHAR *member = L"ShellExecute";
   DISPID dispid = 0;

    hr = id->lpVtbl->GetIDsOfNames(id, &IID_NULL, &member, 1, LOCALE_USER_DEFAULT, &dispid);
    if(hr != S_OK) {
            printf("GetIDsOfNames failed to do something. Error %d HRESULT 0x%08x\n", GetLastError(), (unsigned int)hr);

            CoUninitialize();
            ExitProcess(0);          
    }

    printf("DISPID 0x%08x\n", dispid);

Again compile and run :

4.Initialize parameters to be used on Invoke method

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 :

HRESULT Invoke(
  [in]      IDispatch  *this
  [in]      DISPID     dispIdMember,
  [in]      REFIID     riid,
  [in]      LCID       lcid,
  [in]      WORD       wFlags,
  [in, out] DISPPARAMS *pDispParams,
  [out]     VARIANT    *pVarResult,
  [out]     EXCEPINFO  *pExcepInfo,
  [out]     UINT       *puArgErr
);

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.

typedef struct tagDISPPARAMS {
  VARIANTARG *rgvarg;//An array of arguments.
  DISPID     *rgdispidNamedArgs;
  UINT       cArgs; //The number of arguments.
  UINT       cNamedArgs;
} DISPPARAMS;

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

// initialize parameters

   //VARIANT describes arguments passed within DISPPARAMS.
    VARIANT args = { VT_EMPTY };
    args.vt = VT_BSTR;
    args.bstrVal = SysAllocString(L"calc");

    // Contains the arguments passed to ShellExecute method.
    DISPPARAMS dp = {&args, NULL, 1, 0};

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

5. Pop the calc

 VARIANT output = { VT_EMPTY };
 hr = id->lpVtbl->Invoke(id, dispid, &IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &dp, &output, NULL, NULL);
     if(hr != S_OK) {
            printf("Invoke failed to do something. Error %d HRESULT 0x%08x\n", GetLastError(), (unsigned int)hr);
            CoUninitialize();
            ExitProcess(0);          
    }

    id->lpVtbl->Release(id);
    icf->lpVtbl->Release(icf);
    SysFreeString(args.bstrVal);
    CoUninitialize();

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

Demo:

Conclusion

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.

References

Last updated