内核前置——Hook技术

总问题

地址问题

这个我遇到过几次,首先:

地址大小: 在 32 位系统中,指针的大小是 4 字节,而在 64 位系统中,指针的大小是 8 字节。因此,对于 64 位系统,函数的地址计算、指针运算需要使用 64 位大小的地址。

所以网上的脚本有些是32位脚本,无法运行的话就要考虑这个

Detour函数

Detour 函数一般指的是在程序执行过程中,修改或替换某个函数的执行路径的技术,也叫做 函数钩子(Function Hooking)函数劫持(Function Detouring)。这个过程允许我们在不改变原函数代码的情况下,插入自定义的逻辑或代码。

TrampolineFun函数

该函数不是一个完整的函数,是调用原函数的入口

Hook分类

对于hook,我们不难发现,大致可以分为两种类型,一种是对引用函数的地址进行操作,另一种是对引用函数的地址的内容进行操作

举个例子,下面是一个c语言程序,目的是将原本打印的A换成B

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<stdio.h>
#include<Windows.h>
void PrintChar(char* pch) {
printf("Address=0x%x Char=%c\n", pch, *pch);
}
int main() {
char ch = 'A';
char ch2 = 'B';
char* pChar;
pChar = &ch;
PrintChar(pChar); //Address=0x84cff704 Char=A
//Adress hook
pChar = &ch2;
PrintChar(pChar); //Address=0x84cff724 Char=B
pChar = &ch;
//Inline hook
*pChar = 'B';
PrintChar(pChar); //Address=0x84cff704 Char=B
}

通过三种结果不难看出,第一种方法是通过改变地址,实现的打印改变,第二种不改变地址,但是改变指针指向的值

在这个简单的例子之后,去了解别的Hook方式就不难了。名目繁多的Hook,总结起来其实只有两种:

Address Hook 和** Inline Hook**

感悟

在InlineHook这里,学习的很多,感觉比之前悟了很多

hook原理就是跳跳跳

问题

那么首先第一个我卡住的是HotPatch Hook

这个hook遇到的问题就是跳转的时候,距离太大了,源地址:00007FFE556D61B6 ,跳转地址是000000014001213B,就是一个是APT函数,一个是自己写的hook函数,距离太远了所以跳转实现不了

第二个遇到问题是在InlineHook里面的InlineHook,其中本来应该,小跳到IAT表中地址然后对地址跳转,通过hook后变成小跳到申请的地址中,对地址中的地址挑战,但遇到的问题和上面一样

为什么有问题

对于一个64位程序来说,一个普普通通的程序开始的地方,都在1 40 00 00 00如此的高地址起步,IAT表一般都会在非常非常高的位置如00007FFE556D61B6 ,然而自己申请的地址又在十分低的位置,这样想实现通过本来的小跳转实现hook就很艰难了

解决

对于这些的解决,其实就是找64位程序的跳转方法,找实现远距离大跳的字节码,基本都是得通过修改字节码来实现跳转了(除非这两个很近)

收集到的大跳字节码

1
2
0x48,0xB8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,  //mov rax,xxx
0xFF,0xE0 //jmp rax
1
2
3
0x68,0x00,0x00,0x00,0x00//push 000000
0xc7,0x00,0x00,0x00,0x00//mov dword ptr [rsp+4],00000000
0xc3 //retn
1
0xff,0x25,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00//jmp qword ptr [00000000 00000000]

这个可以跳的很远,但是计算的是偏移

Inline Hook

Inline Hook是指直接修改指令的hook

因为其关键是转移程序的执行流程,所以一般是用jmp、call、retn之类的转移指令

那么根据不同场合会有不同的改变指令方式,如下是五种模式:

1.jmp xxxxxxxx(5字节)

直接跳转

2.push xxxxxxxx(6字节)

retn

压栈返回实现跳转

3.mov eax,xxxxxxxx (7字节)

jmp eax

先将转移地址放入寄存器,在实现跳转(注:这里使用eax寄存器是因为eax通常用于存储返回值,在函数开始时不会影响什么)

4.call Hook(更换指令或输入表)

5.HotPatch Hook

这个要细说,重新开一个hook

InlineHook-movjmp

https://zhuanlan.zhihu.com/p/142621824

原理

原理大致是,在一个API函数开始前,会有一个call函数执行,我们通过改变API函数的构造,来做到hook

MessageBoxA举例,这是API函数,所以在这个函数所在模块会记录这个函数如果被调用的话全过程是什么,这个函数正常执行流程是call MessageBoxA,那么在这个模块中,MessageBoxAadress部分第一项就会使call MessageBoxA,所以我们用过使用API函数所在模块的句柄,获得函数地址,再通过函数地址,使用 WriteProcessMemory函数写入准备好的同字节大小跳转函数,即可完成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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
#include <windows.h>
#include <stdio.h>
#include <iostream>
#include <tchar.h>

// 修改API入口为 mov rax, JmpAddr;jmp rax 是程序能跳转到自己的函数
BYTE __NewCode[14] = { 0x48, 0xB8 }; // mov rax, JmpAddr 和 jmp rax
BYTE __OldCode[14] = { 0 }; // 存储MessageBoxA原始代码

FARPROC __MessageBoxAddress;

int WINAPI MyMessageBoxA(
HWND hWnd, // handle to owner window
LPCTSTR lpText, // text in message box
LPCTSTR lpCaption, // message box title
UINT uType // message box style
);

void InlineHook();

void main()
{
InlineHook();

// 调用MessageBoxA测试一下。
MessageBoxA(NULL, "Hello World", "Title", MB_OK);
}

void InlineHook()
{
HMODULE hModule_User32 = LoadLibrary(L"user32.dll");
__MessageBoxAddress = GetProcAddress(hModule_User32, "MessageBoxA");

// 打印MessageBoxA的地址
printf("__MessageBoxAddress is %p\n", __MessageBoxAddress);
// MyMessageBoxA的地址
printf("MyMessageBoxA Addr is %p\n", MyMessageBoxA);

// 读MessageBoxA函数的前6个字节
HANDLE hProcess = OpenProcess(PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION, FALSE, GetCurrentProcessId());
if (ReadProcessMemory(hProcess, __MessageBoxAddress, __OldCode, 14, NULL) == 0)
{
printf("ReadProcessMemory error\n");
CloseHandle(hProcess);
return;
}

// 打印原始MessageBoxA的代码
printf("__OldCode is ");
for (int i = 0; i < 14; i++) {
printf("%02X ", __OldCode[i]);
}
printf("\n");

DWORD64 JmpAddress = (DWORD64)MyMessageBoxA; // 64 位地址
// 构造新头部代码
__NewCode[0] = 0x48; // 64 位操作的前缀
__NewCode[1] = 0xB8; // mov rax, [JmpAddr]
memcpy(&__NewCode[2], &JmpAddress, 8); // 将 64 位地址写入
__NewCode[10] = 0xFF; // jmp rax
__NewCode[11] = 0xE0; // jmp rax

// 打印新代码
printf("NewBytes are ");
for (int i = 0; i < 14; i++) {
printf("%02X ", __NewCode[i]);
}
printf("\n");

DWORD dwOldProtect = 0;
// 去内存保护
VirtualProtectEx(hProcess, (LPVOID)__MessageBoxAddress, 14, PAGE_EXECUTE_READWRITE, &dwOldProtect);

// 写入跳转,开始Hook
if (!WriteProcessMemory(hProcess, __MessageBoxAddress, __NewCode, 14, NULL)) {
printf("WriteProcessMemory error\n");
CloseHandle(hProcess);
return;
}

// 恢复内存保护
VirtualProtectEx(hProcess, (LPVOID)__MessageBoxAddress, 14, dwOldProtect, &dwOldProtect);

CloseHandle(hProcess);
}

int WINAPI MyMessageBoxA(
HWND hWnd, // handle to owner window
LPCTSTR lpText, // text in message box
LPCTSTR lpCaption, // message box title
UINT uType // message box style
)
{
printf("MessageBoxA 已经被Hook\r\n");

// 恢复原始的API代码
HANDLE hProcess = OpenProcess(PROCESS_VM_WRITE | PROCESS_VM_OPERATION, FALSE, GetCurrentProcessId());
DWORD dwOldProtect = 0;
VirtualProtectEx(hProcess, (LPVOID)__MessageBoxAddress, 14, PAGE_EXECUTE_READWRITE, &dwOldProtect);
WriteProcessMemory(hProcess, (LPVOID)__MessageBoxAddress, (LPVOID)__OldCode, 14, NULL);
VirtualProtectEx(hProcess, (LPVOID)__MessageBoxAddress, 14, dwOldProtect, &dwOldProtect);
// 调用正确的函数
int ret = MessageBoxA(NULL, "Hello", "Title", MB_OK);

// 写入跳转语句,继续Hook
WriteProcessMemory(hProcess, (LPVOID)__MessageBoxAddress, (LPVOID)__NewCode, 14, NULL);

CloseHandle(hProcess);
return ret;
}

超详细解读程序
第一段函数void InlineHook()
初始定义
1
2
3
4
5
6
HMODULE hModule_User32 = LoadLibrary(L"user32.dll");
__MessageBoxAddress = GetProcAddress(hModule_User32, "MessageBoxA");
//打印MessageBoxA的地址
printf("__MessageBoxAddress is %x\n", __MessageBoxAddress);
//MyMessageBoxA的地址
printf("MyMessageBoxA Addr is %x\n", MyMessageBoxA);

先获得user32.dll模块的句柄,再用句柄获得模块中MessageBoxA函数的地址,同时打印MyMessageBox的地址和MessageBoxA的地址

读取前14字节
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 读MessageBoxA函数的前14个字节
HANDLE hProcess = OpenProcess(PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION, FALSE, GetCurrentProcessId());
if (ReadProcessMemory(hProcess, __MessageBoxAddress, __OldCode, 14, NULL) == 0)
{
printf("ReadProcessMemory error\n");
CloseHandle(hProcess);
return;
}

// 打印原始MessageBoxA的代码
printf("__OldCode is ");
for (int i = 0; i < 14; i++) {
printf("%02X ", __OldCode[i]);
}
printf("\n");

这里先读取MessageBoxA函数里的前十四个字节,存储在_OldCode中,在打印前十四字节

解析一下ReadProcessMemory函数: 用于从指定进程的内存空间中读取数据。

1
2
3
4
5
6
7
BOOL ReadProcessMemory(
HANDLE hProcess, // 目标进程的句柄
LPCVOID lpBaseAddress, // 目标内存的起始地址
LPVOID lpBuffer, // 存储读取数据的缓冲区
SIZE_T nSize, // 要读取的字节数
SIZE_T *lpNumberOfBytesRead // 实际读取的字节数
);
Hook14字节
1
2
3
4
5
6
7
8
9
10
11
12
13
DWORD64 JmpAddress = (DWORD64)MyMessageBoxA;  // 64 位地址
// 构造新头部代码
__NewCode[0] = 0x48; // 64 位操作的前缀
__NewCode[1] = 0xB8; // mov rax, [JmpAddr]
memcpy(&__NewCode[2], &JmpAddress, 8); // 将 64 位地址写入
__NewCode[10] = 0xFF; // jmp rax
__NewCode[11] = 0xE0; // jmp rax
// 打印新代码
printf("NewBytes are ");
for (int i = 0; i < 14; i++) {
printf("%02X ", __NewCode[i]);
}
printf("\n");

这些作用就是用十四字节实现,mov eax MyMessageBoxA jmp eax,于是函数就被hook成自己的函数了,创造之后看看字节码对不对

写入hook
1
2
3
4
5
6
7
//DWORD dwOldProtect = 0; //旧保护属性
// 去内存保护
::VirtualProtect(__MessageBoxAddress, 14, PAGE_EXECUTE_READWRITE, &dwOldProtect);
//写入跳转,开始Hook
WriteProcessMemory(INVALID_HANDLE_VALUE, __MessageBoxAddress, __NewCode, 14, NULL);
// 写内存保护
::VirtualProtect(__MessageBoxAddress, 14, dwOldProtect, &dwOldProtect);

这个其实没啥好说的看注释就好,函数猜的话也猜出来了,于是这段函数就结束了

于是程序返回执行MessageBoxA,本来应该call MassageBoxA,变成了mov与jmp,hook成了进入自己的函数

InlineHook–HotPatch

一般的hook需要5字节,lock 什么的,也最多同时操作4字节,如果要修改5字节,严格说来是要使用同步的,以防止其他CPU访问正被修改的指令。

hotpatch的技法则是首先在函数上方,一般会因为对齐,或者编译时使用/hotpatch选项而预留出几字节的空间,首先在这些空间里写入一个5字节的远程跳转,然后再将函数的俩字节mov edi,edi指令换成一个近跳转,跳到上方的长跳转中,2字节的修改。此种技法完全不用考虑同步…

hotpatch 的hook 方式_hotpatch hook-CSDN博客

“热补丁”Hook,多线程下InlineHook解决方法-CSDN博客

源码
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
// HotPatch.cpp : 定义控制台应用程序的入口点。
//

//#include "stdafx.h"
#include <Windows.h>
#include<stdio.h>
typedef BOOL(WINAPI* pfnCreateprocessW)(
LPCTSTR lpApplicationName,
LPTSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
);

BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege);
BOOL HOOKByHotpatch(LPWSTR wzDllName, LPCSTR szFuncName, PROC pfnNewFunc);
BOOL UnhookByHotpatch(LPWSTR wzDllName, LPCSTR szFuncName);
BOOL WINAPI NewCreateProcessW(
LPCTSTR lpApplicationName,
LPTSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
);
int main()
{
SetPrivilege(SE_DEBUG_NAME, TRUE);

//hook
wchar_t moduleName[] = L"kernel32.dll"; // 创建一个非常量的数组
HOOKByHotpatch(moduleName, "CreateProcessW", (PROC)NewCreateProcessW);

Sleep(1000);

STARTUPINFO si = { 0 };
si.cb = sizeof(si);
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_SHOW;
PROCESS_INFORMATION pi;

//创建进程
TCHAR cmdLine[MAXBYTE] = L"notepad.exe";
BOOL bOk = CreateProcess(NULL, cmdLine,
NULL, NULL, FALSE, NULL,
NULL, NULL, &si, &pi);

Sleep(1000);

wchar_t wzDllName[] = L"kernel32.dll"; // 创建一个非常量宽字符数组
UnhookByHotpatch(wzDllName, "CreateProcessW");
return 0;
}


//设置权限
BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege)
{
TOKEN_PRIVILEGES TokenPrivileges;//权限令牌
HANDLE TokenHandle = NULL; //权限令牌句柄
LUID Luid;

if (!OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
&TokenHandle))
{
printf("OpenProcessToken error: %u\n", GetLastError());
return FALSE;
}

if (!LookupPrivilegeValue(NULL, // lookup privilege on local system
lpszPrivilege, // privilege to lookup
&Luid)) // receives LUID of privilege
{
printf("LookupPrivilegeValue error: %u\n", GetLastError());
return FALSE;
}

TokenPrivileges.PrivilegeCount = 1;
TokenPrivileges.Privileges[0].Luid = Luid;
if (bEnablePrivilege)
TokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
else
TokenPrivileges.Privileges[0].Attributes = 0;

// Enable the privilege or disable all privileges.
//调整权限
if (!AdjustTokenPrivileges(TokenHandle,
FALSE,
&TokenPrivileges,
sizeof(TOKEN_PRIVILEGES),
(PTOKEN_PRIVILEGES)NULL,
(PDWORD)NULL))
{
printf("AdjustTokenPrivileges error: %u\n", GetLastError());
return FALSE;
}

if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)
{
printf("The token does not have the specified privilege. \n");
return FALSE;
}

return TRUE;
}

//Hootpatch,将函数首字节改为 short jmp(EB F9)
BOOL HOOKByHotpatch(LPWSTR wzDllName, LPCSTR szFuncName, PROC pfnNewFunc)
{
FARPROC pOrgFuncAddr = NULL;
DWORD dwOldProtect, dwAddress;
BYTE pBuf[5] = { 0xE9, 0, };
BYTE pBuf2[2] = { 0xEB, 0xF9 };
PBYTE pByte;

pOrgFuncAddr = (FARPROC)GetProcAddress(GetModuleHandle(wzDllName), szFuncName);
pByte = (PBYTE)pOrgFuncAddr;
//判断是否被勾
if (pByte[0] == 0xEB)
return FALSE;

//将前五字节代码改为可读可写
VirtualProtect((LPVOID)((DWORD)pOrgFuncAddr - 5), 7, PAGE_EXECUTE_READWRITE, &dwOldProtect);

// 1. NOP (0x90)
//将前五字节改为E8 xxxxxxxx
dwAddress = (DWORD)pfnNewFunc - (DWORD)pOrgFuncAddr;
memcpy(&pBuf[1], &dwAddress, 4);
memcpy((LPVOID)((DWORD)pOrgFuncAddr - 5), pBuf, 5);

// 2. MOV EDI, EDI (0x8BFF)
//将函数前两个字节改为EB F9
memcpy(pOrgFuncAddr, pBuf2, 2);

VirtualProtect((LPVOID)((DWORD)pOrgFuncAddr - 5), 7, dwOldProtect, &dwOldProtect);

return TRUE;
}

BOOL UnhookByHotpatch(LPWSTR wzDllName, LPCSTR szFuncName)
{
FARPROC pHookFunc = NULL;
DWORD dwOldProtect;
PBYTE pByte;
BYTE pBuf[5] = { 0x90, 0x90, 0x90, 0x90, 0x90 };
BYTE pBuf2[2] = { 0x8B, 0xFF };


pHookFunc = (FARPROC)GetProcAddress(GetModuleHandle(wzDllName), szFuncName);
pByte = (PBYTE)pHookFunc;
if (pByte[0] != 0xEB)
return FALSE;

VirtualProtect((LPVOID)pHookFunc, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);

// 1. NOP (0x90)
memcpy((LPVOID)((DWORD)pHookFunc - 5), pBuf, 5);

// 2. MOV EDI, EDI (0x8BFF)
memcpy(pHookFunc, pBuf2, 2);

VirtualProtect((LPVOID)pHookFunc, 5, dwOldProtect, &dwOldProtect);

return TRUE;
}

BOOL WINAPI NewCreateProcessW(
LPCTSTR lpApplicationName,
LPTSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
)
{
MessageBox(NULL, L"Hook", L"", 0);
return TRUE;
}
分析

老规矩,按照主函数调用顺序分析

SetPrivilege
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
BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege)
{
TOKEN_PRIVILEGES TokenPrivileges;//权限令牌
HANDLE TokenHandle = NULL; //权限令牌句柄
LUID Luid;

if (!OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
&TokenHandle))
{
printf("OpenProcessToken error: %u\n", GetLastError());
return FALSE;
}

if (!LookupPrivilegeValue(NULL, // lookup privilege on local system
lpszPrivilege, // privilege to lookup
&Luid)) // receives LUID of privilege
{
printf("LookupPrivilegeValue error: %u\n", GetLastError());
return FALSE;
}

TokenPrivileges.PrivilegeCount = 1;
TokenPrivileges.Privileges[0].Luid = Luid;
if (bEnablePrivilege)
TokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
else
TokenPrivileges.Privileges[0].Attributes = 0;

// Enable the privilege or disable all privileges.
//调整权限
if (!AdjustTokenPrivileges(TokenHandle,
FALSE,
&TokenPrivileges,
sizeof(TOKEN_PRIVILEGES),
(PTOKEN_PRIVILEGES)NULL,
(PDWORD)NULL))
{
printf("AdjustTokenPrivileges error: %u\n", GetLastError());
return FALSE;
}

if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)
{
printf("The token does not have the specified privilege. \n");
return FALSE;
}

return TRUE;
}

设置权限函数

HOOKByHotpatch
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
BOOL HOOKByHotpatch(LPWSTR wzDllName, LPCSTR szFuncName, PROC pfnNewFunc)
{
FARPROC pOrgFuncAddr = NULL;
DWORD dwOldProtect, dwAddress;
BYTE pBuf[5] = { 0xE9, 0, };
BYTE pBuf2[2] = { 0xEB, 0xF9 };
PBYTE pByte;

pOrgFuncAddr = (FARPROC)GetProcAddress(GetModuleHandle(wzDllName), szFuncName);
pByte = (PBYTE)pOrgFuncAddr;
//判断是否被勾
if (pByte[0] == 0xEB)
return FALSE;

//将前五字节代码改为可读可写
VirtualProtect((LPVOID)((DWORD)pOrgFuncAddr - 5), 7, PAGE_EXECUTE_READWRITE, &dwOldProtect);

// 1. NOP (0x90)
//将前五字节改为E8 xxxxxxxx
dwAddress = (DWORD)pfnNewFunc - (DWORD)pOrgFuncAddr;
memcpy(&pBuf[1], &dwAddress, 4);
memcpy((LPVOID)((DWORD)pOrgFuncAddr - 5), pBuf, 5);

// 2. MOV EDI, EDI (0x8BFF)
//将函数前两个字节改为EB F9
memcpy(pOrgFuncAddr, pBuf2, 2);

VirtualProtect((LPVOID)((DWORD)pOrgFuncAddr - 5), 7, dwOldProtect, &dwOldProtect);

return TRUE;
}

这里一直在报错一直搞不定,等开始写项目了回来修补,大致意思看两遍就知道了

InlineHook–call

原理

这里分为两种,我们知道在运行一个函数之前,会call <你用的函数>,所以在这里改动,使其变成

call <我的函数>

如果调用的是IAT函数,将先使用 FF 15 <IAT表>,对IAT表中的函数地址,实现跳转,由此我们可以自己先申请一块地址,再把自己写的函数放在地址上,实现跳转,就相当于和上文一样实现了FF 15 <申请的地址表>,对自己申请的地址中存放的地址,实现跳转,不过这里会出现问题我都会在感悟中提到

如果用的是E8call跳转,那就直接换E8后面的值就好

基于异常处理的Hook

这个比我想象的好玩

原理

向量化异常处理(Vectored Exception Handling,VEH):这是一个扩展的异常处理机制,允许注册异常处理程序,以便在程序发生异常时接管处理。

手动的对你接下来要进行的函数设置断点(0xCC),在捕获异常后用自己的方法修复异常,在修复的时候就可以进项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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
/*-----------------------------------------------------------------------
第13章 Hook技术
《加密与解密(第四版)》
(c) 看雪学院 www.kanxue.com 2000-2018
-----------------------------------------------------------------------*/


// VEHHook.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <windows.h>

BOOL SetBreakPoint(PVOID pFuncAddr);
BOOL ClearBreakPoint(PVOID pFuncAddr);
BOOL InstallVEHHook(PVECTORED_EXCEPTION_HANDLER Handler);
VOID UnInstallVEHHook();

typedef int
(WINAPI *PFN_MessageBox)(
HWND hWnd, // handle of owner window
LPCTSTR lpText, // address of text in message box
LPCTSTR lpCaption, // address of title of message box
UINT uType // style of message box
);

int WINAPI My_MessageBox(
HWND hWnd, // handle of owner window
LPCTSTR lpText, // address of text in message box
LPCTSTR lpCaption, // address of title of message box
UINT uType // style of message box
);

LONG WINAPI VectoredHandler1(struct _EXCEPTION_POINTERS *ExceptionInfo);
LONG WINAPI VectoredHandler2(struct _EXCEPTION_POINTERS *ExceptionInfo);
LONG WINAPI VectoredHandler3(struct _EXCEPTION_POINTERS *ExceptionInfo);
VOID ShowMsgBox(LPCTSTR lpMsg);
ULONG_PTR InitTrampolineFun();

PFN_MessageBox g_OriginalMessageBoxA;
PVOID g_AddrofMessageBoxA = 0 ;
PVOID g_hVector;
BYTE g_OldCode[16]={0};

int main(int argc, char* argv[])
{
HMODULE hUser32 = LoadLibrary("user32.dll");
g_AddrofMessageBoxA = (PVOID)GetProcAddress(hUser32,"MessageBoxA");

printf("Address of MessageBoxA = 0x%p\n",g_AddrofMessageBoxA);
g_OriginalMessageBoxA = (PFN_MessageBox)InitTrampolineFun(); //跳过开头的Hook

printf("Addr of VectoredHandler1 = 0x%p\n",VectoredHandler1);
printf("Addr of VectoredHandler2 = 0x%p\n",VectoredHandler2);
printf("Addr of VectoredHandler3 = 0x%p\n",VectoredHandler3);

//选择安装一个进行测试
InstallVEHHook(VectoredHandler2);

//设置断点
SetBreakPoint(g_AddrofMessageBoxA);

//call
ShowMsgBox("VEH Hook Test.");
//ShowMsgBox("uihae");

printf("All Finished!\n");
ClearBreakPoint(g_AddrofMessageBoxA);
UnInstallVEHHook();


ShowMsgBox("Hook Cleared");
return 0;
}

VOID ShowMsgBox(LPCTSTR lpMsg)
{
MessageBoxA(NULL,lpMsg,"Test",MB_OK);
}

ULONG_PTR InitTrampolineFun()
{
ULONG_PTR uResult = 0 ;
PBYTE pFun = NULL;

#ifdef _WIN64
//x64需要申请shellcode
/*
USER32!MessageBoxA:
00000000`779412b8 4883ec38 sub rsp,38h
00000000`779412bc 4533db xor r11d,r11d
00000000`779412bf 44391d760e0200 cmp dword ptr [USER32!gapfnScSendMessage+0x927c (00000000`7796213c)],r11d
*/
pFun = (PBYTE)VirtualAlloc(NULL,128,MEM_COMMIT,PAGE_EXECUTE_READWRITE);
uResult = (ULONG_PTR)pFun;
printf("uResult:%p\n", uResult);
memset(pFun,0,128);
memcpy(pFun,(PVOID)g_AddrofMessageBoxA,4); //拷贝第一条指令,4字节,推荐使用反汇编引擎来实际计算
pFun += 4 ; //下一条指令构造为jmp [xxxxxx]
printf("pFun:%p\n", pFun);
pFun[0] = 0xFF;
pFun[1] = 0x25;
//这里相当于把原函数后面4个字节后面的内容给保存下来,好跳转。
*(ULONG_PTR*)(pFun + 6) = (ULONG_PTR)g_AddrofMessageBoxA + 4 ; //跳回到原函数加4的地方
#else
//x86,第一条指令是mov edi,edi,直接跳过即可
uResult = (ULONG_PTR)g_AddrofMessageBoxA + 2;
#endif
return uResult;
}


//处理方式,修改参数并返回原函数继续执行
LONG WINAPI
VectoredHandler1(
struct _EXCEPTION_POINTERS *ExceptionInfo
)
{
char *szNewMsg = "[VectoredHandler1] Hacked by pediy.com";
LONG lResult = EXCEPTION_CONTINUE_SEARCH;
PEXCEPTION_RECORD pExceptionRecord = ExceptionInfo->ExceptionRecord;
PCONTEXT pContextRecord = ExceptionInfo->ContextRecord;
ULONG_PTR* uESP = 0;
int ret = 0;
printf("Exception Address = %p\n",pExceptionRecord->ExceptionAddress);
if (pExceptionRecord->ExceptionCode == EXCEPTION_BREAKPOINT
&& pExceptionRecord->ExceptionAddress == g_AddrofMessageBoxA)
{
#ifdef _WIN64
//x64上前四个参数依次为RCX,RDX,R8,R9
//修改第二个参数,即LpMsg
printf("lpText = 0x%p %s\n",pContextRecord->Rdx,(char*)pContextRecord->Rdx);
pContextRecord->Rdx = (ULONG_PTR)szNewMsg;
pContextRecord->Rip = (ULONG_PTR)g_OriginalMessageBoxA ; //跳到Trampoline继续执行
#else
/*
0012FF70 0040105A /CALL 到 MessageBoxA 来自 VEHHook.00401054
0012FF74 00000000 |hOwner = NULL
0012FF78 00407030 |Text = "VEH Hook"
0012FF7C 0040703C |Title = "Test"
0012FF80 00000000 \Style = MB_OK|MB_APPLMODAL
0012FF84 00401225 返回到 VEHHook.<ModuleEntryPoint>+0B4 来自 VEHHook.00401000
*/
printf("ESP = 0x%p\n",pContextRecord->Esp) ;
uESP = (ULONG_PTR*)pContextRecord->Esp ; //取中断时的ESP
uESP[2] = (ULONG_PTR)szNewMsg; //修改栈中的参数
pContextRecord->Eip = (ULONG_PTR)g_OriginalMessageBoxA ; //跳过函数开头
#endif

lResult = EXCEPTION_CONTINUE_EXECUTION ;
}
return lResult;
}

//处理方式:直接调用原函数并替原函数返回
LONG WINAPI
VectoredHandler2(
struct _EXCEPTION_POINTERS *ExceptionInfo
)
{
char *szNewMsg = "[VectoredHandler2] Hacked by pediy.com";
LONG lResult = EXCEPTION_CONTINUE_SEARCH;
PEXCEPTION_RECORD pExceptionRecord = ExceptionInfo->ExceptionRecord;
PCONTEXT pContextRecord = ExceptionInfo->ContextRecord;
ULONG_PTR* uESP = 0;
int ret = 0;
if (pExceptionRecord->ExceptionCode == EXCEPTION_BREAKPOINT
&& pExceptionRecord->ExceptionAddress == g_AddrofMessageBoxA)
{

#ifdef _WIN64
//x64上前四个参数依次为RCX,RDX,R8,R9
printf("RSP = 0x%p\n",pContextRecord->Rsp) ;
uESP = (ULONG_PTR*)pContextRecord->Rsp ;
printf("Return Address = 0x%p\n",uESP[0]);
ret = g_OriginalMessageBoxA((HWND)pContextRecord->Rcx,szNewMsg,(LPCTSTR)pContextRecord->R8,(int)pContextRecord->R9);
printf("ret = %d\n",ret);
//修正RSP
printf("RSP(before) = 0x%p\n", pContextRecord->Rsp);
pContextRecord->Rsp += sizeof(ULONG_PTR);//参数在寄存器中,栈中无参数,仅需跳过返回地址
printf("RSP(after) = 0x%p\n", pContextRecord->Rsp);
//直接返回到调用者处
pContextRecord->Rip = uESP[0] ;//设置EIP为返回地址
#else
/*
0012FF70 0040105A /CALL 到 MessageBoxA 来自 VEHHook.00401054
0012FF74 00000000 |hOwner = NULL
0012FF78 00407030 |Text = "VEH Hook"
0012FF7C 0040703C |Title = "Test"
0012FF80 00000000 \Style = MB_OK|MB_APPLMODAL
0012FF84 00401225 返回到 VEHHook.<ModuleEntryPoint>+0B4 来自 VEHHook.00401000
*/
printf("ESP = 0x%p\n",pContextRecord->Esp) ;
uESP = (ULONG_PTR*)pContextRecord->Esp ;
ret = g_OriginalMessageBoxA((HWND)uESP[1],szNewMsg,(LPCTSTR)uESP[3],(int)uESP[4]);
printf("ret = %d\n",ret);

//直接返回到调用者处
pContextRecord->Eip = uESP[0] ;//设置EIP为返回地址
pContextRecord->Esp += (4 + 1)*sizeof(ULONG_PTR); //4为参数个数,1为返回地址
#endif

lResult = EXCEPTION_CONTINUE_EXECUTION ;
}
return lResult;
}

//处理方式:直接返回,相当于过滤掉
LONG WINAPI
VectoredHandler3(
struct _EXCEPTION_POINTERS *ExceptionInfo
)
{
LONG lResult = EXCEPTION_CONTINUE_SEARCH ;
PEXCEPTION_RECORD pExceptionRecord = ExceptionInfo->ExceptionRecord ;
PCONTEXT pContextRecord = ExceptionInfo->ContextRecord ;
ULONG_PTR* uESP = 0 ;
int ret = 0;
if (pExceptionRecord->ExceptionCode == EXCEPTION_BREAKPOINT
&& pExceptionRecord->ExceptionAddress == g_AddrofMessageBoxA)
{

/*
0012FF70 0040105A /CALL 到 MessageBoxA 来自 VEHHook.00401054
0012FF74 00000000 |hOwner = NULL
0012FF78 00407030 |Text = "VEH Hook"
0012FF7C 0040703C |Title = "Test"
0012FF80 00000000 \Style = MB_OK|MB_APPLMODAL
0012FF84 00401225 返回到 VEHHook.<ModuleEntryPoint>+0B4 来自 VEHHook.00401000
*/


//直接返回到调用者处
#ifdef _WIN64
printf("RSP = 0x%p\n",pContextRecord->Rsp) ;
uESP = (ULONG_PTR*)pContextRecord->Rsp ;
pContextRecord->Rip = uESP[0] ;//设置EIP为返回地址
pContextRecord->Rsp += sizeof(ULONG_PTR); //将压入栈内的参数和返回地址清理掉,4为参数个数,1为返回地址
#else
printf("ESP = 0x%X\n",pContextRecord->Esp) ;
uESP = (ULONG_PTR*)pContextRecord->Esp ;
pContextRecord->Eip = uESP[0] ;//设置EIP为返回地址
pContextRecord->Esp += (4 + 1)*sizeof(ULONG_PTR); //将压入栈内的参数和返回地址清理掉,4为参数个数,1为返回地址
#endif

lResult = EXCEPTION_CONTINUE_EXECUTION ;
}
return lResult;
}

BOOL InstallVEHHook(PVECTORED_EXCEPTION_HANDLER Handler)
{
printf("Current Handler Address = 0x%p\n",Handler);
g_hVector = AddVectoredExceptionHandler(1,Handler);
return g_hVector != NULL ;
}

VOID UnInstallVEHHook()
{
RemoveVectoredExceptionHandler(g_hVector);
}

/*
0:000> u user32!messageboxA
USER32!MessageBoxA:
77d507ea 8bff mov edi,edi
77d507ec 55 push ebp
77d507ed 8bec mov ebp,esp
*/
BOOL SetBreakPoint(PVOID pFuncAddr)
{
DWORD dwCnt = 0 ;
BYTE *pTarget = (BYTE*)pFuncAddr;


g_OldCode[0] = *pTarget;
printf("Original Fun Head Code = 0x%02X\n",g_OldCode[0]);
//修改内存页的属性
DWORD dwOLD;
MEMORY_BASIC_INFORMATION mbi;
VirtualQuery(pTarget,&mbi,sizeof(mbi));
VirtualProtect(mbi.BaseAddress,mbi.RegionSize,PAGE_EXECUTE_READWRITE,&dwOLD);

//写入int3
*pTarget = 0xCC ;

//恢复内存页的属性
VirtualProtect(mbi.BaseAddress,mbi.RegionSize,dwOLD,0);
return TRUE;
}

BOOL ClearBreakPoint(PVOID pFuncAddr)
{
BYTE *pTarget = (BYTE*)pFuncAddr;
//修改内存页的属性
DWORD dwOLD;
MEMORY_BASIC_INFORMATION mbi;
VirtualQuery(pTarget,&mbi,sizeof(mbi));
VirtualProtect(mbi.BaseAddress,mbi.RegionSize,PAGE_EXECUTE_READWRITE,&dwOLD);

*pTarget = g_OldCode[0] ;

//恢复内存页的属性
VirtualProtect(mbi.BaseAddress,mbi.RegionSize,dwOLD,0);
return TRUE;
}

int WINAPI My_MessageBox(
HWND hWnd, // handle of owner window
LPCTSTR lpText, // address of text in message box
LPCTSTR lpCaption, // address of title of message box
UINT uType // style of message box
)
{
char newMsg[400];
char newCation[]="标题被我改了!";
int result;
if (lpText)
{
ZeroMemory(newMsg,400);
lstrcpy(newMsg,lpText);
lstrcat(newMsg,"\n\tMessage Box hacked by pediy.com");
}
printf("有人调用MessageBox...\n");
result = g_OriginalMessageBoxA(hWnd,newMsg,newCation,uType);
return result;

}

解析

实现流程,先获得先获得地址,获得之后安装一个解决方案,然后设置接下来进行函数的断点,然后调用即可

main函数
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
int main(int argc, char* argv[])
{
HMODULE hUser32 = LoadLibrary("user32.dll");
g_AddrofMessageBoxA = (PVOID)GetProcAddress(hUser32,"MessageBoxA");
printf("Address of MessageBoxA = 0x%p\n",g_AddrofMessageBoxA);
g_OriginalMessageBoxA = (PFN_MessageBox)InitTrampolineFun(); //跳过开头的Hook

printf("Addr of VectoredHandler1 = 0x%p\n",VectoredHandler1);
printf("Addr of VectoredHandler2 = 0x%p\n",VectoredHandler2);
printf("Addr of VectoredHandler3 = 0x%p\n",VectoredHandler3);

//选择安装一个进行测试
InstallVEHHook(VectoredHandler2);

//设置断点
SetBreakPoint(g_AddrofMessageBoxA);

//call
ShowMsgBox("VEH Hook Test.");

printf("All Finished!\n");
ClearBreakPoint(g_AddrofMessageBoxA);
UnInstallVEHHook();


ShowMsgBox("Hook Cleared");
return 0;
}

在printf(“All Finished!\n”)之前是对异常的准备工作,后面的是清理工作,这里需要重点研究的是,三种异常处理方法函数和安装函数

三种异常函数
共同部分
1
2
3
4
5
6
7
8
LONG lResult = EXCEPTION_CONTINUE_SEARCH;
PEXCEPTION_RECORD pExceptionRecord = ExceptionInfo->ExceptionRecord;
PCONTEXT pContextRecord = ExceptionInfo->ContextRecord;
ULONG_PTR* uESP = 0;
int ret = 0;
if (pExceptionRecord->ExceptionCode == EXCEPTION_BREAKPOINT
&& pExceptionRecord->ExceptionAddress == g_AddrofMessageBoxA)
{

EXCEPTION_RECORD 结构体

EXCEPTION_RECORD 结构体包含了异常的基本信息,主要字段有:

  • ExceptionCode:异常的代码,表示异常类型,例如 EXCEPTION_ACCESS_VIOLATIONEXCEPTION_BREAKPOINT 等。
  • ExceptionAddress:异常发生时的地址(通常是程序崩溃的指令地址)。
  • ExceptionFlags:异常的标志位,用来表示异常的特殊类型或处理方式。
  • NumberParameters:异常的参数数量。
  • ExceptionInformation[]:包含异常相关的附加信息,具体信息取决于异常类型。

CONTEXT 结构体

CONTEXT 结构体保存了线程的寄存器状态。对于 x86 和 x64 平台,结构体的成员有所不同。

  • x64 系统中,CONTEXT 结构体包含以下寄存器:
    • Rax, Rbx, Rcx, Rdx 等通用寄存器。
    • Rip:指令指针,表示当前执行的指令地址。
    • Rsp:堆栈指针,指向栈的当前位置。
    • Rbp:基址指针。
    • Rdi, Rsi 等其他寄存器

先设置一个默认的返回值,没有完全处理异常

初始化了两个指针,一个指向异常的详细信息,一个指向保存当前CPU寄存器状况的环境

  • **EXCEPTION_BREAKPOINT**:这是一个常量,值为 0x80000003,表示这是一个断点异常。通常是由调试器或某些程序内的显式 int 3 指令(触发断点)引发的。断点异常一般在调试程序时用来暂停程序的执行。

这里检测断点是不是自己设置的0xCC,并且检测是不是在MessageBoxA里的

VectoredHandler1
1
2
3
4
5
//x64上前四个参数依次为RCX,RDX,R8,R9
//修改第二个参数,即LpMsg
printf("lpText = 0x%p %s\n",pContextRecord->Rdx,(char*)pContextRecord->Rdx);
pContextRecord->Rdx = (ULONG_PTR)szNewMsg;
pContextRecord->Rip = (ULONG_PTR)g_OriginalMessageBoxA ; //跳到Trampoline继续执行

这个比较好理解,把函数调用进去的寄存器改成需要的值,然后将rip回正

VectoredHandler2
1
2
3
4
5
6
7
8
9
10
//x64上前四个参数依次为RCX,RDX,R8,R9
printf("RSP = 0x%p\n",pContextRecord->Rsp) ;
uESP = (ULONG_PTR*)pContextRecord->Rsp ;
printf("Return Address = 0x%p\n",uESP[0]);
ret = g_OriginalMessageBoxA((HWND)pContextRecord->Rcx,szNewMsg,(LPCTSTR)pContextRecord->R8,(int)pContextRecord->R9);
printf("ret = %d\n",ret);
//修正RSP
printf("RSP(before) = 0x%p\n", pContextRecord->Rsp);//RSP(before) = 0x000000000014FD98
pContextRecord->Rsp += sizeof(ULONG_PTR);//参数在寄存器中,栈中无参数,仅需跳过返回地址
printf("RSP(after) = 0x%p\n", pContextRecord->Rsp);//RSP(after) = 0x000000000014FDA0

直接先调用一个你需要的函数,传参在保存上下文中有,然后传入需要不同的地方

因为这里是刚进入函数的时候报错的,所以栈中只保存了返回地址,所以uESP中保存的是返回地址,后面调用完函数之后,将Rip回正,Rsp跳过返回地址

VectoredHandler3
1
2
3
4
printf("RSP = 0x%p\n",pContextRecord->Rsp) ;
uESP = (ULONG_PTR*)pContextRecord->Rsp ;
pContextRecord->Rip = uESP[0] ;//设置EIP为返回地址
pContextRecord->Rsp += sizeof(ULONG_PTR); //将压入栈内的参数和返回地址清理掉,4为参数个数,1为返回地址

这里直接将保存环境中的Rip改回返回值,栈跳过返回值,然后返回

InstallVEHHook函数
1
2
3
4
5
6
BOOL InstallVEHHook(PVECTORED_EXCEPTION_HANDLER Handler)
{
printf("Current Handler Address = 0x%p\n",Handler);
g_hVector = AddVectoredExceptionHandler(1,Handler);
return g_hVector != NULL ;
}

这里很好分析,首先确定你使用的是哪个处理方案,然后通过AddVectoredExceptionHandler函数来将解决方案加入到VEH中,于是就hook了

Address Hook(待完善)(偏内核)

前言

通过改变函数的地址(函数在各类表或结构中,或者某个指定的地址处),但他们共同点就是调用函数了,就会成为eip,因此把本来的地址换成我们自己的就实现hook了

这里先看一眼这个文章预备一下前置知识

《逆向工程核心原理》第13章——PE文件格式(2):IAT与EAT_od查看iat-CSDN博客

各类表中的地址

PE中的IAT

导入地址表(Import Adress Table)。简而言之IAT是一种表格,用来记录程序正在使用哪些库中的哪些函数。

PE中的EAT

EAT是一种核心机制,使不同的应用程序可以调用库文件中提供的函数,只有通过EAT才能准确求得从相应库中到处函数的起始地址。

user32.dll的回调函数表

存储系统回调函数的表

IDT

SSDT和Shadow SSDT

C++类的虚函数表–这里的虚函数hook需要注意

注意的地方是,子类函数中重载函数的地址会被替换成虚函数表中对应的地址(其继承函数)

COM借口的功能函数表

处理历程地址

书上所说,这部分内容常用于内核中

这里补习一下例程的概念

什么是例程?_例程是什么意思-CSDN博客

例程≈ 函数?或者说例程>=函数?

DRIVER_OBJECT 的 MajorFunction 及FasIo 派遣历程地址

在这本书的这里,我还没有学习他说的例子,并且有很多生名词,我先把不知道的自己简单搜索的标注在这里

什么是 ntfs.sys

该过程的Ntfs.sys也被称为NT文件系统驱动程序,它是由微软公司所拥有。该的Ntfs.sys过程用于创建和存储
与使用NTFS的消防处的应用程序。在Windows操作系统检验机构(因为只有合法的用户才能运行此),其次是I
/ O管理,将所述文件句柄到一个文件对象的指标。这个过程之后,得到的NTFS文件在磁盘使用的文件对象指标。 NTFS的收集与使用文件对象指针的文件属性的渣打银行(流控制块)。流控制块是由单一的属性文件表示,并包含有关如何获取文件点的国有商业银行的数据建设FCB(文件控制块)指定的属性信息。该FCB具有指定MFT(主文件表)的文件记录的指标

书中的例子也没太看懂,以后懂了回来解释补充,总之看到了,FSD Hook 是修改的FileSystem Driver

StartIo等特殊例程的地址

这个例程主要用于IRP的串行处理,在nt!IoStartPacket中被调用

OBJECT_TYPE 中 _OBJECT_TYPE_INITIALIZER包含的各种处理过程

看不懂思密达,以后再说吧

特殊寄存器中的地址

好像还是内核

特殊的函数指针

前面说的还正常后面又是内核

好像是结束了,那么综上所看,addresshook多用于内核,以后回来看

IATHook

IAT

IAT:导入地址表(Import Adress Table)。简而言之IAT是一种表格,用来记录程序正在使用哪些库中的哪些函数

原理

每个调用的 API 函数地址都保存在 IAT 表中。 API 函数调用时,每个输入节( IMPORT SECTION )所指向的 IAT结构如下图所示。

程序中每个调用 API 函数的 CALL 指令所使用的地址都是相应函数登记在 IAT 表的地址。所以为了截获 API 函数,我们将 IAT 表中的地址换成用户自己的 API PROXY 函数地址,这样每个 API 调用都是先调用用户自己的 API PROXY 函数。在这个函数中我们可以完成函数名称的记录、参数的记录、调用原来的过程,并在返回时记录结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main(int argc, char *argv[ ])
{
BOOL bIsWow64 = IsWow64();
printf("IsWow64 = %d\n",bIsWow64);
ShowMsgBox("Before IAT Hook");
IAT_InstallHook();
ShowMsgBox("After IAT Hook");
IAT_UnInstallHook();
ShowMsgBox("After IAT Hook UnHooked");
return 0;
}
VOID ShowMsgBox(char *szMsg)
{
MessageBoxA(NULL,szMsg,"Test",MB_OK);
}

如上操作,如果按照正常输出的话,只会有三个框,但是经过IAT_InstallHook函数之后,box表就会改变,使用了修改IAT的方法拦截了API的调用,所以称为IAT 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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
/////////////////////////////////////////////////////////////////
// 第13章 Hook技术 《加密与解密(第四版)》 //
// //
// Author: achillis(黑月教主) //
// Blog : http://www.cnblogs.com/achillis/ //
// QQ : 344132161 //
// Email : achillis@126.com //
// 转载请保留作者信息 //
// (c) 看雪学院 www.kanxue.com 2000-2018 //
/////////////////////////////////////////////////////////////////
//程序功能:为本进程的exe模块安装IAT Hook,目标函数是MessageBoxA

#include <windows.h>
#include <stdio.h>
#include <imagehlp.h>
#pragma comment(lib,"imagehlp.lib")


//以MessageBoxA的原型定义一个函数指针类型
typedef int
(WINAPI *PFN_MessageBoxA)(
HWND hWnd, // handle of owner window
LPCTSTR lpText, // address of text in message box
LPCTSTR lpCaption, // address of title of message box
UINT uType // style of message box
);

//以MessageBoxA的原型定义一个函数来替代原始的MessageBoxA
int WINAPI My_MessageBoxA(
HWND hWnd, // handle of owner window
LPCTSTR lpText, // address of text in message box
LPCTSTR lpCaption, // address of title of message box
UINT uType // style of message box
);

//存在以下关系
//*(*pThunkPointer) == *pOriginalFuncAddr ;
BOOL InstallModuleIATHook(
HMODULE hModToHook,
char *szModuleName,
char *szFuncName,
PVOID ProxyFunc,
PULONG_PTR *pThunkPointer,
ULONG_PTR *pOriginalFuncAddr
);

VOID ShowMsgBox(char *szMsg);
BOOL IAT_InstallHook();
VOID IAT_UnInstallHook();
BOOL IsWow64();
//保存原始MessageBoxA的地址
PFN_MessageBoxA OldMessageBox=NULL;
//指向IAT中pThunk的地址
PULONG_PTR g_PointerToIATThunk = NULL;

int main(int argc, char *argv[ ])
{
BOOL bIsWow64 = IsWow64();
printf("IsWow64 = %d\n",bIsWow64);
ShowMsgBox("Before IAT Hook");
IAT_InstallHook();
ShowMsgBox("After IAT Hook");
IAT_UnInstallHook();
ShowMsgBox("After IAT Hook UnHooked");
return 0;
}

//之所以把这个调用单独放在一个函数中,是因为Release模式下对调用进行了优化,第二次调用时直接采用了寄存器寻址而不是导入表
//因此,单独放在一个函数中可以避免这个情况。

VOID ShowMsgBox(char *szMsg)
{
MessageBoxA(NULL,szMsg,"Test",MB_OK);
}


int WINAPI My_MessageBoxA(
HWND hWnd, // handle of owner window
LPCTSTR lpText, // address of text in message box
LPCTSTR lpCaption, // address of title of message box
UINT uType // style of message box
)
{
//在这里,你可以对原始参数进行任意操作
int ret;
char newText[1024]={0};
char newCaption[256]="pediy.com";
printf("有人调用MessageBox!\n");
//在调用原函数之前,可以对IN(输入类)参数进行干涉
lstrcpy(newText,lpText);//为防止原函数提供的缓冲区不够,这里复制到我们自己的一个缓冲区中再进行操作
lstrcat(newText,"\n\tMessageBox Hacked by pediy.com!");//篡改消息框内容
uType|=MB_ICONERROR;//增加一个错误图标
ret = OldMessageBox(hWnd,newText,newCaption,uType);//调用原MessageBox,并保存返回值
//调用原函数之后,可以继续对OUT(输出类)参数进行干涉,比如网络函数的recv,可以干涉返回的内容
return ret;//这里你还可以干涉原始函数的返回值

}

BOOL IAT_InstallHook()
{
BOOL bResult = FALSE ;
HMODULE hCurExe = GetModuleHandle(NULL);
PULONG_PTR pt ;
ULONG_PTR OrginalAddr;
bResult = InstallModuleIATHook(hCurExe,"user32.dll","MessageBoxA",(PVOID)My_MessageBoxA,&pt,&OrginalAddr);
if (bResult)
{
printf("[*]Hook安装完毕! pThunk=0x%p OriginalAddr = 0x%p\n",pt,OrginalAddr);
g_PointerToIATThunk = pt ;
OldMessageBox = (PFN_MessageBoxA)OrginalAddr ;
}
return bResult;

}

VOID IAT_UnInstallHook()
{

DWORD dwOLD;
MEMORY_BASIC_INFORMATION mbi;
if (g_PointerToIATThunk)
{
//查询并修改内存页的属性
VirtualQuery((LPCVOID)g_PointerToIATThunk,&mbi,sizeof(mbi));
VirtualProtect(mbi.BaseAddress,mbi.RegionSize,PAGE_EXECUTE_READWRITE,&dwOLD);
//将原始的MessageBoxA地址填入IAT中
*g_PointerToIATThunk = (ULONG)OldMessageBox;
//恢复内存页的属性
VirtualProtect(mbi.BaseAddress,mbi.RegionSize,dwOLD,0);
}

}

//************************************
// FullName: InstallModuleIATHook
// Description: 为指定模块安装IAT Hook
// Access: public
// Returns: BOOL
// Parameter: HMODULE hModToHook , 待Hook的模块基址
// Parameter: char * szModuleName , 目标函数所在模块的名字
// Parameter: char * szFuncName , 目标函数的名字
// Parameter: PVOID DetourFunc , Detour函数地址
// Parameter: PULONG * pThunkPointer , 用以接收指向修改的位置的指针
// Parameter: ULONG * pOriginalFuncAddr , 用以接收原始函数地址
//************************************
BOOL InstallModuleIATHook(
HMODULE hModToHook,// IN
char *szModuleName,// IN
char *szFuncName,// IN
PVOID DetourFunc,// IN
PULONG_PTR *pThunkPointer,//OUT
ULONG_PTR *pOriginalFuncAddr//OUT
)
{
PIMAGE_IMPORT_DESCRIPTOR pImportDescriptor;
PIMAGE_THUNK_DATA pThunkData;
ULONG ulSize;
HMODULE hModule=0;
ULONG_PTR TargetFunAddr;
PULONG_PTR lpAddr;
char *szModName;
BOOL result = FALSE ;
BOOL bRetn = FALSE;

hModule = LoadLibrary(szModuleName);
TargetFunAddr = (ULONG_PTR)GetProcAddress(hModule,szFuncName);
printf("[*]Address of %s:0x%p\n",szFuncName,TargetFunAddr);
printf("[*]Module To Hook at Base:0x%p\n",hModToHook);
pImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToData(hModToHook, TRUE,IMAGE_DIRECTORY_ENTRY_IMPORT, &ulSize);;
printf("[*]Find ImportTable,Address:0x%p\n",pImportDescriptor);
while (pImportDescriptor->FirstThunk)
{
szModName = (char*)((PBYTE)hModToHook+pImportDescriptor->Name) ;
printf("[*]Cur Module Name:%s\n",szModName);
if (stricmp(szModName,szModuleName) != 0)
{
printf("[*]Module Name does not match, search next...\n");
pImportDescriptor++;
continue;
}
//程序的导入表处理完毕后OriginalFirstThunk可能是无效的,不能再根据名称来查找,而是遍历FirstThunk直接根据地址判断
pThunkData = (PIMAGE_THUNK_DATA)((BYTE *)hModToHook + pImportDescriptor->FirstThunk);
while(pThunkData->u1.Function)
{
lpAddr = (ULONG_PTR*)pThunkData;
//找到了地址
if((*lpAddr) == TargetFunAddr)
{
printf("[*]Find target address!\n");
//通常情况下导入表所在内存页都是只读的,因此需要先修改内存页的属性为可写
DWORD dwOldProtect;
MEMORY_BASIC_INFORMATION mbi;
VirtualQuery(lpAddr,&mbi,sizeof(mbi));
bRetn = VirtualProtect(mbi.BaseAddress,mbi.RegionSize,PAGE_EXECUTE_READWRITE,&dwOldProtect);
if (bRetn)
{
//内存页属性修改成功,继续下一步操作,先保存原始数据
if (pThunkPointer != NULL)
{
*pThunkPointer = lpAddr ;
}
if (pOriginalFuncAddr != NULL)
{
*pOriginalFuncAddr = *lpAddr ;
}
//修改地址
*lpAddr = (ULONG_PTR)DetourFunc;
result = TRUE ;
//恢复内存页的属性
VirtualProtect(mbi.BaseAddress,mbi.RegionSize,dwOldProtect,0);
printf("[*]Hook ok.\n");
}

break;
}
//---------
pThunkData++;
}
pImportDescriptor++;
}

FreeLibrary(hModule);
return result;
}
typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL);

LPFN_ISWOW64PROCESS fnIsWow64Process;

BOOL IsWow64()
{
BOOL bIsWow64 = FALSE;

fnIsWow64Process = (LPFN_ISWOW64PROCESS)GetProcAddress(
GetModuleHandle(TEXT("kernel32")),"IsWow64Process");

if (NULL != fnIsWow64Process)
{
if (!fnIsWow64Process(GetCurrentProcess(),&bIsWow64))
{
// handle error
}
}
return bIsWow64;
}


解析

这里只解析这个关键函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
BOOL IAT_InstallHook()
{
BOOL bResult = FALSE ;
HMODULE hCurExe = GetModuleHandle(NULL);
PULONG_PTR pt ;
ULONG_PTR OrginalAddr;
bResult = InstallModuleIATHook(hCurExe,"user32.dll","MessageBoxA",(PVOID)My_MessageBoxA,&pt,&OrginalAddr);
if (bResult)
{
printf("[*]Hook安装完毕! pThunk=0x%p OriginalAddr = 0x%p\n",pt,OrginalAddr);
g_PointerToIATThunk = pt ;
OldMessageBox = (PFN_MessageBoxA)OrginalAddr ;
}
return bResult;

}

第一段

BOOL bResult = FALSE ;

HMODULE hCurExe = GetModuleHandle(NULL);

定义两个初始值,一个是返回是否成功,一个是获取当前线程的句柄

其中函数解析GetMouduleHandle

是一个win32API函数,用于获取句柄,当传入参数为NULL的时候代表获取当前进程中主模块的句柄

1
2
3
4
5
6
7
8
9
10
PULONG_PTR pt;
ULONG_PTR OrginalAddr;
bResult = InstallModuleIATHook(
hCurExe, // 模块句柄
"user32.dll", // 被钩子的模块
"MessageBoxA", // 目标函数名称
(PVOID)My_MessageBoxA,// 替换的新函数
&pt, // 原函数地址指针
&OrginalAddr // 存储目标函数的原始地址
);
第二段

定义了pt(原函数地址),OrginalAddr(存储目标函数地址)

其中的函数解析InstallModuleIATHook,函数定义如下

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
BOOL InstallModuleIATHook(
HMODULE hModToHook, // [IN] 要修改的目标模块句柄。
char* szModuleName, // [IN] 被钩取的目标模块名称(例如 "user32.dll")。
char* szFuncName, // [IN] 要拦截的目标函数名称(例如 "MessageBoxA")。
PVOID DetourFunc, // [IN] 替换函数地址(钩子函数地址)。
PULONG_PTR* pThunkPointer,// [OUT] 保存导入表中目标函数地址的指针。
ULONG_PTR* pOriginalFuncAddr// [OUT] 保存目标函数的原始地址。
)
{
// 定义所需的变量。
PIMAGE_IMPORT_DESCRIPTOR pImportDescriptor; // 导入表描述符,用于定位导入表。
PIMAGE_THUNK_DATA pThunkData; // 用于遍历导入表的函数指针。
ULONG ulSize; // 存储导入表大小。
HMODULE hModule = 0; // 存储被钩取模块的句柄。
ULONG_PTR TargetFunAddr; // 存储目标函数的真实地址。
PULONG_PTR lpAddr; // 指向导入表中的目标地址。
char* szModName; // 保存当前模块的名称。
BOOL result = FALSE; // 返回值,表示是否钩子成功。
BOOL bRetn = FALSE; // 临时变量,用于存储内存权限修改的结果。

// 加载目标模块并获取目标函数的地址。
hModule = LoadLibrary(szModuleName); // 加载被钩取的模块(例如 "user32.dll")。
TargetFunAddr = (ULONG_PTR)GetProcAddress(hModule, szFuncName); // 获取目标函数地址。
printf("[*]Address of %s:0x%p\n", szFuncName, TargetFunAddr);
printf("[*]Module To Hook at Base:0x%p\n", hModToHook);

// 查找目标模块的导入表地址。
pImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToData(
hModToHook,
TRUE,
IMAGE_DIRECTORY_ENTRY_IMPORT,
&ulSize); // 获取导入表的起始地址及大小。
printf("[*]Find ImportTable,Address:0x%p\n", pImportDescriptor);

// 遍历导入表中的每个模块。
while (pImportDescriptor->FirstThunk)
{
// 获取当前模块的名称。
szModName = (char*)((PBYTE)hModToHook + pImportDescriptor->Name);
printf("[*]Cur Module Name:%s\n", szModName);

// 如果模块名称与目标模块不匹配,继续查找下一个。
if (stricmp(szModName, szModuleName) != 0)
{
printf("[*]Module Name does not match, search next...\n");
pImportDescriptor++;
continue;
}

// 开始遍历导入表中的函数地址。
pThunkData = (PIMAGE_THUNK_DATA)((BYTE*)hModToHook + pImportDescriptor->FirstThunk);

while (pThunkData->u1.Function)
{
lpAddr = (ULONG_PTR*)pThunkData; // 获取当前函数的地址指针。

// 如果找到目标函数地址。
if ((*lpAddr) == TargetFunAddr)
{
printf("[*]Find target address!\n");

// 获取当前地址所在内存页信息并尝试修改权限。
DWORD dwOldProtect; // 保存原内存保护属性。
MEMORY_BASIC_INFORMATION mbi;
VirtualQuery(lpAddr, &mbi, sizeof(mbi)); // 查询内存页属性。
bRetn = VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &dwOldProtect);

if (bRetn)
{
// 修改内存页属性成功。

// 保存导入表中目标函数地址指针和原始函数地址。
if (pThunkPointer != NULL)
{
*pThunkPointer = lpAddr; // 保存导入表中的地址。
}
if (pOriginalFuncAddr != NULL)
{
*pOriginalFuncAddr = *lpAddr; // 保存目标函数的原始地址。
}

// 替换导入表中的目标函数地址为自定义函数地址。
*lpAddr = (ULONG_PTR)DetourFunc;

result = TRUE; // 钩子安装成功。

// 恢复内存页的原始权限。
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, dwOldProtect, 0);
printf("[*]Hook ok.\n");
}
break; // 跳出循环。
}

pThunkData++; // 检查下一个函数地址。
}

pImportDescriptor++; // 检查下一个模块的导入表。
}

// 释放加载的模块句柄。
FreeLibrary(hModule);

return result; // 返回钩子安装结果。
}

简单来说就是传入要修改的模块句柄和名称以及具体的函数,然后在传入要替换的函数的地址,最后保存源函数地址的指针和目标函数的地址

第三段
1
2
3
4
5
6
7
8
if (bResult)
{
printf("[*]Hook安装完毕! pThunk=0x%p OriginalAddr = 0x%p\n",pt,OrginalAddr);
g_PointerToIATThunk = pt ;
OldMessageBox = (PFN_MessageBoxA)OrginalAddr ;
}
return bResult;
}

这里根据上一个函数的返回值也就是TRUE或FULL来判断是否成功,并打印结果,最后保存一下原函数地址与指针就结束了

虚函数hook

虚函数表

虚函数表详解-CSDN博客

原理

十分符合Address 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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
/*-----------------------------------------------------------------------
第13章 Hook技术
《加密与解密(第四版)》
(c) 看雪学院 www.kanxue.com 2000-2018
-----------------------------------------------------------------------*/


// CClassHook.cpp : Defines the entry point for the console application.
// // 包含 AfxWinInit 所在的头文件
#include "stdafx.h"
#include "CClassHook.h"
#include<windows.h>
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/*
要解决两个问题
1.如何获取类成员函数的地址
2.如何使用普通函数替换类成员函数
*/
/////////////////////////////////////////////////////////////////////////////
// The one and only application object

CWinApp theApp;

using namespace std;


class base
{
public:
virtual int Add(int a,int b);
virtual void g(){cout<<"base::g"<<endl;};
virtual void h(){cout<<"base::h"<<endl;};
void novirtual(){cout<<"base::not virtual"<<endl;};
};

int base::Add(int a,int b)
{
printf("base::Add\n");
return a + b ;
}

class DetourClass
{
public:
virtual int DetourFun(int a,int b);
};

class TrampolineClass
{
public:
virtual int TrampolineFun(int a,int b){printf("TrampolineClass\n");return 0 ;};//原型与被Hook函数相同
};

DetourClass Detour;
TrampolineClass Trampoline;

int DetourClass::DetourFun(int a,int b)
{
//TrampolineFun(); //由于这里的类实际上是base,所以调用TrampolineFun即调用第二个虚函数,相当于调用pbase->g()
TrampolineClass *pTrampoline = new TrampolineClass;
int result = pTrampoline->TrampolineFun(a,b);
printf("DetourClass:: OriginalFun returned %d\n",result);
result += 10 ;
delete pTrampoline;
return result;
}



typedef void (*pfun)();
LPVOID GetClassVirtualFnAddress(LPVOID pthis,int Index);
VOID HookClassMemberByAnotherClassMember();


int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0;

// initialize MFC and print and error on failure
if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
{
// TODO: change error code to suit your needs
cerr << _T("Fatal Error: MFC initialization failed") << endl;
nRetCode = 1;
}
else
{
// TODO: code your application's behavior here.
HookClassMemberByAnotherClassMember();
getchar();
}

return nRetCode;
}


void HookClassMemberByAnotherClassMember()
{
base b;
base *pbase=&b;

DWORD dwOLD;
MEMORY_BASIC_INFORMATION mbi;

printf("pbase = 0x%X\n",pbase);

ULONG_PTR *vfTableToHook = (ULONG_PTR*)*(ULONG_PTR*)pbase;
printf("vfTable = 0x%x\n",vfTableToHook);

ULONG_PTR *vfTableTrampoline = (ULONG_PTR*)*(ULONG_PTR*)&Trampoline;

//先将原函数的地址保存到当前类的表中,作为调用原函数的入口
VirtualQuery(vfTableTrampoline,&mbi,sizeof(mbi));
VirtualProtect(mbi.BaseAddress,mbi.RegionSize,PAGE_EXECUTE_READWRITE,&dwOLD);
//保存原始数据
//原函数位于第几个这里就是第几个,必须保证位置一样
vfTableTrampoline[0] = (ULONG_PTR)GetClassVirtualFnAddress(pbase,0);
printf("Base::Add() %p\n",vfTableTrampoline[0]);
TrampolineClass *p = &Trampoline;
//恢复内存页的属性
VirtualProtect(mbi.BaseAddress,mbi.RegionSize,dwOLD,0);
//修改内存页的属性

VirtualQuery(vfTableToHook,&mbi,sizeof(mbi));
VirtualProtect(mbi.BaseAddress,mbi.RegionSize,PAGE_EXECUTE_READWRITE,&dwOLD);
//保存原始数据
vfTableToHook[0] = (ULONG_PTR)GetClassVirtualFnAddress(&Detour,0);
printf("Detour::Add() %p\n",vfTableToHook[0]);
//恢复内存页的属性
VirtualProtect(mbi.BaseAddress,mbi.RegionSize,dwOLD,0);
int result = pbase->Add(1,2); //调用第3个虚函数,实际调用的是HookClass::DetourFun()
printf("result = %d \nafter call member fun.\n",result);
}

//获得类虚拟成员函数指针
LPVOID GetClassVirtualFnAddress(LPVOID pthis,int Index)
{
ULONG_PTR *vfTable = (ULONG_PTR*)*(ULONG_PTR*)pthis;
return (LPVOID)vfTable[Index];
}
函数分析

这里主要分析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
void HookClassMemberByAnotherClassMember() {
base b;
base *pbase = &b;

DWORD dwOLD;
MEMORY_BASIC_INFORMATION mbi;

printf("pbase = 0x%X\n", pbase);

ULONG_PTR *vfTableToHook = (ULONG_PTR*)*(ULONG_PTR*)pbase;
printf("vfTable = 0x%x\n", vfTableToHook);

ULONG_PTR *vfTableTrampoline = (ULONG_PTR*)*(ULONG_PTR*)&Trampoline;

VirtualQuery(vfTableTrampoline, &mbi, sizeof(mbi));
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &dwOLD);

vfTableTrampoline[0] = (ULONG_PTR)GetClassVirtualFnAddress(pbase, 0);
printf("Base::Add() %p\n", vfTableTrampoline[0]);
TrampolineClass *p = &Trampoline;

VirtualProtect(mbi.BaseAddress, mbi.RegionSize, dwOLD, 0);

VirtualQuery(vfTableToHook, &mbi, sizeof(mbi));
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &dwOLD);

vfTableToHook[0] = (ULONG_PTR)GetClassVirtualFnAddress(&Detour, 0);
printf("Detour::Add() %p\n", vfTableToHook[0]);

VirtualProtect(mbi.BaseAddress, mbi.RegionSize, dwOLD, 0);

int result = pbase->Add(1, 2);
printf("result = %d \nafter call member fun.\n", result);
}

步骤 1: 获取虚拟函数表地址
  • vfTableToHook = (ULONG_PTR*)*(ULONG_PTR*)pbase 获取到 base 类实例的虚拟函数表指针。
  • vfTableTrampoline = (ULONG_PTR*)*(ULONG_PTR*)&Trampoline 获取到 TrampolineClass 的虚拟函数表指针。
步骤 2: 修改虚拟函数表
  • 保存原始函数地址:通过 VirtualQueryVirtualProtect 修改内存权限,使得可以修改虚拟函数表。在 vfTableTrampoline[0] 中保存原来 base::Add 函数的地址。
  • 替换为 Detour 函数:将 vfTableToHook[0] 替换为 DetourClass::DetourFun 的地址,从而使得 base::Add 函数被替换为 DetourClass::DetourFun
步骤 3: 恢复虚拟函数表修改
  • 通过 VirtualProtect 恢复内存页的保护,以保证修改后的内存区域可以正常访问。
步骤 4: 调用虚拟函数
  • int result = pbase->Add(1, 2) 调用了被 Hook 的 base::Add 函数,但由于我们替换了虚拟函数表中的地址,实际上调用的是 DetourClass::DetourFun,最终输出和返回的值是通过 DetourClass::DetourFun 处理后的。

二次hook注意事项

1.不Hook

Hook更改稍有不慎就可能造成程序错误的后果(蓝屏警告),尤其是偏向内核的东西AddressHook,在早期病毒查杀市场,各大病毒查杀软件,就是在各个风险的点添加Hook,但是病毒查杀软件之间添加Hook,并且这些Hook并不兼容,谁也不能保准哪个Hook刚好会覆盖掉另一个的Hook,这样的问题也持续至今,所以到如今这个时代病毒查杀软件也最好只安装一种

所以有时候Hook随意更改会带来风险

2.恢复Hook前的指令

这种方法的缺点是,该段的Hook会是唯一性的,一个程序里只能有一个Hook

3.换个目标地址Hook

就跟题目一样,换个地方

4.Hook上一个Hook过程的Detour函数

这个也和题目一样理解就可以