开发者

Detect when a Module (DLL) is unloaded

Is there a way to progammatically detect when a module - specifically a DLL - has been unloaded from a process?

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.

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.

I should note that I had problems with MSDetours not actually unloading the module from memory if a deto开发者_运维问答ur existed within it.


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.


Try using LdrRegisterDllNotification if 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.


Maybe a less bad way then Necrolis's would be to use Microsoft Research's Detours package to hook the dll's entry point to watch for DLL_PROCESS_DETACH notifications.

You can find the entry point given an HMODULE (as returned by LoadLibrary) using this function:

#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.

I hope this helps.


@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.

The linked article shows how to get to the internal 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.

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.

This is all Windows internals. It’s (mostly) documented, but the MSDN documentation warns “ZwQueryInformationProcess may be altered or unavailable in future versions of 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;
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜