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

发布于 2024-10-05 22:20:32 字数 2310 浏览 0 评论 0原文

这不是 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++ 编译的)。我想在调用原始函数之前我必须保存一些寄存器并修复堆栈?或者还有其他想法我在这里做错了什么吗?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

闻呓 2024-10-12 22:20:32

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

正确执行此操作非常重要,必须尽早保留 CPU 寄存器值,尤其是 ECX。这需要一个使用机器代码的存根,当然托管存根中没有机器代码。我怀疑 EasyHook 是否支持它,功能列表中肯定没有承诺。

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.

风情万种。 2024-10-12 22:20:32

看来我想通了。 @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);

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

Looks like i figured it out. @Hans Passant was right: I have to save the hidden this argument. EasyHook does actually take care for everything but this (like cleaning up the .net stuff). As this is the first argument i simply did add it to my function (connection is my this reference):

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

Can't really explain why this does work (also I think I did understand most of it :) I really should go back and learn some more assembler/compiling theory.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文