Определить, когда модуль (DLL) выгружается - PullRequest
11 голосов
/ 22 ноября 2010

Есть ли способ прогамматически определить, когда модуль - в частности, DLL - выгружен из процесса?

У меня нет источника DLL, поэтому я не могу изменить его точку входа DLL,Также я не могу опрашивать, если DLL в данный момент загружена, потому что DLL может быть выгружена и затем перезагружена между опросами.

РЕЗУЛЬТАТЫ :

В итоге я использовал решение jimharks для обходаточка входа в dll и перехват DLL_PROCESS_DETACH.Я обнаружил, что обход FreeLibrary () также работает, но необходимо добавить код, чтобы определить, когда модуль действительно выгружен или счетчик ссылок просто уменьшается.Ссылка Некролиса о поиске счетчика ссылок была удобна для этого метода.

Я должен заметить, что у меня были проблемы с MSDetours, который фактически не выгружал модуль из памяти, если в нем существовал обходной путь.

Ответы [ 4 ]

10 голосов
/ 22 ноября 2010

Один очень плохой способ (который использовался в starcraft 2) - заставить вашу программу присоединиться к себе, а затем отслеживать событие отладки dll unload (http://msdn.microsoft.com/en-us/library/ms679302(VS.85).aspx),, иначе вам понадобится перехватить IAT * 1003). * и FreeLibraryEx в процессе или hotpatch функции в kernel32, они контролируют передаваемые имена и подсчитывает глобальные ссылки.

7 голосов
/ 22 ноября 2010

Попробуйте использовать LdrRegisterDllNotification , если вы используете Vista или выше. Требуется использовать GetProcAddress, чтобы найти адрес функции из ntdll.dll, но это правильный способ сделать это.

4 голосов
/ 24 ноября 2010

@ 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, внутренний указатель Window на точку входа модуля. Для DLL это 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'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;
}
4 голосов
/ 22 ноября 2010

Возможно, менее опасный способ, чем Necrolis, заключался бы в использовании пакета Microsoft Research's 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;
}

Ваша замена точки входа может принять прямое действие или увеличить счетчик, который вы проверяете в своем основном цикле, или там, где это важно для вас. (И почти наверняка должен назвать исходную точку входа.)

ОБНОВЛЕНИЕ: Спасибо @LeoDavidson за указание на это в комментариях ниже. Detours 4.0 теперь лицензируется с использованием либеральной лицензии MIT.

Надеюсь, это поможет.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...