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

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

火山引擎 最新活动