Click here to Skip to main content
16,016,693 members
Articles / Programming Languages / ASM
Tip/Trick

Loading Win32/64 DLLs "manually" without LoadLibrary()

Rate me:
Please Sign up or sign in to vote.
4.97/5 (31 votes)
8 Mar 2014CPOL5 min read 266.6K   8.2K   107   61
This library loads the DLL (from file/memory/etc) into an allocated memory block and takes care of its relocations and imports.

Introduction

Sooner or later, most developers start thinking about loading DLLs without LoadLibrary(). OK, maybe not most...

The technique presented here has only a few niche uses and can introduce a lot of inconvenience (depending on what your DLL does) compared to the WinAPI LoadLibrary(). I will explain the related problems below. Still, this library can be useful as a tutorial if you want to understand what's going on behind the curtains... I used it to implement some DLLs in C/C++ instead of coding offset-independent assembly (in an anti-cheat engine), but that is another story.

Implementation

The most important steps of loading a DLL:

  1. Mapping or loading the sections of the DLL into the memory of the host process.
  2. Relocating offsets in the loaded DLL using its relocation table (if present).
  3. Loading other DLLs that are imported by this DLL and resolving the pointers in its import table to point to the imported functions.
  4. Calling the DLL entrypoint (if present) with the DLL_PROCESS_ATTACH parameter.

I wrote the code to perform the basic steps listed above but then quickly found out that something was not right: my loaded DLL didn't have a valid HMODULE/HINSTANCE handle, and many windows functions expect you to specify one (for example, GetProcAddress(), CreateDialog(), and so on...). 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 - having a valid header at the pointed memory area isn't enough. This makes using manually loaded DLLs a bit harder.

I had to write my own GetProcAddress() because the windows version didn't work without a valid HMODULE. Later, I wanted to use dialog resources in the DLL, and CreateDialog() also requires a module handle. For this reason, I implemented a custom/manual FindResource() function that works with manually loaded DLLs and can be used to find dialog resources that can be passed to the CreateDialogIndirect() function. You can also use other types of resources in manually loaded DLLs if you find a related function (like CreateDialogIndirect()) that accepts a pointer to the raw resource data. In this tip, you get the code for the manual DLL loader and GetProcAddress(), but I post the resource related functions in another tip.

Limitations

  1. The loaded DLL doesn't have a valid HMODULE.
  2. The DllMain() doesn't receive DLL_THREAD_ATTACH and DLL_THREAD_DETACH notifications. You can simulate this by creating a small DLL that you load with normal LoadLibrary(). From the DllMain() of this normally loaded DLL you can call the entrypoint of your manually loaded DLLs in case of DLL_THREAD_ATTACH/DLL_THREAD_DETACH.
  3. If your DLL imports other DLLs, then the other DLLs are loaded with the WinAPI LoadLibrary(). This isn't a serious limitation, but it would be nice to be able to pass an optional custom LoadLibrary() callback to this manual DLL loader to be used for loading dependencies. You would still want to use the native LoadLibrary() call for most DLLs in a callback like that (especially for system DLLs like kernel32.dll) by making exceptions only for a few of your own DLLs.
  4. DLLs that make use of SEH *may* fail. The fact that the DLL contains SEH related code alone isn't a problem, but the __try blocks in the loaded DLL won't be able to catch the exceptions because the ntdll.dll!RtlIsValidHandler() doesn't accept exception handler routines from the memory area of our manually loaded DLL. This is a problem only if an exception is raised inside a __try block of the DLL because windows can't run the exception handler of the DLL and raises another exception that escapes the exception handler of the DLL - the result is usually a crash.
  5. Whether the CRT works with manual DLL loading or not depends on several factors, including the CRT version and the functions you call from it. If you are using only a few simple functions (like printf) then the CRT may work. I implemented my DLLs with /NODEFAULTLIB linker option (that reduces your DLL size considerably) so they can't use CRT functions. They have to rely on WinAPI! This can be quite inconvenient, but you can overcome it by writing your own mini CRT. I've provided one such mini CRT in my C++ example. It doesn't attempt to be comprehensive, but it allows you to use the most basic C++ features: automatically initialized static variables, new/delete operators. BTW, if you plan to use this code, then you should understand most of these issues and appreciate that writing C/C++ DLL without CRT is still much more convenient than creating an offset-independent or relocatable assembly patch.

Using the code

Implement your DLL in C/C++. If you don't want to use the default CRT, then link with /NODEFAULTLIB (this is the recommended approach to avoid problems with complicated/dysfunctional CRT functions). It's good practice to write your code so that a define can switch between WinAPI LoadLibrary() and the manual DLL loader. In my example solution, you can do this by switching to the "Debug LoadLibrary" configuration. This way, you can easily check whether a bug or crash is caused by manual DLL loading. An additional benefit of being able to switch to the WinAPI LoadLibrary() is that you can debug the code of the DLL easily in Visual Studio (as its debugger identifies code only inside normally loaded modules).

With this manual DLL loader, you have to use the 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 manual FindResource() function provided in one of my other tips (along with the CreateDialogIndirect WinAPI function), as it works with both normally and manually loaded DLLs: The inner working of FindResource() and LoadString() Win32 functions. Download the attached VC++2010 solution, which contains a sample program that loads and uses 2 DLLs: one written in C and the other in C++.

History:

  • Around 2000: The first spaghetti version of this code was born (Win32-only).
  • 2012 July: Publication on codeproject in its original form.
  • 2013 Sept: De-spaghettization of the code, adding 64-bit support and an example VC++2010 solution with DLL loading example written in C and C++. The C++ DLL contains a mini home grown CRT.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
SuggestionAll your Limitations can be overcome Pin
Elmue1-Jul-20 10:35
Elmue1-Jul-20 10:35 
You write several limitations in your article. But they are not really an obstacle.

For example you don't need a HMODULE to find a resource.
In your code you can use the Windows APIs
FindResource((HMODULE)ctx->image_base, .., ..);
LoadString((HMODULE)ctx->image_base, .., .., ..);
This works perfectly because Windows does the job to search the resource in the DLL image in memory.

You will never need LoadLibrary("Kernel32.dll") because this is first DLL which Windows loads into every process.
Just use GetModuleHandle("Kernel32.dll") instead.

Also loading CRT or other DLL's which exist in multiple versions on you disk in the SXS Side-by-Side folder is no problem. For example COMCTL32.dLL and MSVCRT80.DLL
Just add a small function which reads the manifest and creates an Activation Context. You can use the one which I posted here: Create Activation Context Implementation - GitHub

Also Data Execution Prevention is not an issue. It works perfectly with DEP enabled.

This project fancycode MemoryModule - Github works well, however read the bugfixes and pull requests which never have been merged.

Your code here is cleaner than the code on Github but some things are missing.

If the DLL uses a IMAGE_DIRECTORY_ENTRY_EXCEPTION (which is rarely used for SEH exceptions) this is already implemented on Github for x64 DLL Injection. But for 32 bit this is hairy. But there is a hack: strivexjun MemoryModulePP - Github
But very few DLLs make use of SafeSEH.

modified 5-Jul-20 1:49am.

GeneralRe: All your Limitations can be overcome Pin
pasztorpisti22-Jul-20 9:09
pasztorpisti22-Jul-20 9:09 
AnswerRe: All your Limitations can be overcome Pin
Elmue23-Jul-20 6:54
Elmue23-Jul-20 6:54 
Questiongreat work Pin
HANSBALD19-May-18 10:35
HANSBALD19-May-18 10:35 
GeneralMy vote of 5 Pin
Member 1342662224-Sep-17 20:26
Member 1342662224-Sep-17 20:26 
QuestionDLL_THREAD_ATTACH/DETACH Pin
HmilyAlex18-Dec-16 3:00
HmilyAlex18-Dec-16 3:00 
AnswerRe: DLL_THREAD_ATTACH/DETACH Pin
pasztorpisti13-Jan-17 9:37
pasztorpisti13-Jan-17 9:37 
QuestionNot valid hInstance. Pin
Member 1072186914-May-16 1:56
Member 1072186914-May-16 1:56 
AnswerRe: Not valid hInstance. Pin
pasztorpisti14-May-16 5:32
pasztorpisti14-May-16 5:32 
GeneralRe: Not valid hInstance. Pin
Member 1072186920-May-16 5:30
Member 1072186920-May-16 5:30 
GeneralRe: Not valid hInstance. Pin
pasztorpisti20-May-16 10:15
pasztorpisti20-May-16 10:15 
AnswerRe: Not valid hInstance. Pin
Elmue4-Jul-20 19:39
Elmue4-Jul-20 19:39 
QuestionWhat about LoadLibraryEx Pin
adc6821-Feb-16 11:54
adc6821-Feb-16 11:54 
AnswerRe: What about LoadLibraryEx Pin
pasztorpisti21-Feb-16 14:04
pasztorpisti21-Feb-16 14:04 
AnswerEasy: LoadLibraryEx Pin
Elmue4-Jul-20 19:41
Elmue4-Jul-20 19:41 
BugLoadLibraryA in LoadDLL_ResolveImports Pin
Member 1209747529-Oct-15 17:27
Member 1209747529-Oct-15 17:27 
GeneralRe: LoadLibraryA in LoadDLL_ResolveImports Pin
pasztorpisti9-Nov-15 3:12
pasztorpisti9-Nov-15 3:12 
AnswerRe: LoadLibraryA in LoadDLL_ResolveImports Pin
Elmue4-Jul-20 19:42
Elmue4-Jul-20 19:42 
QuestionDebug question Pin
Tsury23-Jun-14 2:47
Tsury23-Jun-14 2:47 
AnswerRe: Debug question Pin
pasztorpisti23-Jun-14 4:06
pasztorpisti23-Jun-14 4:06 
Questionfor getprocaddress etc to work Pin
evlncrn819-Apr-14 4:09
evlncrn819-Apr-14 4:09 
AnswerRe: for getprocaddress etc to work Pin
pasztorpisti19-Apr-14 5:37
pasztorpisti19-Apr-14 5:37 
AnswerRe: for getprocaddress etc to work Pin
Elmue4-Jul-20 19:45
Elmue4-Jul-20 19:45 
GeneralRe: for getprocaddress etc to work Pin
evlncrn84-Jul-20 23:01
evlncrn84-Jul-20 23:01 
AnswerRe: for getprocaddress etc to work Pin
Elmue5-Jul-20 3:37
Elmue5-Jul-20 3:37 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.