让应用程序在其启动代码中挂钩 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 的入口点,还需要挂钩出口。