计算hotpatching /内联函数挂钩的偏移量

时间:2013-09-05 15:12:34

标签: c++ c hook hotpatching

来自http://lastfrag.com/hotpatching-and-inline-hooking-explained/

Q1)代码是从高内存进入低内存还是低内存?

Q2)更重要的是,在计算替换偏移量时,为什么必须减去函数前导码?是因为偏移从指令的结尾开始而不是从开头开始吗?

DWORD ReplacementAddressOffset = ReplacementAddress - OriginalAddress - 5;

完整代码:

void HookAPI(wchar_t *Module, char *API, DWORD Function)
{
    HMODULE hModule = LoadLibrary(Module);
    DWORD OriginalAddress = (DWORD)GetProcAddress(hModule, API);
    DWORD ReplacementAddress = (DWORD)Function;
    DWORD ReplacementAddressOffset = ReplacementAddress - OriginalAddress - 5;
    LPBYTE pOriginalAddress = (LPBYTE)OriginalAddress;
    LPBYTE pReplacementAddressOffset = (LPBYTE)(&ReplacementAddressOffset);

    DWORD OldProtect = 0;
    DWORD NewProtect = PAGE_EXECUTE_READWRITE;

    VirtualProtect((PVOID)OriginalAddress, 5, NewProtect, &OldProtect);

    for (int i = 0; i < 5; i++)
        Store[i] = pOriginalAddress[i];

    pOriginalAddress[0] = (BYTE)0xE9;

    for (int i = 0; i < 4; i++)
        pOriginalAddress[i + 1] = pReplacementAddressOffset[i];

    VirtualProtect((PVOID)OriginalAddress, 5, OldProtect, &NewProtect);

    FlushInstructionCache(GetCurrentProcess(), NULL, NULL);

    FreeLibrary(hModule);
}

Q3)在此代码中,正在替换jmp指令的相对地址; relAddrSet是指向原始目标的指针; to是指向新目标的指针。我不明白地址的计算,为什么你必须将原始目的地添加到functionForHook + opcodeOffset?

DWORD *relAddrSet = (DWORD *)(currentOpcode + 1);
DWORD_PTR to = (*relAddrSet) + ((DWORD_PTR)functionForHook + opcodeOffset);
*relAddrSet = (DWORD)(to - ((DWORD_PTR)originalFunction + opcodeOffset));

3 个答案:

答案 0 :(得分:1)

是的,相对地址是指令后的偏移量,这就是你必须减去5的原因。

但是,在我看来,你应该忘记相对跳跃的想法并尝试绝对跳跃 为什么?因为它更容易兼容x86-64(相对跳跃限制为 +/- 2GB )。

绝对跳跃是(x64):

48 b8 ef cd ab 89 67 45 23 01   mov rax, 0x0123456789abcdef
ff e0                           jmp rax

对于x86:

b8 67 45 23 01   mov eax, 0x01234567
ff e0            jmp eax

这是修改后的代码(加载器现在是7个字节而不是5个):

void HookAPI(wchar_t *Module, char *API, DWORD Function)
{
    HMODULE hModule = LoadLibrary(Module);
    DWORD OriginalAddress = (DWORD)GetProcAddress(hModule, API);

    DWORD OldProtect = 0;
    DWORD NewProtect = PAGE_EXECUTE_READWRITE;

    VirtualProtect((PVOID)OriginalAddress, 7, NewProtect, &OldProtect);

    memcpy(Store, OriginalAddress, 7);

    memcpy(OriginalAddress, "\xb8\x00\x00\x00\x00\xff\xe0", 7);
    memcpy(OriginalAddress+1, &ReplacementAddress, sizeof(void*));

    VirtualProtect((PVOID)OriginalAddress, 7, OldProtect, &NewProtect);

    FlushInstructionCache(GetCurrentProcess(), NULL, NULL);

    FreeLibrary(hModule);
}

x64的代码相同,但您必须在开头或结尾添加2 nops 90),以便与以下指令的大小相匹配,因此加载程序是"\x48\xb8<8-bytes addr>\xff\xe0\x90\x90"(14个字节)

答案 1 :(得分:1)

Q1)程序从低地址到最高地址运行(即程序计数器增加每条指令的大小,除非是跳转,调用或返回)。但我可能忽略了问题的重点。

Q2)是的,在x86上,在程序计数器增加了跳转指令的大小(5个字节)后执行跳转;当CPU将跳转偏移量添加到程序计数器以计算目标地址时,程序计数器已​​经增加了5个。

Q3)这段代码非常奇怪,但可能有用。我想* relAddrset最初包含对originalFunction的跳转偏移(即* relAddSet == originalFunction-relativeOffset)。如果是这样,最终结果是* reladdrSet包含到functionFoHook的跳转偏移量。实际上,最后一条指令变为:

* relAddrSet =(originalFunction-relativeOffset)+ functionForHook-originalFunction

== functionForHook-relativeOffset

答案 2 :(得分:0)

是的,如果我正确理解了这个问题,代码会“向前”运行。如果一条指令没有分支,则执行一条指令。

执行相对跳转(JMP,CALL)的指令相对于 next 指令的开头执行跳转。这就是为什么你必须从差异中减去指令的长度(这里:5)。

我无法回答你的第三个问题。请提供一些上下文以及代码应该做什么。