static DWORD WINAPI ThreadCreateInDllMain(LPVOID) { printf("ThreadCreateInDllMain start"); return 0; } BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { printf("DllMain start.\n"); HMODULE hMod = NULL; HANDLE hThread = NULL; switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: printf("DLL_PROCESS_ATTACH\n"); hThread = CreateThread(NULL, 0, ThreadCreateInDllMain, NULL, 0, NULL); WaitForSingleObject(hThread, INFINITE); break; case DLL_THREAD_ATTACH: printf("DLL_THREAD_ATTACH\n"); break; case DLL_THREAD_DETACH: printf("DLL_THREAD_DETACH\n"); break; case DLL_PROCESS_DETACH: printf("DLL_PROCESS_DETACH\n"); break; } return TRUE; }我们看看这个NtWaitforSingleObject在等待什么(其实我们自己写的代码我们肯定知道是在等待一个线程对象):
查看句柄对应的详细信息:
等待3号线程:
我们看3号线程在干啥,3号线程也在等待(3号线程的等待是关键):
它在等待一个事件:
IDA看它等的是什么,它在等待LdrpWorkCompleteEvent事件:
设置这个Event的线程不是在等待,就是已经退出了。所以我们要找哪里能SetEvent(LdrpWorkCompleteEvent),找到是在LdrpProcessWork中:
我没有具体去追踪调用ZwSetEvent(LdrpWorkCompleteEvent)的调用关系,但是,总之,我们知道DllMain中的线程会负责ZwSetEvent(LdrpWorkCompleteEvent),但是它在等待线程完成,而线程又在等待DllMain的ZwSetEvent(LdrpWorkCompleteEvent),所以会死锁。 如果代码是这样,就不会死锁:
static DWORD WINAPI ThreadCreateInDllMain(LPVOID) { printf("ThreadCreateInDllMain start"); return 0; } BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { printf("DllMain start.\n"); HMODULE hMod = NULL; HANDLE hThread = NULL; switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: printf("DLL_PROCESS_ATTACH\n"); hThread = CreateThread(NULL, 0, ThreadCreateInDllMain, NULL, 0, NULL); break; case DLL_THREAD_ATTACH: printf("DLL_THREAD_ATTACH\n"); break; case DLL_THREAD_DETACH: printf("DLL_THREAD_DETACH\n"); break; case DLL_PROCESS_DETACH: printf("DLL_PROCESS_DETACH\n"); break; } return TRUE; }我单步跟踪了LdrpDrainWorkQueue,即线程的NtWaitForsingleobject的调用者,如果DllMain中没有WaitForSingleObject的流程,LdrpDrainWorkQueue从这里就break掉了:
如果DllMain中有WaitForsingleobject,那么这里直接跳转:
跳转到这里等待:
其实LdrpDrainWorkQueue中会走到NtWaitforSingleObject进行等待的根本原因就在于上边判断了是否有WorkInProcess:
所以我们可以推断LdrpWorkInProgress表示是否还有DllMain在处理。创建线程的时候,如果还有DllMain没有执行完,那么会等待这个DllMain执行完才创建线程。而此时Thread在等待DllMain完,而DllMain又在等待Thread则必然死锁。