非托管函数挂钩、调用约定的堆栈/寄存器问题?
这不是 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
您正在尝试挂钩 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.
看来我想通了。 @Hans Passant 是对的:我必须保存隐藏的
this
参数。 EasyHook 实际上会处理除此之外的所有事情(例如清理 .net 内容)。由于this
是第一个参数,我只是将其添加到我的函数中(connection
是我的this
引用):无法真正解释为什么会这样确实有效(而且我认为我确实理解了其中的大部分内容:)我真的应该回去学习更多的汇编器/编译理论。
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). Asthis
is the first argument i simply did add it to my function (connection
is mythis
reference):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.