3

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

Q1)代码是否从高内存进入低内存,反之亦然?

Q2)更重要的是,在计算replacement offset的过程中,为什么要减去函数前导码?是因为偏移量是从指令的结尾而不是开头开始的吗?

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 是指向新目的地的指针。我不明白to地址的计算,为什么你必须将原始目的地添加到functionForHook + opcodeOffset?

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

3 回答 3

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 字节)

于 2013-09-05T19:52:40.250 回答
1

Q1) 程序从低地址到最高地址运行(即程序计数器随着每条指令的大小而增加,除非在跳转、调用或 ret 的情况下)。但我可能错过了问题的重点。

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

Q3)这段代码很奇怪,但它可能有效。我假设 *relAddrset 最初包含到 originalFunction 的跳转偏移量(即 *relAddSet==originalFunction-relativeOffset)。如果为真,最终结果是 *reladdrSet 包含到 functionFoHook 的跳转偏移量。事实上,最后一条指令变为:

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

== functionForHook-relativeOffset

于 2013-09-06T06:48:36.083 回答
0

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

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

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

于 2013-09-05T19:49:38.770 回答