Skip to content

Module Stomping (DLL Hollowing)

Loading a benign signed DLL into a target process and overwriting its code section with shellcode, so the malicious memory is backed by a legitimate module and evades unbacked-memory scanners.

Module stomping (also called DLL hollowing) sidesteps the biggest tell of most injection techniques — private, executable, unbacked memory. Instead of allocating fresh RWX memory, the injector loads a real, signed, otherwise-unused DLL into the target, then overwrites that module's .text section with shellcode via WriteProcessMemory. The malicious code now lives inside a legitimately mapped, image-backed, signed module, so scanners that hunt for unbacked executable regions see nothing unusual.

How it works

The attacker picks a benign DLL the target does not actually use, forces it to load (locally or remotely, e.g. via LoadLibrary in the target), locates the module's .text region, makes it writable, and stomps shellcode over the original code. Execution is then directed at the stomped code (remote thread, APC, or a hijacked thread pointed at the overwritten offset).

text
LoadLibrary("benign_signed.dll")           // map a real signed module in target
base = GetModuleHandle("benign_signed.dll")
VirtualProtectEx(hProc, base+textRVA, len, PAGE_EXECUTE_READWRITE, &old)
WriteProcessMemory(hProc, base+textRVA, shellcode, len)  // stomp .text
VirtualProtectEx(hProc, base+textRVA, len, PAGE_EXECUTE_READ, &old)
CreateRemoteThread(hProc, NULL, 0, base+textRVA, arg, 0, NULL)  // run stomped code

Detection & bypass

  • Static — The injector still imports WriteProcessMemory and VirtualProtectEx alongside LoadLibrary/GetModuleHandle. The distinguishing static cue is the absence of VirtualAllocEx for the code region — the shellcode target is an existing module base rather than fresh allocation. Hardcoded benign-DLL names that the process has no reason to use can hint at the chosen stomp host.
  • Dynamic — Watch for WriteProcessMemory whose destination falls inside a mapped module's image range (not private memory), frequently bracketed by VirtualProtectEx flipping that module's code to writable then back to executable. A remote thread or APC starting inside a signed module but at a non-export, mid-.text offset is suspicious.
  • Memory forensics — The region is image-backed and signed, so unbacked-memory scans miss it; the giveaway is an image/disk byte mismatch. Moneta and pe-sieve compare in-memory module pages against the on-disk DLL and flag modified code / hash mismatch in the .text of the stomped module. Look for private+executable or modified pages within an otherwise mapped, named module.
  • Tools — pe-sieve (/imp, modified-code detection), Moneta (modified image / private-in-mapped flags), Process Hacker (module memory inspection), API Monitor (WriteProcessMemory/VirtualProtectEx targets), Sysmon/ETW, and Volatility (ldrmodules + page-hash comparison against disk).
Votes

Comments(0)