From 2f531f7279a01810494cdde6342434077456edf0 Mon Sep 17 00:00:00 2001 From: Mark Rowe Date: Sun, 21 Sep 2025 17:33:20 -0700 Subject: [PATCH] [Mach-O] Refactor fixup chain parsing The parsing of fix-up chains is moved out of `MachoView` and into its own class. It deals purely in terms of offsets into the Mach-O slice. `MachoView` translates those offsets to mapped addresses when needed. This is primarily aimed at fixing incorrect handling of pointer formats that use offsets where in some cases the relocations would be applied at incorrect addresses due to confusion between file offsets, Mach-O slice offsets, and VM offsets. It incidentally fixes addends from bind operations not being respected. These show up most frequently in C++ RTTI information. --- view/macho/chained_fixups.cpp | 528 ++++++++++++++++++++++++++++++++++ view/macho/chained_fixups.h | 115 ++++++++ view/macho/machoview.cpp | 459 +++-------------------------- view/macho/machoview.h | 2 + 4 files changed, 686 insertions(+), 418 deletions(-) create mode 100644 view/macho/chained_fixups.cpp create mode 100644 view/macho/chained_fixups.h diff --git a/view/macho/chained_fixups.cpp b/view/macho/chained_fixups.cpp new file mode 100644 index 0000000000..682f561c33 --- /dev/null +++ b/view/macho/chained_fixups.cpp @@ -0,0 +1,528 @@ +#include "chained_fixups.h" + +#include "view/macho/machoview.h" +#include + +// 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 : fmt::formatter +{ + template + 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::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(*)(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 raw = reader.Read64(); + return {raw, ParseFixup(ChainedFixupPointer{ .raw64 = raw }.arm64e) }; + }; + case DYLD_CHAINED_PTR_ARM64E_USERLAND24: + return +[](BinaryReader& reader) -> std::pair { + 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 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 { + uint32_t raw = reader.Read32(); + return {static_cast(raw), ParseFixup(ChainedFixupPointer{ .raw32 = raw }.generic32) }; + }; + case DYLD_CHAINED_PTR_32_CACHE: + return +[](BinaryReader& reader) -> std::pair { + uint32_t raw = reader.Read32(); + return {static_cast(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 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 symbolData) +{ + dyld_chained_import_addend import; + reader.Read(&import, sizeof(import)); + return { + std::string_view(&symbolData[import.name_offset]), + static_cast(import.addend), + import.lib_ordinal, + (bool)import.weak_import, + }; +} + +ImportEntry ReadChainedImportAddend64(BinaryReader& reader, std::span 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 ChainedFixupProcessor::ProcessImports() const +{ + std::vector 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 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 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> ChainedFixupProcessor::ReadChainStartsInSegment(const dyld_chained_starts_in_segment& segment, BinaryReader& reader) const +{ + uint64_t chainStartsOffset = reader.GetOffset(); + std::vector> 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 0000000000..9a26a1f5cd --- /dev/null +++ b/view/macho/chained_fixups.h @@ -0,0 +1,115 @@ +#pragma once + +#include "machoview.h" + +#include +#include +#include +#include + +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 raw, Ref logger, uint64_t machOStartOffset, uint64_t preferredLoadAddress, + const linkedit_data_command& chainedFixupCommand, std::unordered_map 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 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 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> 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 m_raw; + Ref 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 m_segmentVMAddrToFileOffset; + + mutable std::function m_fixupHandler; + mutable std::vector m_symbolData; +}; + +} // namespace BinaryNinja diff --git a/view/macho/machoview.cpp b/view/macho/machoview.cpp index 5a3da11ba1..3b6fb74db4 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 #include #include @@ -8,11 +16,6 @@ #ifndef _MSC_VER #include #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) + std::unordered_map segmentVMAddrToFileOffset; + segmentVMAddrToFileOffset.reserve(m_allSegments.size()); + for (const auto& segment : m_allSegments) { - m_logger->LogError("Chained Fixup parsing failed. Binary is malformed"); - return; + // 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; } - 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 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(&importEntry)); - processChainedImport(static_cast(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(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(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 segInfoOffsets {}; - for (size_t i = 0; i < segs.seg_count; i++) - { - segInfoOffsets.push_back(parentReader.Read32()); - } - 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(); - - uint8_t strideSize; - ChainedFixupPointerGeneric format; - - // 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; - } - } + 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; - uint16_t fmt = starts.pointer_format; - m_logger->LogDebugF("Chained Fixups: Segment start @ {:#x}, fmt {}", starts.segment_offset, fmt); + if (fixup.type == FixupType::Rebase) { + reloc.type = StandardRelocationType; + DefineRelocation(m_arch, reloc, GetStart() + fixup.rebase.target, reloc.address); - uint64_t pageStartsTableStartAddress = parentReader.GetOffset(); - vector> 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 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 d9c02b0846..f41cd8bdfa 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