This document provides in-depth technical information about process injection mechanisms, Windows internals, and implementation details.
Disclaimer: This is for educational and authorized security research only. Unauthorized process injection is illegal.
- Windows Internals: Processes and Threads
- Memory Management Architecture
- Access Tokens and Security Context
- Process Lifecycle Management
- Memory Architecture in Depth
- Critical Windows APIs
- Injection Techniques - Deep Dive
- Security Considerations
- Debugging Process Injection
- Common Pitfalls
- Performance and Optimization
A process is a container that encapsulates all resources required for a running program:
- Program vs. Process: A program is compiled source code stored in a PE executable file (.exe or .dll). A process is an active instance of that program with its own isolated resources, memory space, and execution context.
- Resource Isolation: Each process maintains its own virtual address space, heap, and file handles. Multiple instances of the same program run independently without interfering with one another.
Process Creation APIs:
Windows provides three primary APIs for creating new processes:
CreateProcessW– Spawns a new process using the same access token as the calling process. This means the new process inherits the security context and privileges of the parent.CreateProcessAsUserW– Spawns a process under an alternate access token. Requires the caller to possessSeImpersonatePrivilegeor higher privileges.CreateProcessWithLogonW– Creates a new process using plaintext username and password credentials. Useful for interactive logins.
All three APIs ultimately invoke the kernel function NtCreateUserProcess, which performs the actual process creation at the kernel level.
Process Creation Call Chain Diagram:
┌─────────────────────────────────────────────────────────────────────────────┐
│ Creating Process │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Kernel32.dll Advapi32.dll SvcHost.Exe │
│ ┌──────────────────┐ ┌──────────────────┐ ┌────────────────┐ │
│ │ CreateProcess │ │ CreateProcess │ │ SecLogon.dll │ │
│ │ CreateProcessAs │ │ WithLogonW │ │ │ │
│ │ User │ │ CreateProcess │ │ SlrCreateProcess│ │
│ └────────┬─────────┘ │ WithTokenW │ │ WithLogon │ │
│ │ └────────┬─────────┘ └────────┬───────┘ │
│ └───────────────────────────┬────────────────────────┬│ │
│ │ ││ RPC │
│ CreateProcessInternal │ ││ Call │
│ ┌──────────────────────────┘ ││ │
│ │ ││ │
│ └─────────────────────────────────────────────────┘│ │
│ │ │
│ Kernel32.dll │ │
│ ┌────────────────────┐ │ │
│ │ CreateProcessAs │<─────────────────┘ │
│ │ User │ │
│ │ NtDll.dll │ │
│ └────────┬───────────┘ │
│ │ │
└───────────────────────────────┼─────────────────────────────────────────┘
│
User Mode │
──────────┼──────────────────────────────────────────
Kernel │
Mode │
│
┌───────────────┴────────────────┐
│ │
┌───▼────────────────────┐ ┌─────▼──────────────────┐
│ NtCreateUserProcess │ │ NtCreateUserProcess │
│ (Executive) │ │ (Executive) │
└────────────────────────┘ └────────────────────────┘
A thread is the actual execution unit that Windows schedules to run on a CPU:
- State: Each thread holds CPU register state and its own call stack (typically 1 MB by default).
- Minimum Requirement: Every functional program must have at least one thread executing from the program's entry point.
- Parallelism: Multiple threads within a single process enable parallel execution of multiple code paths simultaneously.
Thread Creation APIs:
CreateThread– Creates a new thread within the current process. The new thread starts executing at a specified function address.CreateRemoteThread– Creates a new thread in a different process. This is a foundational technique for process injection—write code to a target process's memory, then create a remote thread pointing to that code.- Both APIs internally call
CreateRemoteThreadEx(the extended version), which in turn callsNtCreateThreadExat the kernel level.
Every process on Windows operates within its own private virtual address space:
Virtual vs. Physical Memory:
- Virtual Memory: The memory space visible to a process. On 32-bit systems, this is typically 4 GB (2 GB user-mode, 2 GB kernel-mode). On 64-bit systems, much larger (typically 128 TB or more per process).
- Physical Memory: Actual RAM installed in the computer. The Windows memory manager translates virtual addresses to physical addresses.
- Paging: When RAM is exhausted, the memory manager can page (write) data to disk, freeing physical memory for other processes. Paged data is retrieved back into RAM on demand.
Virtual Memory Translation Diagram:
Virtual Memory Physical Memory Virtual Memory
(Process A) (Shared RAM) (Process B)
┌──────────────┐ ┌──────────────┐
│ Virtual │ ┌──────────────┐ │ Virtual │
│ Page 1 │───────▶│ Physical │ │ Page 1 │
│ (Green) │ │ Frame 1 │◀──────│ (Blue) │
└──────────────┘ │ (Orange) │ └──────────────┘
└──────────────┘
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Virtual │ │ │ │ Virtual │
│ Page 2 │───────▶│ Physical │ │ Page 2 │
│ (Green) │ │ Frame 2 │ │ (Green) │
└──────────────┘ │ (Orange) │ └──────────────┘
└──────────────┘
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Virtual │ │ │ │ Virtual │
│ Page 3 │───────▶│ Physical │ │ Page 3 │
│ (Orange) │ │ Frame 3 │ │ (Blue) │
└──────────────┘ │ (Gray) │ └──────────────┘
└──────────────┘
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Virtual │ │ │ │ Virtual │
│ Page 4 │───────▶│ Physical │◀──────│ Page 4 │
│ (Green) │ │ Frame 4 │ │ (Green) │
└──────────────┘ │ (Orange) │ └──────────────┘
└──────────────┘
┌──────────────┐ ┌──────────────┐
│ Virtual │ ┌──────────────┐ │ Virtual │
│ Page 5 │───────▶│ │ │ Page 5 │
│ (Orange) │ │ Physical │ │ (Blue) │
└──────────────┘ │ Frame 5 │ └──────────────┘
│ (Orange) │
┌──────────────┐ └──────────────┘
│ Virtual │
│ Page 6 │ ┌──────────────┐
│ (Green) │───────▶│ │
└──────────────┘ │ Disk (Paged) │
└──────────────┘
Key Observations:
- Each process has its own virtual address space (left and right columns)
- Virtual pages map to shared physical frames (middle)
- Multiple processes can map to the same physical frame (resource sharing)
- Pages can be paged to disk when RAM is full (gray/disk area)
- Isolation is maintained: Process A's virtual addresses are independent from Process B's
Memory Pages:
Memory is organized into fixed-size chunks called pages:
- Small Pages: 4 KB (standard size on x86, x64, and ARM architectures)
- Large Pages: 2 MB (x86/x64) or 4 MB (ARM). Used for performance-critical applications.
1. Virtual Memory APIs (Lowest level)
VirtualAlloc,VirtualFree,VirtualProtect,VirtualAllocEx,VirtualFreeEx- Operate on entire pages
- Allocations are rounded up to the nearest complete page boundary
- Typical use: Allocating large blocks, protecting memory ranges, injecting code
2. Heap APIs (Mid level)
HeapAlloc,HeapReAlloc,HeapFree,GetProcessHeap- Manage sub-page allocations
- Heap manager sits atop virtual APIs and provides fine-grained allocation
- Typical use: General-purpose memory allocation in applications
3. Memory-Mapping APIs (File-based)
CreateFileMappingA,OpenFileMappingA,MapViewOfFile,UnmapViewOfFile- Map files on disk directly into a process's virtual address space
- Can be shared across multiple processes
- Typical use: Shared memory between processes, memory-mapped I/O
When a process is created, it receives a primary access token that defines its security context:
An access token contains:
- User SID (Security Identifier) – Uniquely identifies the user
- Group SIDs – Lists of groups the user belongs to (e.g., Administrators, Domain Users)
- User Privileges – Special rights granted to the user (e.g., SeDebugPrivilege, SeTakeOwnershipPrivilege)
- Integrity Level – Indicates the trustworthiness of the process (Low, Medium, High, System)
Access Token and Thread Hierarchy Diagram:
┌──────────────────────────────────────────────────────────────────────────┐
│ Process and Thread Security │
├──────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────┐ ┌──────────────────────────┐ │
│ │ ACL │ Process │◄─────────────────────┤ Access Token │ │
│ └───────────────┘ │ ┌────────────────────┐ │ │
│ │ │ │ User's SID │ │ │
│ │ │ │ Group SIDs │ │ │
│ │ │ │ Privileges │ │ │
│ ├─────────────┬───────────────┤ │ Owner SID │ │ │
│ │ │ │ │ Default ACL │ │ │
│ │ │ │ │ ... │ │ │
│ │ │ │ └────────────────────┘ │ │
│ │ │ └──────────────────────────┘ │
│ │ │ │
│ ┌────▼────┐ ┌────▼────┐ ┌────────────┐ │
│ │ ACL │ │ ACL │ │ ACL │ │
│ │Thread 1 │ │Thread 2 │ │ Thread 3 │ │
│ └────┬────┘ └────┬────┘ └────┬───────┘ │
│ │ │ │ │
│ ┌────▼──────────┐ │ │ │
│ │Access Token │ │ │ │
│ │┌────────────┐ │ │ │ │
│ ││User's SID │ │ │ │ │
│ ││Group SIDs │ │ │ │ │
│ ││Privileges │ │ │ │ │
│ ││Owner SID │ │ │ │ │
│ ││Default ACL │ │ │ │ │
│ ││... │ │ │ │ │
│ │└────────────┘ │ │ │ │
│ └────────────────┘ │ │ │
│ │ │ │
│ ┌────▼──────────┐ │ │
│ │Access Token │ │ │
│ │┌────────────┐ │ │ │
│ ││User's SID │ │ │ │
│ ││Group SIDs │ │ │ │
│ ││Privileges │ │ │ │
│ ││Owner SID │ │ │ │
│ ││Default ACL │ │ │ │
│ ││... │ │ │ │
│ │└────────────┘ │ │ │
│ └────────────────┘ │ │
│ │ │
│ ┌────▼──────────┐ │
│ │Access Token │ │
│ │┌────────────┐ │ │
│ ││User's SID │ │ │
│ ││Group SIDs │ │ │
│ ││Privileges │ │ │
│ ││Owner SID │ │ │
│ ││Default ACL │ │ │
│ ││... │ │ │
│ │└────────────┘ │ │
│ └────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────────┘
Key Security Concepts:
- Process Token: The primary access token defines the security context for the entire process
- Thread Tokens: Each thread inherits the process's primary token by default, but can impersonate a different token
- DACL Hierarchy: Both processes and threads have associated DACLs that restrict who can access them
- Access Checks: When code tries to access a protected resource (like another process), Windows checks if the caller's token allows the access
By default, new threads inherit the process's primary access token. However:
- Threads can explicitly impersonate another user's access token using
ImpersonateLoggedOnUseror similar APIs - When a thread impersonates, all work performed by that thread occurs under the impersonated user's security context
- This is a powerful mechanism but also a security risk if misused
Every securable object on Windows (files, processes, threads, registry keys, etc.) is protected by a DACL:
- The DACL specifies who has what access to the object
- When code attempts to access an object, Windows performs an access check:
- Does the caller's access token match the object's DACL?
- Does the caller have the required access level?
- Access is granted only if the checks pass; otherwise, access is denied with an error
A privilege grants a security principal (user or process) the right to perform system-level operations:
Common Privileges:
SeTimeZonePrivilege– Change system time zoneSeShutdownPrivilege– Shut down the computerSeLoadDriverPrivilege– Load a device driverSeBackupPrivilege– Bypass file access restrictions for backupSeRestorePrivilege– Bypass file access restrictions for restore
Powerful Privileges (Can Compromise the System):
SeDebugPrivilege– Obtain unrestricted read/write access to any process. This is one of the most dangerous privileges—with it, an attacker can inject code into SYSTEM processes or extract sensitive data from any process.SeTakeOwnershipPrivilege– Take ownership of any securable object (file, registry key, process). Allows bypassing permissions.SeRestorePrivilege– Replace any file on the system. Can be used to overwrite critical system binaries or plant malware.SeLoadDriverPrivilege– Load arbitrary device drivers into the kernel. Drivers run with kernel privileges and can compromise the entire system.SeCreateTokenPrivilege– Create arbitrary access tokens with any user, privilege, or domain group. Effectively grants unlimited privilege escalation.
Privilege Management:
- Privileges are granted by system administrators via Group Policy Objects (GPOs) or Local Security Policy (secpol)
- A privilege must be enabled before use using
AdjustTokenPrivilegesAPI - Verify if a privilege is held using
LsaEnumerateAccountRightsorCheckTokenMembership
When a process terminates cleanly:
ExitProcess– Called by the process itself (usually when the primary thread's main function returns)- All loaded DLLs (Dynamic Link Libraries) receive notification via their
DllMainfunction withDLL_PROCESS_DETACH - DLLs have an opportunity to perform cleanup (close file handles, release resources, save state)
- All threads are terminated orderly
- Resources are disposed of properly
When a process is forcibly terminated:
TerminateProcess– Called by another process to forcibly kill a target process- All threads are terminated immediately and abruptly
- Loaded DLLs are NOT given an opportunity to clean up
- Risk: Data may be left in an inconsistent state, files may be left open, memory may leak, databases may be corrupted
Key Difference:
ExitProcess: Self-termination → Graceful cleanup → Safe
TerminateProcess: Forced termination → No cleanup → Risk of data corruption
[PE Headers]
├─ DOS Header (0x40 bytes)
├─ PE Signature
├─ COFF Header
└─ Optional Header (contains entry point)
[.text] (Code section)
└─ Executable instructions
[.data] (Data section)
└─ Global initialized variables
[.rdata] (Read-only data)
└─ Constants, import table
[Relocation Table]
└─ Addresses to patch during loading
Modern Windows enables ASLR by default:
- Each process run has different base addresses
- Impact on injection: Can't hardcode addresses
- Solution: Use RIP-relative addressing (x64) or relocations
- Bypass: Leak addresses through info disclosure
All imports from DLLs are resolved at load time:
User32.dll imports:
MessageBoxA → 0x77d5e480
CreateWindowA → 0x77d5f100
...
Why it matters:
- Inject code that needs Windows APIs? Must resolve or set up IAT
- Can hook IAT entries for API interception
- Position-independent code must use GetProcAddress() at runtime
// Open existing process
HANDLE hProcess = OpenProcess(
PROCESS_ALL_ACCESS, // Desired access
FALSE, // Inherit handle
dwProcessId // Target PID
);
// Create process
CreateProcessA(...)Access flags:
PROCESS_VM_OPERATION– Allocate/free memoryPROCESS_VM_READ– Read process memoryPROCESS_VM_WRITE– Write process memoryPROCESS_CREATE_THREAD– Create threadsPROCESS_QUERY_INFORMATION– Get process infoPROCESS_ALL_ACCESS– All permissions (requires admin)
// Allocate memory in target process
LPVOID lpBuffer = VirtualAllocEx(
hProcess, // Target process handle
NULL, // Preferred address (NULL for any)
1024, // Size
MEM_COMMIT, // Allocation type
PAGE_EXECUTE_READWRITE // Protection
);
// Write data to target process
BOOL success = WriteProcessMemory(
hProcess, // Target process
lpBuffer, // Remote address
shellcode, // Local buffer
sizeof(shellcode), // Size
NULL // Bytes written
);
// Read data from target process
BOOL success = ReadProcessMemory(
hProcess,
remoteAddress,
localBuffer,
size,
NULL
);
// Free allocated memory
VirtualFreeEx(hProcess, lpBuffer, 0, MEM_RELEASE);Memory protection flags:
PAGE_EXECUTE– Execute onlyPAGE_EXECUTE_READ– Execute + readPAGE_EXECUTE_READWRITE– Execute + read + writePAGE_READWRITE– Read + write only
// Create remote thread (execute code in target process)
HANDLE hThread = CreateRemoteThread(
hProcess, // Target process
NULL, // Thread attributes
0, // Stack size (0 = default 1MB)
(LPTHREAD_START_ROUTINE)lpBuffer, // Entry point
NULL, // Thread parameter
0, // Flags (0 = auto-start)
NULL // Thread ID
);
// Wait for thread to complete
WaitForSingleObject(hThread, INFINITE);
// Get thread exit code
DWORD dwExitCode;
GetExitCodeThread(hThread, &dwExitCode);
// Close handles
CloseHandle(hThread);
CloseHandle(hProcess);// Get address of exported function
HMODULE hModule = GetModuleHandle("kernel32.dll");
FARPROC pLoadLibrary = GetProcAddress(hModule, "LoadLibraryA");Overview:
- Allocate memory in target process
- Write DLL path to allocated memory
- Get address of
LoadLibraryAin kernel32.dll - Create remote thread pointing to
LoadLibraryA - Pass allocated memory (DLL path) as thread parameter
Advantages:
- Simple to implement
- Works reliably across Windows versions
- DLL handles all initialization
Disadvantages:
- Leaves traces (DLL loaded in target process)
- Requires DLL file on disk
- EDR/AV may block
Code flow:
Injector Process Target Process
│ │
├─ AllocateMemory ─────────>│
│ [Memory allocated]
│
├─ WriteDLL Path ──────────>│ (remote addr)
│ [DLL path written]
│
├─ CreateRemoteThread ─────>│
│ with LoadLibraryA [Thread created]
│ │
│ [LoadLibraryA executes]
│ [DLL loads]
│ [DllMain() called]
│ [Code executes]
Overview:
- Allocate executable memory in target process
- Write raw shellcode (position-independent)
- Create remote thread pointing to shellcode
- Shellcode executes without DLL dependencies
Advantages:
- No DLL file needed
- Smaller footprint
- More difficult to trace
Disadvantages:
- Must write position-independent code
- No automatic initialization
- Harder to handle complex functionality
Position-Independent Code (PIC) Requirements:
; x64 PIC requirements:
; - Use RIP-relative addressing for data
; - Don't hardcode addresses
; - Resolve APIs at runtime using GetProcAddress
; Example: Call MessageBoxA from shellcode
; 1. Find kernel32.dll base in PEB
; 2. Walk export table to find GetProcAddress
; 3. Call GetProcAddress("user32.dll")
; 4. Call GetProcAddress("MessageBoxA")
; 5. Call the resolved MessageBoxA functionOverview:
- Create target process in suspended state (
CREATE_SUSPENDED) - Unmap original PE image from process memory
- Allocate new memory at preferred PE base
- Write new PE image to target process
- Update entry point and thread context
- Resume process execution
Advantages:
- Process appears legitimate (uses real executable)
- Can hide parent-child process relationship
- Difficult to detect without instrumentation
Disadvantages:
- Complex to implement correctly
- Requires PE file understanding
- Can destabilize process
Code flow:
1. CreateProcessW(..., CREATE_SUSPENDED, ...)
2. GetThreadContext(hThread, &ctx)
└─ Save original entry point
3. NtUnmapViewOfSection(hProcess, imageBase)
└─ Remove original PE
4. VirtualAllocEx(hProcess, preferredBase, imageSize)
└─ Allocate space for new PE
5. WriteProcessMemory(..., ntHeaders, ...)
└─ Write new PE headers and sections
6. SetThreadContext(hThread, &newCtx)
└─ Point to new entry point
7. ResumeThread(hThread)
└─ Execute new code
Overview:
- Allocate shellcode memory in target process
- Write shellcode
- Queue APC to target thread
- When target thread enters alertable state (WaitForSingleObject, Sleep, etc.), APC executes
- Shellcode runs in thread context
Advantages:
- No visible thread creation
- Harder to detect
- Executes in existing thread
Disadvantages:
- Thread must enter alertable state
- Limited to one thread
- May not execute immediately
APC Functions:
// Queue APC to thread
DWORD QueueUserAPC(
PAPC_FUNC pfnAPC, // APC function pointer
HANDLE hThread, // Target thread
ULONG_PTR dwData // Parameter
);Overview:
- Inject code that installs API hook
- When target application calls hooked API, injected code executes
- Can be chained to inject into multiple processes
Example: SetWindowsHookEx
HHOOK hHook = SetWindowsHookEx(
WH_GETMESSAGE, // Hook type
(HOOKPROC)HookFunction, // Hook callback
hModule, // DLL instance
0 // Thread (0 = all threads)
);Advantages:
- Passive injection (no active hooking needed)
- Can target multiple processes
- Executes in response to system events
Disadvantages:
- Requires registering hook (may be monitored)
- Limited to specific APIs
- Removed when application unregisters
Signature-based Detection:
- Known injection patterns
- Suspicious API sequences
- Unusual memory allocations
Behavior-based Detection:
- Unexpected remote thread creation
- Memory allocation + write + execute
- Cross-process memory operations
- Unusual registry/file access from injected code
Heuristic Detection:
- Calls to kernel32 functions from unusual addresses
- Stack anomalies
- Suspicious entropy patterns
SeDebugPrivilege:
- Allows opening any process with PROCESS_ALL_ACCESS
- Required for most injection techniques
- Admin privs typically needed
Token inherited by injected thread:
Injector Process Token
├─ User SID (e.g., Domain\User)
├─ Groups (Domain\Admins, etc.)
├─ Privileges (SeDebugPrivilege, etc.)
└─ Integrity Level (High, Medium, Low)
Target Process Token
└─ Injected code inherits target's token!
Implication:
- If target runs as SYSTEM, injected code runs as SYSTEM
- If target is sandboxed, injected code inherits sandbox
# Attach to target process
windbg -p <PID>
# Set breakpoint at VirtualAllocEx
bp kernel32!VirtualAllocEx
# View memory
db <address> L<count> ; display bytes
dq <address> ; display qwords
# Step through injector
p ; step over
t ; trace into
# View registers (x64)
rax, rcx, rdx, r8, r9, rip, rsp
- Attach to target process
- Set breakpoint on
CreateRemoteThread - Step and observe:
- Memory allocations
- Data written
- Thread creation
- Execution flow
# PowerShell: Monitor process creation and thread creation
Get-Process | Where {$_.ProcessName -eq "target"}
Get-Process | Get-Member
# Monitor with Process Monitor
# Filter: Process Name contains "notepad"
# Monitor: CreateRemoteThread, VirtualAllocEx, etc.Problem:
// Wrong: mixing calling conventions
typedef int (__stdcall *pFunc)(int a, int b);
FARPROC pFn = GetProcAddress(...);
// pFn might be __cdecl, not __stdcall!Solution:
// Right: explicitly cast or verify
typedef int (WINAPI *pMessageBoxA)(HWND, LPCSTR, LPCSTR, UINT);
pMessageBoxA pMB = (pMessageBoxA)GetProcAddress(hUser32, "MessageBoxA");Problem:
HANDLE hProcess = OpenProcess(...);
// ... forgot to close
// Result: Handle leak, resource exhaustionSolution:
HANDLE hProcess = OpenProcess(...);
// ... use it ...
CloseHandle(hProcess); // Always close!Problem:
// Wrong: assumes fixed address
LPVOID addr = (LPVOID)0x77d50000; // kernel32 base
CreateRemoteThread(hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE)addr, ...);
// Fails with ASLR enabled!Solution:
// Right: get address dynamically
HMODULE hKernel32 = GetModuleHandleA("kernel32.dll");
FARPROC pFunc = GetProcAddress(hKernel32, "FunctionName");Problem:
// Fails: missing PROCESS_VM_OPERATION
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
VirtualAllocEx(hProcess, ...); // Fails!Solution:
// Right: request all needed access flags
HANDLE hProcess = OpenProcess(
PROCESS_VM_OPERATION | PROCESS_VM_READ |
PROCESS_VM_WRITE | PROCESS_CREATE_THREAD,
FALSE, pid
);Problem:
// CPU cache may have old instructions
WriteProcessMemory(hProcess, lpBuffer, shellcode, size, NULL);
CreateRemoteThread(hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE)lpBuffer, ...);
// May execute cached old code!Solution:
WriteProcessMemory(hProcess, lpBuffer, shellcode, size, NULL);
FlushInstructionCache(hProcess, lpBuffer, size);
CreateRemoteThread(hProcess, NULL, 0, ...);Problem:
// Shellcode with hardcoded addresses
void shellcode() {
void (*pFunc)() = (void (*)())0xdeadbeef; // Wrong!
pFunc();
}Solution:
; x64 PIC: use RIP-relative addressing
lea rax, [rel data] ; RIP-relative
mov rcx, [rax] ; Read from data// Single large allocation
LPVOID lpBuffer = VirtualAllocEx(hProcess, NULL,
10000, // Allocate 10KB at once
MEM_COMMIT, PAGE_EXECUTE_READWRITE);
// Better than multiple small allocations:
// VirtualAllocEx x 100 = 100 kernel transitions!Techniques:
- Remove unnecessary code
- Use compression (Donut with
-z 2) - Optimize for size (
-Osin gcc) - Strip debug symbols
Example:
# Compile with size optimization
gcc -Os -s -o payload.exe payload.c -luser32
# Generate compressed shellcode
donut.exe -i payload.exe -o payload.bin -z 2// Wait for injected thread to complete
HANDLE hThread = CreateRemoteThread(...);
WaitForSingleObject(hThread, INFINITE);
// Alternative: Don't wait (thread runs independently)
CreateRemoteThread(...);
// Continue immediately- Windows API Reference: https://learn.microsoft.com/en-us/windows/win32/
- Windows Internals Book: Pavel Yosifovich
- Ghidra: https://ghidra-sre.org/
- x64dbg: https://x64dbg.com/
- Process Monitor: https://docs.microsoft.com/en-us/sysinternals/downloads/procmon
This technical documentation is for educational and authorized security research only. Unauthorized process injection is illegal. Always obtain written authorization before conducting security testing.