Обнаруживать, когда модуль (DLL) выгружен
Есть ли способ прогамматически определить, когда модуль - в частности, DLL - был выгружен из процесса?
У меня нет источника DLL, поэтому я не могу изменить его точку входа в DLL. Я также не могу опросить, загружена ли DLL, потому что DLL может быть выгружена, а затем перезагружена между опросами.
Результаты
Я закончил с помощью jimharks решение обхода точки входа dll и поймав DLL_PROCESS_DETACH. Я обнаружил, что FreeLibrary() также работает, но код должен быть добавлен, чтобы определить, когда модуль фактически выгружен или если счетчик ссылок просто уменьшается. Ссылка Некролиса о поиске ссылочного счета была удобна для этого.
Следует отметить, что у меня были проблемы с MSDetours, которые фактически не выгружали модуль из памяти, если в нем существовал объезд.
Ответы
Ответ 1
Возможно, менее опасный способ, чтобы 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
Один очень плохой способ (который использовался starcraft 2) заключается в том, чтобы сделать вашу программу прикрепленной к себе, а затем следить за тем, чтобы DLL выгрузило событие отладки (http://msdn.microsoft.com/en-us/library/ms679302(VS.85).aspx), иначе вам понадобится IAT-hook FreeLibrary
и FreeLibraryEx
в процессе или в hotpatch, функции в kernel32 будут отслеживать передаваемые имена и подсчитывать глобальные ссылки.
Ответ 3
Попробуйте использовать LdrRegisterDllNotification, если вы находитесь в Vista или выше. Это требует использования GetProcAddress, чтобы найти адрес функции из ntdll.dll, но это правильный способ сделать это.
Ответ 4
@Necrolis, ваша ссылка на " скрытый способ найти количество ссылок DLL" было слишком интригующим для меня игнорировать, потому что это содержит технические детали, которые мне необходимы для реализации этого альтернативного решения (о котором я думал вчера, но не было внутренних внутренних компонентов Windows). Благодарю. Я проголосовал за ваш ответ из-за ссылки, которую вы поделили.
Связанная статья показывает, как добраться до внутреннего 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;
Прямо здесь мы имеем EntryPoint
, внутренний указатель окна к точке ввода модулей. Для dll thats DllMain
(или функция времени выполнения языка, которая в конечном итоге вызывает DllMain
). Что, если мы просто изменим это? Я написал тест и, похоже, работает, по крайней мере, на XP. Квест DllMain
вызывается с причиной DLL_PROCESS_DETACH
непосредственно перед выгрузкой DLL.
BaseAddress
- это то же значение, что и HMODULE
, и полезно для нахождения правильного 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 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;
}