skip to content

How do you show from a memory image that code was injected into a signed running process?

level: seniorimportance: must knowfreq 58%

answer

  1. the process list is correct here
  2. private, committed, and executable
  3. who mapped a PE header by hand
  4. where does the thread start
  5. a runtime compiler does this legitimately

basics

~10 s

Read the address space, not the process list. Look for private committed regions marked executable with no file backing them, a PE header in anonymous memory, and threads starting outside every loaded module.

solid answer

~50 s

The process list will be correct, so the evidence has to come from inside the process. Walk its region map and look for regions that are **private** (no mapped file behind them) yet marked executable - unbacked code. Then strengthen it: an `MZ`/PE header at the base of such a region, a thread whose start address falls inside it rather than inside any loaded module, a module present in the region map but absent from the loader's module list, and network endpoints owned by that process id that its role does not explain. Process hollowing looks different: the region is still image-backed, but its in-memory content diverges from the file on disk. The discipline is in the false positives - runtimes that compile code at execution time allocate private executable memory constantly. The discriminators are the PE header, the thread origin and the network ownership, not the permission bits alone.

code

text · 8 lines
text
Process: w3wp.exe   PID 4188   Image: C:\Windows\System32\inetsrv\w3wp.exe (signed)

Base 0x00007ff6a2b10000  Size 0x1e000  PAGE_EXECUTE_READ        Backing: w3wp.exe (image)
Base 0x000001d4c0a30000  Size 0x0c000  PAGE_EXECUTE_READWRITE   Backing: <private>   first bytes: 48 89 5C 24 ...
Base 0x000001d4c1f80000  Size 0x21000  PAGE_EXECUTE_READWRITE   Backing: <private>   first bytes: 4D 5A 90 00 ...
...
Thread TID 5512  start address 0x000001d4c1f81a30  -> inside no loaded module
Endpoint  10.20.4.19:49731 -> 203.0.113.44:443  ESTABLISHED  owner PID 4188

go deeper

for a junior

Know that injected code lives inside another process's memory, that it may have no file on disk, and that the process list will look completely normal when it happens.

for a middle

Explain the region map: image-backed versus private, what execute permission on a private region means, and why a PE header in anonymous memory is a stronger signal than the permission bits.

for a senior

Work the corroboration and the false positives together - thread start addresses, module-list divergence, owned endpoints, and why a managed application host legitimately allocates private executable memory.

for a principal

Decide what the organisation does with this: whether memory inspection is a routine capability or an escalation-only one, and how much of the estate's normal region-map behaviour must be baselined before anyone can act on the signal.

## Why the process list cannot help An operator injects a payload into a process that is already running: a signed, correctly installed Windows binary on a production application server. No file is written, no process is created, no unusual parent-child pair appears. The image on disk is genuinely untampered and its signature genuinely verifies. Every enumeration-based check passes. The evidence, if it exists at all, is inside one process's address space. ## The region map is the instrument Windows describes each process's address space as a set of regions, each with a size, a protection value and - crucially - a **backing**. A region is either *image-backed* (it maps a portion of an executable file the loader opened), *file-backed* (it maps some other file) or **private** (it maps nothing; the kernel simply gave the process memory). Legitimate program code lives in image-backed regions, because that is what loading an executable does. So the first-order question is: **is there private, committed memory in this process that carries execute permission?** That is code with no file behind it. Nothing the loader did put it there. ### Strengthening the finding Unbacked executable memory alone is suggestive; it is the corroboration that makes it defensible. - **A PE header in anonymous memory.** If the first bytes of a private executable region are `MZ` and a valid PE structure follows, someone mapped a whole executable image by hand rather than through the loader. Compilers that generate code at runtime emit bare instructions, not full PE images. - **Thread origin.** Every thread has a start address. A thread whose start address falls inside a private executable region - or anywhere outside every loaded module - is executing code the loader never loaded. - **Module-list divergence.** The loader keeps a per-process list of loaded modules. A module visible in the region map but missing from that list was mapped deliberately outside the loader, or unlinked afterwards. - **Ownership of network state.** If the kernel's endpoint structures show an outbound connection owned by that process id and the process's function does not explain it, the injected code has a purpose you can name. - **Handles and access.** A separate process holding a handle to this one with rights sufficient to write memory and create a remote thread is the other half of the story, when that process is still alive. ### Hollowing looks different Process hollowing does not add a private region; it replaces the contents of one that is image-backed. The tell is a **comparison**: read the mapped image's bytes out of memory and compare them against the file on disk that the region claims to map. Legitimate divergence exists - the loader applies relocations and patches the import table - so compare section characteristics and code bytes with that in mind rather than expecting a byte-for-byte match. A base address recorded in the process's own environment block that disagrees with the module list is another signal. ## The false positives are the hard part This is where candidates separate. Runtimes that compile code while the program runs - managed application hosts, Java virtual machines, browser script engines - allocate private executable memory continuously and by design. On an IIS worker process running managed code, private executable regions are the *normal* condition, and an analyst who flags every one of them will drown a purple-team report in noise and lose the argument. The discriminators hold up: JIT-emitted code has no PE header, its threads belong to the runtime and start inside runtime modules, and it does not open a long-lived connection to an address nobody can account for. Other benign sources of unbacked or unusual executable memory include software packers used by legitimate products, and security tooling that hooks processes on purpose. ## What the finding actually proves Be careful with the claim. The image shows that executable, unbacked memory existed in that process and that a thread ran from it. It proves code executed that the loader did not load. It does not, on its own, prove *what* the code did - and the analysis of the recovered payload itself is a different discipline. In an exercise context the useful statement is narrower and stronger: the technique executed on this host, it is visible in memory, and nothing in the estate recorded it - no rule fired and no sensor produced a record - which is a statement about coverage, evidenced by the image. ## The signature argument When someone objects that the host process is signed, the answer is that Authenticode covers a *file* and is verified when it is loaded. Nothing re-verifies a process's memory afterwards. The injected thread inherits the host's name, its signature, its user context and often its network reputation - which is the entire reason the technique is used.

  • What distinguishes classic injection from process hollowing in the image?
    Injection adds a private executable region alongside the real image, so the anomaly is a region the loader never created. Hollowing keeps the image-backed region but replaces its contents, so the anomaly is a mismatch: the bytes mapped in memory no longer correspond to the file the region claims to map. Allow for relocations and import patching when comparing, and check the base address the process itself reports against the loader's module list.
  • The host process is signed by Microsoft and the signature verifies. Why does that not help?
    The signature covers the file on disk and is checked when the loader maps it. Nothing re-verifies the process's memory afterwards, so an injected thread runs under a signed name, a signed image path and the host's user context. That inheritance is the point of the technique, which is why process name, signature and reputation are worthless as evidence for this question.
  • How do you avoid flagging every managed application host as injected?
    Accept that runtimes which compile code at execution time allocate private executable memory as normal behaviour, so the permission bits alone are not a finding. Require corroboration: a PE header in anonymous memory, a thread starting outside every loaded module, a module absent from the loader's list, or an unexplained network endpoint owned by the process. Baseline what the region map looks like on a healthy host of the same role.

saying these in an interview costs you the question

  • Treats the host process's signature as exonerating
  • Calls every executable private region malicious, ignoring runtime compilation
  • Says a clean process list rules out injection
  • Thinks hollowing changes the process name or path
  • Claims the region map proves what the injected code did

context