检测何时卸载模块(DLL)

时间:2010-11-22 05:10:26

标签: c++ c windows winapi

有没有办法以程序方式检测何时从某个进程卸载模块(特别是DLL)?

我没有DLL源代码,所以我无法更改它的DLL入口点。我也无法轮询DLL当前是否已加载,因为DLL可能会被卸载,然后在轮询之间重新加载。

结果

我最终使用jimharks解决方案绕过dll入口点并捕获DLL_PROCESS_DETACH。我发现绕行FreeLibrary()也可以工作,但是必须添加代码来检测模块实际卸载的时间或者引用计数是否正在减少。 Necrolis关于找到引用计数的链接对于这样做的方法很方便。

我应该注意到,如果MSDetours中存在绕道而没有实际从内存中卸载模块,我遇到了问题。

4 个答案:

答案 0 :(得分:10)

一种非常糟糕的方式(由星际争霸2使用),是让你的程序附加到自己然后监视dll卸载调试事件(http://msdn.microsoft.com/en-us/library/ms679302(VS.85).aspx),否则你需要IAT hook { {1}}和FreeLibrary在进程或hotpatch中,kernel32中的函数监视传递的名称和全局引用计数。

答案 1 :(得分:7)

如果您使用的是Vista或更高版本,请尝试使用LdrRegisterDllNotification。它确实需要使用GetProcAddress从ntdll.dll中查找函数地址,但这是正确的方法。

答案 2 :(得分:4)

然后Necrolis会使用Microsoft Research's Detours package挂钩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许可证进行许可。

我希望这会有所帮助。

答案 3 :(得分:4)

@Necrolis,您链接到“The covert way to find the Reference Count of DLL”对我来说太有趣了,因为它包含了我实现这个备用解决方案所需的技术细节(我昨天想到了,但缺少Windows Internals )。谢谢。由于您分享的链接,我投票给您答案。

链接文章介绍了如何访问内部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的内部指针指向模块的入口点。对于DllMain的dll(或最终调用DllMain的语言运行时函数)。如果我们改变它怎么办?我写了一个测试,它似乎工作,至少在XP上。在DLL卸载之前,DllMain挂钩被调用原因DLL_PROCESS_DETACH

BaseAddressHMODULE的值相同,可用于查找正确的LDR_MODULELoadCount就在这里,所以我们可以跟踪它。最后FullDllName有助于调试,并且可以搜索DLL名称而不是HMODULE

这是所有Windows内部。它(大部分)都有记录,但the MSDN documentation警告“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;
}