Run-time determination of VC++ virtual member function addresses: Take II

I wrote about this tricky little problem a while ago and wasn’t too happy with the desperate methods that seemed necessary. Since then, I’ve been shown a much cleaner way to do the same thing, by manipulating the vTable manually. It seems that Microsoft haven’t changed their vTable implementation since Visual Studio 6 (at least) and so with a little modification, the following piece of inline-assembly will do the trick: no muss, no fuss.

__declspec(naked) void* ResolveVirtualFunction(IDirect3DDevice9* pDevice, ...) {
    __asm {
        mov eax, dword ptr ss:[esp+0x08]
        add eax, 0x8
        cmp byte ptr ds:[eax-1], 0xA0
        mov eax, dword ptr ds:[eax]
        je normal_index
        and eax, 0xFF
normal_index:
        mov ecx, eax
        mov eax, dword ptr ss:[esp+0x4]
        mov eax, dword ptr ds:[eax]
        mov eax, dword ptr ds:[eax+ecx]
        retn
    }
}
 
// ...
 
// The function should be invoked like this:
void* address_device_present = ResolveVirtualFunction(device, &IDirect3DDevice9::Present);

Thanks go to Vuurvlieg for this function. The beauty (or horror), here, is the use of a variadic parameter-list to overcome C++’s strong-typing that would otherwise make this operation very difficult. Obviously, this implementation will only work for objects of type IDirect3DDevice9, but the method extends to any other class by simply replacing the class name in the function declaration. Don’t be tempted to generalise this function to IUnknown or some other common base-class, as you’ll quickly run into problems with object-slicing. A final warning to those still using Visual C++ 6 (not that you deserve any help for such a crime): you’ll need to drop the ampersand from the second argument in the function call, as VC++6 handles function pointers slightly differently.

13 Responses to “Run-time determination of VC++ virtual member function addresses: Take II”

  1. Hi Greg,

    I tried using this method to get a member function address but I believe it only works on virtual methods right? I’m trying to retrieve the address, in runtime, of one of my hook functions which is a member function in opposite to you where you used a global method. Do you have any hints on how this can be achieved? a simple &Class:MethodName doesn’t work because as you know, member functions are no ordinary func ptrs. They point to a sort of struct containing many info.

    Also, regarding the run-time determination of a virtual method explained in this article, what would be the member values of the class once you’re in the method? I’m trying to understand how all of this works. Any articles/books you recommend for reading?

    Thanks,
    Alexandre

  2. Hi Alexandre.

    C++ offers a means of doing this so it’s best to avoid nasty inline assembly hacks, despite the terrifying syntax. At least, this is the case for function-pointers alone - if you need the address in void* or integral form then direct conversion will require some assembly, as C++ strictly disallows casting from a function pointer.

    Suppose we have a class DLLInjection with a static function, CallThreadProc. Then if the prototype is given by the following (I picked something general);

    bool DLLInjection::CallThreadProc(const char* thread_proc_name, void* parameter, DWORD timeout_ms, DWORD &exit_code);

    Then we can define a function pointer using:

    typedef bool (__thiscall DLLInjection::*FPTR) (const char*, void*, DWORD, DWORD&);
    FPTR fptr = &DLLInjection::CallThreadProc;

    We define a type FPTR to hold the member-function-pointer and instantiate it as fptr. You can do this without a typedef, but that only serves to make things even messier. If you then need this as a plain address, use something like:

    void* address = NULL;
    __asm MOV EAX, fptr;
    __asm MOV address, EAX;

    That covers the address resolution (a good explanation can be found on goingware). As for usage of the pointer, you’ll probably want to read up on the thiscall calling convention. Simply put (in this - static - case), the function is called just like any stdcall function (the default for externally linked functions) only the this pointer is loaded into ECX prior to calling.

    So to use this function from assembly, you could invoke it thus:

    DWORD exit_code, timeout_ms;
    void* parameter;
    char* thread_proc_name;
    DLLInjection* this_ptr; // - will become 'this' in the member function
    __asm {
        LEA EAX, exit_code
        PUSH EAX
        PUSH timeout_ms
        PUSH parameter
        PUSH thread_proc_name
        MOV ECX, this_ptr
        CALL fptr
    }
  3. Hi again Greg,

    Have you tested this code on your end? Does it require to be in a specific scope? I tried it with a Direct3DDevice9 I created from IDirect3D9::Direct3DCreate9 which wasn’t null, and I tried with an instance of one of my classes that has a pure virtual method that is overridden by a subclass. i.e. MainClass* pInst = new SubClass(); (SubClass inherits from MainClass). I changed declaration of the resolveaddr method to accept a MainClass* and I still got an access violation. :\ I am using VS2005. I will do more tests this weekend.

    Thank you,
    Alexandre

  4. Right, sorry about that. I did test the code, but I decided to ‘clean’ it up a bit before posting and neglected to update the stack offsets in the process.

    I’ve corrected that little error and it now works just fine :).

  5. Frank Harmann Says:
    March 30th, 2008 at 11:01 am

    Hi,

    is it possible that the function is broken in VC++ 2008 Express?

    Doing this:
    HWND hWnd = ::GetTopWindow(NULL);

    if (hWnd == NULL) MessageBox(NULL, “Can get Window handle”, “D3DHOOK.dll”, MB_OK);
    d3d->CreateDevice(
    D3DADAPTER_DEFAULT,
    D3DDEVTYPE_HAL,
    hWnd,
    D3DCREATE_SOFTWARE_VERTEXPROCESSING,
    &dp,
    &d3dDevice
    );
    if (d3dDevice == NULL) MessageBox(NULL, “Can get 3D Device”, “D3DHOOK.dll”, MB_OK);
    char buffer[100];
    address_DevicePresent = ResolveVirtualFunction(d3dDevice, &IDirect3DDevice9::Present);
    address_SwapChainPresent = ResolveVirtualFunction(d3dDevice, &IDirect3DSwapChain9::Present);
    address_Clear = ResolveVirtualFunction(d3dDevice, &IDirect3DDevice9::Clear);
    sprintf(buffer,”Device present from vtable at %p”,address_DevicePresent);
    MessageBox(NULL, buffer,”D3DHOOK.dll”, MB_OK);
    sprintf(buffer,”Swapchain present from vtable at %p”,address_SwapChainPresent);
    MessageBox(NULL, buffer,”D3DHOOK.dll”, MB_OK);
    sprintf(buffer,”Clear from vtable at %p”,address_Clear);
    MessageBox(NULL, buffer,”D3DHOOK.dll”, MB_OK);
    d3dDevice->Release();

    i get exactly the same pointer for all three functions (which looks strange to me).

    Thanks,

    Frank

  6. Frank Harmann Says:
    March 30th, 2008 at 11:04 am

    Vista64 SP1 btw (if thats makes a difference??)

  7. Hi Frank.

    Yes, it is quite likely that something’s up on my end. I’ve had a couple of problem reports that I have been too lazy to investigate fully, and your code looks fine. I’m afraid I can’t recommend anything at the moment, other than manual investigation under a debugger. At least until I can get to the root of the problem.

  8. This can probably be done in 4 lines of C++ with a template function and a couple of unsafe casts :)

  9. That’s what we all thought first time ’round, Anonymouse. I’d love to see your implementation ;)

  10. Note that the function in the blog post does not work in all cases (eg. when the vtable offset is 0) and the below function only works on thunks produced by Visual C++ (and probably not on the type of thunks that the original function examines), so this is only a proof-of-concept.

    template
    void* ResolveVirtualFunction(C obj, VF fun) {
    void* code = *(void**)&fun;
    int offset = *((char*)code + 4);
    void* vtable = *(void**)obj;
    void* entry = *((void**)vtable + offset);

    return entry;
    }

  11. Your blog software destroyed the template arguments in the last post, the first line was supposed to be:

    template < typename C, typename VF >

    You can modify the code to work exactly as the assembly code, for that you need to modify the “int offset = …” to use an int* and conditionally do the offset & 0xFF.

  12. As for hooking methods in COM interfaces: the easiest way to do this is to use eg. the DirectX headers in C mode (by a define which I can not remember right now), so the vtable is exposed directly. The hook then basically boils down to patching the vtable:

    device->lpVtbl->IDirect3DDevice9_Present = pointer_to_my_function;

  13. Four lines of C then :P. That’s certainly more readable than the inline assembly, but there’s no way to use a C++ cast (even a reinterpret_cast) for that first line, as far as I could tell. Nevertheless, that’s some useful information, particularly regarding the vTable hooking.

    Thanks
    Greg

Leave a Reply