Here is the code I currently have:
#include <Windows.h>
DWORD run_portable_executable(unsigned char* binary)
{
BOOL success;
const DWORD binary_address = (DWORD)binary;
IMAGE_DOS_HEADER* const dos_header = (LPVOID)binary;
IMAGE_NT_HEADERS* const nt_header = (LPVOID)(binary_address + dos_header->e_lfanew);
STARTUPINFOW startup_info;
PROCESS_INFORMATION process_info;
// Zero the structs to ensure valid values.
SecureZeroMemory(&startup_info, sizeof(startup_info));
SecureZeroMemory(&process_info, sizeof(process_info));
WCHAR current_file_path[MAX_PATH];
GetModuleFileNameW(NULL, current_file_path, MAX_PATH);
// Use the current executable as a dummy process to be taken over by the binary.
success = CreateProcessW(current_file_path, NULL, NULL, NULL, FALSE,
CREATE_SUSPENDED, NULL, NULL, &startup_info, &process_info);
if (!success)
goto err;
CONTEXT ctx =
{
.ContextFlags = CONTEXT_FULL
};
success = GetThreadContext(process_info.hThread, &ctx);
if (!success)
goto err;
// The following will occasionally fail because the fixed address of 0x400000 might
// not be available or might not contain enough space.
LPVOID const pe_base = VirtualAllocEx(process_info.hProcess,
(LPVOID)nt_header->OptionalHeader.ImageBase,
nt_header->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
if (!pe_base)
goto err;
success = WriteProcessMemory(process_info.hProcess, pe_base, binary,
nt_header->OptionalHeader.SizeOfHeaders, NULL);
if (!success)
goto err;
const DWORD pe_base_address = (DWORD)pe_base;
const DWORD end_of_pe_header =
binary_address + dos_header->e_lfanew + sizeof(IMAGE_NT_HEADERS);
for (WORD i = 0; i < nt_header->FileHeader.NumberOfSections; ++i)
{
const DWORD section_offset = i * sizeof(IMAGE_SECTION_HEADER);
const IMAGE_SECTION_HEADER* const section_header =
(LPVOID)(end_of_pe_header + section_offset);
LPVOID const section_base_address =
(LPVOID)(pe_base_address + section_header->VirtualAddress);
LPCVOID const section_binary_buffer =
(LPVOID)(binary_address + section_header->PointerToRawData);
success = WriteProcessMemory(process_info.hProcess, section_base_address,
section_binary_buffer, section_header->SizeOfRawData, NULL);
if (!success)
goto err;
}
// Ebx points to the PEB struct, where the 8 byte offset points to the
// ImageBaseAddress member.
LPVOID const modified_ebx = (LPVOID)(ctx.Ebx + 8);
success = WriteProcessMemory(process_info.hProcess, modified_ebx,
&nt_header->OptionalHeader.ImageBase, sizeof(DWORD), NULL);
if (!success)
goto err;
ctx.Eax = pe_base_address + nt_header->OptionalHeader.AddressOfEntryPoint;
success = SetThreadContext(process_info.hThread, &ctx);
if (!success)
goto err;
success = ResumeThread(process_info.hThread);
if (!success)
goto err;
return 0;
err:
return GetLastError();
}
This will work most of the time. The problem is here:
VirtualAllocEx(process_info.hProcess, (LPVOID)nt_header->OptionalHeader.ImageBase,
nt_header->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
This will occasionally fail and return the 487
error code. I have found a highly relevant question about this error which explained exactly what was going wrong. This virtual allocation would always happen at the fixed address of 0x400000
(default for executables), but if that address was currently unavailable at the time, it would return the 487
error code (invalid address).
My question is how should I handle this? I cannot simply set the second argument passed into VirtualAllocEx()
to NULL
as it would not match with the current image. The question I linked suggests using ReBaseImage()
, but I do not know how this would be done for a portable executable in memory. The function requires the path to the .exe
or .dll
to then write the changes made to the image. How could this be done in memory?
Edit: RbMm proposed to relocate the image using LdrProcessRelocationBlock
, a function from ntdll.dll
which has the following signature:
IMAGE_BASE_RELOCATION* WINAPI LdrProcessRelocationBlock(ULONG_PTR VA, ULONG SizeOfBlock,
PUSHORT NextOffset, LONG_PTR Diff)
However, I am not sure how this could be used to move the image to rebase the image as the 3rd party documentation of this method is scarce. If anyone is familiar with its usage, an example would be highly appreciated.
The Portable Executable (PE) format is a file format for executables, object code, DLLs and others used in 32-bit and 64-bit versions of Windows operating systems. The PE format is a data structure that encapsulates the information necessary for the Windows OS loader to manage the wrapped executable code.
The Portable Executable format is the standard file format for executables, object code and Dynamic Link Libraries (DLLs) used in 32- and 64-bit versions of Windows operating systems.
Portable Executable (PE, PE/COFF, PE32, PE32+) is a member of the EXE family of executable file formats. It is used by the Microsoft Windows family of operating systems (starting with Windows 95 and Win32s), EFI and sometimes in other environments. It is an extension/hybrid of MS-DOS EXE, and a successor to NE.
The . pdata section is an array of RUNTIME_FUNCTION. It gives you a code range (first two members) and an RVA to the corresponding UNWIND_INFO. From there you get info like exception handler RVA, size of prolog, etc.
Rebasing the PE image requires fixing up RVAs using the PE file's relocation table (usually in the .reloc
section). If VirtualAlloc
does not return memory at the preferred base address, you will indeed have to call VirtualAlloc
again with NULL
and perform fixups to the address space it returned you.
A public implementation of this is available from hasherezade's libpeconv library.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With