检测何时卸载模块(DLL)

有没有一种方法可以在程序中检测某个模块(特别是DLL)何时从进程中卸载?

我没有DLL源,所以我不能改变它的DLL入口点。 我也不能轮询如果DLL当前被加载,因为DLL可能被卸载,然后在轮询之间重新加载。

结果

我结束了使用jimharks解决scheme绕开DLL入口点和捕捉DLL_PROCESS_DETACH。 我发现迂回FreeLibrary()工作,但必须添加代码,以检测模块实际上卸载或引用计数正在减less。 Necrolis关于查找引用计数的链接是非常方便的。

我应该注意到,如果在内存中存在绕道,实际上并没有从内存中卸载模块,我遇到了MSDetours问题。

Solutions Collecting From Web of "检测何时卸载模块(DLL)"

也许Necrolis的一个不太坏的方法是使用Microsoft Research的Detours包来挂钩DLL的入口点来监视DLL_PROCESS_DETACH通知。

您可以使用此函数找到HMODULE(由LoadLibrary返回)的入口点:

 #include <windows.h> #include <DelayImp.h> PVOID GetAddressOfEntryPoint(HMODULE hmod) { PIMAGE_DOS_HEADER pidh = (PIMAGE_DOS_HEADER)hmod; PIMAGE_NT_HEADERS pinth = (PIMAGE_NT_HEADERS)((PBYTE)hmod + pidh->e_lfanew); PVOID pvEntry = (PBYTE)hmod + pinth->OptionalHeader.AddressOfEntryPoint; return pvEntry; } 

您的入口点替换可能会采取直接行动或增加您在主循环中检查的计数器或对您而言重要的计数器。 (并且几乎可以肯定地称为原始入口点。)

我希望这有帮助。

一个非常糟糕的方式(这是由星际争霸2使用),是让你的程序附加到自己然后监视DLL卸载调试事件( http://msdn.microsoft.com/en-us/library/ms679302(VS&#x3002; 85).aspx ),否则你可能需要IAT挂钩FreeLibraryFreeLibraryEx或者hotpatch函数在kernel32中监视被传递的名字和全局引用计数。

如果您使用的是Vista或更高版本,请尝试使用LdrRegisterDllNotification 。 它确实需要使用GetProcAddress从ntdll.dll中查找函数地址,但这是正确的方法。

@Necrolis,你的链接到“ 找到DLL的引用计数的隐蔽的方式 ”是太让人感兴趣,我不能忽略,因为它包含我需要实现这个替代解决方案的技术细节(我想到了昨天,但缺乏Windows内部)。 谢谢。 我投了你的答案,因为你分享的链接。

链接的文章显示如何到达内部LDR_MODULE

 struct _LDR_MODULE { LIST_ENTRY InLoadOrdermoduleeList; LIST_ENTRY InMemoryOrdermoduleeList; LIST_ENTRY InInitializationOrdermoduleeList; PVOID BaseAddress; PVOID EntryPoint; ULONG SizeOfImage; UNICODE_STRING FullDllName; UNICODE_STRING BaseDllName; ULONG Flags; USHORT LoadCount; USHORT TlsIndex; LIST_ENTRY HashTableEntry; ULONG TimeDateStamp; } LDR_MODULE, *PLDR_MODULE; 

就在这里,我们有EntryPoint ,窗口的内部指针,模块的入口点。 对于DllMain (或最终调用DllMain的语言运行时函数)的dll。 如果我们改变这个呢? 我写了一个测试,似乎工作,至少在XP上。 在DLL卸载之前, DllMain钩子被调用,原因是DLL_PROCESS_DETACH

BaseAddressHMODULE值相同,对于找到正确的LDR_MODULE非常有用。 LoadCount在这里,所以我们可以跟踪。 最后, FullDllName对调试很有帮助,可以搜索DLL名称而不是HMODULE

这是所有Windows内部。 这是(主要)记录,但MSDN文档警告“ZwQueryInformationProcess可能会改变或在未来版本的Windows中不可用”。

这是一个完整的例子(但没有完整的错误检查)。 它似乎工作,但没有看到太多的测试。

 // HookDllEntryPoint.cpp by Jim Harkins (jimhark), Nov 2010 #include "stdafx.h" #include <stdio.h> #include <winternl.h> #include <process.h> // for _beginthread, only needed for testing typedef NTSTATUS(WINAPI *pfnZwQueryInformationProcess)( __in HANDLE ProcessHandle, __in PROCESSINFOCLASS ProcessInformationClass, __out PVOID ProcessInformation, __in ULONG ProcessInformationLength, __out_opt PULONG ReturnLength); HMODULE hmodNtdll = LoadLibrary(_T("ntdll.dll")); // Should test pZwQueryInformationProcess for NULL if you // might ever run in an environment where this function // is not available (like future version of Windows). pfnZwQueryInformationProcess pZwQueryInformationProcess = (pfnZwQueryInformationProcess)GetProcAddress( hmodNtdll, "ZwQueryInformationProcess"); typedef BOOL(WINAPI *PDLLMAIN) ( __in HINSTANCE hinstDLL, __in DWORD fdwReason, __in LPVOID lpvReserved); // Note: It's possible for pDllMainNew to be called before // HookDllEntryPoint returns. If pDllMainNew calls the old // function, it should pass a pointer to the variable used // so we can set it here before we hook. VOID HookDllEntryPoint( HMODULE hmod, PDLLMAIN pDllMainNew, PDLLMAIN *ppDllMainOld) { PROCESS_BASIC_INFORMATION pbi = {0}; ULONG ulcbpbi = 0; NTSTATUS nts = (*pZwQueryInformationProcess)( GetCurrentProcess(), ProcessBasicInformation, &pbi, sizeof(pbi), &ulcbpbi); BOOL fFoundMod = FALSE; PLIST_ENTRY pcurmodulee = pbi.PebBaseAddress->Ldr->InMemoryOrdermoduleeList.Flink; while (!fFoundMod && pcurmodulee != &pbi.PebBaseAddress->Ldr->InMemoryOrdermoduleeList) { PLDR_DATA_TABLE_ENTRY pldte = (PLDR_DATA_TABLE_ENTRY) (CONTAINING_RECORD( pcurmodulee, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks)); // Note: pldte->FullDllName.Buffer is Unicode full DLL name // *(PUSHORT)&pldte->Reserved5[1] is LoadCount if (pldte->DllBase == hmod) { fFoundMod = TRUE; *ppDllMainOld = (PDLLMAIN)pldte->Reserved3[0]; pldte->Reserved3[0] = pDllMainNew; } pcurmodulee = pcurmodulee->Flink; } return; } PDLLMAIN pDllMain_advapi32 = NULL; BOOL WINAPI DllMain_advapi32( __in HINSTANCE hinstDLL, __in DWORD fdwReason, __in LPVOID lpvReserved) { char *pszReason; switch (fdwReason) { case DLL_PROCESS_ATTACH: pszReason = "DLL_PROCESS_ATTACH"; break; case DLL_PROCESS_DETACH: pszReason = "DLL_PROCESS_DETACH"; break; case DLL_THREAD_ATTACH: pszReason = "DLL_THREAD_ATTACH"; break; case DLL_THREAD_DETACH: pszReason = "DLL_THREAD_DETACH"; break; default: pszReason = "*UNKNOWN*"; break; } printf("\n"); printf("DllMain(0x%.8X, %s, 0x%.8X)\n", (int)hinstDLL, pszReason, (int)lpvReserved); printf("\n"); if (NULL == pDllMain_advapi32) { return FALSE; } else { return (*pDllMain_advapi32)( hinstDLL, fdwReason, lpvReserved); } } void TestThread(void *) { // Do nothing } // Test HookDllEntryPoint int _tmain(int argc, _TCHAR* argv[]) { HMODULE hmodAdvapi = LoadLibrary(L"advapi32.dll"); printf("advapi32.dll Base Addr: 0x%.8X\n", (int)hmodAdvapi); HookDllEntryPoint( hmodAdvapi, DllMain_advapi32, &pDllMain_advapi32); _beginthread(TestThread, 0, NULL); Sleep(1000); return 0; }