summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--view/macho/chained_fixups.cpp528
-rw-r--r--view/macho/chained_fixups.h115
-rw-r--r--view/macho/machoview.cpp459
-rw-r--r--view/macho/machoview.h2
4 files changed, 686 insertions, 418 deletions
diff --git a/view/macho/chained_fixups.cpp b/view/macho/chained_fixups.cpp
new file mode 100644
index 00000000..682f561c
--- /dev/null
+++ b/view/macho/chained_fixups.cpp
@@ -0,0 +1,528 @@
+#include "chained_fixups.h"
+
+#include "view/macho/machoview.h"
+#include <span>
+
+// If enabled, prints detailed information in the same format as `dyld_info -fixup_chain_details`
+// to allow comparing behavior.
+// #define DEBUG_PRINT_DYLD_INFO 1
+
+#if DEBUG_PRINT_DYLD_INFO
+namespace BinaryNinja {
+std::string format_as(const FixupInfo& fixup);
+}
+
+template <>
+struct fmt::formatter<BinaryNinja::AuthKeyType> : fmt::formatter<std::string>
+{
+ template <typename FormatContext>
+ auto format(const BinaryNinja::AuthKeyType& type, FormatContext& ctx) const
+ {
+ using BinaryNinja::AuthKeyType;
+ std::string_view name = "Unknown";
+ switch (type)
+ {
+ case AuthKeyType::IA: name = "IA"; break;
+ case AuthKeyType::IB: name = "IB"; break;
+ case AuthKeyType::DA: name = "DA"; break;
+ case AuthKeyType::DB: name = "DB"; break;
+ case AuthKeyType::None: name = "None"; break;
+ }
+ return fmt::formatter<std::string>::format(name, ctx);
+ }
+};
+#endif
+
+namespace BinaryNinja {
+
+namespace {
+
+FixupInfo BindFixup(uint32_t ordinal, int32_t addend, uint16_t next)
+{
+ return { .bind = { ordinal, addend }, FixupType::Bind, .next = next };
+}
+
+FixupInfo RebaseFixup(uint64_t target, uint16_t next)
+{
+ return { .rebase = { target }, FixupType::Rebase, .next = next };
+}
+
+FixupInfo AuthBindFixup(uint32_t ordinal,
+ AuthKeyType keyType, bool usesAddressDiversity, uint16_t addressDiversity, uint16_t next)
+{
+ return { .bind = { ordinal, 0 }, FixupType::Bind, true, keyType, usesAddressDiversity, addressDiversity, next };
+}
+
+FixupInfo AuthRebaseFixup(uint64_t target,
+ AuthKeyType keyType, bool usesAddressDiversity, uint16_t addressDiversity, uint16_t next)
+{
+ return { .rebase = { target }, FixupType::Rebase, true, keyType, usesAddressDiversity, addressDiversity, next };
+}
+
+FixupInfo ConvertFromAddressToOffset(FixupInfo fixup, uint64_t preferredLoadAddress)
+{
+ if (fixup.type == FixupType::Rebase)
+ fixup.rebase.target -= preferredLoadAddress;
+
+ return fixup;
+}
+
+uint64_t Stride(int pointerFormat)
+{
+ switch (pointerFormat)
+ {
+ case DYLD_CHAINED_PTR_ARM64E:
+ case DYLD_CHAINED_PTR_ARM64E_USERLAND:
+ case DYLD_CHAINED_PTR_ARM64E_FIRMWARE:
+ case DYLD_CHAINED_PTR_ARM64E_USERLAND24:
+ case DYLD_CHAINED_PTR_ARM64E_SHARED_CACHE:
+ return 8;
+
+ case DYLD_CHAINED_PTR_32_CACHE:
+ case DYLD_CHAINED_PTR_32_FIRMWARE:
+ case DYLD_CHAINED_PTR_32:
+ case DYLD_CHAINED_PTR_64_KERNEL_CACHE:
+ case DYLD_CHAINED_PTR_64_OFFSET:
+ case DYLD_CHAINED_PTR_64:
+ case DYLD_CHAINED_PTR_ARM64E_KERNEL:
+ case DYLD_CHAINED_PTR_ARM64E_SEGMENTED:
+ return 4;
+
+ case DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE:
+ return 1;
+
+ default:
+ return 0;
+ }
+}
+
+bool IsOffsetBased(int pointerFormat)
+{
+ switch (pointerFormat)
+ {
+ case DYLD_CHAINED_PTR_ARM64E_USERLAND:
+ case DYLD_CHAINED_PTR_ARM64E_USERLAND24:
+ case DYLD_CHAINED_PTR_ARM64E_KERNEL:
+ case DYLD_CHAINED_PTR_ARM64E_SHARED_CACHE:
+ case DYLD_CHAINED_PTR_64_OFFSET:
+ case DYLD_CHAINED_PTR_32_CACHE:
+ case DYLD_CHAINED_PTR_32:
+ case DYLD_CHAINED_PTR_64_KERNEL_CACHE:
+ case DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE:
+ return true;
+
+ case DYLD_CHAINED_PTR_ARM64E:
+ case DYLD_CHAINED_PTR_ARM64E_FIRMWARE:
+ case DYLD_CHAINED_PTR_ARM64E_SEGMENTED:
+ case DYLD_CHAINED_PTR_64:
+ case DYLD_CHAINED_PTR_32_FIRMWARE:
+ return false;
+ }
+ return false;
+}
+
+// DYLD_CHAINED_PTR_ARM64E, DYLD_CHAINED_PTR_ARM64E_USERLAND, DYLD_CHAINED_PTR_ARM64E_FIRMWARE,
+// DYLD_CHAINED_PTR_ARM64E_KERNEL, DYLD_CHAINED_PTR_ARM64E_SHARED_CACHE
+FixupInfo ParseFixup(Arm64e ptr)
+{
+ if (ptr.bind.bind)
+ {
+ if (!ptr.bind.auth)
+ return BindFixup(ptr.bind.ordinal, ptr.bind.addend, ptr.bind.next);
+
+ return AuthBindFixup(ptr.authBind.ordinal,
+ (AuthKeyType)ptr.authBind.key, ptr.authBind.addrDiv,
+ ptr.authBind.diversity, ptr.authBind.next);
+ }
+
+ if (!ptr.rebase.auth) {
+ // TODO: Handle high8.
+ return RebaseFixup(ptr.rebase.target, ptr.rebase.next);
+ }
+
+ return AuthRebaseFixup(ptr.authRebase.target, (AuthKeyType)ptr.authRebase.key,
+ ptr.authRebase.addrDiv, ptr.authRebase.diversity, ptr.authRebase.next);
+}
+
+// DYLD_CHAINED_PTR_ARM64E_USERLAND24
+FixupInfo ParseFixup24(Arm64e ptr)
+{
+ // DYLD_CHAINED_PTR_ARM64E_USERLAND24 has special handling for binds only.
+ if (ptr.bind24.bind) {
+ if (!ptr.bind24.auth)
+ return BindFixup(ptr.bind24.ordinal, 0, ptr.bind24.next);
+
+ return AuthBindFixup(ptr.authBind24.ordinal,
+ (AuthKeyType)ptr.authBind24.key, ptr.authBind24.addrDiv,
+ ptr.authBind24.diversity, ptr.authBind24.next);
+ }
+
+ // For rebases it is interpreted as if it were DYLD_CHAINED_PTR_ARM64E.
+ return ParseFixup(ptr);
+}
+
+// DYLD_CHAINED_PTR_64, DYLD_CHAINED_PTR_64_OFFSET
+FixupInfo ParseFixup(Generic64 ptr)
+{
+ if (ptr.bind.bind)
+ return BindFixup(ptr.bind.ordinal, ptr.bind.addend, ptr.bind.next);
+
+ // TODO: Handle high8.
+ return RebaseFixup(ptr.rebase.target, ptr.rebase.next);
+}
+
+// DYLD_CHAINED_PTR_32, DYLD_CHAINED_PTR_32_FIRMWARE
+FixupInfo ParseFixup(Generic32 ptr)
+{
+ if (ptr.bind.bind)
+ return BindFixup(ptr.bind.ordinal, ptr.bind.addend, ptr.bind.next);
+
+ return RebaseFixup(ptr.rebase.target, ptr.rebase.next);
+}
+
+// DYLD_CHAINED_PTR_32_CACHE
+FixupInfo ParseFixup(dyld_chained_ptr_32_cache_rebase ptr)
+{
+ return RebaseFixup(ptr.target, ptr.next);
+}
+
+// Returns a function that will read a single chained fixup of the given format
+// from the provided BinaryReader, returning the raw value and the parsed FixupInfo.
+auto FixupReaderForFormat(int format) -> std::pair<uint64_t, FixupInfo>(*)(BinaryReader&)
+{
+ switch (format)
+ {
+ case DYLD_CHAINED_PTR_ARM64E:
+ case DYLD_CHAINED_PTR_ARM64E_USERLAND:
+ case DYLD_CHAINED_PTR_ARM64E_FIRMWARE:
+ case DYLD_CHAINED_PTR_ARM64E_KERNEL:
+ case DYLD_CHAINED_PTR_ARM64E_SHARED_CACHE:
+ return +[](BinaryReader& reader) -> std::pair<uint64_t, FixupInfo> {
+ uint64_t raw = reader.Read64();
+ return {raw, ParseFixup(ChainedFixupPointer{ .raw64 = raw }.arm64e) };
+ };
+ case DYLD_CHAINED_PTR_ARM64E_USERLAND24:
+ return +[](BinaryReader& reader) -> std::pair<uint64_t, FixupInfo> {
+ uint64_t raw = reader.Read64();
+ return {raw, ParseFixup24(ChainedFixupPointer{ .raw64 = raw }.arm64e) };
+ };
+ case DYLD_CHAINED_PTR_64:
+ case DYLD_CHAINED_PTR_64_OFFSET:
+ return +[](BinaryReader& reader) -> std::pair<uint64_t, FixupInfo> {
+ uint64_t raw = reader.Read64();
+ return {raw, ParseFixup(ChainedFixupPointer{ .raw64 = raw }.generic64) };
+ };
+ case DYLD_CHAINED_PTR_32:
+ case DYLD_CHAINED_PTR_32_FIRMWARE:
+ return +[](BinaryReader& reader) -> std::pair<uint64_t, FixupInfo> {
+ uint32_t raw = reader.Read32();
+ return {static_cast<uint64_t>(raw), ParseFixup(ChainedFixupPointer{ .raw32 = raw }.generic32) };
+ };
+ case DYLD_CHAINED_PTR_32_CACHE:
+ return +[](BinaryReader& reader) -> std::pair<uint64_t, FixupInfo> {
+ uint32_t raw = reader.Read32();
+ return {static_cast<uint64_t>(raw), ParseFixup(ChainedFixupPointer{ .raw32 = raw }.cache32)};
+ };
+ case DYLD_CHAINED_PTR_64_KERNEL_CACHE:
+ case DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE:
+ case DYLD_CHAINED_PTR_ARM64E_SEGMENTED:
+ // These formats are not yet supported.
+ return nullptr;
+ }
+ throw std::invalid_argument("Unknown chained pointer format: " + std::to_string(format));
+}
+
+ImportEntry ReadChainedImport32(BinaryReader& reader, std::span<const char> symbolData)
+{
+ dyld_chained_import import;
+ reader.Read(&import, sizeof(import));
+ return {
+ std::string_view(&symbolData[import.name_offset]),
+ 0,
+ import.lib_ordinal,
+ (bool)import.weak_import,
+ };
+}
+
+ImportEntry ReadChainedImportAddend32(BinaryReader& reader, std::span<const char> symbolData)
+{
+ dyld_chained_import_addend import;
+ reader.Read(&import, sizeof(import));
+ return {
+ std::string_view(&symbolData[import.name_offset]),
+ static_cast<uint32_t>(import.addend),
+ import.lib_ordinal,
+ (bool)import.weak_import,
+ };
+}
+
+ImportEntry ReadChainedImportAddend64(BinaryReader& reader, std::span<const char> symbolData)
+{
+ dyld_chained_import_addend64 import;
+ reader.Read(&import, sizeof(import));
+ return {
+ std::string_view(&symbolData[import.name_offset]),
+ import.addend,
+ (int32_t)import.lib_ordinal,
+ (bool)import.weak_import,
+ };
+}
+
+// Returns a function that will read a single chained import of the given format
+// from the provided BinaryReader, returning the parsed ImportEntry.
+auto ChainedImportReaderForFormat(int format)
+{
+ switch (format)
+ {
+ case DYLD_CHAINED_IMPORT:
+ return ReadChainedImport32;
+ case DYLD_CHAINED_IMPORT_ADDEND:
+ return ReadChainedImportAddend32;
+ case DYLD_CHAINED_IMPORT_ADDEND64:
+ return ReadChainedImportAddend64;
+ }
+
+ throw std::invalid_argument("Unknown chained import format");
+}
+
+} // unnamed namespace
+
+std::vector<ImportEntry> ChainedFixupProcessor::ProcessImports() const
+{
+ std::vector<ImportEntry> imports;
+
+ BinaryReader reader(m_raw);
+ reader.Seek(m_fixupsStartOffset);
+
+ auto header = ReadHeader(reader);
+
+ uint64_t symbolDataSize = m_fixupsSize - header.symbols_offset;
+ m_symbolData.resize(symbolDataSize);
+ m_raw->Read(&m_symbolData[0], OffsetInFixups(header.symbols_offset), symbolDataSize);
+
+ reader.Seek(OffsetInFixups(header.imports_offset));
+
+ auto importHandler = ChainedImportReaderForFormat(header.imports_format);
+ for (uint32_t i = 0; i < header.imports_count; i++)
+ {
+ ImportEntry entry = importHandler(reader, m_symbolData);
+
+#if DEBUG_PRINT_DYLD_INFO
+ fmt::println(" import[{}]: ", i);
+ fmt::println(" name: {}", entry.name);
+ fmt::println(" addend: 0x{:X}", entry.addend);
+ fmt::println(" library_ordinal: {}", entry.libraryOrdinal);
+#endif
+
+ imports.push_back(std::move(entry));
+ }
+
+ return imports;
+}
+
+void ChainedFixupProcessor::ProcessFixups(std::function<void(uint64_t, const FixupInfo&)> fixupHandler) const
+{
+ m_fixupHandler = std::move(fixupHandler);
+
+ BinaryReader reader(m_raw);
+ reader.Seek(m_fixupsStartOffset);
+
+ auto header = ReadHeader(reader);
+#if DEBUG_PRINT_DYLD_INFO
+ fmt::println("Chained Fixups Header:");
+ fmt::println(" fixups_version: 0x{:08X}", header.fixups_version);
+ fmt::println(" starts_offset: 0x{:08X}", header.starts_offset);
+ fmt::println(" imports_offset: 0x{:08X}", header.imports_offset);
+ fmt::println(" symbols_offset: 0x{:08X}", header.symbols_offset);
+ fmt::println(" imports_count: {}", header.imports_count);
+ fmt::println(" imports_format: {}", header.imports_format);
+ fmt::println(" symbols_format: {}", header.symbols_format);
+#endif
+ ProcessChainedFixups(header, reader);
+
+ m_fixupHandler = {};
+}
+
+dyld_chained_fixups_header ChainedFixupProcessor::ReadHeader(BinaryReader& reader) const
+{
+ return {
+ reader.Read32(),
+ reader.Read32(),
+ reader.Read32(),
+ reader.Read32(),
+ reader.Read32(),
+ reader.Read32(),
+ reader.Read32(),
+ };
+}
+
+void ChainedFixupProcessor::ProcessChainedFixups(const dyld_chained_fixups_header& header, BinaryReader& reader) const
+{
+ reader.Seek(OffsetInFixups(header.starts_offset));
+
+ // Read dyld_chained_starts_in_image
+ uint32_t segmentCount = reader.Read32();
+ std::vector<uint32_t> segmentOffsets(segmentCount);
+ for (uint32_t i = 0; i < segmentCount; i++)
+ segmentOffsets[i] = reader.Read32();
+
+ [[maybe_unused]] size_t i = 0;
+ for (uint32_t offset : segmentOffsets)
+ {
+ i++;
+ if (!offset)
+ continue;
+
+ reader.Seek(OffsetInFixups(header.starts_offset + offset));
+
+ // Read dyld_chained_starts_in_segment
+ dyld_chained_starts_in_segment segment = {
+ reader.Read32(),
+ reader.Read16(),
+ reader.Read16(),
+ reader.Read64(),
+ reader.Read32(),
+ reader.Read16(),
+ };
+#if DEBUG_PRINT_DYLD_INFO
+ fmt::println(" seg[{}]:", i);
+ fmt::println(" page_size: 0x{:04X}", segment.page_size);
+ fmt::println(" pointer_format: {}", segment.pointer_format);
+ fmt::println(" segment_offset: 0x{:08X}", segment.segment_offset);
+ fmt::println(" max_pointer: 0x{:08X}", segment.max_valid_pointer);
+ fmt::println(" pages: {}", segment.page_count);
+#endif
+ ProcessChainsInSegment(segment, reader);
+ }
+
+}
+
+void ChainedFixupProcessor::ProcessChainsInSegment(const dyld_chained_starts_in_segment& segment, BinaryReader& reader) const
+{
+ uint64_t stride = Stride(segment.pointer_format);
+ bool isOffset = IsOffsetBased(segment.pointer_format);
+ auto fixupReader = FixupReaderForFormat(segment.pointer_format);
+ if (!fixupReader)
+ {
+ m_logger->LogWarnF("Unsupported pointer format in chained fixups: {}", segment.pointer_format);
+ return;
+ }
+ auto it = m_segmentVMAddrToFileOffset.find(segment.segment_offset + m_preferredLoadAddress);
+ if (it == m_segmentVMAddrToFileOffset.end())
+ {
+ m_logger->LogWarnF("No file offset found for segment vmaddr {:#x}", segment.segment_offset);
+ return;
+ }
+ uint64_t segmentFileOffset = it->second;
+
+ auto chainStarts = ReadChainStartsInSegment(segment, reader);
+ for (auto [pageIndex, offsetInPage] : chainStarts)
+ {
+#if DEBUG_PRINT_DYLD_INFO
+ fmt::println(" start[{:2}]: 0x{:04X}", pageIndex, offsetInPage);
+#endif
+
+ uint64_t pageOffset = segmentFileOffset + (pageIndex * segment.page_size);
+ reader.Seek(pageOffset + offsetInPage);
+
+ bool done = false;
+ while (!done)
+ {
+ uint64_t position = reader.GetOffset();
+ auto [raw, fixupInfo] = fixupReader(reader);
+
+ if (!isOffset)
+ fixupInfo = ConvertFromAddressToOffset(fixupInfo, m_preferredLoadAddress);
+
+ uint64_t positionVMAddr = position - segmentFileOffset + segment.segment_offset;
+#if DEBUG_PRINT_DYLD_INFO
+ fmt::println(" 0x{:08X}: raw: 0x{:016X} {}", positionVMAddr, raw, fixupInfo);
+#endif
+ m_fixupHandler(positionVMAddr, fixupInfo);
+
+ done = (fixupInfo.next == 0);
+ reader.Seek(position + (fixupInfo.next * stride));
+ }
+ }
+}
+
+std::vector<std::pair<uint64_t, uint16_t>> ChainedFixupProcessor::ReadChainStartsInSegment(const dyld_chained_starts_in_segment& segment, BinaryReader& reader) const
+{
+ uint64_t chainStartsOffset = reader.GetOffset();
+ std::vector<std::pair<uint64_t, uint16_t>> chainStarts;
+ for (size_t i = 0; i < segment.page_count; i++) {
+ uint16_t start = reader.Read16();
+ if (start == DYLD_CHAINED_PTR_START_NONE)
+ continue;
+
+ if (!(start & DYLD_CHAINED_PTR_START_MULTI))
+ {
+ chainStarts.push_back({i, start});
+ continue;
+ }
+
+ // For armv7, and potentially other architectures, there can be multiple chains per page.
+ // When this is the case, the value has the DYLD_CHAINED_PTR_START_MULTI bit set, and the
+ // lower 15 bits are an index into chain starts table.
+ uint64_t savedOffset = reader.GetOffset();
+ uint16_t overflowIndex = start & ~DYLD_CHAINED_PTR_START_MULTI;
+ reader.Seek(chainStartsOffset + (overflowIndex * sizeof(uint16_t)));
+
+ bool chainEnd = false;
+ while (!chainEnd)
+ {
+ uint16_t start = reader.Read16();
+ chainEnd = (start & DYLD_CHAINED_PTR_START_LAST);
+ chainStarts.push_back({i, start & ~DYLD_CHAINED_PTR_START_LAST});
+
+ }
+ reader.Seek(savedOffset);
+ }
+
+ return chainStarts;
+}
+
+#ifdef DEBUG_PRINT_DYLD_INFO
+
+std::string format_as(const FixupInfo& fixup)
+{
+ if (fixup.type == FixupType::Bind)
+ {
+ if (fixup.isAuthenticated)
+ {
+ return fmt::format(" auth-bind: (next: {:03}, key: {}, addrDiv: {:d}, diversity: 0x{:04X}, bindOrdinal: 0x{:06X})",
+ fixup.next,
+ fixup.authKeyType,
+ fixup.usesAddressDiversity,
+ fixup.addressDiversity,
+ fixup.bind.ordinal);
+ }
+
+ std::string addend = "";
+ if (fixup.bind.addend)
+ addend = fmt::format(", addend: {}", fixup.bind.addend);
+
+ return fmt::format(" bind: (next: {:03}, bindOrdinal: 0x{:06X}{})",
+ fixup.next,
+ fixup.bind.ordinal,
+ addend);
+ }
+
+ if (fixup.isAuthenticated)
+ {
+ return fmt::format("auth-rebase: (next: {:03}, key: {}, addrDiv: {:d}, diversity: 0x{:04X}, target: 0x{:011X})",
+ fixup.next,
+ fixup.authKeyType,
+ fixup.usesAddressDiversity,
+ fixup.addressDiversity,
+ fixup.rebase.target);
+ }
+
+ return fmt::format(" rebase: (next: {:03}, target: 0x{:011X})",
+ fixup.next,
+ fixup.rebase.target);
+}
+
+#endif
+
+} // namespace BinaryNinja
diff --git a/view/macho/chained_fixups.h b/view/macho/chained_fixups.h
new file mode 100644
index 00000000..9a26a1f5
--- /dev/null
+++ b/view/macho/chained_fixups.h
@@ -0,0 +1,115 @@
+#pragma once
+
+#include "machoview.h"
+
+#include <functional>
+#include <stdint.h>
+#include <string_view>
+#include <vector>
+
+namespace BinaryNinja
+{
+
+enum class FixupType : uint8_t
+{
+ Bind,
+ Rebase,
+};
+
+enum class AuthKeyType : uint8_t
+{
+ IA,
+ IB,
+ DA,
+ DB,
+ None,
+};
+
+struct FixupInfo
+{
+ union
+ {
+ struct
+ {
+ uint32_t ordinal = 0;
+ int32_t addend = 0;
+ } bind;
+ struct
+ {
+ uint64_t target = 0;
+ } rebase;
+ };
+ FixupType type : 1;
+ bool isAuthenticated : 1 = false;
+ AuthKeyType authKeyType : 3 = AuthKeyType::None;
+ bool usesAddressDiversity : 1 = false;
+ uint16_t addressDiversity = 0;
+ uint16_t next = 0;
+};
+
+struct ImportEntry
+{
+ std::string_view name;
+ uint64_t addend;
+ int32_t libraryOrdinal;
+ bool weakImport = false;
+};
+
+class ChainedFixupProcessor
+{
+public:
+ ChainedFixupProcessor(Ref<BinaryView> raw, Ref<Logger> logger, uint64_t machOStartOffset, uint64_t preferredLoadAddress,
+ const linkedit_data_command& chainedFixupCommand, std::unordered_map<uint64_t, uint64_t> segmentVMAddrToFileOffset)
+ : m_raw(std::move(raw))
+ , m_logger(std::move(logger))
+ , m_machOStartOffset(machOStartOffset)
+ , m_fixupsStartOffset(OffsetInRaw(chainedFixupCommand.dataoff))
+ , m_fixupsSize(chainedFixupCommand.datasize)
+ , m_preferredLoadAddress(preferredLoadAddress)
+ , m_segmentVMAddrToFileOffset(std::move(segmentVMAddrToFileOffset))
+ {}
+
+ std::vector<ImportEntry> ProcessImports() const;
+
+ // Calls the provided handler for each fixup found. `offset` is relative to `machOStartOffset`.
+ //
+ // Note that `FixupInfo` references data owned by this object and so should not be used outside
+ // of the handler function.
+ void ProcessFixups(std::function<void(uint64_t offset, const FixupInfo&)> fixupHandler) const;
+
+private:
+ dyld_chained_fixups_header ReadHeader(BinaryReader&) const;
+
+ void ProcessChainedFixups(const dyld_chained_fixups_header&, BinaryReader&) const;
+ void ProcessChainsInSegment(const dyld_chained_starts_in_segment&, BinaryReader&) const;
+
+ // Returns a vector of pairs of (page index, offset in page) representing the start of each fixup chain.
+ std::vector<std::pair<uint64_t, uint16_t>> ReadChainStartsInSegment(const dyld_chained_starts_in_segment&, BinaryReader&) const;
+
+ uint64_t OffsetInRaw(uint64_t offset) const { return m_machOStartOffset + offset; }
+ uint64_t OffsetInFixups(uint64_t offset) const { return m_fixupsStartOffset + offset; }
+
+ Ref<BinaryView> m_raw;
+ Ref<Logger> m_logger;
+
+ // Offset to the start of the Mach-O file within the BinaryView.
+ // This will be non-zero for Mach-O files within a universal binary.
+ uint64_t m_machOStartOffset;
+
+ // Offset to the start of the chained fixups data within the BinaryView.
+ uint64_t m_fixupsStartOffset;
+
+ // Total size of the chained fixups data.
+ uint64_t m_fixupsSize;
+
+ // The preferred load address of the __TEXT segment. Used for translating
+ // address-based fixups into offset-based fixups.
+ uint64_t m_preferredLoadAddress;
+
+ std::unordered_map<uint64_t, uint64_t> m_segmentVMAddrToFileOffset;
+
+ mutable std::function<void(uint64_t offset, const FixupInfo&)> m_fixupHandler;
+ mutable std::vector<char> m_symbolData;
+};
+
+} // namespace BinaryNinja
diff --git a/view/macho/machoview.cpp b/view/macho/machoview.cpp
index 5a3da11b..3b6fb74d 100644
--- a/view/macho/machoview.cpp
+++ b/view/macho/machoview.cpp
@@ -1,3 +1,11 @@
+#include "machoview.h"
+
+#include "chained_fixups.h"
+#include "fatmachoview.h"
+#include "lowlevelilinstruction.h"
+#include "rapidjsonwrapper.h"
+#include "universalview.h"
+
#include <algorithm>
#include <cstdint>
#include <sstream>
@@ -8,11 +16,6 @@
#ifndef _MSC_VER
#include <cxxabi.h>
#endif
-#include "machoview.h"
-#include "fatmachoview.h"
-#include "universalview.h"
-#include "lowlevelilinstruction.h"
-#include "rapidjsonwrapper.h"
using namespace BinaryNinja;
using namespace std;
@@ -2151,7 +2154,7 @@ bool MachoView::InitializeHeader(MachOHeader& header, bool isMainHeader, uint64_
default:
if (ordinal > 0)
{
- if (auto symbol = GetSymbolByRawName(name, GetExternalNameSpace()); symbol)
+ if (auto symbol = GetSymbolByRawName(name, GetExternalNameSpace()))
{
DefineRelocation(m_arch, relocation, symbol, relocation.address);
handled = true;
@@ -3270,427 +3273,44 @@ void MachoView::ParseChainedFixups(
if (!chainedFixups.dataoff)
return;
- m_logger->LogDebug("Processing Chained Fixups");
-
- // Dummy relocation
- BNRelocationInfo reloc;
- memset(&reloc, 0, sizeof(BNRelocationInfo));
- reloc.type = StandardRelocationType;
- reloc.size = m_addressSize;
- reloc.nativeType = BINARYNINJA_MANUAL_RELOCATION;
-
- bool processBinds = true;
-
- BinaryReader parentReader(GetParentView());
- BinaryReader mappedReader(this);
-
try {
- dyld_chained_fixups_header fixupsHeader {};
- uint64_t fixupHeaderAddress = m_universalImageOffset + chainedFixups.dataoff;
- parentReader.Seek(fixupHeaderAddress);
- fixupsHeader.fixups_version = parentReader.Read32();
- fixupsHeader.starts_offset = parentReader.Read32();
- fixupsHeader.imports_offset = parentReader.Read32();
- fixupsHeader.symbols_offset = parentReader.Read32();
- fixupsHeader.imports_count = parentReader.Read32();
- fixupsHeader.imports_format = parentReader.Read32();
- fixupsHeader.symbols_format = parentReader.Read32();
-
- m_logger->LogDebugF(
- "Chained Fixups: Header @ {:#x} // Fixups version {}", fixupHeaderAddress, fixupsHeader.fixups_version);
-
- size_t importsAddress = fixupHeaderAddress + fixupsHeader.imports_offset;
- size_t importTableSize = sizeof(dyld_chained_import) * fixupsHeader.imports_count;
-
- if (fixupsHeader.fixups_version > 0)
- {
- m_logger->LogError("Chained Fixup parsing failed. Unknown Fixups Version");
- return;
- }
-
- if (importTableSize > chainedFixups.datasize)
- {
- m_logger->LogError("Chained Fixup parsing failed. Binary is malformed");
- return;
- }
-
- size_t symbolsAddress = fixupHeaderAddress + fixupsHeader.symbols_offset;
-
- // Pre-load the import table. We may re-access the same ordinal multiple times, this will be faster.
- std::vector<import_entry> importTable;
- parentReader.Seek(importsAddress);
-
- auto processChainedImport =
- [symbolsAddress, &importTable](
- uint64_t ordinal, uint64_t addend, uint32_t nameOffset, bool weak, auto& reader) {
- import_entry entry;
- entry.lib_ordinal = ordinal;
- entry.addend = addend;
- entry.weak = weak;
-
- auto nextEntryAddress = reader.GetOffset();
- size_t symNameAddr = symbolsAddress + nameOffset;
-
- reader.Seek(symNameAddr);
- try
- {
- string symbolName = reader.ReadCString();
- entry.name = symbolName;
- }
- catch (ReadException& ex)
- {
- entry.name = "";
- }
-
- importTable.push_back(entry);
- reader.Seek(nextEntryAddress);
- };
-
- switch (fixupsHeader.imports_format)
- {
- case DYLD_CHAINED_IMPORT:
- {
- for (size_t i = 0; i < fixupsHeader.imports_count; i++)
- {
- uint32_t importEntry = parentReader.Read32();
- dyld_chained_import import = *(reinterpret_cast<dyld_chained_import*>(&importEntry));
- processChainedImport(static_cast<int8_t>(import.lib_ordinal), 0, import.name_offset, import.weak_import, parentReader);
- }
- break;
- }
- case DYLD_CHAINED_IMPORT_ADDEND:
- {
- for (size_t i = 0; i < fixupsHeader.imports_count; i++)
- {
- dyld_chained_import_addend import;
- parentReader.Read(&import, sizeof(import));
- processChainedImport(static_cast<int8_t>(import.lib_ordinal), import.addend, import.name_offset, import.weak_import, parentReader);
- }
- break;
- }
- case DYLD_CHAINED_IMPORT_ADDEND64:
- {
- for (size_t i = 0; i < fixupsHeader.imports_count; i++)
- {
- dyld_chained_import_addend64 import;
- parentReader.Read(&import, sizeof(import));
- processChainedImport(static_cast<int16_t>(import.lib_ordinal), import.addend, import.name_offset, import.weak_import, parentReader);
- }
- break;
- }
- default:
- {
- m_logger->LogWarnF("Chained Fixups: Unknown import binding format {}", fixupsHeader.imports_format);
- processBinds = false; // We can still handle rebases.
- break;
- }
- }
-
- m_logger->LogDebugF("Chained Fixups: {:#x} import table entries", importTable.size());
-
- uint64_t fixupStartsAddress = fixupHeaderAddress + fixupsHeader.starts_offset;
- parentReader.Seek(fixupStartsAddress);
- dyld_chained_starts_in_image segs {};
- segs.seg_count = parentReader.Read32();
- vector<uint32_t> segInfoOffsets {};
- for (size_t i = 0; i < segs.seg_count; i++)
+ std::unordered_map<uint64_t, uint64_t> segmentVMAddrToFileOffset;
+ segmentVMAddrToFileOffset.reserve(m_allSegments.size());
+ for (const auto& segment : m_allSegments)
{
- segInfoOffsets.push_back(parentReader.Read32());
+ // Note that while `fileoff` within the binary is relative to the start of the Mach-O slice,
+ // `MachoView::HeaderForAddress` updates it to be relative to the start of the file.
+ segmentVMAddrToFileOffset[segment.vmaddr] = segment.fileoff;
}
- for (auto offset : segInfoOffsets)
- {
- if (!offset)
- continue;
- dyld_chained_starts_in_segment starts {};
- uint64_t startsAddr = fixupStartsAddress + offset;
- parentReader.Seek(startsAddr);
- starts.size = parentReader.Read32();
- starts.page_size = parentReader.Read16();
- starts.pointer_format = parentReader.Read16();
- starts.segment_offset = parentReader.Read64();
- starts.max_valid_pointer = parentReader.Read32();
- starts.page_count = parentReader.Read16();
+ ChainedFixupProcessor processor(GetParentView(), m_logger, m_universalImageOffset, GetOriginalImageBase(), chainedFixups, std::move(segmentVMAddrToFileOffset));
+ auto importsTable = processor.ProcessImports();
+ processor.ProcessFixups([=, this, &header](uint64_t offset, const FixupInfo& fixup) {
+ BNRelocationInfo reloc{};
+ reloc.size = m_addressSize;
+ reloc.nativeType = BINARYNINJA_MANUAL_RELOCATION;
+ reloc.address = GetStart() + offset;
- uint8_t strideSize;
- ChainedFixupPointerGeneric format;
+ if (fixup.type == FixupType::Rebase) {
+ reloc.type = StandardRelocationType;
+ DefineRelocation(m_arch, reloc, GetStart() + fixup.rebase.target, reloc.address);
- // Firmware formats will require digging up whatever place they're being used and reversing it.
- // They are not handled by dyld.
- switch (starts.pointer_format) {
- case DYLD_CHAINED_PTR_ARM64E:
- case DYLD_CHAINED_PTR_ARM64E_USERLAND:
- case DYLD_CHAINED_PTR_ARM64E_USERLAND24:
- strideSize = 8;
- format = GenericArm64eFixupFormat;
- break;
- case DYLD_CHAINED_PTR_ARM64E_KERNEL:
- strideSize = 4;
- format = GenericArm64eFixupFormat;
- break;
- // case DYLD_CHAINED_PTR_ARM64E_FIRMWARE: Unsupported.
- case DYLD_CHAINED_PTR_64:
- case DYLD_CHAINED_PTR_64_OFFSET:
- strideSize = 4;
- format = Generic64FixupFormat;
- break;
- case DYLD_CHAINED_PTR_32:
- case DYLD_CHAINED_PTR_32_CACHE:
- strideSize = 4;
- format = Generic32FixupFormat;
- break;
- case DYLD_CHAINED_PTR_32_FIRMWARE:
- strideSize = 4;
- format = Firmware32FixupFormat;
- break;
- case DYLD_CHAINED_PTR_64_KERNEL_CACHE:
- strideSize = 4;
- format = Kernel64Format;
- break;
- case DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE:
- strideSize = 1;
- format = Kernel64Format;
- break;
- default:
- {
- m_logger->LogErrorF("Chained Fixups: Unknown or unsupported pointer format {}, "
- "unable to process chains for segment at @ {:#x}", starts.pointer_format, starts.segment_offset);
- continue;
- }
- }
-
- uint16_t fmt = starts.pointer_format;
- m_logger->LogDebugF("Chained Fixups: Segment start @ {:#x}, fmt {}", starts.segment_offset, fmt);
-
- uint64_t pageStartsTableStartAddress = parentReader.GetOffset();
- vector<vector<uint16_t>> pageStartOffsets {};
- for (size_t i = 0; i < starts.page_count; i++)
- {
- // On armv7, Chained pointers here can have multiple starts.
- // And if so, there's another table *overlapping* the table we're currently reading.
- // dyld handles this through 'overflow indexing'
- // This is technically supported on other archs however is not (currently) used.
- parentReader.Seek(pageStartsTableStartAddress + (sizeof(uint16_t) * i));
- uint16_t start = parentReader.Read16();
- if ((start & DYLD_CHAINED_PTR_START_MULTI) && (start != DYLD_CHAINED_PTR_START_NONE))
- {
- uint64_t overflowIndex = start & ~DYLD_CHAINED_PTR_START_MULTI;
- vector<uint16_t> pageStartSubStarts;
- parentReader.Seek(pageStartsTableStartAddress + (overflowIndex * sizeof(uint16_t)));
- bool done = false;
- while (!done)
- {
- uint16_t subPageStart = parentReader.Read16();
- if ((subPageStart & DYLD_CHAINED_PTR_START_LAST) == 0)
- {
- pageStartSubStarts.push_back(subPageStart);
- }
- else
- {
- pageStartSubStarts.push_back(subPageStart & ~DYLD_CHAINED_PTR_START_LAST);
- done = true;
- }
- }
- pageStartOffsets.push_back(pageStartSubStarts);
- }
- else
- {
- pageStartOffsets.push_back({start});
+ if (objcProcessor)
+ objcProcessor->AddRelocatedPointer(reloc.address, GetStart() + fixup.rebase.target);
+ } else {
+ if (fixup.bind.ordinal >= importsTable.size()) {
+ m_logger->LogWarnF("Chained Fixups: Import ordinal {} out of bounds (max {})", fixup.bind.ordinal, importsTable.size());
+ return;
}
- }
-
- int i = -1;
- for (auto pageStarts : pageStartOffsets)
- {
- i++;
- uint64_t pageAddress = m_universalImageOffset + starts.segment_offset + (i * starts.page_size);
- for (uint16_t start : pageStarts)
- {
- if (start == DYLD_CHAINED_PTR_START_NONE)
- continue;
- uint64_t chainEntryAddress = pageAddress + start;
-
- bool fixupsDone = false;
-
- while (!fixupsDone)
- {
- ChainedFixupPointer pointer;
- parentReader.Seek(chainEntryAddress);
- mappedReader.Seek(chainEntryAddress - m_universalImageOffset + GetStart());
- if (format == Generic32FixupFormat || format == Firmware32FixupFormat)
- pointer.raw32 = (uint32_t)(uintptr_t)mappedReader.Read32();
- else
- pointer.raw64 = (uintptr_t)mappedReader.Read64();
-
- bool bind = false;
- uint64_t nextEntryStrideCount;
-
- switch (format)
- {
- case Generic32FixupFormat:
- bind = pointer.generic32.bind.bind;
- nextEntryStrideCount = pointer.generic32.rebase.next;
- break;
- case Generic64FixupFormat:
- bind = pointer.generic64.bind.bind;
- nextEntryStrideCount = pointer.generic64.rebase.next;
- break;
- case GenericArm64eFixupFormat:
- bind = pointer.arm64e.bind.bind;
- nextEntryStrideCount = pointer.arm64e.rebase.next;
- break;
- case Firmware32FixupFormat:
- nextEntryStrideCount = pointer.firmware32.next;
- bind = false;
- break;
- case Kernel64Format:
- nextEntryStrideCount = pointer.kernel64.next;
- bind = false;
- break;
- }
-
- m_logger->LogTraceF("Chained Fixups: @ {:#x} ( {:#x} ) - {} {:#x}", chainEntryAddress,
- GetStart() + (chainEntryAddress - m_universalImageOffset),
- bind, nextEntryStrideCount);
-
- if (bind && processBinds)
- {
- uint64_t ordinal;
-
- switch (starts.pointer_format)
- {
- case DYLD_CHAINED_PTR_64:
- case DYLD_CHAINED_PTR_64_OFFSET:
- ordinal = pointer.generic64.bind.ordinal;
- break;
- // case DYLD_CHAINED_PTR_ARM64E_OFFSET: ; old _KERNEL name.
- case DYLD_CHAINED_PTR_ARM64E:
- case DYLD_CHAINED_PTR_ARM64E_USERLAND24:
- case DYLD_CHAINED_PTR_ARM64E_KERNEL:
- if (pointer.arm64e.bind.auth)
- ordinal = starts.pointer_format == DYLD_CHAINED_PTR_ARM64E_USERLAND24
- ? pointer.arm64e.authBind24.ordinal : pointer.arm64e.authBind.ordinal;
- else
- ordinal = starts.pointer_format == DYLD_CHAINED_PTR_ARM64E_USERLAND24
- ? pointer.arm64e.bind24.ordinal : pointer.arm64e.bind.ordinal;
- break;
- case DYLD_CHAINED_PTR_32:
- ordinal = pointer.generic32.bind.ordinal;
- break;
- case DYLD_CHAINED_PTR_64_KERNEL_CACHE: // no binding
- case DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE: // ''
- default:
- m_logger->LogWarnF("Chained Fixups: Unknown Bind Pointer Format at {:#x}",
- GetStart() + (chainEntryAddress - m_universalImageOffset));
-
- chainEntryAddress += (nextEntryStrideCount * strideSize);
- if (chainEntryAddress > pageAddress + starts.page_size)
- {
- m_logger->LogErrorF("Chained Fixups: Pointer at {:#x} left page",
- GetStart() + ((chainEntryAddress - (nextEntryStrideCount * strideSize))) - m_universalImageOffset);
- fixupsDone = true;
- }
- if (nextEntryStrideCount == 0)
- fixupsDone = true;
-
- continue;
- }
-
- if (ordinal < importTable.size())
- {
- import_entry entry = importTable.at(ordinal);
- uint64_t targetAddress = GetStart() + (chainEntryAddress - m_universalImageOffset);
-
- if (!entry.name.empty())
- {
- reloc.address = targetAddress;
-
- BNRelocationInfo externReloc;
- memset(&externReloc, 0, sizeof(externReloc));
- externReloc.nativeType = BINARYNINJA_MANUAL_RELOCATION;
- externReloc.address = targetAddress;
- externReloc.size = m_addressSize;
- externReloc.pcRelative = false;
- externReloc.addend = entry.addend;
- header.bindingRelocations.emplace_back(externReloc, entry.name, entry.lib_ordinal);
- }
- else
- {
- m_logger->LogWarnF("Chained Fixups: Import Table entry {:#x} has no symbol; "
- "Unable to bind item at {:#x}", ordinal, targetAddress);
- }
- }
- }
- else if (!bind)
- {
- uint64_t entryOffset;
- switch (starts.pointer_format)
- {
- case DYLD_CHAINED_PTR_ARM64E:
- case DYLD_CHAINED_PTR_ARM64E_KERNEL:
- case DYLD_CHAINED_PTR_ARM64E_USERLAND:
- case DYLD_CHAINED_PTR_ARM64E_USERLAND24:
- {
- if (pointer.arm64e.bind.auth)
- entryOffset = pointer.arm64e.authRebase.target;
- else
- entryOffset = pointer.arm64e.rebase.target;
-
- if ( starts.pointer_format != DYLD_CHAINED_PTR_ARM64E || pointer.arm64e.bind.auth)
- entryOffset += GetStart();
-
- break;
- }
- case DYLD_CHAINED_PTR_64:
- entryOffset = pointer.generic64.rebase.target;
- break;
- case DYLD_CHAINED_PTR_64_OFFSET:
- entryOffset = pointer.generic64.rebase.target + GetStart();
- break;
- case DYLD_CHAINED_PTR_64_KERNEL_CACHE:
- case DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE:
- entryOffset = pointer.kernel64.target;
- break;
- case DYLD_CHAINED_PTR_32:
- case DYLD_CHAINED_PTR_32_CACHE:
- entryOffset = pointer.generic32.rebase.target;
- break;
- case DYLD_CHAINED_PTR_32_FIRMWARE:
- entryOffset = pointer.firmware32.target;
- break;
- }
-
- reloc.address = GetStart() + (chainEntryAddress - m_universalImageOffset);
- DefineRelocation(m_arch, reloc, entryOffset, reloc.address);
-
- if (objcProcessor)
- {
- objcProcessor->AddRelocatedPointer(reloc.address, entryOffset);
- }
- }
-
- chainEntryAddress += (nextEntryStrideCount * strideSize);
-
- if (chainEntryAddress > pageAddress + starts.page_size)
- {
- // Something is seriously wrong here. likely malformed binary, or our parsing failed elsewhere.
- // This will log the pointer in mapped memory.
- m_logger->LogErrorF("Chained Fixups: Pointer at {:#x} left page",
- GetStart() + ((chainEntryAddress - (nextEntryStrideCount * strideSize))) - m_universalImageOffset);
- fixupsDone = true;
- }
-
- if (nextEntryStrideCount == 0)
- fixupsDone = true;
- }
- }
+ const auto& import = importsTable[fixup.bind.ordinal];
+ reloc.type = ELFGlobalRelocationType;
+ reloc.addend = import.addend + fixup.bind.addend;
+ header.bindingRelocations.emplace_back(reloc, std::string(import.name), import.libraryOrdinal);
}
- }
- }
- catch (ReadException&)
- {
- m_logger->LogError("Chained Fixup parsing failed");
+ });
+ } catch (std::exception& e) {
+ m_logger->LogErrorForExceptionF(e, "Failed to parse chained fixups: {}", e.what());
}
}
@@ -3700,6 +3320,9 @@ void MachoView::ParseChainedStarts(MachOHeader& header, section_64 chainedStarts
if (!chainedStarts.offset)
return;
+ // TODO: Share code with ChainedFixupProcessor.
+ // TODO: Distinguish between `-fixup_chains_section` and `-fixup_chains_section_vm`.
+
m_logger->LogDebug("Processing Chained Starts");
// Dummy relocation
diff --git a/view/macho/machoview.h b/view/macho/machoview.h
index d9c02b08..f41cd8bd 100644
--- a/view/macho/machoview.h
+++ b/view/macho/machoview.h
@@ -1132,6 +1132,8 @@ namespace BinaryNinja
DYLD_CHAINED_PTR_ARM64E_FIRMWARE = 10, // stride 4, unauth target is vmaddr
DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE = 11, // stride 1, x86_64 kernel caches
DYLD_CHAINED_PTR_ARM64E_USERLAND24 = 12, // stride 8, unauth target is vm offset, 24-bit bind
+ DYLD_CHAINED_PTR_ARM64E_SHARED_CACHE = 13, // stride 8, regular/auth targets both vm offsets. Only A keys supported
+ DYLD_CHAINED_PTR_ARM64E_SEGMENTED = 14, // stride 4, rebase offsets use segIndex and segOffset
};
// DYLD_CHAINED_PTR_ARM64E