#include #include #include #include #include #include #include #include namespace Elfpatcher { std::vector GuestModuleWriter::WriteLinux(const Relinker::GuestImage& image, const std::vector& dependencies, const std::string& runPath) const { auto bytes = image.Bytes; std::vector headers; std::uint64_t end = 0; for (auto header : image.Headers) { if (header.Type != 1 && header.Type != 7 && header.Type != 0x6474e550 && header.Type != PT_GNU_STACK) continue; if (header.Type == 1) { end = std::max(end, header.MappedAddress + header.MemorySize); if (header.Flags == 0) continue; header.Flags |= 4; } headers.push_back(header); } if (std::none_of(headers.begin(), headers.end(), [&](const auto& header) { return header.Type == PT_GNU_STACK; })) headers.push_back({PT_GNU_STACK, PF_R | PF_W, 0, 0, 0, 0, 0, 16}); if (end > std::numeric_limits::max() - 0x4000) throw Domain::RelinkerException("Guest virtual address overflow"); Io::AlignBuffer(bytes, 0x4000); const auto extraOffset = bytes.size(); const auto extraAddress = Io::AlignUp64(end, 0x4000); const auto address = [&] { return extraAddress + bytes.size() - extraOffset; }; const bool passesModuleArgs = image.Init != 0; std::uint64_t argsSlot = 0; std::uint64_t resultSlot = 0; if (passesModuleArgs) { Io::AlignBuffer(bytes, 8); argsSlot = address(); bytes.insert(bytes.end(), 8, 0); resultSlot = address(); bytes.insert(bytes.end(), 8, 0); } auto strings = image.Dynamic.DynStrData; auto symbols = image.Dynamic.DynSymData; constexpr std::uint64_t kGlobDat = 6; const auto addString = [&](const std::string& value) { if (strings.size() > std::numeric_limits::max()) throw Domain::RelinkerException("Guest string table too large"); const auto offset = static_cast(strings.size()); Io::AppendString(strings, value); return offset; }; bool needsTlsResolver = false; for (std::size_t index = 1; index < image.Symbols.size(); ++index) { const auto& symbol = image.Symbols[index]; if (image.UsePlatformTlsResolver && symbol.Section == 0 && symbol.Name == "vNe1w4diLCs") { Io::WriteU32(symbols, index * 24, addString("__tls_get_addr")); needsTlsResolver = true; } if (symbol.Section == 0 && (symbol.Info >> 4) == 2) symbols[index * 24 + 4] = static_cast(0x10 | (symbol.Info & 15)); } std::vector needed; for (const auto& dependency : dependencies) needed.push_back(addString(dependency)); if (needsTlsResolver) needed.push_back(addString("ld-linux-x86-64.so.2")); const auto addUndefinedImport = [&](const std::string& name) { const auto nameOffset = addString(name); const auto entry = symbols.size(); symbols.resize(entry + 24, 0); Io::WriteU32(symbols, entry, nameOffset); symbols[entry + 4] = 0x12; Io::WriteU16(symbols, entry + 6, 0); return entry / 24; }; auto relaData = image.Dynamic.RelaData; std::uint64_t argsImport = 0; std::uint64_t resultImport = 0; const auto addGlobDat = [&](std::uint64_t slot, std::uint64_t symbol) { const auto entry = relaData.size(); relaData.resize(entry + 24, 0); Io::WriteU64(relaData, entry, slot); Io::WriteU64(relaData, entry + 8, (symbol << 32) | kGlobDat); }; if (passesModuleArgs) { argsImport = addUndefinedImport("__aps5_get_pending_module_args_nid_no_patch"); resultImport = addUndefinedImport("__aps5_set_module_init_result_nid_no_patch"); addGlobDat(argsSlot, argsImport); addGlobDat(resultSlot, resultImport); } const auto soname = addString(image.OutputName); const auto search = addString(runPath); const auto strAddress = address(); bytes.insert(bytes.end(), strings.begin(), strings.end()); Io::AlignBuffer(bytes, 8); const auto symAddress = address(); bytes.insert(bytes.end(), symbols.begin(), symbols.end()); const auto hashAddress = address(); const auto count = static_cast(symbols.size() / 24); const auto bucketCount = std::max(count, 1); std::vector buckets(bucketCount), chains(count); for (std::uint32_t index = count; index-- > 1;) { std::uint32_t hash = 0; for (auto offset = Io::ReadU32(symbols, index * 24); offset < strings.size() && strings[offset] != 0; ++offset) { hash = (hash << 4u) + strings[offset]; hash = (hash ^ ((hash & 0xf0000000u) >> 24u)) & 0x0fffffffu; } chains[index] = buckets[hash % bucketCount]; buckets[hash % bucketCount] = index; } Io::AppendU32(bytes, bucketCount); Io::AppendU32(bytes, count); for (const auto bucket : buckets) Io::AppendU32(bytes, bucket); for (const auto chain : chains) Io::AppendU32(bytes, chain); Io::AlignBuffer(bytes, 8); const auto relaAddress = address(); bytes.insert(bytes.end(), relaData.begin(), relaData.end()); const auto pltAddress = address(); bytes.insert(bytes.end(), image.Dynamic.RelaPltData.begin(), image.Dynamic.RelaPltData.end()); constexpr std::size_t kArgsDisplacement = 3, kTargetDisplacement = 0x11, kResultDisplacement = 0x19; constexpr std::size_t kStubSize = 0x1f; const auto stubDisplacement = [](std::uint64_t target, std::uint64_t next) { const auto distance = static_cast(target) - static_cast(next); if (distance < std::numeric_limits::min() || distance > std::numeric_limits::max()) throw Domain::RelinkerException("Guest initializer exceeds relative branch range"); return static_cast(static_cast(distance)); }; const auto lifecycle = [&](std::uint64_t target) { if (target == 0) return std::uint64_t{}; const auto start = address(); std::vector stub(kStubSize, 0xc3); stub[0x00] = 0x50; stub[0x01] = 0xff; stub[0x02] = 0x15; Io::WriteU32(stub, kArgsDisplacement, stubDisplacement(argsSlot, start + 7)); stub[0x07] = 0x48; stub[0x08] = 0x8b; stub[0x09] = 0x38; stub[0x0a] = 0x48; stub[0x0b] = 0x8b; stub[0x0c] = 0x70; stub[0x0d] = 0x08; stub[0x0e] = 0x31; stub[0x0f] = 0xd2; stub[0x10] = 0xe8; Io::WriteU32(stub, kTargetDisplacement, stubDisplacement(target, start + 0x15)); stub[0x15] = 0x89; stub[0x16] = 0xc7; stub[0x17] = 0xff; stub[0x18] = 0x15; Io::WriteU32(stub, kResultDisplacement, stubDisplacement(resultSlot, start + 0x1d)); stub[0x1d] = 0x58; stub[0x1e] = 0xc3; bytes.insert(bytes.end(), stub.begin(), stub.end()); return start; }; const auto callThrough = [&](std::uint64_t target) { if (target == 0) return std::uint64_t{}; const auto start = address(); const auto distance = static_cast(target) - static_cast(start + 5); if (distance < std::numeric_limits::min() || distance > std::numeric_limits::max()) throw Domain::RelinkerException("Guest finaliser exceeds relative branch range"); bytes.push_back(0xe9); Io::AppendU32(bytes, static_cast(distance)); return start; }; const auto init = lifecycle(image.Init); const auto fini = callThrough(image.Fini); Io::AlignBuffer(bytes, 8); const auto dynamicOffset = bytes.size(); const auto dynamicAddress = address(); const auto tag = [&](std::uint64_t key, std::uint64_t value) { Io::AppendU64(bytes, key); Io::AppendU64(bytes, value); }; for (const auto offset : needed) tag(1, offset); tag(14, soname); tag(29, search); tag(4, hashAddress); tag(5, strAddress); tag(10, strings.size()); tag(6, symAddress); tag(11, 24); if (!relaData.empty()) { tag(7, relaAddress); tag(8, relaData.size()); tag(9, 24); } if (!image.Dynamic.RelaPltData.empty()) { tag(23, pltAddress); tag(2, image.Dynamic.RelaPltData.size()); tag(20, 7); tag(3, image.Got); } if (!image.InitArray.empty()) { tag(25, image.InitArray.front()); tag(27, image.InitArray.size() * 8); } if (!image.FiniArray.empty()) { tag(26, image.FiniArray.front()); tag(28, image.FiniArray.size() * 8); } if (init != 0) tag(12, init); if (fini != 0) tag(13, fini); tag(30, 8); tag(0, 0); const auto dynamicSize = bytes.size() - dynamicOffset; using namespace Codegen::Amd64OnlySubstitutionTable; const auto checkedAdd = [](const std::uint64_t left, const std::uint64_t right, const std::uint64_t offset) { if (right > std::numeric_limits::max() - left) throw Domain::RelinkerException("AMD-only guest stub address overflow", offset); return left + right; }; const auto displacement = [](const std::uint64_t target, const std::uint64_t next, const std::uint64_t offset) { if (target >= next) { const auto distance = target - next; if (distance > static_cast(std::numeric_limits::max())) throw Domain::RelinkerException("AMD-only guest stub exceeds rel32 range", offset); return static_cast(distance); } const auto distance = next - target; if (distance > static_cast(std::numeric_limits::max()) + 1) throw Domain::RelinkerException("AMD-only guest stub exceeds rel32 range", offset); return static_cast(-static_cast(distance)); }; for (const auto& site : image.Trampolines) { if (site.Length < kJmpRel32.Size || site.OriginalBytes.size() != site.Length || site.Body.size() < kJmpRel32.Size || site.ReturnBranchOffset > site.Body.size() - kJmpRel32.Size || site.Body[site.ReturnBranchOffset] != kJmpRel32.Bytes[0]) throw Domain::RelinkerException("Invalid AMD-only guest trampoline site", site.Offset); if (site.Offset > image.Bytes.size() || site.Length > image.Bytes.size() - site.Offset) throw Domain::RelinkerException("AMD-only guest instruction is outside the original image", site.Offset); const auto mapped = std::any_of(image.Headers.begin(), image.Headers.end(), [&](const auto& header) { if (header.Type != 1 || (header.Flags & 1) == 0 || site.Address < header.MappedAddress || site.Offset < header.Offset) return false; const auto addressDelta = site.Address - header.MappedAddress; const auto offsetDelta = site.Offset - header.Offset; return addressDelta == offsetDelta && addressDelta <= header.FileSize && site.Length <= header.FileSize - addressDelta; }); if (!mapped) throw Domain::RelinkerException("AMD-only guest instruction is outside an executable segment", site.Offset); if (!std::equal(site.OriginalBytes.begin(), site.OriginalBytes.end(), bytes.begin() + static_cast(site.Offset))) throw Domain::RelinkerException("AMD-only guest site bytes changed before patching", site.Offset); bytes.resize(Io::AlignUp(bytes.size(), kStubAlignment), kTrapFill); const auto stubOffset = bytes.size(); const auto stubAddress = checkedAdd(extraAddress, stubOffset - extraOffset, site.Offset); checkedAdd(stubAddress, site.Body.size(), site.Offset); const auto returnAddress = checkedAdd(site.Address, site.Length, site.Offset); const auto stubReturn = checkedAdd(stubAddress, site.ReturnBranchOffset + kJmpRel32.Size, site.Offset); const auto siteNext = checkedAdd(site.Address, kJmpRel32.Size, site.Offset); const auto returnDisplacement = displacement(returnAddress, stubReturn, site.Offset); const auto siteDisplacement = displacement(stubAddress, siteNext, site.Offset); bytes.insert(bytes.end(), site.Body.begin(), site.Body.end()); Codegen::ApplyStubRelocations(std::span(bytes.data() + stubOffset, site.ReturnBranchOffset), site.Relocations, site.Address, stubAddress, site.Offset); Io::WriteU32(bytes, stubOffset + site.ReturnBranchOffset + 1, static_cast(returnDisplacement)); std::fill_n(bytes.begin() + static_cast(site.Offset), site.Length, kNop1.Bytes[0]); bytes[static_cast(site.Offset)] = kJmpRel32.Bytes[0]; Io::WriteU32(bytes, static_cast(site.Offset + 1), static_cast(siteDisplacement)); } const auto phOffset = bytes.size(); const auto phCount = headers.size() + 2; if (phCount > std::numeric_limits::max()) throw Domain::RelinkerException("Too many guest program headers"); const auto extraSize = bytes.size() + phCount * 56 - extraOffset; headers.push_back({1, 7, extraOffset, extraAddress, extraAddress, extraSize, extraSize, 0x4000}); headers.push_back({2, 6, dynamicOffset, dynamicAddress, dynamicAddress, dynamicSize, dynamicSize, 8}); for (const auto& header : headers) { Io::AppendU32(bytes, header.Type); Io::AppendU32(bytes, header.Flags); Io::AppendU64(bytes, header.Offset); Io::AppendU64(bytes, header.MappedAddress); Io::AppendU64(bytes, header.PhysicalAddress); Io::AppendU64(bytes, header.FileSize); Io::AppendU64(bytes, header.MemorySize); Io::AppendU64(bytes, header.Alignment); } bytes[7] = 0; bytes[8] = 0; Io::WriteU16(bytes, 16, 3); Io::WriteU64(bytes, 24, 0); Io::WriteU64(bytes, 32, phOffset); Io::WriteU64(bytes, 40, 0); Io::WriteU16(bytes, 56, static_cast(phCount)); Io::WriteU16(bytes, 58, 0); Io::WriteU16(bytes, 60, 0); Io::WriteU16(bytes, 62, 0); return bytes; } }