diff options
| -rw-r--r-- | view/macho/chained_fixups.cpp | 528 | ||||
| -rw-r--r-- | view/macho/chained_fixups.h | 115 | ||||
| -rw-r--r-- | view/macho/machoview.cpp | 459 | ||||
| -rw-r--r-- | view/macho/machoview.h | 2 |
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 |
