fix(relinker): update elf to windows pe relinking

This commit is contained in:
boykopovar 2026-09-08 18:24:03 +03:00
parent 4f892eeb15
commit 6af387172a
19 changed files with 801 additions and 636 deletions

View File

@ -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

View File

@ -2,37 +2,12 @@
#define ELFPATCHER_WINDOWS_WINDOWSELFPATCHER_HPP
#include <elfpatcher/general/IElfPatcher.hpp>
#include <elfpatcher/general/IEntryStubBuilder.hpp>
#include <elfpatcher/general/IProgramHeaderLayoutBuilder.hpp>
#include <elfpatcher/general/ISectionHeaderTableBuilder.hpp>
#include <io/IByteWriter.hpp>
#include <memory>
#include <string>
namespace Elfpatcher::Windows {
class WindowsPePatcher : public IElfPatcher {
public:
WindowsPePatcher(
std::shared_ptr<IEntryStubBuilder> entryStubBuilder,
std::shared_ptr<IProgramHeaderLayoutBuilder> programHeaderLayoutBuilder,
std::shared_ptr<ISectionHeaderTableBuilder> sectionHeaderTableBuilder,
std::shared_ptr<Io::IByteWriter> byteWriter
);
std::vector<std::uint8_t> Patch(
const std::vector<std::uint8_t>& sourceElf,
const std::vector<Domain::ProgramHeader>& originalHeaders,
const Domain::SysVDynamicSection& dynamicSection,
std::uint64_t originalPltGotVaddr,
const std::string& runPath
) override;
private:
std::shared_ptr<IEntryStubBuilder> _entryStubBuilder;
std::shared_ptr<IProgramHeaderLayoutBuilder> _programHeaderLayoutBuilder;
std::shared_ptr<ISectionHeaderTableBuilder> _sectionHeaderTableBuilder;
std::shared_ptr<Io::IByteWriter> _byteWriter;
std::vector<std::uint8_t> Patch(const std::vector<std::uint8_t>& sourceElf, const std::vector<Domain::ProgramHeader>& originalHeaders, const Domain::SysVDynamicSection& dynamicSection, std::uint64_t originalPltGotVaddr, const std::string& runPath) override;
};
}

View File

@ -0,0 +1,190 @@
#include "WindowsEntryStubBuilder.hpp"
#include "WindowsStubEmitter.hpp"
#include <io/BufferUtils.hpp>
#include <algorithm>
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<std::string>& libraries, const std::vector<PeImport>& 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<std::uint32_t> libraryPaths;
std::vector<std::string> 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<std::uint32_t> symbolNames;
for (const auto& import : imports)
symbolNames.push_back(addString(import.Name));
const auto diagnosticsOffset = data.size();
std::vector<std::string> 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<std::uint32_t> 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;
}
}

View File

@ -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<std::string>& libraries, const std::vector<PeImport>& imports, const std::string& runPath) const;
};
}
#endif

View File

@ -0,0 +1,54 @@
#include "WindowsImportBuilder.hpp"
#include <io/BufferUtils.hpp>
#include <set>
namespace Elfpatcher::Windows {
WindowsImports WindowsImportBuilder::Build(const std::uint32_t sectionRva) const {
const std::vector<std::string> names = {"ExitProcess", "GetModuleFileNameA", "GetProcAddress", "GetStdHandle", "LoadLibraryExA", "RaiseException", "WriteFile"};
WindowsImports result{{".idata", sectionRva, SectionRead | SectionWrite | 0x40u, std::vector<std::uint8_t>(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<std::string> 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<std::string> result;
std::set<std::string> 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<std::size_t>(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;
}
}

View File

@ -0,0 +1,25 @@
#ifndef ELFPATCHER_WINDOWS_IMPORTBUILDER_HPP
#define ELFPATCHER_WINDOWS_IMPORTBUILDER_HPP
#include "WindowsPeFormat.hpp"
#include <domain/Types.hpp>
#include <map>
namespace Elfpatcher::Windows {
struct WindowsImports {
PeSection Section;
PeDirectory Directory;
PeDirectory AddressTable;
std::map<std::string, std::uint32_t> Functions;
};
class WindowsImportBuilder {
public:
WindowsImports Build(std::uint32_t sectionRva) const;
std::vector<std::string> ReadLibraries(const Domain::SysVDynamicSection& dynamicSection) const;
};
}
#endif

View File

@ -0,0 +1,107 @@
#include "WindowsLoadImage.hpp"
#include <io/BufferUtils.hpp>
#include <algorithm>
#include <limits>
namespace Elfpatcher::Windows {
WindowsLoadImage::WindowsLoadImage(const std::vector<std::uint8_t>& source, const std::vector<Domain::ProgramHeader>& 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<std::uint64_t>::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<std::uint64_t>(SectionAlignment - 1);
const auto end = segments.back().MappedAddress + segments.back().MemorySize;
data.resize(AlignRva(end - firstAddress));
CheckedRva(LoadRva + static_cast<std::uint64_t>(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<std::size_t>(segment.MappedAddress - firstAddress);
std::copy_n(source.begin() + static_cast<std::ptrdiff_t>(segment.Offset), static_cast<std::size_t>(segment.FileSize), data.begin() + static_cast<std::ptrdiff_t>(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<PeSection> WindowsLoadImage::BuildSections() const {
std::vector<PeSection> 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<std::ptrdiff_t>(first * SectionAlignment), data.begin() + static_cast<std::ptrdiff_t>(last * SectionAlignment)}});
first = last;
}
return sections;
}
}

View File

@ -0,0 +1,30 @@
#ifndef ELFPATCHER_WINDOWS_LOADIMAGE_HPP
#define ELFPATCHER_WINDOWS_LOADIMAGE_HPP
#include "WindowsPeFormat.hpp"
#include <domain/Types.hpp>
namespace Elfpatcher::Windows {
class WindowsLoadImage {
public:
WindowsLoadImage(const std::vector<std::uint8_t>& source, const std::vector<Domain::ProgramHeader>& 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<PeSection> BuildSections() const;
private:
std::vector<Domain::ProgramHeader> segments;
std::vector<std::uint8_t> data;
std::vector<std::uint32_t> pageFlags;
std::uint64_t firstAddress;
std::uint32_t entryRva;
};
}
#endif

View File

@ -0,0 +1,28 @@
#include "WindowsPeFormat.hpp"
#include <domain/Types.hpp>
#include <algorithm>
#include <limits>
namespace Elfpatcher::Windows {
std::uint32_t CheckedRva(const std::uint64_t value) {
if (value > std::numeric_limits<std::int32_t>::max())
throw Domain::RelinkerException("PE image exceeds the supported 2 GiB address range", value);
return static_cast<std::uint32_t>(value);
}
std::uint32_t AlignRva(const std::uint64_t value) {
return CheckedRva((static_cast<std::uint64_t>(CheckedRva(value)) + SectionAlignment - 1) & ~(static_cast<std::uint64_t>(SectionAlignment) - 1));
}
std::string ReadString(const std::vector<std::uint8_t>& 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<std::ptrdiff_t>(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};
}
}

View File

@ -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 <array>
#include <cstdint>
#include <string>
#include <vector>
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<std::uint8_t> data;
std::string Name;
std::uint32_t Rva;
std::uint32_t Characteristics;
std::vector<std::uint8_t> 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<std::uint32_t> BaseRelocations;
std::vector<PeImport> Imports;
};
struct ImportDllBlock {
std::string dllName;
std::vector<ImportThunkEntry> thunks;
};
std::uint32_t CheckedRva(std::uint64_t value);
std::uint32_t AlignRva(std::uint64_t value);
std::string ReadString(const std::vector<std::uint8_t>& bytes, std::size_t offset);
#endif
}
#endif

View File

@ -1,556 +1,40 @@
#include <elfpatcher/windows/WindowsElfPatcher.hpp>
#include <elfpatcher/general/ElfConstants.hpp>
#include <domain/Types.hpp>
#include <io/BufferUtils.hpp>
#include <cstring>
#include <cstdint>
#include <vector>
#include <string>
#include <map>
#include <algorithm>
#include "WindowsPeFormat.hpp"
#include "WindowsEntryStubBuilder.hpp"
#include "WindowsLoadImage.hpp"
#include "WindowsPeWriter.hpp"
#include "WindowsRelocationBuilder.hpp"
#include <utility>
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<std::uint8_t>& 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<std::uint8_t> buildRelocSection(
const std::vector<std::uint8_t>& 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<std::uint32_t, std::vector<std::uint32_t>> 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<std::uint32_t>(rInfo & 0xffffffff);
const auto symIdx = static_cast<std::uint32_t>(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<std::uint32_t>(rva);
const std::uint32_t page = rva32 & ~0xFFFu;
const std::uint32_t pageOff = rva32 & 0xFFFu;
pageToOffsets[page].push_back(pageOff);
std::vector<std::uint8_t> WindowsPePatcher::Patch(const std::vector<std::uint8_t>& sourceElf, const std::vector<Domain::ProgramHeader>& 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<PeDirectory, 16> 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<std::uint8_t> buf;
for (auto& [page, offsets] : pageToOffsets) {
std::sort(offsets.begin(), offsets.end());
auto entryCount = static_cast<std::uint32_t>(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<std::uint16_t>((kRelTypeDir64 << 12) | (off & 0xFFF));
Io::AppendU16(buf, entry);
}
}
return buf;
}
std::vector<std::uint8_t> buildImportSection(
const std::vector<ImportDllBlock>& dlls,
const std::uint32_t sectionRva,
std::vector<std::pair<std::uint32_t, std::uint32_t>>& outGotPatches)
{
if (dlls.empty())
return {};
const auto descriptorTableSize = static_cast<std::uint32_t>((dlls.size() + 1) * 20);
std::vector<std::uint8_t> descriptorBuf;
std::vector<std::uint8_t> dataBuf;
const std::uint32_t dataBase = sectionRva + descriptorTableSize;
for (const auto& dll : dlls) {
const std::uint32_t dllNameRva = dataBase + static_cast<std::uint32_t>(dataBuf.size());
Io::AppendString(dataBuf, dll.dllName);
Io::AlignBuffer(dataBuf, 2);
const std::uint32_t iltRva = dataBase + static_cast<std::uint32_t>(dataBuf.size());
for (const auto& thunk : dll.thunks) {
const std::uint32_t hintNameRva = dataBase + static_cast<std::uint32_t>(dataBuf.size())
+ static_cast<std::uint32_t>((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<std::uint32_t>(dataBuf.size());
for (std::size_t i = 0; i < dll.thunks.size(); ++i) {
const std::uint32_t iatEntryRva = iatRva + static_cast<std::uint32_t>(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<std::uint8_t> result;
result.insert(result.end(), descriptorBuf.begin(), descriptorBuf.end());
result.insert(result.end(), dataBuf.begin(), dataBuf.end());
return result;
}
std::vector<std::uint8_t> buildEntryStub(std::uint64_t realEntryVaddr, std::uint64_t stubVaddr) {
std::vector<std::uint8_t> s;
const std::uint64_t callInsnVaddr = stubVaddr + s.size() + 5;
const std::int64_t rel = static_cast<std::int64_t>(realEntryVaddr) - static_cast<std::int64_t>(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<std::int32_t>(rel);
s.push_back(0xE8);
s.push_back(static_cast<std::uint8_t>(rel32 & 0xFF));
s.push_back(static_cast<std::uint8_t>((rel32 >> 8) & 0xFF));
s.push_back(static_cast<std::uint8_t>((rel32 >> 16) & 0xFF));
s.push_back(static_cast<std::uint8_t>((rel32 >> 24) & 0xFF));
s.push_back(0xC3);
return s;
}
std::uint64_t readElfU64(const std::vector<std::uint8_t>& 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<std::uint8_t>& 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<std::uint8_t>& buf, std::size_t off) {
std::string s;
while (off < buf.size() && buf[off] != 0)
s.push_back(static_cast<char>(buf[off++]));
return s;
}
}
WindowsPePatcher::WindowsPePatcher(
std::shared_ptr<IEntryStubBuilder> entryStubBuilder,
std::shared_ptr<IProgramHeaderLayoutBuilder> programHeaderLayoutBuilder,
std::shared_ptr<ISectionHeaderTableBuilder> sectionHeaderTableBuilder,
std::shared_ptr<Io::IByteWriter> byteWriter)
: _entryStubBuilder(std::move(entryStubBuilder))
, _programHeaderLayoutBuilder(std::move(programHeaderLayoutBuilder))
, _sectionHeaderTableBuilder(std::move(sectionHeaderTableBuilder))
, _byteWriter(std::move(byteWriter))
{
}
std::vector<std::uint8_t> WindowsPePatcher::Patch(
const std::vector<std::uint8_t>& sourceElf,
const std::vector<Domain::ProgramHeader>& 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<LoadSegment> 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<std::uint32_t>(gotRva64);
gotFound = true;
}
std::vector<std::uint8_t> imageBuf(static_cast<std::size_t>(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<std::size_t>(seg.fileSize));
}
const auto loadImageRva = static_cast<std::uint32_t>(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<ImportDllBlock> importDlls;
std::vector<std::pair<std::uint32_t, std::uint32_t>> gotPatches;
{
const std::vector<std::uint8_t>& relaPlt = dynamicSection.RelaPltData;
const std::vector<std::uint8_t>& dynStr = dynamicSection.DynStrData;
const std::vector<std::uint8_t>& 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<std::string, ImportDllBlock*> 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<std::uint32_t>(rInfo >> 32);
const auto relType = static_cast<std::uint32_t>(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<std::size_t>(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<std::uint32_t>(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<std::uint8_t>& 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<std::uint32_t>(rInfo >> 32);
const auto relType = static_cast<std::uint32_t>(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<std::size_t>(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<std::uint32_t>(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<std::uint32_t>(imageBuf.size());
const std::uint32_t importSectionRva = Io::AlignUp(loadImageRva + loadImageSize, kSectionAlignment);
std::vector<std::uint8_t> importSectionData = buildImportSection(importDlls, importSectionRva, gotPatches);
Io::AlignBuffer(importSectionData, kFileAlignment);
for (const auto& [gotRva_, iatRva] : gotPatches) {
const auto destInImage = static_cast<std::uint64_t>(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<std::uint32_t>(importSectionData.size()), kSectionAlignment);
std::vector<std::uint8_t> relocSectionData = buildRelocSection(dynamicSection.RelaData, imageBase);
Io::AlignBuffer(relocSectionData, kFileAlignment);
const std::uint32_t stubSectionRva = Io::AlignUp(relocSectionRva + static_cast<std::uint32_t>(relocSectionData.size()), kSectionAlignment);
const std::uint64_t stubVaddr = adjustedBase + stubSectionRva;
std::vector<std::uint8_t> 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<std::uint32_t>(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<std::uint32_t>(stubData.size()), kSectionAlignment);
const std::uint32_t mzPeOffset = 0x40;
std::vector<std::uint8_t> 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<std::uint16_t>(kOsVersion)); off += 2;
Io::WriteU16(result, off, static_cast<std::uint16_t>(kOsVersionMinor)); off += 2;
Io::WriteU16(result, off, 0); off += 2;
Io::WriteU16(result, off, 0); off += 2;
Io::WriteU16(result, off, static_cast<std::uint16_t>(kOsVersion)); off += 2;
Io::WriteU16(result, off, static_cast<std::uint16_t>(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<std::uint16_t>(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<std::uint32_t>(importDlls.size() * 20));
}
if (!relocSectionData.empty()) {
Io::WriteU32(result, mzPeOffset + 4 + 20 + 168, relocSectionRva);
Io::WriteU32(result, mzPeOffset + 4 + 20 + 172, static_cast<std::uint32_t>(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<std::uint32_t>(importSectionData.size()), importSectionRva,
static_cast<std::uint32_t>(importSectionData.size()), curRawOff, kSecRW);
curRawOff += static_cast<std::uint32_t>(importSectionData.size());
}
std::uint32_t relocRawOff = 0;
if (!relocSectionData.empty()) {
relocRawOff = curRawOff;
writeSec(".reloc", static_cast<std::uint32_t>(relocSectionData.size()), relocSectionRva,
static_cast<std::uint32_t>(relocSectionData.size()), curRawOff, kSecDiscard | kSecRO);
curRawOff += static_cast<std::uint32_t>(relocSectionData.size());
}
const std::uint32_t stubRawOff = curRawOff;
writeSec(".entry", static_cast<std::uint32_t>(stubData.size()), stubSectionRva,
static_cast<std::uint32_t>(stubData.size()), stubRawOff, kSecExec);
Io::AlignBuffer(result, kFileAlignment);
{
std::vector<std::uint8_t> 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);
}
}

View File

@ -0,0 +1,83 @@
#include "WindowsPeWriter.hpp"
#include <domain/Types.hpp>
#include <io/BufferUtils.hpp>
#include <algorithm>
namespace Elfpatcher::Windows {
std::vector<std::uint8_t> WindowsPeWriter::Write(const std::vector<PeSection>& sections, const std::uint32_t entryRva, const std::array<PeDirectory, 16>& 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<std::uint8_t> 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<std::uint16_t>(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<std::uint32_t>(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<std::ptrdiff_t>(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<std::uint64_t>(codeSize) + rawSize);
foundEntry |= entryRva >= section.Rva && entryRva - section.Rva < section.Data.size();
} else {
dataSize = CheckedRva(static_cast<std::uint64_t>(dataSize) + rawSize);
}
result.insert(result.end(), section.Data.begin(), section.Data.end());
result.resize(static_cast<std::size_t>(rawOffset) + rawSize);
endRva = AlignRva(section.Rva + static_cast<std::uint64_t>(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;
}
}

View File

@ -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<std::uint8_t> Write(const std::vector<PeSection>& sections, std::uint32_t entryRva, const std::array<PeDirectory, 16>& directories) const;
};
}
#endif

View File

@ -0,0 +1,71 @@
#include "WindowsRelocationBuilder.hpp"
#include <io/BufferUtils.hpp>
#include <map>
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<std::uint64_t, std::uint64_t> ranges;
const auto applyTable = [&](const std::vector<std::uint8_t>& 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<std::uint32_t>(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<std::size_t>(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<std::uint8_t> WindowsRelocationBuilder::BuildBaseRelocations(const std::vector<std::uint32_t>& targets) const {
std::map<std::uint32_t, std::vector<std::uint16_t>> pages;
for (const auto target : targets)
pages[target & ~0xfffu].push_back(static_cast<std::uint16_t>(0xa000u | (target & 0xfffu)));
std::vector<std::uint8_t> 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;
}
}

View File

@ -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<std::uint8_t> BuildBaseRelocations(const std::vector<std::uint32_t>& targets) const;
};
}
#endif

View File

@ -0,0 +1,51 @@
#include "WindowsStubEmitter.hpp"
#include <domain/Types.hpp>
#include <io/BufferUtils.hpp>
#include <limits>
#include <utility>
namespace Elfpatcher::Windows {
WindowsStubEmitter::WindowsStubEmitter(const std::uint32_t codeRva) : codeRva(codeRva) {}
void WindowsStubEmitter::Emit(const std::initializer_list<std::uint8_t> 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<std::uint8_t> opcode, const std::uint32_t targetRva) {
PatchBranch(Branch(opcode), targetRva);
}
std::size_t WindowsStubEmitter::Branch(const std::initializer_list<std::uint8_t> 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<std::int64_t>(targetRva) - (static_cast<std::int64_t>(codeRva) + static_cast<std::int64_t>(offset) + 4);
if (displacement < std::numeric_limits<std::int32_t>::min() || displacement > std::numeric_limits<std::int32_t>::max())
throw Domain::RelinkerException("Bootstrap branch exceeds rel32 range", targetRva);
Io::WriteU32(bytes, offset, static_cast<std::uint32_t>(displacement));
}
std::uint32_t WindowsStubEmitter::GetRva() const {
return CheckedRva(codeRva + bytes.size());
}
std::vector<std::uint8_t> WindowsStubEmitter::TakeBytes() {
return std::move(bytes);
}
}

View File

@ -0,0 +1,28 @@
#ifndef ELFPATCHER_WINDOWS_STUBEMITTER_HPP
#define ELFPATCHER_WINDOWS_STUBEMITTER_HPP
#include "WindowsPeFormat.hpp"
#include <initializer_list>
namespace Elfpatcher::Windows {
class WindowsStubEmitter {
public:
explicit WindowsStubEmitter(std::uint32_t codeRva);
void Emit(std::initializer_list<std::uint8_t> bytes);
void U32(std::uint32_t value);
void U64(std::uint64_t value);
void Rip(std::initializer_list<std::uint8_t> opcode, std::uint32_t targetRva);
std::size_t Branch(std::initializer_list<std::uint8_t> opcode);
void PatchBranch(std::size_t offset, std::uint32_t targetRva);
std::uint32_t GetRva() const;
std::vector<std::uint8_t> TakeBytes();
private:
std::uint32_t codeRva;
std::vector<std::uint8_t> bytes;
};
}
#endif

View File

@ -96,7 +96,7 @@ int main(const int argc, char* argv[]) {
std::make_shared<Relinker::ValidationPolicy>(),
std::make_shared<Relinker::SysVDynamicSectionBuilder>(),
Relinker::MakeUnusedNidFilter(),
!skipUnusedNidFilter
!skipUnusedNidFilter && !toWindows
);
auto result = pipeline->Relink(sourceBytes);
@ -112,15 +112,7 @@ int main(const int argc, char* argv[]) {
std::shared_ptr<Elfpatcher::IElfPatcher> patcher;
if (toWindows) {
patcher = std::make_shared<Elfpatcher::Windows::WindowsPePatcher>(
std::make_shared<Elfpatcher::EntryStubBuilder>(),
std::make_shared<Elfpatcher::ProgramHeaderLayoutBuilder>(
std::make_shared<Elfpatcher::SegmentFilter>(),
byteWriter
),
std::make_shared<Elfpatcher::SectionHeaderTableBuilder>(byteWriter),
byteWriter
);
patcher = std::make_shared<Elfpatcher::Windows::WindowsPePatcher>();
} else {
patcher = std::make_shared<Elfpatcher::Linux::LinuxElfPatcher>(
std::make_shared<Elfpatcher::EntryStubBuilder>(),

View File

@ -171,6 +171,12 @@ RelinkResult RelinkerPipeline::Relink(const std::vector<std::uint8_t>& sourceElf
const std::uint32_t symIdx = static_cast<std::uint32_t>(rInfo >> 32);
const std::uint32_t relType = static_cast<std::uint32_t>(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<FileByteOffset>(symIdx) * symEntSize;
if (symOff + 4 > raw.size())
throw RelinkerException("Symbol table entry out of bounds", symOff);