Запустить переносимый исполняемый файл в памяти - 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.