COM is very abstract idea in Object Oriented Programming (OOP). To understand this well, you need some in-depth knowledge of Inheritance in OOP, Virtual Method Table, COM in separate process and Data Marshaling with COM.
Those subroutines are part of a COM Proxy/Stub DLL. Proxy DLL are used when the COM interfaces are defined/implemented in separate DLL files. Think these as a simple function call but with COM virtual tables. In case of dynamic linking, DLL has exported functions and EXE files call them. In case of proxy/stub COM, the underlying functions are defined in a separate DLL files. When an executable call any method the parameters are marshalled (i.e. packed/assembled) through Proxy DLL to the main DLL where all the real things happens. Proxy DLLs contain just the list of those methods, aka. Virtual Functions in Virtual Method Table (vtable or vtbl). So, the "ObjectStublessClient" are the virtual functions only. In simple C language, this can be compared as function pointers in a structure (with oversimplification).
In IDA, go to View --> Open subviews --> Names or press Shift + F4 to open 'Names Window'.
Search for 'ProxyVtbl', you can find defined virtual tables. Here is an example of IOleCacheControl interface in Ole32.DLL file.
.rdata:00000001800CE9D0 ; $2F7D790A470334608EE0E1481017719B IOleCacheControlProxyVtbl
.rdata:00000001800CE9D0 _IOleCacheControlProxyVtbl dq offset IOleCacheControl_ProxyInfo; header.piid
.rdata:00000001800CE9D0 ; DATA XREF: .rdata:00000001800C9BF8↑o
.rdata:00000001800CE9D0 db 0D0h, 0AEh, 0Fh, 80h, 1, 3 dup(0); gap8
.rdata:00000001800CE9D0 dq offset IUnknown_QueryInterface_Proxy, offset IUnknown_AddRef_Proxy; Vtbl
.rdata:00000001800CE9D0 dq offset IUnknown_Release_Proxy, offset ObjectStublessClient3_0; Vtbl
.rdata:00000001800CE9D0 dq offset ObjectStublessClient4_0; Vtbl
.rdata:00000001800CEA08 align 10h
- In oleidl.h file (C++ interface):
MIDL_INTERFACE("00000129-0000-0000-C000-000000000046")
IOleCacheControl : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE OnRun(
LPDATAOBJECT pDataObject) = 0;
virtual HRESULT STDMETHODCALLTYPE OnStop( void) = 0;
};
GUID IID_IOleCacheControl = { 0x00000129, 0x0000, 0x0000, { 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 };
typedef struct _IOleCacheControl IOleCacheControl, *PIOleCacheControl;
struct _IOleCacheControl {
//0th IUnknown_QueryInterface_Proxy
HRESULT (__fastcall *QueryInterface )(
PIOleCacheControl* This,
GUID* riid,
PVOID* ppvObject
);
//1st IUnknown_AddRef_Proxy
ULONG (__fastcall *AddRef )(
PIOleCacheControl* This
);
//2nd IUnknown_Release_Proxy
ULONG (__fastcall *Release )(
PIOleCacheControl* This
);
//3rd ObjectStublessClient3_0
HRESULT (__fastcall *OnRun )(
PIOleCacheControl* This,
IDataObject* pDataObject
);
//4th ObjectStublessClient4_0
HRESULT (__fastcall *OnStop )(
PIOleCacheControl* This
);
};
The first three functions (QueryInterface, AddRef, Release) are inherited from (i.e. copied from) IUnknown interface. Then other remaining virtual functions are named with their offsets. Hence ObjectStublessClient3_0
is the OnRun()
and ObjectStublessClient4_0
is the OnStop()
function pointers. I changed the calling conventions to __fastcall
because Windows binary generally use that calling conventions.
One can see a real example of this method in my repository GitHub: WslReverse where I show the hidden COM interface of LxssManager.DLL.
Further Readings: