C++中自定义UnhandledExceptionHandler被DLL篡改:如何调试?
定位篡改UnhandledExceptionHandler的DLL的调试方法
这个问题确实挺闹心的——第三方DLL悄悄替换你的异常处理函数,直接搞崩了崩溃上报的核心逻辑。我之前也遇到过类似情况,下面分享几个实践下来有效的调试方案,帮你快速揪出“凶手”:
1. 给SetUnhandledExceptionFilter加API钩子,监控所有调用
替换异常处理函数必然会调用SetUnhandledExceptionFilter,咱们可以给这个API加个钩子,拦截所有调用请求,记录调用者的模块信息和调用栈。
- 实现方式:可以用微软的Detours库(轻量易用),或者自己写简单的inline hook。在钩子函数里,咱们可以:
- 用
GetModuleFileNameEx获取当前调用模块的路径,输出到日志; - 用
CaptureStackBackTrace生成调用栈,保存下来方便后续分析。
- 用
- 示例代码片段(Detours版):
LONG WINAPI OriginalSetUnhandledExceptionFilter(LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter) { // 记录调用者模块信息 HMODULE callerModule = nullptr; GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCSTR)_ReturnAddress(), &callerModule); char modulePath[MAX_PATH] = {0}; GetModuleFileNameA(callerModule, modulePath, MAX_PATH); printf("SetUnhandledExceptionFilter called by: %s\n", modulePath); // 调用原函数 return DetourGetOriginalFunction((PVOID)SetUnhandledExceptionFilter)(lpTopLevelExceptionFilter); } // 初始化钩子 DetourTransactionBegin(); DetourUpdateThread(GetCurrentThread()); DetourAttach(&(PVOID&)SetUnhandledExceptionFilter, OriginalSetUnhandledExceptionFilter); DetourTransactionCommit();
2. 在调试器里给SetUnhandledExceptionFilter设断点
这是最直接的调试方式,不需要额外写代码,用VS或者WinDbg就能搞定:
- VS操作:打开调试窗口,在“断点”面板右键选择“新建断点->函数断点”,输入
kernel32!SetUnhandledExceptionFilter,然后启动调试。每次断点触发时,查看调用栈窗口,就能找到调用这个API的模块。 - WinDbg操作:启动WinDbg附加到你的进程,输入命令
bp kernel32!SetUnhandledExceptionFilter设置断点。触发时用k命令打印调用栈,用lm命令查看当前模块的详细信息。 - 进阶技巧:可以设置条件断点,比如只在传入的新处理函数不是你自己的
MyExceptionHandler时中断,减少无效断点触发次数。
3. 启用加载器快照,跟踪DLL加载全程
Windows的加载器快照可以记录每个DLL加载时的所有操作,包括它调用的API,帮你定位是哪个DLL在初始化时搞事情:
- 用
gflags.exe(Windows调试工具集里的工具):打开gflags,找到你的程序,勾选“Show loader snaps”,然后运行程序。调试器里会输出详细的加载日志,你可以搜索SetUnhandledExceptionFilter关键词,找到对应的调用模块。 - VS调试选项:打开VS的“调试->选项->调试->符号”,勾选“加载DLL时中断”。这样每个DLL加载完成后程序都会暂停,你可以在此时调用
GetUnhandledExceptionFilter检查是否被篡改,逐步排查。
4. 定期检查并恢复异常处理函数(辅助定位)
这算是个临时兼辅助的方法——在关键节点(比如每个DLL加载后、业务流程的关键步骤前)检查当前的异常处理函数是否还是你自己的,如果被替换了,立刻记录上下文信息:
- 示例代码:
LONG WINAPI MyExceptionHandler(PEXCEPTION_POINTERS pExInfo) { // 你的崩溃上报逻辑 return EXCEPTION_EXECUTE_HANDLER; } void CheckExceptionHandler() { auto currentHandler = GetUnhandledExceptionFilter(); if (currentHandler != MyExceptionHandler) { // 记录日志:当前时间、调用栈、当前模块 printf("Exception handler was replaced! Current handler: %p\n", currentHandler); // 可选:暂时恢复自己的处理函数 SetUnhandledExceptionFilter(MyExceptionHandler); } } - 调用时机:可以通过DLL注入的方式,在
DLL_PROCESS_ATTACH事件里触发检查,或者用定时器定期执行。
5. 用Process Monitor跟踪异常行为
如果怀疑是恶意DLL或者通过注册表/文件操作间接篡改,Process Monitor可以帮你监控进程的所有活动:
- 操作步骤:打开Process Monitor,过滤你的进程,设置过滤条件为“Operation is SetUnhandledExceptionFilter”,然后运行程序。所有调用这个API的操作都会被记录,你可以查看对应的进程、模块和调用栈。
总结
优先用调试器断点和API钩子,这两个方法最快定位到作恶的DLL。找到之后,你可以:
- 联系DLL开发者修复这个问题;
- 在该DLL加载完成后,重新设置你的异常处理函数;
- 如果是无法修改的第三方DLL,考虑用钩子强制拦截它的替换操作。
内容的提问来源于stack exchange,提问作者Stas Tokmakou




