Inline Hook还是挺好玩的
文章很短,只是打个总结,记录一些有趣的发现
写在前面
之前在学习hook的时候,发现抄的一段代码只能在Debug模式下运行,调试后发现MSVC很有趣的一个点
当我们使用Debug模式编译的时候,程序为了调试方便,会将所有函数加入这个表中
比如
1 2 3 4 5 6 7 8 9
| void oldtest() { MessageBoxA(NULL, "not hook", "test", NULL); }
int main() { oldtest(); }
|
按照直觉
这里的call
应该会直接来到函数的位置,但是真实情况并非如此
我们会发现一张跳表,根据距离这张表的地址差进行跳转,那段hook只是修改了这一段代码
x86 hook
首先是x86
为啥叫32位架构,最突出的就是一个寄存器有32位(bit)大小,那么计算一下
1
| (1<<32)/int(1024*1024*1024) = 4
|
没错,32位的寄存器最多只能存放4GB个数字(比如从1
到0x100000000
),
由于要做加减发(有负数),所以能找到的地址位±2GB
,
那么8位为一个字节,那么一个寄存器只能由4个字节,
jmp
根据4字节这样对齐,所以32位下我们只需要覆盖5字节就能实现任意地址的跳转,
再加上之前的debug模式,可以写出下列hook代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| void Hooks::InlineHook_x32(FuncAddr HookFunction) { DWORD old; DWORD size = 5; FuncAddr offset = 0;
if (!VirtualProtect(LPVOID(this->FunctionAddr), size, PAGE_EXECUTE_READWRITE, &old)) { Error("Can't Set Memory read/write"); return; }
#ifdef _DEBUG offset = HookFunction - this->FunctionAddr - 5; *(char*)this->FunctionAddr = '\xE9'; *(DWORD*)(this->FunctionAddr + 1) = offset;
size = 5; FuncAddr RealAddr = this->GetRealAddr((void *)HookFunction); FuncAddr func_size = this->GetProcSize(RealAddr) - 1; if (!VirtualProtect(LPVOID(RealAddr + func_size), size, PAGE_EXECUTE_READWRITE, &old)) { Error("Can't Set Memory read/write"); return; } *(char*)(RealAddr + func_size + 0) = '\xE9'; offset = this->RealAddr - RealAddr; *(DWORD*)(RealAddr + func_size + 1) = (offset - func_size - 5);
#else char head[6] = { 0 }; memcpy(head, (void*)this->FunctionAddr, 5);
if (HookFunction > this->FunctionAddr) offset = (HookFunction - this->FunctionAddr - 5); else offset = ~(this->FunctionAddr - HookFunction - 5);
*(char*)(this->FunctionAddr + 0) = '\xE9'; *(DWORD*)(this->FunctionAddr + 1) = offset - 9;
FuncAddr func_size = this->GetProcSize(HookFunction) - 1; size = 10; if (!VirtualProtect(LPVOID(HookFunction + func_size), size, PAGE_EXECUTE_READWRITE, &old)) { Error("Can't Set Memory read/write"); return; } memcpy((void*)((FuncAddr)HookFunction + func_size), head, 5); *(char*)(HookFunction + func_size + 5) = '\xE9'; *(DWORD*)(HookFunction + func_size + 6) = ~(offset + func_size); #endif }
|
x64 hook
上面讲到了x86 hook只能2GB内内存寻址,到了x64寻址空间大大加大,单纯jmp
和call
已经无法跳转到地址,后来我看到一个很有意思的方法
1 2 3 4 5 6
| [Bits 64]
_start: push addr.low mov dword [rsp+4], addr.high ret
|
这个方法不会污染寄存器,也不会对栈造成影响,所以得到hook函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| BOOL Hook::EnHook() { if (this->OldFuncOpcode != NULL) { std::cout << "[!]Already Hooked\n"; return FALSE; } DWORD old; VirtualProtect((LPVOID)this->OldFunc, this->OpSzie, PAGE_EXECUTE_READWRITE, &old); this->OldFuncOpcode = (PBYTE)malloc(this->OpSzie + 1); if (this->OldFuncOpcode == NULL) { std::cout << "[!] Hook Function Malloc Failed\n"; return FALSE; }
memcpy(this->OldFuncOpcode, (void*)this->OldFunc, this->OpSzie); std::cout << "[*] Capture The First " << this->OpSzie << " Bytes: "; std::vector<BYTE> vec(this->OldFuncOpcode, this->OldFuncOpcode + this->OpSzie); std::cout << std::hex; for (int num : vec) std::cout << "0x" << num << " "; std::cout << std::dec << "\n"; std::cout << "[*] Now Start Hook\n";
unsigned char jmpopcode[14] = { 0x68, 0x00, 0x00, 0x00, 0x00, 0xC7, 0x44, 0x24, 0x04, 0x00, 0x00, 0x00,0x00, 0xC3 };
FuncAddr gap = this->NewFunc; *(DWORD32*)(jmpopcode + 1) = (DWORD32)gap; *(DWORD32*)(jmpopcode + 9) = (DWORD32)(gap >> 32); std::cout << "[*] Jmp Code Set\n";
VirtualProtect((LPVOID)this->OldFunc, this->OpSzie, PAGE_EXECUTE_READWRITE, &old); memcpy((PVOID)this->OldFunc, jmpopcode, this->OpSzie); std::cout << "[*] Opcode Set\n"; return TRUE; }
|
保存好覆盖的字节,后续接触hook的时候再覆盖回来