> For the complete documentation index, see [llms.txt](https://www.adroxz.foo/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://www.adroxz.foo/hollowed-herped-and-pooled-process-masquerading-on-windows.md).

# Hollowed, Herped, and Pooled: Process Masquerading on Windows

Introduction to advanced Windows process injection: Process Hollowing, Ghostly Hollowing, Herpaderping, and Pool Party. Learn internals, code, and detection strategies.

<figure><img src="https://1261874797-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKOwfT2aPvdlOv0PeHSTM%2Fuploads%2FytexgfXv2XTHbfBEIsoL%2Fimage.png?alt=media&amp;token=a03d0a09-bef4-4431-887c-106bc4f68205" alt="" width="365"><figcaption></figcaption></figure>

### INTRODUCTION

Process Injection is one of the most prevalent and used technique since it works by executing the malicious payload inside a legitimate process. with Advanced process injection techniques it is easier to evade detection, blend into normal system activity. Over the years defenders have evolved making stronger detections and defences against injection techniques, this has driven the development of more sophisticated and stealthy techniques that manipulate the very fabric of Windows process and memory management.\
In this blog I'll cover the following techniques, (Covering multiple techniques in a single post is impossible due to the amount of technical details behind them, so I'll cover them from somewhat surfave level, and release detailed blog about each later)-

1. **Process Hollowing** - The classic method of replacing a legitimate process image with malicious code.
2. **Ghostly Hollowing** - A stealthier variant that avoids suspicious unmapping operations.
3. **Herpaderping** - A file-based technique that hides malicious code from disk scanners by exploiting image section objects.
4. **Pool Party** - A novel approach that abuses Windows thread pools for code execution.

***

### 1. Process Hollowing

#### 1.1 Overview

Process hollowing is one of the oldest and most well-known process injection techniques. It was first documented around 2011 and has been used by countless malware families. The core idea is simple: start a legitimate process in a suspended state, hollow out its memory, replace it with malicious code, and then resume execution. The process appears legitimate from the outside (e.g., `svchost.exe`, `explorer.exe`), but its code is entirely attacker-controlled.

The technique relies on the fact that Windows allows processes to be created with the `CREATE_SUSPENDED` flag. This pauses the main thread before any user-mode code runs, giving the attacker a window to modify the process memory.

#### 1.2 Windows Internals and PE Format

To understand process hollowing, one must be familiar with the Portable Executable (PE) format and how Windows loads an executable:

* When a process is created, the Windows loader maps the executable file into memory as an **image section** (a memory-mapped file with `SEC_IMAGE` attribute).
* The PE headers contain crucial information: the preferred base address (`ImageBase`), the section table (virtual address, size, characteristics), and the entry point (AddressOfEntryPoint).
* The process environment block (PEB) contains a pointer to the image base address of the main executable (`PEB->ImageBaseAddress`).\
  In process hollowing, the attacker unmaps the original image and allocates a new memory region at the same (or a different) base address, then manually loads the malicious PE into that region.

#### 1.3 Step-by-Step Implementation

The typical process hollowing procedure involves the following steps:

1. **Create a suspended process**\
   Use `CreateProcess` with a legitimate executable (e.g., `C:\Windows\System32\svchost.exe`) and the `CREATE_SUSPENDED` flag. This ensures the process is initialized but its main thread has not started executing.

```c
CreateProcessW(
    L"C:\\Windows\\System32\\svchost.exe",
    NULL, NULL, NULL, FALSE,
    CREATE_SUSPENDED,
    NULL, NULL, &si, &pi
);
```

2. **Query the thread context**\
   Retrieve the context of the suspended thread to find the process's image base address. The `CONTEXT` structure contains the `Ebx` register (on x86) or `Rdx` register (on x64) that points to the PEB after `NtQueryInformationProcess` is used. More commonly, `GetThreadContext` is used to get the register state, and then `NtQueryInformationProcess` with `ProcessBasicInformation` to get the PEB address. The image base is read from `PEB->ImageBaseAddress`.

```c
CONTEXT ctx;
ctx.ContextFlags = CONTEXT_FULL;
GetThreadContext(pi.hThread, &ctx);
// On x64, ctx.Rdx points to PEB
// Read PEB.ImageBaseAddress from process memory
```

3. **Unmap the original executable image**\
   Call `NtUnmapViewOfSection` (or `ZwUnmapViewOfSection`) on the remote process, passing the image base address. This unmaps the legitimate executable from memory, leaving behind a hollowed process.

```c
NtUnmapViewOfSection(pi.hProcess, (PVOID)imageBase);
```

4. **Allocate new memory for the malicious image**\
   Use `VirtualAllocEx` to allocate a new memory region in the remote process. The allocation should be at the same base address as the original image (or at the preferred base address of the malicious PE) to avoid relocation issues. Permissions are typically `PAGE_EXECUTE_READWRITE` for simplicity.

```c
LPVOID remoteImage = VirtualAllocEx(
    pi.hProcess,
    (LPVOID)preferredBase,
    sizeOfImage,
    MEM_COMMIT | MEM_RESERVE,
    PAGE_EXECUTE_READWRITE
);
```

5. **Write the malicious PE headers and sections**\
   Copy the headers (DOS header, PE header, optional header) to the allocated memory. Then iterate through the section table of the malicious PE, copying each section's raw data to its corresponding virtual address in the remote process. Use `WriteProcessMemory`.

```c
// Write headers
WriteProcessMemory(pi.hProcess, remoteImage, localImage, headersSize, NULL);
// Write each section
for each section:
WriteProcessMemory(pi.hProcess,
    remoteImage + section->VirtualAddress,
    localImage + section->PointerToRawData,
    section->SizeOfRawData,
    NULL);
```

6. **Patch the entry point**\
   The suspended thread's instruction pointer (EIP/RIP) must be redirected to the malicious entry point. Compute the new entry point as `remoteImageBase + AddressOfEntryPoint` and set it in the thread context.

```c
ctx.Rip = (DWORD64)((BYTE*)remoteImage + entryPointRVA);
SetThreadContext(pi.hThread, &ctx);
```

7. **Resume the thread**\
   Call `ResumeThread` to let the process continue execution from the malicious entry point.

```c
ResumeThread(pi.hThread);
```

***

#### 1.5 Detection and Forensics

Process hollowing is relatively easy to detect if you know what to look for:

* **`NtUnmapViewOfSection` calls** - Monitoring for this API is a strong indicator because legitimate processes rarely unmap their own image.
* **Memory permissions** - The newly allocated memory is often `PAGE_EXECUTE_READWRITE` (RWX), which is abnormal for image sections (usually `PAGE_EXECUTE_READ` or `PAGE_READWRITE`).
* **Process memory vs. disk image mismatch** - Compare the in-memory executable sections with the file on disk. In a hollowed process, the memory contents differ significantly.
* **Missing or replaced modules** - The original executable's module is no longer mapped; the module list may be inconsistent.
* **Thread start address anomaly** - The main thread's start address does not correspond to the legitimate entry point of the original image.
* **Use of `GetThreadContext`/`SetThreadContext`** - These APIs are essential to hollowing and can be monitored for unusual cross-process usage.

***

### 2. Ghostly Hollowing

#### 2.1 Overview

Ghostly Hollowing is a process creation technique that produces a running process whose **image is backed by a file that no longer exists on disk**. It avoids the classic `NtUnmapViewOfSection` API used in standard process hollowing and therefore bypasses many behavioral detections that look for that call.

The core idea is to create a **"ghost" file** a file that is created, written, mapped, and deleted before the process is fully spawned. The Windows kernel still treats the memory mapping as an image section, so the loader can initialise the process from it. Once the process is running, the file is gone, leaving no disk artifact for forensic comparison. The process's `PEB->ImageBaseAddress` points to a valid image, but that image is orphaned from the filesystem.

#### 2.2 Windows Internals: Image Sections and File Deletion

When Windows loads an executable, it does not open and read the file directly. Instead, it creates a **section object** backed by the file. For executables, this section is created with the `SEC_IMAGE` attribute. The kernel's memory manager and the PE loader use this section to map the image into the process address space.\
Crucially, the section object holds a reference to the file object. Even if the file is deleted from the filesystem (i.e., the directory entry is removed), the file's underlying data remains accessible as long as any handle or section references it. This is standard Windows behaviour: deleting a file with open handles removes the name but keeps the data until all handles are closed.\
Ghostly Hollowing exploits this in the following way:

1. Create a file and mark it for deletion when its handle is closed (delete-on-close)
2. Write the malicious payload (a complete PE) to the file.
3. Create an image section from that file (using `NtCreateSection` with `SEC_IMAGE`).
4. Close the file handle. Because it was opened with `FILE_DELETE_ON_CLOSE`, the file is deleted from disk. However, the section still references the file object, so the data remains in memory / in the cache.
5. Spawn a new process using that section as its image (via `NtCreateProcessEx`).
6. The process starts normally, but its backing file no longer exists.

#### 2.3 Detection

Ghostly Hollowing is extremely difficult to detect because the process image is not backed by any file. Traditional file‑to‑memory comparisons are impossible. However, several forensic artifacts may reveal its presence:

* **Missing file on disk** - When enumerating the loaded modules of a process (e.g., with `EnumProcessModules`), the full path to the executable is reported. If that path does not exist on disk, it is a strong indicator of Ghostly Hollowing or Process Doppelgänging.
* **Orphaned file objects** - The kernel still holds a file object for the deleted file, which can be discovered by kernel debugging or advanced ETW tracing.
* **Unusual section characteristics** - The image section may have been created with write access (if the attacker used `PAGE_READWRITE` during section creation), leading to RWX memory pages. Legitimate image sections are almost always read‑only or execute‑read.
* **Suspicious process creation** - Monitoring for `NtCreateProcessEx` (especially from non‑system processes) and cross‑process section handles can flag this technique.
* **Memory forensics** - The memory pages of the image will not match any file on disk. Advanced memory scanners can detect that the image is not file‑backed (or is backed by a deleted file) by checking the section object's file pointer.
* **ETW and kernel callbacks** - Events like `ProcessStart` may show a file path that no longer exists. Correlating process creation with file system deletion events can reveal the race.

***

### 3. Herpaderping

#### 3.1 Overview

Herpaderping is a file-based process injection technique that manipulates the Windows image loader to execute code that never appears on disk. It was introduced by Johnny Shaw in 2020 and exploits the way Windows creates executable images from memory-mapped files.\
The name is a playful reference to "herp derp," reflecting the seemingly silly but powerful trick: the file on disk contains benign code, but the process in memory runs malicious code, and after execution, the file can be changed to something else entirely.\
The technique leverages **image section objects** and the fact that once an image section is created, it is independent of the file on disk. If an attacker can pre-create a section object for a file, modify its memory, and then launch a process using that same section, the process will load the modified content while the file remains unchanged.

#### 3.2 Windows Internals: Image Sections and File Mapping

When Windows loads an executable, it does not directly read the file bytes into memory. Instead, it creates a **section object** (a kernel object representing a memory-mapped file) and maps that section into the process's address space. For executables, the section is created with the `SEC_IMAGE` attribute, which tells the memory manager to treat the mapping as a PE image and apply appropriate protections.\
Crucially, multiple processes can share the same section object if they map the same file. The loader checks whether a section object already exists for the file (with compatible attributes) before creating a new one. If it exists, the loader reuses it.\
Herpaderping abuses this reuse. The attack steps are:

1. **Create a file with benign content**\
   Write a legitimate executable (or any file) to disk. This will be the decoy.
2. **Create an image section for that file**\
   Open the file and call `CreateFileMapping` with the `SEC_IMAGE` flag. This creates a section object that the loader will later recognize as an image section.
3. **Map the section into the attacker's address space**\
   Call `MapViewOfFile` to map the section with read/write permissions (`FILE_MAP_WRITE`). This gives the attacker a writable view of the image.
4. **Overwrite the mapped view with malicious code**\
   The attacker writes a malicious PE (or shellcode) into the mapped memory. Because the mapping is writable, this modifies the section's data in memory, but **not the file on disk** (unless the attacker flushes the view, which they do not).
5. **Create a process from the same file path**\
   Call `CreateProcess` using the file path. The Windows loader will attempt to create an image section for that file. Since a section object already exists (and it is marked as an image section), the loader reuses it instead of reading the file from disk. The new process is mapped with the **modified content** from the section object.
6. **Restore the file on disk**\
   After the process is created, the attacker can modify the file on disk to anything (e.g., the original benign content, or even a different file). The running process is unaffected because its image is based on the section object, which is now independent of the file.\
   The result: the process executes malicious code, but the file on disk is benign. Moreover, if the attacker changes the file after process creation, any subsequent disk-based inspection will see a completely different file-hence the name "herpaderping"

#### 3.3 Step-by-Step Technical Implementation

Let's break down the required Windows APIs and sequence:

1. **Create a benign executable file**\
   Write a valid PE file to `C:\Temp\benign.exe`. This file can be a harmless program or even a copy of a trusted executable.
2. **Open the file**\
   Use `CreateFile` with `GENERIC_READ | GENERIC_WRITE` access.

```c
HANDLE hFile = CreateFileW(L"C:\\Temp\\benign.exe",
                        GENERIC_READ | GENERIC_WRITE,
                        FILE_SHARE_READ,
	                    NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
```

1. **Create an image section**\
   Call `CreateFileMapping` with `SEC_IMAGE` flag. The `SEC_IMAGE` flag is crucial; it tells the system that this mapping is for an executable image.

```c
HANDLE hSection = CreateFileMapping(hFile, NULL, PAGE_READONLY | SEC_IMAGE, 0, 0, NULL);
```

*Note:* The `SEC_IMAGE` flag is not publicly documented for `CreateFileMapping`, but it is supported. Alternatively, `NtCreateSection` can be used with `SEC_IMAGE`.\
2\. **Map the section into the current process's address space**\
Use `MapViewOfFile` with `FILE_MAP_WRITE` to get a writable view. Since the section was created as an image, writing to it may require that the section was created with write access. The `SEC_IMAGE` flag may restrict write access; often attackers use `PAGE_READWRITE` instead of `PAGE_READONLY` when creating the mapping, but `SEC_IMAGE` may conflict with write. The original research details using `NtCreateSection` with `SECTION_ALL_ACCESS` and `SEC_IMAGE`. For simplicity, assume it works with proper privileges.

```c
LPVOID mappedView = MapViewOfFile(hSection, FILE_MAP_WRITE, 0, 0, 0); 
```

2. **Overwrite the mapped view with malicious PE**\
   Copy a malicious PE image (headers + sections) into `mappedView`. The size must not exceed the mapped size; otherwise, the mapping must be large enough. The malicious PE should be crafted to fit within the original file's size or the mapping will fail.
3. **Create a process from the same file path**\
   Call `CreateProcess` with the path to `benign.exe`. Because the section object for that file already exists, the loader will use it.

```c
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi;
CreateProcessW(L"C:\\Temp\\benign.exe", NULL, NULL, NULL, FALSE,
                CREATE_SUSPENDED, NULL, NULL, &si, &pi); // Or no suspend
// The process now has the malicious image mapped.
```

4. **Modify the file on disk**\
   After process creation, write any content to the file, e.g., the original benign PE or random data.

```c
// Write benign content back
DWORD written;
WriteFile(hFile, originalBenignBytes, originalSize, &written, NULL); 
```

4. **Clean up**\
   Close handles, unmap view, etc.\
   The key insight is that the loader uses the existing section object, not the file, for the image. Therefore, the process's memory content is the modified malicious image, while the disk file is benign.

***

#### 3.4 Detection

Herpaderping is extremely stealthy because it leaves no trace in the file system that matches the executed code. Detection strategies include:

* **Monitoring for `SEC_IMAGE` section creation with write access** - This is unusual; legitimate loaders create read-only image sections. EDR can hook `NtCreateSection` and flag when `SEC_IMAGE` is combined with write access.
* **File mapping inconsistencies** - Detect when a process's memory image does not match the file on disk. However, herpaderping is designed to create this mismatch, so traditional checks may not work if the file is modified after creation.
* **Kernel-level monitoring** - Since the trick relies on section object reuse, kernel drivers can track when a section object is created and then used by a different process without the file content being read.
* **Behavioral analysis** - Look for processes that were launched from a file that was recently modified or that have an unusual memory layout compared to their disk image.
* **Memory forensics** - Even though the file on disk is benign, the memory of the running process contains the malicious image. Memory scanning can reveal the actual code being executed.

***

### 4. Pool Party

#### 4.1 Overview

Pool Party is a cutting-edge process injection technique disclosed by SafeBreach Labs in 2023. It abuses the Windows **thread pool** mechanism to execute arbitrary code in the context of a target process, completely bypassing traditional injection detection methods that rely on suspicious API calls like `CreateRemoteThread` or `VirtualAllocEx`.\
Thread pools are a Windows facility that manages a set of worker threads to execute asynchronous work items. The internals involve a complex set of structures and APIs. Pool Party exploits these internals by corrupting thread pool work items to redirect execution to attacker-controlled code.\
The technique is notable because it uses only legitimate thread pool APIs (e.g., `CreateThreadpoolWork`, `SubmitThreadpoolWork`) and does not require creating new threads or modifying executable memory in a suspicious way. It can be used for both local and remote injection, though remote injection requires writing to the target process's memory to set up the attack.

#### 4.2 Windows Thread Pool Internals

To understand Pool Party, one must grasp the key components of Windows thread pools:

* **Worker Factory (`TP_WORKER_FACTORY`)** – A kernel object that manages a group of worker threads. Each process has a default worker factory, but additional ones can be created via `TpAllocWork`.
* **Work Items (`TP_WORK`)** – User-mode structures that describe a unit of work to be executed by a worker thread. They contain a callback function pointer and optional parameters.
* **Thread Pool APIs** – Functions like `CreateThreadpoolWork`, `SubmitThreadpoolWork`, `WaitForThreadpoolWorkCallbacks`, etc., allow applications to queue work items.
* **Worker Thread Loop** – Worker threads wait for work items to be posted to the factory's queue. When an item is posted, the thread dequeues it and calls the callback.\
  The internal structures involved include `TP_TASK`, `TP_CALLBACK_INSTANCE`, `TP_WORK`, and the factory's queue. These are not officially documented but have been reverse-engineered.

#### 4.3 How Pool Party Works

SafeBreach's research identified several variants, but the core idea is to manipulate the thread pool's queue or work item structures so that when a worker thread processes a work item, it executes attacker-supplied code instead of the intended callback.\
The general attack flow for **remote injection** (injecting into another process) is:

1. **Obtain a handle to the target process**\
   Open the target process with sufficient rights (`PROCESS_VM_WRITE`, `PROCESS_VM_OPERATION`).
2. **Locate the target's thread pool structures**\
   Find the default worker factory or a specific worker factory. This can be done by reading the PEB (`PEB->TppWorkerpList` or similar) or by scanning memory for known signatures (e.g., the `TP_WORKER_FACTORY` structure has a recognizable pool of threads).
3. **Allocate memory in the target for payload and fake structures**\
   Use `VirtualAllocEx` to allocate a region in the target process. This region will hold the shellcode and possibly a fake `TP_WORK` or `TP_TASK` structure.
4. **Construct a malicious work item or modify an existing one**\
   Overwrite the callback pointer of an existing queued work item or create a new work item with a callback pointing to the shellcode. This involves writing to the thread pool's internal memory.
5. **Submit the work item**\
   Trigger the worker thread to process the malicious work item by calling `SubmitThreadpoolWork` (if operating locally) or by directly manipulating the queue (remotely). When the worker thread dequeues the item and calls the callback, it jumps to the shellcode.\
   The challenge is finding and correctly modifying these undocumented structures. SafeBreach's paper provides detailed reverse-engineered layouts for Windows 10 and 11.

#### 4.4 Variants of Pool Party

SafeBreach documented three primary variants, each exploiting different thread pool components:

1. **Worker Factory Start Routine Hijack**\
   Overwrite the start routine of a worker factory (stored in the `TP_WORKER_FACTORY` structure) to point to shellcode. When a new worker thread is created, it executes the shellcode instead of the normal worker loop. This requires injecting a signal to cause the factory to spawn a new thread (e.g., by queuing many work items).
2. **`TP_TASK` Callback Overwrite**\
   Locate a queued `TP_TASK` structure and overwrite its callback function pointer with the address of the shellcode. When the worker thread processes that task, it calls the shellcode. This is more direct but requires finding a task in the queue.
3. **`TP_WAIT` Object Manipulation**\
   Abuse `TP_WAIT` objects (used for wait operations) by corrupting their internal state to cause a callback to be invoked with attacker-controlled parameters.\
   The paper also describes a "PoolParty" variant that uses `TpAllocWork` and `TpPostWork` to queue work items into a remote process's default thread pool, but these APIs are not officially exported; they are internal to `ntdll.dll`.

#### 4.5 Detection

Pool Party is extremely difficult to detect with traditional user-mode hooks because it uses legitimate thread pool APIs and does not create new threads or allocate executable memory in a suspicious manner. However, some detection strategies include:

* **Memory integrity monitoring** – Watch for modifications to thread pool internal structures (e.g., callback pointers) in other processes. This requires deep hooking or kernel callbacks.
* **Behavioral analysis** – Monitor for unusual thread pool activity: a process suddenly queuing many work items, or a worker thread executing code from a region that was recently written with `PAGE_EXECUTE_READWRITE`.
* **Kernel-level telemetry** – Use ETW (Event Tracing for Windows) to trace thread pool operations and detect anomalies.
* **Memory scanning for non-image executable memory** – The shellcode is placed in dynamically allocated memory, which is not backed by a file. This can be flagged by memory scanners looking for RWX pages.
* **API monitoring for remote memory writes** – Although `WriteProcessMemory` is used, it may be called with low frequency. Still, monitoring cross-process writes to sensitive areas can help.

*Disclaimer: This article is for educational purposes only. The techniques described should only be used in authorized environments.*
