<100 subscribers


DLL Injection is an exploit technique that can be used for anything from harmless game mods to sophisticated malware campaigns. At its core, DLL Injection is about convincing a running process to execute code it never intended to run - whether that's adding custom features to your favorite game, or establishing persistence in a compromised system.
As part of my 100 day Infosec challenge, I decided I would spend today diving deeper into DLL injection. I wanted to understand how this technique works under the hood. Here are some of my key takeaways.
Before we can understand what DLL injection is and how it works, we need to know what a DLL is!
A DLL (Dynamic Link Library) is a Windows binary in the Portable Executable (PE) format. Every PE file contains several important components which we will touch on briefly:
Sections organize the file into logical chunks: .text holds executable code, .data stores initialized global variables, .rdata contains read-only data like string literals, and .reloc contains relocation information for address adjustments.
Import Address Table (IAT): Lists the functions that the DLL depends on from other DLLs. During loading, the Windows loader resolves these symbolic names and writes function pointers into the IAT.
Export Table: Lists the functions that the DLL exposes to other programs (its public API).
Image Base: The preferred virtual memory address where the loader should map the DLL. The loader tries to honor it, but if that memory space is occupied, it has to relocate.
Base Relocations: Adjustment instructions used when the DLL can't load at its preferred address (Image Base). Every absolute address reference in the code needs to be patched with the offset between the preferred and actual load addresses.
DllMain: An optional entry point called by the loader during process/thread attach/detach events. It allows the DLL to execute initialization code.
For the sake of this article we’ll use the term “Windows Loader” to describe the cooperation between user-mode components (like ntdll.dll) and kernel-mode components (like the PE loader in ntoskrnl.exe) that make loading DLLs possible. So when we say “the loader” just know that it’s a system of multiple components working together, not a single program.
When a process needs a DLL, the Windows loader performs a carefully choreographed sequence:
Map the PE file from disk into the process's virtual address space.
Apply relocations if needed (adjust addresses if not at preferred base).
Walk the import table and resolve all external function references.
Execute Thread Local Storage (TLS) callbacks if present.
Call DllMain with DLL_PROCESS_ATTACH to allow the DLL to initialize.
The DLL is now ready for use by the process.
The key idea behind DLL injection is to subvert or otherwise manipulate this loading sequence to execute arbitrary code inside the target process.
So DLL injection is any technique that allows arbitrary code execution inside another process’s address space without that process’s explicit cooperation. It’s the difference between inviting someone into your home versus them finding a way in through the basement window.
The traditional approach to achieving DLL injection follows this pattern:
Get a handle to the target process (OpenProcess with appropriate privileges)
Allocate memory in the target’s address space (VirtualAllocEx)
Write the DLL path into that memory (WriteProcessMemory)
Create a remote thread that calls LoadLibrary on that path (CreateRemoteThread)
LoadLibrary does all the heavy lifting. The normal Windows loader takes over and performs all the steps we described earlier. The catch with this approach though is that it’s noisy. Modern Endpoint Detection and Response (EDR) solutions watch for exactly this pattern: OpenProcess → VirtualAllocEx → WriteProcessMemory → CreateRemoteThread is practically a neon sign saying “malicious activity over here”.
The weakness in the classic injection method above (the ease in which it can be detected) is due to the traces left behind by the loading process. Reflective DLL injection, on the other hand, contains its own custom loader code, allowing it to better evade EDR and forensics.
This is how Meterpreter works for example.
Because the reflective DLL contains its own loader code, it can bypass standard security mechanisms like DLL load logging, and leave a minimal forensic footprint. Because it bypasses LoadLibrary and the Windows loader, a reflective DLL must include a special function (the “reflective loader”) that manually implements everything the OS loader would normally do (Steps 1-6 of the DLL loading process).
The DLL never exists as a file on disk! The stager receives it over the network, writes it directly into memory, and jumps to the reflective loader. No file creation events, no scanning by AV, no forensic artifacts. You never call LoadLibrary-which is heavily monitored. The only suspicious calls are the initial VirtualAlloc/WriteProcessMemoryto get the image into memory.
Metasploit’s Meterpreter payload is a great example of reflective DLL injection in practice. When you get a Meterpreter shell:
The initial stager (tiny piece of shellcode) establishes a connection.
The full Meterpreter DLL is downloaded from the attacking machine.
The stager allocates memory and writes the reflective DLL.
Execution transfers to the reflective loader stub.
The loader performs all the PE loading tasks manually.
DllMain executes and Meterpreter establishes its full C2 channel.
The entire payload lives in memory. If the compromised system reboots, Meterpreter disappears (unless persistence mechanisms are used). This makes incident response and forensics significantly harder -- there’s no malicious binary to find on disk, and memory analysis requires knowing what to look for.
I often get confused about related but distinct DDL exploit techniques that are not considered injection attacks. I’ll attempt to make distinctions between them here.
Disk-based DLL loading, for example, involves placing a malicious DLL where a process will naturally load it. Two methods of doing this are:
DLL search-order hijacking: Exploits the DLL search path when calling LoadLibrary to load a malicious DLL instead of the legitimate one
DLL sideloading: placing a malicious DLL with the right name next to a legitimate executable that will load it. The following article goes more in-depth on this*:* Bitdefender - What is DLL Sideloading.
Process replacement techniques like process hollowing or process doppelgänging create what looks like a benign process but replace its code entirely. These are all related but distinct from DLL injection; the main distinction is that they’re not adding code to a running process.
They key difference between DLL injection and process doppelgänging, for example, is that DLL injection involves forcefully loading a malicious DLL into an already running legitimate process, while process doppelgänging involves creating a new, hidden, malicious process that masquerades as a legitimate one using file transactions.
DLL injection is a fundamental technique in offensive security. From dropping a Meterpreter shell in a CTF to advanced persistent threats, the ability to run code in another process’s context remains valuable across the threat spectrum. Reflective DLL injection specifically demonstrates how adversaries evolve techniques to evade detection -- by removing disk artifacts and implementing their own custom loaders, they operate in the gaps of traditional security mechanisms.
For security practitioners, understanding these techniques isn’t only about learning to attack systems, but about comprehending how modern malware operates so we can build better defenses, hunt more effectively, and respond to incidents with full knowledge of what adversaries are capable of.
MITRE ATT&CK Process Injection: Dynamic-link Library Injection
Cobalt Strike Delivering Custom Payloads With Metasploit Using DLL Injection
DLL Injection is an exploit technique that can be used for anything from harmless game mods to sophisticated malware campaigns. At its core, DLL Injection is about convincing a running process to execute code it never intended to run - whether that's adding custom features to your favorite game, or establishing persistence in a compromised system.
As part of my 100 day Infosec challenge, I decided I would spend today diving deeper into DLL injection. I wanted to understand how this technique works under the hood. Here are some of my key takeaways.
Before we can understand what DLL injection is and how it works, we need to know what a DLL is!
A DLL (Dynamic Link Library) is a Windows binary in the Portable Executable (PE) format. Every PE file contains several important components which we will touch on briefly:
Sections organize the file into logical chunks: .text holds executable code, .data stores initialized global variables, .rdata contains read-only data like string literals, and .reloc contains relocation information for address adjustments.
Import Address Table (IAT): Lists the functions that the DLL depends on from other DLLs. During loading, the Windows loader resolves these symbolic names and writes function pointers into the IAT.
Export Table: Lists the functions that the DLL exposes to other programs (its public API).
Image Base: The preferred virtual memory address where the loader should map the DLL. The loader tries to honor it, but if that memory space is occupied, it has to relocate.
Base Relocations: Adjustment instructions used when the DLL can't load at its preferred address (Image Base). Every absolute address reference in the code needs to be patched with the offset between the preferred and actual load addresses.
DllMain: An optional entry point called by the loader during process/thread attach/detach events. It allows the DLL to execute initialization code.
For the sake of this article we’ll use the term “Windows Loader” to describe the cooperation between user-mode components (like ntdll.dll) and kernel-mode components (like the PE loader in ntoskrnl.exe) that make loading DLLs possible. So when we say “the loader” just know that it’s a system of multiple components working together, not a single program.
When a process needs a DLL, the Windows loader performs a carefully choreographed sequence:
Map the PE file from disk into the process's virtual address space.
Apply relocations if needed (adjust addresses if not at preferred base).
Walk the import table and resolve all external function references.
Execute Thread Local Storage (TLS) callbacks if present.
Call DllMain with DLL_PROCESS_ATTACH to allow the DLL to initialize.
The DLL is now ready for use by the process.
The key idea behind DLL injection is to subvert or otherwise manipulate this loading sequence to execute arbitrary code inside the target process.
So DLL injection is any technique that allows arbitrary code execution inside another process’s address space without that process’s explicit cooperation. It’s the difference between inviting someone into your home versus them finding a way in through the basement window.
The traditional approach to achieving DLL injection follows this pattern:
Get a handle to the target process (OpenProcess with appropriate privileges)
Allocate memory in the target’s address space (VirtualAllocEx)
Write the DLL path into that memory (WriteProcessMemory)
Create a remote thread that calls LoadLibrary on that path (CreateRemoteThread)
LoadLibrary does all the heavy lifting. The normal Windows loader takes over and performs all the steps we described earlier. The catch with this approach though is that it’s noisy. Modern Endpoint Detection and Response (EDR) solutions watch for exactly this pattern: OpenProcess → VirtualAllocEx → WriteProcessMemory → CreateRemoteThread is practically a neon sign saying “malicious activity over here”.
The weakness in the classic injection method above (the ease in which it can be detected) is due to the traces left behind by the loading process. Reflective DLL injection, on the other hand, contains its own custom loader code, allowing it to better evade EDR and forensics.
This is how Meterpreter works for example.
Because the reflective DLL contains its own loader code, it can bypass standard security mechanisms like DLL load logging, and leave a minimal forensic footprint. Because it bypasses LoadLibrary and the Windows loader, a reflective DLL must include a special function (the “reflective loader”) that manually implements everything the OS loader would normally do (Steps 1-6 of the DLL loading process).
The DLL never exists as a file on disk! The stager receives it over the network, writes it directly into memory, and jumps to the reflective loader. No file creation events, no scanning by AV, no forensic artifacts. You never call LoadLibrary-which is heavily monitored. The only suspicious calls are the initial VirtualAlloc/WriteProcessMemoryto get the image into memory.
Metasploit’s Meterpreter payload is a great example of reflective DLL injection in practice. When you get a Meterpreter shell:
The initial stager (tiny piece of shellcode) establishes a connection.
The full Meterpreter DLL is downloaded from the attacking machine.
The stager allocates memory and writes the reflective DLL.
Execution transfers to the reflective loader stub.
The loader performs all the PE loading tasks manually.
DllMain executes and Meterpreter establishes its full C2 channel.
The entire payload lives in memory. If the compromised system reboots, Meterpreter disappears (unless persistence mechanisms are used). This makes incident response and forensics significantly harder -- there’s no malicious binary to find on disk, and memory analysis requires knowing what to look for.
I often get confused about related but distinct DDL exploit techniques that are not considered injection attacks. I’ll attempt to make distinctions between them here.
Disk-based DLL loading, for example, involves placing a malicious DLL where a process will naturally load it. Two methods of doing this are:
DLL search-order hijacking: Exploits the DLL search path when calling LoadLibrary to load a malicious DLL instead of the legitimate one
DLL sideloading: placing a malicious DLL with the right name next to a legitimate executable that will load it. The following article goes more in-depth on this*:* Bitdefender - What is DLL Sideloading.
Process replacement techniques like process hollowing or process doppelgänging create what looks like a benign process but replace its code entirely. These are all related but distinct from DLL injection; the main distinction is that they’re not adding code to a running process.
They key difference between DLL injection and process doppelgänging, for example, is that DLL injection involves forcefully loading a malicious DLL into an already running legitimate process, while process doppelgänging involves creating a new, hidden, malicious process that masquerades as a legitimate one using file transactions.
DLL injection is a fundamental technique in offensive security. From dropping a Meterpreter shell in a CTF to advanced persistent threats, the ability to run code in another process’s context remains valuable across the threat spectrum. Reflective DLL injection specifically demonstrates how adversaries evolve techniques to evade detection -- by removing disk artifacts and implementing their own custom loaders, they operate in the gaps of traditional security mechanisms.
For security practitioners, understanding these techniques isn’t only about learning to attack systems, but about comprehending how modern malware operates so we can build better defenses, hunt more effectively, and respond to incidents with full knowledge of what adversaries are capable of.
MITRE ATT&CK Process Injection: Dynamic-link Library Injection
Cobalt Strike Delivering Custom Payloads With Metasploit Using DLL Injection
Share Dialog
Share Dialog
No comments yet