fix(relinker): update elf to windows pe relinking
This commit is contained in:
parent
4f892eeb15
commit
6af387172a
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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>(),
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue