Introduction
Sooner or later many people start thinking about loading a DLL without LoadLibrary(). OK, maybe not so many...
It has only a few advantages and can introduce lots of inconvenience problems when coding the DLL (depending on what your DLL does) compared to a situation where you load the DLL with an ordinary LoadLibrary() call, so this technique has limited use. (I will aim the inconvenience problems below.) Still this tip can make good service as a tutorial if you want to understand what's going on behind the curtains... I myself used this stuff to write DLLs in C/C++ instead of coding offset independent assembly (in an anticheat engine), but that is another story.
Implementation
The most important steps of DLL loading are:
- Mapping or loading the DLL into memory.
- Relocating offsets in the DLL using the relocating table of the DLL (if present).
- Resolving the dependencies of the DLL, loading other DLLs needed by this DLL and resolving the offset of the needed functions.
- Calling its entrypoint (if present) with the
DLL_PROCESS_ATTACH parameter.
I wrote the code that performed these steps but then quickly found out something is not OK: This DLL doesn't have a valid HMODULE/HINSTANCE handle
and many windows functions expect you to specify one (for example, GetProcAddress(), CreateDialog(), and so on...).
Actually the HINSTANCE handle of a module is nothing more than the address of the DOS/PE header of the loaded DLL in memory. I tried to pass
this address to the functions but it didn't work because windows checks whether this handle is really a handle! This makes using manually loaded DLLs a bit harder!
After this I wrote my own GetProcAddress() as well. Later I found out that I want to use dialog resources in the DLL and CreateDialog()
also requires a module handle to get the dialog resources from the DLL. For this reason I invented my custom FindResource() function that works with
manually loaded DLLs and it can be used to find dialog resources that can be passed to the CreateDialogIndirect() function. You can use other types
of resources as well in manually loaded DLLs if you find a function for that resource that cooperates with FindResource(). In this tip you get the code
for the manual DLL loader and GetProcAddress(), but I post here the resource related functions in another tip.
Limitations
- The loaded DLL doesn't have a
HMODULE so it makes life harder especially when its about resources.
- The
DllMain() doesn't receive DLL_THREAD_ATTACH and DLL_THREAD_DETACH notifications so don't use compiler supported TLS variables
because they won't work! - If your DLL imports other DLLs, then the other DLLs are loaded with the WinAPI LoadLibrary(). This is actually not a limitation,
just mentioned it for your information. Actually it would be useless to start loading for example kernel32.dll with manual dll loading, most system DLLs would probably disfunction/crash!
- I've written my DLLs with /NODEFAULTLIB linker option that means you can't reach CRT functions and it reduces your DLL size considerably (like with 4K intros).
But then you have to go with pure WinAPI! Actually I haven't tried linking such DLLs with CRT lib because I was afraid that its initialization code would fail.
Actually it might work, you should give it a try! This depends on your compiler version and its CRT! I think that CRT linking with dynamic library has more chances
to succeed because then the dependency CRT DLL will be loaded with
LoadLibrary()!
Using the code
Write your DLL in C/C++ without using CRT (link with /NODEFAULTLIB). Load your DLL with the LoadLibrary() code I provided. You can use my custom GetProcAddress()
on the loaded DLL. If you want to use dialog resources (or some other kind of resource, but dialog is the most common) then you can use the FindResource() function I provided in one of my other tips (and the CreateDialogIndirect
WinAPI function) because that works with manually loaded DLLs as well: The inner working of FindResource() and LoadString() Win32 functions.
Sources:
Note that my sources were compiled with VC++6. Most of these
sources should compile with newer versions by issueing minor modifications.
load_dll.h
#ifndef load_dll_h
#define load_dll_h
#include <windows.h>
#include <stdio.h>
typedef BOOL (WINAPI *LPDLLMAIN) (DWORD dwImageBase, DWORD fdwReason, LPVOID lpvReserved);
typedef struct _LOAD_DLL_INFO {
DWORD dwSize;
DWORD dwFlags;
DWORD dwImageBase;
void *lpvMemBlock;
LPDLLMAIN lpfDllMain;
DWORD dwExportDirRVA;
} LOAD_DLL_INFO;
typedef BOOL (*LOAD_DLL_READPROC) (void *lpBuff, DWORD dwPosition, DWORD dwSize, void *lpvParam);
DWORD LoadDLL (
LOAD_DLL_READPROC lpfRead, void *lpvParam, DWORD dwFlags, LOAD_DLL_INFO *lpInfo );
#define DLL_SIZE_UNK 0xFFFFFFFF
DWORD LoadDLLFromFile (char *lpszFileName, DWORD dwDLLOffset, DWORD dwDLLSize,
DWORD dwFlags, LOAD_DLL_INFO *lpInfo);
DWORD LoadDLLFromFile (FILE *f, DWORD dwDLLOffset, DWORD dwDLLSize,
DWORD dwFlags, LOAD_DLL_INFO *lpInfo);
DWORD LoadDLLFromMemory (void *lpvDLLData, DWORD dwDLLSize,
DWORD dwFlags, LOAD_DLL_INFO *lpInfo);
BOOL UnloadDLL (
LOAD_DLL_INFO *lpInfo, DWORD dwFlags );
#define DLL_NO_ENTRY_CALL 0x00000001
#define DLL_NO_HEADERS 0x00000002
#define LOAD_DLL_OK 0x00000000
#define LOAD_DLL_READ_ERR 0x00000001
#define LOAD_DLL_INVALID_IMAGE 0x00000002
#define LOAD_DLL_MEMORY_ERR 0x00000003
#define LOAD_DLL_RELOC_ERR 0x00000004
#define LOAD_DLL_BAD_RELOC 0x00000005
#define LOAD_DLL_IMPORT_ERR 0x00000006
#define LOAD_DLL_BAD_IMPORT 0x00000007
#define LOAD_DLL_BOUND_IMPORT_ERR 0x00000008
#define LOAD_DLL_PROTECT_ERR 0x00000009
#define LOAD_DLL_DLLENTRY_ERR 0x0000000A
#define LOAD_DLL_FILE_NOT_FOUND 0xFFFFFFFD
#define LOAD_DLL_UNKNOWN_ERR 0xFFFFFFFE
#define LOAD_DLL_BADPARAMS 0xFFFFFFFF
FARPROC MyGetProcAddress (LOAD_DLL_INFO *lpInfo, LPCSTR lpProcName);
FARPROC MyGetProcAddress (HMODULE hModule, LPCSTR lpProcName);
FARPROC MyGetProcAddress (DWORD dwExportDirRVA, DWORD dwImageBase, LPCSTR lpProcName);
#endif
load_dll.cpp (old spaghetti style):
#include <windows.h>
#include <winnt.h>
#include <stdlib.h>
#include <string.h>
#include "load_dll.h"
#define RAISE_EXCEPTION (RaiseException (0xE0000000, 0, 0, NULL))
#define RAISE_EXCEPTION_RESULT(return_result) (result = (return_result), RAISE_EXCEPTION)
#define READ(lpBuff,dwPos,dwSize) __try { \
if (!lpfRead ((lpBuff), (dwPos), (dwSize), (lpvParam))) RAISE_EXCEPTION; \
} __except (EXCEPTION_EXECUTE_HANDLER) { RAISE_EXCEPTION_RESULT (LOAD_DLL_READ_ERR); }
DWORD LoadDLL (
LOAD_DLL_READPROC lpfRead, void *lpvParam, DWORD dwFlags, LOAD_DLL_INFO *lpInfo ) {
DWORD result = LOAD_DLL_UNKNOWN_ERR;
IMAGE_DOS_HEADER dos_hdr;
IMAGE_NT_HEADERS hdr;
IMAGE_SECTION_HEADER *sect, *s;
__try
{
if (!lpfRead) RAISE_EXCEPTION_RESULT (LOAD_DLL_BADPARAMS);
READ (&dos_hdr, 0, sizeof (dos_hdr));
if (dos_hdr.e_magic != IMAGE_DOS_SIGNATURE || !dos_hdr.e_lfanew)
RAISE_EXCEPTION_RESULT (LOAD_DLL_INVALID_IMAGE);
READ (&hdr, dos_hdr.e_lfanew, sizeof (hdr));
if (hdr.Signature != IMAGE_NT_SIGNATURE ||
hdr.OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR32_MAGIC ||
!hdr.FileHeader.NumberOfSections)
RAISE_EXCEPTION_RESULT (LOAD_DLL_INVALID_IMAGE);
DWORD size_section_headers = hdr.FileHeader.NumberOfSections * sizeof (IMAGE_SECTION_HEADER);
sect = (IMAGE_SECTION_HEADER*)malloc (size_section_headers);
if (!sect) RAISE_EXCEPTION_RESULT (LOAD_DLL_MEMORY_ERR);
__try
{
DWORD file_offset_section_headers = dos_hdr.e_lfanew + sizeof (DWORD) +
sizeof (IMAGE_FILE_HEADER) + hdr.FileHeader.SizeOfOptionalHeader;
READ (sect, file_offset_section_headers, size_section_headers);
DWORD RVAlow = (dwFlags & DLL_NO_HEADERS) ? 0xFFFFFFFF : 0, RVAhigh = 0;
WORD i;
for (i=0,s=sect; i<hdr.FileHeader.NumberOfSections; i++,s++)
{
if (!s->Misc.VirtualSize) continue;
if (s->VirtualAddress < RVAlow) RVAlow = s->VirtualAddress;
if ((s->VirtualAddress + s->Misc.VirtualSize) > RVAhigh)
RVAhigh = s->VirtualAddress + s->Misc.VirtualSize;
}
LPVOID image = VirtualAlloc (
(LPVOID)(hdr.OptionalHeader.ImageBase + RVAlow),
RVAhigh - RVAlow,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE
);
DWORD dwImageBase = hdr.OptionalHeader.ImageBase;
if (!image)
{
if (hdr.FileHeader.Characteristics & IMAGE_FILE_RELOCS_STRIPPED)
RAISE_EXCEPTION_RESULT (LOAD_DLL_RELOC_ERR);
image = VirtualAlloc (
NULL,
RVAhigh - RVAlow,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
dwImageBase = (DWORD)image - RVAlow;
}
if (!image) RAISE_EXCEPTION_RESULT (LOAD_DLL_MEMORY_ERR);
__try
{
if (!(dwFlags & DLL_NO_HEADERS))
READ ((LPVOID)dwImageBase, 0, file_offset_section_headers + size_section_headers);
for (i=0,s=sect; i<hdr.FileHeader.NumberOfSections; i++,s++)
{
DWORD dwSectionSize = min (s->Misc.VirtualSize, s->SizeOfRawData);
if (dwSectionSize) READ (
(LPVOID)(s->VirtualAddress + dwImageBase),
s->PointerToRawData,
dwSectionSize
);
}
if (dwImageBase != hdr.OptionalHeader.ImageBase &&
hdr.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress &&
hdr.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size)
{
__try
{
DWORD dwDiff = dwImageBase - hdr.OptionalHeader.ImageBase;
IMAGE_BASE_RELOCATION *reloc, *r = (IMAGE_BASE_RELOCATION*)(dwImageBase
+ hdr.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
reloc = (IMAGE_BASE_RELOCATION*)((DWORD)r
+ hdr.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size
- sizeof (IMAGE_BASE_RELOCATION));
for (; r<reloc; r=(IMAGE_BASE_RELOCATION*)((DWORD)r + r->SizeOfBlock))
{
WORD *reloc_item = (WORD*)(r + 1);
for (DWORD i = 0, dwNumItems = (r->SizeOfBlock - sizeof (IMAGE_BASE_RELOCATION)) >> 1;
i < dwNumItems;
i++, reloc_item++)
{
switch (*reloc_item >> 12)
{
case IMAGE_REL_BASED_ABSOLUTE:
break;
case IMAGE_REL_BASED_HIGHLOW:
*(DWORD*)(dwImageBase + r->VirtualAddress + (*reloc_item & 0xFFF)) += dwDiff;
break;
default:
RAISE_EXCEPTION;
}
}
}
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
RAISE_EXCEPTION_RESULT (LOAD_DLL_BAD_RELOC);
}
}
if (hdr.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress &&
hdr.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size)
{
result = LOAD_DLL_BAD_RELOC;
IMAGE_IMPORT_DESCRIPTOR *import_desc = (IMAGE_IMPORT_DESCRIPTOR*)(dwImageBase
+ hdr.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
for (; import_desc->Name; import_desc++)
{
HMODULE hDLL = LoadLibrary ((char*)(dwImageBase + import_desc->Name));
if (!hDLL) RAISE_EXCEPTION_RESULT (LOAD_DLL_IMPORT_ERR);
DWORD *src_iat, *dest_iat = (DWORD*)(dwImageBase + import_desc->FirstThunk);
if (import_desc->TimeDateStamp)
{
if (!import_desc->OriginalFirstThunk)
RAISE_EXCEPTION_RESULT (LOAD_DLL_BOUND_IMPORT_ERR);
src_iat = (DWORD*)(dwImageBase + import_desc->OriginalFirstThunk);
}
else
{
src_iat = dest_iat;
}
for (; *src_iat; src_iat++,dest_iat++)
{
*dest_iat = (DWORD)GetProcAddress (hDLL, (LPCSTR)
((*src_iat & IMAGE_ORDINAL_FLAG32) ? IMAGE_ORDINAL32(*src_iat) : dwImageBase + *src_iat + 2));
if (!*dest_iat) RAISE_EXCEPTION_RESULT (LOAD_DLL_IMPORT_ERR);
}
}
}
__try
{
for (i=0,s=sect; i<hdr.FileHeader.NumberOfSections; i++,s++)
{
if (s->Characteristics & IMAGE_SCN_CNT_CODE)
s->Characteristics |= IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ;
DWORD dwProtection;
switch ((DWORD)s->Characteristics >> (24+5))
{
case 1: dwProtection = PAGE_EXECUTE; break;
case 0: case 2: dwProtection = PAGE_READONLY; break;
case 3: dwProtection = PAGE_EXECUTE_READ; break;
case 4:
case 6: dwProtection = PAGE_READWRITE; break;
case 5:
default: dwProtection = PAGE_EXECUTE_READWRITE; break;
}
if (!VirtualProtect (
(LPVOID)(dwImageBase + s->VirtualAddress),
s->Misc.VirtualSize,
dwProtection,
&dwProtection
))
RAISE_EXCEPTION;
}
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
RAISE_EXCEPTION_RESULT (LOAD_DLL_PROTECT_ERR);
}
LPDLLMAIN lpfDllMain = NULL;
if (hdr.OptionalHeader.AddressOfEntryPoint)
lpfDllMain = (LPDLLMAIN)(hdr.OptionalHeader.AddressOfEntryPoint + dwImageBase);
if (lpfDllMain && !(dwFlags & DLL_NO_ENTRY_CALL))
{
BOOL bDllEntryResult;
__try
{
bDllEntryResult = lpfDllMain (dwImageBase, DLL_PROCESS_ATTACH, NULL);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
bDllEntryResult = FALSE;
}
if (!bDllEntryResult) RAISE_EXCEPTION_RESULT (LOAD_DLL_DLLENTRY_ERR);
}
if (lpInfo)
{
__try
{
lpInfo->dwSize = sizeof (LOAD_DLL_INFO);
lpInfo->dwFlags = dwFlags;
lpInfo->dwImageBase = dwImageBase;
lpInfo->lpvMemBlock = image;
lpInfo->lpfDllMain = lpfDllMain;
lpInfo->dwExportDirRVA =
hdr.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
RAISE_EXCEPTION_RESULT (LOAD_DLL_BADPARAMS);
}
}
result = LOAD_DLL_OK;
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
VirtualFree (image, 0, MEM_RELEASE);
}
}
__finally
{
free (sect);
}
}
__except (EXCEPTION_EXECUTE_HANDLER) {}
return result;
}
BOOL UnloadDLL (
LOAD_DLL_INFO *lpInfo, DWORD dwFlags )
{
BOOL result = TRUE;
__try
{
if (!lpInfo || lpInfo->dwSize != sizeof LOAD_DLL_INFO ||
!lpInfo->dwImageBase || !lpInfo->lpvMemBlock)
return FALSE;
if (!(dwFlags & DLL_NO_ENTRY_CALL) && lpInfo->lpfDllMain)
{
__try
{
result = lpInfo->lpfDllMain (lpInfo->dwImageBase, DLL_PROCESS_DETACH, NULL);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
result = FALSE;
}
}
VirtualFree (lpInfo->lpvMemBlock, 0, MEM_RELEASE);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
result = FALSE;
}
return result;
}
typedef struct _LOAD_DLL_FROM_FILE_STRUCT {
FILE *f;
DWORD dwDLLOffset;
DWORD dwDLLSize;
} LOAD_DLL_FROM_FILE_STRUCT;
static BOOL LoadDLLFromFileCallback (void *lpBuff, DWORD dwPosition,
DWORD dwSize, LOAD_DLL_FROM_FILE_STRUCT *lpvParam)
{
if (!dwSize) return TRUE;
if ((dwPosition + dwSize) > lpvParam->dwDLLSize) return FALSE;
fseek (lpvParam->f, lpvParam->dwDLLOffset + dwPosition, SEEK_SET);
return fread (lpBuff, 1, dwSize, lpvParam->f) == dwSize;
}
DWORD LoadDLLFromFile (FILE *f, DWORD dwDLLOffset, DWORD dwDLLSize,
DWORD dwFlags, LOAD_DLL_INFO *lpInfo)
{
if (dwDLLSize == DLL_SIZE_UNK) dwDLLSize -= dwDLLOffset;
LOAD_DLL_FROM_FILE_STRUCT ldffs = { f, dwDLLOffset, dwDLLSize };
return LoadDLL ((LOAD_DLL_READPROC)&LoadDLLFromFileCallback, &ldffs, dwFlags, lpInfo);
}
DWORD LoadDLLFromFile (char *lpszFileName, DWORD dwDLLOffset, DWORD dwDLLSize,
DWORD dwFlags, LOAD_DLL_INFO *lpInfo)
{
FILE *f = fopen (lpszFileName, "rb");
if (!f) return LOAD_DLL_FILE_NOT_FOUND;
DWORD res;
__try
{
res = LoadDLLFromFile (f, dwDLLOffset, dwDLLSize, dwFlags, lpInfo);
}
__finally
{
fclose (f);
}
return res;
}
typedef struct _LOAD_DLL_FROM_MEMORY_STRUCT {
void *lpvDLLData;
DWORD dwDLLSize;
} LOAD_DLL_FROM_MEMORY_STRUCT;
static BOOL LoadDLLFromMemoryCallback (void *lpBuff, DWORD dwPosition,
DWORD dwSize, LOAD_DLL_FROM_MEMORY_STRUCT*lpvParam)
{
if (!dwSize) return TRUE;
if ((dwPosition + dwSize) > lpvParam->dwDLLSize) return FALSE;
memcpy (lpBuff, (char*)lpvParam->lpvDLLData + dwPosition, dwSize);
return TRUE;
}
DWORD LoadDLLFromMemory (void *lpvDLLData, DWORD dwDLLSize,
DWORD dwFlags, LOAD_DLL_INFO *lpInfo)
{
if (dwDLLSize == DLL_SIZE_UNK) dwDLLSize -= (DWORD)lpvDLLData;
LOAD_DLL_FROM_MEMORY_STRUCT ldfms = { lpvDLLData, dwDLLSize };
return LoadDLL ((LOAD_DLL_READPROC)&LoadDLLFromMemoryCallback, &ldfms, dwFlags, lpInfo);
}
FARPROC MyGetProcAddress (LOAD_DLL_INFO *lpInfo, LPCSTR lpProcName)
{
return MyGetProcAddress (lpInfo->dwExportDirRVA, lpInfo->dwImageBase, lpProcName);
}
FARPROC MyGetProcAddress (HMODULE hModule, LPCSTR lpProcName)
{
__try
{
if (((IMAGE_DOS_HEADER*)hModule)->e_magic != IMAGE_DOS_SIGNATURE) return NULL;
IMAGE_NT_HEADERS *hdr = (IMAGE_NT_HEADERS*)((DWORD)hModule
+ ((IMAGE_DOS_HEADER*)hModule)->e_lfanew);
if (hdr->Signature != IMAGE_NT_SIGNATURE ||
hdr->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR32_MAGIC)
return NULL;
return MyGetProcAddress (
hdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress,
(DWORD)hModule,
lpProcName
);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
return NULL;
}
}
FARPROC MyGetProcAddress (DWORD dwExportDirRVA, DWORD dwImageBase, LPCSTR lpProcName)
{
if (!dwExportDirRVA) return NULL;
IMAGE_EXPORT_DIRECTORY *exp = (IMAGE_EXPORT_DIRECTORY*)(dwImageBase + dwExportDirRVA);
DWORD dwOrd = (DWORD)lpProcName;
__try
{
if (dwOrd < 0x10000)
{
if (dwOrd < exp->Base) return NULL;
dwOrd -= exp->Base;
}
else
{
for (DWORD i=0; i<exp->NumberOfNames; i++)
if ( !strcmp( (char*)(((DWORD*)(exp->AddressOfNames + dwImageBase))[i] + dwImageBase),
lpProcName) )
{
dwOrd = ((WORD*)(exp->AddressOfNameOrdinals + dwImageBase))[i];
break;
}
}
if (dwOrd >= exp->NumberOfFunctions) return NULL;
return (FARPROC)(((DWORD*)(exp->AddressOfFunctions
+ dwImageBase))[dwOrd] + dwImageBase);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
return NULL;
}
}