来自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));
答案 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)。
我无法回答你的第三个问题。请提供一些上下文以及代码应该做什么。