Joe1sn's Cabinet

【免杀】Windows Inline Hook小结

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();
}

按照直觉image-20240315135117012

这里的call应该会直接来到函数的位置,但是真实情况并非如此

image-20240315135138849

我们会发现一张跳表,根据距离这张表的地址差进行跳转,那段hook只是修改了这一段代码

x86 hook

首先是x86为啥叫32位架构,最突出的就是一个寄存器有32位(bit)大小,那么计算一下

1
(1<<32)/int(1024*1024*1024) = 4

没错,32位的寄存器最多只能存放4GB个数字(比如从10x100000000),
由于要做加减发(有负数),所以能找到的地址位±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) {
//set read write
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
//hook with debug jmp table
offset = HookFunction - this->FunctionAddr - 5;
*(char*)this->FunctionAddr = '\xE9';
*(DWORD*)(this->FunctionAddr + 1) = offset;

//return to the origin
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
//get first 5 bytes
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; //API hook
//*(DWORD*)(this->FunctionAddr + 1) = offset; //

//return to the origin
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;
}
//reset the top 5bytes in header
memcpy((void*)((FuncAddr)HookFunction + func_size), head, 5);
*(char*)(HookFunction + func_size + 5) = '\xE9';
//*(DWORD*)(HookFunction + func_size + 6) = ~(offset + func_size + 9);
*(DWORD*)(HookFunction + func_size + 6) = ~(offset + func_size); //win api hook
#endif
}

x64 hook

上面讲到了x86 hook只能2GB内内存寻址,到了x64寻址空间大大加大,单纯jmpcall已经无法跳转到地址,后来我看到一个很有意思的方法

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";

/*
* push 0x10000
* mov dword [rsp+4], 0x20000
* ret
*/
unsigned char jmpopcode[14] = {
0x68, 0x00, 0x00, 0x00, 0x00, 0xC7, 0x44, 0x24, 0x04, 0x00, 0x00, 0x00,0x00, 0xC3
};

//over write to jmp
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的时候再覆盖回来