代码之家  ›  专栏  ›  技术社区  ›  jay.lee

卸载模块(DLL)时检测

  •  11
  • jay.lee  · 技术社区  · 14 年前

    有没有一种方法可以通过程序检测模块(特别是DLL)何时从进程中卸载?

    我没有DLL源,所以无法更改它的DLL入口点。如果DLL当前已加载,我也无法进行轮询,因为DLL可能已卸载,然后在轮询之间重新加载。

    结果 :

    最后,我使用了jimharks解决方案绕开dll入口点并捕获dll进程分离。我发现detouring FreeLibrary()也可以工作,但是必须添加代码来检测模块何时实际卸载,或者引用计数是否正在减少。亡灵的链接找到参考计数是方便的方法这样做。

    我应该注意到,我在MSDeTrices中遇到了一些问题,实际上,如果内存中存在一个绕行,就不能从内存中卸载模块。

    4 回复  |  直到 14 年前
        1
  •  4
  •   jimhark    6 年前

    也许亡灵巫师的一个不那么坏的方法是 Microsoft Research's Detours package 挂接dll的入口点以监视dll进程分离通知。

    使用此函数可以找到给定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现在使用自由麻省理工学院许可证获得许可。

    我希望这能有帮助。

        2
  •  10
  •   Hasturkun    14 年前

    一个非常糟糕的方法(被星际争霸2使用)是让你的程序连接到它自己,然后监视dll卸载调试事件( http://msdn.microsoft.com/en-us/library/ms679302(VS.85).aspx ),否则你要么就得挂钩 FreeLibrary FreeLibraryEx 在进程或热修补程序中,kernel32中的函数监视传递的名称和全局引用计数。

        3
  •  7
  •   wj32    14 年前

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

        4
  •  4
  •   jimhark    13 年前

    @亡灵,你的链接 The covert way to find the Reference Count of 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 ,窗口指向模块入口点的内部指针。对于 DllMain (或者最终调用 德尔曼 ). 如果我们改变一下呢?我写了一个测试,看起来很有效,至少在XP上是这样。这个 德尔曼 钩子被调用是有原因的 DLL_PROCESS_DETACH 就在DLL卸载之前。

    这个 BaseAddress HMODULE 有助于找到正确的 激光雷达模块 . 这个 LoadCount 在这里我们可以追踪。最后 FullDllName 有助于调试,并使搜索DLL名称而不是 H模块 .

    这都是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;
    }