Dynamic Link Library
Intro to DLL code structure and concepts
Dynamic Link Library
DLL (Dynamic Link Library) is one of the most used PE (Portable Executable) file formats in Windows. as the name suggests, DLLs are linked during or before application run-time ( depending on execution flow and compilation options). the main advantage of DLL is code reuse, dependency and backward compatibility and maintainability. DLLs can be compiled once and used many times between many different applications that want to use the same capabilities (same functions and sub-routines). you can update DLL functionality without recompiling the dependent programs.
DLL Code Structure
A simple DLL code looks like this:
To write a DLL in VisualStudio, create a new project and select the (DLL) project type:
After creating the project, you will see two source files in the right hand side (solution explorer):
dllamin.cpp
is the main dll source file.pch.cpp
is the "Pre-compiled Header" file.
Precompiled Header
Is a header file that includes other commonly used header files that don’t change frequently (such as standard libraries or system headers). The purpose of precompiled headers is to speed up the build process. Compilers often take a significant amount of time compiling the same headers across multiple source files. By creating a precompiled header, the compiler only needs to process the included headers once and can reuse the precompiled version in subsequent compilations.
Large projects typically include many common header files (like windows.h
, iostream
, etc.). Without pre-compilation, every time a source file is compiled, these headers would be processed again, which is inefficient.
By using precompiled headers (in pch.h
), Visual Studio compiles these headers only once, and then stores the compiled version. When compiling other source files, the compiler simply uses the precompiled version, reducing compilation time significantly.
As you can in the image bellow, the pch.h
header file is including another header called "framework.h"
:
The framework.h
file is importing the standard Windows API header file:
DLL Attach and Detach Events
When a DLL is loaded into or unloaded from a process or thread, the system notifies the DLL through the DllMain
function. These events are signaled by the values of the ul_reason_for_call
parameter passed to DllMain
.
DLL_PROCESS_ATTACH : This happens when the DLL is loaded into the memory space of a process. This usually occurs when:
A process starts and the DLL is statically linked.
A process explicitly loads the DLL using
LoadLibrary
.
The DllMain
function is called once per process, so we should avoid performing lengthy operations here as it can delay the process startup.
DLL_PROCESS_DETACH : This happens when the DLL is unloaded from a process. This can occur when:
The process terminates.
The DLL is explicitly unloaded using
FreeLibrary
.
This is the last chance to clean up before the DLL is removed from the process memory space, so we should ensure that all dynamically allocated resources are freed to avoid memory leaks.
DLL_THREAD_ATTACH : This happens when a new thread is created in the process that uses the DLL. this event occurs only if the thread is created after the DLL is loaded.
DLL_THREAD_DETACH : This happens when a thread exits cleanly within a process that uses the DLL.
DLL Execution
There are two (practical) ways to load and execute DLLs:
Loading the DLL into a local or remote running process (e.g., loading or injecting)
Calling and executing the exported function using tools like
rundll32.exe
To keep things simple, in this example we will use the second way, compile the DLL and use rundll32.exe
to execute the entry point. the entry point for our simple DLL is HelloWorld
so we run it and call the function:
You can't pass custom parameters to DllMain()
. The signature is fixed, and you don't call DllMain()
directly anyway, only the OS does.
Your options are to either:
have the DLL export a separate function that you call after injecting/loading the DLL into a process.
store the data in a block of shared memory that the DLL can access after being injected/loaded.
setup an inter-process communication channel between the DLL and injector/loader, such as with a named pipe or a socket.
Explicit and Implicit Linking
There are two ways to link a DLL to a program:
Implicit Linking
An executable (or another DLL) links with a small LIB file containing DLL name (no path) and exported symbols. at runtime, the loaders job is to locate the DLL and bind to the real functions implemented. if the dll is not found, the process terminates.
Explicit Linking
A process calls LoadLibrary(Ex) to load the DLL from disk when the process is already running a full path can be specified. if an error occurs a NULL handle is returned (process is not terminated). actual functions addresses are retrieved with GetProcAddress API.
Explicit linking is usually the best choice because we can load the library whenever we want. in malware development, this gives us the advantage to avoid some static analysis and detection mechanisms such as IAT (Import Address Table) analysis which will reveal the name of the functions/libraries that we are using in the code.
Dynamic Loading
So how can we dynamically (and manually) load a DLL into our program?
There are 2 Windows API functions that are heavily used when dealing with DLLs:
LoadLibraryA
: loads a DLL from disk. takes a ASCII DLL name string (or path) as parameterGetProcAddress
: finds the address of DLL's exported function (ASCII string). takes a handle to DLL and a function name to search for as parameter.
There is another API function called GetModuleHandle
that is used when importing DLLs that export the standard Windows API (like ntdll.dll
or kernerl32.dll
) or other DLLs that are universally loaded in the system memory. this will be discussed in later sections when we go through the process of function call obfuscation.
In this case, we just want to load our custom DLL that was compiled and written to disk, so we use LoadLibraryA
instead of GetModuleHandle
.
Here is a simple code that will load the hello-dll
DLL from disk and execute the HelloWorld
function:
By default, LoadLibraryA
will look for DLLs in following order:
Known system DLLs (e.g.,
kernel32.dll
)Directory of the executable (current program path)
The system directory (
GetSystemDirectory
, e.g.C:\Windows\System32
)
Default DLL Search Paths
When a Windows program attempts to load a DLL, it searches for the DLL in a series of predefined locations, which are referred to as DLL search paths. The order in which these locations are searched is important to understand, as it determines where Windows looks for the required DLLs when you call LoadLibrary
or similar functions.
Default DLL Search Order (Windows 10/11)
The default search order for a DLL in Windows is as follows:
Current Directory (Application Directory): When you call
LoadLibrary
without specifying a full path, Windows will first look for the DLL in the directory from which the executable was launched. If the DLL is in the same directory as the application, Windows will find it here first.System Directory (
C:\Windows\System32
): Windows will then look in the system directory, which is typicallyC:\Windows\System32
for 64-bit Windows systems. For 32-bit applications on a 64-bit Windows system, this will beC:\Windows\SysWow64
.Windows Directory (
C:\Windows
): After the system directory, Windows searches theC:\Windows
directory itself.PATH Environment Variable: Windows will then search all directories listed in the PATH environment variable, which may include custom paths set by the user or installed programs. This is the final location where Windows will look.
Modifying the Search Order
Current Directory:
The current directory search behavior can be modified in a few ways:
By changing the working directory before calling
LoadLibrary
usingSetCurrentDirectory
.By providing an absolute or relative path in the
LoadLibrary
call (e.g.,LoadLibrary("C:\\path\\to\\mydll.dll")
).
Environment Variable PATH:
You can modify the PATH environment variable to include additional directories where your DLLs might be located.
This can be done via the System Properties (
Control Panel → System → Advanced System Settings → Environment Variables
) or by modifying it in your program.
Explicit Path in
LoadLibrary
:Always specify the full path of the DLL if you want to ensure the correct version is loaded:
Set DLL Search Order with
SetDllDirectory
:You can use the
SetDllDirectory
function to add a specific directory to the search order at runtime:
Last updated
Was this helpful?