- Llambduh's Newsletter
- Posts
- WTF Are DLL Files?
WTF Are DLL Files?
A Technical and Historical Overview of Dynamic-Link Library (DLL) Files
What is a DLL?
A Dynamic Link Library (DLL) is a shared library in Microsoft Windows (and OS/2) operating systems. DLLs contain executable code (functions), data, and resources such as icons, strings, or dialogs. They provide modular functionality so that multiple programs can reuse or share code and resources. DLLs foster code reuse, plug in architectures, and ease software maintenance.
Origin of DLL Files
The concept of the Dynamic Link Library was introduced by Microsoft in 1987 with the release of Windows 2.0. Although early forms of shared library mechanisms existed on other platforms (such as UNIX’s so files), Microsoft’s DLL implementation was unique and purpose built for the Windows graphical operating system.
Year Introduced: 1987
Creators: Microsoft Windows Development Team
Primary Contributors: Gordon Letwin (Lead Architect for early Windows), together with the core Windows engineering team at Microsoft.
Why were DLLs created?
Memory Constraints: Early personal computers had extremely limited memory (often less than 1 MB of RAM). The Windows team needed a method for multiple programs to share common code and data efficiently, avoiding redundant in memory copies.
Modularity and Maintainability: Rather than statically linking all library code into every application (which increases size/memory footprint and complicates updates), DLLs let code be modular and updatable independently of applications.
Device Independence: Windows was designed to interface with many kinds of hardware, and DLLs (including those for device drivers) allowed dynamic loading of just the code required for particular hardware configurations.
Support Rapid GUI Evolution: The Windows API, including critical libraries for graphics (GDI), user interface (USER), and kernel functions, was architected around DLLs to allow Microsoft to update and extend the ability of Windows without requiring total system rewrites or recompiles for third party apps.
In Windows 1.0 (released in 1985), Microsoft began exploring modular architectures, but it was really with Windows 2.0 (1987) that DLLs as we know them were formalized and became central to Windows’ architecture. The GDI.EXE and USER.EXE in early Windows were actually DLLs under the hood, even if that was not always clear to external developers. By Windows 3.x (1990), DLLs were fundamental for third party developers, as documented in the Windows SDK.
File Extensions and Resource DLLs
A DLL file commonly uses the .dll extension, but this is not strictly required. Other common extensions include .ocx for ActiveX controls and .drv for device drivers. A resource only DLL containing icons, fonts, or other resources may bear .icl, .fon, or .fot extensions.
File Extensions:
.dll: General purpose dynamic libraries
.ocx: ActiveX controls
.drv: Device drivers (legacy, 16 bit)
.icl: Icon libraries
.fon, .fot: Font resources
Note: While .dll is standard, the extension alone does not mandate file usage; it's primarily for convention and clarity.
File Format and Execution
Structurally, DLL files use the Portable Executable (PE) format the same as for Windows executables (.exe). The difference is semantic: a DLL must be loaded by another process; it cannot be launched directly, as it lacks a required initial entry point for the OS to start execution. However, Windows offers utilities like RUNDLL.EXE and RUNDLL32.EXE to invoke exported functions from DLLs. Given the shared file structure, an .exe file can be loaded as a DLL under certain circumstances.
PE/COFF Structure Breakdown:
DOS/PE headers
Section Table (.text, .data, .rdata, .rsrc, etc.)
Export Table: Exposes functions/data for use by outside programs.
Import Table: Details dependencies on other DLLs.
Resource Table: Embedded icons, dialogs, bitmaps, version info, strings.
Relocation Table: For address rebasing if not loaded at preferred addresses.
Background and Evolution
Early Windows
The first versions of Microsoft Windows ran all programs within a single address space. Every program needed to cooperate (yielding CPU time) to permit basic GUI multitasking and responsiveness. All operating system level operations called into MS DOS, while Windows specific higher level services were implemented via DLLs the "Dynamic Link Library" mechanism. For instance:
GDI.EXE: Graphics Device Interface, the drawing API, loaded device drivers (.DRV files) to work with different hardware, translating high level drawing requests to hardware level commands.
USER.EXE: Provided user interface services.
This architecture meant code (like drawing logic or device drivers) could be reused and swapped dynamically. This not only allowed Windows to run on very limited hardware, but also made replacing and extending the OS possible through third party modules.
Dynamic Linking Compared to Static Linking
In static libraries, code gets copied into each executable during the build process. Multiple programs each get their own copy of library routines in their binaries. In contrast, under dynamic linking, code is placed into a single separate file (the DLL), which is loaded as needed at runtime by applications. The operating system resolves these links dynamically.
In early Windows (1.x, 2.x, 3.x), all Windows applications and all DLLs ran in the same address space and shared the same memory. DLLs were loaded once and shared by all consumers, including their data enabling both intentional and accidental inter process communication or data corruption.
With Windows 95 and later introducing 32 bit libraries and process isolation, each process received its own virtual address space. DLL code could still be shared (if loaded at the same base address), but data became private by default unless explicit shared sections were defined. Large portions, though, of the consumer market’s operating systems (Windows 95/98/Me) continued to use 16 bit components, hurting stability and making complete process isolation difficult.
DLL Benefits & Features
Modularity: Updates or patches can be applied independently for a DLL, and all applications using that DLL can benefit instantly provided backward compatibility is preserved.
Upgradability: Even system components (DLLs) can be replaced to upgrade substantial OS/subsystem functionality for all programs on the next run.
Plug in/Extensibility: A generic interface allows new modules to be loaded at runtime, providing plug in or extensibility architectures. ActiveX and COM technologies (like shell extensions or scripting objects) are rooted in this model.
Resource Sharing: Enables the OS and applications to share resources (e.g., bitmaps, icons) through resource DLLs, facilitating both system consistency and multi language/localization support.
Linking and Loading DLLs
Load Time Linking (Static/Dynamic Linking in Practice)
When you build an executable against a DLL, the linker uses an import library (.lib) which contains stubs mapping imports to the export table of the DLL. The resulting executable contains an Import Address Table (IAT); at runtime, Windows will resolve IAT entries when loading the process, so all function pointers refer directly to the code in the relevant DLL.
Runtime Dynamic Linking
Applications can load DLLs manually using Windows API functions such as LoadLibrary or LoadLibraryEx, then look up function addresses using GetProcAddress. The application can unload the DLL using FreeLibrary. This approach is crucial for plug in systems and optional features.
Delayed Loading
The linker can create a stub that defers loading a DLL until a symbol is actually called the first time. If the DLL is unavailable at that time, an exception is generated either handled by the application or allowed to terminate the process.
Symbol Resolution and Export/Import
Each function or variable exported from a DLL is identified by a name (ASCII string, possibly name mangled) and/or a numeric ordinal (export order index). Import libraries reference these export entries; the correspondence is established at link time for load time dynamic linking, or at runtime with explicit loading.
Export by ordinal: Slightly faster than by name (just indexed lookup), but fragile across DLL versions.
Export by name: Stable across versions (unless the API is removed or renamed).
Binding Imports (pre resolving at compile/install time) can speed up process loading, but isn’t effective when DLLs are built as address randomized (ASLR) for modern security.
Memory Layout and Relocation
PE files (DLLs) are broken into sections (code, read only data, modifiable data, resources) and each section has attributes (read, write, execute, shared, etc.).
Code sections: Shared among all processes when loaded at the same base address, otherwise relocated and result in additional private copies.
Data sections: Private by default to each process; can be explicitly shared (rare and potentially insecure).
Windows does not require position independent code for DLLs relocation is performed at load time, patching absolute addresses.
Compression by packers (e.g. UPX) makes code sections writable, thus defeating sharing and causing increased memory usage, as each process gets its own copy.
Limitations and Security
DLL Hell
Problems (collectively known as DLL Hell) arise when:
Applications depend on different versions of the same DLL.
Installing/updating software overwrites a DLL, breaking other apps.
Mitigations include:
Side by side assemblies (SxS), manifest files, the .NET Framework GAC, and virtualization (App V, etc).
DLLs run in the address space and with the privileges of their caller. There’s virtually no protection if a DLL misbehaves bugs or malicious acts in DLLs can crash or compromise the host process. Shared writable data between processes can be a security hole; one process could overwrite data used by a privileged process.
DLL Hijacking and Preloading
A critical vulnerability called DLL hijacking or preloading occurs when an attacker places a malicious DLL where a program will load it first (usually due to weak search order and unguarded directory scanning). Modern mitigations involve:
Using SetDefaultDllDirectories() and SetDllDirectory() to control DLL search paths properly (excluding the current working directory/application directory, etc.)
Code Isolation and Upgradability
While the code section is “shared,” no memory protection is provided for calls into DLL functions. Upgrading a DLL is straightforward (delete/replace), but dangerous if backward compatibility is not maintained, leading to unpredictable crashes or data corruption.
DLLs in Programming Languages
C /C++ (MSVC, MinGW, etc.)
Functions imported/exported using declspec(dllexport)/declspec(dllimport) or via .def files.
Name mangling in C++ means C functions should be marked as extern "C".
DLL and accompanying import library (.lib under MSVC, .dll.a under MinGW) produced during compilation.
// Compile time linking
extern "C" __declspec(dllimport) double AddNumbers(double a, double b);
// Runtime dynamic linking
typedef double (*importFunction)(double, double);
HINSTANCE hinstLib = LoadLibrary(TEXT("Example.dll"));
importFunction addNumbers = (importFunction) GetProcAddress(hinstLib, "AddNumbers");
double result = addNumbers(1, 3);
FreeLibrary(hinstLib);
Python
Uses ctypes to load and call Windows DLL functions (using POSIX APIs like dlopen/dlsym/dlclose on non Windows systems).
import ctypes
my_dll = ctypes.cdll.LoadLibrary("Example.dll")
my_dll.AddNumbers.restype = ctypes.c_double
p = my_dll.AddNumbers(1.0, 2.0)
Component Object Model (COM) and DLLs
COM is a binary standard for hosting reusable components (objects) in DLLs (and EXEs).
COM classes are loaded and created through GUIDs and registry references.
DLL based COM servers are lighter weight and support resource sharing with the client process.
Cross Platform Considerations
Unix/Linux: .so (shared object) libraries, loaded by dlopen.
macOS: .dylib dynamic libraries.
While concepts are similar, DLLs are Windows/OS2 specific and not binary compatible with their Unix counterparts.
Tools and Analysis
Dependency Walker (depends.exe): For inspecting imports and exports.
dumpbin: Command line PE file inspector.
PE Explorer, Ghidra, IDA Pro: For deeper binary analysis or reverse engineering.
Summary Table
Aspect | Details |
---|---|
OS Support | Windows, OS/2 (not Unix compatible, but see .so/.dylib for other platforms) |
File Format | Portable Executable (PE), same format as EXE |
Extensions | .dll (general), .ocx (ActiveX), .drv (device driver), .icl (icon), .fon/.fot (font resources) |
Export/Import | By name (stable) or ordinal (faster but brittle); via .def files or __declspec attributes |
Linking | Load time (import libs), runtime (LoadLibrary), or delayed (on demand) |
Memory Sharing | Code shared (if same address), data private unless marked shared |
Security | Vulnerable to hijacking/preloading; must secure DLL search order and control access |
Application | System level (OS API), apps, plugins, resource libraries, COM servers |
Language | Supported across C/C++, Delphi, VB, Python, and more |
Analysis Tools | depends.exe, dumpbin, PE Explorer, IDA Pro, Ghidra |
Cross Platform | See .so (Linux) and .dylib (macOS) for analogous but incompatible mechanisms |
References: