如何检查是不是在 Visual C++ 应用程序中的第三方 DLL 中创建了新线程

Posted

技术标签:

【中文标题】如何检查是不是在 Visual C++ 应用程序中的第三方 DLL 中创建了新线程【英文标题】:How to check whether new threads created inside third party DLL in visual c++ application如何检查是否在 Visual C++ 应用程序中的第三方 DLL 中创建了新线程 【发布时间】:2019-05-08 11:44:19 【问题描述】:

我目前正在努力验证是否在我们从 Visual c++ 应用程序中的代码调用的 dll 文件中创建了新线程。如果在 dll 中创建了任何线程,我最好查看资源消耗和调用堆栈。我需要在运行应用程序而不是在调试模式下发生这种情况。你们有谁知道我们可以如何解决这个问题吗?

【问题讨论】:

我认为 ProcMon 工具会对您有所帮助。 docs.microsoft.com/en-us/sysinternals/downloads/procmon 除非正在调试进程,否则它是不可靠的。您可能希望使用 PSAPI 拍摄快照。 顺便说一下,这些信息将如何处理?因此 DLL 创建线程并消耗资源。大概它需要他们工作...... @Mayur 感谢您的链接。我已经尝试过 procmon、proc explorer、proc debug。正如迈克尔提到的,经过几次测试运行后,我意识到 procmon 不能可靠地记录所有线程。与进程资源管理器进行比较时,日志文件中缺少一些线程,并且一些线程在事件日志中具有不同的线程 ID。 Seva 已经提到过 API 挂钩,这会很有帮助。在 API Hooking 中,您需要在每次调用创建线程 API 时将其记录在文件中并将该调用传递给内核。 【参考方案1】:

让应用程序在其启动代码中挂钩 CreateThread API,最好在 DLL 初始化之前。这是很深奥的魔法;你必须确切地知道你在做什么。

编辑:如果你不得不问,你可能无法胜任这项任务。风险自负。

一般的想法是:使用反汇编程序在运行的进程中查看CreateThread() 的前几个命令。在主 exe 代码中,修补 CreateThread 的内存以将 JMP 命令插入到您自己的蹦床函数中。在汇编中编写一个蹦床,它会调用你自己的钩子函数,可能会从修补部分重复一些命令,然后跳回 CreateThread() 的未修补部分。

这不适用于生产 - 仅用于在您自己的机器上运行可执行文件,使用已知版本的 CreateThread,因为它取决于 CreateThread 的内容。在某种程度上,应用程序变成了它自己的调试器,但这不是 DLL 可以轻易注意到的调试器。

为了修补进程内存的可执行部分,您可能需要调整内存保护标志,默认情况下它是不可写的。

当然,你得注意一点点。

或者,您可以修补 CreateThread 的开头以导致异常(例如 INT 3)并使用向量异常处理程序来捕获它。 Win32 API 函数开头有一个 2 字节的 NOP,刚好可以打补丁。

编辑:尝试了 VEH 方法,它看起来更干净。现在,在我的机器上,CreateThread 的第一个命令是MOV EDI, EDI - 这实际上是一个 2 字节的无操作,非常适合打补丁。所以异常处理程序去:

LONG NTAPI OnExc(_EXCEPTION_POINTERS* Exc)

    if (Exc->ExceptionRecord->ExceptionCode == EXCEPTION_BREAKPOINT)
     //Feel free to add an extra check for exception address
      //Just in case there are rogue INT 3's elsewhere

        wprintf(L"Yay, thread created\n");
        //Not a good idea to do I/O from the exception handler :)

        //Continue from the next command after INT 3
        Exc->ContextRecord->Eip++; 
        return EXCEPTION_CONTINUE_EXECUTION;
    
    else
        return EXCEPTION_CONTINUE_SEARCH;

挂钩代码如下:

AddVectoredExceptionHandler(1, OnExc);

HMODULE hKernel32 = GetModuleHandle(L"kernel32.dll");
unsigned char* pCreateThread = (unsigned char*)GetProcAddress(hKernel32, "CreateThread");

//Allow writing to the memory block where CreateThread is
MEMORY_BASIC_INFORMATION mi;
VirtualQuery(pCreateThread, &mi, sizeof mi);
DWORD dw;
VirtualProtect(mi.BaseAddress, mi.RegionSize, PAGE_EXECUTE_READWRITE, &dw);

//Check if the first two bytes are indeed MOV EDI, EDI
if (pCreateThread[0] == 0x8b && pCreateThread[1] == 0xff)

    //And patch!
    pCreateThread[0] = 0xcc; //Replace with INT 3 
    pCreateThread[1] = 0x90; //Replace with NOP

就是这样,上瘾了。随意调用_beginthread_beginthreadex 来测试它——但不在调试器下。调试器会在异常处理程序之前捕获并处理 INT 3。

这都是 32 位代码。甚至没有看过 Win64 等效项是什么样的。

我没有涵盖这项工作的其他方面。具体来说,如果有问题的 DLL 是静态加载的,并且它会在其启动代码中创建线程,并且如果挂钩安装在 [Win]main() ,那么钩子就不会捕获 那些 线程。由于这是您的项目,我无法检查。

另一种增强途径 - 如果您想捕获线程创建的结果(例如线程 ID),仅此技术是行不通的。您不仅需要挂钩 CreateThread 的入口点,还需要挂钩出口。

【讨论】:

感谢您的建议。但是你能详细说明一下吗?

以上是关于如何检查是不是在 Visual C++ 应用程序中的第三方 DLL 中创建了新线程的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Visual C++ 6.0 中检查单选按钮的值

如何在 Visual Studio (C++) 中设置发布分析

如何比较参数字符串 Visual C++

C# 检查已安装的 .NET Framework 和 MS Visual C++ Redist 版本

在 windows 中,如何使用 c++ 检查端口是不是免费

如何在 GUI 输出窗口而不是 Visual Studio 2015 中的 cmd 控制台上显示我的 C++ 程序输出?