DLL injection and DLL hijacking: risks and detection

Last update: December 29, 2025
  • DLL injection and hijacking exploit the search order and library loading in Windows to execute malicious code within legitimate processes.
  • Process injection techniques (remote DLL, direct code, APC, hollow process, reflective DLL) leverage APIs such as OpenProcess, VirtualAllocEx, and CreateRemoteThread.
  • The detection combines memory analysis, API call monitoring, and DLL load control using EDR, digital signatures, and tools like Process Monitor.
  • Prevention involves secure coding, use of absolute paths, policies such as AppLocker/WDAC, and memory mitigations such as DEP, ASLR, and CFG.

dll injection risks

When we talk about DLL injection and DLL hijacking in Windows , we're not just dealing with a technical curiosity, but rather one of attackers' favorite ways to infiltrate systems, steal data, and operate with elevated privileges without raising suspicion. It's a family of veteran techniques, but they remain highly effective because they exploit something as fundamental as how Windows loads libraries into memory.

If you've ever wondered why a single malicious DLL can bring down an entire organization , here's why: all it takes is for a legitimate process to load a manipulated library for the attacker's code to inherit all its permissions, evade many antivirus programs, and, in the worst-case scenario, remain on the system for months. Let's break down how this all works, what types of techniques exist, what real risks they entail, and what you can do to detect and mitigate the impact.

What is Visual Basic 9?
Related articles:
Visual Basic: what it is, what it's used for, and how to learn it

What is a DLL and why is it so interesting to an attacker?

A DLL (Dynamic-Link Library) is essentially a shared library containing code and data that can be reused by multiple applications simultaneously. Windows uses them extensively to avoid code duplication and to manage memory, disk space, and system resources more efficiently.

From the user's perspective, there's not much mystery: they don't open like a normal program ; they're transparently consumed by the application that needs them when it runs. For the system, however, they are critical components: they contain functions, graphics resources, drivers, and all sorts of routines that allow the software to function.

Most of these files end in .dll , although they can also appear as .drv, .drov, or even .exe executables that act as libraries. A single file can be called by multiple processes simultaneously , meaning that if that file is compromised, an attacker can infiltrate several applications at once.

That's the beauty (and the problem): if you manage to get a process to load a manipulated DLL , the code in that library will run with the permissions of that process, whether it's a user application, a privileged service, or even security software that, paradoxically, should protect you.

DLL Hijacking: Concept and Operation

DLL hijacking is a technique that exploits how some Windows applications search for the libraries they need. Instead of always specifying the exact path to each DLL, many programs let the system itself follow its default search order.

The problem arises when an application attempts to load a DLL by name, without the full path and without validating its integrity or origin. If an attacker manages to place a malicious file with the same name as the expected DLL in an accessible directory, Windows could load the fake one before the legitimate one, giving the attacker control of the process.

This technique has been around since the Windows 2000 era and still works because the basic search mechanism has barely changed: the system scans several directories in a specific order until it finds the file. It is precisely this order that an attacker exploits to slip their altered version higher up in the list.

In real-world settings, DLL hijacking has been used in supply chain attacks , compromising trusted vendors to distribute digitally signed but malicious DLLs, as happened in the SolarWinds incident, where a simple altered library triggered a massive breach against US government agencies.

DLL search order in Windows and where the vulnerability is exploited

Windows applications rely on a standard DLL search order when a full path is not specified. This behavior also depends on whether "safe DLL search mode" is enabled.

With safe search enabled, the system prioritizes more reliable routes. The typical order is:

  • Directory from which the application is loaded.
  • System directory (usually C:\Windows\System32).
  • 16-bit system directory.
  • Windows directory.
  • User's current working directory.
  • Directories defined in the environment variable PATH.

If safe search is disabled, the user's current directory moves up in the search hierarchy and is checked before certain system paths. This small change is enough for a DLL placed by the attacker in the working directory to be loaded instead of the genuine one.

Imagine an application that needs a specific DLL located in C:\Windows\System32 , but whose code doesn't explicitly specify that path. The system will first look at the application's directory, then (depending on the configuration) the current directory or other directories before reaching System32. If an attacker leaves a modified copy along the way , the application will open it, believing it to be the original.

  Risks of USB drives and how to protect your information

This is known as DLL search order hijacking . The vector is simple: place the malicious DLL in a directory that Windows scans before the legitimate directory. The methods used to get there can vary: social engineering, phishing, writing to shared paths, deployments through vendors, etc.

Common variants of DLL hijacking

The basic concept (injecting a malicious DLL into the normal program load) gives rise to several technical variations . They all aim for the same thing, but differ in the details of how the library is inserted into the process.

One of these vulnerabilities relies on KnownDLLs , a list of known system DLLs managed through the Windows Registry. When an application requests one of these libraries, the loader first consults this special set. By manipulating related registry keys, an attacker can bypass the KnownDLLs filter and force the system to resolve the name against their own malicious version.

Another popular technique is DLL side-loading , which exploits the WinSxS directory (C:\Windows\WinSxS). This repository contains multiple versions of the same library to prevent conflicts between applications. A program consults a "manifest" that indicates which versions it can use. An attacker can introduce a forged DLL into this directory by manipulating its metadata to make it appear valid according to the manifest, thus causing its version to be loaded.

There is also the so-called phantom DLL hijacking , which exploits old or rarely used libraries that some applications still try to load at startup even though they are no longer essential. If an attacker creates a DLL with that "forgotten" name and places it in the correct path, they can execute malicious code without anyone noticing, since the original file often doesn't even exist.

In all cases, the goal is the same: to place the fake DLL in the right place, at the right time, and with a name that the target process will look for, so that the operating system loads and executes the attacker's code within the legitimate process.

DLL injection: when the attacker forces the loading into memory

In addition to passive hijacking of the search order, there is active DLL injection into processes . Here, the attacker doesn't wait for the program to take the wrong path, but instead uses the Windows API to force a process to load their library.

The idea is simple yet powerful: by injecting a DLL into a running process , the malware inherits its privileges and accesses its memory space. From there, it can log keystrokes, capture screenshots, steal data, intercept calls to security functions, install more malware, or modify the application's behavior.

Injection usually involves several phases: preparing the DLL (with a DllMain that executes the payload upon loading), reserving memory in the target process to store the path to that DLL, writing that path with WriteProcessMemory , and finally, creating a remote thread that invokes LoadLibraryA within the target process.

The injected DLL executes within the context of that process, using its security tokens and trust level. This allows, among other things, the evasion of file-based security controls , since the malicious code only exists in memory, or the disabling of protection solutions from within its own processes.

Differences between programming malicious EXE and DLL files

When a malware developer decides to create an executable (.exe) or a DLL, the entry point and execution model change significantly. In a typical EXE, the system invokes a main function (or WinMain) when launching the process; in a DLL, the flow does not work that way.

Dynamic libraries use a special function called DllMain , which is the entry point when the DLL is loaded, unloaded, or when threads are created/destroyed within the process that maps it. The loader doesn't "launch" the DLL as an isolated program, but simply calls DllMain on various events.

Many legitimate DLLs export various functions for use by other modules. In contrast, a malicious DLL designed solely for injection might simply implement DllMain with the necessary code and export nothing, so that its execution occurs immediately upon loading without requiring any subsequent explicit calls.

This means that the attacker must be careful about how they compile and link their DLL: what dependencies they add, what runtime options they use, and how they manage compatibility between different versions of Windows or between 32-bit and 64-bit architectures.

Typical development environment for malicious DLLs

To compile test DLLs or actual malicious payloads, Visual Studio on Windows (including Visual Basic projects ) or cross-platform tools like mingw-w64 on Linux are frequently used. In the first case, simply create a C/C++ project and configure it as a dynamic library so that the IDE automatically generates the necessary structure.

Within the build options, it's common to adjust the Code Generation section . For example, you can select Multi-threaded Debug (/MTd) during development to include debugging information and then switch to Multi-threaded (/MT) in the final version, integrating the runtime library into the binary itself, which makes it easier to run on more machines without external dependencies.

From Linux, you can use x86_64-w64-mingw32-gcc to generate executables and DLLs for Windows. It's common to add optimization flags like -O2, reduce the size with -s, and section functions and data (with -ffunction-sections and -fdata-sections) to eliminate dead code during linking.

  The most important news and rumors about Windows 12

Options such as `-static-libstdc++` and `-static-libgcc` are also commonly used to statically link the C and C++ standard libraries, making the resulting binary more self-contained. Other parameters like `-fno-exceptions`, `-fmerge-all-constants`, and `-fpermissive` help fine-tune compiler behavior and reduce unnecessary code.

Remote DLL injection: a technical step-by-step guide

One classic technique is remote DLL injection , in which an attacker-controlled process injects a library into another legitimate process. The general flow typically includes several key Windows API calls.

First, the malicious process can enable the SE_DEBUG_PRIVILEGE debugging privilege to gain the right to manipulate other processes. It then opens a handle to the target process using OpenProcess , requesting permissions such as PROCESS_CREATE_THREAD or PROCESS_VM_WRITE to write to its memory and create threads.

Next, memory is allocated within the victim process using VirtualAllocEx , where the full path of the DLL to be injected is stored. The attacker writes this path to the newly allocated region using WriteProcessMemory , so that the target process now has a buffer containing the path of the malicious DLL in its address space.

To load the DLL, LoadLibrary must be called within that process. This function resides in kernel32.dll, which is usually mapped to the same address in all processes on the same machine. The attacker obtains a handle to the module using GetModuleHandle and then locates the exact address of LoadLibrary using GetProcAddress.

Finally, it creates a remote thread with CreateRemoteThread , specifying the LoadLibrary address as the entry point and the pointer to the buffer containing the DLL path as a parameter. When the thread starts in the target process, it executes LoadLibrary with that path, loading the malicious DLL into its memory space and triggering its DllMain.

Other process injection techniques and their dangers

Remote DLL injection is just one piece of the puzzle. Under the umbrella of process injection are many variations that all pursue the same goal: to make another process execute arbitrary code within its own context.

Among the most common is direct code injection , where instead of loading a DLL, raw shellcode is written into the target process's memory. The flow is similar: the process is opened, memory is allocated using VirtualAllocEx, the payload is written using WriteProcessMemory, and its execution is triggered using CreateRemoteThread or other mechanisms.

Another variant is thread hijacking , which doesn't create new threads but rather takes over an existing one. The attacker suspends it with `SuspendThread`, reads its context with `GetThreadContext`, modifies the instruction pointer to point to their own code, and resumes it with `ResumeThread`. In this way, the malicious code executes disguised as a legitimate thread.

Asynchronous Procedure Call (APC) injection uses QueueUserAPC to queue a routine within a thread that is in a wait state. When the thread processes its APC queue, it executes the injected code without the creation of a new remote thread being readily apparent.

Reflexive DLL injection is particularly stealthy , as the DLL itself contains a built-in loader capable of mapping itself to memory without writing anything to disk or using the standard LoadLibrary mechanism. This makes detection based on file system activity and module load events extremely difficult.

Finally, the so-called process hollowing creates a legitimate process in a suspended state, erases its original memory image (using functions like NtUnmapViewOfSection), allocates new memory, writes the malicious PE code into it, adjusts the header and relocations, corrects the main thread context with SetThreadContext, and resumes execution. To the system, it appears that the legitimate application is running, but in reality, the inside has been replaced with the attacker's code.

Process memory fundamentals in Windows

To understand why these techniques work, it's essential to understand the structure of a process's virtual memory in Windows. Each process has its own isolated virtual address space, which in 32 bits ranges approximately from 0 to 0x7FFFFFFF and in 64 bits extends much further.

This space is divided into different regions: the program's executable code, its loaded DLLs, thread stacks , heaps, and other reserved areas. The system's memory manager is responsible for mapping these virtual addresses to physical memory frames through paging.

Each region has associated access protections that define what can be done with it: PAGE_READ, PAGE_WRITE, PAGE_EXECUTE, combinations such as PAGE_EXECUTE_READWRITE, etc. If a process attempts unauthorized access, Windows generates an access violation.

Injection techniques exploit these protections by requesting memory with specific permissions, changing attributes with VirtualProtect or VirtualProtectEx , reading data from other processes with ReadProcessMemory, or freeing memory regions with VirtualFreeEx. Many EDR detections rely on observing suspicious strings of these inter-process calls.

In summary, the isolated memory model managed through the API makes it easy for one process to "touch" another's memory as long as it obtains the appropriate permissions, something that malware readily exploits while trying to stay under the radar of defense tools.

Persistence using AppInit_DLLs and associated risks

Beyond one-off injections, some malware abuses Windows mechanisms to remain persistent on every startup or every time certain applications are run. One classic example is the use of the AppInit_DLLs registry key.

  Best alternatives to Microsoft Office for Mac

By modifying this key, the attacker instructs the loading of a specific DLL in virtually any process that uses user32.dll —that is, in almost all applications with a graphical interface. The result is that, every time a program is opened, the malicious DLL is automatically injected before the application begins its normal execution.

A typical pattern observed in real-world samples involves a malicious executable creating another file (for example, a new .exe) and scheduling its periodic execution using the Windows Task Scheduler . This task is launched at every system startup, and the second binary writes the malicious DLL to disk and configures AppInit_DLLs to point to it.

From that point on, each graphics process carries the DLL with it , which can inspect which application is loading and decide whether to act or discreetly remove itself from memory to avoid detection. For example, it might ignore processes like the calculator but remain in browsers or email clients to steal credentials and sensitive information.

This type of persistence causes malicious code to execute long before the user can even interact with the application, making it difficult to detect at first glance and allowing attackers to have quite deep control over the user environment.

Detection of DLL hijacking and injection

Detecting these techniques is not trivial because, in many cases, the malicious code only exists in memory and executes within legitimate processes . However, several approaches, when combined, offer a good chance of catching anomalous behavior.

One of the most direct methods is analyzing DLL loading activity using tools like Process Monitor or Process Explorer. By filtering for operations related to files ending in .dll that return results like FILE NOT FOUND, you can see when a process attempts to locate libraries outside of expected system directories.

By applying additional filters to the load path (for example, the application's own directory instead of System32), you can identify external DLLs that are being loaded outside the legitimate path. This list of files is a good starting point for checking for manipulated libraries.

On the other hand, many modern EDR solutions monitor for suspicious API patterns : sequences such as OpenProcess → VirtualAllocEx → WriteProcessMemory → CreateRemoteThread or massive calls to QueueUserAPC and SetThreadContext trigger behavior rules.

The scan also analyzes executable memory regions within each process, looking for pages with execute permissions but whose content doesn't match the expected code of the original binary. Memory scanners can detect unusual jumps, strange relocations, or blocks of shellcode that shouldn't be there.

In forensics, examining memory dumps and comparing the digital signatures of loaded DLLs against their disk equivalents provides clear clues to manipulation. If a library in memory differs from its signed disk version, it's a red flag of possible malicious injection or patching.

Prevention and hardening measures against DLL injection

Completely shielding a system against DLL injection and library hijacking is complicated, but barriers can be put in place that make the attacker's job much more difficult and reduce the impact if they manage to get in.

The first front lies with software developers: it is crucial that applications specify absolute paths when loading critical DLLs, avoiding relying on the system's search order to determine file location. Furthermore, it is advisable to validate the digital signatures of libraries before accepting them.

On the system side, tools like AppLocker or Windows Defender Application Control (WDAC) allow you to restrict which executables and DLLs are authorized to load. Combined with code integrity policies, they block unsigned modules or modules with untrusted signatures, preventing many hijacking attempts.

We must not forget memory protection measures such as DEP (Data Execution Prevention), which prevents code from being executed from regions marked only as data, or ASLR (Address Space Layout Randomization), which makes it difficult to know the exact addresses of modules and reduces the reliability of some exploits.

Additional mitigations such as Control Flow Guard (CFG) ensure that indirect code calls do not jump to arbitrary locations, and protected process mechanisms (PPL) limit which processes can open handlers to others, hardening the attack surface for cross-process injections.

Finally, at an organizational level, it is essential to keep antivirus and EDR updated , periodically review write permissions in directories where executables and DLLs reside, and train staff to minimize the initial vectors (phishing, attachments, downloads from dubious sources) that allow the first malicious DLL to be planted in the system.

DLL injection, library hijacking, and process injection techniques, in general, constitute a veteran but tremendously effective arsenal for attackers, capable of exploiting details of module loading in Windows to hide code in trusted processes , escalate privileges , and bypass traditional controls. A thorough understanding of how DLLs work, how applications search for them, which APIs are used to manipulate memory and processes, and what prevention and monitoring mechanisms are available, makes all the difference between an environment where a single altered DLL can wreak havoc unchecked and one where any attempt to infiltrate leaves a noisy trail that defenses can detect and stop in time.