【问题标题】:How does function hooking actually work? WinAPI, C++函数挂钩实际上是如何工作的? WinAPI, C++
【发布时间】:2017-09-15 12:11:09
【问题描述】:

我将我的 DLL 注入到一个进程中,然后像这样挂钩一个函数:(recv)

BOOL HookFunction(LPCWSTR moduleName, LPCSTR funcName, LPVOID funcProxy, 
unsigned char* lpBackup)
{
     BYTE jmp[6] = { 0xe9,0x00,0x00,0x00,0x00,0xc3 };
     DWORD funcAddr = (DWORD)GetProcAddress(GetModuleHandle(moduleName), funcName);

     DWORD prev;
     VirtualProtect((LPVOID)funcAddr, 6, PAGE_EXECUTE_READWRITE, &prev);

     ReadProcessMemory(GetCurrentProcess(), (LPVOID)funcAddr, lpBackup, 6, NULL);

     DWORD proxy = ((DWORD)funcProxy - funcAddr) - 5;
     memcpy(&jmp[1], &proxy, 4);
     memcpy((LPVOID)funcAddr, jmp, 6);

     VirtualProtect((LPVOID)funcAddr, 6, prev, &prev);
     FlushInstructionCache(GetCurrentProcess(), NULL, NULL);

     return funcAddr;
}

// Hook
HookFunction(L"ws2_32.dll", "recv", (LPVOID*)nRecv, hookR);

我正在附加一个调试器,结果如下:

挂钩前:

挂钩后:

虽然有几件事我不明白,因为我仍在尝试理解和可视化堆栈、堆等如何在调试器中一起工作。

BYTE jmp[6] = { 0xe9,0x00,0x00,0x00,0x00,0xc3 };

我是不是在这里替换指令,例如将原始函数的“move, edi, edi” (recv) 替换为 0xe9?然后是 0x00 的下一条指令......或者它究竟是如何工作的?

任何详细的解释将不胜感激。

【问题讨论】:

  • 看起来更像 C 而不是 C++,至少在风格上。
  • 代码使用的是 Windows API 函数,这些函数与标准 C 或 C++ 无关。要获得更有用的答案,请移除 C++ 标签,并添加相关标签,例如 windows。
  • 谢谢你,已经做到了:)
  • @Anders 这是一个非常无效的声明。我正在学习,我需要帮助,在这里问这件事是不是很糟糕,或者你们是天才?
  • 您正在使用 ReadProcessMemory,但使用的是普通的 memcpy 写入,这闻起来像复制和粘贴。在尝试以这种方式挂钩之前,您应该了解地址空间和汇编语言。 IAT 挂钩是一个更安全的选择。

标签: windows debugging hook code-injection


【解决方案1】:
BOOL HookFunction(LPCWSTR moduleName, LPCSTR funcName, LPVOID funcProxy, 
unsigned char* lpBackup)
{
     BYTE jmp[6] = 
     { 
        0xe9,0x00,0x00,0x00,0x00, /*JMP and 4 bytes of offset*/
        0xc3                      /*RET*/
     };

     /*
       JMP (e9) is relative, its 32-bit signed immediate operand encodes the 
       number of bytes to jump forward relative to the NEXT instruction.
     */

     /* Get the target address of the function to hook */
     DWORD funcAddr = (DWORD)GetProcAddress(GetModuleHandle(moduleName), funcName);

     /* Code is not necessarily mapped as writable, we remap it */
     DWORD prev;
     VirtualProtect((LPVOID)funcAddr, 6, PAGE_EXECUTE_READWRITE, &prev);

     /* Read the original 6 bytes we are going to overwrite */
     ReadProcessMemory(GetCurrentProcess(), (LPVOID)funcAddr, lpBackup, 6, NULL);

     /* 
        Compute the offset: target - source 
        target = funcProxy
        source = funcAddr + 5 (length of JMP)

        target - source = funcProxy - funcAddr - 5
     */
     DWORD proxy = ((DWORD)funcProxy - funcAddr) - 5;

     /*
        Create the JMP instruction: set the offset
     */
     memcpy(&jmp[1], &proxy, 4);

     /* Overwrite the first 6 bytes of the target function */
     memcpy((LPVOID)funcAddr, jmp, 6);

     /* Reset the memory protection to its original value*/
     VirtualProtect((LPVOID)funcAddr, 6, prev, &prev);

     /* Since we write to a code section with DS, flush the L1 I cache */
     FlushInstructionCache(GetCurrentProcess(), NULL, NULL);

     return funcAddr;
}

HookFunction 在内存中创建一小段 x86 代码(一个trampoline)的形式

jmp <0>     ;e9 00 00 00 00
ret         ;c3

其中&lt;0&gt; 被跳转的编码目标(参见代码中的 cmets)连续覆盖 - 钩子函数。
蹦床,一旦制作完成,就变成了

jmp funcProxy     ;e9 .. .. .. ..
ret               ;c3

然后这段代码直接写在被钩子函数的开头,从而覆盖了它的原始代码。

代码是多语言的 - 它适用于 x86 和 x86-64。


将钩子函数的原始代码复制到lpBackup中。
这需要再次调用原来的函数,钩子函数不先恢复就无法调用。

由于这很昂贵且不可重入,因此更简洁的方法是修改Import Address Table - 但是,此解决方案的有效性取决于您的要求。

【讨论】:

  • 非常感谢您的解释,不胜感激! :)
猜你喜欢
  • 2013-09-19
  • 1970-01-01
  • 2014-03-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-09
  • 1970-01-01
相关资源
最近更新 更多