You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

使用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

火山引擎 最新活动