14

我正在做一个所谓的'seh hooking'。实际上它改变了内存区域的权限并在访问时捕获异常,因此它可以挂钩该函数。

它使用如下所示的单步陷阱:

info->ContextRecord->EFlags |= 0x100;

恢复保护PAGE_NOACCESS

该应用程序在 win xp 上运行良好,但在 win 7 上运行良好。它刚刚在 win 7 上冻结。我非常怀疑这是因为“设置单步陷阱”的事情,但我不确定。

点击这里到源包的直接下载链接

4

1 回答 1

11

简短的回答:

是的,单步标志是 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. 受保护的页面将导致访问异常,并在此之后自动删除保护保护。这个异常不会被任何人处理,所以它会落到我们的向量处理程序中。抛出异常后,页面又可以访问了。(请参阅创建保护页面

内存只能在页面中保护(通常为 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;
}

如果代码运行到受保护的内存页面,它将引发保护页面冲突。我们检查我们是否在钩子位置。如果我们一切都很好,我们可以调用钩子回调。如果我们不在正确的位置,我们需要以某种方式重新保护代码,但如果我们现在就这样做,我们将无法前进并且总是在同一位置获得异常并死锁应用程序。所以我们设置处理器单步标志。

现在当收到单步异常时,我们可以再次设置保护保护,因为我们前进了一条指令。这就是我们如何始终保护目标页面而不会错过任何钩子命中的方法。

成本是在目标页面中执行的每条指令的两个异常和一个页面保护。不要尝试在附加调试器的情况下执行此操作。它会发疯的。

对于实际实现,您可能需要同步对象来摆脱钩子而不会使程序崩溃并更好地管理钩子。

我真的很喜欢这种巧妙的机制,希望有人能从中有所了解。

于 2013-05-07T19:25:22.720 回答