简答:
是的,单步标志是 x86 架构的一部分,并且仍然通过处理器上下文的 eflags 组件在 Windows 7 中实现。
我已成功下载您的项目,并且在关闭 UAC 的 Windows 8 上一切正常,无需修改。所以它也应该在 Windows 7 上运行。启动VEH Hooking Test.exe 时,它会显示两个消息框,在每个消息框之后我都会得到MessageBoxA 控制台输出,所以钩子起作用了。也许尝试在 Windows 7 上以管理员身份启动程序?
长答案:
SEH 代表结构化异常处理,但您所描述的听起来更像VEH - 矢量异常处理。
VEH 挂钩是一种非常慢的挂钩方法,因此它不能真正用于性能关键挂钩,例如图形挂钩,例如,您的挂钩每秒命中多次。它通常用于一次性钩子。 VEH 挂钩的目的是真正隐身。没有用别人的代码写的内存,你也不必使用调试寄存器。
以下是我将如何使用 c++ 实现它。
首先你必须注册一个向量异常处理程序。这是进程的全局异常处理程序。每个抛出的未处理的异常都会被操作系统捕获。
PVOID pExHandler = AddVectoredExceptionHandler(1, VectoredHandler);
在此之后,您应该设置HOOK_LOCATION(要挂钩的地址)所在页面的内存保护。我正在使用的新保护是PAGE_EXECUTE_READ|PAGE_GUARD。受保护的页面将导致访问异常,并在此之后自动删除保护保护。这个异常不会被任何人处理,所以它会落到我们的向量处理程序中。抛出异常后,页面又可以访问了。 (见Creating Guard Pages)
内存只能在页中保护(通常为 0x1000 字节长)。这就是为什么我们不能只保护钩子位置并产生巨大的性能开销。
DWORD orgProt;
VirtualProtect(HOOK_LOCATION, 1, PAGE_EXECUTE_READ|PAGE_GUARD, &orgProt);
现在是我们的异常处理程序。这就是它的样子:
LONG CALLBACK VectoredHandler(PEXCEPTION_POINTERS exc)
{
if (exc->ExceptionRecord->ExceptionCode == STATUS_GUARD_PAGE_VIOLATION)
{
// guard page exeption occured. guard page protection is gone now
if (HOOK_LOCATION == reinterpret_cast<long*>(exc->ContextRecord->Eip)) {
// hook location was hit! call any hook callbacks here
} else {
// hook was not hit and has to be refreshed. set single-step flag
exc->ContextRecord->EFlags |= 0x100;
}
return EXCEPTION_CONTINUE_EXECUTION;
}
if (exc->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP)
{
// single-step exeption occured. single-step flag is cleared now
// set guard page protection
DWORD oldProt;
VirtualProtect(HOOK_LOCATION, 1, PAGE_EXECUTE_READ|PAGE_GUARD, &oldProt);
return EXCEPTION_CONTINUE_EXECUTION;
}
return EXCEPTION_CONTINUE_SEARCH;
}
如果代码运行到受保护的内存页面,它将引发保护页面冲突。我们检查我们是否在钩子位置。如果我们一切都很好,我们可以调用钩子回调。如果我们不在正确的位置,我们需要以某种方式重新保护代码,但如果我们现在就这样做,我们就无法前进并且总是在同一位置获得异常并死锁应用程序。所以我们设置处理器单步标志。
现在当收到单步异常时,我们可以再次设置保护保护,因为我们前进了一条指令。这就是我们如何始终保护目标页面而不会错过任何钩子命中的方法。
成本是在目标页面中执行的每条指令有两个例外和一个页面保护。不要尝试在附加调试器的情况下执行此操作。会发疯的。
对于实际实现,您可能需要同步对象来摆脱钩子而不会使程序崩溃并更好地管理钩子。
我真的很喜欢这种巧妙的机制,希望有人能从中有所了解。