4

我正在尝试挂钩 Windows API 函数 FindWindowA()。我用下面的代码成功地做到了,没有“热补丁”它:我已经覆盖了函数开头的字节。调用 myHook() 并在调用 FindWindowA() 时显示一个消息框。

user32.dll 启用了热补丁,我想在实际函数之前覆盖 NOP,而不是覆盖函数本身。但是,当我将热补丁设置为 TRUE 时,下面的代码将不起作用。当 FindWindowA() 被执行时它什么也不做。

#include <stdio.h>
#include <windows.h>

void myHook()
{
    MessageBoxA(NULL, "Hooked", "Hook", MB_ICONINFORMATION);
}

int main(int argc, char *argv[])
{
    BOOLEAN hotpatching = FALSE;

    LPVOID fwAddress = GetProcAddress(GetModuleHandleA("user32.dll"), "FindWindowA");
    LPVOID fwHotpatchingAddress = (LPVOID)((DWORD)fwAddress - 5);
    LPVOID myHookAddress = &myHook;

    DWORD jmpOffset = (DWORD)&myHook - (DWORD)(!hotpatching ? fwAddress : fwHotpatchingAddress) - 5; // -5 because "JMP offset" = 5 bytes (1 + 4)

    printf("fwAddress: %X\n", fwAddress);
    printf("fwHotpatchingAddress: %X\n", fwHotpatchingAddress);
    printf("myHookAddress: %X\n", myHookAddress);
    printf("jmpOffset: %X\n", jmpOffset);
    printf("Ready?\n\n");
    getchar();


    char JMP[1] = {0xE9};
    char RETN[1] = {0xC3};

    LPVOID offset0 = NULL;
    LPVOID offset1 = NULL;
    LPVOID offset2 = NULL;

    if (!hotpatching)
        offset0 = fwAddress;
    else
        offset0 = fwHotpatchingAddress;

    offset1 = (LPVOID)((DWORD)offset0 + 1);
    offset2 = (LPVOID)((DWORD)offset1 + 4);


    DWORD oldProtect = 0;
    VirtualProtect(offset0, 6, PAGE_EXECUTE_READWRITE, &oldProtect);

    memcpy(fwAddress, JMP, 1);
    memcpy(offset1, &jmpOffset, 4);
    memcpy(offset2, RETN, 1);

    VirtualProtect(offset0, 6, oldProtect, &oldProtect);


    printf("FindWindowA() Patched");
    getchar();


    FindWindowA(NULL, "Test");
    getchar();


    return 0;
}

你能告诉我有什么问题吗?

谢谢你。

4

1 回答 1

6

启用热补丁的可执行映像由编译器和链接器准备,以允许在使用时替换映像。应用了以下两个更改 (x86):

  1. 函数入口点设置为 2 字节无操作mov edi, edi( /hotpatch )。
  2. 每个函数入口点 ( /FUNCTIONPADMIN )前附加五个连续的 nop 。

为了说明这一点,下面是一个启用热补丁的函数的典型反汇编列表:

(2) 768C8D66 90                   nop  
    768C8D67 90                   nop  
    768C8D68 90                   nop  
    768C8D69 90                   nop  
    768C8D6A 90                   nop  
(1) 768C8D6B 8B FF                mov         edi,edi
(3) 768C8D6D 55                   push        ebp  
    768C8D6E 8B EC                mov         ebp,esp  

(1)用 2 字节无操作指定函数入口点。(2)是链接器提供的填充,(3)是非平凡函数实现的开始。

要挂接到一个函数,您必须(2)用跳转到您的挂接函数来覆盖jmp myHook,并通过替换(1)为相对跳转来使此代码可访问jmp $-5

钩子函数必须使堆栈保持一致状态。它应该被声明为__declspec(naked)防止编译器生成函数 prolog 和 epilog 代码。最后一条指令必须要么按照挂钩函数的调用约定执行堆栈清理,要么跳回到由 指定的地址处的挂钩函数(3)

于 2014-01-06T09:08:09.700 回答