Sunday, September 25, 2016

Detecting loaded DLL modules in process

I collected this code from various sources across the net, cleaned and tested it. (VS2015, x86 and x64)
Note that this is for listing the DLL from inside the process, and not from a different one.

It is important to use Loader Lock when dealing with the loader internal structures. It will also make you tread-safe, and make sure that nobody will load another DLL while you are processing the current list.

Alternative to part 1 is the WinAPI call EnumProcessModules.

Part 1: listing currently loaded DLLs



#include <Windows.h>
#include <winnt.h>
#include <winternl.h>


void ListCurrentModules() 
{

  HMODULE ntdll = GetModuleHandleA("ntdll.dll");
  NTSTATUS(NTAPI *LdrLockLoaderLock)(ULONG Flags, ULONG *State, UINT_PTR *Cookie)
    = (NTSTATUS(NTAPI *)(ULONG, ULONG *, UINT_PTR *))GetProcAddress(ntdll, "LdrLockLoaderLock");
  NTSTATUS(NTAPI *LdrUnlockLoaderLock)(ULONG Flags, UINT_PTR Cookie)

    = (NTSTATUS(NTAPI *)(ULONG, UINT_PTR))GetProcAddress(ntdll, "LdrUnlockLoaderLock");

  UINT_PTR ldrCookie;
  LdrLockLoaderLock(0, NULL, &ldrCookie);

#if defined(_M_X64) // x64
  auto pTeb = reinterpret_cast<PTEB>(__readgsqword(reinterpret_cast<UINT_PTR>(&static_cast<NT_TIB*>(nullptr)->Self)));
#elif defined(_M_ARM) // ARM
  auto pTeb = reinterpret_cast<PTEB>(_MoveFromCoprocessor(15, 0, 13, 0, 2)); // CP15_TPIDRURW
#else // x86
  auto pTeb = reinterpret_cast<PTEB>(__readfsdword(reinterpret_cast<DWORD>(&static_cast<NT_TIB*>(nullptr)->Self)));
#endif

  auto pPeb = pTeb->ProcessEnvironmentBlock;
  auto pLdrData = pPeb->Ldr;
  auto pModListHdr = &pLdrData->InMemoryOrderModuleList;

  for (auto pModListCurrent = pModListHdr->Flink; pModListCurrent != pModListHdr; pModListCurrent = pModListCurrent->Flink)
  {
    // Get current module in list
    auto pModEntry = reinterpret_cast<PLDR_DATA_TABLE_ENTRY>(pModListCurrent);
    HMODULE moduleHandle = (HMODULE)pModEntry->Reserved2[0]; // ModuleBaseAddress
    printf("Module [%S] handle is [%p]\n",
        pModEntry->FullDllName.Buffer, moduleHandle);
  }

  LdrUnlockLoaderLock(0, ldrCookie);
}

Part 2: Getting notifications for DLL loading

I'm using the non official function, LdrRegisterDllNotification.

enum NotificationReasonEnum
{
  LDR_DLL_NOTIFICATION_REASON_LOADED = 1,
  LDR_DLL_NOTIFICATION_REASON_UNLOADED = 2
};

typedef struct _LDR_DLL_LOADED_NOTIFICATION_DATA {
  ULONG Flags;                    //Reserved.
  PCUNICODE_STRING FullDllName;   //The full path name of the DLL module.
  PCUNICODE_STRING BaseDllName;   //The base file name of the DLL module.
  PVOID DllBase;                  //A pointer to the base address for the DLL in memory.
  ULONG SizeOfImage;              //The size of the DLL image, in bytes.
} LDR_DLL_LOADED_NOTIFICATION_DATA, *PLDR_DLL_LOADED_NOTIFICATION_DATA;

typedef struct _LDR_DLL_UNLOADED_NOTIFICATION_DATA {
  ULONG Flags;                    //Reserved.
  PCUNICODE_STRING FullDllName;   //The full path name of the DLL module.
  PCUNICODE_STRING BaseDllName;   //The base file name of the DLL module.
  PVOID DllBase;                  //A pointer to the base address for the DLL in memory.
  ULONG SizeOfImage;              //The size of the DLL image, in bytes.
} LDR_DLL_UNLOADED_NOTIFICATION_DATA, *PLDR_DLL_UNLOADED_NOTIFICATION_DATA;

typedef union _LDR_DLL_NOTIFICATION_DATA {
  LDR_DLL_LOADED_NOTIFICATION_DATA Loaded;
  LDR_DLL_UNLOADED_NOTIFICATION_DATA Unloaded;
} LDR_DLL_NOTIFICATION_DATA, *PLDR_DLL_NOTIFICATION_DATA;

void NTAPI DllNotificationCallback(NotificationReasonEnum eAction, LDR_DLL_NOTIFICATION_DATA *NotificationData, void *Context)
{
  if (eAction == LDR_DLL_NOTIFICATION_REASON_LOADED)
  {
    HANDLE moduleHandle = NotificationData->Loaded.DllBase;
    printf("Module [%S] dynamically loaded handle=%p\n", NotificationData->Loaded.BaseDllName->Buffer, moduleHandle);
  }
}

void RegisterDLLNotifications(PVOID Context) 
{
  HMODULE ntdll = LoadLibraryA("ntdll.dll");
  NTSTATUS(NTAPI *LdrRegisterDllNotification)(ULONG Flags, void *NotificationFunction, PVOID Context, PVOID  *Cookie) 
    = (NTSTATUS(NTAPI *)(ULONG, void *, PVOID, PVOID *))GetProcAddress(ntdll, "LdrRegisterDllNotification");
  void *cookie;
  LdrRegisterDllNotification(0, (void*)DllNotificationCallback, Context, &cookie);
}

Context can be NULL. Surprisingly, cookie is not optional, but you can just discard the value if you don't need to unregister the callback.

No comments:

Post a Comment