// Windows COFF file format for objects and executables #include "coff.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace fs = std::filesystem; using namespace std::string_literals; namespace { #pragma pack(push) #pragma pack(1) struct MSDOSStub { uint8_t padding[0x3c]; uint32_t PESignatureOffset; }; struct PESignature { uint8_t bytes[4]{}; // "PE\0\0" }; struct COFFHeader { uint16_t Machine{}; uint16_t NumberOfSections{}; uint32_t TimeDateStamp{}; uint32_t PointerToSymbolTable{}; uint32_t NumberOfSymbols{}; uint16_t SizeOfOptionalHeader{}; uint16_t Characteristics{}; }; struct COFFOptionalHeader { uint16_t Magic{}; uint8_t MajorLinkerVersion{}; uint8_t MinorLinkerVersion{}; uint32_t SizeOfCode{}; uint32_t SizeOfInitializedData{}; uint32_t SizeOfUninitializedData{}; uint32_t AddressOfEntryPoint{}; uint32_t BaseOfCode{}; uint32_t BaseOfData{}; }; struct COFFOptionalHeader_PE32p { uint16_t Magic{}; uint8_t MajorLinkerVersion{}; uint8_t MinorLinkerVersion{}; uint32_t SizeOfCode{}; uint32_t SizeOfInitializedData{}; uint32_t SizeOfUninitializedData{}; uint32_t AddressOfEntryPoint{}; uint32_t BaseOfCode{}; }; struct COFFOptionalHeader_Windows { uint32_t ImageBase{}; uint32_t SectionAlignment{}; uint32_t FileAlignment{}; uint16_t MajorOperatingSystemVersion{}; uint16_t MinorOperatingSystemVersion{}; uint16_t MajorImageVersion{}; uint16_t MinorImageVersion{}; uint16_t MajorSubsystemVersion{}; uint16_t MinorSubsystemVersion{}; uint32_t Win32VersionValue{}; // reserved, =0 uint32_t SizeOfImage{}; uint32_t SizeOfHeaders{}; uint32_t CheckSum{}; uint16_t Subsystem{}; uint16_t DllCharacteristics{}; uint32_t SizeOfStackReserve{}; uint32_t SizeOfStackCommit{}; uint32_t SizeOfHeapReserve{}; uint32_t SizeOfHeapCommit{}; uint32_t LoaderFlags{}; uint32_t NumberOfRvaAndSizes{}; }; struct COFFOptionalHeader_Windows_PE32p { uint64_t ImageBase{}; uint32_t SectionAlignment{}; uint32_t FileAlignment{}; uint16_t MajorOperatingSystemVersion{}; uint16_t MinorOperatingSystemVersion{}; uint16_t MajorImageVersion{}; uint16_t MinorImageVersion{}; uint16_t MajorSubsystemVersion{}; uint16_t MinorSubsystemVersion{}; uint32_t Win32VersionValue{}; // reserved, =0 uint32_t SizeOfImage{}; uint32_t SizeOfHeaders{}; uint32_t CheckSum{}; uint16_t Subsystem{}; uint16_t DllCharacteristics{}; uint64_t SizeOfStackReserve{}; uint64_t SizeOfStackCommit{}; uint64_t SizeOfHeapReserve{}; uint64_t SizeOfHeapCommit{}; uint32_t LoaderFlags{}; uint32_t NumberOfRvaAndSizes{}; }; // For each section: struct SectionHeader { uint8_t Name[8]{}; uint32_t VirtualSize{}; uint32_t VirtualAddress{}; uint32_t SizeOfRawData{}; uint32_t PointerToRawData{}; uint32_t PointerToRelocations{}; uint32_t PointerToLinenumbers{}; uint16_t NumberOfRelocations{}; uint16_t NumberOfLinenumbers{}; uint32_t Characteristics{}; }; struct COFFRelocation { uint32_t VirtualAddress{}; uint32_t SymbolTableIndex{}; uint16_t Type{}; }; struct COFFSymbolTableRecord { uint64_t Name{}; // up-to-8-Byte String or COFFSymbolTableRecordName (if longer) uint32_t Value{}; uint16_t SectionNumber{}; uint16_t Type{}; uint8_t StorageClass{}; uint8_t NumberOfAuxSymbols{}; }; struct COFFSymbolTableRecordName { uint32_t Zeroes{}; uint32_t Offset{}; }; struct LibSignature { uint8_t bytes[8]{}; // "!\n" }; struct LibHeader { uint8_t Name[16]{}; uint8_t Date[12]{}; uint8_t UserID[6]{}; uint8_t GroupID[6]{}; uint8_t Mode[8]{}; uint8_t Size[10]{}; // ASCII-decimal size of Member Body (size without LibHeader size) uint8_t End[2]{}; // "~\n" std::string GetName() const { std::string s{ (char*)&Name, sizeof(Name) }; size_t pos = s.find("/"); if (pos == s.npos) throw std::runtime_error("LibHeader Name doesn't contain '/'"); if (pos == 0) { if (s[1] == '/') { // "//" return "//"; // longnames header } else if (s[1] == ' ') { // "/ " return "/"; // linker members (#0 and #1) } else { pos = s.find(" "); // string is zero-padded. We return string without trailing zeros. return s.substr(0, pos); // "/" } } else { return s.substr(0, pos); // "name/" } } size_t BodySize() const { std::string s{(char*)&Size, sizeof(Size)}; size_t pos{s.find(" ")}; // remove trailing space-padding s = s.substr(0, pos); try { return std::stoll(s); } catch (const std::exception &) { throw std::runtime_error("Bad size for LibHeader"); } } }; struct FirstLinkerMember { uint32_t NumberOfSymbols; // big endian // NumberOfSymbols x uint32_t Offsets; // String Table }; struct SecondLinkerMember { uint32_t NumberOfMembers; // NumberOfMembers x uint32_t Offsets; // uint32_t NumberOfSymbols; // NumberOfSymbols x uint16_t Indices; }; // TODO: export table // TODO: import table // TODO: relocations table // TODO: TLS table (thread local storage) #pragma pack(pop) std::vector getFile(const fs::path& filename) { std::ifstream file(filename.string(), std::ios::in | std::ios::binary | std::ios::ate); if (file.is_open()) { std::ifstream::pos_type fileSize = file.tellg(); file.seekg(0, std::ios::beg); std::vector bytes(fileSize, 0); file.read(reinterpret_cast(bytes.data()), fileSize); return bytes; } else { throw std::runtime_error("Opening "s + filename.string() + " for reading"); } } uint32_t PE_addr(const std::vector& data) { if (data.size() >= 0x40) { size_t offset = *(reinterpret_cast(data.data() + 0x3c)); if (data.size() >= offset + 4) { std::vector ref{ 'P', 'E', '\0', '\0' }; auto [data_it, ref_it] { std::mismatch(data.begin() + offset, data.end(), ref.begin(), ref.end()) }; if (ref_it == ref.end()) return uint8_t(offset) + 4; } } return 0; } bool isPE(const std::vector& data) { return PE_addr(data); } char to_hex_digit(uint8_t value) { if (value < 10) return '0' + value; else return 'a' + value - 10; } template< typename T > std::string to_hex(T i) { std::stringstream stream; if (sizeof(T) == 1) stream << to_hex_digit(i >> 4) << to_hex_digit(i & 0xF); else stream << std::setfill('0') << std::setw(sizeof(T) * 2) << std::hex << i; return stream.str(); } template< typename T > std::string to_0xhex(T i) { std::stringstream stream; if (sizeof(T) == 1) stream << to_hex_digit(i >> 4) << to_hex_digit(i & 0xF); else stream << "0x" << std::setfill('0') << std::setw(sizeof(T) * 2) << std::hex << i; return stream.str(); } std::string to_string(const uint8_t(&name)[8]) { if (name[0] == '/') { // rest contains a decimal number ASCII coded as offset into string table throw std::runtime_error("Unimplemented /-based name, TODO!"); } return std::string(reinterpret_cast(name), 8); } void DumpSection(const std::vector& data, uint32_t Offset, uint32_t Size, uint32_t VirtualSize) { if (data.size() < Offset + Size) throw std::runtime_error("Not enough raw data to dump, got "s + std::to_string(data.size()) + ", expected "s + std::to_string(Offset + Size)); // Size < VirtualSize: the rest is implicitly padded std::string printable; for (uint32_t i = 0; i < VirtualSize; i++) { if (i % 16 == 0) { std::cout << " " << printable << "\n " << to_0xhex(i) << " "; printable = ""; } else if (i % 16 == 8) std::cout << " "; std::string value = (i < Size) ? to_hex(uint8_t(data[Offset + i])) : "oo"; int c = (i < Size) ? data[Offset + i] : 0; std::cout << " " << value; if (std::isprint(c)) printable.append(size_t(1), char(c)); else printable.append("."); } std::cout << (VirtualSize % 16 > 0 ? std::string(size_t(3 * (16 - VirtualSize % 16)), ' ') + (VirtualSize % 16 <= 8 ? " " : "") : "") << " " << printable; std::cout << "\n"; } // PE = // MSDOSStub // PESignature // COFFHeader // + COFFOptionalHeader or COFFOptionalHeader_P32p // + COFFOptionalHeader_Windows or COFFOptionalHeader_Windows_PE32p // + N x DataDirectory // SectionHeader(s) void DumpExe(const std::vector& data) { size_t offset{ PE_addr(data) }; if (data.size() >= offset + sizeof(COFFHeader)) { std::cout << "COFF Image (EXE) found.\n" << std::endl; const COFFHeader& coffHeader{ *(reinterpret_cast(data.data() + offset)) }; std::cout << "Machine: " << to_0xhex(coffHeader.Machine) << "\n"; if (coffHeader.Machine != COFF::IMAGE_FILE_MACHINE_AMD64) std::cout << " Warning: Unsupported.\n"; std::cout << "NumberOfSections: " << coffHeader.NumberOfSections << "\n"; if (coffHeader.SizeOfOptionalHeader == 0) std::cout << "Warning: SizeOfOptionalHeader is " << coffHeader.SizeOfOptionalHeader << ". Expected " << sizeof(COFFOptionalHeader) << ".\n"; for (int i = 1; i <= coffHeader.NumberOfSections; i++) { if (data.size() < offset + sizeof(COFFHeader) + coffHeader.SizeOfOptionalHeader + i * sizeof(SectionHeader)) throw std::runtime_error("Data size too small to read next Section Header"); const SectionHeader& sectionHeader{ *(reinterpret_cast(data.data() + offset + coffHeader.SizeOfOptionalHeader + sizeof(COFFHeader) + (i - 1) * sizeof(SectionHeader))) }; std::cout << "\nSection #" << i << ":\n"; std::cout << " Name: " << to_string(sectionHeader.Name) << "\n"; std::cout << " Size: " << sectionHeader.VirtualSize << " bytes\n"; std::cout << " Raw Data:\n"; DumpSection(data, sectionHeader.PointerToRawData, sectionHeader.SizeOfRawData, sectionHeader.VirtualSize); } } else throw std::runtime_error("Data size too small to read COFF Header."); } // COFF OBJ = // COFFHeader // SectionHeader(s) void DumpObj(const std::vector& data) { if (data.size() >= sizeof(COFFHeader)) { std::cout << "COFF OBJ found.\n" << std::endl; const COFFHeader& coffHeader{ *(reinterpret_cast(data.data())) }; std::cout << "Machine: " << to_0xhex(coffHeader.Machine) << "\n"; if (coffHeader.Machine != COFF::IMAGE_FILE_MACHINE_AMD64) { std::cout << " Warning: Unsupported.\n"; return; } std::cout << "NumberOfSections: " << coffHeader.NumberOfSections << "\n"; if (coffHeader.SizeOfOptionalHeader != 0) std::cout << "Warning: SizeOfOptionalHeader is " << coffHeader.SizeOfOptionalHeader << ". Expected 0.\n"; for (int i = 1; i <= coffHeader.NumberOfSections; i++) { if (data.size() < sizeof(COFFHeader) + i * sizeof(SectionHeader)) throw std::runtime_error("Data size too small to read next Section Header"); const SectionHeader& sectionHeader{ *(reinterpret_cast(data.data() + sizeof(COFFHeader) + (i - 1) * sizeof(SectionHeader))) }; std::cout << "\nSection #" << i << ":\n"; std::cout << " Name: " << to_string(sectionHeader.Name) << "\n"; std::cout << " Size: " << sectionHeader.SizeOfRawData << " bytes\n"; std::cout << " Raw Data:\n"; DumpSection(data, sectionHeader.PointerToRawData, sectionHeader.SizeOfRawData, sectionHeader.SizeOfRawData); // sectionHeader.VirtualSize is 0 for obj } } else throw std::runtime_error("Data size too small to read COFF Header."); } void DumpMember(size_t n, const std::vector& data, size_t byteoffset) { const LibHeader& libHeader{ *(reinterpret_cast(data.data() + byteoffset)) }; if (libHeader.End[0] != 0x60 || libHeader.End[1] != 0x0A) throw std::runtime_error("Bad EndOFHeader signature for header #"s + std::to_string(n + 1) + " at byte offset "s + std::to_string(byteoffset)); if (data.size() < byteoffset + sizeof(LibHeader) + libHeader.BodySize()) throw std::runtime_error("Too few bytes for linker member #"s + std::to_string(n + 1)); if (n == 0) { // 1st Linker Member if (libHeader.GetName() != "/") throw std::runtime_error("Bad Name for 1st Linker Member: "s + libHeader.GetName()); if (data.size() < byteoffset + sizeof(LibHeader) + sizeof(FirstLinkerMember)) throw std::runtime_error("Too few bytes for first linker member."); const FirstLinkerMember& firstLinkerMember{ *(reinterpret_cast(data.data() + byteoffset + sizeof(LibHeader))) }; std::cout << "First Linker Member with " << boost::endian::big_to_native(firstLinkerMember.NumberOfSymbols) << " Symbol(s): Ignored (obsolete).\n" << std::endl; } else if (n == 1) { // 2nd Linker Member if (libHeader.GetName() != "/") throw std::runtime_error("Bad Name for 2nd Linker Member: "s + libHeader.GetName()); if (data.size() < byteoffset + sizeof(LibHeader) + sizeof(SecondLinkerMember)) throw std::runtime_error("Too few bytes for second linker member."); const SecondLinkerMember& secondLinkerMember{ *(reinterpret_cast(data.data() + byteoffset + sizeof(LibHeader))) }; std::cout << "Second Linker Member: " << secondLinkerMember.NumberOfMembers << " Archive Member(s)\n" << std::endl; } else if (n == 2 && libHeader.GetName() == "//") { // Longnames Member. // undocumented: Longnames Member not always present if (libHeader.GetName() != "//") throw std::runtime_error("Bad Name for Longnames Member: "s + libHeader.GetName()); std::cout << "Longnames Member\n" << std::endl; } else { // n >= 3: OBJ members std::cout << "OBJ Member #" << (n - 2) << "\n" << std::endl; std::vector obj{ data.begin() + byteoffset + sizeof(LibHeader), data.begin() + byteoffset + sizeof(LibHeader) + libHeader.BodySize() }; DumpObj(obj); } } // LIB = // LibSignature // LibHeaders (+ Body each): // Linker Member 1 (directory, obsolete) // Linker Member 2 (directory) // Longnames Member (names of archive members) // OBJ1 // [OBJ2] // [...] void DumpLib(const std::vector& data) { #if 0 size_t p1{ 0 }; std::vector x{ {'\\', '\\'} }; auto it = std::search(data.begin(), data.end(), x.begin(), x.end()); if (it != data.end()) std::cout << "DEBUG: " << (it - data.begin()) << std::endl; else std::cout << "DEBUG: " << "not found." << std::endl; #endif size_t n{ 0 }; size_t byteoffset{ sizeof(LibSignature) }; while (byteoffset < data.size()) { const LibHeader& libHeader{ *(reinterpret_cast(data.data() + byteoffset)) }; if (data.size() < byteoffset + sizeof(LibHeader)) throw std::runtime_error("Too few bytes in lib header for member #"s + std::to_string(n + 1) + ": "s + std::to_string(data.size())); DumpMember(n, data, byteoffset); n++; byteoffset += sizeof(LibHeader) + libHeader.BodySize(); while (byteoffset % 2 != 0) // align to 2-byte ??? (undocumented) byteoffset++; } } } // namespace void COFF::Dump(fs::path path) { auto data{getFile(path)}; if (data.size() >= 8 && boost::starts_with(data, "!\n"s)) { DumpLib(data); } else if (data.size() >= 2 && data[0] == 0x64 && data[1] == 0x86) { DumpObj(data); } else if (isPE(data)) { DumpExe(data); } else throw std::runtime_error("Bad file type."); } namespace { void PutDOSStub(std::vector& data) { std::vector x{ 'M', 'Z' }; x.resize(0x3c); data.insert(data.end(), x.begin(), x.end()); std::vector address{0x40, 0, 0, 0}; // 32-bit address points to end of thus DOSStub data.insert(data.end(), address.begin(), address.end()); } void PutPESignature(std::vector& data) { std::vector sig{ 'P', 'E', '\0', '\0' }; data.insert(data.end(), sig.begin(), sig.end()); } void PutCOFFHeader(std::vector& data) { { std::vector header_v(sizeof(COFFHeader), uint8_t{}); COFFHeader& header{ *reinterpret_cast(header_v.data()) }; header.Machine = 0x8664; // AMD64 header.NumberOfSections = 2; header.SizeOfOptionalHeader = sizeof(COFFOptionalHeader_PE32p) + sizeof(COFFOptionalHeader_Windows_PE32p) + 8 * 16; // 0xf0 header.Characteristics = COFF::IMAGE_FILE_EXECUTABLE_IMAGE | COFF::IMAGE_FILE_LARGE_ADDRESS_AWARE; data.insert(data.end(), header_v.begin(), header_v.end()); } { std::vector optional_header_v(sizeof(COFFOptionalHeader_PE32p), uint8_t{}); COFFOptionalHeader_PE32p& optional_header{ *reinterpret_cast(optional_header_v.data()) }; optional_header.Magic = 0x20B; // PE32+ optional_header.SizeOfCode = 512; optional_header.SizeOfInitializedData = 512; optional_header.SizeOfUninitializedData = 0; optional_header.AddressOfEntryPoint = 0x1000; optional_header.BaseOfCode = 0x1000; data.insert(data.end(), optional_header_v.begin(), optional_header_v.end()); } { std::vector optional_windows_v(sizeof(COFFOptionalHeader_Windows_PE32p), uint8_t{}); COFFOptionalHeader_Windows_PE32p& optional_windows{ *reinterpret_cast(optional_windows_v.data()) }; optional_windows.ImageBase = 0x140000000; optional_windows.SectionAlignment = 0x1000; optional_windows.FileAlignment = 512; #if 1 optional_windows.MajorImageVersion = 6; optional_windows.MajorOperatingSystemVersion = 6; optional_windows.MajorSubsystemVersion = 6; #endif optional_windows.SizeOfImage = 0x3000; optional_windows.SizeOfHeaders = 512; optional_windows.CheckSum = 0; optional_windows.Subsystem = COFF::IMAGE_SUBSYSTEM_WINDOWS_CUI; #if 0 optional_windows.DllCharacteristics = 0x8160; #endif optional_windows.SizeOfStackReserve = 0x100000; optional_windows.SizeOfStackCommit = 0x1000; optional_windows.SizeOfHeapReserve = 0x100000; optional_windows.SizeOfHeapCommit = 0x1000; optional_windows.NumberOfRvaAndSizes = 0x10; data.insert(data.end(), optional_windows_v.begin(), optional_windows_v.end()); } { std::vector data_directories(8 * 16, uint8_t{}); data.insert(data.end(), data_directories.begin(), data_directories.end()); } } void PutCOFFSectionCodeHeader(std::vector& data) { std::vector section_header_v(sizeof(SectionHeader), uint8_t{}); SectionHeader& section_header{ *reinterpret_cast(section_header_v.data()) }; uint8_t Name[8]{ '.', 't', 'e', 'x', 't', 0, 0, 0 }; memcpy(section_header.Name, Name, 8); section_header.VirtualSize = 3; // TODO section_header.VirtualAddress = 0x1000; section_header.SizeOfRawData = 512; // multiple of optional_windows.FileAlignment section_header.PointerToRawData = 512; section_header.Characteristics = COFF::IMAGE_SCN_CNT_CODE | COFF::IMAGE_SCN_MEM_EXECUTE | COFF::IMAGE_SCN_MEM_READ; data.insert(data.end(), section_header_v.begin(), section_header_v.end()); } void PutCOFFSectionCode(std::vector& data) { { // pad before code std::vector pad(512 - data.size(), uint8_t{}); data.insert(data.end(), pad.begin(), pad.end()); } { // test code: return 0 std::vector code{0x33, 0xC0, 0xC3}; data.insert(data.end(), code.begin(), code.end()); } { // pad after code std::vector pad(1024 - data.size(), uint8_t{}); data.insert(data.end(), pad.begin(), pad.end()); } } void PutCOFFSectionDataHeader(std::vector& data) { std::vector section_header_v(sizeof(SectionHeader), uint8_t{}); SectionHeader& section_header{ *reinterpret_cast(section_header_v.data()) }; uint8_t Name[8]{ '.', 'd', 'a', 't', 'a', 0, 0, 0 }; memcpy(section_header.Name, Name, 8); section_header.VirtualSize = 3; // TODO section_header.VirtualAddress = 0x2000; section_header.SizeOfRawData = 512; // multiple of optional_windows.FileAlignment section_header.PointerToRawData = 1024; section_header.Characteristics = COFF::IMAGE_SCN_CNT_INITIALIZED_DATA | COFF::IMAGE_SCN_MEM_READ; data.insert(data.end(), section_header_v.begin(), section_header_v.end()); } void PutCOFFSectionData(std::vector& data) { { // test data std::vector x(1536 - data.size(), uint8_t{}); data.insert(data.end(), x.begin(), x.end()); } } } // namespace void COFF::Create(std::filesystem::path path) { std::vector data; PutDOSStub(data); PutPESignature(data); PutCOFFHeader(data); PutCOFFSectionCodeHeader(data); PutCOFFSectionDataHeader(data); PutCOFFSectionCode(data); PutCOFFSectionData(data); Reichwein::File::setFile(path, data); }