diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index fbfba157c..48cfcfa8a 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -18,7 +18,7 @@ foreach(LIB_DIR ${LIBS_DIRS}) target_include_directories(${LIB_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) set_target_properties(${LIB_NAME} PROPERTIES PREFIX "" - SUFFIX .rpx + SUFFIX .prx RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/libs LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/libs ) diff --git a/core/libs/nid/src/main.cpp b/core/libs/nid/src/main.cpp index 2f25371da..d879b4ee4 100644 --- a/core/libs/nid/src/main.cpp +++ b/core/libs/nid/src/main.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -71,6 +72,19 @@ struct PeExportDirectory { std::uint32_t AddressOfNameOrdinals; }; +struct PeSectionHeader { + std::uint8_t Name[8]; + std::uint32_t VirtualSize; + std::uint32_t VirtualAddress; + std::uint32_t SizeOfRawData; + std::uint32_t PointerToRawData; + std::uint32_t PointerToRelocations; + std::uint32_t PointerToLinenumbers; + std::uint16_t NumberOfRelocations; + std::uint16_t NumberOfLinenumbers; + std::uint32_t Characteristics; +}; + template T _read(const std::vector& buf, std::size_t offset) { if (offset + sizeof(T) > buf.size()) throw std::runtime_error("read out of bounds"); @@ -154,6 +168,16 @@ void _patchNidsElf(std::vector& elf, const std::string& libraryNam if (dynStrOffset == 0u) throw std::runtime_error("no .dynstr section"); + constexpr std::uint32_t SHT_NOBITS = 8u; + std::size_t dynStrAvailable = elf.size() - dynStrOffset; + for (std::uint16_t i = 0u; i < ehdr.e_shnum; ++i) { + const std::size_t shOffset = static_cast(ehdr.e_shoff) + i * sizeof(Elf64_Shdr); + const auto shdr = _read(elf, shOffset); + if (shdr.sh_type == SHT_NOBITS) continue; + const auto otherOffset = static_cast(shdr.sh_offset); + if (otherOffset > dynStrOffset) dynStrAvailable = std::min(dynStrAvailable, otherOffset - dynStrOffset); + } + std::vector newDynStr(elf.begin() + static_cast(dynStrOffset), elf.begin() + static_cast(dynStrOffset + dynStrSize)); @@ -193,27 +217,41 @@ void _patchNidsElf(std::vector& elf, const std::string& libraryNam _write(elf, symOffset, sym); } - if (newDynStr.size() > dynStrSize) throw std::runtime_error("new .dynstr exceeds original size - rebuild with padding"); + if (newDynStr.size() > dynStrAvailable) throw std::runtime_error("new .dynstr exceeds available padding - cannot patch in place"); std::memcpy(elf.data() + dynStrOffset, newDynStr.data(), newDynStr.size()); - for (std::size_t i = newDynStr.size(); i < dynStrSize; ++i) + for (std::size_t i = newDynStr.size(); i < dynStrAvailable; ++i) elf[dynStrOffset + i] = 0u; + + if (newDynStr.size() > dynStrSize) { + const std::size_t dynStrShOffset = static_cast(ehdr.e_shoff) + dynStrSectionLink * sizeof(Elf64_Shdr); + auto dynStrShdr = _read(elf, dynStrShOffset); + dynStrShdr.sh_size = static_cast(newDynStr.size()); + _write(elf, dynStrShOffset, dynStrShdr); + } +} + +std::size_t _peSectionTableOffset(std::uint32_t peHeaderOffset, std::uint32_t sizeOfOptionalHeader) { + return static_cast(peHeaderOffset) + 4u + 20u + sizeOfOptionalHeader; +} + +std::size_t _peFindSectionOffsetByRva(const std::vector& pe, std::uint32_t rva, std::uint32_t peHeaderOffset, std::uint16_t numberOfSections, std::uint32_t sizeOfOptionalHeader) { + const std::size_t sectionTableOffset = _peSectionTableOffset(peHeaderOffset, sizeOfOptionalHeader); + for (std::uint16_t i = 0u; i < numberOfSections; ++i) { + const std::size_t sectionOffset = sectionTableOffset + i * sizeof(PeSectionHeader); + if (sectionOffset + sizeof(PeSectionHeader) > pe.size()) throw std::runtime_error("section header out of bounds"); + const auto section = _read(pe, sectionOffset); + const std::uint32_t effectiveSize = section.VirtualSize != 0u ? section.VirtualSize : section.SizeOfRawData; + if (rva >= section.VirtualAddress && rva < section.VirtualAddress + effectiveSize) + return sectionOffset; + } + throw std::runtime_error("rva not mapped to any section"); } std::size_t _peRvaToOffset(const std::vector& pe, std::uint32_t rva, std::uint32_t peHeaderOffset, std::uint16_t numberOfSections, std::uint32_t sizeOfOptionalHeader) { - const std::size_t sectionTableOffset = static_cast(peHeaderOffset) + 4u + 20u + sizeOfOptionalHeader; - for (std::uint16_t i = 0u; i < numberOfSections; ++i) { - const std::size_t sectionOffset = sectionTableOffset + i * 40u; - if (sectionOffset + 40u > pe.size()) throw std::runtime_error("section header out of bounds"); - const auto virtualSize = _read(pe, sectionOffset + 8u); - const auto virtualAddress = _read(pe, sectionOffset + 12u); - const auto sizeOfRawData = _read(pe, sectionOffset + 16u); - const auto pointerToRawData = _read(pe, sectionOffset + 20u); - const std::uint32_t effectiveSize = virtualSize != 0u ? virtualSize : sizeOfRawData; - if (rva >= virtualAddress && rva < virtualAddress + effectiveSize) - return static_cast(pointerToRawData) + (rva - virtualAddress); - } - throw std::runtime_error("rva not mapped to any section"); + const std::size_t sectionOffset = _peFindSectionOffsetByRva(pe, rva, peHeaderOffset, numberOfSections, sizeOfOptionalHeader); + const auto section = _read(pe, sectionOffset); + return static_cast(section.PointerToRawData) + (rva - section.VirtualAddress); } void _patchNidsPe(std::vector& pe, const std::string& libraryName) { @@ -251,23 +289,55 @@ void _patchNidsPe(std::vector& pe, const std::string& libraryName) const std::size_t namesArrayOffset = _peRvaToOffset(pe, exportTable.AddressOfNames, peHeaderOffset, numberOfSections, sizeOfOptionalHeader); - std::vector> nameOffsets; + std::vector nameRvas(exportTable.NumberOfNames); + std::vector names(exportTable.NumberOfNames); for (std::uint32_t i = 0u; i < exportTable.NumberOfNames; ++i) { const auto nameRva = _read(pe, namesArrayOffset + i * 4u); const std::size_t nameOffset = _peRvaToOffset(pe, nameRva, peHeaderOffset, numberOfSections, sizeOfOptionalHeader); const std::string name = _readCStr(pe, nameOffset); if (name.empty()) throw std::runtime_error("empty exported name"); - nameOffsets.emplace_back(nameOffset, name); + nameRvas[i] = nameRva; + names[i] = name; } - for (const auto& [nameOffset, name] : nameOffsets) { - const std::string nid = Nid::ComputeNid(name, libraryName); - if (nid.size() > name.size()) throw std::runtime_error("nid longer than original export name - cannot patch in place"); + const std::size_t edataSectionOffset = _peFindSectionOffsetByRva(pe, exportDir.VirtualAddress, peHeaderOffset, numberOfSections, sizeOfOptionalHeader); + const auto edataSection = _read(pe, edataSectionOffset); - std::memcpy(pe.data() + nameOffset, nid.data(), nid.size()); - pe[nameOffset + nid.size()] = 0u; - for (std::size_t i = nid.size() + 1u; i < name.size() + 1u; ++i) - pe[nameOffset + i] = 0u; + const auto sectionAlignment = _read(pe, optionalHeaderOffset + 32u); + const auto fileAlignment = _read(pe, optionalHeaderOffset + 36u); + if (sectionAlignment == 0u || fileAlignment == 0u) throw std::runtime_error("invalid section/file alignment"); + + const std::uint32_t rawLimit = ((edataSection.SizeOfRawData + fileAlignment - 1u) / fileAlignment) * fileAlignment; + const std::uint32_t virtualLimit = ((edataSection.VirtualSize + sectionAlignment - 1u) / sectionAlignment) * sectionAlignment; + + std::uint32_t usedEnd = 0u; + for (std::uint32_t i = 0u; i < exportTable.NumberOfNames; ++i) + usedEnd = std::max(usedEnd, nameRvas[i] - edataSection.VirtualAddress + static_cast(names[i].size()) + 1u); + + for (std::uint32_t i = 0u; i < exportTable.NumberOfNames; ++i) { + const std::string nid = Nid::ComputeNid(names[i], libraryName); + const std::size_t oldNameOffset = _peRvaToOffset(pe, nameRvas[i], peHeaderOffset, numberOfSections, sizeOfOptionalHeader); + + if (nid.size() <= names[i].size()) { + std::memcpy(pe.data() + oldNameOffset, nid.data(), nid.size()); + pe[oldNameOffset + nid.size()] = 0u; + for (std::size_t j = nid.size() + 1u; j < names[i].size() + 1u; ++j) + pe[oldNameOffset + j] = 0u; + continue; + } + + const std::uint32_t newSize = static_cast(nid.size()) + 1u; + if (usedEnd + newSize > rawLimit || edataSection.VirtualAddress + usedEnd + newSize > edataSection.VirtualAddress + virtualLimit) + throw std::runtime_error("no free space left in edata section to relocate export name"); + + const std::uint32_t newNameRva = edataSection.VirtualAddress + usedEnd; + const std::size_t newNameOffset = static_cast(edataSection.PointerToRawData) + usedEnd; + + std::memcpy(pe.data() + newNameOffset, nid.data(), nid.size()); + pe[newNameOffset + nid.size()] = 0u; + _write(pe, namesArrayOffset + i * 4u, newNameRva); + + usedEnd += newSize; } }