Lesson 17.5: Recognizing process injection in malware
This lesson is from the blue-team side. We’re not writing an injector. When you open a malware sample and see it touching another process, you should be able to name the technique, know which tool confirms it, and write indicators to hunt for it again. These are the behaviors malware uses most to run code inside a legitimate process (svchost, explorer, a browser) to hide and get past security products.
Everything here is at the recognition and detection level. No step-by-step instructions, since what you need is an analyst’s eye.
Why malware runs inside another process
A strange process called invoice_2024.exe eating CPU and connecting to a Russian IP makes anyone suspicious. If the same behavior happens inside explorer.exe, it blends into the system background. That’s the motive behind every technique in this lesson, namely borrowing the identity and the address space of a trusted process.
So don’t only look at the sample’s own process, look at the processes it touches too.
Four technique families
You don’t need to memorize their implementations. Remember the behavioral signature so you recognize it when you meet it.
| Technique family | Signature | Traces left behind |
|---|---|---|
| Remote thread injection | The sample opens a handle to another process then creates a thread in it | A thread whose entry point lies outside every module |
| APC injection | Queues work into an existing thread instead of creating a new thread | No new thread, but a strange code region appears |
| Thread hijacking | Suspends a thread, changes its instruction pointer, resumes it | A legitimate thread suddenly executes outside its original module |
| Process hollowing | Creates a legitimate process in a suspended state then swaps its contents | The image on disk differs from the image in memory of the same process |
All four have something in common. Somewhere in the victim process a code region appears that doesn’t correspond to any file on disk. Every detection tool relies on that.
Suspicious sign one: private executable memory with no module
Legit code lives in sections mapped from the .exe/.dll file (memory of type image, tagged with a module name). Injected code lives in a private or mapped region that has execute permission. When you open a suspicious process, go to the Memory tab in Process Hacker / System Informer and filter for regions with execute permission where the “Use” column is empty (not Image). A Private + RX/RWX region is a prime candidate. In the Threads tab, check each thread’s Start Address, since a legit thread points at a named function inside a module, while a thread pointing at a bare, nameless address is suspicious.
RWX permission (writable and executable) is rare in clean software and very common in injected code, since the payload needs to be written and then run. A private RWX region deserves a close look.
Detection tools
You don’t have to inspect each memory region by hand. PE-sieve (Hasherezade) scans a running process, compares each module against the original on disk, finds strange code regions and hooked parts, and dumps them for further analysis. It’s the main tool when you suspect injection. HollowsHunter runs PE-sieve over all processes on the system in one go, good for hunting on a suspected infected machine. Moneta lists memory anomalies (private executable, modified images, missing disk mapping). Process Hacker / System Informer is for manual observation of threads, memory, handles, and dumping suspicious regions.
Procmon and ETW / Sysmon record behavior at the event level. Sysmon Event ID 8 (CreateRemoteThread) and Event ID 25 (process tampering) are the two entries to watch for this class of behavior.
Reversing statically: imports and call chains
Open the sample in IDA/Ghidra, and the imports and capa give you clues before you read any code. capa (Mandiant) infers capabilities and often attaches labels like “inject into process” or “spawn suspended process” along with the related function addresses, so running capa first is a good shortcut.
In the import list, the APIs that manipulate memory and threads of other processes (the versions with the Ex suffix, the matching Nt functions in ntdll) are pointers. A normal program rarely needs to write into another process’s memory. Sophisticated malware hides these APIs (resolving them dynamically via hashed APIs, tying back to Lesson 1.13), so if the imports are too clean while the behavior is suspicious, that’s also a signal.
To see it yourself, set breakpoints at the APIs that write process memory to catch the exact moment the payload is transferred, then dump that payload. That’s how you get the next-stage shellcode/PE without rebuilding the whole logic.
Process hollowing: disk and memory mismatch
Hollowing gets its own section because it’s the most refined disguise, since the process looks right, with the right name and path, but its contents have been replaced. A child process gets created in a suspended state and only then run, which is visible in Procmon/Sysmon logs. The image in memory differs from the image on disk for the same path, and PE-sieve compares exactly this and reports “replaced/hollowed”. The base address or entry point in memory also doesn’t match the original file.
The process tree helps too. A system process with an absurd parent (for example svchost.exe spawned by an Office file) is abnormal, so check with Process Explorer or in EDR.
Turning behavior into hunting indicators
After analyzing a sample, pull out what’s reusable. Record the target victim processes, the API chain observed, and the characteristics of the memory region (size, permissions). Write YARA for the loader part if there’s a stable pattern, and a behavioral rule (Sigma) for the sequence of events creating a remote thread / process tampering. Save the hash of the dumped next-stage payload as an IOC, which connects to Part 19 on IOCs and YARA.
Lab
This lab trains your eye for detection and doesn’t build an injector. Use a suspicious process, such as a malware sample in an isolated VM (see Lesson 0.3), or a demo program of your own.
Open the process in Process Hacker or System Informer. In the Memory tab, look for regions with execute permission whose Use column is not Image (private or mapped executable memory), and write down the address, size and permissions. In the Threads tab, look at each thread’s Start Address and mark any thread that points to an address belonging to no module. Run PE-sieve on that process and read the report. Does it find a foreign code region, a hooked part, or a replaced (hollowed) image? Dump the results. For a sample, use capa to see whether it labels an injection capability and which function addresses are involved. Finally, build a Sysmon or Sigma rule targeting the sequence of events that creates a remote thread or tampers with a process.
Some questions to think about. Why is a private RWX region rare in clean software? How does hollowing differ from ordinary injection when you compare disk with memory? And if a sample’s imports look too clean while its behavior is suspicious, what does that suggest?
Show solution
This is the standard analysis workflow, so do it in an isolated VM when you work with real malware. It needs a Windows GUI and a sample.
For abnormal memory regions, injected code is usually in Private or Mapped memory with execute permission and no module name. A private RWX region is the top candidate because a payload has to be written and then run. Legitimate code lives in Image-type memory (mapped from an .exe or .dll file on disk).
For threads with a strange entry point, a legitimate thread’s Start Address points into a function that belongs to a module (for example kernel32.dll!BaseThreadInitThunk and then the program’s own function). A thread created by injection has a Start Address that is a bare address inside a private region, with no symbol or module name. That’s the sign of CreateRemoteThread or NtCreateThreadEx.
PE-sieve compares each module in the process against the original on disk. Its report classifies findings as implanted (extra code regions), hooked (a modified prologue) and replaced or hollowed (an image that differs from disk). It dumps these regions into a folder so you can open them in IDA or Ghidra for further analysis. HollowsHunter runs the same job across the whole system.
capa reads the binary and infers capabilities, typically printing labels such as “inject into process”, “spawn suspended process” and “write process memory”, with function addresses. Use it as a map before you read by hand.
For Sigma and Sysmon, watch Sysmon Event ID 8 (CreateRemoteThread) and Event ID 25 (process tampering). A behavioral rule raises a flag when one process writes memory and creates a thread in another process, or when a system process has an image that differs from disk.
On the questions. A private RWX region is rare because clean software separates write and execute permissions (W^X). Code from a file is RX and data is RW, so RWX is the pattern of self-generated or injected code. Hollowing keeps the process name and path while the contents (the image in memory) have been replaced, so the sign is a mismatch between the image on disk and the image in memory for the same process. And imports that are too clean while the behavior is suspicious suggest the sample resolves APIs dynamically (hashed or dynamic APIs) to hide its intent, see Lesson 1.13.
Key takeaways
Every injection technique leaves one common mark, which is a code region in the victim process that doesn’t correspond to any file on disk. The suspicious signs are private executable memory (especially RWX) and a thread whose start address isn’t in any module. PE-sieve / HollowsHunter / Moneta automatically detect injected code and hollowing, and Process Hacker is for manual inspection. capa and the import list give static clues, and you set breakpoints at the process-memory-writing APIs to dump the next-stage payload. Hollowing shows up as an in-memory image that differs from the on-disk image, and in an absurd process tree. Turn the results into YARA/Sigma/IOCs to hunt again next time.
