diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 40c657509..2226a7d6a 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -2,6 +2,13 @@ add_executable(relinker ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp ${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/linux/LinuxElfPatcher.cpp ${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsPePatcher.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsPeFormat.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsLoadImage.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsRelocationBuilder.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsImportBuilder.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsStubEmitter.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsPeWriter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/general/SegmentFilter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/general/EntryStubBuilder.cpp ${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/general/ProgramHeaderLayoutBuilder.cpp diff --git a/core/elfpatcher/include/elfpatcher/windows/WindowsElfPatcher.hpp b/core/elfpatcher/include/elfpatcher/windows/WindowsElfPatcher.hpp index 8937affca..c0dd14447 100644 --- a/core/elfpatcher/include/elfpatcher/windows/WindowsElfPatcher.hpp +++ b/core/elfpatcher/include/elfpatcher/windows/WindowsElfPatcher.hpp @@ -2,37 +2,12 @@ #define ELFPATCHER_WINDOWS_WINDOWSELFPATCHER_HPP #include -#include -#include -#include -#include -#include -#include namespace Elfpatcher::Windows { class WindowsPePatcher : public IElfPatcher { public: - WindowsPePatcher( - std::shared_ptr entryStubBuilder, - std::shared_ptr programHeaderLayoutBuilder, - std::shared_ptr sectionHeaderTableBuilder, - std::shared_ptr byteWriter - ); - - std::vector Patch( - const std::vector& sourceElf, - const std::vector& originalHeaders, - const Domain::SysVDynamicSection& dynamicSection, - std::uint64_t originalPltGotVaddr, - const std::string& runPath - ) override; - -private: - std::shared_ptr _entryStubBuilder; - std::shared_ptr _programHeaderLayoutBuilder; - std::shared_ptr _sectionHeaderTableBuilder; - std::shared_ptr _byteWriter; + std::vector Patch(const std::vector& sourceElf, const std::vector& originalHeaders, const Domain::SysVDynamicSection& dynamicSection, std::uint64_t originalPltGotVaddr, const std::string& runPath) override; }; } diff --git a/core/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp b/core/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp new file mode 100644 index 000000000..32ccc1d13 --- /dev/null +++ b/core/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp @@ -0,0 +1,190 @@ +#include "WindowsEntryStubBuilder.hpp" +#include "WindowsStubEmitter.hpp" +#include +#include + +namespace Elfpatcher::Windows { + +namespace { + +constexpr std::uint32_t PathCapacity = 32768; + +std::string normalizeRunPath(std::string path) { + if (path.empty() || std::any_of(path.begin(), path.end(), [](const unsigned char value) { return value == 0 || value >= 128; })) + throw Domain::RelinkerException("Windows run path must be a nonempty ASCII path"); + std::replace(path.begin(), path.end(), '/', '\\'); + if (path == "$ORIGIN") + path = "."; + if (path.starts_with("$ORIGIN\\")) + path.erase(0, 8); + if (path.find('$') != std::string::npos) + throw Domain::RelinkerException("Unsupported variable in Windows run path"); + if (path.back() != '\\') + path.push_back('\\'); + return path; +} + +} + +WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, const std::uint32_t entryRva, const WindowsImports& nativeImports, const std::vector& libraries, const std::vector& imports, const std::string& runPath) const { + if (!imports.empty() && libraries.empty()) + throw Domain::RelinkerException("ELF imports have no DT_NEEDED libraries"); + const auto path = normalizeRunPath(runPath); + const bool absolutePath = path.starts_with("\\\\") || (path.size() >= 3 && path[1] == ':' && path[2] == '\\'); + if (!absolutePath && (path.front() == '\\' || path.find(':') != std::string::npos)) + throw Domain::RelinkerException("Ambiguous Windows run path: " + path); + WindowsEntryStub result{{".startup", dataRva, SectionRead | SectionWrite | 0x40u, {}}, {".entry", 0, SectionRead | SectionExecute | 0x20u, {}}, {}}; + auto& data = result.Data.Data; + const auto reserve = [&](const std::size_t size) { + const auto rva = CheckedRva(dataRva + data.size()); + data.resize(data.size() + size); + return rva; + }; + const auto addString = [&](const std::string& value) { + const auto rva = CheckedRva(dataRva + data.size()); + Io::AppendString(data, value); + return rva; + }; + const auto programPath = reserve(PathCapacity); + const auto modulePath = reserve(PathCapacity); + const auto handles = reserve(libraries.size() * 8); + const auto functionTable = reserve(12); + const auto unwindRva = CheckedRva(dataRva + data.size()); + data.insert(data.end(), {1, 10, 6, 0, 10, 0xb2, 6, 0xc0, 4, 0x70, 3, 0x60, 2, 0x50, 1, 0x30}); + std::vector libraryPaths; + std::vector libraryNames; + for (const auto& library : libraries) { + auto name = path + library; + if (name.size() + 1 >= PathCapacity) + throw Domain::RelinkerException("Windows library path exceeds the startup buffer: " + name); + libraryPaths.push_back(addString(name)); + libraryNames.push_back(std::move(name)); + } + std::vector symbolNames; + for (const auto& import : imports) + symbolNames.push_back(addString(import.Name)); + const auto diagnosticsOffset = data.size(); + std::vector errors = {"FAIL: cannot obtain executable path\n", "FAIL: executable or library path is too long\n", "FAIL: executable path has no directory\n"}; + for (const auto& name : libraryNames) + errors.push_back("FAIL: cannot load library " + name + "\n"); + for (const auto& import : imports) + errors.push_back("FAIL: unresolved ELF import " + import.Name + "\n"); + std::vector errorRvas; + for (const auto& error : errors) + errorRvas.push_back(addString(error)); + if (data.size() <= diagnosticsOffset) + throw Domain::RelinkerException("Empty startup diagnostics"); + result.Code.Rva = AlignRva(dataRva + data.size()); + WindowsStubEmitter code(result.Code.Rva); + const auto call = [&](const std::string& name) { code.Rip({0xff, 0x15}, nativeImports.Functions.at(name)); }; + const auto fail = [&](const std::size_t error, const std::uint32_t status) { + code.Emit({0xb9}); + code.U32(0xfffffff4u); + call("GetStdHandle"); + code.Emit({0x48, 0x89, 0xc1}); + code.Rip({0x48, 0x8d, 0x15}, errorRvas.at(error)); + code.Emit({0x41, 0xb8}); + code.U32(CheckedRva(errors.at(error).size())); + code.Emit({0x4c, 0x8d, 0x4c, 0x24, 0x30, 0x48, 0xc7, 0x44, 0x24, 0x20, 0, 0, 0, 0}); + call("WriteFile"); + code.Emit({0xb9}); + code.U32(status); + code.Emit({0xba, 1, 0, 0, 0, 0x45, 0x31, 0xc0, 0x45, 0x31, 0xc9}); + call("RaiseException"); + code.Emit({0x0f, 0x0b}); + }; + const auto requireNonzero = [&](const std::size_t error, const std::uint32_t status) { + code.Emit({0x48, 0x85, 0xc0}); + const auto success = code.Branch({0x0f, 0x85}); + fail(error, status); + code.PatchBranch(success, code.GetRva()); + }; + code.Emit({0x53, 0x55, 0x56, 0x57, 0x41, 0x54, 0x48, 0x83, 0xec, 0x60}); + code.Emit({0x31, 0xc9}); + code.Rip({0x48, 0x8d, 0x15}, programPath); + code.Emit({0x41, 0xb8}); + code.U32(PathCapacity); + call("GetModuleFileNameA"); + requireNonzero(0, 0xc000000du); + code.Emit({0x3d}); + code.U32(PathCapacity); + const auto pathFits = code.Branch({0x0f, 0x82}); + fail(1, 0xc0000106u); + code.PatchBranch(pathFits, code.GetRva()); + code.Emit({0x89, 0xc1}); + code.Rip({0x48, 0x8d, 0x35}, programPath); + code.Rip({0x48, 0x8d, 0x3d}, modulePath); + code.Emit({0xfc, 0xf3, 0xa4, 0x49, 0x89, 0xfc}); + code.Rip({0x48, 0x8d, 0x1d}, modulePath); + const auto findSeparator = code.GetRva(); + code.Emit({0x49, 0x39, 0xdc}); + const auto hasDirectory = code.Branch({0x0f, 0x85}); + fail(2, 0xc000000du); + code.PatchBranch(hasDirectory, code.GetRva()); + code.Emit({0x49, 0xff, 0xcc, 0x41, 0x80, 0x3c, 0x24, 0x5c}); + code.Rip({0x0f, 0x85}, findSeparator); + code.Emit({0x49, 0xff, 0xc4}); + for (std::size_t index = 0; index < libraries.size(); ++index) { + if (absolutePath) { + code.Rip({0x48, 0x8d, 0x0d}, libraryPaths[index]); + } else { + code.Emit({0x4c, 0x89, 0xe0, 0x48, 0x29, 0xd8, 0x48, 0x05}); + code.U32(CheckedRva(libraryNames[index].size() + 1)); + code.Emit({0x48, 0x3d}); + code.U32(PathCapacity); + const auto fits = code.Branch({0x0f, 0x86}); + fail(1, 0xc0000106u); + code.PatchBranch(fits, code.GetRva()); + code.Emit({0x4c, 0x89, 0xe7}); + code.Rip({0x48, 0x8d, 0x35}, libraryPaths[index]); + code.Emit({0xb9}); + code.U32(CheckedRva(libraryNames[index].size() + 1)); + code.Emit({0xf3, 0xa4}); + code.Rip({0x48, 0x8d, 0x0d}, modulePath); + } + code.Emit({0x31, 0xd2, 0x41, 0xb8, 0, 0x11, 0, 0}); + call("LoadLibraryExA"); + requireNonzero(3 + index, 0xc0000135u); + code.Rip({0x48, 0x89, 0x05}, CheckedRva(handles + index * 8)); + } + for (std::size_t index = 0; index < imports.size(); ++index) { + code.Rip({0x48, 0x8d, 0x1d}, handles); + code.Rip({0x48, 0x8d, 0x35}, symbolNames[index]); + code.Emit({0xbd}); + code.U32(CheckedRva(libraries.size())); + const auto search = code.GetRva(); + code.Emit({0x48, 0x8b, 0x0b, 0x48, 0x89, 0xf2}); + call("GetProcAddress"); + code.Emit({0x48, 0x85, 0xc0}); + const auto resolved = code.Branch({0x0f, 0x85}); + code.Emit({0x48, 0x83, 0xc3, 8, 0xff, 0xcd}); + code.Rip({0x0f, 0x85}, search); + fail(3 + libraries.size() + index, 0xc0000139u); + code.PatchBranch(resolved, code.GetRva()); + if (imports[index].Addend != 0) { + code.Emit({0x48, 0xba}); + code.U64(imports[index].Addend); + code.Emit({0x48, 0x01, 0xd0}); + } + code.Rip({0x48, 0x89, 0x05}, imports[index].TargetRva); + } + code.Emit({0x48, 0xc7, 0x44, 0x24, 0x40, 1, 0, 0, 0}); + code.Rip({0x48, 0x8d, 0x05}, programPath); + code.Emit({0x48, 0x89, 0x44, 0x24, 0x48, 0x31, 0xc0, 0x48, 0x89, 0x44, 0x24, 0x50, 0x48, 0x89, 0x44, 0x24, 0x58, 0x48, 0x8d, 0x7c, 0x24, 0x40}); + const auto exitCallback = code.Branch({0x48, 0x8d, 0x35}); + code.Rip({0xe8}, entryRva); + code.Emit({0x89, 0xc1}); + call("ExitProcess"); + code.Emit({0x0f, 0x0b}); + const auto functionEnd = code.GetRva(); + code.PatchBranch(exitCallback, functionEnd); + code.Emit({0xc3}); + Io::WriteU32(data, functionTable - dataRva, result.Code.Rva); + Io::WriteU32(data, functionTable - dataRva + 4, functionEnd); + Io::WriteU32(data, functionTable - dataRva + 8, unwindRva); + result.ExceptionDirectory = {functionTable, 12}; + result.Code.Data = code.TakeBytes(); + return result; +} + +} diff --git a/core/elfpatcher/src/windows/WindowsEntryStubBuilder.hpp b/core/elfpatcher/src/windows/WindowsEntryStubBuilder.hpp new file mode 100644 index 000000000..b81186f67 --- /dev/null +++ b/core/elfpatcher/src/windows/WindowsEntryStubBuilder.hpp @@ -0,0 +1,21 @@ +#ifndef ELFPATCHER_WINDOWS_ENTRYSTUBBUILDER_HPP +#define ELFPATCHER_WINDOWS_ENTRYSTUBBUILDER_HPP + +#include "WindowsImportBuilder.hpp" + +namespace Elfpatcher::Windows { + +struct WindowsEntryStub { + PeSection Data; + PeSection Code; + PeDirectory ExceptionDirectory; +}; + +class WindowsEntryStubBuilder { +public: + WindowsEntryStub Build(std::uint32_t dataRva, std::uint32_t entryRva, const WindowsImports& nativeImports, const std::vector& libraries, const std::vector& imports, const std::string& runPath) const; +}; + +} + +#endif diff --git a/core/elfpatcher/src/windows/WindowsImportBuilder.cpp b/core/elfpatcher/src/windows/WindowsImportBuilder.cpp new file mode 100644 index 000000000..e21ef680f --- /dev/null +++ b/core/elfpatcher/src/windows/WindowsImportBuilder.cpp @@ -0,0 +1,54 @@ +#include "WindowsImportBuilder.hpp" +#include +#include + +namespace Elfpatcher::Windows { + +WindowsImports WindowsImportBuilder::Build(const std::uint32_t sectionRva) const { + const std::vector names = {"ExitProcess", "GetModuleFileNameA", "GetProcAddress", "GetStdHandle", "LoadLibraryExA", "RaiseException", "WriteFile"}; + WindowsImports result{{".idata", sectionRva, SectionRead | SectionWrite | 0x40u, std::vector(40)}, {sectionRva, 40}, {}, {}}; + auto& bytes = result.Section.Data; + const auto lookupOffset = bytes.size(); + bytes.resize(bytes.size() + (names.size() + 1) * 8); + const auto addressOffset = bytes.size(); + bytes.resize(bytes.size() + (names.size() + 1) * 8); + result.AddressTable = {CheckedRva(sectionRva + addressOffset), CheckedRva((names.size() + 1) * 8)}; + for (std::size_t index = 0; index < names.size(); ++index) { + const auto nameRva = CheckedRva(sectionRva + bytes.size()); + Io::AppendU16(bytes, 0); + Io::AppendString(bytes, names[index]); + Io::AlignBuffer(bytes, 2); + Io::WriteU64(bytes, lookupOffset + index * 8, nameRva); + Io::WriteU64(bytes, addressOffset + index * 8, nameRva); + result.Functions.emplace(names[index], CheckedRva(sectionRva + addressOffset + index * 8)); + } + const auto libraryRva = CheckedRva(sectionRva + bytes.size()); + Io::AppendString(bytes, "KERNEL32.dll"); + Io::WriteU32(bytes, 0, CheckedRva(sectionRva + lookupOffset)); + Io::WriteU32(bytes, 12, libraryRva); + Io::WriteU32(bytes, 16, CheckedRva(sectionRva + addressOffset)); + return result; +} + +std::vector WindowsImportBuilder::ReadLibraries(const Domain::SysVDynamicSection& dynamicSection) const { + const auto& bytes = dynamicSection.DynamicSegmentData; + if (bytes.size() % 16 != 0) + throw Domain::RelinkerException("Invalid dynamic segment size"); + std::vector result; + std::set unique; + for (std::size_t offset = 0; offset < bytes.size(); offset += 16) { + const auto tag = Io::ReadU64(bytes, offset); + if (tag != 1) + throw Domain::RelinkerException("Unexpected tag in rebuilt ELF dependency table", offset); + const auto nameOffset = Io::ReadU64(bytes, offset + 8); + if (nameOffset >= dynamicSection.DynStrData.size()) + throw Domain::RelinkerException("DT_NEEDED string offset is out of bounds", nameOffset); + auto name = ReadString(dynamicSection.DynStrData, static_cast(nameOffset)); + if (name.empty() || name.find_first_of("/\\:") != std::string::npos || !unique.insert(name).second) + throw Domain::RelinkerException("Invalid or duplicate DT_NEEDED library: " + name); + result.push_back(std::move(name)); + } + return result; +} + +} diff --git a/core/elfpatcher/src/windows/WindowsImportBuilder.hpp b/core/elfpatcher/src/windows/WindowsImportBuilder.hpp new file mode 100644 index 000000000..00d87faad --- /dev/null +++ b/core/elfpatcher/src/windows/WindowsImportBuilder.hpp @@ -0,0 +1,25 @@ +#ifndef ELFPATCHER_WINDOWS_IMPORTBUILDER_HPP +#define ELFPATCHER_WINDOWS_IMPORTBUILDER_HPP + +#include "WindowsPeFormat.hpp" +#include +#include + +namespace Elfpatcher::Windows { + +struct WindowsImports { + PeSection Section; + PeDirectory Directory; + PeDirectory AddressTable; + std::map Functions; +}; + +class WindowsImportBuilder { +public: + WindowsImports Build(std::uint32_t sectionRva) const; + std::vector ReadLibraries(const Domain::SysVDynamicSection& dynamicSection) const; +}; + +} + +#endif diff --git a/core/elfpatcher/src/windows/WindowsLoadImage.cpp b/core/elfpatcher/src/windows/WindowsLoadImage.cpp new file mode 100644 index 000000000..9900d2b2f --- /dev/null +++ b/core/elfpatcher/src/windows/WindowsLoadImage.cpp @@ -0,0 +1,107 @@ +#include "WindowsLoadImage.hpp" +#include +#include +#include + +namespace Elfpatcher::Windows { + +WindowsLoadImage::WindowsLoadImage(const std::vector& source, const std::vector& headers) { + if (source.size() < 64 || Io::ReadU32(source, 0) != 0x464c457f || source[4] != 2 || source[5] != 1 || source[6] != 1 || Io::ReadU16(source, 18) != 62) + throw Domain::RelinkerException("Windows output requires a little-endian ELF64 x86-64 image"); + const auto type = Io::ReadU16(source, 16); + if (type != 3 && type != 0xfe10 && type != 0xfe18) + throw Domain::RelinkerException("Windows output requires a position-independent ELF image"); + for (const auto& header : headers) { + if (header.Type != 1) + continue; + if (header.FileSize > header.MemorySize || header.Offset > source.size() || header.FileSize > source.size() - header.Offset) + throw Domain::RelinkerException("Invalid PT_LOAD file range", header.Offset); + if (header.MemorySize == 0) + throw Domain::RelinkerException("Empty PT_LOAD segment", header.Offset); + if (header.MappedAddress > std::numeric_limits::max() - header.MemorySize) + throw Domain::RelinkerException("PT_LOAD address overflow", header.MappedAddress); + if ((header.Flags & ~7u) != 0) + throw Domain::RelinkerException("Unsupported PT_LOAD permission flags", header.Offset); + segments.push_back(header); + } + if (segments.empty()) + throw Domain::RelinkerException("No PT_LOAD segments found"); + std::sort(segments.begin(), segments.end(), [](const auto& left, const auto& right) { return left.MappedAddress < right.MappedAddress; }); + firstAddress = segments.front().MappedAddress & ~static_cast(SectionAlignment - 1); + const auto end = segments.back().MappedAddress + segments.back().MemorySize; + data.resize(AlignRva(end - firstAddress)); + CheckedRva(LoadRva + static_cast(data.size())); + pageFlags.resize(data.size() / SectionAlignment); + std::uint64_t previousEnd = firstAddress; + for (const auto& segment : segments) { + if (segment.MappedAddress < previousEnd) + throw Domain::RelinkerException("Overlapping PT_LOAD memory ranges", segment.MappedAddress); + previousEnd = segment.MappedAddress + segment.MemorySize; + const auto offset = static_cast(segment.MappedAddress - firstAddress); + std::copy_n(source.begin() + static_cast(segment.Offset), static_cast(segment.FileSize), data.begin() + static_cast(offset)); + std::uint32_t flags = 0; + if ((segment.Flags & 1) != 0) + flags |= SectionExecute | SectionRead; + if ((segment.Flags & 2) != 0) + flags |= SectionWrite; + if ((segment.Flags & 4) != 0) + flags |= SectionRead; + for (auto page = offset / SectionAlignment; page <= (offset + segment.MemorySize - 1) / SectionAlignment; ++page) + pageFlags[page] |= flags; + } + const auto entry = Io::ReadU64(source, 24); + entryRva = GetRva(entry); + if ((pageFlags[(entryRva - LoadRva) / SectionAlignment] & SectionExecute) == 0) + throw Domain::RelinkerException("ELF entry point is not executable", entry); +} + +std::uint32_t WindowsLoadImage::GetRva(const std::uint64_t address, const std::uint64_t size) const { + if (size == 0) + throw Domain::RelinkerException("Cannot map an empty ELF address range", address); + for (const auto& segment : segments) { + if (address >= segment.MappedAddress && address - segment.MappedAddress < segment.MemorySize && size <= segment.MemorySize - (address - segment.MappedAddress)) + return CheckedRva(LoadRva + address - firstAddress); + } + throw Domain::RelinkerException("ELF address is not contained in PT_LOAD memory", address); +} + +std::uint32_t WindowsLoadImage::GetEndRva() const { + return CheckedRva(LoadRva + data.size()); +} + +std::uint32_t WindowsLoadImage::GetEntryRva() const { + return entryRva; +} + +std::uint64_t WindowsLoadImage::GetRelocatedAddress(const std::uint64_t address) const { + if (address < firstAddress || address - firstAddress > data.size()) + throw Domain::RelinkerException("Relative relocation addend is outside the ELF image", address); + return ImageBase + LoadRva + address - firstAddress; +} + +void WindowsLoadImage::WritePointer(const std::uint64_t address, const std::uint64_t value) { + Io::WriteU64(data, GetRva(address, 8) - LoadRva, value); +} + +void WindowsLoadImage::RequireWritable(const std::uint64_t address, const std::uint64_t size) const { + const auto offset = GetRva(address, size) - LoadRva; + for (auto page = offset / SectionAlignment; page <= (offset + size - 1) / SectionAlignment; ++page) { + if ((pageFlags[page] & SectionWrite) == 0) + throw Domain::RelinkerException("Runtime import target is not writable", address); + } +} + +std::vector WindowsLoadImage::BuildSections() const { + std::vector sections; + for (std::size_t first = 0; first < pageFlags.size();) { + auto last = first + 1; + while (last < pageFlags.size() && pageFlags[last] == pageFlags[first]) + ++last; + const auto characteristics = pageFlags[first] | ((pageFlags[first] & SectionExecute) != 0 ? 0x20u : 0x40u); + sections.push_back({".elf" + std::to_string(sections.size()), CheckedRva(LoadRva + first * SectionAlignment), characteristics, {data.begin() + static_cast(first * SectionAlignment), data.begin() + static_cast(last * SectionAlignment)}}); + first = last; + } + return sections; +} + +} diff --git a/core/elfpatcher/src/windows/WindowsLoadImage.hpp b/core/elfpatcher/src/windows/WindowsLoadImage.hpp new file mode 100644 index 000000000..65fb55129 --- /dev/null +++ b/core/elfpatcher/src/windows/WindowsLoadImage.hpp @@ -0,0 +1,30 @@ +#ifndef ELFPATCHER_WINDOWS_LOADIMAGE_HPP +#define ELFPATCHER_WINDOWS_LOADIMAGE_HPP + +#include "WindowsPeFormat.hpp" +#include + +namespace Elfpatcher::Windows { + +class WindowsLoadImage { +public: + WindowsLoadImage(const std::vector& source, const std::vector& headers); + std::uint32_t GetRva(std::uint64_t address, std::uint64_t size = 1) const; + std::uint32_t GetEndRva() const; + std::uint32_t GetEntryRva() const; + std::uint64_t GetRelocatedAddress(std::uint64_t address) const; + void WritePointer(std::uint64_t address, std::uint64_t value); + void RequireWritable(std::uint64_t address, std::uint64_t size) const; + std::vector BuildSections() const; + +private: + std::vector segments; + std::vector data; + std::vector pageFlags; + std::uint64_t firstAddress; + std::uint32_t entryRva; +}; + +} + +#endif diff --git a/core/elfpatcher/src/windows/WindowsPeFormat.cpp b/core/elfpatcher/src/windows/WindowsPeFormat.cpp new file mode 100644 index 000000000..3f26d472a --- /dev/null +++ b/core/elfpatcher/src/windows/WindowsPeFormat.cpp @@ -0,0 +1,28 @@ +#include "WindowsPeFormat.hpp" +#include +#include +#include + +namespace Elfpatcher::Windows { + +std::uint32_t CheckedRva(const std::uint64_t value) { + if (value > std::numeric_limits::max()) + throw Domain::RelinkerException("PE image exceeds the supported 2 GiB address range", value); + return static_cast(value); +} + +std::uint32_t AlignRva(const std::uint64_t value) { + return CheckedRva((static_cast(CheckedRva(value)) + SectionAlignment - 1) & ~(static_cast(SectionAlignment) - 1)); +} + +std::string ReadString(const std::vector& bytes, const std::size_t offset) { + if (offset >= bytes.size()) + throw Domain::RelinkerException("String offset is outside its table", offset); + const auto begin = bytes.begin() + static_cast(offset); + const auto end = std::find(begin, bytes.end(), 0); + if (end == bytes.end()) + throw Domain::RelinkerException("Unterminated string in ELF table", offset); + return {begin, end}; +} + +} diff --git a/core/elfpatcher/src/windows/WindowsPeFormat.hpp b/core/elfpatcher/src/windows/WindowsPeFormat.hpp index bbea05be0..9c2d5036d 100644 --- a/core/elfpatcher/src/windows/WindowsPeFormat.hpp +++ b/core/elfpatcher/src/windows/WindowsPeFormat.hpp @@ -1,66 +1,48 @@ -#ifndef CORE_ELFPATCHER_SRC_WINDOWS_WINDOWSPEFORMAT_HPP -#define CORE_ELFPATCHER_SRC_WINDOWS_WINDOWSPEFORMAT_HPP +#ifndef ELFPATCHER_WINDOWS_PEFORMAT_HPP +#define ELFPATCHER_WINDOWS_PEFORMAT_HPP -constexpr std::uint16_t kMzMagic = 0x5A4D; -constexpr std::uint32_t kPeSignature = 0x00004550; -constexpr std::uint16_t kMachinAmd64 = 0x8664; -constexpr std::uint16_t kOptMagicPe32Plus = 0x020B; -constexpr std::uint16_t kCharsDll = 0x2000; -constexpr std::uint16_t kCharsExe = 0x0002; -constexpr std::uint16_t kCharsLargeAddressAware = 0x0020; -constexpr std::uint32_t kSectionAlignment = 0x1000; -constexpr std::uint32_t kFileAlignment = 0x200; -constexpr std::uint32_t kOsVersion = 6; -constexpr std::uint32_t kOsVersionMinor = 0; -constexpr std::uint32_t kSubsystemConsole = 3; -constexpr std::uint32_t kNumberOfRvaAndSizes = 16; -constexpr std::uint32_t kSizeOfPeHeaders = 0x400; +#include +#include +#include +#include -constexpr std::uint32_t kRelTypeDir64 = 10; -constexpr std::uint32_t kRelTypeAbsolute = 0; -constexpr std::uint32_t kRelocBlockHeaderSize = 8; -constexpr std::uint32_t kRelocEntrySize = 2; +namespace Elfpatcher::Windows { -constexpr std::uint32_t kSecExec = 0x60000020; -constexpr std::uint32_t kSecRW = 0xC0000040; -constexpr std::uint32_t kSecRO = 0x40000040; -constexpr std::uint32_t kSecDiscard = 0x02000000; - -struct MzHeader { - std::uint8_t data[0x40]; -}; +inline constexpr std::uint32_t SectionAlignment = 0x1000; +inline constexpr std::uint32_t FileAlignment = 0x200; +inline constexpr std::uint32_t LoadRva = 0x10000; +inline constexpr std::uint64_t ImageBase = 0x140000000; +inline constexpr std::uint32_t SectionRead = 0x40000000; +inline constexpr std::uint32_t SectionWrite = 0x80000000; +inline constexpr std::uint32_t SectionExecute = 0x20000000; struct PeSection { - char name[8]; - std::uint32_t virtualSize; - std::uint32_t virtualAddress; - std::uint32_t rawSize; - std::uint32_t rawOffset; - std::uint32_t characteristics; - std::vector data; + std::string Name; + std::uint32_t Rva; + std::uint32_t Characteristics; + std::vector Data; }; -struct DataDirectory { - std::uint32_t rva; - std::uint32_t size; +struct PeDirectory { + std::uint32_t Rva = 0; + std::uint32_t Size = 0; }; -struct LoadSegment { - std::uint64_t vaddr; - std::uint64_t memSize; - std::uint64_t fileOffset; - std::uint64_t fileSize; - std::uint32_t flags; +struct PeImport { + std::string Name; + std::uint32_t TargetRva; + std::uint64_t Addend; }; -struct ImportThunkEntry { - std::string symbolName; - std::uint32_t gotRva; +struct PeRelocations { + std::vector BaseRelocations; + std::vector Imports; }; -struct ImportDllBlock { - std::string dllName; - std::vector thunks; -}; +std::uint32_t CheckedRva(std::uint64_t value); +std::uint32_t AlignRva(std::uint64_t value); +std::string ReadString(const std::vector& bytes, std::size_t offset); -#endif \ No newline at end of file +} + +#endif diff --git a/core/elfpatcher/src/windows/WindowsPePatcher.cpp b/core/elfpatcher/src/windows/WindowsPePatcher.cpp index 54954b1ab..15a414c7d 100644 --- a/core/elfpatcher/src/windows/WindowsPePatcher.cpp +++ b/core/elfpatcher/src/windows/WindowsPePatcher.cpp @@ -1,556 +1,40 @@ #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "WindowsPeFormat.hpp" +#include "WindowsEntryStubBuilder.hpp" +#include "WindowsLoadImage.hpp" +#include "WindowsPeWriter.hpp" +#include "WindowsRelocationBuilder.hpp" +#include namespace Elfpatcher::Windows { -namespace { - -MzHeader buildMzStub(std::uint32_t peOffset) { - MzHeader h{}; - h.data[0] = 'M'; - h.data[1] = 'Z'; - std::memcpy(h.data + 0x3c, &peOffset, 4); - return h; -} - -void writeSectionHeader(std::vector& buf, std::size_t off, const PeSection& sec) { - std::memcpy(buf.data() + off, sec.name, 8); - Io::WriteU32(buf, off + 8, sec.virtualSize); - Io::WriteU32(buf, off + 12, sec.virtualAddress); - Io::WriteU32(buf, off + 16, sec.rawSize); - Io::WriteU32(buf, off + 20, sec.rawOffset); - Io::WriteU32(buf, off + 24, 0); - Io::WriteU32(buf, off + 28, 0); - Io::WriteU16(buf, off + 32, 0); - Io::WriteU16(buf, off + 34, 0); - Io::WriteU32(buf, off + 36, sec.characteristics); -} - -std::vector buildRelocSection( - const std::vector& relaData, - const std::uint64_t imageBase) -{ - constexpr std::size_t kRelaEntSize = 24; - if (relaData.size() % kRelaEntSize != 0) - throw Domain::RelinkerException("RELA data size is not a multiple of 24"); - - std::map> pageToOffsets; - - for (std::size_t i = 0; i + kRelaEntSize <= relaData.size(); i += kRelaEntSize) { - std::uint64_t rOffset, rInfo; - std::int64_t rAddend; - std::memcpy(&rOffset, relaData.data() + i, 8); - std::memcpy(&rInfo, relaData.data() + i + 8, 8); - std::memcpy(&rAddend, relaData.data() + i + 16, 8); - - const auto relType = static_cast(rInfo & 0xffffffff); - const auto symIdx = static_cast(rInfo >> 32); - - constexpr std::uint32_t kRelativeType = 8; - if (relType != kRelativeType) - continue; - if (symIdx != 0) - throw Domain::RelinkerException("R_X86_64_RELATIVE with non-zero symbol index"); - - if (rOffset < imageBase) - throw Domain::RelinkerException("RELATIVE relocation target vaddr below imageBase", rOffset); - - const std::uint64_t rva = rOffset - imageBase; - if (rva > 0xFFFFFFFFull) - throw Domain::RelinkerException("RELATIVE relocation RVA exceeds 32-bit range", rOffset); - - const auto rva32 = static_cast(rva); - const std::uint32_t page = rva32 & ~0xFFFu; - const std::uint32_t pageOff = rva32 & 0xFFFu; - pageToOffsets[page].push_back(pageOff); +std::vector WindowsPePatcher::Patch(const std::vector& sourceElf, const std::vector& originalHeaders, const Domain::SysVDynamicSection& dynamicSection, const std::uint64_t originalPltGotVaddr, const std::string& runPath) { + WindowsLoadImage image(sourceElf, originalHeaders); + if (originalPltGotVaddr != 0) + image.GetRva(originalPltGotVaddr, 8); + const WindowsRelocationBuilder relocationBuilder; + const auto relocations = relocationBuilder.Apply(image, dynamicSection); + auto sections = image.BuildSections(); + std::array directories{}; + auto nextRva = image.GetEndRva(); + auto relocationData = relocationBuilder.BuildBaseRelocations(relocations.BaseRelocations); + if (!relocationData.empty()) { + directories[5] = {nextRva, CheckedRva(relocationData.size())}; + sections.push_back({".reloc", nextRva, SectionRead | 0x02000040u, std::move(relocationData)}); + nextRva = AlignRva(nextRva + sections.back().Data.size()); } - - std::vector buf; - for (auto& [page, offsets] : pageToOffsets) { - std::sort(offsets.begin(), offsets.end()); - auto entryCount = static_cast(offsets.size()); - if (entryCount % 2 != 0) { - offsets.push_back(0); - entryCount++; - } - const std::uint32_t blockSize = kRelocBlockHeaderSize + entryCount * kRelocEntrySize; - Io::AppendU32(buf, page); - Io::AppendU32(buf, blockSize); - for (const std::uint32_t off : offsets) { - const auto entry = static_cast((kRelTypeDir64 << 12) | (off & 0xFFF)); - Io::AppendU16(buf, entry); - } - } - return buf; -} - - -std::vector buildImportSection( - const std::vector& dlls, - const std::uint32_t sectionRva, - std::vector>& outGotPatches) -{ - if (dlls.empty()) - return {}; - - const auto descriptorTableSize = static_cast((dlls.size() + 1) * 20); - - std::vector descriptorBuf; - std::vector dataBuf; - - const std::uint32_t dataBase = sectionRva + descriptorTableSize; - - for (const auto& dll : dlls) { - const std::uint32_t dllNameRva = dataBase + static_cast(dataBuf.size()); - Io::AppendString(dataBuf, dll.dllName); - Io::AlignBuffer(dataBuf, 2); - - const std::uint32_t iltRva = dataBase + static_cast(dataBuf.size()); - for (const auto& thunk : dll.thunks) { - const std::uint32_t hintNameRva = dataBase + static_cast(dataBuf.size()) - + static_cast((dll.thunks.size() + 1) * 8); - Io::AppendU64(dataBuf, hintNameRva); - } - Io::AppendU64(dataBuf, 0); - - for (const auto& thunk : dll.thunks) { - Io::AppendU16(dataBuf, 0); - Io::AppendString(dataBuf, thunk.symbolName); - Io::AlignBuffer(dataBuf, 2); - } - - const std::uint32_t iatRva = dataBase + static_cast(dataBuf.size()); - for (std::size_t i = 0; i < dll.thunks.size(); ++i) { - const std::uint32_t iatEntryRva = iatRva + static_cast(i * 8); - outGotPatches.emplace_back(dll.thunks[i].gotRva, iatEntryRva); - Io::AppendU64(dataBuf, 0); - } - Io::AppendU64(dataBuf, 0); - - Io::AppendU32(descriptorBuf, iltRva); - Io::AppendU32(descriptorBuf, 0); - Io::AppendU32(descriptorBuf, 0); - Io::AppendU32(descriptorBuf, dllNameRva); - Io::AppendU32(descriptorBuf, iatRva); - } - - Io::AppendU32(descriptorBuf, 0); - Io::AppendU32(descriptorBuf, 0); - Io::AppendU32(descriptorBuf, 0); - Io::AppendU32(descriptorBuf, 0); - Io::AppendU32(descriptorBuf, 0); - - std::vector result; - result.insert(result.end(), descriptorBuf.begin(), descriptorBuf.end()); - result.insert(result.end(), dataBuf.begin(), dataBuf.end()); - return result; -} - -std::vector buildEntryStub(std::uint64_t realEntryVaddr, std::uint64_t stubVaddr) { - std::vector s; - const std::uint64_t callInsnVaddr = stubVaddr + s.size() + 5; - const std::int64_t rel = static_cast(realEntryVaddr) - static_cast(callInsnVaddr); - if (rel < std::int64_t(-0x80000000) || rel > std::int64_t(0x7FFFFFFF)) - throw Domain::RelinkerException("Entry stub: rel32 call target out of range"); - const auto rel32 = static_cast(rel); - s.push_back(0xE8); - s.push_back(static_cast(rel32 & 0xFF)); - s.push_back(static_cast((rel32 >> 8) & 0xFF)); - s.push_back(static_cast((rel32 >> 16) & 0xFF)); - s.push_back(static_cast((rel32 >> 24) & 0xFF)); - s.push_back(0xC3); - return s; -} - -std::uint64_t readElfU64(const std::vector& buf, std::size_t off) { - if (off + 8 > buf.size()) - throw Domain::RelinkerException("ELF read out of bounds", off); - std::uint64_t v; - std::memcpy(&v, buf.data() + off, 8); - return v; -} - -std::uint32_t readElfU32(const std::vector& buf, std::size_t off) { - if (off + 4 > buf.size()) - throw Domain::RelinkerException("ELF read out of bounds", off); - std::uint32_t v; - std::memcpy(&v, buf.data() + off, 4); - return v; -} - -std::string readCStr(const std::vector& buf, std::size_t off) { - std::string s; - while (off < buf.size() && buf[off] != 0) - s.push_back(static_cast(buf[off++])); - return s; -} - -} - -WindowsPePatcher::WindowsPePatcher( - std::shared_ptr entryStubBuilder, - std::shared_ptr programHeaderLayoutBuilder, - std::shared_ptr sectionHeaderTableBuilder, - std::shared_ptr byteWriter) - : _entryStubBuilder(std::move(entryStubBuilder)) - , _programHeaderLayoutBuilder(std::move(programHeaderLayoutBuilder)) - , _sectionHeaderTableBuilder(std::move(sectionHeaderTableBuilder)) - , _byteWriter(std::move(byteWriter)) -{ -} - -std::vector WindowsPePatcher::Patch( - const std::vector& sourceElf, - const std::vector& originalHeaders, - const Domain::SysVDynamicSection& dynamicSection, - std::uint64_t originalPltGotVaddr, - const std::string& runPath) -{ - if (sourceElf.size() < 64) - throw Domain::RelinkerException("Source ELF too small"); - if (sourceElf[0] != 0x7f || sourceElf[1] != 'E' || sourceElf[2] != 'L' || sourceElf[3] != 'F') - throw Domain::RelinkerException("Source is not an ELF file"); - - const std::uint64_t elfEntryVaddr = readElfU64(sourceElf, 0x18); - - std::vector loadSegs; - auto imageBase = std::uint64_t(-1); - std::uint64_t imageTop = 0; - - for (const auto& ph : originalHeaders) { - if (ph.Type != PT_LOAD) continue; - if (ph.MemorySize == 0) continue; - LoadSegment seg{}; - seg.vaddr = ph.MappedAddress; - seg.memSize = ph.MemorySize; - seg.fileOffset = ph.Offset; - seg.fileSize = ph.FileSize; - seg.flags = ph.Flags; - loadSegs.push_back(seg); - if (seg.vaddr < imageBase) imageBase = seg.vaddr; - const std::uint64_t top = Io::AlignUp64(seg.vaddr + seg.memSize, kSectionAlignment); - if (top > imageTop) imageTop = top; - } - - if (loadSegs.empty()) - throw Domain::RelinkerException("No PT_LOAD segments found"); - if (imageBase == std::uint64_t(-1)) - throw Domain::RelinkerException("Could not determine image base"); - if (imageBase % kSectionAlignment != 0) - throw Domain::RelinkerException("Image base is not section-aligned"); - - std::uint32_t gotRva = 0; - bool gotFound = false; - if (originalPltGotVaddr != 0) { - if (originalPltGotVaddr < imageBase) - throw Domain::RelinkerException("GOT vaddr below image base"); - const std::uint64_t gotRva64 = originalPltGotVaddr - imageBase; - if (gotRva64 > 0xFFFFFFFFull) - throw Domain::RelinkerException("GOT RVA exceeds 32-bit range"); - gotRva = static_cast(gotRva64); - gotFound = true; - } - - std::vector imageBuf(static_cast(imageTop - imageBase), 0); - - for (const auto& seg : loadSegs) { - const std::uint64_t destOff = seg.vaddr - imageBase; - if (destOff + seg.fileSize > imageBuf.size()) - throw Domain::RelinkerException("PT_LOAD segment exceeds image buffer", seg.vaddr); - if (seg.fileOffset + seg.fileSize > sourceElf.size()) - throw Domain::RelinkerException("PT_LOAD segment file data out of bounds", seg.fileOffset); - std::memcpy(imageBuf.data() + destOff, sourceElf.data() + seg.fileOffset, static_cast(seg.fileSize)); - } - - const auto loadImageRva = static_cast(kSizeOfPeHeaders); - if (imageBase < loadImageRva) - throw Domain::RelinkerException("Image base too small to accommodate PE headers"); - - const std::uint64_t adjustedBase = imageBase - loadImageRva; - if (adjustedBase % kSectionAlignment != 0) - throw Domain::RelinkerException("Adjusted image base is not section-aligned"); - - std::vector importDlls; - std::vector> gotPatches; - - { - const std::vector& relaPlt = dynamicSection.RelaPltData; - const std::vector& dynStr = dynamicSection.DynStrData; - const std::vector& dynSym = dynamicSection.DynSymData; - - constexpr std::size_t kRelaEnt = 24; - constexpr std::size_t kSymEnt = 24; - - if (relaPlt.size() % kRelaEnt != 0) - throw Domain::RelinkerException("RelaPlt size not multiple of 24"); - - std::map dllMap; - - for (std::size_t i = 0; i + kRelaEnt <= relaPlt.size(); i += kRelaEnt) { - std::uint64_t rOffset, rInfo; - std::memcpy(&rOffset, relaPlt.data() + i, 8); - std::memcpy(&rInfo, relaPlt.data() + i + 8, 8); - - const auto symIdx = static_cast(rInfo >> 32); - const auto relType = static_cast(rInfo & 0xffffffff); - - constexpr std::uint32_t kJumpSlot = 7; - if (relType != kJumpSlot) - throw Domain::RelinkerException("Unexpected relocation type in RelaPlt"); - if (symIdx == 0) - throw Domain::RelinkerException("JUMP_SLOT relocation with zero symbol index"); - - const std::size_t symOff = static_cast(symIdx) * kSymEnt; - if (symOff + 4 > dynSym.size()) - throw Domain::RelinkerException("Symbol index out of dynSym bounds"); - - std::uint32_t nameOff; - std::memcpy(&nameOff, dynSym.data() + symOff, 4); - if (nameOff >= dynStr.size()) - throw Domain::RelinkerException("Symbol name offset out of dynStr bounds"); - - const std::string symName = readCStr(dynStr, nameOff); - if (symName.empty()) - throw Domain::RelinkerException("Empty symbol name in JUMP_SLOT relocation"); - - if (rOffset < imageBase) - throw Domain::RelinkerException("JUMP_SLOT target vaddr below image base", rOffset); - const std::uint64_t rva64 = rOffset - imageBase; - if (rva64 > 0xFFFFFFFFull) - throw Domain::RelinkerException("JUMP_SLOT target RVA exceeds 32-bit range"); - const auto targetGotRva = static_cast(rva64); - - const std::string dllName = symName + ".dll"; - if (dllMap.find(dllName) == dllMap.end()) { - importDlls.push_back({dllName, {}}); - dllMap[dllName] = &importDlls.back(); - } - dllMap[dllName]->thunks.push_back({symName, targetGotRva}); - } - - const std::vector& relaData = dynamicSection.RelaData; - if (relaData.size() % kRelaEnt != 0) - throw Domain::RelinkerException("RelaData size not multiple of 24"); - - constexpr std::uint32_t kGlobDat = 6; - constexpr std::uint32_t kAbs64 = 1; - - for (std::size_t i = 0; i + kRelaEnt <= relaData.size(); i += kRelaEnt) { - std::uint64_t rOffset, rInfo; - std::memcpy(&rOffset, relaData.data() + i, 8); - std::memcpy(&rInfo, relaData.data() + i + 8, 8); - - const auto symIdx = static_cast(rInfo >> 32); - const auto relType = static_cast(rInfo & 0xffffffff); - - constexpr std::uint32_t kRelativeType = 8; - if (relType == kRelativeType) continue; - - if (relType != kGlobDat && relType != kAbs64) - throw Domain::RelinkerException("Unexpected relocation type in RelaData"); - if (symIdx == 0) - throw Domain::RelinkerException("GLOB_DAT/ABS64 relocation with zero symbol index"); - - const std::size_t symOff = static_cast(symIdx) * kSymEnt; - if (symOff + 4 > dynSym.size()) - throw Domain::RelinkerException("Symbol index out of dynSym bounds"); - - std::uint32_t nameOff; - std::memcpy(&nameOff, dynSym.data() + symOff, 4); - if (nameOff >= dynStr.size()) - throw Domain::RelinkerException("Symbol name offset out of dynStr bounds"); - - const std::string symName = readCStr(dynStr, nameOff); - if (symName.empty()) - throw Domain::RelinkerException("Empty symbol name in GLOB_DAT/ABS64 relocation"); - - if (rOffset < imageBase) - throw Domain::RelinkerException("GLOB_DAT target vaddr below image base", rOffset); - const std::uint64_t rva64 = rOffset - imageBase; - if (rva64 > 0xFFFFFFFFull) - throw Domain::RelinkerException("GLOB_DAT target RVA exceeds 32-bit range"); - const auto targetGotRva = static_cast(rva64); - - const std::string dllName = symName + ".dll"; - if (dllMap.find(dllName) == dllMap.end()) { - importDlls.push_back({dllName, {}}); - dllMap[dllName] = &importDlls.back(); - } - dllMap[dllName]->thunks.push_back({symName, targetGotRva}); - } - } - - const auto loadImageSize = static_cast(imageBuf.size()); - const std::uint32_t importSectionRva = Io::AlignUp(loadImageRva + loadImageSize, kSectionAlignment); - - std::vector importSectionData = buildImportSection(importDlls, importSectionRva, gotPatches); - Io::AlignBuffer(importSectionData, kFileAlignment); - - for (const auto& [gotRva_, iatRva] : gotPatches) { - const auto destInImage = static_cast(gotRva_); - if (destInImage + 8 > imageBuf.size()) - throw Domain::RelinkerException("GOT patch target outside image buffer"); - const std::uint64_t iatVa = adjustedBase + iatRva; - std::memcpy(imageBuf.data() + destInImage, &iatVa, 8); - } - - const std::uint32_t relocSectionRva = Io::AlignUp(importSectionRva + static_cast(importSectionData.size()), kSectionAlignment); - std::vector relocSectionData = buildRelocSection(dynamicSection.RelaData, imageBase); - Io::AlignBuffer(relocSectionData, kFileAlignment); - - const std::uint32_t stubSectionRva = Io::AlignUp(relocSectionRva + static_cast(relocSectionData.size()), kSectionAlignment); - const std::uint64_t stubVaddr = adjustedBase + stubSectionRva; - std::vector stubData = buildEntryStub(elfEntryVaddr, stubVaddr); - Io::AlignBuffer(stubData, kFileAlignment); - - const std::uint32_t entryPointRva = stubSectionRva; - - std::uint16_t numSections = 1; - if (!importSectionData.empty()) numSections++; - if (!relocSectionData.empty()) numSections++; - numSections++; - - const std::uint32_t sizeOfHeaders = Io::AlignUp( - 0x40 + 4 + 20 + 240 + static_cast(numSections) * 40, - kFileAlignment); - - if (sizeOfHeaders > kSizeOfPeHeaders) - throw Domain::RelinkerException("PE headers exceed reserved header block size"); - - const std::uint32_t sizeOfImage = Io::AlignUp(stubSectionRva + static_cast(stubData.size()), kSectionAlignment); - - const std::uint32_t mzPeOffset = 0x40; - std::vector result(kSizeOfPeHeaders, 0); - - Io::WriteU16(result, 0, kMzMagic); - Io::WriteU32(result, 0x3c, mzPeOffset); - - std::size_t off = mzPeOffset; - Io::WriteU32(result, off, kPeSignature); off += 4; - - Io::WriteU16(result, off, kMachinAmd64); off += 2; - Io::WriteU16(result, off, numSections); off += 2; - Io::WriteU32(result, off, 0); off += 4; - Io::WriteU32(result, off, 0); off += 4; - Io::WriteU32(result, off, 0); off += 4; - Io::WriteU16(result, off, 240); off += 2; - Io::WriteU16(result, off, kCharsExe | kCharsLargeAddressAware); off += 2; - - Io::WriteU16(result, off, kOptMagicPe32Plus); off += 2; - Io::WriteU8(result, off, 0); off += 1; - Io::WriteU8(result, off, 0); off += 1; - Io::WriteU32(result, off, 0); off += 4; - Io::WriteU32(result, off, 0); off += 4; - Io::WriteU32(result, off, 0); off += 4; - Io::WriteU32(result, off, entryPointRva); off += 4; - Io::WriteU32(result, off, loadImageRva); off += 4; - - Io::WriteU64(result, off, adjustedBase); off += 8; - Io::WriteU32(result, off, kSectionAlignment); off += 4; - Io::WriteU32(result, off, kFileAlignment); off += 4; - Io::WriteU16(result, off, static_cast(kOsVersion)); off += 2; - Io::WriteU16(result, off, static_cast(kOsVersionMinor)); off += 2; - Io::WriteU16(result, off, 0); off += 2; - Io::WriteU16(result, off, 0); off += 2; - Io::WriteU16(result, off, static_cast(kOsVersion)); off += 2; - Io::WriteU16(result, off, static_cast(kOsVersionMinor)); off += 2; - Io::WriteU32(result, off, 0); off += 4; - Io::WriteU32(result, off, sizeOfImage); off += 4; - Io::WriteU32(result, off, kSizeOfPeHeaders); off += 4; - Io::WriteU32(result, off, 0); off += 4; - Io::WriteU16(result, off, static_cast(kSubsystemConsole)); off += 2; - Io::WriteU16(result, off, 0); off += 2; - Io::WriteU64(result, off, 0); off += 8; - Io::WriteU64(result, off, 0); off += 8; - Io::WriteU64(result, off, 0); off += 8; - Io::WriteU64(result, off, 0); off += 8; - Io::WriteU32(result, off, 0); off += 4; - Io::WriteU32(result, off, kNumberOfRvaAndSizes); off += 4; - - for (std::uint32_t i = 0; i < kNumberOfRvaAndSizes; ++i) { - Io::WriteU32(result, off, 0); off += 4; - Io::WriteU32(result, off, 0); off += 4; - } - - if (!importSectionData.empty()) { - Io::WriteU32(result, mzPeOffset + 4 + 20 + 104, importSectionRva); - Io::WriteU32(result, mzPeOffset + 4 + 20 + 108, static_cast(importDlls.size() * 20)); - } - if (!relocSectionData.empty()) { - Io::WriteU32(result, mzPeOffset + 4 + 20 + 168, relocSectionRva); - Io::WriteU32(result, mzPeOffset + 4 + 20 + 172, static_cast(relocSectionData.size())); - } - - std::size_t shdrsOff = mzPeOffset + 4 + 20 + 240; - - auto writeSec = [&](const char* name, std::uint32_t virtualSize, std::uint32_t rva, std::uint32_t rawSize, std::uint32_t rawOff, std::uint32_t chars) { - char n[8]{}; - std::memcpy(n, name, std::min(std::strlen(name), std::size_t(8))); - std::memcpy(result.data() + shdrsOff, n, 8); - Io::WriteU32(result, shdrsOff + 8, virtualSize); - Io::WriteU32(result, shdrsOff + 12, rva); - Io::WriteU32(result, shdrsOff + 16, rawSize); - Io::WriteU32(result, shdrsOff + 20, rawOff); - Io::WriteU32(result, shdrsOff + 24, 0); - Io::WriteU32(result, shdrsOff + 28, 0); - Io::WriteU16(result, shdrsOff + 32, 0); - Io::WriteU16(result, shdrsOff + 34, 0); - Io::WriteU32(result, shdrsOff + 36, chars); - shdrsOff += 40; - }; - - const std::uint32_t loadRawSize = Io::AlignUp(loadImageSize, kFileAlignment); - std::uint32_t curRawOff = kSizeOfPeHeaders; - - writeSec(".load", loadImageSize, loadImageRva, loadRawSize, curRawOff, kSecExec | kSecRW); - curRawOff += loadRawSize; - - std::uint32_t importRawOff = 0; - if (!importSectionData.empty()) { - importRawOff = curRawOff; - writeSec(".idata", static_cast(importSectionData.size()), importSectionRva, - static_cast(importSectionData.size()), curRawOff, kSecRW); - curRawOff += static_cast(importSectionData.size()); - } - - std::uint32_t relocRawOff = 0; - if (!relocSectionData.empty()) { - relocRawOff = curRawOff; - writeSec(".reloc", static_cast(relocSectionData.size()), relocSectionRva, - static_cast(relocSectionData.size()), curRawOff, kSecDiscard | kSecRO); - curRawOff += static_cast(relocSectionData.size()); - } - - const std::uint32_t stubRawOff = curRawOff; - writeSec(".entry", static_cast(stubData.size()), stubSectionRva, - static_cast(stubData.size()), stubRawOff, kSecExec); - - Io::AlignBuffer(result, kFileAlignment); - - { - std::vector loadRaw(imageBuf.begin(), imageBuf.end()); - loadRaw.resize(loadRawSize, 0); - result.insert(result.end(), loadRaw.begin(), loadRaw.end()); - } - - if (!importSectionData.empty()) - result.insert(result.end(), importSectionData.begin(), importSectionData.end()); - - if (!relocSectionData.empty()) - result.insert(result.end(), relocSectionData.begin(), relocSectionData.end()); - - result.insert(result.end(), stubData.begin(), stubData.end()); - - return result; + const WindowsImportBuilder importBuilder; + auto nativeImports = importBuilder.Build(nextRva); + directories[1] = nativeImports.Directory; + directories[12] = nativeImports.AddressTable; + nextRva = AlignRva(nextRva + nativeImports.Section.Data.size()); + const auto libraries = importBuilder.ReadLibraries(dynamicSection); + auto entry = WindowsEntryStubBuilder().Build(nextRva, image.GetEntryRva(), nativeImports, libraries, relocations.Imports, runPath); + directories[3] = entry.ExceptionDirectory; + const auto entryRva = entry.Code.Rva; + sections.push_back(std::move(nativeImports.Section)); + sections.push_back(std::move(entry.Data)); + sections.push_back(std::move(entry.Code)); + return WindowsPeWriter().Write(sections, entryRva, directories); } } diff --git a/core/elfpatcher/src/windows/WindowsPeWriter.cpp b/core/elfpatcher/src/windows/WindowsPeWriter.cpp new file mode 100644 index 000000000..c12c9bcff --- /dev/null +++ b/core/elfpatcher/src/windows/WindowsPeWriter.cpp @@ -0,0 +1,83 @@ +#include "WindowsPeWriter.hpp" +#include +#include +#include + +namespace Elfpatcher::Windows { + +std::vector WindowsPeWriter::Write(const std::vector& sections, const std::uint32_t entryRva, const std::array& directories) const { + constexpr std::size_t peOffset = 0x80; + constexpr std::size_t optionalOffset = peOffset + 24; + constexpr std::size_t sectionTable = optionalOffset + 240; + if (sections.empty() || sections.size() > 96 || sectionTable + sections.size() * 40 > LoadRva) + throw Domain::RelinkerException("Invalid PE section count"); + std::vector result(LoadRva); + Io::WriteU16(result, 0, 0x5a4d); + Io::WriteU32(result, 0x3c, peOffset); + Io::WriteU32(result, peOffset, 0x4550); + Io::WriteU16(result, peOffset + 4, 0x8664); + Io::WriteU16(result, peOffset + 6, static_cast(sections.size())); + Io::WriteU16(result, peOffset + 20, 240); + Io::WriteU16(result, peOffset + 22, directories[5].Size == 0 ? 0x23 : 0x22); + Io::WriteU16(result, optionalOffset, 0x20b); + Io::WriteU32(result, optionalOffset + 16, entryRva); + Io::WriteU64(result, optionalOffset + 24, ImageBase); + Io::WriteU32(result, optionalOffset + 32, SectionAlignment); + Io::WriteU32(result, optionalOffset + 36, FileAlignment); + Io::WriteU16(result, optionalOffset + 40, 6); + Io::WriteU16(result, optionalOffset + 42, 2); + Io::WriteU16(result, optionalOffset + 48, 6); + Io::WriteU16(result, optionalOffset + 50, 2); + Io::WriteU32(result, optionalOffset + 60, LoadRva); + Io::WriteU16(result, optionalOffset + 68, 3); + Io::WriteU16(result, optionalOffset + 70, directories[5].Size == 0 ? 0x100 : 0x160); + Io::WriteU64(result, optionalOffset + 72, 0x200000); + Io::WriteU64(result, optionalOffset + 80, 0x4000); + Io::WriteU64(result, optionalOffset + 88, 0x100000); + Io::WriteU64(result, optionalOffset + 96, 0x1000); + Io::WriteU32(result, optionalOffset + 108, static_cast(directories.size())); + std::uint32_t endRva = LoadRva; + std::uint32_t codeSize = 0; + std::uint32_t dataSize = 0; + bool foundEntry = false; + for (std::size_t index = 0; index < sections.size(); ++index) { + const auto& section = sections[index]; + if (section.Name.empty() || section.Name.size() > 8 || section.Data.empty() || section.Rva != endRva) + throw Domain::RelinkerException("Invalid PE section layout: " + section.Name, section.Rva); + const auto rawSize = Io::AlignUp(CheckedRva(section.Data.size()), FileAlignment); + const auto rawOffset = CheckedRva(result.size()); + const auto header = sectionTable + index * 40; + std::copy(section.Name.begin(), section.Name.end(), result.begin() + static_cast(header)); + Io::WriteU32(result, header + 8, CheckedRva(section.Data.size())); + Io::WriteU32(result, header + 12, section.Rva); + Io::WriteU32(result, header + 16, rawSize); + Io::WriteU32(result, header + 20, rawOffset); + Io::WriteU32(result, header + 36, section.Characteristics); + if ((section.Characteristics & SectionExecute) != 0) { + if (codeSize == 0) + Io::WriteU32(result, optionalOffset + 20, section.Rva); + codeSize = CheckedRva(static_cast(codeSize) + rawSize); + foundEntry |= entryRva >= section.Rva && entryRva - section.Rva < section.Data.size(); + } else { + dataSize = CheckedRva(static_cast(dataSize) + rawSize); + } + result.insert(result.end(), section.Data.begin(), section.Data.end()); + result.resize(static_cast(rawOffset) + rawSize); + endRva = AlignRva(section.Rva + static_cast(section.Data.size())); + } + if (!foundEntry) + throw Domain::RelinkerException("PE entry point is not inside executable section data", entryRva); + Io::WriteU32(result, optionalOffset + 4, codeSize); + Io::WriteU32(result, optionalOffset + 8, dataSize); + Io::WriteU32(result, optionalOffset + 56, endRva); + for (std::size_t index = 0; index < directories.size(); ++index) { + const auto& directory = directories[index]; + if ((directory.Rva == 0) != (directory.Size == 0) || directory.Rva > endRva || directory.Size > endRva - directory.Rva) + throw Domain::RelinkerException("Invalid PE data directory", index); + Io::WriteU32(result, optionalOffset + 112 + index * 8, directory.Rva); + Io::WriteU32(result, optionalOffset + 116 + index * 8, directory.Size); + } + return result; +} + +} diff --git a/core/elfpatcher/src/windows/WindowsPeWriter.hpp b/core/elfpatcher/src/windows/WindowsPeWriter.hpp new file mode 100644 index 000000000..bf4275971 --- /dev/null +++ b/core/elfpatcher/src/windows/WindowsPeWriter.hpp @@ -0,0 +1,15 @@ +#ifndef ELFPATCHER_WINDOWS_PEWRITER_HPP +#define ELFPATCHER_WINDOWS_PEWRITER_HPP + +#include "WindowsPeFormat.hpp" + +namespace Elfpatcher::Windows { + +class WindowsPeWriter { +public: + std::vector Write(const std::vector& sections, std::uint32_t entryRva, const std::array& directories) const; +}; + +} + +#endif diff --git a/core/elfpatcher/src/windows/WindowsRelocationBuilder.cpp b/core/elfpatcher/src/windows/WindowsRelocationBuilder.cpp new file mode 100644 index 000000000..e4adf727d --- /dev/null +++ b/core/elfpatcher/src/windows/WindowsRelocationBuilder.cpp @@ -0,0 +1,71 @@ +#include "WindowsRelocationBuilder.hpp" +#include +#include + +namespace Elfpatcher::Windows { + +PeRelocations WindowsRelocationBuilder::Apply(WindowsLoadImage& image, const Domain::SysVDynamicSection& dynamicSection) const { + if (dynamicSection.DynSymData.empty() || dynamicSection.DynSymData.size() % 24 != 0) + throw Domain::RelinkerException("Invalid ELF dynamic symbol table size"); + PeRelocations result; + std::map ranges; + const auto applyTable = [&](const std::vector& table, const bool plt) { + if (table.size() % 24 != 0) + throw Domain::RelinkerException("Invalid ELF RELA table size"); + for (std::size_t offset = 0; offset < table.size(); offset += 24) { + const auto target = Io::ReadU64(table, offset); + const auto info = Io::ReadU64(table, offset + 8); + const auto addend = Io::ReadU64(table, offset + 16); + const auto type = static_cast(info); + const auto symbol = info >> 32; + if ((plt && type != 7) || (type != 1 && type != 6 && type != 7 && type != 8)) + throw Domain::RelinkerException("Unsupported Windows relocation type " + std::to_string(type), target); + const auto rva = image.GetRva(target, 8); + const auto next = ranges.lower_bound(target); + if ((next != ranges.end() && next->first < target + 8) || (next != ranges.begin() && std::prev(next)->second > target)) + throw Domain::RelinkerException("Overlapping ELF relocation targets", target); + ranges.emplace(target, target + 8); + if (type == 8) { + if (symbol != 0) + throw Domain::RelinkerException("RELATIVE relocation has a symbol index", target); + image.WritePointer(target, image.GetRelocatedAddress(addend)); + result.BaseRelocations.push_back(rva); + continue; + } + if (symbol == 0 || symbol >= dynamicSection.DynSymData.size() / 24) + throw Domain::RelinkerException("Invalid import symbol index", target); + const auto symbolOffset = static_cast(symbol) * 24; + if (Io::ReadU16(dynamicSection.DynSymData, symbolOffset + 6) != 0) + throw Domain::RelinkerException("Defined symbols require explicit relocation support", target); + const auto name = ReadString(dynamicSection.DynStrData, Io::ReadU32(dynamicSection.DynSymData, symbolOffset)); + if (name.empty()) + throw Domain::RelinkerException("Empty import symbol name", target); + if (type != 1 && addend != 0) + throw Domain::RelinkerException("GLOB_DAT/JUMP_SLOT relocation has a nonzero addend", target); + image.RequireWritable(target, 8); + image.WritePointer(target, 0); + result.Imports.push_back({name, rva, addend}); + } + }; + applyTable(dynamicSection.RelaData, false); + applyTable(dynamicSection.RelaPltData, true); + return result; +} + +std::vector WindowsRelocationBuilder::BuildBaseRelocations(const std::vector& targets) const { + std::map> pages; + for (const auto target : targets) + pages[target & ~0xfffu].push_back(static_cast(0xa000u | (target & 0xfffu))); + std::vector result; + for (const auto& [page, entries] : pages) { + Io::AppendU32(result, page); + Io::AppendU32(result, CheckedRva(8 + (entries.size() + entries.size() % 2) * 2)); + for (const auto entry : entries) + Io::AppendU16(result, entry); + if (entries.size() % 2 != 0) + Io::AppendU16(result, 0); + } + return result; +} + +} diff --git a/core/elfpatcher/src/windows/WindowsRelocationBuilder.hpp b/core/elfpatcher/src/windows/WindowsRelocationBuilder.hpp new file mode 100644 index 000000000..f85b565a1 --- /dev/null +++ b/core/elfpatcher/src/windows/WindowsRelocationBuilder.hpp @@ -0,0 +1,16 @@ +#ifndef ELFPATCHER_WINDOWS_RELOCATIONBUILDER_HPP +#define ELFPATCHER_WINDOWS_RELOCATIONBUILDER_HPP + +#include "WindowsLoadImage.hpp" + +namespace Elfpatcher::Windows { + +class WindowsRelocationBuilder { +public: + PeRelocations Apply(WindowsLoadImage& image, const Domain::SysVDynamicSection& dynamicSection) const; + std::vector BuildBaseRelocations(const std::vector& targets) const; +}; + +} + +#endif diff --git a/core/elfpatcher/src/windows/WindowsStubEmitter.cpp b/core/elfpatcher/src/windows/WindowsStubEmitter.cpp new file mode 100644 index 000000000..60998bb9e --- /dev/null +++ b/core/elfpatcher/src/windows/WindowsStubEmitter.cpp @@ -0,0 +1,51 @@ +#include "WindowsStubEmitter.hpp" +#include +#include +#include +#include + +namespace Elfpatcher::Windows { + +WindowsStubEmitter::WindowsStubEmitter(const std::uint32_t codeRva) : codeRva(codeRva) {} + +void WindowsStubEmitter::Emit(const std::initializer_list values) { + bytes.insert(bytes.end(), values.begin(), values.end()); +} + +void WindowsStubEmitter::U32(const std::uint32_t value) { + Io::AppendU32(bytes, value); +} + +void WindowsStubEmitter::U64(const std::uint64_t value) { + Io::AppendU64(bytes, value); +} + +void WindowsStubEmitter::Rip(const std::initializer_list opcode, const std::uint32_t targetRva) { + PatchBranch(Branch(opcode), targetRva); +} + +std::size_t WindowsStubEmitter::Branch(const std::initializer_list opcode) { + Emit(opcode); + const auto offset = bytes.size(); + U32(0); + return offset; +} + +void WindowsStubEmitter::PatchBranch(const std::size_t offset, const std::uint32_t targetRva) { + if (offset > bytes.size() || bytes.size() - offset < 4) + throw Domain::RelinkerException("Invalid bootstrap branch offset", offset); + const auto displacement = static_cast(targetRva) - (static_cast(codeRva) + static_cast(offset) + 4); + if (displacement < std::numeric_limits::min() || displacement > std::numeric_limits::max()) + throw Domain::RelinkerException("Bootstrap branch exceeds rel32 range", targetRva); + Io::WriteU32(bytes, offset, static_cast(displacement)); +} + +std::uint32_t WindowsStubEmitter::GetRva() const { + return CheckedRva(codeRva + bytes.size()); +} + +std::vector WindowsStubEmitter::TakeBytes() { + return std::move(bytes); +} + +} diff --git a/core/elfpatcher/src/windows/WindowsStubEmitter.hpp b/core/elfpatcher/src/windows/WindowsStubEmitter.hpp new file mode 100644 index 000000000..33c7c7bbe --- /dev/null +++ b/core/elfpatcher/src/windows/WindowsStubEmitter.hpp @@ -0,0 +1,28 @@ +#ifndef ELFPATCHER_WINDOWS_STUBEMITTER_HPP +#define ELFPATCHER_WINDOWS_STUBEMITTER_HPP + +#include "WindowsPeFormat.hpp" +#include + +namespace Elfpatcher::Windows { + +class WindowsStubEmitter { +public: + explicit WindowsStubEmitter(std::uint32_t codeRva); + void Emit(std::initializer_list bytes); + void U32(std::uint32_t value); + void U64(std::uint64_t value); + void Rip(std::initializer_list opcode, std::uint32_t targetRva); + std::size_t Branch(std::initializer_list opcode); + void PatchBranch(std::size_t offset, std::uint32_t targetRva); + std::uint32_t GetRva() const; + std::vector TakeBytes(); + +private: + std::uint32_t codeRva; + std::vector bytes; +}; + +} + +#endif diff --git a/core/main.cpp b/core/main.cpp index 424b6b2d2..9adbd8e65 100644 --- a/core/main.cpp +++ b/core/main.cpp @@ -96,7 +96,7 @@ int main(const int argc, char* argv[]) { std::make_shared(), std::make_shared(), Relinker::MakeUnusedNidFilter(), - !skipUnusedNidFilter + !skipUnusedNidFilter && !toWindows ); auto result = pipeline->Relink(sourceBytes); @@ -112,15 +112,7 @@ int main(const int argc, char* argv[]) { std::shared_ptr patcher; if (toWindows) { - patcher = std::make_shared( - std::make_shared(), - std::make_shared( - std::make_shared(), - byteWriter - ), - std::make_shared(byteWriter), - byteWriter - ); + patcher = std::make_shared(); } else { patcher = std::make_shared( std::make_shared(), diff --git a/core/relinker/src/pipeline/RelinkerPipeline.cpp b/core/relinker/src/pipeline/RelinkerPipeline.cpp index d8ee463c6..da00686a3 100644 --- a/core/relinker/src/pipeline/RelinkerPipeline.cpp +++ b/core/relinker/src/pipeline/RelinkerPipeline.cpp @@ -171,6 +171,12 @@ RelinkResult RelinkerPipeline::Relink(const std::vector& sourceElf const std::uint32_t symIdx = static_cast(rInfo >> 32); const std::uint32_t relType = static_cast(rInfo & 0xffffffff); + if (relType == 8) { + if (symIdx != 0) + throw RelinkerException("RELATIVE relocation has a nonzero symbol index", pos); + continue; + } + const FileByteOffset symOff = dynSymTabOffset + static_cast(symIdx) * symEntSize; if (symOff + 4 > raw.size()) throw RelinkerException("Symbol table entry out of bounds", symOff);