Module 1: Process Creation Internals
How Windows really creates processes — from file on disk to executing code in memory, through the lens of section objects.
Module Objective
Understand the internal steps the Windows kernel takes to create a process: opening the executable file, creating an image section object from it, calling NtCreateProcessEx with that section, and why every process is fundamentally backed by a file-mapped section. This foundation is critical for understanding how Process Ghosting subverts the model.
1. The User-Mode View: CreateProcessW
When user-mode code calls CreateProcessW, it appears simple — pass an executable path and get a running process. But underneath, kernel32!CreateProcessW calls kernel32!CreateProcessInternalW, which orchestrates a complex sequence involving multiple NT-native system calls. The high-level user-mode API hides the real machinery:
// What most developers write:
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi = {};
CreateProcessW(
L"C:\\Windows\\System32\\notepad.exe",
NULL, NULL, NULL, FALSE,
0, NULL, NULL, &si, &pi
);
// What actually happens inside the kernel involves:
// 1. NtOpenFile - open the executable
// 2. NtCreateSection - create an image section from it
// 3. NtCreateProcessEx - create the process from the section
// 4. NtCreateThreadEx - create the initial thread
// Plus: PEB setup, process parameters, csrss notification...
Understanding this decomposition is the key to understanding every PE tampering technique, because each technique manipulates a different step in this chain.
2. Step 1: Opening the Executable File
The first internal step is opening the target executable. The kernel calls NtOpenFile (or NtCreateFile) to obtain a file handle to the PE image. This handle must have FILE_EXECUTE access rights because the file will be mapped as an executable image.
// Conceptual kernel-side file open:
HANDLE hFile;
IO_STATUS_BLOCK iosb;
OBJECT_ATTRIBUTES objAttr;
UNICODE_STRING filePath;
RtlInitUnicodeString(&filePath,
L"\\??\\C:\\Windows\\System32\\notepad.exe");
InitializeObjectAttributes(&objAttr,
&filePath, OBJ_CASE_INSENSITIVE, NULL, NULL);
NtOpenFile(
&hFile,
FILE_READ_DATA | FILE_EXECUTE | SYNCHRONIZE,
&objAttr,
&iosb,
FILE_SHARE_READ,
FILE_SYNCHRONOUS_IO_NONALERT
);
Key Access Rights
| Access Right | Purpose |
|---|---|
FILE_READ_DATA | Read PE headers and section data |
FILE_EXECUTE | Required for creating an image section (SEC_IMAGE) |
SYNCHRONIZE | Allow synchronous I/O operations |
FILE_SHARE_READ | Allow other processes to read the file concurrently |
At this point, the kernel has a handle to the file on disk. The file is just data — PE headers, section data, relocations, imports. No code is executing yet.
3. Step 2: Creating the Image Section
The kernel next creates a section object from the file handle. A section object (also known as a memory-mapped file or file mapping) represents a region of memory that can be shared between processes. For executable images, the section type is SEC_IMAGE:
HANDLE hSection = NULL;
// Create an image section from the file handle
NTSTATUS status = NtCreateSection(
&hSection,
SECTION_ALL_ACCESS,
NULL, // no object attributes
NULL, // size determined by PE headers
PAGE_READONLY, // initial page protection
SEC_IMAGE, // IMAGE section - parsed as PE
hFile // backed by this file
);
SEC_IMAGE vs SEC_COMMIT
SEC_IMAGE tells the kernel to parse the file as a PE image. The memory manager reads the PE headers, validates the format, and sets up virtual address mappings according to the PE section table (e.g., .text at one RVA, .data at another). This is fundamentally different from SEC_COMMIT, which maps raw file bytes without any PE parsing. Only SEC_IMAGE sections can be used to create processes.
When the kernel creates a SEC_IMAGE section, it performs PE validation, checks the machine type, parses the section table, and builds the CONTROL_AREA and SUBSECTION structures that describe how the image will be mapped into virtual memory. The resulting section object holds a reference to the underlying file object.
| Kernel Structure | Role |
|---|---|
SECTION_OBJECT | Top-level object representing the section |
CONTROL_AREA | Links the section to its backing file object and manages shared/private pages |
SUBSECTION | One per PE section (.text, .data, etc.) — defines RVA range, file offset, and protection |
FILE_OBJECT | The backing file — what the section was created from |
4. Step 3: NtCreateProcessEx
With a section handle in hand, the kernel creates the process object. NtCreateProcessEx is the native API that does this. Unlike NtCreateProcess (the older variant), NtCreateProcessEx takes a section handle directly:
HANDLE hProcess = NULL;
// Create a process from the image section
NTSTATUS status = NtCreateProcessEx(
&hProcess,
PROCESS_ALL_ACCESS,
NULL, // object attributes
NtCurrentProcess(), // parent process
0, // flags
hSection, // THE IMAGE SECTION
NULL, // debug port
NULL, // exception port
FALSE // InJob
);
The Critical Insight
NtCreateProcessEx takes a section handle, not a file path. It does not open or read any file. It does not know or care where the section came from. It simply creates a process whose virtual address space is initialized from the section’s mappings. This separation between “creating the section from a file” and “creating a process from a section” is the architectural seam that every PE tampering technique exploits.
When NtCreateProcessEx executes, the kernel:
- Allocates a new
EPROCESSstructure - Creates a new virtual address space
- Maps the image section into the address space at the PE’s preferred base (or a relocated address)
- Maps
ntdll.dllinto the process - Creates the PEB (Process Environment Block)
- Sets the image base address and entry point in the PEB
At this point, the process exists but has no threads — it is not executing. It is an empty vessel with memory mapped from the section.
5. Step 4: Thread Creation and PEB Setup
Before the process can run, it needs process parameters (command line, environment variables, current directory) and an initial thread:
// 1. Create process parameters
PRTL_USER_PROCESS_PARAMETERS processParams = NULL;
RtlCreateProcessParametersEx(
&processParams,
&imagePathUS, // NtImagePath
NULL, // DllPath
NULL, // CurrentDirectory
&imagePathUS, // CommandLine
NULL, // Environment
NULL, NULL, NULL, NULL,
RTL_USER_PROC_PARAMS_NORMALIZED
);
// 2. Write parameters into the target process's memory
// (VirtualAllocEx + WriteProcessMemory to copy params into target PEB)
// 3. Create the initial thread
HANDLE hThread = NULL;
NtCreateThreadEx(
&hThread,
THREAD_ALL_ACCESS,
NULL,
hProcess,
(LPTHREAD_START_ROUTINE)entryPoint, // PE entry point
NULL, // parameter
FALSE, // not suspended
0, 0, 0, // stack sizes
NULL // attribute list
);
Once NtCreateThreadEx is called, the thread starts executing at the PE entry point. The process is now live. The Windows loader (ntdll!LdrInitializeThunk) runs first, processing the import table, loading DLLs, and eventually transferring control to the application entry point.
6. The File-Backed Section Requirement
A fundamental Windows design principle is that every process image is backed by a file on disk via a section object. This creates a chain of trust:
Process Creation Chain
notepad.exe
SEC_IMAGE
EPROCESS
Execution
Security tools rely on this chain. They expect to be able to trace from a running process back to its image file on disk and scan that file. NtQueryInformationProcess with ProcessImageFileName returns the path the kernel stored during section creation. If the file on disk matches what is in memory, the process is legitimate.
What If the Chain Is Broken?
Every PE tampering technique works by breaking some link in this chain. What if the section’s content does not match the file? What if the file no longer exists? What if the file was modified after the section was created? These questions drive the entire family of techniques: Hollowing, Doppelgänging, Herpaderping, and Ghosting.
7. Kernel Structures Overview
| Structure | Created By | Contains |
|---|---|---|
EPROCESS | NtCreateProcessEx | Process metadata, token, handle table, VAD tree, image filename |
PEB | Kernel (during process creation) | Image base, loader data, process parameters, heap info |
SECTION_OBJECT | NtCreateSection | Reference to CONTROL_AREA, segment, size |
CONTROL_AREA | Memory manager | Reference count, file object pointer, subsection chain |
FILE_OBJECT | NtCreateFile/NtOpenFile | File name, device object, current byte offset, flags, section object pointers |
RTL_USER_PROCESS_PARAMETERS | RtlCreateProcessParametersEx | Command line, image path, environment block, current directory |
8. Why This Matters for Ghosting
Process Ghosting exploits the fact that NtCreateProcessEx only needs a section handle. The technique manipulates the file state between creating the section and creating the process. Specifically:
The Ghosting Insight
- Create a file and write malicious PE content to it
- Put the file into the delete-pending state (it is still open, still readable via the handle, but marked for deletion)
- Create a
SEC_IMAGEsection from the delete-pending file — this works because the file handle is still valid - Close the file handle — the file is now deleted from disk
- Create a process from the section — the process runs, but its backing file no longer exists
When AV tries to scan the file at any of its normal inspection points, the file is either in the delete-pending state (cannot be opened by other processes) or already deleted. The process runs from a ghost — a section backed by a file that no longer exists on the filesystem.
Knowledge Check
Q1: What does NtCreateProcessEx take to create a new process?
Q2: What flag must be passed to NtCreateSection to create an executable image section?
Q3: What is the state of a process immediately after NtCreateProcessEx but before NtCreateThreadEx?