非托管函数挂钩,堆栈/寄存器问题与调用约定?

时间:2010-12-02 21:56:32

标签: c# assembly hook calling-convention detours

这不是关于EasyHook的特定功能,而是关于一般的挂钩。我想用这个签名挂钩一个函数:

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

这显然是非托管代码,我正在尝试使用EasyHook将其与我的托管c#代码挂钩。但我认为这不是EasyHook在这里造成问题,而是我在调用约定等方面的知识...... 这是我定义DllImport和删除的方式:

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

但是,只要我注入钩子,钩住的程序就会继续崩溃 - 没什么大惊喜。我认为它是调用约定的问题,并且我的钩子函数以某种方式干扰了钩子程序的堆栈。

所以我看了另一个项目谁挂钩相同的功能,但在c ++中弯路(挂钩部分):

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

被叫函数:

__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];
    }
}

(目标程序集和c ++示例都使用visual c ++编译)。我想在调用原始函数之前我必须保存一些寄存器并修复堆栈吗?或者其他任何想法我在这里做错了什么?

2 个答案:

答案 0 :(得分:7)

您正在尝试挂钩C ++类实例方法。它有一个隐藏的参数, this 。此参数通常通过带有__this调用约定的ECX寄存器传递。这就是Detours版本所做的。

实现这一点非常重要,必须尽早保留CPU寄存器值,特别是ECX。这需要一个使用机器代码的存根,当然在托管存根中没有机器代码。我怀疑EasyHook是否支持它,它肯定不会在功能列表中得到承诺。

答案 1 :(得分:0)

看起来我想通了。 @Hans Passant是对的:我必须保存隐藏的this参数。 EasyHook确实照顾了除此之外的一切(比如清理.net的东西)。由于this是第一个参数,我只是将其添加到我的函数中(connection是我的this引用):

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

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

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

无法解释为什么这样做有效(我认为我确实理解了大部分内容:)我真的应该回去学习一些更多的汇编/编译理论。