UnhandledExceptionFilter本身不检测调试器,但可间接判断:当主动触发异常(如除零)时,若该函数未被调用,进程大概率正被调试,因Windows会优先将异常交给调试器而非执行此过滤器。

UnhandledExceptionFilter 能否检测调试器
不能直接检测,但可以间接判断——UnhandledExceptionFilter 本身不提供调试器存在信息,它的作用是捕获未处理异常;而 Windows 在进程被调试时,异常分发路径会绕过该过滤器(先通知调试器),导致你的过滤器根本不会被调用。这是关键突破口。
所以真正有效的思路是:主动触发一个异常,观察 UnhandledExceptionFilter 是否被调用。如果没被调用,大概率正被调试(或系统禁用了过滤器,但后者极少见)。
如何正确注册并验证过滤器是否生效
必须在主线程早期、任何可能抛异常之前调用 SetUnhandledExceptionFilter,且返回值需检查:
-
SetUnhandledExceptionFilter返回非NULL表示成功设置,返回原过滤器地址;返回NULL多数因权限不足或已被其他模块覆盖(如某些杀软、注入 DLL) - 不要在 DLL 的
DllMain中注册——此时 CRT 尚未初始化,SetUnhandledExceptionFilter可能静默失败 - 注册后立即用
__debugbreak()或除零触发一次异常,确认回调函数是否执行(加日志或断点)
为什么除零异常比 __debugbreak 更可靠
__debugbreak() 生成的是 EXCEPTION_BREAKPOINT,部分调试器(尤其是远程/内核调试场景)可能拦截后不继续传递,导致你的过滤器仍收不到;而 EXCEPTION_INT_DIVIDE_BY_ZERO 是用户态硬异常,Windows 异常分发链更“刚性”,调试器若不处理就会回落到你的过滤器。
立即学习“C++免费学习笔记(深入)”;
实操建议:
- 用
volatile int a = 0; int b = 1 / a;避免编译器优化掉除零 - 在过滤器回调中调用
IsDebuggerPresent()做双重验证(虽然它可被绕过,但和异常路径结合能提高置信度) - 注意:x64 下 Structured Exception Handling(SEH)默认不启用帧指针校验,若链接时加了
/SAFESEH:NO,可能导致异常无法进入过滤器
常见误判场景和绕过风险
这个方法不是银弹,以下情况会导致假阳性或漏判:
- 目标进程启动时带
-no-debug或类似参数(某些游戏反调试会主动调用DebugActiveProcessStop后再设过滤器) - 调试器启用了 “忽略所有异常” 选项,此时异常被吞掉,过滤器不触发,但进程未必处于调试状态
- 使用
SetThreadErrorMode(SEM_NOGPFAULTERRORBOX)不影响过滤器,但若同时禁用SEM_FAILCRITICALERRORS,可能干扰异常传播路径 - EasyHook、Microsoft Detours 等 API Hook 工具常会劫持
SetUnhandledExceptionFilter并返回伪造句柄,导致你以为注册成功,实际无效
真正稳定的做法是组合多个信号:异常过滤器响应 + NtQueryInformationProcess 查询 ProcessDebugPort + 检查父进程是否为 conhost.exe 或 vsjitdebugger.exe。单靠 UnhandledExceptionFilter 的触发与否,只是其中一环,且最容易被针对性绕过。


















