Обнаруживать, когда модуль (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;
}