无痕Hook的原理

1.C语言使用VirtualAlloc分配内存后写入指令调用

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
#include <stdio.h>
#include <windows.h>

void test()
{
// 使用 VirtualAlloc 分配内存,确保内存页面可执行
SIZE_T size = 1; // 1 字节的内存
unsigned char* arry = (unsigned char*)VirtualAlloc(NULL, size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (arry == NULL)
{
printf("Memory allocation failed.\n");
return;
}

// 将内存填充为 RET 指令
arry[0] = 0xC3;

// 直接执行内存中的代码(0xC3 为 RET,会立即返回)
__try
{
void (*func)() = (void(*)())arry;
func(); // 执行 arry 中的代码(0xC3 为 RET)
printf("Function call succeeded.\n");
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
printf("Exception caught while executing arry.\n");
}

// 释放堆内存
VirtualFree(arry, 0, MEM_RELEASE);
}

int main()
{
printf("Hello, World!\n");
test();
return 0;
}

2.C语言使用VirtualProtect修改写入指令的内存页面属性

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
#include <stdio.h>
#include <windows.h>

void test()
{
// 获取系统页面大小
SYSTEM_INFO si;
GetSystemInfo(&si);
DWORD pageSize = si.dwPageSize;

// 分配一个完整页的可执行 + 可读写内存
unsigned char* arry = (unsigned char*)VirtualAlloc(
NULL,
pageSize,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE
);

if (arry == NULL)
{
printf("Memory allocation failed.\n");
return;
}

// 写入简单指令:0xC3 (RET)
arry[0] = 0xC3;

// 可选:VirtualProtect 再设置一次(虽然没必要)
DWORD oldProtect;
if (VirtualProtect(arry, pageSize, PAGE_EXECUTE_READWRITE, &oldProtect))
{
__try
{
void (*func)() = (void(*)())arry;
func(); // 执行 arry 中的代码
printf("Function call succeeded.\n");
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
printf("Exception caught while executing arry.\n");
}

// 恢复旧权限(可选)
VirtualProtect(arry, pageSize, oldProtect, &oldProtect);
}
else
{
printf("VirtualProtect failed: %lu\n", GetLastError());
}

// 释放内存
VirtualFree(arry, 0, MEM_RELEASE);
}

int main()
{
printf("Hello, World!\n");
test();
return 0;
}

3.C语言修改函数的第一条指令为ret(0xC3)后调用

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
#include <stdio.h>
#include <windows.h>

void target() {
printf("Original target function.\n");
}

void patch_function_to_ret(void* func) {
DWORD oldProtect;
SYSTEM_INFO si;
GetSystemInfo(&si);
DWORD pageSize = si.dwPageSize;

// 获取函数起始页地址
void* pageStart = (void*)((uintptr_t)func & ~(pageSize - 1));

// 先修改为可写
if (VirtualProtect(pageStart, pageSize, PAGE_EXECUTE_READWRITE, &oldProtect)) {
// 写入 RET 指令(0xC3)
unsigned char* p = (unsigned char*)func;
*p = 0xC3;

// 恢复原权限
VirtualProtect(pageStart, pageSize, oldProtect, &oldProtect);
} else {
printf("VirtualProtect failed: %lu\n", GetLastError());
}
}

int main() {
printf("Before patching:\n");
target();

patch_function_to_ret((void*)target);

printf("After patching:\n");
target(); // 不会再输出任何内容

return 0;
}

4.在代码中手动添加int3(0xCC)指令后捕获断点

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
#include <stdio.h>
#include <windows.h>

void Int3()
{
__asm
{
int 3
}
}
void test()
{

__try
{

printf("Calling func...\n");
Int3();
printf("Function call returned.\n");
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
printf("Exception caught while executing code.\n");
}
}

int main()
{
printf("Hello, World!\n");
test();
return 0;
}

5.分配内存执行ret(0xcc),调用后处理异常

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
#include <stdio.h>
#include <windows.h>

void test()
{
// 分配1页内存(4KB),可执行+可读+可写
void* exec_mem = VirtualAlloc(NULL, 4096, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!exec_mem) {
printf("Failed to allocate memory.\n");
return;
}

// 写入一条 x86 的 RET 指令
unsigned char shellcode[] = { 0xC3 }; // RET
memcpy(exec_mem, shellcode, sizeof(shellcode));

__try
{
void (*func)() = (void(*)())exec_mem;
printf("Calling func...\n");
func(); // 执行 shellcode
printf("Function call returned.\n");
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
printf("Exception caught while executing code.\n");
}

// 释放内存
VirtualFree(exec_mem, 0, MEM_RELEASE);
}

int main()
{
printf("Hello, World!\n");
test();
return 0;
}

6.C语言修改函数的第一条指令为int3(0xCC)后调用

msvc编译器在Realse下会让修改指令失效,clang编译器正常使用。不过int 3断点在IDE中会优先被调试器先接管。

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
#include <windows.h>
#include <stdio.h>

void Demo()
{
printf("Demo Function Start\n");
printf("Demo Function End\n");
}
int main() {
DWORD oldProtect;
LPVOID address = (LPVOID)&Demo;
printf("%p\n", Demo);

// 把前面写成非法指令(0xFF)
unsigned char data[] = { 0xCC,0x90,0x90,0x90,0x90,0x90,0x90,0x90 };

if (VirtualProtect(address, sizeof(data), PAGE_EXECUTE_READWRITE, &oldProtect))
{
printf("Memory protection changed successfully.\n");
memcpy(address, data, sizeof(data));
}
else
{
printf("Failed to change memory protection. Error: %lu\n", GetLastError());
}
VirtualProtect(address, sizeof(data), oldProtect, &oldProtect);

__try
{
Demo();
}

__except (EXCEPTION_EXECUTE_HANDLER)
{
printf("try except demo\n");
}

getchar();
return 0;
}

7.C语言修改函数的第一条指令为int3(0xCC)调用后在异常处理中恢复指令(软件断点的原理)

msvc编译器在Realse下会让修改指令失效,clang编译器正常使用。不过int 3断点在IDE中会优先被调试器先接管。

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
#include <windows.h>
#include <stdio.h>

void Demo()
{
printf("Demo Function Start\n");
printf("Demo Function End\n");
}
int main() {
DWORD oldProtect;
LPVOID address = (LPVOID)&Demo;
printf("%p\n", Demo);

unsigned char recover[] = { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 };
// 把前面写成非法指令(0xFF)
unsigned char data[] = { 0xCC,0x90,0x90,0xFF,0x90,0x90,0x90,0x90 };

if (VirtualProtect(address, sizeof(data), PAGE_EXECUTE_READWRITE, &oldProtect))
{
printf("Memory protection changed successfully.\n");
memcpy(recover, address, sizeof(recover));
memcpy(address, data, sizeof(data));
}
else
{
printf("Failed to change memory protection. Error: %lu\n", GetLastError());
}
VirtualProtect(address, sizeof(data), oldProtect, &oldProtect);

__try
{
Demo();
}

__except (EXCEPTION_EXECUTE_HANDLER)
{
printf("try except demo\n");
if (VirtualProtect(address, sizeof(data), PAGE_EXECUTE_READWRITE, &oldProtect))
{
printf("Memory protection changed successfully.\n");
memcpy(address, recover, sizeof(recover));
}
else
{
printf("Failed to change memory protection. Error: %lu\n", GetLastError());
}

Demo();
}

getchar();
return 0;
}

8.修改函数指令int 3(0xcc)调用后在异常处理中恢复原指令/调用其他shellcode/调用源指令(利用软中断注入shellcode的原理)

msvc编译器在Realse下会让修改指令失效,clang编译器正常使用。不过int 3断点在IDE中会优先被调试器先接管。

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
#include <windows.h>
#include <stdio.h>

void Demo()
{
printf("Demo***************\n");
}
int main() {
DWORD oldProtect;
// 假设我们要修改某个内存区域的保护属性//
LPVOID address = (LPVOID)&Demo; // 示例地址//
printf("%p\n", Demo);
// 为 new_target() 分配可执行内存//
unsigned char* new_target = (unsigned char*)VirtualAlloc(NULL, 1024, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (new_target == NULL) {
printf("Memory allocation failed.\n");
return 1;
}
unsigned char code[] = {
0x48, 0x31, 0xC0, // xor rax, rax
0x48, 0xFF, 0xC0, // inc rax
0xC3, // ret
0x90
};
memcpy(new_target, code, 8);
void (*pnew_target)() = (void(*)())new_target;

unsigned char recover[] = { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 };
unsigned char data[] = { 0xCC,0xCC,0xCC,0xCC,0x90,0x90,0x90,0x90 };

SIZE_T size = sizeof(data); // 示例大小//
if (VirtualProtect(address, size, PAGE_EXECUTE_READWRITE, &oldProtect))
{
printf("Memory protection changed successfully.\n");
memcpy(recover, address, sizeof(recover));
memcpy(address, data, sizeof(data));
}
else
{
printf("Failed to change memory protection. Error: %lu\n", GetLastError());
}
VirtualProtect(address, size, oldProtect, &oldProtect);
__try
{
Demo();
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
SIZE_T size = sizeof(data); // 示例大小//
if (VirtualProtect(address, size, PAGE_EXECUTE_READWRITE, &oldProtect))
{
printf("Memory protection changed successfully.\n");

pnew_target(); // 执行 arry 中的代码//
//恢复指令
memcpy(address, recover, sizeof(data));
Demo();

}
else
{
printf("Failed to change memory protection. Error: %lu\n", GetLastError());
}
VirtualProtect(address, size, oldProtect, &oldProtect);
}

getchar();
return 0;
}

9.修改函数指令int 3(0xcc)调用后在异常处理中恢复原指令/调用其他函数/调用源指令(利用软中断Hook的原理)

msvc编译器在Realse下会让修改指令失效,clang编译器正常使用。不过int 3断点在IDE中会优先被调试器先接管。

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
#include <windows.h>
#include <stdio.h>

void Demo()
{
printf("Demo***************\n");
}
void new_target()
{
printf("new_target***************\n");
}
int main() {
DWORD oldProtect;
// 假设我们要修改某个内存区域的保护属性//
LPVOID address = (LPVOID)&Demo; // 示例地址//
printf("%p\n", Demo);

unsigned char recover[] = { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 };
unsigned char data[] = { 0xCC,0xCC,0xCC,0xCC,0x90,0x90,0x90,0x90 };

SIZE_T size = sizeof(data); // 示例大小//
if (VirtualProtect(address, size, PAGE_EXECUTE_READWRITE, &oldProtect))
{
printf("Memory protection changed successfully.\n");
memcpy(recover, address, sizeof(recover));
memcpy(address, data, sizeof(data));
}
else
{
printf("Failed to change memory protection. Error: %lu\n", GetLastError());
}
VirtualProtect(address, size, oldProtect, &oldProtect);
__try
{
Demo();
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
SIZE_T size = sizeof(data); // 示例大小//
if (VirtualProtect(address, size, PAGE_EXECUTE_READWRITE, &oldProtect))
{
printf("Memory protection changed successfully.\n");

new_target(); // 执行 arry 中的代码//
//恢复指令
memcpy(address, recover, sizeof(data));
Demo();

}
else
{
printf("Failed to change memory protection. Error: %lu\n", GetLastError());
}
VirtualProtect(address, size, oldProtect, &oldProtect);
}

getchar();
return 0;
}

10.使用AddVectoredExceptionHandler(VEH)捕获断点异常

此异常如果在debug模式下运行会被IDE接管异常,自己写的接管异常会被忽略,单独运行就行。

而Realse模式下编译的程序断点会被忽略。

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
#include <windows.h>
#include <stdio.h>

// 示例目标函数
void Demo() {
printf("Demo***************\n");
}

// 新的目标函数 (作为Hook目标)
void new_target() {
printf("new_target***************\n");
}
// 用于修复的字节
unsigned char recover[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
unsigned char data[] = { 0xCC, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 };
LPVOID address = (LPVOID)&Demo; // 获取Demo函数地址
// 异常处理程序
LONG CALLBACK ExceptionHandler(PEXCEPTION_POINTERS ExceptionPointers) {
// 检查是不是访问违规异常
if (ExceptionPointers->ExceptionRecord->ExceptionCode == EXCEPTION_BREAKPOINT) {
// 触发了非法访问,我们将其拦截
printf("Caught an access violation, switching to new target...\n");
// 回復原始代码
memcpy(address, recover, sizeof(recover));
// 跳转到我们的目标函数
new_target();

// 恢复执行,返回到Demo函数继续
return EXCEPTION_CONTINUE_EXECUTION;
}

// 如果不是我们关心的异常,继续默认处理
return EXCEPTION_CONTINUE_SEARCH;
}

int main() {
DWORD oldProtect;

SIZE_T size = sizeof(data); // 示例大小

// 安装异常处理器
AddVectoredExceptionHandler(1, ExceptionHandler);

// 修改页面权限为可读写
if (VirtualProtect(address, size, PAGE_EXECUTE_READWRITE, &oldProtect)) {
printf("Memory protection changed successfully.\n");

// 备份原始代码
memcpy(recover, address, sizeof(recover));

// 修改目标地址的内存内容,模拟Hook
memcpy(address, data, sizeof(data));
}
else {
printf("Failed to change memory protection. Error: %lu\n", GetLastError());
}

// 执行目标函数,触发异常
Demo();

// 恢复原始代码

memcpy(address, recover, sizeof(recover));
VirtualProtect(address, size, oldProtect, &oldProtect);
// 等待用户按键后退出
getchar();
return 0;
}

11.C语言设置硬件断点后调用函数,再在处理异常中调用其他函数(无痕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
#include <windows.h>
#include <stdio.h>

void Demo()
{
printf("Demo***************\n");
}

void new_target()
{
printf("new_target***************\n");
}

// 设置硬件断点函数
BOOL SetHardwareBreakpoint(HANDLE hThread, PVOID address)
{
CONTEXT ctx = { 0 };
ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;

if (GetThreadContext(hThread, &ctx))
{
ctx.Dr0 = (DWORD_PTR)address; // 设置要断的地址
ctx.Dr7 |= 0x00000001; // 开启 Dr0 的本地断点

if (SetThreadContext(hThread, &ctx))
{
return TRUE;
}
}
return FALSE;
}

// 清除硬件断点
BOOL ClearHardwareBreakpoint(HANDLE hThread)
{
CONTEXT ctx = { 0 };
ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;

if (GetThreadContext(hThread, &ctx))
{
ctx.Dr0 = 0;
ctx.Dr7 &= ~0x00000001; // 清除 Dr0 的本地断点位

if (SetThreadContext(hThread, &ctx))
{
return TRUE;
}
}
return FALSE;
}

int main()
{
HANDLE hThread = GetCurrentThread();
LPVOID address = (LPVOID)Demo; // 要设置硬件断点的位置

printf("Demo address: %p\n", Demo);

// 设置硬件断点
if (SetHardwareBreakpoint(hThread, address))
{
printf("Hardware breakpoint set successfully.\n");
}
else
{
printf("Failed to set hardware breakpoint.\n");
return -1;
}

__try
{
Demo(); // 触发硬件断点
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
printf("Caught hardware breakpoint exception!\n");

// 清除硬件断点
ClearHardwareBreakpoint(hThread);

new_target();
Demo();
}

getchar();
return 0;
}

12.AddVectoredExceptionHandler(VEH)机制来捕获硬件断点异常(EXCEPTION_SINGLE_STEP`)

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
#include <windows.h>
#include <stdio.h>

void Demo()
{
printf("Demo***************\n");
}

void new_target()
{
printf("new_target***************\n");
}

PVOID vehHandle = NULL;
PVOID targetAddress = NULL;

// VEH 回调
LONG CALLBACK VectoredHandler(EXCEPTION_POINTERS* ExceptionInfo)
{
if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP)
{
CONTEXT* ctx = ExceptionInfo->ContextRecord;

#ifdef _WIN64
if ((PVOID)(ctx->Rip) == targetAddress)
{
printf("[+] Caught hardware breakpoint at 64-bit address (VEH)!\n");

// 解除硬件断点
ctx->Dr0 = 0;
ctx->Dr7 &= ~0x1;

// 跳到新的逻辑
ctx->Rip = (DWORD64)new_target;

return EXCEPTION_CONTINUE_EXECUTION;
}
#else
if ((PVOID)(ctx->Eip) == targetAddress)
{
printf("[+] Caught hardware breakpoint at 32-bit address (VEH)!\n");

// 解除硬件断点
ctx->Dr0 = 0;
ctx->Dr7 &= ~0x1;

// 跳到新的逻辑
ctx->Eip = (DWORD)new_target;

return EXCEPTION_CONTINUE_EXECUTION;
}
#endif
}
return EXCEPTION_CONTINUE_SEARCH;
}

BOOL SetHardwareBreakpoint(HANDLE hThread, PVOID address)
{
CONTEXT ctx = { 0 };
ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;

if (GetThreadContext(hThread, &ctx))
{
ctx.Dr0 = (DWORD_PTR)address;
ctx.Dr7 |= 0x1; // 开启 Dr0 的本地断点

if (SetThreadContext(hThread, &ctx))
{
return TRUE;
}
}
return FALSE;
}

int main()
{
HANDLE hThread = GetCurrentThread();
targetAddress = (PVOID)Demo;

printf("Demo address: %p\n", Demo);

if (SetHardwareBreakpoint(hThread, targetAddress))
{
printf("Hardware breakpoint set successfully.\n");
}
else
{
printf("Failed to set hardware breakpoint.\n");
return -1;
}

vehHandle = AddVectoredExceptionHandler(1, VectoredHandler);
if (!vehHandle)
{
printf("Failed to add Vectored Exception Handler.\n");
return -1;
}

Demo();

if (vehHandle)
RemoveVectoredExceptionHandler(vehHandle);

getchar();
return 0;
}

13.跨进程捕获页属性异常

1.target.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Target.cpp
#include <windows.h>
#include <stdio.h>

void Demo()
{
printf("[Target] Demo function running at address: %p\n", Demo);
Sleep(500);
}

int main()
{
printf("[Target] PID: %lu\n", GetCurrentProcessId());

while (1)
{
Demo();
}
return 0;
}

2.Debugger.cpp

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
#include <windows.h>
#include <iostream>

int main()
{
DWORD pid;
std::cout << "Enter target PID: ";
std::cin >> pid;

// 打开目标进程
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if (!hProcess)
{
std::cout << "OpenProcess failed! Error: " << GetLastError() << "\n";
return 1;
}

// 假设 target.exe 中 Demo() 的地址是硬编码的
LPVOID demoAddr = (LPVOID)0x00671186; // <<< 这里要换成实际地址!

// 查询 demo 地址所在页面
MEMORY_BASIC_INFORMATION mbi = { 0 };
VirtualQueryEx(hProcess, demoAddr, &mbi, sizeof(mbi));

// 修改页面保护,加 PAGE_GUARD
DWORD oldProtect = 0;
if (VirtualProtectEx(hProcess, mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READ | PAGE_GUARD, &oldProtect))
{
std::cout << "Successfully set PAGE_GUARD.\n";
}
else
{
std::cout << "Failed to set PAGE_GUARD! Error: " << GetLastError() << "\n";
return 1;
}

// Attach 调试
if (!DebugActiveProcess(pid))
{
std::cout << "DebugActiveProcess failed! Error: " << GetLastError() << "\n";
return 1;
}

DEBUG_EVENT dbgEvent = { 0 };
bool guardHandled = false; // 只处理一次

while (true)
{
if (!WaitForDebugEvent(&dbgEvent, INFINITE))
break;

if (dbgEvent.dwDebugEventCode == EXCEPTION_DEBUG_EVENT)
{
auto& ex = dbgEvent.u.Exception.ExceptionRecord;

if (ex.ExceptionCode == EXCEPTION_GUARD_PAGE && !guardHandled)
{
std::cout << "Caught PAGE_GUARD Exception at address: " << ex.ExceptionAddress << "\n";

// 恢复页面权限
DWORD tempProtect;
VirtualProtectEx(hProcess, mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READ, &tempProtect);
std::cout << "Restored page protection.\n";

guardHandled = true;
}
}

// 继续调试
ContinueDebugEvent(dbgEvent.dwProcessId, dbgEvent.dwThreadId, DBG_CONTINUE);

if (guardHandled)
break; // 捕获一次后退出
}

// detach
DebugActiveProcessStop(pid);
std::cout << "Detached.\n";

CloseHandle(hProcess);

return 0;
}