DLL Injection and Hijacking
Dynamic Link Library (DLL) injection inserts code, packaged as a DLL, into a running process so it runs inside that process. Legitimate software uses this for hot patching, so updates can be applied without restarting a service. Attackers use it to run code inside a trusted process and blend in with normal activity, and as an evasion step around security products.
DLL hijacking is a related technique: an application loads a DLL without a full path, and the Windows DLL search order lets an attacker place a rogue DLL where it will be picked up. Depending on context, hijacking gives code execution, privilege escalation or persistence.
This note covers the core techniques plus additional methods so it can serve as an engagement reference.
Part 1: DLL Injection
LoadLibrary
The most common method uses the LoadLibrary API, which loads a DLL into the current process and returns a handle used to resolve exported functions.
Loading a DLL into the current process (legitimate use):
#include <windows.h>
#include <stdio.h>
int main() {
HMODULE hModule = LoadLibrary("example.dll");
if (hModule == NULL) {
printf("Failed to load example.dll\n");
return -1;
}
printf("Successfully loaded example.dll\n");
return 0;
}
Injecting into another process: allocate memory in the target for the DLL path, write the path, resolve LoadLibraryA in kernel32, then start a remote thread at that address.
#include <windows.h>
#include <stdio.h>
int main() {
DWORD targetProcessId = 123456; // target process id
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetProcessId);
if (hProcess == NULL) { printf("Failed to open target process\n"); return -1; }
LPVOID mem = VirtualAllocEx(hProcess, NULL, strlen(dllPath), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
if (mem == NULL) { printf("Failed to allocate memory in target process\n"); return -1; }
BOOL wrote = WriteProcessMemory(hProcess, mem, dllPath, strlen(dllPath), NULL);
if (!wrote) { printf("Failed to write DLL path to target process\n"); return -1; }
LPVOID load = (LPVOID)GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA");
if (load == NULL) { printf("Failed to get address of LoadLibraryA\n"); return -1; }
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)load, mem, 0, NULL);
if (hThread == NULL) { printf("Failed to create remote thread in target process\n"); return -1; }
printf("Successfully injected example.dll into target process\n");
return 0;
}
Because LoadLibrary and CreateRemoteThread are heavily monitored, this is easy to detect.
APCs and thread hijacking
Alternative ways to run loaded code in a target without CreateRemoteThread:
- QueueUserAPC: allocate and write the DLL path, then queue an APC to a thread in the target that calls
LoadLibraryA. Works well against threads in an alertable state. - Thread hijacking: open a target thread (
SuspendThread), save its context (GetThreadContext), point the instruction pointer at your code, then resume after the payload runs (SetThreadContext,ResumeThread). - SetWindowsHookEx: install a hook (for example
WH_GETMESSAGE) with a hook procedure in your DLL to get it loaded into processes that call the hook.
Manual mapping
Manual mapping loads a DLL by hand and resolves its imports and relocations without calling LoadLibrary, which avoids the API calls that security and anti-cheat products watch.
Simplified steps:
- Load the DLL as raw data in the injecting process.
- Map the DLL sections into the target process.
- Inject and run shellcode that relocates the DLL, fixes imports, runs Thread Local Storage callbacks, and calls the DLL entry point.
Reflective DLL injection
Reflective DLL injection loads a library from memory into a host process. The library carries its own minimal Portable Executable (PE) loader, so it manages its own loading and minimizes interaction with the host.
- Control transfers to
ReflectiveLoader, either throughCreateRemoteThreador a small bootstrap shellcode stub. ReflectiveLoadercomputes its own image location and parses its own headers.- It parses the host
kernel32.dllexport table to findLoadLibraryA,GetProcAddressandVirtualAlloc. - It allocates a contiguous region and copies its image there, then loads headers and sections.
- It processes the import table and then the relocation table.
- It calls the entry point (
DllMainwithDLL_PROCESS_ATTACH) and returns to the bootstrap.
Stephen Fewer's ReflectiveDLLInjection project is the reference implementation.
Module stomping and process hollowing
Related variants that avoid leaving a new module on disk:
- Module stomping: overwrite the
.textsection of a legitimately loaded DLL with your payload, then call it. - Process hollowing: start a suspended process, unmap its image, write your payload, then resume. This is a separate execution technique often grouped with injection.
Part 2: DLL Hijacking
DLL hijacking abuses the Windows DLL search order. If an application asks for a DLL without a full path, the loader searches directories in order, and an attacker who can write to an earlier directory can supply their own DLL.
The search order depends on Safe DLL Search Mode, which is enabled by default. With it enabled, the current directory is moved further down the order.
Enable or disable it in the registry:
- Path:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager - Value:
SafeDllSearchMode(DWORD).1enabled,0disabled. Reboot to apply.
With Safe DLL Search Mode enabled, the order is:
- The directory the application loaded from (example
C:\Program Files\App\). - The system directory (example
C:\Windows\System32). - The 16-bit system directory (example
C:\Windows\System). - The Windows directory (example
C:\Windows). - The current directory, meaning the process working directory (example
C:\Users\user\Desktop). - Directories listed in the PATH environment variable (example
C:\Program Files\App\bin).
With Safe DLL Search Mode disabled, the order becomes:
- The directory the application loaded from (example
C:\Program Files\App\). - The current directory, meaning the process working directory (example
C:\Users\user\Desktop). - The system directory (example
C:\Windows\System32). - The 16-bit system directory (example
C:\Windows\System). - The Windows directory (example
C:\Windows). - Directories listed in the PATH environment variable (example
C:\Program Files\App\bin).
Finding a hijackable load
- Process Explorer (Sysinternals): shows a process loaded modules.
- PE Explorer: opens a PE file and lists the DLLs it imports.
- Process Monitor (Procmon): shows the exact DLL paths the app looks for and the result.
In Procmon, filter the target process, capture a run, then filter Operation to Load Image to see loaded libraries, or filter for paths ending in .dll with result NAME NOT FOUND to find libraries the app wants but cannot find.
Example of a missing library searched for in the app directory:
main.exe CreateFile C:\Users\user\Desktop\Hijack\x.dll NAME NOT FOUND ...
Proxying
With a known DLL the app imports (for example library.dll exporting Add), build a proxy DLL that forwards to the original while tampering with the result.
// tamper.c
#include <stdio.h>
#include <Windows.h>
#ifdef _WIN32
#define DLL_EXPORT __declspec(dllexport)
#else
#define DLL_EXPORT
#endif
typedef int (*AddFunc)(int, int);
DLL_EXPORT int Add(int a, int b)
{
HMODULE originalLibrary = LoadLibraryA("library.o.dll");
if (originalLibrary != NULL)
{
AddFunc originalAdd = (AddFunc)GetProcAddress(originalLibrary, "Add");
if (originalAdd != NULL)
{
printf("============ HIJACKED ============\n");
int result = originalAdd(a, b);
printf("= Adding 1 to the sum to be evil\n");
result += 1;
printf("============ RETURN ============\n");
return result;
}
}
return -1;
}
Compile the proxy, rename the original library.dll to library.o.dll, and rename tamper.dll to library.dll. The app now calls your proxy.
Invalid and phantom libraries
Replace a library the application tries to load but cannot find, or one that does not exist at all (a phantom DLL). In Procmon, look for .dll entries whose result is NAME NOT FOUND, choose one searched for in a writable directory, and place your DLL there.
#include <stdio.h>
#include <Windows.h>
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
printf("Hijacked... Oops...\n");
break;
case DLL_PROCESS_DETACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}
Rename your DLL to the missing name (for example x.dll) and run the app.
DLL sideloading
Many signed applications load a signed-looking but attacker controlled DLL from their own directory. Copy your payload DLL next to the signed executable using the expected name. This is the common "malicious DLL side by side with a signed exe" pattern used by real malware and by red teams.
Service and PATH hijacking
Windows services often load DLLs or executables from their directory or from directories on PATH. If any of those directories is writable by a lower privileged user, placing a rogue DLL or exe there yields code execution as the service account (often SYSTEM). This is a reliable local privilege escalation path and is covered further in windows-privesc.
COM hijacking
A per-user override of a COM server (under HKCU\Software\Classes\CLSID) lets you substitute a DLL that a privileged process loads when it activates that COM object. It is a persistence and escalation technique that complements DLL hijacking.
Systematic discovery checklist
- Enumerate loaded modules per process with Process Explorer.
- Enumerate import tables with PE Explorer for candidate DLL names.
- Run Procmon with the process filter and look for
NAME NOT FOUNDon.dllpaths, plusLoad Imageevents. - Check writable directories: the app install dir, the current directory, and every PATH entry.
- Check service
ImagePathand service DLLs, and whether their directories are writable. - Check per-user COM overrides and Startup folders.
- For each candidate, build either a proxy DLL (files exist) or a drop-in DLL (file missing, or phantom).
References
- ReflectiveDLLInjection (Stephen Fewer): https://github.com/stephenfewer/ReflectiveDLLInjection
- LoadLibraryA (Microsoft Learn): https://learn.microsoft.com/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibrarya
- Dynamic-Link Library Search Order (Microsoft Learn): https://learn.microsoft.com/windows/win32/dlls/dynamic-link-library-search-order
- Process Monitor (Sysinternals): https://learn.microsoft.com/sysinternals/downloads/procmon
- Process Explorer (Sysinternals): https://learn.microsoft.com/sysinternals/downloads/process-explorer
- LOLBAS (execution primitives): https://lolbas-project.github.io