2010-11-22 11 views
10

¿Hay alguna forma de detectar de manera programada cuándo se ha descargado un módulo (específicamente un archivo DLL) de un proceso?Detecta cuando un Módulo (DLL) está descargado

No tengo el origen de la DLL, por lo que no puedo cambiarlo. Tampoco puedo sondear si el DLL está actualmente cargado porque el archivo DLL puede descargarse y luego volver a cargarse entre las encuestas.

RESULTADOS:

Terminé usando solución jimharks del desvío del punto de entrada DLL y la captura de DLL_PROCESS_DETACH. También encontré el desvío de FreeLibrary() para que funcione, pero el código debe agregarse para detectar cuándo el módulo está realmente descargado o si el recuento de referencias se está reduciendo. El enlace de Necrolis sobre la búsqueda del recuento de referencias fue útil para el método de hacerlo.

Debería tener en cuenta que tuve problemas con MSDetours que no descargaba realmente el módulo de la memoria si existía un desvío dentro de él.

+0

es este requisito para el código de entrada o durante la depuración? – Chubsdad

+0

@Chubsdad no está exactamente seguro de lo que quiere decir con "código de acceso", pero no es para la depuración. –

+0

¿Desea verificar de manera programática si el módulo ha sido descargado? – Chubsdad

Respuesta

4

Quizás una manera menos mala que Necrolis sería utilizar Microsoft Research's Detours package para enganchar el punto de entrada del dll para ver las notificaciones de DLL_PROCESS_DETACH.

puede encontrar el punto de entrada dado una HMODULE (devuelto por LoadLibrary) utilizando esta función:

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

su sustitución punto de entrada podría tomar acción directa o incrementar un contador que compruebe en su bucle principal o donde es importante para ti (Y casi seguramente debería llamar al punto de entrada original.)

Espero que esto ayude.

+0

Un problema con Detours es que tiene que pagar a Microsoft $ 10,000 si alguna vez desea hacer una compilación de 64 bits de su programa. La versión gratuita carece de soporte x64 y la versión no gratuita es ridículamente costosa. Esto ha descartado usar Desvíos cada vez que me he sentido tentado. :( –

+0

Buena idea. Probaré esto. En una nota al margen: intenté desviar FreeLibrary() para lograr mi objetivo. En el desvío llamaría FreeLibrary() trampolín y luego usar GetModuleHandle() para ver si el módulo todavía estaba cargado. Cuando utilicé mi biblioteca de desvío, funcionó. Cuando usé MSDetours, no fue así. No sé por qué, pero cuando uso MSDetours para desviar una función DLL, el DLL nunca parece estar descargado a menos el desvío está desacoplado. –

+0

+ buen ejemplo ... – Baba

9

Una forma muy malo (que fue utilizado por Starcraft 2), es hacer que su programa de adjuntar a itsself luego monitorear para el evento de descarga DLL de depuración (http://msdn.microsoft.com/en-us/library/ms679302(VS.85).aspx), más que le sea necesario IAT gancho y FreeLibraryFreeLibraryEx en el proceso o hotpatch las funciones en kernel32 supervisan los nombres que se pasan y la referencia global cuenta.

+0

Esta es una forma bastante razonable. –

+0

Tengo un código que desvía FreeLibrary() para hacer esto pero usa GetModuleHandle() para ver si el módulo todavía está cargado. ¿Hay algo que simplemente pueda verificar el recuento de referencias? –

+0

Necesitarás spelunk a través del PEB, afortunadamente está todo en los encabezados de Windows, este artículo debería ayudar: http://www.securityxploded.com/dllrefcount.php de esta manera puedes verificar si es la descarga final antes de descargarlo :) – Necrolis

5

Pruebe usar LdrRegisterDllNotification si está en Vista o superior. Requiere el uso de GetProcAddress para encontrar la dirección de función de ntdll.dll, pero es la forma correcta de hacerlo.

+0

No juguetear con funciones privadas, esta es una mala idea. –

+1

No es menos "privado" que cualquier cosa que OP trate de hacer. Explique su extraña aversión a la API nativa con más claridad. – wj32

4

@Necrolis, su enlace a "The covert way to find the Reference Count of DLL" me resultaba demasiado intrigante como para ignorarlo, ya que contiene los detalles técnicos que necesitaba para implementar esta solución alternativa (que pensé ayer, pero me faltaban los Windows Internals). Gracias. He votado por tu respuesta debido al enlace que compartiste.

El artículo enlazado muestra como llegar a la interna 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; 

Aquí tenemos EntryPoint, puntero interno de la ventana de punto de entrada del módulo. Para un DLL que es DllMain (o la función de tiempo de ejecución de idioma que eventualmente llama al DllMain). ¿Qué pasa si simplemente cambiamos eso? Escribí una prueba y parece funcionar, al menos en XP. El gancho DllMain se llama con el motivo DLL_PROCESS_DETACH justo antes de que se descargue el archivo DLL.

El BaseAddress tiene el mismo valor que HMODULE y es útil para encontrar el LDR_MODULE correcto. El LoadCount está aquí para que podamos rastrear eso.Y finalmente FullDllName es útil para la depuración y hace posible buscar el nombre de la DLL en lugar de HMODULE.

Esto es todas las partes internas de Windows. Está (en su mayoría) documentado, pero the MSDN documentation advierte que "ZwQueryInformationProcess puede estar alterado o no disponible en versiones futuras de Windows".

Aquí hay un ejemplo completo (pero sin comprobación de error completo). Parece que funciona, pero no ha visto muchas pruebas.

// 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

@ilmcuts, gracias por corregir/corregir – jimhark

+0

Si estoy tratando de detectar las DLL que se cargan/descargan durante la depuración, ¿se sigue aplicando este enfoque? – adub3

Cuestiones relacionadas