Published on 12 March 2025
Malware authors frequently employ a strategy of manually loading DLLs and dynamically linking functions at runtime to evade static analysis. This approach bypasses the traditional import table, making it more challenging for analysts to pinpoint which API calls are being used.
In this post, I begin by examining the various loading and linking strategies available in Windows development, covering header files, DLLs, and import libraries, to establish a clear understanding of how these mechanisms work. I then explore how malware utilizes these techniques to obscure its behavior and hinder detection efforts.
Linking in Windows determines how an application accesses external functionality from libraries (DLLs) and can be divided into two main approaches: implicit (load-time) linking and explicit (run-time) linking. The linking process involves:
Header files (*.h) that declare functions and data types .
Import libraries (*.lib) that help the linker resolve references to functions in DLLs.
Dynamic-Link Libraries (*.dll) that contain the actual compiled code.
Header files provide the function prototypes, macros, and data structures that the code needs at compile time acting as a contract between your code and the library’s implementation. They don’t include actual executable code, just declarations that inform the compiler of the function signatures, calling conventions, and data types.
Import libraries serve as a bridge between an application and its DLLs during the linking phase. Rather than containing executable code, these files provide stubs and metadata that the linker uses to build the import table in the executable.
DLLs are compiled binaries that encapsulate reusable code and resources. They allow multiple programs to share common functions, which are loaded into memory at runtime. Thanks to mechanisms like ASLR, the actual memory locations of DLL functions are determined dynamically.
Implicit linking unfolds through three phases: compilation, linking, and runtime.
Compilation Phase:
Header Files (*.h): Provide function prototypes, data type definitions, and macros.
Import Libraries (*.lib): Instead of containing executable code, import libraries supply stubs and metadata that the linker uses to set up the import table in the executable. This table includes the Import Name Table (INT), which lists the names (or ordinals) of the functions your application imports.
Linking Phase: The linker uses the information from import libraries to set up references in the executable’s import table.
Runtime: When the application starts, the Windows loader examines the import table, loads the required DLLs, and uses the entries (from the INT) to locate the actual function addresses. The loader then populates the Import Address Table (IAT) with these addresses, ensuring the functions can be called directly from the executable.
Explicit linking, in contrast, is a process where the application itself manages DLL loading and function resolution at runtime.
Manual DLL Loading: Instead of relying on an import library, the application calls functions such as LoadLibrary (or LoadLibraryEx) to load the DLL at runtime.
Function Resolution: Once the DLL is loaded, the application calls GetProcAddress to retrieve the address of a desired function.
This approach bypasses the need for an import library (.lib) entirely, giving the application full control over which libraries and functions to load and when.
There's a third approach known as delay loading. It is essentially a hybrid of implicit and explicit linking where the DLL dependency is set up as in implicit linking, but the actual loading of the DLL is postponed until the first time one of its functions is invoked.
It reuses the typical implicit linking structure - header files for declarations and import libraries for referencing - but includes special “delay-load” stubs. These stubs instruct the linker to configure the executable so that the DLL is not loaded at startup. Instead, when the application first calls a function from the delay-loaded DLL, the loader uses those stubs to dynamically load the DLL on demand.
Delaying DLL loading until it's needed can improve startup performance.
Malware often favors explicit linking, which involves manually loading DLLs and resolving function addresses using LoadLibrary and GetProcAddress. This technique is popular because it helps malware evade static analysis by avoiding clear, pre-populated import tables.
In such cases, the executable is built without import library references, leaving the INT and IAT either missing or minimally populated, since no compile-time binding to DLL functions occurs. Instead, DLLs are loaded dynamically at runtime and addresses are resolved on the fly.
This dynamic approach is essential for evasion since it reveals little information about the APIs that the malware will eventually use; however, an executable with no DLL references or an empty import table immediately raises concerns, as such sparsity is highly suspicious.
To show practically the concept, I built a simple example that demonstrates both implicit and explicit linking in Windows. If you'd like to analyze the example yourself, a zip archive containing all the source files and build instructions (README.txt) is available for download:
Download Complete Example Zip.
A DLL was created to export a function, serving as the common module for both methods. Then, two separate executables were developed:
Implicit Linking Example: The executable "implicit_example.exe" was compiled using standard header files and an import library. During the build process, the linker incorporates the DLL dependency into the executable's import table so that at runtime, the operating system automatically loads the DLL and resolves function addresses via the Import Address Table (IAT).
The screenshots below, captured with Pestudio, shows the populated import table with references to the DLL and its functions, clearly outlining the libraries and imports:
Explicit Linking Example: In this case, the executable "explicit_example.exe" does not use an import library. Instead, it dynamically loads the DLL at runtime using LoadLibraryA and GetProcAddress. This method results in an import table that lacks a direct reference to the DLL, effectively obscuring the dependency. The program prints a message indicating that explicit linking is employed.
The screenshot below, captured with Pestudio, illustrates the minimal import table, with only standard Windows API functions (such as LoadLibraryA and GetProcAddress) visible, highlighting the absence of a direct DLL reference:
Through the initial analysis of Windows linking mechanisms and the demonstration examples, it becomes clear why many malware authors prefer dynamic linking. By resolving function addresses at runtime instead of relying on a static import table, they can obscure their actual dependencies and evade straightforward static analysis.
Additionally, some malware authors combine dynamic linking with obfuscation, encrypting or encoding the DLL and function names so they’re only revealed at runtime. This makes it even harder for a disassembler or static analysis tool to detect the libraries in use, since the usual strings for functions like LoadLibraryA or GetProcAddress might not appear in the code.
We’ll explore these advanced obfuscation strategies in a future discussion here in the Cyber Corner!