Search code examples
c#assemblyhookcalling-conventiondetours

Unmanaged function hooking, stack/register problem with calling convention?


This is not a particular function about EasyHook but about hooking in general. I want to hook a function with this signature:

public: int __thiscall Connection_t::Send(unsigned int,unsigned int,void const *)

This is clearly unmanaged code and I'm trying to hook it with my managed c# code using EasyHook.But I think it's not EasyHook causing problems here but my knowlegde on calling conventions etc...
This is how I define DllImport and delete:

    public static int Send_Hooked(uint connection, uint size, IntPtr pDataBlock)
    {
        return Send(connection, size, pDataBlock);
    }

    [DllImport("Connection.dll", EntryPoint = "?Send@Connection_t@@QAEHIIPBX@Z", CallingConvention = CallingConvention.ThisCall)]
    static extern int Send(uint connection, uint size, IntPtr pDataBlock);

    [UnmanagedFunctionPointer(CallingConvention.ThisCall, CharSet = CharSet.Unicode, SetLastError = true)]
    delegate int DSend(uint connection, uint size, IntPtr pDataBlock);

But the hooked programm keeps on crashing as soon as I inject the hook - no big surprise. I supppose it's a problem of the calling convention and that my hooking-function somehow interferes with the stack of the hooked programm.

So I had a look at another project who do hook the same function but with detours in c++ (the hooking part):

Func =  (int (__stdcall *)(unsigned int, unsigned short, void const ))::GetProcAddress(::GetModuleHandle("Connection.dll"), "?Send@Connection_t@@QAEHIIPBX@Z");
PVOID DetourPtr;
PVOID TargetPtr;
DetourTransactionBegin();
DetourAttachEx(&Func, SendConnectionHook, &Trampoline, &TargetPtr, &DetourPtr );
DetourTransactionCommit();

And the called function:

__declspec(naked) void SendConnectionHook (CPU_CONTEXT saved_regs, void * ret_addr, WORD arg1, DWORD arg2, DWORD arg3)
{
    DWORD edi_value;
    DWORD old_last_error;

    __asm
    {
        pushad;   /* first "argument", which is also used to store registers */
        push ecx; /* padding so that ebp+8 refers to the first "argument" */

        /* set up standard prologue */
        push ebp;
        mov ebp, esp;
        sub esp, __LOCAL_SIZE;
    }

    edi_value = saved_regs.edi;
    old_last_error = GetLastError();
    OnConnectionSend((void *) saved_regs.ecx, (unsigned char *) arg3, arg2);
    SetLastError(old_last_error);

    __asm
    {
        /* standard epilogue */
        mov esp, ebp;
        pop ebp;

        pop ecx; /* clear padding */
        popad; /* clear first "argument" */
        jmp [Trampoline];
    }
}

(Target assembly and c++ example are both compiled with visual c++). I guess I'll have to save some registers and repair the stack before I call the original function? Or any other idea what I'm doing wrong here?


Solution

  • You are trying to hook a C++ class instance method. It has a hidden argument, this. This argument is commonly passed through the ECX register with the __this calling convention. That's what you see that Detours version doing.

    Getting this right is quite untrivial, the CPU register values must be preserved early, ECX in particular. That requires a stub that uses machine code, no machine code in a managed stub of course. I doubt that EasyHook has any support for it, it certainly isn't promised in the feature list.