Difficulty: Beginner

Module 3: File System States & Delete-Pending

Understanding the NTFS delete-pending state — the key mechanism that makes Process Ghosting possible.

Module Objective

Understand how NTFS file deletion really works at the kernel level, the delete-pending state, the difference between FILE_DELETE_ON_CLOSE and FileDispositionInformation, how mapped files interact with deletion, and why the delete-pending state creates a window where the file exists (for the handle owner) but is inaccessible (to everyone else).

1. How File Deletion Actually Works in Windows

When you call DeleteFile in Win32, the file does not immediately vanish from disk. Instead, Windows follows a multi-stage process that involves marking the file and then removing it only when all handles are closed. The NT-native view reveals the actual mechanics:

The Two-Stage Deletion Model

  1. Stage 1 — Mark for deletion: The file is marked as “delete-pending.” The MFT (Master File Table) entry is flagged, but the file’s data and metadata remain intact. The file still exists in the namespace.
  2. Stage 2 — Actual removal: When the last handle to the file is closed, the filesystem completes the deletion. The directory entry is removed and the data clusters are freed.

This two-stage model exists because Windows must handle the case where a file is in use when deletion is requested. Unlike Unix (where unlink immediately removes the directory entry while keeping the inode alive for existing file descriptors), Windows keeps the directory entry until all handles close.

2. Two Ways to Mark a File for Deletion

There are two mechanisms to put a file into the delete-pending state:

2.1 FILE_DELETE_ON_CLOSE (at open time)

The FILE_DELETE_ON_CLOSE flag is specified when opening or creating a file. It tells the filesystem to delete the file when the last handle with this flag is closed:

// FILE_DELETE_ON_CLOSE - set at file open time
HANDLE hFile;
IO_STATUS_BLOCK iosb;
OBJECT_ATTRIBUTES objAttr;

NtCreateFile(
    &hFile,
    DELETE | GENERIC_WRITE,   // DELETE access required
    &objAttr,
    &iosb,
    NULL,
    FILE_ATTRIBUTE_NORMAL,
    0,                         // no sharing
    FILE_SUPERSEDE,            // create or replace
    FILE_DELETE_ON_CLOSE,      // <-- delete when handle closes
    NULL, 0
);

2.2 FileDispositionInformation (after open)

The NtSetInformationFile call with FileDispositionInformation can mark an already-open file for deletion. This is the approach Process Ghosting uses:

// FileDispositionInformation - set AFTER file is open
FILE_DISPOSITION_INFORMATION dispInfo;
dispInfo.DeleteFile = TRUE;

IO_STATUS_BLOCK iosb;
NTSTATUS status = NtSetInformationFile(
    hFile,                           // existing open handle
    &iosb,
    &dispInfo,
    sizeof(dispInfo),
    FileDispositionInformation       // information class
);

Why Ghosting Uses FileDispositionInformation

Process Ghosting uses NtSetInformationFile with FileDispositionInformation rather than FILE_DELETE_ON_CLOSE because it needs to control the order of operations: create the file, mark it delete-pending, write the payload, then create the section. The FILE_DELETE_ON_CLOSE flag does not allow this fine-grained control because the delete-on-close behavior is tied to handle closure, not to an explicit API call.

PropertyFILE_DELETE_ON_CLOSEFileDispositionInformation
When setAt NtCreateFile timeAnytime via NtSetInformationFile
Reversible?No (once set, cannot be cleared)Yes (set DeleteFile = FALSE to unmark)
Access rights neededDELETE at open timeDELETE on the handle
When deletion occursWhen handle with this flag closesWhen the last handle closes
Used by GhostingNoYes

3. The Delete-Pending State in Detail

Once a file is marked for deletion (by either mechanism), it enters the delete-pending state. This is a real state tracked by the filesystem driver (NTFS). While in this state:

Delete-Pending State Properties

OperationFrom Existing HandleFrom New Open Attempt
Read fileSucceeds (handle is still valid)Fails: STATUS_DELETE_PENDING
Write fileSucceeds (handle is still valid)Fails: STATUS_DELETE_PENDING
Create sectionSucceeds (handle is still valid)Cannot open file to create section
Open file (NtCreateFile/NtOpenFile)N/AFails: STATUS_DELETE_PENDING
Delete file againNo-op (already pending)Fails: STATUS_DELETE_PENDING
List in directoryMay still appear (implementation-dependent)

The critical insight is the asymmetry: the handle owner can still do everything with the file (read, write, create sections), but no one else can open the file. This is exactly the property Process Ghosting exploits.

4. Interaction with Section Objects

A key question for Process Ghosting is: can you create a section from a delete-pending file? The answer is yes, as long as you use the existing open handle.

// This works: create section from delete-pending file via existing handle
HANDLE hSection = NULL;
NTSTATUS status = NtCreateSection(
    &hSection,
    SECTION_ALL_ACCESS,
    NULL,
    NULL,
    PAGE_READONLY,
    SEC_IMAGE,
    hFile           // handle to delete-pending file - WORKS
);

Once the section is created, it holds a reference to the underlying file object in the kernel. Even when the file handle is closed and the file is deleted from disk, the section persists because the kernel keeps the file object alive through the section’s CONTROL_AREAFILE_OBJECT reference chain.

What About Existing Mapped Sections?

Windows prevents deletion of files that have existing image sections mapped. If you try to delete a file that is currently mapped as a SEC_IMAGE section (e.g., a running executable), you get STATUS_CANNOT_DELETE. Process Ghosting avoids this by marking the file delete-pending before creating the section. The order matters: mark delete first, then create section.

5. The Ghosting Order of Operations

The specific order in which operations are performed is critical. Here is why each step must happen in this exact sequence:

Step-by-Step Rationale

  1. NtCreateFile — Creates the file. It must exist to write to it and create a section from it.
  2. NtSetInformationFile (FileDispositionInformation) — Marks the file delete-pending. This must happen before writing the payload, because once the payload is written and a section is created, the file would have an image section mapped, which would prevent deletion. By marking delete-pending first, the deletion is already queued.
  3. NtWriteFile — Writes the malicious PE content. The file is delete-pending but the handle is still valid for writes.
  4. NtCreateSection (SEC_IMAGE) — Creates the image section. The file is delete-pending and contains the malicious PE. The section captures this content.
  5. NtClose (hFile) — Closes the file handle. Since the file is delete-pending, closing the last handle triggers actual deletion. The file vanishes from disk.
  6. NtCreateProcessEx — Creates the process from the section. The section is still valid even though the backing file is gone.

File State Transitions

Created
Normal file
Delete-Pending
Marked, still open
Written + Sectioned
PE content, section created
Deleted
Handle closed, gone from disk

6. STATUS_DELETE_PENDING vs STATUS_FILE_DELETED

When another process (like an AV scanner) tries to open a file in these states, it receives different error codes depending on timing:

File StateNtCreateFile/NtOpenFile ResultMeaning
Delete-pending (handle still open)STATUS_DELETE_PENDING (0xC0000056)File exists in MFT but is queued for deletion. Cannot open.
Deleted (all handles closed)STATUS_OBJECT_NAME_NOT_FOUND (0xC0000034)File no longer exists in the directory namespace.

In both cases, the AV scanner cannot open or read the file. This is the fundamental evasion mechanism of Process Ghosting. The file is either locked by the delete-pending state or gone entirely.

7. NTFS Internals: The MFT Perspective

At the NTFS level, deletion involves the Master File Table (MFT) entry for the file:

MFT Entry Lifecycle During Ghosting

PhaseMFT StateDirectory EntryData Clusters
File createdAllocated, flags = IN_USEPresent in parent directory indexAllocated to file
Delete-pendingStill IN_USE, pending delete flagStill present (may be hidden from some queries)Still allocated
After last handle closesMarked free (IN_USE cleared)Removed from parent directory indexFreed to bitmap

The key point is that during the delete-pending phase, the MFT entry and data clusters are fully intact. The file’s data (our malicious PE) is physically present on disk and readable through the existing handle. It is only inaccessible to processes that try to open it, because NTFS rejects new opens for delete-pending files.

8. Practical Implications for Security Tools

AV Scanner Perspective

When a process creation notification fires (via PsSetCreateProcessNotifyRoutineEx, which fires at first thread insertion via NtCreateThreadEx, not at process object creation), the AV driver receives the FILE_OBJECT of the image backing the process. The driver typically tries to read this file to scan its content. But if the file is delete-pending or already deleted:

This creates a blind spot: the process is running, but its backing file cannot be scanned through conventional means.

Knowledge Check

Q1: What happens when another process tries to open a file that is in the delete-pending state?

A) The open succeeds but reads return zeroes
B) The open fails with STATUS_DELETE_PENDING
C) The open succeeds in read-only mode
D) The file is immediately deleted and the open fails with STATUS_OBJECT_NAME_NOT_FOUND

Q2: Why does Process Ghosting use NtSetInformationFile (FileDispositionInformation) instead of FILE_DELETE_ON_CLOSE?

A) It allows controlling the exact moment the file is marked for deletion, enabling the correct order of operations
B) FILE_DELETE_ON_CLOSE does not work on NTFS
C) FileDispositionInformation deletes the file faster
D) FILE_DELETE_ON_CLOSE requires administrator privileges

Q3: Can you create a SEC_IMAGE section from a file that is in the delete-pending state using the existing open handle?

A) No, the kernel rejects section creation from delete-pending files
B) Only if the file has PAGE_EXECUTE protection
C) Yes, the existing handle is still fully functional for section creation
D) Only with SEC_COMMIT, not SEC_IMAGE