mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-11-08 13:50:20 +00:00
ParseTrieNode recursed once per trie level with no limit. A chain of single-child nodes about 200k levels deep (roughly 1 MB of trie) overflowed the stack, so nid_patcher crashed instead of reporting a malformed image. Throw a runtime_error once the depth passes 1024 levels. Every level adds at least one byte to the exported name, so the bound only rejects names longer than 1024 bytes, which no real export has.
542 lines
24 KiB
C++
542 lines
24 KiB
C++
#include <nid/MachONidPatcher.hpp>
|
|
#include <nid/NidResolver.hpp>
|
|
#include <nid/NidPatcherUtils.hpp>
|
|
#include <algorithm>
|
|
#include <cctype>
|
|
#include <cstddef>
|
|
#include <cstring>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <set>
|
|
#include <stdexcept>
|
|
#include <string_view>
|
|
#include <unordered_map>
|
|
|
|
namespace Nid {
|
|
|
|
namespace {
|
|
|
|
using Internal::Read;
|
|
using Internal::Write;
|
|
|
|
constexpr std::uint32_t kLcSegment64 = 0x19;
|
|
constexpr std::uint32_t kLcCodeSignature = 0x1d;
|
|
constexpr std::uint32_t kLcDyldInfo = 0x22;
|
|
constexpr std::uint32_t kLcDyldInfoOnly = 0x80000022u;
|
|
constexpr std::uint32_t kLcDyldExportsTrie = 0x80000033u;
|
|
constexpr std::uint32_t kLcDyldChainedFixups = 0x80000034u;
|
|
constexpr std::uint32_t kLcLoadDylib = 0x0c;
|
|
constexpr std::uint32_t kLcLoadWeakDylib = 0x80000018u;
|
|
constexpr std::uint32_t kLcReexportDylib = 0x8000001fu;
|
|
constexpr std::uint32_t kLcLazyLoadDylib = 0x20;
|
|
constexpr std::uint32_t kLcLoadUpwardDylib = 0x80000023u;
|
|
|
|
constexpr std::uint64_t kExportWeakDefinition = 0x04;
|
|
constexpr std::uint64_t kExportReexport = 0x08;
|
|
constexpr std::uint64_t kExportStubAndResolver = 0x10;
|
|
|
|
constexpr std::uint32_t kImportFormatPlain = 1;
|
|
constexpr std::uint32_t kImportFormatAddend = 2;
|
|
constexpr std::uint32_t kImportFormatAddend64 = 3;
|
|
|
|
constexpr std::uint64_t kPageSize = 0x1000;
|
|
|
|
struct MachHeader64 {
|
|
std::uint32_t Magic;
|
|
std::uint32_t CpuType;
|
|
std::uint32_t CpuSubtype;
|
|
std::uint32_t FileType;
|
|
std::uint32_t CommandCount;
|
|
std::uint32_t CommandBytes;
|
|
std::uint32_t Flags;
|
|
std::uint32_t Reserved;
|
|
};
|
|
|
|
struct LoadCommand {
|
|
std::uint32_t Cmd;
|
|
std::uint32_t Size;
|
|
};
|
|
|
|
struct SegmentCommand64 {
|
|
std::uint32_t Cmd;
|
|
std::uint32_t Size;
|
|
char Name[16];
|
|
std::uint64_t VmAddress;
|
|
std::uint64_t VmSize;
|
|
std::uint64_t FileOffset;
|
|
std::uint64_t FileSize;
|
|
std::uint32_t MaxProtection;
|
|
std::uint32_t InitProtection;
|
|
std::uint32_t SectionCount;
|
|
std::uint32_t Flags;
|
|
};
|
|
|
|
struct LinkeditDataCommand {
|
|
std::uint32_t Cmd;
|
|
std::uint32_t Size;
|
|
std::uint32_t DataOffset;
|
|
std::uint32_t DataSize;
|
|
};
|
|
|
|
struct DyldInfoCommand {
|
|
std::uint32_t Cmd;
|
|
std::uint32_t Size;
|
|
std::uint32_t RebaseOffset;
|
|
std::uint32_t RebaseSize;
|
|
std::uint32_t BindOffset;
|
|
std::uint32_t BindSize;
|
|
std::uint32_t WeakBindOffset;
|
|
std::uint32_t WeakBindSize;
|
|
std::uint32_t LazyBindOffset;
|
|
std::uint32_t LazyBindSize;
|
|
std::uint32_t ExportOffset;
|
|
std::uint32_t ExportSize;
|
|
};
|
|
|
|
struct ChainedFixupsHeader {
|
|
std::uint32_t Version;
|
|
std::uint32_t StartsOffset;
|
|
std::uint32_t ImportsOffset;
|
|
std::uint32_t SymbolsOffset;
|
|
std::uint32_t ImportsCount;
|
|
std::uint32_t ImportsFormat;
|
|
std::uint32_t SymbolsFormat;
|
|
};
|
|
|
|
void RequireRange(const std::vector<std::uint8_t>& binary, std::uint64_t offset, std::uint64_t size) {
|
|
if (offset > binary.size() || size > binary.size() - offset) throw std::runtime_error("Mach-O range out of bounds");
|
|
}
|
|
|
|
std::uint64_t ReadUleb(const std::vector<std::uint8_t>& data, std::size_t& cursor, std::size_t end) {
|
|
std::uint64_t value = 0;
|
|
unsigned shift = 0;
|
|
for (;;) {
|
|
if (cursor >= end) throw std::runtime_error("truncated uleb128 in export trie");
|
|
const auto byte = data[cursor++];
|
|
if (shift >= 64 || (shift == 63 && (byte & 0x7e) != 0)) throw std::runtime_error("uleb128 overflow in export trie");
|
|
value |= std::uint64_t{byte & 0x7fu} << shift;
|
|
if ((byte & 0x80) == 0) return value;
|
|
shift += 7;
|
|
}
|
|
}
|
|
|
|
void WriteUleb(std::vector<std::uint8_t>& out, std::uint64_t value) {
|
|
do {
|
|
std::uint8_t byte = value & 0x7f;
|
|
value >>= 7;
|
|
if (value != 0) byte |= 0x80;
|
|
out.push_back(byte);
|
|
} while (value != 0);
|
|
}
|
|
|
|
std::size_t UlebSize(std::uint64_t value) {
|
|
std::size_t size = 1;
|
|
while (value >>= 7) ++size;
|
|
return size;
|
|
}
|
|
|
|
std::string ReadTrieString(const std::vector<std::uint8_t>& data, std::size_t& cursor, std::size_t end) {
|
|
const auto start = cursor;
|
|
while (cursor < end && data[cursor] != 0) ++cursor;
|
|
if (cursor >= end) throw std::runtime_error("unterminated string in export trie");
|
|
std::string text(reinterpret_cast<const char*>(data.data() + start), cursor - start);
|
|
++cursor;
|
|
return text;
|
|
}
|
|
|
|
struct Image {
|
|
MachHeader64 Header {};
|
|
std::vector<std::size_t> Commands;
|
|
};
|
|
|
|
Image ParseImage(const std::vector<std::uint8_t>& binary) {
|
|
Image image;
|
|
image.Header = Read<MachHeader64>(binary, 0);
|
|
if (image.Header.Magic != kMachOMagic64) throw std::runtime_error("not a 64-bit little-endian Mach-O image");
|
|
RequireRange(binary, sizeof(MachHeader64), image.Header.CommandBytes);
|
|
std::size_t cursor = sizeof(MachHeader64);
|
|
const auto end = cursor + image.Header.CommandBytes;
|
|
for (std::uint32_t index = 0; index < image.Header.CommandCount; ++index) {
|
|
if (cursor + sizeof(LoadCommand) > end) throw std::runtime_error("load command out of bounds");
|
|
const auto command = Read<LoadCommand>(binary, cursor);
|
|
if (command.Size < sizeof(LoadCommand) || command.Size % 8 != 0 || command.Size > end - cursor) throw std::runtime_error("invalid load command size");
|
|
image.Commands.push_back(cursor);
|
|
cursor += command.Size;
|
|
}
|
|
return image;
|
|
}
|
|
|
|
struct TrieLocation {
|
|
std::size_t OffsetField;
|
|
std::size_t SizeField;
|
|
};
|
|
|
|
std::optional<TrieLocation> FindTrie(const std::vector<std::uint8_t>& binary, const Image& image) {
|
|
std::optional<TrieLocation> found;
|
|
for (const auto offset : image.Commands) {
|
|
const auto cmd = Read<LoadCommand>(binary, offset).Cmd;
|
|
std::optional<TrieLocation> here;
|
|
if (cmd == kLcDyldExportsTrie) here = TrieLocation{offset + offsetof(LinkeditDataCommand, DataOffset), offset + offsetof(LinkeditDataCommand, DataSize)};
|
|
else if (cmd == kLcDyldInfo || cmd == kLcDyldInfoOnly) {
|
|
if (Read<DyldInfoCommand>(binary, offset).ExportSize != 0)
|
|
here = TrieLocation{offset + offsetof(DyldInfoCommand, ExportOffset), offset + offsetof(DyldInfoCommand, ExportSize)};
|
|
}
|
|
if (!here) continue;
|
|
if (found) throw std::runtime_error("Mach-O image has more than one export trie");
|
|
found = here;
|
|
}
|
|
return found;
|
|
}
|
|
|
|
constexpr std::size_t kMaxExportTrieDepth = 1024;
|
|
|
|
void ParseTrieNode(const std::vector<std::uint8_t>& data, std::size_t start, std::size_t end, std::size_t node, std::string& prefix, std::set<std::size_t>& visited, std::vector<MachOExport>& out, std::size_t depth) {
|
|
if (depth > kMaxExportTrieDepth) throw std::runtime_error("export trie nesting too deep");
|
|
if (node >= end - start) throw std::runtime_error("export trie node out of bounds");
|
|
if (!visited.insert(node).second) throw std::runtime_error("export trie has a cycle");
|
|
std::size_t cursor = start + node;
|
|
const auto terminalSize = ReadUleb(data, cursor, end);
|
|
if (terminalSize > end - cursor) throw std::runtime_error("export trie terminal out of bounds");
|
|
const auto children = cursor + terminalSize;
|
|
if (terminalSize != 0) {
|
|
MachOExport entry;
|
|
entry.Name = prefix;
|
|
entry.Flags = ReadUleb(data, cursor, children);
|
|
if (entry.Flags & kExportReexport) {
|
|
entry.Other = ReadUleb(data, cursor, children);
|
|
entry.ImportName = ReadTrieString(data, cursor, children);
|
|
} else {
|
|
entry.Address = ReadUleb(data, cursor, children);
|
|
if (entry.Flags & kExportStubAndResolver) entry.Other = ReadUleb(data, cursor, children);
|
|
}
|
|
out.push_back(std::move(entry));
|
|
}
|
|
cursor = children;
|
|
if (cursor >= end) throw std::runtime_error("export trie child count out of bounds");
|
|
const auto childCount = data[cursor++];
|
|
for (unsigned index = 0; index < childCount; ++index) {
|
|
const auto label = ReadTrieString(data, cursor, end);
|
|
const auto child = ReadUleb(data, cursor, end);
|
|
const auto length = prefix.size();
|
|
prefix += label;
|
|
ParseTrieNode(data, start, end, static_cast<std::size_t>(child), prefix, visited, out, depth + 1);
|
|
prefix.resize(length);
|
|
}
|
|
}
|
|
|
|
std::vector<MachOExport> ParseTrie(const std::vector<std::uint8_t>& binary, std::uint32_t offset, std::uint32_t size) {
|
|
RequireRange(binary, offset, size);
|
|
std::vector<MachOExport> exports;
|
|
if (size == 0) return exports;
|
|
std::string prefix;
|
|
std::set<std::size_t> visited;
|
|
ParseTrieNode(binary, offset, std::size_t{offset} + size, 0, prefix, visited, exports, 0);
|
|
return exports;
|
|
}
|
|
|
|
class TrieBuilder {
|
|
public:
|
|
explicit TrieBuilder(const std::vector<MachOExport>& exports) {
|
|
for (const auto& entry : exports) Insert(entry);
|
|
}
|
|
|
|
std::vector<std::uint8_t> Build() {
|
|
std::vector<Node*> order;
|
|
Collect(&root, order);
|
|
for (bool changed = true; changed;) {
|
|
changed = false;
|
|
std::uint64_t offset = 0;
|
|
for (auto* node : order) {
|
|
if (node->Offset != offset) {
|
|
node->Offset = offset;
|
|
changed = true;
|
|
}
|
|
offset += NodeSize(*node);
|
|
}
|
|
}
|
|
std::vector<std::uint8_t> out;
|
|
for (auto* node : order) {
|
|
if (out.size() != node->Offset) throw std::runtime_error("export trie layout mismatch");
|
|
const auto terminal = TerminalBytes(*node);
|
|
WriteUleb(out, terminal.size());
|
|
out.insert(out.end(), terminal.begin(), terminal.end());
|
|
if (node->Children.size() > 255) throw std::runtime_error("export trie node has too many children");
|
|
out.push_back(static_cast<std::uint8_t>(node->Children.size()));
|
|
for (const auto& [label, child] : node->Children) {
|
|
out.insert(out.end(), label.begin(), label.end());
|
|
out.push_back(0);
|
|
WriteUleb(out, child->Offset);
|
|
}
|
|
}
|
|
while (out.size() % 8 != 0) out.push_back(0);
|
|
return out;
|
|
}
|
|
|
|
private:
|
|
struct Node {
|
|
std::vector<std::pair<std::string, std::unique_ptr<Node>>> Children;
|
|
std::optional<MachOExport> Entry;
|
|
std::uint64_t Offset = 0;
|
|
};
|
|
|
|
Node root;
|
|
|
|
void Insert(const MachOExport& entry) {
|
|
Node* node = &root;
|
|
std::string_view rest = entry.Name;
|
|
while (!rest.empty()) {
|
|
bool descended = false;
|
|
for (auto& [label, child] : node->Children) {
|
|
std::size_t common = 0;
|
|
while (common < label.size() && common < rest.size() && label[common] == rest[common]) ++common;
|
|
if (common == 0) continue;
|
|
if (common < label.size()) {
|
|
auto middle = std::make_unique<Node>();
|
|
middle->Children.emplace_back(label.substr(common), std::move(child));
|
|
label.resize(common);
|
|
child = std::move(middle);
|
|
}
|
|
node = child.get();
|
|
rest.remove_prefix(common);
|
|
descended = true;
|
|
break;
|
|
}
|
|
if (!descended) {
|
|
node->Children.emplace_back(std::string(rest), std::make_unique<Node>());
|
|
node = node->Children.back().second.get();
|
|
rest = {};
|
|
}
|
|
}
|
|
if (node->Entry) throw std::runtime_error("duplicate patched export name: " + entry.Name);
|
|
node->Entry = entry;
|
|
}
|
|
|
|
static void Collect(Node* node, std::vector<Node*>& order) {
|
|
order.push_back(node);
|
|
for (auto& [label, child] : node->Children) Collect(child.get(), order);
|
|
}
|
|
|
|
static std::vector<std::uint8_t> TerminalBytes(const Node& node) {
|
|
std::vector<std::uint8_t> bytes;
|
|
if (!node.Entry) return bytes;
|
|
const auto& entry = *node.Entry;
|
|
WriteUleb(bytes, entry.Flags);
|
|
if (entry.Flags & kExportReexport) {
|
|
WriteUleb(bytes, entry.Other);
|
|
bytes.insert(bytes.end(), entry.ImportName.begin(), entry.ImportName.end());
|
|
bytes.push_back(0);
|
|
} else {
|
|
WriteUleb(bytes, entry.Address);
|
|
if (entry.Flags & kExportStubAndResolver) WriteUleb(bytes, entry.Other);
|
|
}
|
|
return bytes;
|
|
}
|
|
|
|
static std::uint64_t NodeSize(const Node& node) {
|
|
const auto terminal = TerminalBytes(node).size();
|
|
std::uint64_t size = UlebSize(terminal) + terminal + 1;
|
|
for (const auto& [label, child] : node.Children) size += label.size() + 1 + UlebSize(child->Offset);
|
|
return size;
|
|
}
|
|
};
|
|
|
|
std::string StripUnderscore(const std::string& name) {
|
|
return !name.empty() && name[0] == '_' ? name.substr(1) : name;
|
|
}
|
|
|
|
|
|
std::vector<bool> DylibOrdinalsArePrx(const std::vector<std::uint8_t>& binary, const Image& image) {
|
|
std::vector<bool> result;
|
|
for (const auto offset : image.Commands) {
|
|
const auto command = Read<LoadCommand>(binary, offset);
|
|
if (command.Cmd != kLcLoadDylib && command.Cmd != kLcLoadWeakDylib && command.Cmd != kLcReexportDylib && command.Cmd != kLcLazyLoadDylib && command.Cmd != kLcLoadUpwardDylib) continue;
|
|
const auto nameOffset = Read<std::uint32_t>(binary, offset + 8);
|
|
if (nameOffset >= command.Size) throw std::runtime_error("dylib name out of bounds");
|
|
const auto name = Internal::ReadCStr(binary, offset + nameOffset);
|
|
result.push_back(name.size() >= 4 && name.compare(name.size() - 4, 4, ".prx") == 0);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void WriteLinkeditData(std::vector<std::uint8_t>& binary, const Image& image, const TrieLocation& location, const std::vector<std::uint8_t>& trie) {
|
|
const auto oldOffset = Read<std::uint32_t>(binary, location.OffsetField);
|
|
const auto oldSize = Read<std::uint32_t>(binary, location.SizeField);
|
|
if (trie.size() <= oldSize) {
|
|
std::copy(trie.begin(), trie.end(), binary.begin() + oldOffset);
|
|
std::fill(binary.begin() + oldOffset + static_cast<std::ptrdiff_t>(trie.size()), binary.begin() + oldOffset + oldSize, 0);
|
|
Write(binary, location.SizeField, static_cast<std::uint32_t>(trie.size()));
|
|
return;
|
|
}
|
|
std::optional<std::size_t> linkedit;
|
|
for (const auto offset : image.Commands) {
|
|
const auto cmd = Read<LoadCommand>(binary, offset).Cmd;
|
|
if (cmd == kLcCodeSignature) throw std::runtime_error("signed Mach-O images cannot grow linkedit data");
|
|
if (cmd != kLcSegment64) continue;
|
|
const auto segment = Read<SegmentCommand64>(binary, offset);
|
|
if (std::strncmp(segment.Name, "__LINKEDIT", sizeof(segment.Name)) == 0) linkedit = offset;
|
|
}
|
|
if (!linkedit) throw std::runtime_error("Mach-O image has no __LINKEDIT segment");
|
|
auto segment = Read<SegmentCommand64>(binary, *linkedit);
|
|
if (segment.FileOffset + segment.FileSize != binary.size()) throw std::runtime_error("__LINKEDIT does not end the file");
|
|
const auto newOffset = (binary.size() + 7) & ~std::size_t{7};
|
|
if (newOffset + trie.size() > 0xffffffffu) throw std::runtime_error("linkedit data offset overflow");
|
|
binary.resize(newOffset, 0);
|
|
binary.insert(binary.end(), trie.begin(), trie.end());
|
|
std::fill(binary.begin() + oldOffset, binary.begin() + oldOffset + oldSize, 0);
|
|
segment.FileSize = binary.size() - segment.FileOffset;
|
|
segment.VmSize = std::max(segment.VmSize, (segment.FileSize + kPageSize - 1) & ~(kPageSize - 1));
|
|
Write(binary, *linkedit, segment);
|
|
Write(binary, location.OffsetField, static_cast<std::uint32_t>(newOffset));
|
|
Write(binary, location.SizeField, static_cast<std::uint32_t>(trie.size()));
|
|
}
|
|
|
|
void PatchChainedImports(std::vector<std::uint8_t>& binary, const Image& image, const std::unordered_set<std::string>& excluded, const PatchOptions& options) {
|
|
std::size_t commandOffset = 0;
|
|
std::optional<LinkeditDataCommand> fixups;
|
|
for (const auto offset : image.Commands) {
|
|
const auto cmd = Read<LoadCommand>(binary, offset).Cmd;
|
|
if (cmd == kLcDyldChainedFixups) {
|
|
fixups = Read<LinkeditDataCommand>(binary, offset);
|
|
commandOffset = offset;
|
|
}
|
|
if (options.executable && cmd == kLcCodeSignature) throw std::runtime_error("remove the Mach-O code signature before patching and sign again afterwards");
|
|
if ((cmd == kLcDyldInfo || cmd == kLcDyldInfoOnly)) {
|
|
const auto info = Read<DyldInfoCommand>(binary, offset);
|
|
if (info.BindSize != 0 || info.LazyBindSize != 0 || info.WeakBindSize != 0) throw std::runtime_error("Mach-O bind opcodes are not supported; link with chained fixups");
|
|
}
|
|
}
|
|
if (!fixups || fixups->DataSize == 0) return;
|
|
RequireRange(binary, fixups->DataOffset, fixups->DataSize);
|
|
const auto base = std::size_t{fixups->DataOffset};
|
|
const auto header = Read<ChainedFixupsHeader>(binary, base);
|
|
if (header.Version != 0) throw std::runtime_error("unsupported chained fixups version");
|
|
if (header.SymbolsFormat != 0) throw std::runtime_error("compressed chained fixup symbols are not supported");
|
|
const std::size_t entrySize = header.ImportsFormat == kImportFormatPlain ? 4 : header.ImportsFormat == kImportFormatAddend ? 8 : header.ImportsFormat == kImportFormatAddend64 ? 16 : 0;
|
|
if (entrySize == 0) throw std::runtime_error("unsupported chained import format");
|
|
if (header.ImportsOffset > fixups->DataSize || std::uint64_t{header.ImportsCount} * entrySize > fixups->DataSize - header.ImportsOffset || header.SymbolsOffset > fixups->DataSize)
|
|
throw std::runtime_error("chained imports out of bounds");
|
|
const auto symbols = base + header.SymbolsOffset;
|
|
const auto symbolsEnd = base + fixups->DataSize;
|
|
const auto prx = DylibOrdinalsArePrx(binary, image);
|
|
|
|
struct Import { std::uint32_t NameOffset; std::uint32_t Ordinal; };
|
|
std::vector<Import> imports;
|
|
for (std::uint32_t index = 0; index < header.ImportsCount; ++index) {
|
|
const auto at = base + header.ImportsOffset + index * entrySize;
|
|
if (header.ImportsFormat == kImportFormatAddend64) {
|
|
const auto raw = Read<std::uint64_t>(binary, at);
|
|
imports.push_back({static_cast<std::uint32_t>(raw >> 32), static_cast<std::uint32_t>(raw & 0xffff)});
|
|
} else {
|
|
const auto raw = Read<std::uint32_t>(binary, at);
|
|
imports.push_back({raw >> 9, raw & 0xff});
|
|
}
|
|
}
|
|
std::vector<std::string> names;
|
|
std::vector<std::string> originalNames;
|
|
std::vector<bool> selected;
|
|
for (const auto& item : imports) {
|
|
const auto at = symbols + item.NameOffset;
|
|
if (at >= symbolsEnd) throw std::runtime_error("chained import name out of bounds");
|
|
const auto name = Internal::ReadCStr(binary, at);
|
|
if (at + name.size() >= symbolsEnd) throw std::runtime_error("unterminated chained import name");
|
|
const auto bare = StripUnderscore(name);
|
|
const bool special = header.ImportsFormat == kImportFormatAddend64 ? item.Ordinal >= 0xfff0 : item.Ordinal >= 0xf0;
|
|
const bool eligible = options.executable || (bare != name && item.Ordinal != 0 && !special && item.Ordinal <= prx.size() && prx[item.Ordinal - 1]);
|
|
originalNames.push_back(name);
|
|
selected.push_back(eligible);
|
|
if (eligible) names.push_back(bare);
|
|
}
|
|
const auto mapping = ResolveImportNids(names, excluded, options);
|
|
bool grows = false;
|
|
for (std::size_t index = 0; index < imports.size(); ++index)
|
|
grows = grows || (selected[index] && mapping.at(StripUnderscore(originalNames[index])).size() + 1 > originalNames[index].size());
|
|
if (!options.executable && !grows) {
|
|
std::set<std::uint32_t> nameOffsets;
|
|
for (const auto& item : imports) nameOffsets.insert(item.NameOffset);
|
|
std::set<std::uint32_t> rewritten;
|
|
for (const auto& item : imports) {
|
|
const bool special = header.ImportsFormat == kImportFormatAddend64 ? item.Ordinal >= 0xfff0 : item.Ordinal >= 0xf0;
|
|
if (item.Ordinal == 0 || special || item.Ordinal > prx.size() || !prx[item.Ordinal - 1]) continue;
|
|
if (rewritten.contains(item.NameOffset)) continue;
|
|
const auto at = symbols + item.NameOffset;
|
|
if (at >= symbolsEnd) throw std::runtime_error("chained import name out of bounds");
|
|
const auto name = Internal::ReadCStr(binary, at);
|
|
if (at + name.size() >= symbolsEnd) throw std::runtime_error("unterminated chained import name");
|
|
const auto bare = StripUnderscore(name);
|
|
if (bare == name) continue;
|
|
const auto patched = "_" + mapping.at(bare);
|
|
const auto inside = nameOffsets.upper_bound(item.NameOffset);
|
|
if (inside != nameOffsets.end() && *inside <= item.NameOffset + name.size()) throw std::runtime_error("chained import names share storage: " + name);
|
|
std::memcpy(binary.data() + at, patched.data(), patched.size());
|
|
std::fill(binary.begin() + static_cast<std::ptrdiff_t>(at + patched.size()), binary.begin() + static_cast<std::ptrdiff_t>(at + name.size() + 1), 0);
|
|
rewritten.insert(item.NameOffset);
|
|
}
|
|
return;
|
|
}
|
|
if (imports.empty()) return;
|
|
std::vector<std::uint8_t> data(binary.begin() + base, binary.begin() + symbolsEnd);
|
|
const auto newSymbols = data.size();
|
|
if (newSymbols > 0xffffffffu) throw std::runtime_error("chained symbol offset overflow");
|
|
Write(data, offsetof(ChainedFixupsHeader, SymbolsOffset), static_cast<std::uint32_t>(newSymbols));
|
|
for (std::size_t index = 0; index < imports.size(); ++index) {
|
|
const auto& original = originalNames[index];
|
|
const auto name = selected[index] ? (original.starts_with("_") ? "_" : "") + mapping.at(StripUnderscore(original)) : original;
|
|
const auto offset = data.size() - newSymbols;
|
|
const auto at = header.ImportsOffset + index * entrySize;
|
|
if (header.ImportsFormat == kImportFormatAddend64) {
|
|
if (offset > 0xffffffffu) throw std::runtime_error("chained symbol offset overflow");
|
|
const auto raw = Read<std::uint64_t>(data, at);
|
|
Write(data, at, (raw & 0xffffffffu) | (std::uint64_t{offset} << 32));
|
|
} else {
|
|
if (offset > 0x7fffffu) throw std::runtime_error("chained symbol offset overflow");
|
|
const auto raw = Read<std::uint32_t>(data, at);
|
|
Write(data, at, (raw & 0x1ffu) | (static_cast<std::uint32_t>(offset) << 9));
|
|
}
|
|
data.insert(data.end(), name.begin(), name.end());
|
|
data.push_back(0);
|
|
}
|
|
WriteLinkeditData(binary, image, {commandOffset + 8, commandOffset + 12}, data);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
std::vector<MachOExport> ReadMachOExports(const std::vector<std::uint8_t>& binary) {
|
|
const auto image = ParseImage(binary);
|
|
const auto location = FindTrie(binary, image);
|
|
if (!location) return {};
|
|
return ParseTrie(binary, Read<std::uint32_t>(binary, location->OffsetField), Read<std::uint32_t>(binary, location->SizeField));
|
|
}
|
|
|
|
void MachONidPatcher::PatchNids(std::vector<std::uint8_t>& binary, const std::string& libraryName, const std::unordered_set<std::string>& excludedExports, const PatchOptions& options) const {
|
|
const auto image = ParseImage(binary);
|
|
if (options.executable) {
|
|
PatchChainedImports(binary, image, excludedExports, options);
|
|
return;
|
|
}
|
|
const auto location = FindTrie(binary, image);
|
|
if (!location) throw std::runtime_error("no export trie");
|
|
auto exports = ParseTrie(binary, Read<std::uint32_t>(binary, location->OffsetField), Read<std::uint32_t>(binary, location->SizeField));
|
|
if (exports.empty()) throw std::runtime_error("no exported names");
|
|
|
|
std::vector<std::string> names;
|
|
names.reserve(exports.size());
|
|
for (const auto& entry : exports) {
|
|
if (entry.Name.empty() || entry.Name[0] != '_') throw std::runtime_error("exported name without a C underscore: " + entry.Name);
|
|
names.push_back(entry.Name.substr(1));
|
|
}
|
|
const auto nidMap = ResolveNids(names, libraryName, excludedExports);
|
|
for (std::size_t index = 0; index < exports.size(); ++index) {
|
|
const auto& name = names[index];
|
|
const bool marked = name.ends_with(Internal::kNidPostfix) || Internal::IsNidNoPatch(name) || Internal::IsNidNoPatchCut(name);
|
|
if ((exports[index].Flags & kExportWeakDefinition) != 0 && !marked) continue;
|
|
const auto found = nidMap.find(name);
|
|
if (found == nidMap.end()) throw std::runtime_error("symbol not in nid map: " + name);
|
|
exports[index].Name = "_" + found->second;
|
|
}
|
|
WriteLinkeditData(binary, image, *location, TrieBuilder(exports).Build());
|
|
PatchChainedImports(binary, ParseImage(binary), excludedExports, options);
|
|
}
|
|
|
|
}
|