如何检查是不是在 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 Studio (C++) 中设置发布分析
C# 检查已安装的 .NET Framework 和 MS Visual C++ Redist 版本
在 windows 中,如何使用 c++ 检查端口是不是免费
如何在 GUI 输出窗口而不是 Visual Studio 2015 中的 cmd 控制台上显示我的 C++ 程序输出?