windows 检测何时卸载模块 (DLL)
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/4242469/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Detect when a Module (DLL) is unloaded
提问by jay.lee
Is there a way to progammatically detect when a module - specifically a DLL - has been unloaded from a process?
有没有办法以编程方式检测模块(特别是 DLL)何时从进程中卸载?
I don't have the DLL source, so I can't change it's DLL entry point. Nor can I poll if the DLL is currently loaded because the DLL may be unloaded and then reloaded between polling.
我没有 DLL 源,所以我不能改变它的 DLL 入口点。我也不能轮询当前是否加载了 DLL,因为 DLL 可能会在轮询之间被卸载然后重新加载。
RESULTS:
结果:
I ended up using jimharks solution of detouring the dll entry point and catching DLL_PROCESS_DETACH. I found detouring FreeLibrary() to work as well but code must be added to detect when the module is actually unloaded or if the reference count is just being decreased. Necrolis' link about finding the reference count was handy for on method of doing so.
我最终使用了绕过 dll 入口点并捕获 DLL_PROCESS_DETACH 的 jimharks 解决方案。我发现绕道 FreeLibrary() 也可以工作,但必须添加代码以检测模块何时实际卸载或引用计数是否正在减少。Necrolis 关于查找引用计数的链接对于这样做的方法很方便。
I should note that I had problems with MSDetours not actually unloading the module from memory if a detour existed within it.
我应该注意的是,如果 MSDetours 中存在绕路,则我在使用 MSDetours 时实际上不会从内存中卸载该模块。
采纳答案by jimhark
Maybe a less bad way then Necrolis's would be to use Microsoft Research's Detours packageto hook the dll's entry point to watch for DLL_PROCESS_DETACH notifications.
也许比 Necrolis 更糟糕的方法是使用Microsoft Research 的 Detours 包来挂钩 dll 的入口点以监视 DLL_PROCESS_DETACH 通知。
You can find the entry point given an HMODULE (as returned by LoadLibrary) using this function:
您可以使用以下函数找到给定 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;
}
Your entrypoint replacement could take direct action or increment a counter that you check for in your main loop or where it's important to you. (And should almost certainly call the original entrypoint.)
您的入口点替换可以采取直接行动或增加您在主循环中或对您很重要的地方检查的计数器。(并且几乎肯定应该调用原始入口点。)
UPDATE: Thanks to @LeoDavidson for pointing this out in the comments below. Detours 4.0 is now licensed using the liberal MIT License.
更新:感谢@LeoDavidson 在下面的评论中指出这一点。Detours 4.0 现在使用自由的 MIT 许可证获得许可。
I hope this helps.
我希望这有帮助。
回答by Necrolis
One very bad way(which was used by starcraft 2), is to make your program attach to itsself then monitor for the dll unload debug event(http://msdn.microsoft.com/en-us/library/ms679302(VS.85).aspx), else you'd either need to IAT hook FreeLibrary
and FreeLibraryEx
in the process or hotpatch the functions in kernel32 them monitor the names being passed and the global reference counts.
一种非常糟糕的方法(星际争霸 2 使用过)是让您的程序附加到自身然后监视 dll 卸载调试事件(http://msdn.microsoft.com/en-us/library/ms679302(VS. 85).aspx),否则你要么需要 IAT 钩子FreeLibrary
,FreeLibraryEx
在这个过程中要么热补丁 kernel32 中的函数,它们监视正在传递的名称和全局引用计数。
回答by wj32
Try using LdrRegisterDllNotificationif you're on Vista or above. It does require using GetProcAddress to find the function address from ntdll.dll, but it's the proper way of doing it.
如果您使用的是 Vista 或更高版本,请尝试使用LdrRegisterDllNotification。它确实需要使用 GetProcAddress 从 ntdll.dll 中查找函数地址,但这是正确的方法。
回答by jimhark
@Necrolis, your link to “The covert way to find the Reference Count of DLL” was just too intriguing for me to ignore because it contains the technical details I needed to implement this alternate solution (that I thought of yesterday, but was lacking the Windows Internals). Thanks. I voted for your answer because of the link you shared.
@Necrolis,您指向“查找 DLL 引用计数的隐蔽方法”的链接太吸引我了,无法忽略,因为它包含实现此替代解决方案所需的技术细节(我昨天想到了,但缺乏Windows 内部)。谢谢。由于您分享的链接,我投票支持您的答案。
The linked article shows how to get to the internal LDR_MODULE
:
链接的文章显示了如何进入内部LDR_MODULE
:
struct _LDR_MODULE
{
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
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;
Right here we have EntryPoint
, Window's internal pointer to the module's entry point. For a dll that's DllMain
(or the language run time function that eventually calls DllMain
). What if we just change that? I wrote a test and it seems to work, at least on XP. The DllMain
hook gets called with reason DLL_PROCESS_DETACH
just before the DLL unloads.
就在这里,我们有EntryPoint
Window 指向模块入口点的内部指针。对于 dll DllMain
(或最终调用 的语言运行时函数DllMain
)。如果我们只是改变它呢?我写了一个测试,它似乎工作,至少在 XP 上。该DllMain
钩子被调用的原因DLL_PROCESS_DETACH
就在DLL卸载之前。
The BaseAddress
is the same value as an HMODULE
and is useful for finding the right LDR_MODULE
. The LoadCount
is here so we can track that. And finally FullDllName
is helpful for debugging and makes it possible to search for DLL name instead of HMODULE
.
TheBaseAddress
与 an 的值相同HMODULE
,可用于查找正确的LDR_MODULE
。在LoadCount
这里,所以我们可以跟踪它。最后FullDllName
有助于调试,并可以搜索 DLL 名称而不是HMODULE
.
This is all Windows internals. It's (mostly) documented, but the MSDN documentationwarns “ZwQueryInformationProcess may be altered or unavailable in future versions of Windows.”
这是所有 Windows 内部结构。它(大部分)已记录在案,但MSDN 文档警告“ZwQueryInformationProcess 可能会在未来版本的 Windows 中更改或不可用。”
Here's a full example (but without full error checking). It seems to work but hasn't seen much testing.
这是一个完整的示例(但没有完整的错误检查)。它似乎有效,但没有看到太多测试。
// 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 pcurModule =
pbi.PebBaseAddress->Ldr->InMemoryOrderModuleList.Flink;
while (!fFoundMod && pcurModule !=
&pbi.PebBaseAddress->Ldr->InMemoryOrderModuleList)
{
PLDR_DATA_TABLE_ENTRY pldte = (PLDR_DATA_TABLE_ENTRY)
(CONTAINING_RECORD(
pcurModule, 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;
}
pcurModule = pcurModule->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;
}