Understanding Process Hollowing

Process hollowing is a stealthy technique used in malware development where an attacker starts a legitimate process and then replaces its executable code with malicious code. This allows the attacker to run their payload while maintaining the illusion that a trusted application is running. To understand how this works, we need to look at how executables are structured in memory and what happens when an executable is unmapped and replaced.

Process memory regions

Virtual memory of a process

When a program is loaded into memory, the operating system sets up several key memory regions:

  • .text: Contains executable code (CPU instructions).
  • .rodata: Stores read-only data like constant strings.
  • .data: Holds initialized global variables.
  • .bss: Stores uninitialized global variables.
  • Stack: Manages function calls and local variables.
  • Heap: Dynamically allocated memory at runtime.
  • ARGV & ENV: Store command-line arguments and environment variables.

A running process consists of these sections, along with metadata that the OS maintains to track the process.

Process hollowing explained

1. Creating a Suspended Process

The attacker begins by launching a legitimate executable (e.g., notepad.exe) in a suspended state. This means the OS initializes the process structure but does not start executing its code. The process memory at this stage contains its usual sections: .text, .data, .bss, stack, and heap.

2. Unmapping the Original Code

Once the process is created, the attacker calls the NtUnmapViewOfSection function to remove the original executable from memory. This step erases the .text, .rodata, and other related sections while keeping the process’s overall structure intact. The stack, heap, and OS metadata remain unchanged, allowing the process to maintain its identity despite losing its executable code.

At this stage, the process still exists, but its executable instructions are gone. The memory where .text once existed is now an empty, unallocated space.

3. Allocating Memory for the Malicious Code

With the original code removed, the attacker allocates new memory at the same address where the .text section was previously located. This allocation ensures that the new code will execute correctly without breaking the process’s structure. The attacker writes their own PE (Portable Executable) structure into this memory, including a new .text section containing malicious instructions.

4. Modifying Execution Context

Once the malicious payload is written, the attacker updates the process’s execution context using SetThreadContext. This modifies the instruction pointer (EIP/RIP) so that, when the process resumes, execution begins in the injected code rather than the original entry point.

5. Resuming the Process

Finally, the process is resumed using ResumeThread. From the OS’s perspective, the original application appears to be running, but in reality, it is executing entirely different code.

Illustrations

+----------------------------+
|  Attacker's Executable     |
+------------+---------------+
             |
             | (1) Create a Suspended Process
             v
+----------------------------------+
|  Target Process (Suspended)      |
+----------------------------------+
             |
             | (2) Unmap Original Code (NtUnmapViewOfSection)
             v
+----------------------------------+
|  Target Process (Code Unmapped)  |
+----------------------------------+
             |
             | (3) Allocate Memory in the Target Process
             v
+----------------------------------+
|  Target Process (Memory Allocated) |
+----------------------------------+
             |
             | (4) Write Malicious Code (WriteProcessMemory)
             v
+----------------------------------+
|  Target Process (Malicious Code Injected) |
+----------------------------------+
             |
             | (5) Update Entry Point (SetThreadContext)
             v
+----------------------------------+
|  Target Process (Entry Point Modified) |
+----------------------------------+
             |
             | (6) Resume Process (ResumeThread)
             v
+----------------------------------+
|  Target Process (Executes Malicious Code) |
+----------------------------------+