/*
 * COFFLoader.c -- Complete COFF loader with full Beacon compatibility
 * Step 07: Loads a COFF object file, resolves all symbols (including
 *          Beacon API internal functions and DLL imports), applies
 *          relocations, and executes the BOF entry point.
 *
 * Usage:  COFFLoader.exe <function_name> <coff_file.o>
 *         COFFLoader.exe go hello_bof.o
 *
 * The loader works in these phases:
 *   1. Read the COFF file into memory
 *   2. Parse the COFF header and locate sections/symbols
 *   3. Allocate executable memory for each section (VirtualAlloc)
 *   4. Populate the InternalFunctions table with Beacon API pointers
 *   5. Resolve all external symbols (internal or DLL-imported)
 *   6. Apply relocations to patch code with resolved addresses
 *   7. Find and call the requested entry point function
 *   8. Retrieve any captured output and clean up
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <stdint.h>
#include "COFFLoader.h"
#include "beacon_compatibility.h"

/* ================================================================
 * functionMapping -- tracks resolved addresses for each symbol
 *
 * After symbol resolution, functionMapping[i] holds the address
 * that symbol i should resolve to (either an internal function,
 * a DLL export, or a section-relative offset).
 * ================================================================ */
static void** functionMapping = NULL;

/* ================================================================
 * Section memory tracking
 *
 * We need to VirtualFree each allocated section at cleanup.
 * ================================================================ */
#define MAX_SECTIONS 32
static void*  sectionMemory[MAX_SECTIONS]  = {0};
static size_t sectionSizes[MAX_SECTIONS]   = {0};
static int    sectionCount                 = 0;


/* ================================================================
 * populate_internal_functions -- fill the InternalFunctions table.
 *
 * Maps Beacon API function names to their implementation pointers.
 * The loader's process_symbol() searches this table when it
 * encounters a __imp_BeaconXxx or __imp_toWideChar symbol.
 * ================================================================ */
static void populate_internal_functions(void) {
    int i = 0;

    /* Data parsing API */
    InternalFunctions[i][0] = (unsigned char*)"BeaconDataParse";
    InternalFunctions[i][1] = (unsigned char*)BeaconDataParse;
    i++;

    InternalFunctions[i][0] = (unsigned char*)"BeaconDataInt";
    InternalFunctions[i][1] = (unsigned char*)BeaconDataInt;
    i++;

    InternalFunctions[i][0] = (unsigned char*)"BeaconDataShort";
    InternalFunctions[i][1] = (unsigned char*)BeaconDataShort;
    i++;

    InternalFunctions[i][0] = (unsigned char*)"BeaconDataLength";
    InternalFunctions[i][1] = (unsigned char*)BeaconDataLength;
    i++;

    InternalFunctions[i][0] = (unsigned char*)"BeaconDataExtract";
    InternalFunctions[i][1] = (unsigned char*)BeaconDataExtract;
    i++;

    /* Output API */
    InternalFunctions[i][0] = (unsigned char*)"BeaconPrintf";
    InternalFunctions[i][1] = (unsigned char*)BeaconPrintf;
    i++;

    InternalFunctions[i][0] = (unsigned char*)"BeaconOutput";
    InternalFunctions[i][1] = (unsigned char*)BeaconOutput;
    i++;

    /* Format buffer API */
    InternalFunctions[i][0] = (unsigned char*)"BeaconFormatAlloc";
    InternalFunctions[i][1] = (unsigned char*)BeaconFormatAlloc;
    i++;

    InternalFunctions[i][0] = (unsigned char*)"BeaconFormatReset";
    InternalFunctions[i][1] = (unsigned char*)BeaconFormatReset;
    i++;

    InternalFunctions[i][0] = (unsigned char*)"BeaconFormatFree";
    InternalFunctions[i][1] = (unsigned char*)BeaconFormatFree;
    i++;

    InternalFunctions[i][0] = (unsigned char*)"BeaconFormatAppend";
    InternalFunctions[i][1] = (unsigned char*)BeaconFormatAppend;
    i++;

    InternalFunctions[i][0] = (unsigned char*)"BeaconFormatPrintf";
    InternalFunctions[i][1] = (unsigned char*)BeaconFormatPrintf;
    i++;

    InternalFunctions[i][0] = (unsigned char*)"BeaconFormatToString";
    InternalFunctions[i][1] = (unsigned char*)BeaconFormatToString;
    i++;

    InternalFunctions[i][0] = (unsigned char*)"BeaconFormatInt";
    InternalFunctions[i][1] = (unsigned char*)BeaconFormatInt;
    i++;

    /* Token / Process API */
    InternalFunctions[i][0] = (unsigned char*)"BeaconUseToken";
    InternalFunctions[i][1] = (unsigned char*)BeaconUseToken;
    i++;

    InternalFunctions[i][0] = (unsigned char*)"BeaconRevertToken";
    InternalFunctions[i][1] = (unsigned char*)BeaconRevertToken;
    i++;

    InternalFunctions[i][0] = (unsigned char*)"BeaconIsAdmin";
    InternalFunctions[i][1] = (unsigned char*)BeaconIsAdmin;
    i++;

    InternalFunctions[i][0] = (unsigned char*)"BeaconGetSpawnTo";
    InternalFunctions[i][1] = (unsigned char*)BeaconGetSpawnTo;
    i++;

    InternalFunctions[i][0] = (unsigned char*)"BeaconSpawnTemporaryProcess";
    InternalFunctions[i][1] = (unsigned char*)BeaconSpawnTemporaryProcess;
    i++;

    InternalFunctions[i][0] = (unsigned char*)"BeaconInjectProcess";
    InternalFunctions[i][1] = (unsigned char*)BeaconInjectProcess;
    i++;

    InternalFunctions[i][0] = (unsigned char*)"BeaconInjectTemporaryProcess";
    InternalFunctions[i][1] = (unsigned char*)BeaconInjectTemporaryProcess;
    i++;

    InternalFunctions[i][0] = (unsigned char*)"BeaconCleanupProcess";
    InternalFunctions[i][1] = (unsigned char*)BeaconCleanupProcess;
    i++;

    /* Utility */
    InternalFunctions[i][0] = (unsigned char*)"toWideChar";
    InternalFunctions[i][1] = (unsigned char*)toWideChar;
    i++;

    InternalFunctions[i][0] = (unsigned char*)"BeaconGetOutputData";
    InternalFunctions[i][1] = (unsigned char*)BeaconGetOutputData;
    i++;

    /* Sentinel -- remaining slots stay NULL */
}


/* ================================================================
 * get_symbol_name -- resolve a COFF symbol's name.
 *
 * Short names (<= 8 chars) are stored inline in the symbol entry.
 * Longer names use an offset into the string table (located right
 * after the symbol table).
 * ================================================================ */
static char* get_symbol_name(coff_sym_t* sym, char* stringTable) {
    static char shortName[9];

    /* If first 4 bytes are zero, name is in string table */
    if (sym->first.value[0] == 0) {
        return stringTable + sym->first.value[1];
    }

    /* Otherwise, copy the inline name (may not be null-terminated) */
    memcpy(shortName, sym->first.Name, 8);
    shortName[8] = '\0';
    return shortName;
}


/* ================================================================
 * process_symbol -- resolve a single external symbol.
 *
 * Two kinds of imports:
 *   1. __imp_BeaconXxx  -- internal Beacon API, found in InternalFunctions
 *   2. __imp_LIBRARY$Function -- DLL import, split on '$', use
 *      LoadLibraryA + GetProcAddress
 *
 * Returns the resolved function pointer, or NULL on failure.
 * ================================================================ */
static void* process_symbol(char* symbolName) {
    char  libName[256];
    char  funcName[256];
    char* dollar;
    char* importName;
    int   j;
    HMODULE hLib;
    void* proc;

    /* Strip the __imp_ prefix to get the real name */
    if (strncmp(symbolName, "__imp_", 6) == 0) {
        importName = symbolName + 6;
    } else {
        importName = symbolName;
    }

    /* Check InternalFunctions table first */
    for (j = 0; j < 30; j++) {
        if (InternalFunctions[j][0] == NULL) {
            break;  /* end of populated entries */
        }
        if (strcmp((char*)InternalFunctions[j][0], importName) == 0) {
            return (void*)InternalFunctions[j][1];
        }
    }

    /* Check for LIBRARY$Function pattern (DLL import) */
    dollar = strchr(importName, '$');
    if (dollar != NULL) {
        /* Extract library name (before $) */
        size_t libLen = dollar - importName;
        if (libLen >= sizeof(libName)) {
            libLen = sizeof(libName) - 1;
        }
        memcpy(libName, importName, libLen);
        libName[libLen] = '\0';

        /* Extract function name (after $) */
        strncpy(funcName, dollar + 1, sizeof(funcName) - 1);
        funcName[sizeof(funcName) - 1] = '\0';

        /* Load the DLL and get the function address */
        hLib = LoadLibraryA(libName);
        if (hLib == NULL) {
            printf("[-] Failed to load library: %s\n", libName);
            return NULL;
        }

        proc = (void*)GetProcAddress(hLib, funcName);
        if (proc == NULL) {
            printf("[-] Failed to find function: %s!%s\n", libName, funcName);
            return NULL;
        }

        return proc;
    }

    /* Symbol not found in either table */
    return NULL;
}


/* ================================================================
 * main -- COFF loader entry point
 * ================================================================ */
int main(int argc, char* argv[]) {
    FILE*               fp       = NULL;
    unsigned char*      fileData = NULL;
    long                fileSize = 0;
    coff_file_header_t* header   = NULL;
    coff_sect_t*        sections = NULL;
    coff_sym_t*         symbols  = NULL;
    char*               stringTable = NULL;
    int                 i, j;
    char*               entryName = NULL;
    void*               entryPoint = NULL;

    /* --- Argument validation --- */
    if (argc < 3) {
        printf("Usage: %s <function_name> <coff_file.o>\n", argv[0]);
        printf("Example: %s go hello_bof.o\n", argv[0]);
        return 1;
    }

    entryName = argv[1];
    printf("[*] Loading COFF file: %s\n", argv[2]);
    printf("[*] Entry function:    %s\n", entryName);

    /* ============================================================
     * Phase 1: Read the COFF file into memory
     * ============================================================ */
    fp = fopen(argv[2], "rb");
    if (fp == NULL) {
        printf("[-] Failed to open file: %s\n", argv[2]);
        return 1;
    }

    fseek(fp, 0, SEEK_END);
    fileSize = ftell(fp);
    fseek(fp, 0, SEEK_SET);

    if (fileSize < (long)sizeof(coff_file_header_t)) {
        printf("[-] File too small to be a valid COFF object\n");
        fclose(fp);
        return 1;
    }

    fileData = (unsigned char*)malloc(fileSize);
    if (fileData == NULL) {
        printf("[-] Memory allocation failed\n");
        fclose(fp);
        return 1;
    }

    fread(fileData, 1, fileSize, fp);
    fclose(fp);

    /* ============================================================
     * Phase 2: Parse the COFF header
     * ============================================================ */
    header = (coff_file_header_t*)fileData;

    printf("[*] Machine:           0x%04X (%s)\n", header->Machine,
           header->Machine == MACHINE_AMD64 ? "x64" : "x86");
    printf("[*] Sections:          %d\n", header->NumberOfSections);
    printf("[*] Symbols:           %d\n", header->NumberOfSymbols);

    if (header->Machine != MACHINE_AMD64 && header->Machine != MACHINE_I386) {
        printf("[-] Unsupported machine type: 0x%04X\n", header->Machine);
        free(fileData);
        return 1;
    }

    /* Locate section headers, symbol table, and string table */
    sections = (coff_sect_t*)(fileData + sizeof(coff_file_header_t));
    symbols  = (coff_sym_t*)(fileData + header->PointerToSymbolTable);
    stringTable = (char*)(symbols + header->NumberOfSymbols);

    /* ============================================================
     * Phase 3: Load sections into executable memory
     *
     * Each section gets its own VirtualAlloc with RWX permissions.
     * This is intentionally permissive for simplicity -- a
     * production loader would set proper page protections.
     * ============================================================ */
    printf("[+] Loading %d sections into memory\n", header->NumberOfSections);

    sectionCount = 0;
    for (i = 0; i < header->NumberOfSections; i++) {
        size_t allocSize = sections[i].SizeOfRawData;
        void*  mem;

        /* Uninitialized data (.bss) may have zero raw data size */
        if (allocSize == 0) {
            allocSize = sections[i].VirtualSize;
        }
        if (allocSize == 0) {
            allocSize = 1;  /* at least 1 byte so VirtualAlloc succeeds */
        }

        mem = VirtualAlloc(
            NULL, allocSize,
            MEM_COMMIT | MEM_RESERVE,
            PAGE_EXECUTE_READWRITE
        );
        if (mem == NULL) {
            printf("[-] VirtualAlloc failed for section %d\n", i);
            goto cleanup;
        }

        /* Zero-fill then copy raw data if present */
        memset(mem, 0, allocSize);
        if (sections[i].SizeOfRawData > 0 && sections[i].PointerToRawData > 0) {
            memcpy(mem, fileData + sections[i].PointerToRawData,
                   sections[i].SizeOfRawData);
        }

        sectionMemory[i] = mem;
        sectionSizes[i]  = allocSize;
        sectionCount++;

        printf("    Section %d [%.8s]: %zu bytes at %p\n",
               i, sections[i].Name, allocSize, mem);
    }

    /* ============================================================
     * Phase 4: Set up InternalFunctions and functionMapping
     * ============================================================ */
    populate_internal_functions();

    /* Allocate one slot per symbol for resolved addresses */
    functionMapping = (void**)calloc(header->NumberOfSymbols, sizeof(void*));
    if (functionMapping == NULL) {
        printf("[-] Failed to allocate function mapping table\n");
        goto cleanup;
    }

    /* ============================================================
     * Phase 5: Symbol resolution
     *
     * Walk every symbol in the COFF symbol table:
     *   - EXTERNAL with SectionNumber==0: unresolved import
     *     -> process_symbol() resolves from InternalFunctions or DLL
     *   - EXTERNAL/STATIC with SectionNumber>0: defined in a section
     *     -> compute address = sectionBase + Value
     *   - Skip aux symbols
     * ============================================================ */
    printf("[+] Resolving symbols\n");

    for (i = 0; i < (int)header->NumberOfSymbols; i++) {
        char* name = get_symbol_name(&symbols[i], stringTable);

        if (symbols[i].StorageClass == IMAGE_SYM_CLASS_EXTERNAL &&
            symbols[i].SectionNumber == 0) {
            /* Unresolved external -- resolve via process_symbol */
            void* resolved = process_symbol(name);
            if (resolved == NULL) {
                printf("    [!] Unresolved symbol: %s\n", name);
            } else {
                printf("    [+] Resolved external: %s -> %p\n", name, resolved);
            }
            functionMapping[i] = resolved;
        }
        else if (symbols[i].SectionNumber > 0) {
            /* Symbol defined in a section -- compute its address */
            int secIdx = symbols[i].SectionNumber - 1;  /* 1-based -> 0-based */
            if (secIdx < sectionCount) {
                functionMapping[i] = (char*)sectionMemory[secIdx] +
                                     symbols[i].Value;
            }
        }

        /* Skip auxiliary symbol entries */
        i += symbols[i].NumberOfAuxSymbols;
    }

    /* ============================================================
     * Phase 6: Apply relocations
     *
     * For each section with relocations, iterate its reloc entries.
     * Each reloc says: "at offset VirtualAddress in this section,
     * patch the value using symbol SymbolTableIndex with type Type."
     * ============================================================ */
    printf("[+] Applying relocations\n");

    for (i = 0; i < header->NumberOfSections; i++) {
        coff_reloc_t* relocs;
        int           numRelocs;

        if (sections[i].NumberOfRelocations == 0) {
            continue;
        }

        relocs    = (coff_reloc_t*)(fileData + sections[i].PointerToRelocations);
        numRelocs = sections[i].NumberOfRelocations;

        for (j = 0; j < numRelocs; j++) {
            uint32_t  symIdx    = relocs[j].SymbolTableIndex;
            uint16_t  type      = relocs[j].Type;
            char*     patchSite = (char*)sectionMemory[i] + relocs[j].VirtualAddress;
            void*     symAddr   = functionMapping[symIdx];

            if (symAddr == NULL) {
                char* symName = get_symbol_name(&symbols[symIdx], stringTable);
                printf("    [!] Skipping reloc for unresolved symbol: %s\n", symName);
                continue;
            }

            if (header->Machine == MACHINE_AMD64) {
                /* --- AMD64 relocations --- */
                switch (type) {
                    case IMAGE_REL_AMD64_ADDR64: {
                        /* 64-bit absolute address */
                        uint64_t* target = (uint64_t*)patchSite;
                        *target += (uint64_t)symAddr;
                        break;
                    }
                    case IMAGE_REL_AMD64_ADDR32NB: {
                        /* 32-bit relative to image base (RVA) */
                        int32_t* target = (int32_t*)patchSite;
                        *target = (int32_t)((char*)symAddr - patchSite - 4);
                        break;
                    }
                    case IMAGE_REL_AMD64_REL32: {
                        /* 32-bit PC-relative, standard */
                        int32_t* target = (int32_t*)patchSite;
                        *target = (int32_t)((char*)symAddr - patchSite - 4);
                        break;
                    }
                    case IMAGE_REL_AMD64_REL32_1: {
                        /* 32-bit PC-relative with 1-byte displacement */
                        int32_t* target = (int32_t*)patchSite;
                        *target = (int32_t)((char*)symAddr - patchSite - 4 - 1);
                        break;
                    }
                    case IMAGE_REL_AMD64_REL32_2: {
                        /* 32-bit PC-relative with 2-byte displacement */
                        int32_t* target = (int32_t*)patchSite;
                        *target = (int32_t)((char*)symAddr - patchSite - 4 - 2);
                        break;
                    }
                    case IMAGE_REL_AMD64_REL32_3: {
                        /* 32-bit PC-relative with 3-byte displacement */
                        int32_t* target = (int32_t*)patchSite;
                        *target = (int32_t)((char*)symAddr - patchSite - 4 - 3);
                        break;
                    }
                    case IMAGE_REL_AMD64_REL32_4: {
                        /* 32-bit PC-relative with 4-byte displacement */
                        int32_t* target = (int32_t*)patchSite;
                        *target = (int32_t)((char*)symAddr - patchSite - 4 - 4);
                        break;
                    }
                    case IMAGE_REL_AMD64_REL32_5: {
                        /* 32-bit PC-relative with 5-byte displacement */
                        int32_t* target = (int32_t*)patchSite;
                        *target = (int32_t)((char*)symAddr - patchSite - 4 - 5);
                        break;
                    }
                    default:
                        printf("    [!] Unknown AMD64 reloc type: 0x%04X\n", type);
                        break;
                }
            }
            else if (header->Machine == MACHINE_I386) {
                /* --- i386 relocations --- */
                switch (type) {
                    case IMAGE_REL_I386_DIR32: {
                        /* 32-bit absolute address */
                        uint32_t* target = (uint32_t*)patchSite;
                        *target += (uint32_t)(uintptr_t)symAddr;
                        break;
                    }
                    case IMAGE_REL_I386_REL32: {
                        /* 32-bit PC-relative */
                        int32_t* target = (int32_t*)patchSite;
                        *target = (int32_t)((char*)symAddr - patchSite - 4);
                        break;
                    }
                    default:
                        printf("    [!] Unknown I386 reloc type: 0x%04X\n", type);
                        break;
                }
            }
        }
    }

    /* ============================================================
     * Phase 7: Find and execute the entry point
     *
     * Scan symbols for the requested function name (typically "go").
     * The entry point has the signature: void go(char* args, int len)
     * ============================================================ */
    printf("[+] Searching for entry point: %s\n", entryName);

    entryPoint = NULL;
    for (i = 0; i < (int)header->NumberOfSymbols; i++) {
        char* name = get_symbol_name(&symbols[i], stringTable);

        if (strcmp(name, entryName) == 0 &&
            symbols[i].SectionNumber > 0) {
            int secIdx = symbols[i].SectionNumber - 1;
            entryPoint = (char*)sectionMemory[secIdx] + symbols[i].Value;
            printf("[+] Found entry point: %s at %p\n", name, entryPoint);
            break;
        }

        i += symbols[i].NumberOfAuxSymbols;
    }

    if (entryPoint == NULL) {
        printf("[-] Entry point '%s' not found in COFF\n", entryName);
        goto cleanup;
    }

    /* Call the BOF entry point with NULL args */
    printf("[+] Executing BOF entry point...\n");
    printf("--- BOF output begin ---\n");

    typedef void (*bof_entry_t)(char*, int);
    ((bof_entry_t)entryPoint)(NULL, 0);

    printf("--- BOF output end ---\n");

    /* ============================================================
     * Phase 8: Retrieve captured output
     *
     * If the BOF used BeaconPrintf/BeaconOutput, the text was
     * accumulated in the beacon_compatibility output buffer.
     * Retrieve and display it.
     * ============================================================ */
    {
        int   outSize = 0;
        char* output  = BeaconGetOutputData(&outSize);

        if (output != NULL && outSize > 0) {
            printf("[+] Captured BOF output (%d bytes):\n", outSize);
            printf("%.*s", outSize, output);
            if (output[outSize - 1] != '\n') {
                printf("\n");
            }
            free(output);
        } else {
            printf("[*] No buffered output captured\n");
        }
    }

    printf("[+] BOF execution complete\n");

    /* ============================================================
     * Cleanup: free all allocated memory
     * ============================================================ */
cleanup:
    for (i = 0; i < sectionCount; i++) {
        if (sectionMemory[i] != NULL) {
            VirtualFree(sectionMemory[i], 0, MEM_RELEASE);
            sectionMemory[i] = NULL;
        }
    }

    if (functionMapping != NULL) {
        free(functionMapping);
        functionMapping = NULL;
    }

    if (fileData != NULL) {
        free(fileData);
    }

    return 0;
}
