Запустить переносимый исполняемый файл в памяти - WinApi

Вот код, который у меня есть:

#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();
}

Это будет работать большую часть времени. Проблема здесь:

VirtualAllocEx(process_info.hProcess, (LPVOID)nt_header->OptionalHeader.ImageBase, 
    nt_header->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE,
        PAGE_EXECUTE_READWRITE);

Иногда это приведет к сбою и возврату кода ошибки 487. Я нашел очень важный вопрос об этой ошибке, которая точно объяснила, что происходит. Это виртуальное распределение всегда происходит с фиксированным адресом 0x400000 (по умолчанию для исполняемых файлов), но если этот адрес в настоящее время недоступен, он вернет код ошибки 487 (неверный адрес).

Мой вопрос: как я должен справиться с этим? Я не могу просто установить второй аргумент, переданный в VirtualAllocEx() в NULL поскольку он не соответствует текущему изображению. Вопрос, который я задал, предполагает использование ReBaseImage(), но я не знаю, как это будет сделано для переносимого исполняемого файла в памяти. Функция требует путь к .exe или .dll чтобы затем записать изменения, сделанные на изображение. Как это можно сделать в памяти?

Изменить: RbMm предложил переместить изображение с помощью LdrProcessRelocationBlock, функции из LdrProcessRelocationBlock ntdll.dll которая имеет следующую подпись:

IMAGE_BASE_RELOCATION* WINAPI LdrProcessRelocationBlock(ULONG_PTR VA, ULONG SizeOfBlock,
    PUSHORT NextOffset, LONG_PTR Diff)

Тем не менее, я не уверен, как это можно использовать для перемещения изображения для восстановления изображения, поскольку сторонняя документация по этому методу недостаточна. Если кто-то знаком с его использованием, пример будет высоко оценен.

Ответ 1

Для восстановления изображения PE требуется исправление RVAs с использованием таблицы перемещения PE файлов (обычно в разделе .reloc). Если VirtualAlloc не возвращает память на предпочтительном базовом адресе, вам действительно придется снова вызвать VirtualAlloc с помощью NULL и выполнить исправления в адресное пространство, которое оно вам вернуло.

Публичную реализацию этого можно получить из библиотеки haspezade libpeconv.