挂钩 - 打屁股

时间:2014-01-05 21:41:06

标签: c windows assembly hook hotpatching

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

user32.dll启用了热补丁,我想在实际函数之前覆盖NOP而不是覆盖函数本身。但是,当我将hotpatching设置为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;
}

你能告诉我什么是错的吗?

谢谢。

1 个答案:

答案 0 :(得分:6)

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

  1. 函数入口点设置为2字节的无操作mov edi, edi/hotpatch)。
  2. 每个函数入口点(/FUNCTIONPADMIN)都附加了五个连续的nop。
  3. 为了说明这一点,下面是一个支持hotpaching的函数的典型反汇编列表:

    (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}替换(1)来使该代码可达。 }。

    钩子函数必须使堆栈保持一致状态。它应该声明为__declspec(naked),以防止编译器生成函数prolog和epilog代码。最后一条指令必须根据钩子函数的调用约定执行堆栈清理,或者跳转到jmp $-5指定的地址处的钩子函数。