c ++ - Opdag, når et modul (DLL) er losset

Indlæg af Hanne Mølgaard Plasc

Problem



Er der en måde at progammatisk opdage, når et modul - specifikt en DLL - er blevet losset fra en proces?


Jeg har ikke DLL-kilden, så jeg kan ikke ændre det s DLL-indgangspunkt. Jeg kan heller ikke undersøge, om DLL'en aktuelt er indlæst, fordi DLL'en kan blive aflæst og derefter genindlæst mellem afstemning.


RESULTATER :


Jeg endte med at bruge jimharks løsning af omvejning dll entry point og fange DLL\_PROCESS\_DETACH. Jeg fandt også omvejen FreeLibrary () til at fungere, men kode skal tilføjes for at detektere, hvornår modulet faktisk er aflastet, eller hvis referencetællingen bare bliver reduceret. Necrolis '-linket om at finde referencetællingen var praktisk for en metode til at gøre det.


Jeg skal bemærke, at jeg havde problemer med MSDetours, og at jeg faktisk ikke losede modulet fra hukommelsen, hvis der var en omvej indenfor den.

Bedste reference


Måske en mindre dårlig måde, da Necrolis 's ville være at bruge Microsoft Research' s Detours pakke til at koble dll 's indgangspunkt for at se efter DLL\_PROCESS\_DETACH anmeldelser. [18]


Du kan finde indgangspunktet givet en HMODULE (som returneret af LoadLibrary) ved hjælp af denne funktion:


#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;
}


Din indtastning af udskiftning kan tage direkte handling eller øge en tæller, som du kontrollerer i din hovedløkke eller hvor det er vigtigt for dig. (Og skal næsten helt sikkert ringe til det oprindelige indtastningspunkt.)


UPDATE: Takket være @LeoDavidson for at pege på dette i kommentarerne nedenfor. Omveje 4.0 er nu licenseret ved hjælp af den liberale MIT-licens.


Jeg håber det hjælper.

Andre referencer 1


En meget dårlig måde (som blev brugt af Starcraft 2), er at få dit program til at føje til sig selv, så overvåge for dll-fejl debug event (http://msdn.microsoft.com/en-us/library/ms679302 (VS. 85) .aspx), ellers skal du enten IAT-krog FreeLibrary og FreeLibraryEx i processen eller hotpatch'en overvåger funktionerne i kernel32 dem, der overføres og de globale referencetællinger. [19]

Andre referencer 2


Prøv at bruge LdrRegisterDllNotification, hvis du er på Vista eller over. Det kræver at bruge GetProcAddress til at finde funktionsadressen fra ntdll.dll, men det er den rigtige måde at gøre det på. [20]

Andre referencer 3


@Necrolis, dit link til 'Den skjulte måde at finde Reference Count på DLL' var bare for spændende for mig at ignorere, fordi den indeholder de tekniske detaljer, jeg havde brug for til at gennemføre denne alternative løsning (som jeg tænkte på i går, men manglede den Windows Internals). Tak. Jeg stemte for dit svar på grund af det link, du delte. [21]


Den linkede artikel viser, hvordan du kommer til det interne 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;


Her har vi EntryPoint, Vindues interne pointer til modulets indtastningspunkt. For en dll, der er DllMain (eller sprogkørselstidens funktion, der i sidste ende kalder DllMain. Hvad hvis vi bare ændre det? Jeg skrev en test, og det ser ud til at virke, i hvert fald på XP. Knappen DllMain bliver kaldt med grund DLL\_PROCESS\_DETACH lige før DLL-lossen.


BaseAddress er den samme værdi som en HMODULE og er nyttig til at finde den rigtige LDR\_MODULE. LoadCount er her, så vi kan spore det. Og endelig FullDllName hjælper til debugging og gør det muligt at søge efter DLL-navn i stedet for HMODULE.


Dette er alle Windows internals. Det er (for det meste) dokumenteret, men MSDN-dokumentationen advarer 'ZwQueryInformationProcess kan blive ændret eller utilgængelig i fremtidige versioner af Windows.' [22]


Her er et komplet eksempel (men uden fuld fejl kontrol). Det ser ud til at virke, men har ikke set meget test.


// 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("
");
    printf("DllMain(0x\%.8X, \%s, 0x\%.8X)
",
        (int)hinstDLL, pszReason, (int)lpvReserved);
    printf("
");

    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
", (int)hmodAdvapi);

    HookDllEntryPoint(
        hmodAdvapi, DllMain\_advapi32, &pDllMain\_advapi32);

    \_beginthread(TestThread, 0, NULL);
    Sleep(1000);

    return 0;
}