使用PAGE_GUARD和VirtualProtect设置后执行访问未触发异常的问题
排查PAGE_GUARD钩子未触发异常的问题
我来帮你分析下为什么你的PAGE_GUARD钩子没触发异常,以及对应的解决办法:
最可能的元凶:编译器内联优化
你的HookMe函数太简单了,编译器(尤其是在Release模式下)会自动把它内联到调用它的ExceptionTesting函数里。也就是说,实际执行的代码根本不在HookMe所在的内存页,自然不会触发PAGE_GUARD的异常。
解决办法:
给HookMe加上__declspec(noinline),强制编译器不内联这个函数:
__declspec(noinline) void HookMe(){ printf("Not hooked\n"); }
确认PAGE_GUARD的页级属性生效
PAGE_GUARD是页级别的内存保护属性,哪怕你在VirtualProtect里指定size=1,系统也会修改该地址所在整个页的属性,但为了稳妥,建议明确获取页大小并修改完整页的保护:
SYSTEM_INFO si; GetSystemInfo(&si); DWORD pageSize = si.dwPageSize; // 计算HookMe所在页的起始地址(对齐到页边界) LPVOID pageStart = (LPVOID)((ULONG_PTR)HookMe & ~(pageSize - 1)); if (VirtualProtect(pageStart, pageSize, PAGE_EXECUTE_READWRITE | PAGE_GUARD, &old)) printf("PAGE_GUARD set for entire page\n");
异常处理函数的返回值必须正确
你的ExceptionHandler函数没返回正确的异常处理结果,这会导致即使触发了异常,后续执行也会出问题。正确的实现需要根据异常类型返回对应的状态:
LONG ExceptionHandler(PEXCEPTION_POINTERS ex){ printf("ExceptionHandler called\n"); // 处理PAGE_GUARD异常 if (ex->ExceptionRecord->ExceptionCode == STATUS_GUARD_PAGE_VIOLATION) { // 如果你想每次调用都触发异常,需要重新设置PAGE_GUARD属性 DWORD oldProtect; SYSTEM_INFO si; GetSystemInfo(&si); LPVOID pageStart = (LPVOID)((ULONG_PTR)HookMe & ~(si.dwPageSize - 1)); VirtualProtect(pageStart, si.dwPageSize, PAGE_EXECUTE_READWRITE | PAGE_GUARD, &oldProtect); // 告诉系统继续执行原代码 return EXCEPTION_CONTINUE_EXECUTION; } // 其他异常交给系统处理 return EXCEPTION_CONTINUE_SEARCH; }
注意:第一次触发STATUS_GUARD_PAGE_VIOLATION后,系统会自动清除该页的PAGE_GUARD标记,所以如果需要每次调用HookMe都触发异常,必须在处理函数里重新设置。
验证PAGE_GUARD是否真的被设置
可以用VirtualQuery查看HookMe所在页的属性,确认PAGE_GUARD是否生效:
MEMORY_BASIC_INFORMATION mbi; VirtualQuery((LPCVOID)HookMe, &mbi, sizeof(mbi)); // PAGE_GUARD的标志是0x100,所以如果Protect包含这个值,说明设置成功 printf("Page protect flags: 0x%X\n", mbi.Protect);
整合后的测试代码
把上面的修改整合后,你的代码应该能正常触发异常了:
#include <windows.h> #include <stdio.h> __declspec(noinline) void HookMe(){ printf("Not hooked\n"); } void GoodFnc(){ printf("Hooked!\n"); } LONG ExceptionHandler(PEXCEPTION_POINTERS ex){ printf("ExceptionHandler called\n"); if (ex->ExceptionRecord->ExceptionCode == STATUS_GUARD_PAGE_VIOLATION) { DWORD oldProtect; SYSTEM_INFO si; GetSystemInfo(&si); LPVOID pageStart = (LPVOID)((ULONG_PTR)HookMe & ~(si.dwPageSize - 1)); VirtualProtect(pageStart, si.dwPageSize, PAGE_EXECUTE_READWRITE | PAGE_GUARD, &oldProtect); return EXCEPTION_CONTINUE_EXECUTION; } return EXCEPTION_CONTINUE_SEARCH; } DWORD WINAPI ExceptionTesting(LPVOID) { DWORD old = 0; AddVectoredExceptionHandler(1, ExceptionHandler); SYSTEM_INFO si; GetSystemInfo(&si); LPVOID pageStart = (LPVOID)((ULONG_PTR)HookMe & ~(si.dwPageSize - 1)); if (VirtualProtect(pageStart, si.dwPageSize, PAGE_EXECUTE_READWRITE | PAGE_GUARD, &old)) printf("PAGE_GUARD set\n"); // 验证页属性 MEMORY_BASIC_INFORMATION mbi; VirtualQuery((LPCVOID)HookMe, &mbi, sizeof(mbi)); printf("Page protect after setup: 0x%X\n", mbi.Protect); while (1) { HookMe(); Sleep(1000); } return 0; } int main(){ CreateThread(NULL, 0, ExceptionTesting, NULL, 0, NULL); Sleep(INFINITE); return 0; }
这样修改后,每次调用HookMe都会触发异常处理函数,你就可以在ExceptionHandler里实现你的钩子逻辑(比如跳转到GoodFnc)。
内容的提问来源于stack exchange,提问作者David




