Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Run a portable executable in memory - WinApi

Tags:

c

winapi

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.

like image 660
Michael Smith Avatar asked Feb 27 '18 01:02

Michael Smith


People also ask

What is a Microsoft Portable executable?

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.

What is portable executable in C#?

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.

What is a PE32 executable?

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.

What is .pdata section?

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.


1 Answers

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.

like image 79
Samantha Avatar answered Sep 19 '22 09:09

Samantha