12
votes

As you know, if the call to LoadLibrary specifies a DLL module already mapped into the address space of the calling process, the function simply returns a handle of the DLL and increments the module's reference count.

Somewhere, I need to get the reference count of a dll. How to get the dll's reference count? How to know where the dll was loaded? Thanks.

8
Interesting question, but why do you want to do this? I'd bet there's an easier way to achieve what you're after.Peter Ruderman
I want to unload a dll by calling FreeLibrary, but it's still loaded. I guess somewhere is referencing it, so I want to check the reference count for debuging.ldlchina
For that purpose, use Dependency Walker. It's WAY more powerful than some inserted code.MSalters
@PeterRuderman Here's why I wanted to do this before finding this question and its answers and discovering that I didn't really want to do it after all: I wrote a C++ class that wraps LoadLibraryEx and I wanted a unit test that showed that the DLL was unloaded in the destructor.davidbak

8 Answers

6
votes

I googled it, and found this article which claims to provide the answer. Sorry I couldn't be more helpful:

6
votes

If it is a non programmatic way (thanks to C.Johnson for giving that perspective), WinDBG could be helpful

http://windbg.info/doc/1-common-cmds.html#10_modules

Look at !dlls and it's variants.

!dll - all loaded modules with load count

EDIT 2:

If you want to know from where all the DLL is being loaded from the process, there are two ways:

a. Look at the command

"bu kernel32!LoadLibraryExW ";as /mu ${/v:MyAlias} poi(@esp+4); .if ( $spat( \"${MyAlias}\", \"MYDLL\" ) != 0 ) { kn; } .else { g }" "

in the above URL

b. Run the process under WinDBG. Debug->Even Filter and select "Load Module" and set it to "Enabled" under "Execution". Under "Continue" set it to "Not Handled".

One of these should help you definitely.

2
votes
   typedef 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;

    struct LDR_MODULE_COMPARE
    {
        bool operator()(CONST LDR_MODULE& L, CONST LDR_MODULE& R) CONST {
            ustring ul = L.BaseDllName.Buffer;
            ustring ur = R.BaseDllName.Buffer;
            ul.to_lower();
            ur.to_lower();
            int cmp = wcscmp(ul.c_wstr(), ur.c_wstr());
            if (cmp == 0) {
                ul = L.FullDllName.Buffer;
                ur = R.FullDllName.Buffer;
                cmp = wcscmp(ul.c_wstr(), ur.c_wstr());
            }
            return cmp < 0;
        }
    };

    typedef std::set<LDR_MODULE, LDR_MODULE_COMPARE> LDR_MODULE_SET;
    typedef std::map<ustring, LDR_MODULE, ustring::map_ustring_compare> LDR_MODULE_MAP;

DWORD get_process_id(LPCWSTR processname_z) {
        DWORD aProcesses[1024], cbNeeded, cProcesses;
        unsigned int i;
        DWORD result = 0;
        //Enumerate all processes
        if (!EnumProcesses(aProcesses, sizeof(aProcesses), &cbNeeded))
            return NULL;

        // Calculate how many process identifiers were returned.
        cProcesses = cbNeeded / (DWORD)sizeof(DWORD);
        ustring fullpath(processname_z);
        fullpath.to_lower();
        ustring uprocess(processname_z);
        uprocess = _UWC(uprocess.filename());
        uprocess.to_lower();
        size_t ext_pos = uprocess.find_last_of('.');
        if (ext_pos != ustring::unpos) {
            uprocess = uprocess.left(ext_pos);
        }

        TCHAR szEXEName[MAX_PATH];
        //Loop through all process to find the one that matches
        //the one we are looking for
        for (i = 0; i < cProcesses; i++) {
            // Get a handle to the process
            HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION |
                                          PROCESS_VM_READ, FALSE, aProcesses[i]);

            // Get the process name
            if (NULL != hProcess) {
                HMODULE hMod;
                DWORD cbNeeded;

                if (EnumProcessModules(hProcess, &hMod,
                                       sizeof(hMod), &cbNeeded)) {
                    //Get the name of the exe file
                    GetModuleBaseName(hProcess, hMod, szEXEName,
                                      sizeof(szEXEName) / sizeof(TCHAR));
                    size_t len = _tcslen(szEXEName);
                    _tcscpy(szEXEName + len - 4, TEXT("\0"));

                    ustring uexename((TCHAR*)szEXEName);
                    uexename = _UWC(uexename.filename());
                    uexename.to_lower();
                    if (uexename == uprocess) {
                        result = aProcesses[i];
                    } else if (GetModuleFileNameEx(hProcess, 0, szEXEName, MAX_PATH)) {
                        uexename = (TCHAR*)szEXEName;
                        uexename.to_lower();
                        if (uexename == fullpath) {
                            result = aProcesses[i];
                        }
                    }
                }
            }
            CloseHandle(hProcess);
            if (result > 0) break;
        }
        return result;
    }


     HRESULT get_dll_references_or_count(LPCWSTR process_z, LPCWSTR dll_z,
                                            _Out_ DWORD* count_ptr,
                                            _Out_opt_ LDR_MODULE_SET* pdlls,
                                            _Out_opt_ LDR_MODULE_SET* pnew_dlls,
                                            BOOL append) {
            HRESULT hr = E_FAIL;
            PROCESS_BASIC_INFORMATION  pbi;
            PEB peb;
            DWORD dwSize = 0;
            SIZE_T stSize = 0;
            DWORD process_id = 0;
            HANDLE hProcess = NULL;
            PEB_LDR_DATA peb_ldr_data;
            ustring udll;

            LDR_MODULE peb_ldr_module;

            void *readAddr = NULL;
            HMODULE hMod = NULL;
            typedef NTSTATUS(WINAPI* ZwQueryInformationProcess)(HANDLE, DWORD, PROCESS_BASIC_INFORMATION*, DWORD, DWORD*);
            ZwQueryInformationProcess MyZwQueryInformationProcess = NULL;
            //
            if (count_ptr == NULL && pdlls == NULL) return hr;
            if (count_ptr != NULL) *count_ptr = 0;
            if (pdlls != NULL && !append) pdlls->clear();
            //
            process_id = get_process_id(process_z);
            if (process_id == 0) return hr;
            //
            hProcess = OpenProcess(PROCESS_QUERY_INFORMATION |
                                   PROCESS_VM_READ, FALSE, process_id);
            if (hProcess == NULL) goto Exit;
            //
            hMod = GetModuleHandle(L"ntdll.dll");
            MyZwQueryInformationProcess = (ZwQueryInformationProcess)GetProcAddress(hMod, "ZwQueryInformationProcess");
            if (MyZwQueryInformationProcess == NULL) goto Exit;
            // 
            if (MyZwQueryInformationProcess(hProcess, 0, &pbi, sizeof(PROCESS_BASIC_INFORMATION), &dwSize) < 0) {
                goto Exit;
            }
            //
            if (!ReadProcessMemory(hProcess, pbi.PebBaseAddress, &peb, sizeof(PEB), &stSize)) goto Exit;
            //
            if (!ReadProcessMemory(hProcess, peb.Ldr, &peb_ldr_data, sizeof(peb_ldr_data), &stSize)) goto Exit;
            //
            _LIST_ENTRY* pmodule = peb_ldr_data.InMemoryOrderModuleList.Flink;
            _LIST_ENTRY* pstart = pmodule;
            readAddr = (void*)pmodule;
            // Go through each modules one by one in their load order.
            udll = dll_z;
            udll.to_lower();
            while (ReadProcessMemory(hProcess, readAddr, &peb_ldr_module, sizeof(peb_ldr_module), &stSize)) {
                // Get the reference count of the DLL
                if (pdlls == NULL) {
                    ustring utmp(peb_ldr_module.FullDllName.Buffer);
                    utmp.to_lower();
                    if (utmp == udll) {
                        *count_ptr = (int)(signed short)peb_ldr_module.LoadCount;
                        break;
                    }
                    utmp = peb_ldr_module.BaseDllName.Buffer;
                    utmp.to_lower();
                    if (utmp == udll) {
                        *count_ptr = (int)(signed short)peb_ldr_module.LoadCount;
                        break;
                    }
                } else {
                    if (append) {
                        if (pdlls->find(peb_ldr_module) == pdlls->end()) {
                            pdlls->insert(peb_ldr_module);
                            if (pnew_dlls != NULL) {
                                pnew_dlls->insert(peb_ldr_module);
                            }
                        }
    #ifdef _DEBUG
                        else {
                            ATLTRACE("%s already loaded\n", peb_ldr_module.FullDllName.Buffer);
                        }
    #endif
                    } else {
                        pdlls->insert(peb_ldr_module);
                    }
                }
                _LIST_ENTRY* pprevmodule = pmodule;
                pmodule = pmodule->Flink;
                if (pprevmodule == pmodule || pmodule == pstart) {
                    break;
                }
                readAddr = (void *)(pmodule);
            }
            if (pdlls == NULL) {
                if (*count_ptr == 0) {
                    hr = E_NOINTERFACE;
                } else {
                    hr = S_OK;
                }
            } else {
                if (pdlls->size() == 0) {
                    hr = E_NOINTERFACE;
                } else {
                    if (count_ptr != NULL) {
                        *count_ptr = (DWORD)pdlls->size();
                    }
                    hr = S_OK;
                }
            }
        Exit:
            SAFE_CLOSEHANDLE(hProcess);
            return hr;
        }

This a "covert" way to get information about any proccess's loaded dlls. It does work on Windows 10. Note that ustring is my personal special string implementation, can be replaced accordingly. Worth noticing is the peb_ldr_data.InMemoryOrderModuleList.Flink. Its the linked list with all the loaded dlls. It says in MSDN documentation that when reaching the last entry it will point to itself. THAT IS NOT THE CASE. It well revert back to the first entry in the list. Atleast in Win10 Pro. LDR_MODULE::LoadCount is what you are looking for i believe.

mvh Mattias

hmmm Hold on abit...it might not work correctly in Win10...Ill be back.

3 things there are relevent. And it does work in Win 10 but the LoadCount does not show ref count though. Only if its dynamic(6) or static(-1)

PEB_LDR_DATA: There are to different structures floating around. The system one:

typedef struct _PEB_LDR_DATA {
    BYTE Reserved1[8];
    PVOID Reserved2[3];
    LIST_ENTRY InMemoryOrderModuleList;
} PEB_LDR_DATA, *PPEB_LDR_DATA;

And in some user examples user defined:

typedef struct _PEB_LDR_DATA2
    {
        ULONG Length;
        UCHAR Initialized;
        PVOID SsHandle;
        LIST_ENTRY InLoadOrderModuleList;
        LIST_ENTRY InMemoryOrderModuleList;
        LIST_ENTRY InInitializationOrderModuleList;
        PVOID EntryInProgress;

    } PEB_LDR_DATA2, *PPEB_LDR_DATA2;

This is beginning to become a mess. They both seem to work but as suspected. The memory becomes offsetted in some way. Nevertheless you can get info about a process's (arbitrary Exe program) loaded modules this way but LoadCount does not show the actual ref count in Windows > 7.

Btw i checked again use the USER defined PEB_LDR_DATA structure. The system one generates inaccuracy. Some members in LDR_MODULE becomes junk. Why? I dont know. ("Det fixar båtklubben...").

To bad...

1
votes

I'm not certain you fully understand how LoadLibrary/FreeLibrary is meant to work. You call FreeLibrary when you're finished with it and that decrements the reference count that was incremented when you loaded it. If some other part of your process is still using it, it's probably not your concern.

The reference count may tell you how many times it's been "loaded" but won't help in figuring out who loaded it.

0
votes

This information isn't available via a public API afaik. What's your scenario? Running AppVerifier will catch any mistakes you've made with module (or any other) handles.

0
votes

You can enumerate the loaded modules in a process with Module32First()/Module32Next() and then use MODULEENTRY32.GlblcntUsage to check it's reference count. I'm not sure how reliable this is though.

0
votes

Please shown below code. Note: I have written below code in Visual Studio 2010.

#include <iostream>
#include <ntstatus.h>
#include <Windows.h>
#include <winternl.h>
#include <string>
#include <tchar.h>
#include <excpt.h>
#include <fstream>

using namespace std;

struct _PROCESS_BASIC_INFORMATION_COPY 
{
    PVOID Reserved1;
    PPEB PebBaseAddress;
    PVOID Reserved2[2];
    ULONG_PTR UniqueProcessId;
    PVOID Reserved3;
} PROCESS_BASIC_INFORMATION_COPY;


struct _LDR_MODULE_COPY
{
    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_COPY , *PLDR_MODULE_COPY;


struct _PEB_LDR_DATA_COPY
{ 
    ULONG Length;
    UCHAR Initialized;
    PVOID SsHandle;
    LIST_ENTRY InLoadOrderModuleList;
    LIST_ENTRY InMemoryOrderModuleList;
    LIST_ENTRY InInitializationOrderModuleList;
    PVOID EntryInProgress;
} PEB_LDR_DATA_COPY , *PPEB_LDR_DATA_COPY;


typedef ULONG (WINAPI * ZwQueryInformationProcess)( HANDLE ProcessHandle,
                                                    ULONG  ProcessInformationClass,
                                                    PVOID  ProcessInformation,
                                                    ULONG  ProcessInformationLength,
                                                    PULONG ReturnLength );

char *w2c(char *pcstr,const wchar_t *pwstr, size_t len)
{
    int nlength=wcslen(pwstr);
    //Gets converted length
    int nbytes = WideCharToMultiByte( 0, 0, pwstr, nlength, NULL,0,NULL, NULL ); 
    if(nbytes>len)   nbytes=len;
    // Through the above obtained results, convert Unicode character for the ASCII character
    WideCharToMultiByte( 0,0, pwstr, nlength,   pcstr, nbytes, NULL,   NULL );
    return pcstr ;
}

int filter(unsigned int code, struct _EXCEPTION_POINTERS *ep) {
   puts("in filter.");
   if (code == EXCEPTION_ACCESS_VIOLATION) {
      puts("caught AV as expected.");
      return EXCEPTION_EXECUTE_HANDLER;
   }
   else {
      puts("didn't catch AV, unexpected.");
      return EXCEPTION_CONTINUE_SEARCH;
   };
}

int main()
{
    _PROCESS_BASIC_INFORMATION_COPY     stProcessBasicInformation   = { 0 };
    _PEB_LDR_DATA_COPY                  peb_ldr_data                = { 0 };
    _LDR_MODULE_COPY                    peb_ldr_module              = { 0 };
    PEB                                 peb                         = { 0 };
    USHORT                              loadCount                   = 0;
    //ofstream                          outputfile;

    //outputfile.open("dllNameAndTheirCount.txt", ios::app||ios::beg);

    HMODULE hModule = LoadLibrary( (const char*)"NTDLL.dll" );

    HANDLE hProcess = OpenProcess( PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId()); /* Get current prcess handle */

    ZwQueryInformationProcess ZwQueryInformationProcessPtr = (ZwQueryInformationProcess)GetProcAddress( hModule, "ZwQueryInformationProcess");

    if(ZwQueryInformationProcessPtr){
        ZwQueryInformationProcessPtr(hProcess, 0, &stProcessBasicInformation, sizeof(stProcessBasicInformation), 0);
    }

    DWORD dwSize = 0;
    bool bStatus;
    /* Get list of loaded DLLs from PEB. */
    bStatus = ReadProcessMemory(hProcess, stProcessBasicInformation.PebBaseAddress, &peb, sizeof(peb), &dwSize);

    bStatus = ReadProcessMemory(hProcess, peb.Ldr, &peb_ldr_data, sizeof(peb_ldr_data), &dwSize);


    void *readAddr = (void*) peb_ldr_data.InLoadOrderModuleList.Flink;

     // Go through each modules one by one in their load order.
    while( ReadProcessMemory(hProcess, readAddr, &peb_ldr_module, sizeof(peb_ldr_module), &dwSize) )
    {

        __try{
                // Get the reference count of the DLL
                loadCount = (signed short)peb_ldr_module.LoadCount;
                //outputfile << "DLL Name: " << peb_ldr_module.BaseDllName.Buffer << endl;
                //outputfile << "DLL Load Count: " << peb_ldr_module.LoadCount << endl;
                wcout << "DLL Name: " << peb_ldr_module.BaseDllName.Buffer << endl;
                cout << "DLL Load Count: " << peb_ldr_module.LoadCount << endl;
                cout << endl << endl;
            }_except(filter(GetExceptionCode(), GetExceptionInformation())){
                //outputfile << "DLL Name: " << "No Name Found" << endl;
                //outputfile << "DLL Load Count: " << peb_ldr_module.LoadCount << endl;
                readAddr = (void *) peb_ldr_module.InLoadOrderModuleList.Flink;
                continue;
        }
        readAddr = (void *) peb_ldr_module.InLoadOrderModuleList.Flink;
    }

    FreeLibrary( hModule );

    return 0;
}

For more info go to below url

0
votes

Tested on Windows 8.1. Will not guarantee that this will work on newer windows (e.g. 10, however - according to documentation should be working)

#include <winternl.h>                   //PROCESS_BASIC_INFORMATION


// warning C4996: 'GetVersionExW': was declared deprecated
#pragma warning (disable : 4996)
bool IsWindows8OrGreater()
{
    OSVERSIONINFO ovi = { 0 };
    ovi.dwOSVersionInfoSize = sizeof( OSVERSIONINFO );

    GetVersionEx(&ovi);
    if( (ovi.dwMajorVersion == 6 && ovi.dwMinorVersion >= 2) || ovi.dwMajorVersion > 6 )
        return true;

    return false;
} //IsWindows8OrGreater
#pragma warning (default : 4996)



bool ReadMem( void* addr, void* buf, int size )
{
    BOOL b = ReadProcessMemory( GetCurrentProcess(), addr, buf, size, nullptr );
    return b != FALSE;
}

#ifdef _WIN64
    #define BITNESS 1
#else
    #define BITNESS 0
#endif

typedef NTSTATUS (NTAPI *pfuncNtQueryInformationProcess)(HANDLE,PROCESSINFOCLASS,PVOID,ULONG,PULONG);

//
//  Queries for .dll module load count, returns 0 if fails.
//
int GetModuleLoadCount( HMODULE hDll )
{
    // Not supported by earlier versions of windows.
    if( !IsWindows8OrGreater() )
        return 0;

    PROCESS_BASIC_INFORMATION pbi = { 0 };

    HMODULE hNtDll = LoadLibraryA("ntdll.dll");
    if( !hNtDll )
        return 0;

    pfuncNtQueryInformationProcess pNtQueryInformationProcess = (pfuncNtQueryInformationProcess)GetProcAddress( hNtDll, "NtQueryInformationProcess");
    bool b = pNtQueryInformationProcess != nullptr;
    if( b ) b = NT_SUCCESS(pNtQueryInformationProcess( GetCurrentProcess(), ProcessBasicInformation, &pbi, sizeof( pbi ), nullptr ));
    FreeLibrary(hNtDll);

    if( !b )
        return 0;

    char* LdrDataOffset = (char*)(pbi.PebBaseAddress) + offsetof(PEB,Ldr);
    char* addr;
    PEB_LDR_DATA LdrData;

    if( !ReadMem( LdrDataOffset, &addr, sizeof( void* ) ) || !ReadMem( addr, &LdrData, sizeof( LdrData ) ) ) 
        return 0;

    LIST_ENTRY* head = LdrData.InMemoryOrderModuleList.Flink;
    LIST_ENTRY* next = head;

    do {
        LDR_DATA_TABLE_ENTRY LdrEntry;
        LDR_DATA_TABLE_ENTRY* pLdrEntry = CONTAINING_RECORD( head, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks );

        if( !ReadMem( pLdrEntry , &LdrEntry, sizeof(LdrEntry) ) )
            return 0;

        if( LdrEntry.DllBase == (void*)hDll )
        {
            //  
            //  http://www.geoffchappell.com/studies/windows/win32/ntdll/structs/ldr_data_table_entry.htm
            //
            int offDdagNode = (0x14 - BITNESS) * sizeof(void*);   // See offset on LDR_DDAG_NODE *DdagNode;

            ULONG count = 0;
            char* addrDdagNode = ((char*)pLdrEntry) + offDdagNode;

            //
            //  http://www.geoffchappell.com/studies/windows/win32/ntdll/structs/ldr_ddag_node.htm
            //  See offset on ULONG LoadCount;
            //
            if( !ReadMem(addrDdagNode, &addr, sizeof(void*) ) || !ReadMem( addr + 3 * sizeof(void*), &count, sizeof(count) ) )
                return 0;

            return (int)count;
        } //if

        head = LdrEntry.InMemoryOrderLinks.Flink;
    }while( head != next );

    return 0;
} //GetModuleLoadCount

Usage for injected .dll's:

// Someone reserved us, let's force us to shutdown.
while( GetModuleLoadCount( dll ) > 1 )
    FreeLibrary(dll);

FreeLibraryAndExitThread(dll, 0);