Reputation: 1341
After searching for a way to run a portable executable in memory, I have stumbled upon the same piece of code in around 10 different projects, all with the same black magic hard coded numbers. I refactored it the best I could along with some error handling, here is the final result:
#include <stdint.h>
#include <Windows.h>
unsigned long run_portable_executable(unsigned char* binary)
{
int success = 1, rc = 0;
const uintptr_t binary_address = (uintptr_t)binary;
IMAGE_DOS_HEADER* const dos_header = (IMAGE_DOS_HEADER*)binary;
IMAGE_NT_HEADERS* const nt_header = (IMAGE_NT_HEADERS*)(binary_address + dos_header->e_lfanew);
if (nt_header->Signature != IMAGE_NT_SIGNATURE) {
rc = 1;
goto out;
}
STARTUPINFOW startup_info;
PROCESS_INFORMATION process_info;
SecureZeroMemory(&startup_info, sizeof(startup_info));
SecureZeroMemory(&process_info, sizeof(process_info));
wchar_t current_file_path[MAX_PATH];
GetModuleFileNameW(NULL, current_file_path, MAX_PATH);
success = CreateProcessW(current_file_path, NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &startup_info, &process_info);
if (!success)
goto out;
CONTEXT* const ctx = (CONTEXT*)VirtualAlloc(NULL, sizeof(ctx), MEM_COMMIT, PAGE_READWRITE);
ctx->ContextFlags = CONTEXT_FULL;
success = GetThreadContext(process_info.hThread, ctx);
if (!success)
goto out;
uintptr_t* image_base;
void* const modified_ebx = (void*)(ctx->Ebx + 8);
success = ReadProcessMemory(process_info.hProcess, modified_ebx, &image_base, 4, NULL);
if (!success)
goto out;
void* const binary_base = VirtualAllocEx(process_info.hProcess, (void*)(nt_header->OptionalHeader.ImageBase),
nt_header->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
success = WriteProcessMemory(process_info.hProcess, binary_base, binary, nt_header->OptionalHeader.SizeOfHeaders, NULL);
if (!success)
goto out;
const uintptr_t binary_base_address = (uintptr_t)binary_base;
for (unsigned short i = 0; i < nt_header->FileHeader.NumberOfSections; ++i) {
IMAGE_SECTION_HEADER* section_header = (IMAGE_SECTION_HEADER*)(binary_address + dos_header->e_lfanew + 248 + (i * 40));
void* const virtual_base_address = (void*)(binary_base_address + section_header->VirtualAddress);
void* const virtual_buffer = (void*)(binary_address + section_header->PointerToRawData);
success = WriteProcessMemory(process_info.hProcess, virtual_base_address, virtual_buffer, section_header->SizeOfRawData, 0);
if (!success)
goto out;
}
success = WriteProcessMemory(process_info.hProcess, modified_ebx, (void*)&nt_header->OptionalHeader.ImageBase, 4, 0);
if (!success)
goto out;
ctx->Eax = binary_base_address + nt_header->OptionalHeader.AddressOfEntryPoint;
success = SetThreadContext(process_info.hThread, ctx);
if (!success)
goto out;
success = ResumeThread(process_info.hThread);
if (!success)
goto out;
out:
return !success ? GetLastError() : rc;
}
This works perfectly fine, however, I do not understand several parts.
What does the following pointer point to:
void* const modified_ebx = (void*)(ctx->Ebx + 8);
I am assuming it has to do with the ebx
stack register, but why is it being increased by 8 bytes, what is it supposed to represent?
The next thing that bothered me was the following:
(IMAGE_SECTION_HEADER*)(binary_address + dos_header->e_lfanew + 248 + (i * 40));
My guess would be every file header section is 40 bytes, which explains the i * 40
, but why the 248
offset? Where do these values come from? If there is a specific structure that explains these offsets, I would appreciate if someone could let me know so I could replace it with the proper sizeof()
.
Upvotes: 4
Views: 467
Reputation: 1539
void* const modified_ebx = (void*)(ctx->Ebx + 8);
When RtlUserThreadStart
is called in the main thread of the new created process, ebx
points to the PEB
structure, where offset 8 points to ImageBaseAddress
. You can read more about the PEB
offsets here.
(IMAGE_SECTION_HEADER*)(binary_address + dos_header->e_lfanew + 248 + (i * 40));
248 (0xF8) is the fixed offset from the beginning of PE header to the sections, when indeed every section length is 40 bytes. You can read more about it here.
Upvotes: 5