#include #include #include #include struct Patcher { COFFI::coffi objReader; COFFI::coffi peReader; std::string inputFile; std::string outputFile; COFFI::symbol *mainSymbol = nullptr; COFFI::section *mainSection = nullptr; COFFI::section *textSection = nullptr; COFFI::section *rdataSection = nullptr; uint64_t imageBase = 0; uint32_t mainOffset = 0; uint32_t mainSize = 0; bool loadFiles() { std::string objPath = SRC_OBJECT; SPDLOG_INFO("Loading object file: {}", objPath); if (!objReader.load(objPath)) { spdlog::error("Failed to load object file: {}", objPath); return false; } SPDLOG_INFO("Loading PE file: {}", inputFile); if (!peReader.load(inputFile)) { spdlog::error("Failed to load PE file: {}", inputFile); return false; } // Get image base auto winHeader = peReader.get_win_header(); if (winHeader) { imageBase = winHeader->get_image_base(); } spdlog::info("PE Image base: 0x{:x}", imageBase); return true; } bool findMainSymbol() { auto &symbols = *objReader.get_symbols(); for (auto &sym : symbols) { SPDLOG_INFO("Symbol: {}", sym.get_name()); if (sym.get_name() == "_ref") { mainSymbol = &sym; break; } } if (!mainSymbol) { spdlog::error("Could not find 'main' symbol in object file"); return false; } // Get the section containing the main function auto §ions = objReader.get_sections(); mainSection = sections[mainSymbol->get_section_number() - 1]; mainOffset = mainSymbol->get_value(); return true; } uint32_t calculateMainSize() { auto &symbols = *objReader.get_symbols(); uint32_t nextSymbolOffset = UINT32_MAX; // Find the next symbol in the same section to calculate size for (auto &sym : symbols) { if (sym.get_section_number() == mainSymbol->get_section_number() && sym.get_value() > mainOffset && sym.get_value() < nextSymbolOffset) { nextSymbolOffset = sym.get_value(); } } if (nextSymbolOffset != UINT32_MAX) { mainSize = nextSymbolOffset - mainOffset; spdlog::info( "Calculated main function size: {} bytes (next symbol at offset {})", mainSize, nextSymbolOffset); } else { // If no next symbol found, use remaining section size mainSize = mainSection->get_data_size() - mainOffset; spdlog::info( "No next symbol found, using remaining section size: {} bytes", mainSize); } return mainSize; } COFFI::section *findSection(const std::string &name, bool isPE = true) { auto §ions = isPE ? peReader.get_sections() : objReader.get_sections(); for (auto §ion : sections) { if (section->get_name() == name) { return section; } } return nullptr; } uint32_t findAvailableSpaceAtEnd(COFFI::section *section) { auto sectionData = section->get_data(); uint32_t sectionSize = section->get_data_size(); const char *sectionEnd = sectionData + sectionSize; uint32_t availableSpace = 0; // Search backwards from the end to find contiguous null bytes const char *ptr = sectionEnd - 1; while (ptr > sectionData) { if (*ptr != 0x00) { break; } availableSpace++; ptr--; } // Keep 1 byte marging, might be another string or terminator return availableSpace - 1; } void logMainFunctionCode() { auto mainCodeData = mainSection->get_data(); spdlog::info("Found main function at offset {} with size {}", mainOffset, mainSize); spdlog::info("Main function code:"); std::string s; for (uint32_t i = 0; i < mainSize; i++) { if (i > 0 && i % 16 == 0) { spdlog::info("{}", s); s.clear(); } if (s.size() > 0) s += " "; s += fmt::format("{:02X}", mainCodeData[i]); } if (s.size() > 0) spdlog::info("{}", s); } void logRelocations() { auto §ionRelocations = mainSection->get_relocations(); spdlog::info("Relocations for section {} (containing main function):", mainSymbol->get_section_number()); for (auto &reloc : sectionRelocations) { spdlog::info(" Relocation at offset 0x{:x}, type: {}, symbol index: {}", reloc.get_virtual_address(), reloc.get_type(), reloc.get_symbol_table_index()); spdlog::info(" -> Symbol: {}", reloc.get_symbol()); } } void logSectionData(COFFI::section *section) { uint32_t sectionRVA = section->get_virtual_address(); uint32_t sectionSize = section->get_virtual_size(); uint64_t sectionVA = imageBase + sectionRVA; uint64_t sectionEndVA = sectionVA + sectionSize; spdlog::info("Section {} RVA: 0x{:x}, size: 0x{:x}", section->get_name(), sectionRVA, sectionSize); spdlog::info("Section {} VA: 0x{:x} - 0x{:x}", section->get_name(), sectionVA, sectionEndVA); } bool validateSections() { textSection = findSection(".text"); if (!textSection) { spdlog::error("Could not find .text section in PE file"); return false; } logSectionData(textSection); rdataSection = findSection(".rdata"); if (!rdataSection) { spdlog::error("Could not find .rdata section in PE file"); return false; } logSectionData(rdataSection); return true; } // Tries to find an import in the input PE file and return its IAT address uint32_t findImportInPE(const std::string &functionName) { auto &directories = peReader.get_directories(); // Import table is directory entry 1 if (directories.size() <= 1 || !directories[1]) { spdlog::error("No import directory found in PE file"); return 0; } uint32_t importRVA = directories[1]->get_virtual_address(); uint32_t importSize = directories[1]->get_size(); if (importRVA == 0 || importSize == 0) { spdlog::error("Invalid import directory"); return 0; } spdlog::info("Import directory at RVA: 0x{:x}, size: 0x{:x}", importRVA, importSize); // Find which section contains the import table COFFI::section *importSection = nullptr; uint32_t importFileOffset = 0; auto §ions = peReader.get_sections(); for (auto §ion : sections) { uint32_t sectionRVA = section->get_virtual_address(); uint32_t sectionSize = section->get_virtual_size(); if (importRVA >= sectionRVA && importRVA < sectionRVA + sectionSize) { importSection = section; importFileOffset = importRVA - sectionRVA; break; } } if (!importSection) { spdlog::error("Could not find section containing import table"); return 0; } // Parse import descriptors const char *sectionData = importSection->get_data(); const char *importData = sectionData + importFileOffset; // Import descriptor structure (20 bytes each) struct ImportDescriptor { uint32_t originalFirstThunk; // RVA to import lookup table uint32_t timeDateStamp; uint32_t forwarderChain; uint32_t nameRVA; // RVA to DLL name uint32_t firstThunk; // RVA to import address table }; const ImportDescriptor *desc = reinterpret_cast(importData); // Iterate through import descriptors for (int i = 0; desc[i].nameRVA != 0; i++) { // Get DLL name std::string dllName = getRVAString(desc[i].nameRVA); spdlog::info("Checking imports from DLL: {}", dllName); // Parse the import lookup table uint32_t lookupRVA = desc[i].originalFirstThunk; uint32_t iatRVA = desc[i].firstThunk; if (lookupRVA == 0) lookupRVA = iatRVA; // Use IAT if no lookup table const uint32_t *lookupTable = reinterpret_cast(getRVAData(lookupRVA)); const uint32_t *iatTable = reinterpret_cast(getRVAData(iatRVA)); if (!lookupTable || !iatTable) continue; // Iterate through function imports for (int j = 0; lookupTable[j] != 0; j++) { uint32_t nameRVA = lookupTable[j]; // Skip ordinal imports (high bit set) if (nameRVA & 0x80000000) continue; // Get function name (skip hint, first 2 bytes) const char *funcNamePtr = getRVAString(nameRVA + 2); if (!funcNamePtr) continue; std::string funcName(funcNamePtr); if (funcName == functionName) { uint32_t iatAddress = imageBase + iatRVA + (j * sizeof(uint32_t)); spdlog::info("Found import '{}' in {} at IAT address: 0x{:x}", functionName, dllName, iatAddress); return iatAddress; } } } spdlog::error("Could not find import: {}", functionName); return 0; } bool processRelocations(char *textStart, char *textEnd, char *&rdataPtr) { auto &symbols = *objReader.get_symbols(); auto §ions = objReader.get_sections(); auto §ionRelocations = mainSection->get_relocations(); uint32_t rdataSize = rdataSection->get_data_size(); char *textSectionStart = const_cast(textSection->get_data()); const char *textSectionEnd = textSectionStart + textSection->get_data_size(); const char *rdataEndPtr = rdataSection->get_data() + rdataSize; char *rdataStart = const_cast(rdataSection->get_data()); // char* rdataEndPtr = rdataPtr + rdataSize; size_t textStartRVA = (textStart - textSectionStart) + textSection->get_virtual_address(); size_t textEndRVA = (textEnd - textSectionStart) + textSection->get_virtual_address(); spdlog::info("Processing relocations..."); for (auto &reloc : sectionRelocations) { if (reloc.get_type() != 6) continue; // Only handle type 6 (IMAGE_REL_I386_DIR32) uint32_t relocRVA = reloc.get_virtual_address() + textStartRVA; std::string symbolName = reloc.get_symbol(); uint32_t resolvedAddress = 0; spdlog::info("Processing relocation at VA 0x{:x} for symbol: {}", relocRVA + imageBase, symbolName); if (symbolName.starts_with("__imp__")) { // Import symbol handling std::string functionName = symbolName.substr(7); size_t atPos = functionName.find('@'); if (atPos != std::string::npos) { functionName = functionName.substr(0, atPos); } spdlog::info("Looking for import function: {}", functionName); resolvedAddress = findImportInPE(functionName); } else if (symbolName.starts_with("??_C@")) { // String constant handling COFFI::symbol *constSymbol = nullptr; for (auto &sym : symbols) { if (sym.get_name() == symbolName) { constSymbol = &sym; break; } } if (constSymbol) { auto constSection = sections[constSymbol->get_section_number() - 1]; auto constData = constSection->get_data(); uint32_t constOffset = constSymbol->get_value(); // Find string length uint32_t stringLen = strlen(constData + constOffset); stringLen++; // Include null terminator if (rdataPtr + stringLen > rdataEndPtr) { spdlog::error("Not enough space in .rdata for string constant"); return false; } // Copy string to .rdata std::memcpy(rdataPtr, constData + constOffset, stringLen); size_t rdataRVA = (rdataPtr - rdataStart) + rdataSection->get_virtual_address(); resolvedAddress = imageBase + rdataRVA; spdlog::info("Copied string constant '{}' to .rdata at RVA 0x{:x} " "(VA: 0x{:x})", std::string(constData + constOffset), rdataRVA, resolvedAddress); rdataPtr += stringLen; } } // Apply the relocation if (resolvedAddress != 0) { uint32_t patchOffset = relocRVA - textSection->get_virtual_address(); uint32_t *ptr = reinterpret_cast(textSectionStart + patchOffset); ptr[0] = resolvedAddress; spdlog::info( "Applied relocation: patched offset 0x{:x} with address 0x{:x}", relocRVA + imageBase, resolvedAddress); } } return true; } bool patchAndSave() { // Check available space in both sections uint32_t textAvailableSpace = findAvailableSpaceAtEnd(textSection); uint32_t rdataAvailableSpace = findAvailableSpaceAtEnd(rdataSection); spdlog::info("Found {} bytes of available space (null bytes) at end of " ".text section", textAvailableSpace); spdlog::info("Found {} bytes of available space in .rdata section", rdataAvailableSpace); if (textAvailableSpace < mainSize) { spdlog::error( "Not enough space in .text section! Need {} bytes, found {} bytes", mainSize, textAvailableSpace); return false; } // Calculate injection points uint32_t textSize = textSection->get_data_size(); uint32_t rdataSize = rdataSection->get_data_size(); uint32_t textInjectionOffset = textSize - textAvailableSpace; uint32_t rdataInjectionOffset = rdataSize - rdataAvailableSpace; uint64_t textInjectionRVA = textSection->get_virtual_address() + textInjectionOffset; uint64_t rdataInjectionRVA = rdataSection->get_virtual_address() + rdataInjectionOffset; spdlog::info( "Injecting {} bytes at .text section offset 0x{:x} (VA: 0x{:x})", mainSize, textInjectionOffset, textInjectionRVA + imageBase); // Create copies of section data auto textSectionData = textSection->get_data(); auto rdataData = rdataSection->get_data(); char *dstText = const_cast(textSectionData) + textInjectionOffset; char *dstRdata = const_cast(rdataData) + rdataInjectionOffset; // Copy main function code const uint8_t *mainCode = reinterpret_cast(mainSection->get_data()) + mainOffset; std::memcpy(dstText, mainCode, mainSize); spdlog::info("Injected code into existing .text section space (size " "unchanged: 0x{:x})", textSize); // Process relocations char *rdataPtr = dstRdata; if (!processRelocations(dstText, dstText + mainSize, rdataPtr)) { return false; } // Save the modified PE file spdlog::info("Saving patched PE file to: {}", outputFile); if (!peReader.save(outputFile)) { spdlog::error("Failed to save patched PE file to: {}", outputFile); return false; } spdlog::info( "Successfully patched PE file! Main function injected at VA: 0x{:x}", textInjectionRVA + imageBase); return true; } bool run() { if (!loadFiles()) return false; if (!findMainSymbol()) return false; calculateMainSize(); logMainFunctionCode(); logRelocations(); if (!validateSections()) return false; if (!patchAndSave()) return false; return true; } // Helper function to get string at RVA const char *getRVAString(uint32_t rva) { return reinterpret_cast(getRVAData(rva)); } // Helper function to get data pointer at RVA const void *getRVAData(uint32_t rva) { auto §ions = peReader.get_sections(); for (auto §ion : sections) { uint32_t sectionRVA = section->get_virtual_address(); uint32_t sectionSize = section->get_virtual_size(); if (rva >= sectionRVA && rva < sectionRVA + sectionSize) { uint32_t offset = rva - sectionRVA; return section->get_data() + offset; } } return nullptr; } }; int main(int argc, char *argv[]) { CLI::App app("Patcher"); std::string inputFile; std::string outputFile; app.add_option("-i,--input", inputFile, "Input exe file to patch") ->required(); app.add_option("-o,--output", outputFile, "Output patched exe file") ->required(); CLI11_PARSE(app, argc, argv); Patcher patcher; patcher.inputFile = inputFile; patcher.outputFile = outputFile; return patcher.run() ? 0 : 1; }