The Explainer: Dissecting Process Hollowing
Process hollowing in detail. The seven API calls, what the target process looks like at each step, and the indicators that betray hollowing during analysis.
What hollowing achieves
A process listed in Task Manager as C:\Windows\System32\svchost.exe, signed by Microsoft, with the expected command line — but whose in-memory code is malicious. Hollowing replaces the body of a legitimate process while keeping the wrapper intact. The OS sees a normal process. Antivirus tools that whitelist by image path or signing certificate are bypassed. Behavioural monitors that check process names against expected baselines are bypassed.
This is why hollowing has been a favourite technique of organised crimeware (Dridex, Trickbot, Emotet) and APT operations (Lazarus, FIN7) for over a decade.
The seven steps
1. CreateProcess → spawn target in CREATE_SUSPENDED state
2. ZwQueryProcessInformation → locate the target's PEB, find ImageBaseAddress
3. ReadProcessMemory → read the original image base
4. NtUnmapViewOfSection → unmap the original executable from the target
5. VirtualAllocEx → allocate fresh memory at the same base address
6. WriteProcessMemory → write the malicious payload into the new region
7. SetThreadContext → point the suspended thread at the payload's entry point
ResumeThread → release the thread; the payload runs
Each step is necessary. Skipping any breaks the technique.
Step 1 — Suspended creation
CreateProcessA(
"C:\\Windows\\System32\\svchost.exe", // legitimate target
cmdline, NULL, NULL, FALSE,
CREATE_SUSPENDED, // critical
NULL, NULL, &si, &pi
);
The CREATE_SUSPENDED flag means the process is loaded into memory but its primary thread does not start running. The image is in place, the PEB is set up, the entry point is queued — but no instruction has executed yet. This is the window in which the swap happens.
Step 2 — Locating the PEB and ImageBase
The Process Environment Block (PEB) sits at a known offset in every process. Inside the PEB, ImageBaseAddress points to where the executable image was loaded. The malware needs this address to know where to unmap and where to write the replacement.
ZwQueryProcessInformation (or NtQueryProcessInformation) with class ProcessBasicInformation returns a structure containing the PEB pointer.
Step 3 — NtUnmapViewOfSection
NtUnmapViewOfSection(targetHandle, imageBaseAddress);
This removes the legitimate image from the target's address space. After this call, the address range that held svchost.exe is unmapped — accessing it would fault. The wrapper still holds the process metadata (name, PID, command line) but the body is gone.
Step 4 — Re-allocate at the same base
VirtualAllocEx(targetHandle, imageBaseAddress, sizeOfImage,
MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
The same base address is requested deliberately. Many payloads contain absolute addresses (function pointers, jump tables) that assume their original base. Re-allocating at the original base avoids the need to perform PE base relocations on the payload.
Step 5 — Write the payload
WriteProcessMemory(targetHandle, imageBaseAddress, payloadBytes, sizeOfImage, NULL);
Section by section, the malicious image is written into the target's address space. Headers, code, data, imports — the full PE.
Step 6 — Update the thread context
The suspended thread is currently set to begin at svchost.exe's original entry point. The payload's entry point is at a different offset. We retrieve the thread's CONTEXT structure, update the EAX (32-bit) or RCX (64-bit) register that holds the entry point address, and write it back:
GetThreadContext(targetThreadHandle, &ctx);
ctx.Eax = payloadEntryPoint; // or ctx.Rcx for 64-bit
SetThreadContext(targetThreadHandle, &ctx);
Step 7 — Resume
ResumeThread(targetThreadHandle);
The thread runs. Its first instruction is the payload's entry point. The legitimate svchost.exe code never executes. From now until the process exits, every instruction the OS schedules in this process is malicious code wearing a legitimate uniform.
Detection indicators
Even though hollowing is sophisticated, it leaves traces:
- Image-on-disk vs image-in-memory mismatch. The single strongest indicator. The bytes of the running process do not match the bytes of the file on disk. Tools like
pe-sieveandhollow_huntercheck this directly. - CREATE_SUSPENDED creation. Procmon shows the suspended-state creation. Legitimate uses of
CREATE_SUSPENDEDare rare outside of debuggers and a handful of Microsoft tools. - NtUnmapViewOfSection on the image base. This API call against the calling process's image base is exceedingly rare in benign code. EDRs flag it.
- WriteProcessMemory on a child process. Particularly when followed by
ResumeThread. Some EDRs raise alerts on the combination. - Cross-process thread context modification.
SetThreadContexton a thread in another process, especially modifying the entry point register, is highly suspicious.
A modern EDR generally raises an alert on the API call combination even before the payload runs. This is one reason advanced malware now uses variants — Process Doppelgänging uses TxF transactions, Process Reimaging uses the section cache, Process Ghosting uses delete-pending files. Each variant changes which APIs are involved while preserving the result.
What you should be comfortable with after this lesson
- Naming all seven steps in order and the API for each
- Explaining why
CREATE_SUSPENDEDis essential - Stating which detection indicator each step produces
- Recognising hollowing in a live Procmon log within seconds
References
Scans running processes for replaced or hooked code. The reference tool for hollowing detection.
toolDetailed defender's view of hollowing detection in production environments.
blogSub-technique reference with real-world threat group examples.
mitreExercises
Walk through the API sequence
In a debugger, run any hollowing sample. Set breakpoints on each of the seven APIs in order. At each breakpoint, document the values being passed. By the end, you should have a complete trace of one hollowing event.
Compare in-memory to on-disk
Use pe-sieve against a hollowed process. Examine the dumped images alongside the on-disk svchost.exe. Show that the in-memory image differs.
Spot the suspicious creation
Filter Procmon for 'Process Create' events. Find a CREATE_SUSPENDED creation that is not made by Visual Studio, a debugger, or a known build tool. Treat that as a hollowing candidate.
