Calling IUnknown COM interface

While completing challenge 9 of Flare-On 7, I found a lack of information out there on reverse engineering COM objects. Most tutorials are written from a developer point of view where the Interface Definition Language (IDL) file is available. Reverse engineers don't usually have the luxury and may have to write COM clients without any clue as to what functions are exposed and what arguments to pass to the functions. The code snippet below should allow you to load a COM InprocServer object. Do remember to use regsvr32 to register the COM object and to change the CLSID in the code below.

#include "windows.h"
#include <ObjBase.h>
#include <iostream>
#include "tchar.h"

static const GUID CLSID_SERVER = 
{ 0xCEEACC6E, 0xCCB2, 0x4C4F, { 0xbc, 0xf6, 0xd2, 0x17, 0x60, 0x37, 0xa9, 0xa7 } };

//optional. Can use symbolic constant IID_IUnknown
static const GUID CLSID_IUNKOWN = 
{ 0x00000000, 0x0000, 0x0000, { 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 } };

interface IUnKnownClass :IUnknown 
{
    virtual HRESULT  __stdcall func1(
        const uint64_t y  
    ) = 0;

    virtual HRESULT  __stdcall func2(
        const uint16_t z
    ) = 0;

    virtual HRESULT  __stdcall func3() = 0;
};

int _tmain(int argc, _TCHAR* argv[])
{

    HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
    IUnKnownClass* cl = NULL;
    hr = CoCreateInstance(CLSID_SERVER, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown, (void**)&cl );

    if (SUCCEEDED(hr)) {
        cl->func1(5370220704);
        cl->func2(205);
        cl->func3();
    }
    cl->Release();

    CoFreeUnusedLibraries();
    return 0;
}

After loading the COM object successfully, we can now step through it in the Visual Studio debugger. We see that the vtable contains 3 entries, these are actually functions exposed by the InprocServer object and information about the memory address where the function is located. In order to call these functions, we can just define our own IDL. I have created my own IDL with 3 functions named func1, func2 and func3. The first function defined will automatically map to the first function in the vtable located at 0x7ffc389214b4, the second defined function will map to the second function in the vtable and so on.

image

We will then have to debug the binary in a debugger like x64dbg or windbg, breakpoint at the first instruction of the function, 0x7ffc389214b4 in the example, and slowly step through the instructions. If the function expects an argument, we will likely encounter an access violation. We will then have to figure out what type of argument it expects. If the access violation occurs because it is unable to write to the memory address, the function may be expecting an argument containing address of writable memory space. In the example above, I added const uint64_t y to the IDL for func1 and passed in the address of writable memory. You could also initialise a buffer and pass in the address of the buffer, char* buffer = new char[100]; func1(&buffer); After re-running the program, we can finally see what the function is trying to write out.