#include "machoview.h" #include "chained_fixups.h" #include "fatmachoview.h" #include "lowlevelilinstruction.h" #include "rapidjsonwrapper.h" #include "universaltransform.h" #include "universalview.h" #include #include #include #include #include #include #include #ifndef _MSC_VER #include #endif using namespace BinaryNinja; using namespace std; static MachoViewType* g_machoViewType = nullptr; namespace { // Pseudo-library names used when an import's two-level-namespace ordinal refers to a special // dyld lookup mode rather than a concrete LC_LOAD_DYLIB entry. constexpr std::string_view kPseudoLibraryMainExecutable = "
"; constexpr std::string_view kPseudoLibraryFlatLookup = ""; constexpr std::string_view kPseudoLibraryWeakLookup = ""; string CommandToString(uint32_t lcCommand) { switch(lcCommand) { case LC_REQ_DYLD: return "LC_REQ_DYLD"; case LC_SEGMENT: return "LC_SEGMENT"; case LC_SYMTAB: return "LC_SYMTAB"; case LC_SYMSEG: return "LC_SYMSEG"; case LC_THREAD: return "LC_THREAD"; case LC_UNIXTHREAD: return "LC_UNIXTHREAD"; case LC_LOADFVMLIB: return "LC_LOADFVMLIB"; case LC_IDFVMLIB: return "LC_IDFVMLIB"; case LC_IDENT: return "LC_IDENT"; case LC_FVMFILE: return "LC_FVMFILE"; case LC_PREPAGE: return "LC_PREPAGE"; case LC_DYSYMTAB: return "LC_DYSYMTAB"; case LC_LOAD_DYLIB: return "LC_LOAD_DYLIB"; case LC_ID_DYLIB: return "LC_ID_DYLIB"; case LC_LOAD_DYLINKER: return "LC_LOAD_DYLINKER"; case LC_ID_DYLINKER: return "LC_ID_DYLINKER"; case LC_PREBOUND_DYLIB: return "LC_PREBOUND_DYLIB"; case LC_ROUTINES: return "LC_ROUTINES"; case LC_SUB_FRAMEWORK: return "LC_SUB_FRAMEWORK"; case LC_SUB_UMBRELLA: return "LC_SUB_UMBRELLA"; case LC_SUB_CLIENT: return "LC_SUB_CLIENT"; case LC_SUB_LIBRARY: return "LC_SUB_LIBRARY"; case LC_TWOLEVEL_HINTS: return "LC_TWOLEVEL_HINTS"; case LC_PREBIND_CKSUM: return "LC_PREBIND_CKSUM"; case LC_LOAD_WEAK_DYLIB: return "LC_LOAD_WEAK_DYLIB"; case LC_SEGMENT_64: return "LC_SEGMENT_64"; case LC_ROUTINES_64: return "LC_ROUTINES_64"; case LC_UUID: return "LC_UUID"; case LC_RPATH: return "LC_RPATH"; case LC_CODE_SIGNATURE: return "LC_CODE_SIGNATURE"; case LC_SEGMENT_SPLIT_INFO: return "LC_SEGMENT_SPLIT_INFO"; case LC_REEXPORT_DYLIB: return "LC_REEXPORT_DYLIB"; case LC_LAZY_LOAD_DYLIB: return "LC_LAZY_LOAD_DYLIB"; case LC_ENCRYPTION_INFO: return "LC_ENCRYPTION_INFO"; case LC_DYLD_INFO: return "LC_DYLD_INFO"; case LC_DYLD_INFO_ONLY: return "LC_DYLD_INFO_ONLY"; case LC_LOAD_UPWARD_DYLIB: return "LC_LOAD_UPWARD_DYLIB"; case LC_VERSION_MIN_MACOSX: return "LC_VERSION_MIN_MACOSX"; case LC_VERSION_MIN_IPHONEOS: return "LC_VERSION_MIN_IPHONEOS"; case LC_FUNCTION_STARTS: return "LC_FUNCTION_STARTS"; case LC_DYLD_ENVIRONMENT: return "LC_DYLD_ENVIRONMENT"; case LC_MAIN: return "LC_MAIN"; case LC_DATA_IN_CODE: return "LC_DATA_IN_CODE"; case LC_SOURCE_VERSION: return "LC_SOURCE_VERSION"; case LC_DYLIB_CODE_SIGN_DRS: return "LC_DYLIB_CODE_SIGN_DRS"; case _LC_ENCRYPTION_INFO_64: return "LC_ENCRYPTION_INFO_64"; case _LC_LINKER_OPTION: return "LC_LINKER_OPTION"; case _LC_LINKER_OPTIMIZATION_HINT: return "LC_LINKER_OPTIMIZATION_HINT"; case _LC_VERSION_MIN_TVOS: return "LC_VERSION_MIN_TVOS"; case LC_VERSION_MIN_WATCHOS: return "LC_VERSION_MIN_WATCHOS"; case LC_NOTE: return "LC_NOTE"; case LC_BUILD_VERSION: return "LC_BUILD_VERSION"; case LC_DYLD_EXPORTS_TRIE: return "LC_DYLD_EXPORTS_TRIE"; case LC_DYLD_CHAINED_FIXUPS: return "LC_DYLD_CHAINED_FIXUPS"; default: { stringstream ss; ss << "0x" << std::hex << lcCommand; return ss.str(); } } } string BuildPlatformToString(uint32_t platform) { switch (platform) { case MACHO_PLATFORM_MACOS: return "macos"; case MACHO_PLATFORM_IOS: return "ios"; case MACHO_PLATFORM_TVOS: return "tvos"; case MACHO_PLATFORM_WATCHOS: return "watchos"; case MACHO_PLATFORM_BRIDGEOS: return "bridgeos"; default: { stringstream ss; ss << "0x" << std::hex << platform; return ss.str(); } } } string BuildToolToString(uint32_t tool) { switch (tool) { case MACHO_TOOL_CLANG: return "clang"; case MACHO_TOOL_SWIFT: return "swift"; case MACHO_TOOL_LD: return "ld"; default: { stringstream ss; ss << "0x" << std::hex << tool; return ss.str(); } } } string BuildToolVersionToString(uint32_t version) { uint32_t major = (version >> 16) & 0xffff; uint32_t minor = (version >> 8) & 0xff; uint32_t update = version & 0xff; stringstream ss; ss << major << "." << minor << "." << update; return ss.str(); } int64_t readSLEB128(DataBuffer& buffer, size_t length, size_t &offset) { uint8_t cur; int64_t value = 0; size_t shift = 0; while (offset < length) { cur = buffer[offset++]; value |= (cur & 0x7f) << shift; shift += 7; if ((cur & 0x80) == 0) break; } value = (value << (64 - shift)) >> (64 - shift); return value; } uint64_t readLEB128(DataBuffer& p, size_t end, size_t &offset) { uint64_t result = 0; int bit = 0; do { if (offset >= end) return -1; uint64_t slice = p[offset] & 0x7f; if (bit > 63) return -1; else { result |= (slice << bit); bit += 7; } } while (p[offset++] & 0x80); return result; } uint64_t readValidULEB128(DataBuffer& buffer, size_t& cursor) { uint64_t value = readLEB128(buffer, buffer.GetLength(), cursor); if ((int64_t)value == -1) throw ReadException(); return value; } void CollectSectionByType(MachOHeader& header, section_64& sect) { uint32_t sectionType = sect.flags & SECTION_TYPE; switch (sectionType) { case S_MOD_INIT_FUNC_POINTERS: case S_INIT_FUNC_OFFSETS: header.moduleInitSections.push_back(sect); break; case S_SYMBOL_STUBS: if (sect.flags & S_ATTR_SELF_MODIFYING_CODE) header.symbolStubSections.push_back(sect); break; case S_NON_LAZY_SYMBOL_POINTERS: case S_LAZY_SYMBOL_POINTERS: header.symbolPointerSections.push_back(sect); break; case S_REGULAR: // Fallback: kext bundles may use S_REGULAR for __got if (strncmp(sect.sectname, "__got", 16) == 0) header.symbolPointerSections.push_back(sect); break; } } // TODO: This logic for determining semantics for XNU segments is duplicated in kernelcache. // Protection combinations used in XNU. Named to match the conventions in arm_vm_init.c constexpr uint32_t PROT_RNX = SegmentReadable | SegmentContainsData | SegmentDenyWrite | SegmentDenyExecute; constexpr uint32_t PROT_ROX = SegmentReadable | SegmentExecutable | SegmentContainsCode | SegmentDenyWrite; constexpr uint32_t PROT_RWNX = SegmentReadable | SegmentWritable | SegmentContainsData | SegmentDenyExecute; struct XNUSegmentProtection { std::string_view name; uint32_t protection; }; // Protections taken from arm_vm_prot_init at // https://github.com/apple-oss-distributions/xnu/blob/xnu-12377.1.9/osfmk/arm64/arm_vm_init.c constexpr std::array s_initialSegmentProtections = {{ // Core XNU Kernel Segments {"__TEXT", PROT_RNX}, {"__TEXT_EXEC", PROT_ROX}, {"__DATA_CONST", PROT_RWNX}, {"__DATA", PROT_RWNX}, {"__HIB", PROT_RWNX}, {"__BOOTDATA", PROT_RWNX}, {"__KLD", PROT_ROX}, {"__KLDDATA", PROT_RNX}, {"__LINKEDIT", PROT_RWNX}, {"__LAST", PROT_ROX}, {"__LASTDATA_CONST", PROT_RWNX}, // Prelinked Kext Segments {"__PRELINK_TEXT", PROT_RWNX}, {"__PLK_DATA_CONST", PROT_RWNX}, {"__PLK_TEXT_EXEC", PROT_ROX}, {"__PRELINK_DATA", PROT_RWNX}, {"__PLK_LINKEDIT", PROT_RWNX}, {"__PRELINK_INFO", PROT_RWNX}, {"__PLK_LLVM_COV", PROT_RWNX}, // PPL (Page Protection Layer) Segments {"__PPLTEXT", PROT_ROX}, {"__PPLTRAMP", PROT_ROX}, {"__PPLDATA_CONST", PROT_RNX}, {"__PPLDATA", PROT_RWNX}, }}; std::string FormatSegmentFlags(uint32_t flags) { std::string perms; perms += (flags & SegmentReadable) ? 'R' : '-'; perms += (flags & SegmentWritable) ? 'W' : '-'; perms += (flags & SegmentExecutable) ? 'X' : '-'; std::string type; if (flags & SegmentContainsCode) type = " [CODE]"; else if (flags & SegmentContainsData) type = " [DATA]"; std::string denies; if (flags & SegmentDenyWrite) denies += 'W'; if (flags & SegmentDenyExecute) denies += 'X'; if (!denies.empty()) denies = fmt::format(" (deny:{})", denies); return fmt::format("{}{}{}", perms, type, denies); } // XNU maps certain segments with specific protections regardless of what is in the load command. uint32_t SegmentFlagsForKnownXNUSegment(std::string_view segmentName) { for (const auto& entry : s_initialSegmentProtections) { if (segmentName == entry.name) return entry.protection; } return 0; } uint32_t SegmentFlagsFromMachOProtections(int initProt, int maxProt) { uint32_t flags = 0; if (initProt & MACHO_VM_PROT_READ) flags |= SegmentReadable; if (initProt & MACHO_VM_PROT_WRITE) flags |= SegmentWritable; if (initProt & MACHO_VM_PROT_EXECUTE) flags |= SegmentExecutable; if ((initProt & MACHO_VM_PROT_WRITE) == 0 && (maxProt & MACHO_VM_PROT_WRITE) == 0) flags |= SegmentDenyWrite; if ((initProt & MACHO_VM_PROT_EXECUTE) == 0 && (maxProt & MACHO_VM_PROT_EXECUTE) == 0) flags |= SegmentDenyExecute; return static_cast(flags); } // Determine segment flags for Mach-O segments, applying XNU overrides if necessary. uint32_t SegmentFlagsForSegment(bool isXNU, const segment_command_64& segment) { std::string_view segmentName(segment.segname, std::find(segment.segname, std::end(segment.segname), '\0')); uint32_t flagsFromLoadCommand = SegmentFlagsFromMachOProtections(segment.initprot, segment.maxprot); if (!isXNU) return flagsFromLoadCommand; if (uint32_t flagsFromKnownXNUSegment = SegmentFlagsForKnownXNUSegment(segmentName)) { constexpr int MASK = ~(SegmentContainsData | SegmentContainsCode); if ((flagsFromKnownXNUSegment & MASK) != (flagsFromLoadCommand & MASK)) LogDebugF("Overriding segment protections from load command ({}) with known segment protections {} for segment {} ({:#x} - {:#x})", FormatSegmentFlags(flagsFromLoadCommand), FormatSegmentFlags(flagsFromKnownXNUSegment), segmentName, segment.vmaddr, segment.vmaddr + segment.vmsize); return flagsFromKnownXNUSegment; } return flagsFromLoadCommand; } // Determine overridden section semantics for XNU mapped segments. // Returns 0 if no overrides are necessary (not XNU or no overrides for the segment). uint32_t OverriddenSectionSemanticsForSection(bool isXNU, const section_64& section) { if (!isXNU) return 0; std::string_view segmentName(section.segname, std::find(section.segname, std::end(section.segname), '\0')); int flags = SegmentFlagsForKnownXNUSegment(segmentName); if (!flags) return 0; if (flags & SegmentExecutable) return ReadOnlyCodeSectionSemantics; if (flags & SegmentWritable) return ReadWriteDataSectionSemantics; return ReadOnlyDataSectionSemantics; } } // unnamed namespace void BinaryNinja::InitMachoViewType() { static MachoViewType type; BinaryViewType::Register(&type); g_machoViewType = &type; } MachoView::MachoView(const string& typeName, BinaryView* data, bool parseOnly): BinaryView(typeName, data->GetFile(), data), m_universalImageOffset(0), m_parseOnly(parseOnly) { CreateLogger("BinaryView"); m_logger = CreateLogger("BinaryView.MachoView"); m_backedByDatabase = data->GetFile()->IsBackedByDatabase(typeName); Ref universalViewType = BinaryViewType::GetByName("Universal"); bool isUniversal = (universalViewType && universalViewType->IsTypeValidForData(data)); Ref settings = data->GetLoadSettings(typeName); if (settings && settings->Contains("loader.macho.universalImageOffset")) { settings->SetResourceId(typeName); m_universalImageOffset = settings->Get("loader.macho.universalImageOffset", data); } else if (isUniversal) { Ref loadSettings = universalViewType->GetLoadSettingsForData(data); if (loadSettings && loadSettings->Contains("loader.universal.architectures")) { string json = loadSettings->Get("loader.universal.architectures"); rapidjson::Document jsonArchitectures; jsonArchitectures.Parse(json.data(), json.size()); if (!json.size() || jsonArchitectures.HasParseError()) throw MachoFormatException("Mach-O view could not be created! Json parse error."); if (!jsonArchitectures.IsArray() || jsonArchitectures.Empty()) throw MachoFormatException("Mach-O view could not be created! Json data error."); // Select the object file based on architecture preference. // Note: This code is duplicated in options.cpp vector architectures; for (const auto& entry : jsonArchitectures.GetArray()) architectures.push_back(entry["architecture"].GetString()); auto archPref = Settings::Instance()->Get>("files.universal.architecturePreference"); int archIndex = 0; if (auto result = find_first_of(archPref.begin(), archPref.end(), architectures.begin(), architectures.end()); result != archPref.end()) archIndex = std::find(architectures.begin(), architectures.end(), *result) - architectures.begin(); const auto& archEntry = jsonArchitectures[archIndex]; loadSettings = Settings::Instance(GetUniqueIdentifierString()); loadSettings->SetResourceId(typeName); loadSettings->DeserializeSchema(archEntry["loadSchema"].GetString()); data->SetLoadSettings(typeName, loadSettings); if (!m_file->IsBackedByDatabase(typeName)) { auto defaultSettings = loadSettings->SerializeSettings(Ref(), SettingsDefaultScope); loadSettings->DeserializeSettings(defaultSettings, data, SettingsResourceScope); } m_universalImageOffset = loadSettings->Get("loader.macho.universalImageOffset", data); // NOTE: this silences the 'Updated load schema detected' warning. There is some chicken/egg stuff here. loadSettings->UpdateProperty("loader.macho.universalImageOffset", "default", m_universalImageOffset); } } m_header = HeaderForAddress(data, 0, true); } MachOHeader MachoView::HeaderForAddress(BinaryView* data, uint64_t address, bool isMainHeader, std::string identifierPrefix) { MachOHeader header{}; header.isMainHeader = isMainHeader; header.identifierPrefix = identifierPrefix; std::string errorMsg; if (isMainHeader) { header.loadCommandOffset = g_machoViewType->ParseHeaders(data, m_universalImageOffset + address, header.ident, &m_arch, &m_plat, errorMsg); if (!header.loadCommandOffset) throw MachoFormatException(errorMsg); } else { // address is a Raw file offset BinaryReader subReader(data); subReader.Seek(address); header.ident.magic = subReader.Read32(); BNEndianness endianness; if (header.ident.magic == MH_MAGIC || header.ident.magic == MH_MAGIC_64) endianness = LittleEndian; else if (header.ident.magic == MH_CIGAM || header.ident.magic == MH_CIGAM_64) endianness = BigEndian; else { throw ReadException(); } subReader.SetEndianness(endianness); header.ident.cputype = subReader.Read32(); header.ident.cpusubtype = subReader.Read32(); header.ident.filetype = subReader.Read32(); header.ident.ncmds = subReader.Read32(); header.ident.sizeofcmds = subReader.Read32(); header.ident.flags = subReader.Read32(); if ((header.ident.cputype & MachOABIMask) == MachOABI64) // address size == 8 { header.ident.reserved = subReader.Read32(); } header.loadCommandOffset = subReader.GetOffset(); } if (isMainHeader) { if (header.ident.magic == MH_MAGIC || header.ident.magic == MH_MAGIC_64) { m_endian = LittleEndian; m_logger->LogDebug("Recognized Little Endian Mach-O"); } else // (header.ident.magic == MH_CIGAM || header.ident.magic == MH_CIGAM_64) { m_endian = BigEndian; m_logger->LogDebug("Recognized Big Endian Mach-O"); } } BinaryReader reader(data); reader.SetEndianness(m_endian); reader.SetVirtualBase(m_universalImageOffset); reader.Seek(header.loadCommandOffset); if (isMainHeader) { m_objectFile = header.ident.filetype == MH_OBJECT; m_dylibFile = header.ident.filetype == MH_DYLIB; m_relocatable = ((header.ident.flags & MH_PIE) > 0) || m_objectFile || m_dylibFile; m_archId = header.ident.cputype; m_addressSize = (header.ident.cputype & MachOABIMask) == MachOABI64 ? 8 : 4; } bool first = true; // Parse segment commands try { m_logger->LogDebugF("ident.ncmds: {}", header.ident.ncmds); for (size_t i = 0; i < header.ident.ncmds; i++) { load_command load; segment_command_64 segment64; section_64 sect; memset(§, 0, sizeof(sect)); size_t curOffset = reader.GetOffset(); load.cmd = reader.Read32(); load.cmdsize = reader.Read32(); size_t nextOffset = curOffset + load.cmdsize; m_logger->LogDebugF("Segment cmd: {:08x} - cmdsize: {:08x} - ", load.cmd, load.cmdsize); if (load.cmdsize < sizeof(load_command)) throw MachoFormatException("unable to read header"); switch (load.cmd) { case LC_MAIN: { uint64_t entryPoint = reader.Read64(); m_logger->LogDebug("LC_MAIN entryPoint: %#016" PRIx64, entryPoint); header.entryPoints.push_back({entryPoint, true}); (void)reader.Read64(); // Stack start break; } case LC_SEGMENT: //map the 32bit version to 64 bits m_logger->LogDebug("LC_SEGMENT\n"); segment64.cmd = LC_SEGMENT_64; reader.Read(&segment64.segname, 16); segment64.vmaddr = reader.Read32(); segment64.vmsize = reader.Read32(); segment64.fileoff = reader.Read32() + m_universalImageOffset; segment64.filesize = reader.Read32(); segment64.maxprot = reader.Read32(); segment64.initprot = reader.Read32(); segment64.nsects = reader.Read32(); segment64.flags = reader.Read32(); if (first) { if (!((header.ident.flags & MH_SPLIT_SEGS) || header.ident.cputype == MACHO_CPU_TYPE_X86_64) || (segment64.flags & MACHO_VM_PROT_WRITE)) { header.relocationBase = segment64.vmaddr; first = false; } } m_logger->LogDebug("\tName: %s\n" \ "\tCmd: %#08" PRIx32 "\n" \ "\tvmaddr: %#016" PRIx64 "\n" \ "\tvmsize: %#016" PRIx64 "\n" \ "\tfileoff: %#016" PRIx64 "\n" \ "\tfilesize: %#016" PRIx64 "\n" \ "\tmaxprot: %#08" PRIx32 "\n" \ "\tinitprot: %#08" PRIx32 "\n" \ "\tnsects: %#08" PRIx32 "\n" \ "\tflags: %#08" PRIx32 "\n", (char*)&segment64.segname, segment64.cmd, segment64.vmaddr, segment64.vmsize, segment64.fileoff, segment64.filesize, segment64.maxprot, segment64.initprot, segment64.nsects, segment64.flags); for (size_t j = 0; j < segment64.nsects; j++) { reader.Read(§.sectname, 16); reader.Read(§.segname, 16); sect.addr = reader.Read32(); sect.size = reader.Read32(); sect.offset = reader.Read32(); sect.align = reader.Read32(); sect.reloff = reader.Read32(); sect.nreloc = reader.Read32(); sect.flags = reader.Read32(); sect.reserved1 = reader.Read32(); sect.reserved2 = reader.Read32(); // if the segment isn't mapped into virtual memory don't add the corresponding sections. if (segment64.vmsize > 0) { header.sections.push_back(sect); m_allSections.push_back(sect); } else m_logger->LogInfo("Omitting section %16s at %#" PRIx64 " corresponding to segment %16s which is not mapped into memory", (char*)§.sectname, sect.addr, (char*)§.segname); m_logger->LogDebug("\t\tSegName: %16s\n" \ "\t\tSectName: %16s\n" \ "\t\tAddr: %#" PRIx64 "\n" \ "\t\tSize: %#" PRIx64 "\n" \ "\t\tOffset: %#" PRIx32 "\n" \ "\t\tAlign: %#" PRIx32 "\n" \ "\t\tReloff: %#" PRIx32 "\n" \ "\t\tNReloc: %#" PRIx32 "\n" \ "\t\tFlags: %#" PRIx32 "\n" \ "\t\tReserved1:%#" PRIx32 "\n" \ "\t\tReserved2:%#" PRIx32 "\n" \ "\t\t------------------------\n", (char*)§.segname, (char*)§.sectname, sect.addr, sect.size, sect.offset, sect.align, sect.reloff, sect.nreloc, sect.flags, sect.reserved1, sect.reserved2); CollectSectionByType(header, sect); } header.segments.push_back(segment64); m_allSegments.push_back(segment64); break; case LC_SEGMENT_64: m_logger->LogDebug("LC_SEGMENT_64\n"); segment64.cmd = LC_SEGMENT_64; reader.Read(&segment64.segname, 16); segment64.vmaddr = reader.Read64(); segment64.vmsize = reader.Read64(); segment64.fileoff = reader.Read64() + m_universalImageOffset; segment64.filesize = reader.Read64(); segment64.maxprot = reader.Read32(); segment64.initprot = reader.Read32(); segment64.nsects = reader.Read32(); segment64.flags = reader.Read32(); if (first) { if (!((header.ident.flags & MH_SPLIT_SEGS) || header.ident.cputype == MACHO_CPU_TYPE_X86_64) || (segment64.flags & MACHO_VM_PROT_WRITE)) { header.relocationBase = segment64.vmaddr; first = false; } } m_logger->LogDebug( "\tName: %s\n" \ "\tCmd: %#08" PRIx32 "\n" \ "\tvmaddr: %#016" PRIx64 "\n" \ "\tvmsize: %#016" PRIx64 "\n" \ "\tfileoff: %#016" PRIx64 "\n" \ "\tfilesize: %#016" PRIx64 "\n" \ "\tmaxprot: %#08" PRIx32 "\n" \ "\tinitprot: %#08" PRIx32 "\n" \ "\tnsects: %#08" PRIx32 "\n" \ "\tflags: %#08" PRIx32 "\n", (char*)&segment64.segname, segment64.cmd, segment64.vmaddr, segment64.vmsize, segment64.fileoff, segment64.filesize, segment64.maxprot, segment64.initprot, segment64.nsects, segment64.flags); m_logger->LogDebug("\t\t------------------------\n"); for (size_t j = 0; j < segment64.nsects; j++) { reader.Read(§.sectname, 16); reader.Read(§.segname, 16); sect.addr = reader.Read64(); sect.size = reader.Read64(); sect.offset = reader.Read32(); sect.align = reader.Read32(); sect.reloff = reader.Read32(); sect.nreloc = reader.Read32(); sect.flags = reader.Read32(); sect.reserved1 = reader.Read32(); sect.reserved2 = reader.Read32(); sect.reserved3 = reader.Read32(); // if the segment isn't mapped into virtual memory don't add the corresponding sections. if (segment64.vmsize > 0) { header.sections.push_back(sect); m_allSections.push_back(sect); } else m_logger->LogInfo("Omitting section %16s at %#" PRIx64 " corresponding to segment %16s which is not mapped into memory", (char*)§.sectname, sect.addr, (char*)§.segname); m_logger->LogDebug( "\t\tSegName: %16s\n" \ "\t\tSectName: %16s\n" \ "\t\tAddr: %#" PRIx64 "\n" \ "\t\tSize: %#" PRIx64 "\n" \ "\t\tOffset: %#" PRIx32 "\n" \ "\t\tAlign: %#" PRIx32 "\n" \ "\t\tReloff: %#" PRIx32 "\n" \ "\t\tNReloc: %#" PRIx32 "\n" \ "\t\tFlags: %#" PRIx32 "\n" \ "\t\tReserved1:%#" PRIx32 "\n" \ "\t\tReserved2:%#" PRIx32 "\n" \ "\t\tReserved3:%#" PRIx32 "\n" \ "\t\t------------------------\n", (char*)§.segname, (char*)§.sectname, sect.addr, sect.size, sect.offset, sect.align, sect.reloff, sect.nreloc, sect.flags, sect.reserved1, sect.reserved2, sect.reserved3); CollectSectionByType(header, sect); } header.segments.push_back(segment64); m_allSegments.push_back(segment64); break; case LC_ROUTINES: //map the 32bit version to 64bits m_logger->LogDebug("LC_REOUTINES\n"); header.routines64.cmd = LC_ROUTINES_64; header.routines64.init_address = reader.Read32(); header.routines64.init_module = reader.Read32(); header.routines64.reserved1 = reader.Read32(); header.routines64.reserved2 = reader.Read32(); header.routines64.reserved3 = reader.Read32(); header.routines64.reserved4 = reader.Read32(); header.routines64.reserved5 = reader.Read32(); header.routines64.reserved6 = reader.Read32(); header.routinesPresent = true; m_logger->LogDebug("\tinit_address: %016" PRIx64 "\n\tinit_module: %08" PRIx64 "\n", header.routines64.init_address, header.routines64.init_module); break; case LC_ROUTINES_64: m_logger->LogDebug("LC_REOUTINES_64\n"); header.routines64.cmd = LC_ROUTINES_64; header.routines64.init_address = reader.Read64(); header.routines64.init_module = reader.Read64(); header.routines64.reserved1 = reader.Read64(); header.routines64.reserved2 = reader.Read64(); header.routines64.reserved3 = reader.Read64(); header.routines64.reserved4 = reader.Read64(); header.routines64.reserved5 = reader.Read64(); header.routines64.reserved6 = reader.Read64(); header.routinesPresent = true; m_logger->LogDebug("\tinit_address: %016" PRIx64 "\n\tinit_module: %08" PRIx64 "\n", header.routines64.init_address, header.routines64.init_module); break; case LC_FUNCTION_STARTS: m_logger->LogDebug("LC_FUNCTION_STARTS\n"); header.functionStarts.funcoff = reader.Read32(); header.functionStarts.funcsize = reader.Read32(); header.functionStartsPresent = true; m_logger->LogDebug("\tFunction Starts:\n\toffset: %#08" PRIx32 "\n\tsize: %#08" PRIx32 "\n", header.functionStarts.funcoff, header.functionStarts.funcsize); break; case LC_SYMTAB: m_logger->LogDebug("LC_SYMTAB\n"); header.symtab.symoff = reader.Read32(); header.symtab.nsyms = reader.Read32(); header.symtab.stroff = reader.Read32(); header.symtab.strsize = reader.Read32(); reader.Seek(header.symtab.stroff); header.stringList.Append(reader.Read(header.symtab.strsize)); header.stringListSize = header.symtab.strsize; m_logger->LogDebug("\tstrsize: %08x\n" \ "\tstroff: %08x\n" \ "\tnsyms: %08x\n", header.symtab.strsize, header.symtab.stroff, header.symtab.nsyms); break; case LC_DYSYMTAB: m_logger->LogDebug("LC_DYSYMTAB\n"); header.dysymtab.ilocalsym = reader.Read32(); header.dysymtab.nlocalsym = reader.Read32(); header.dysymtab.iextdefsym = reader.Read32(); header.dysymtab.nextdefsym = reader.Read32(); header.dysymtab.iundefsym = reader.Read32(); header.dysymtab.nundefsym = reader.Read32(); header.dysymtab.tocoff = reader.Read32(); header.dysymtab.ntoc = reader.Read32(); header.dysymtab.modtaboff = reader.Read32(); header.dysymtab.nmodtab = reader.Read32(); header.dysymtab.extrefsymoff = reader.Read32(); header.dysymtab.nextrefsyms = reader.Read32(); header.dysymtab.indirectsymoff = reader.Read32(); header.dysymtab.nindirectsyms = reader.Read32(); header.dysymtab.extreloff = reader.Read32(); header.dysymtab.nextrel = reader.Read32(); header.dysymtab.locreloff = reader.Read32(); header.dysymtab.nlocrel = reader.Read32(); m_logger->LogDebug("\theader.dysymtab.ilocalsym 0x%08x\n"\ "\theader.dysymtab.nlocalsym 0x%08x\n"\ "\theader.dysymtab.iextdefsym 0x%08x\n"\ "\theader.dysymtab.nextdefsym 0x%08x\n"\ "\theader.dysymtab.iundefsym 0x%08x\n"\ "\theader.dysymtab.nundefsym 0x%08x\n"\ "\theader.dysymtab.tocoff 0x%08x\n"\ "\theader.dysymtab.ntoc 0x%08x\n"\ "\theader.dysymtab.modtaboff 0x%08x\n"\ "\theader.dysymtab.nmodtab 0x%08x\n"\ "\theader.dysymtab.extrefsymoff 0x%08x\n"\ "\theader.dysymtab.nextrefsyms 0x%08x\n"\ "\theader.dysymtab.indirectsymoff 0x%08x\n"\ "\theader.dysymtab.nindirectsyms 0x%08x\n"\ "\theader.dysymtab.extreloff 0x%08x\n"\ "\theader.dysymtab.nextrel 0x%08x\n"\ "\theader.dysymtab.locreloff 0x%08x\n"\ "\theader.dysymtab.nlocrel 0x%08x\n", header.dysymtab.ilocalsym, header.dysymtab.nlocalsym, header.dysymtab.iextdefsym, header.dysymtab.nextdefsym, header.dysymtab.iundefsym, header.dysymtab.nundefsym, header.dysymtab.tocoff, header.dysymtab.ntoc, header.dysymtab.modtaboff, header.dysymtab.nmodtab, header.dysymtab.extrefsymoff, header.dysymtab.nextrefsyms, header.dysymtab.indirectsymoff, header.dysymtab.nindirectsyms, header.dysymtab.extreloff, header.dysymtab.nextrel, header.dysymtab.locreloff, header.dysymtab.nlocrel); header.dysymPresent = true; break; case LC_DYLD_CHAINED_FIXUPS: m_logger->LogDebug("LC_DYLD_CHAINED_FIXUPS\n"); header.chainedFixups.dataoff = reader.Read32(); header.chainedFixups.datasize = reader.Read32(); header.chainedFixupsPresent = true; break; case LC_DYLD_INFO: case LC_DYLD_INFO_ONLY: m_logger->LogDebug("LC_DYLD_INFO\n"); header.dyldInfo.rebase_off = reader.Read32(); header.dyldInfo.rebase_size = reader.Read32(); header.dyldInfo.bind_off = reader.Read32(); header.dyldInfo.bind_size = reader.Read32(); header.dyldInfo.weak_bind_off = reader.Read32(); header.dyldInfo.weak_bind_size = reader.Read32(); header.dyldInfo.lazy_bind_off = reader.Read32(); header.dyldInfo.lazy_bind_size = reader.Read32(); header.dyldInfo.export_off = reader.Read32(); header.dyldInfo.export_size = reader.Read32(); header.exportTrie.dataoff = header.dyldInfo.export_off; header.exportTrie.datasize = header.dyldInfo.export_size; // Only mark export trie as present if there's actually data if (header.dyldInfo.export_off != 0 && header.dyldInfo.export_size != 0) header.exportTriePresent = true; header.dyldInfoPresent = true; break; case LC_DYLD_EXPORTS_TRIE: m_logger->LogDebug("LC_DYLD_EXPORTS_TRIE\n"); header.exportTrie.dataoff = reader.Read32(); header.exportTrie.datasize = reader.Read32(); // Only mark export trie as present if there's actually data if (header.exportTrie.dataoff != 0 && header.exportTrie.datasize != 0) header.exportTriePresent = true; break; case LC_THREAD: case LC_UNIXTHREAD: while (reader.GetOffset() < nextOffset) { thread_command thread; thread.flavor = reader.Read32(); thread.count = reader.Read32(); m_logger->LogDebug("LC_THREAD\\LC_UNIXTHREAD\n"); switch (m_archId) { case MachOx64: m_logger->LogDebug("x86_64 Thread state\n"); if (thread.flavor != X86_THREAD_STATE64) { reader.SeekRelative(thread.count * sizeof(uint32_t)); break; } //This wont be big endian so we can just read the whole thing reader.Read(&thread.statex64, sizeof(thread.statex64)); header.entryPoints.push_back({thread.statex64.rip, false}); break; case MachOx86: m_logger->LogDebug("x86 Thread state\n"); if (thread.flavor != X86_THREAD_STATE32) { reader.SeekRelative(thread.count * sizeof(uint32_t)); break; } //This wont be big endian so we can just read the whole thing reader.Read(&thread.statex86, sizeof(thread.statex86)); header.entryPoints.push_back({thread.statex86.eip, false}); break; case MachOArm: m_logger->LogDebug("Arm Thread state\n"); if (thread.flavor != _ARM_THREAD_STATE) { reader.SeekRelative(thread.count * sizeof(uint32_t)); break; } //This wont be big endian so we can just read the whole thing reader.Read(&thread.statearmv7, sizeof(thread.statearmv7)); header.entryPoints.push_back({thread.statearmv7.r15, false}); break; case MachOAarch64: case MachOAarch6432: m_logger->LogDebug("AArch64 Thread state\n"); if (thread.flavor != _ARM_THREAD_STATE64) { reader.SeekRelative(thread.count * sizeof(uint32_t)); break; } reader.Read(&thread.stateaarch64, sizeof(thread.stateaarch64)); header.entryPoints.push_back({thread.stateaarch64.pc, false}); break; case MachOPPC: m_logger->LogDebug("PPC Thread state\n"); if (thread.flavor != PPC_THREAD_STATE) { reader.SeekRelative(thread.count * sizeof(uint32_t)); break; } //Read individual entries for endian reasons header.entryPoints.push_back({reader.Read32(), false}); (void)reader.Read32(); (void)reader.Read32(); //Read the rest of the structure (void)reader.Read(&thread.stateppc.r1, sizeof(thread.stateppc) - (3 * 4)); break; case MachOPPC64: m_logger->LogDebug("PPC64 Thread state\n"); if (thread.flavor != PPC_THREAD_STATE64) { reader.SeekRelative(thread.count * sizeof(uint32_t)); break; } header.entryPoints.push_back({reader.Read64(), false}); (void)reader.Read64(); (void)reader.Read64(); // Stack start (void)reader.Read(&thread.stateppc64.r1, sizeof(thread.stateppc64) - (3 * 8)); break; default: m_logger->LogErrorF("Unknown archid: {:#x}", m_archId); } } break; case LC_LOAD_DYLIB: case LC_LOAD_WEAK_DYLIB: case LC_REEXPORT_DYLIB: case LC_LOAD_UPWARD_DYLIB: { uint32_t offset = reader.Read32(); reader.Read32(); // timestamp uint32_t currentVersion = reader.Read32(); if (offset < nextOffset) { reader.Seek(curOffset + offset); string libname = reader.ReadCString(); auto version = BuildToolVersionToString(currentVersion); header.dylibs.push_back({libname, version}); } } break; case LC_BUILD_VERSION: { m_logger->LogDebug("LC_BUILD_VERSION:"); header.buildVersion.platform = reader.Read32(); header.buildVersion.minos = reader.Read32(); header.buildVersion.sdk = reader.Read32(); header.buildVersion.ntools = reader.Read32(); m_logger->LogDebugF("Platform: {}", BuildPlatformToString(header.buildVersion.platform)); m_logger->LogDebugF("MinOS: {}", BuildToolVersionToString(header.buildVersion.minos)); m_logger->LogDebugF("SDK: {}", BuildToolVersionToString(header.buildVersion.sdk)); for (uint32_t i = 0; (i < header.buildVersion.ntools) && (i < 10); i++) { uint32_t tool = reader.Read32(); uint32_t version = reader.Read32(); header.buildToolVersions.push_back({tool, version}); m_logger->LogDebugF("Build Tool: {}: {}", BuildToolToString(tool), BuildToolVersionToString(version)); } break; } case LC_FILESET_ENTRY: { if (isMainHeader) { uint64_t vmAddr = reader.Read64(); uint64_t fAddr = reader.Read64(); uint64_t strOff = reader.Read32() + curOffset; reader.Seek(strOff); auto identPrefix = reader.ReadCString(load.cmdsize - strOff); MachOHeader subHeader = HeaderForAddress(data, fAddr, false, identPrefix); subHeader.textBase = vmAddr; m_subHeaders[vmAddr] = subHeader; } else { throw ReadException(); } break; } default: m_logger->LogDebug("Unhandled command: %s : %" PRIu32 "\n", CommandToString(load.cmd).c_str(), load.cmdsize); break; } if (reader.GetOffset() != nextOffset) { m_logger->LogDebug("Didn't parse load command: %s fully %" PRIx64 ":%" PRIxPTR, CommandToString(load.cmd).c_str(), reader.GetOffset(), nextOffset); } reader.Seek(nextOffset); } } catch (ReadException&) { throw MachoFormatException("Mach-O section headers invalid"); } return header; } void MachoView::RebaseThreadStarts(BinaryReader& virtualReader, vector& threadStarts, uint64_t stepMultiplier) { std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now(); // create a dummy relocation to handle tagged pointers BNRelocationInfo reloc; memset(&reloc, 0, sizeof(BNRelocationInfo)); reloc.type = StandardRelocationType; reloc.size = 8; reloc.nativeType = BINARYNINJA_MANUAL_RELOCATION; size_t numReg = 0; size_t numAuth = 0; uint64_t imageBase = GetStart(); for (auto threadStart : threadStarts) { if (threadStart == 0xffffffff) break; m_logger->LogDebugF("Rebasing thread chain start: {:#x}", threadStart); try { uint64_t curAddr = imageBase + threadStart; virtualReader.Seek(curAddr); while (true) { if (!IsOffsetBackedByFile(virtualReader.GetOffset())) { m_logger->LogError("Chained address: 0x%" PRIx64 " in thread start: 0x%x not backed by file!", virtualReader.GetOffset(), threadStart); break; } // read tagged pointer uint64_t val = virtualReader.Read64(); bool isAuthenticated = (val & (1ULL << 63)) != 0; bool isRebase = (val & (1ULL << 62)) == 0; if (isRebase) { // calculate new target value uint64_t tgtVal; if (isAuthenticated) { numAuth++; tgtVal = (val & 0xFFFFFFFF) + imageBase; } else { numReg++; uint64_t top8Bits = val & 0x0007F80000000000ULL; uint64_t bottom43Bits = val & 0x000007FFFFFFFFFFULL; tgtVal = (top8Bits << 13) | (((int64_t)(bottom43Bits << 21) >> 21) & 0x00FFFFFFFFFFFFFF); tgtVal += m_imageBaseAdjustment; } reloc.address = curAddr; DefineRelocation(m_arch, reloc, tgtVal, reloc.address); } // seek to next tagged pointer val &= ~(1ULL << 62); // handle bind bit uint64_t delta = (val & 0x3FF8000000000000) >> 51; delta *= stepMultiplier; if (!delta) break; curAddr += delta; virtualReader.Seek(curAddr); } } catch (ReadException&) { m_logger->LogErrorF("Failed rebasing thread start at: {:#x}", threadStart); } } std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); double t = std::chrono::duration_cast(end - start).count() / 1000.0; m_logger->LogInfo("Rebasing thread starts took %.3f seconds. Updated %" PRIuPTR " pointers (authenticated: %" PRIuPTR ", regular: %" PRIuPTR ").", t, numAuth + numReg, numAuth, numReg); } bool MachoView::IsValidFunctionStart(uint64_t addr) { uint8_t opcode[BN_MAX_INSTRUCTION_LENGTH]; size_t opLen = Read(opcode, addr, m_arch->GetMaxInstructionLength()); if (!opLen) return false; Ref ilFunc = new LowLevelILFunction(m_arch, nullptr); ilFunc->SetCurrentAddress(m_arch, addr); m_arch->GetInstructionLowLevelIL(opcode, addr, opLen, *ilFunc); for (size_t i = 0; i < ilFunc->GetInstructionCount(); i++) { const auto& instr = ilFunc->GetInstruction(i); if (instr.operation == LLIL_UNDEF) return false; if (i == 0 && instr.operation == LLIL_TRAP) return false; } return true; } void MachoView::ParseFunctionStarts(Platform* platform, uint64_t textBase, function_starts_command functionStarts) { BinaryReader reader(GetParentView()); reader.SetEndianness(m_endian); reader.SetVirtualBase(m_universalImageOffset); try { if (m_header.ident.filetype == MH_DSYM) { m_logger->LogDebug("Skipping LC_FUNCTION_STARTS parsing"); return; } reader.Seek(functionStarts.funcoff); DataBuffer buffer = reader.Read(functionStarts.funcsize); size_t i = 0; uint64_t curfunc = textBase; uint64_t curOffset = 0; while (i < functionStarts.funcsize) { curOffset = readLEB128(buffer, functionStarts.funcsize, i); if (curOffset == 0) continue; curfunc += curOffset; uint64_t target = curfunc; if (!IsValidFunctionStart(target)) { m_logger->LogInfoF("Address {:#x} referenced from LC_FUNCTION_STARTS does not appear to be a function", target); continue; } Ref targetPlatform = platform->GetAssociatedPlatformByAddress(target); AddFunctionForAnalysis(targetPlatform, target); m_logger->LogDebugF("Adding function start: {:#x}", curfunc); } } catch (ReadException&) { m_logger->LogDebug("LC_FUNCTION_STARTS command invalid"); } } bool MachoView::ParseRelocationEntry(const relocation_info& info, uint64_t start, BNRelocationInfo& result) { // struct BNRelocationInfo // { // BNRelocationType type; // BinaryNinja Relocation Type // bool pcRelative; // PC Relative or Absolute (subtract address from relocation) // bool baseRelative; // Relative to start of module (Add module base to relocation) // size_t size; // Size of the data to be written // size_t truncateSize; // After addition/subtraction truncate to // uint64_t nativeType; // Base type from relocation entry // size_t addend; // Addend value from relocation entry // bool hasSign; // Addend should be subtracted // bool implicitAddend; // Addend should be read from the BinaryView // bool external; // Relocation entry points to external symbol // size_t symbolIndex; // Index into symbol table // size_t sectionIndex; // Index into the section table // uint64_t address; // Absolute address or segment offset // }; m_logger->LogDebug("\tr_address: %" PRIx32 " + %" PRIx64 " = %" PRIx64, info.r_address, start, info.r_address + start); m_logger->LogDebug("\tr_symbolnum: %" PRIx32, info.r_symbolnum); m_logger->LogDebug("\tr_pcrel: %" PRIx32, info.r_pcrel); m_logger->LogDebug("\tr_length: %" PRIx32, info.r_length); m_logger->LogDebug("\tr_extern: %" PRIx32, info.r_extern); m_logger->LogDebug("\tr_type: %" PRIx32, info.r_type); if (m_objectFile && (info.r_address & R_SCATTERED)) { m_logger->LogError("Scattered Relocations not currently supported"); return false; } switch (info.r_length) { case 0: result.size = 1; break; case 1: result.size = 2; break; case 2: result.size = 4; break; case 3: result.size = 8; break; } result.address = start + info.r_address; result.truncateSize = result.size; result.pcRelative = info.r_pcrel; result.baseRelative = start == GetStart(); result.nativeType = info.r_type; result.addend = 0; result.hasSign = false; result.implicitAddend = false; result.external = info.r_extern; if (result.external) result.symbolIndex = info.r_symbolnum; else result.sectionIndex = info.r_symbolnum; return true; } bool MachoView::Init() { Ref settings = GetLoadSettings(GetTypeName()); std::chrono::steady_clock::time_point startTime = std::chrono::steady_clock::now(); BinaryReader reader(GetParentView()); reader.SetEndianness(m_endian); reader.SetVirtualBase(m_universalImageOffset); BinaryReader virtualReader(this); virtualReader.SetEndianness(m_endian); uint64_t initialImageBase = 0; bool initialImageBaseSet = false; string preferredImageBaseDesc; for (const auto& i : m_header.segments) { if ((i.initprot == MACHO_VM_PROT_NONE) || (!i.vmsize)) continue; if (!initialImageBaseSet) { initialImageBase = i.vmaddr; initialImageBaseSet = true; preferredImageBaseDesc = i.segname; } else if (i.vmaddr < initialImageBase) { initialImageBase = i.vmaddr; preferredImageBaseDesc = i.segname; } } SetOriginalImageBase(initialImageBase); uint64_t preferredImageBase = initialImageBase; Ref viewSettings = Settings::Instance(); m_extractMangledTypes = viewSettings->Get("analysis.extractTypesFromMangledNames", this); m_simplifyTemplates = viewSettings->Get("analysis.types.templateSimplifier", this); bool platformSetByUser = false; if (settings) { if (settings->Contains("loader.imageBase")) preferredImageBase = settings->Get("loader.imageBase", this); if (settings->Contains("loader.platform")) { BNSettingsScope scope = SettingsAutoScope; Ref platform = Platform::GetByName(settings->Get("loader.platform", this, &scope)); if (platform) { m_plat = platform; m_arch = platform->GetArchitecture(); platformSetByUser = (scope == SettingsResourceScope); } } } m_imageBaseAdjustment = 0; if (!initialImageBase) m_imageBaseAdjustment = preferredImageBase; else if (initialImageBase <= preferredImageBase) m_imageBaseAdjustment = preferredImageBase - initialImageBase; else m_imageBaseAdjustment = -(int64_t)(initialImageBase - preferredImageBase); // Add Mach-O file header type info EnumerationBuilder cpuTypeBuilder; cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_ANY", MACHO_CPU_TYPE_ANY); cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_VAX", MACHO_CPU_TYPE_VAX); cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_MC680x0", MACHO_CPU_TYPE_MC680x0); cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_X86", MACHO_CPU_TYPE_X86); cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_X86_64", MACHO_CPU_TYPE_X86_64); cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_MIPS", MACHO_CPU_TYPE_MIPS); cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_MC98000", MACHO_CPU_TYPE_MC98000); cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_HPPA", MACHO_CPU_TYPE_HPPA); cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_ARM", MACHO_CPU_TYPE_ARM); cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_ARM64", MACHO_CPU_TYPE_ARM64); cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_ARM64_32", MACHO_CPU_TYPE_ARM64_32); cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_MC88000", MACHO_CPU_TYPE_MC88000); cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_SPARC", MACHO_CPU_TYPE_SPARC); cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_I860", MACHO_CPU_TYPE_I860); cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_ALPHA", MACHO_CPU_TYPE_ALPHA); cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_POWERPC", MACHO_CPU_TYPE_POWERPC); cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_POWERPC64", MACHO_CPU_TYPE_POWERPC64); Ref cpuTypeEnum = cpuTypeBuilder.Finalize(); Ref cpuTypeEnumType = Type::EnumerationType(nullptr, cpuTypeEnum, 4, false); string cpuTypeEnumName = "cpu_type_t"; string cpuTypeEnumId = Type::GenerateAutoTypeId("macho", cpuTypeEnumName); m_typeNames.cpuTypeEnumQualName = DefineType(cpuTypeEnumId, cpuTypeEnumName, cpuTypeEnumType); EnumerationBuilder fileTypeBuilder; fileTypeBuilder.AddMemberWithValue("MH_OBJECT", MH_OBJECT); fileTypeBuilder.AddMemberWithValue("MH_EXECUTE", MH_EXECUTE); fileTypeBuilder.AddMemberWithValue("MH_FVMLIB", MH_FVMLIB); fileTypeBuilder.AddMemberWithValue("MH_CORE", MH_CORE); fileTypeBuilder.AddMemberWithValue("MH_PRELOAD", MH_PRELOAD); fileTypeBuilder.AddMemberWithValue("MH_DYLIB", MH_DYLIB); fileTypeBuilder.AddMemberWithValue("MH_DYLINKER", MH_DYLINKER); fileTypeBuilder.AddMemberWithValue("MH_BUNDLE", MH_BUNDLE); fileTypeBuilder.AddMemberWithValue("MH_DYLIB_STUB", MH_DYLIB_STUB); fileTypeBuilder.AddMemberWithValue("MH_DSYM", MH_DSYM); fileTypeBuilder.AddMemberWithValue("MH_KEXT_BUNDLE", MH_KEXT_BUNDLE); fileTypeBuilder.AddMemberWithValue("MH_FILESET", MH_FILESET); Ref fileTypeEnum = fileTypeBuilder.Finalize(); Ref fileTypeEnumType = Type::EnumerationType(nullptr, fileTypeEnum, 4, false); string fileTypeEnumName = "file_type_t"; string fileTypeEnumId = Type::GenerateAutoTypeId("macho", fileTypeEnumName); m_typeNames.fileTypeEnumQualName = DefineType(fileTypeEnumId, fileTypeEnumName, fileTypeEnumType); EnumerationBuilder flagsTypeBuilder; flagsTypeBuilder.AddMemberWithValue("MH_NOUNDEFS", MH_NOUNDEFS); flagsTypeBuilder.AddMemberWithValue("MH_INCRLINK", MH_INCRLINK); flagsTypeBuilder.AddMemberWithValue("MH_DYLDLINK", MH_DYLDLINK); flagsTypeBuilder.AddMemberWithValue("MH_BINDATLOAD", MH_BINDATLOAD); flagsTypeBuilder.AddMemberWithValue("MH_PREBOUND", MH_PREBOUND); flagsTypeBuilder.AddMemberWithValue("MH_SPLIT_SEGS", MH_SPLIT_SEGS); flagsTypeBuilder.AddMemberWithValue("MH_LAZY_INIT", MH_LAZY_INIT); flagsTypeBuilder.AddMemberWithValue("MH_TWOLEVEL", MH_TWOLEVEL); flagsTypeBuilder.AddMemberWithValue("MH_FORCE_FLAT", MH_FORCE_FLAT); flagsTypeBuilder.AddMemberWithValue("MH_NOMULTIDEFS", MH_NOMULTIDEFS); flagsTypeBuilder.AddMemberWithValue("MH_NOFIXPREBINDING", MH_NOFIXPREBINDING); flagsTypeBuilder.AddMemberWithValue("MH_PREBINDABLE", MH_PREBINDABLE); flagsTypeBuilder.AddMemberWithValue("MH_ALLMODSBOUND", MH_ALLMODSBOUND); flagsTypeBuilder.AddMemberWithValue("MH_SUBSECTIONS_VIA_SYMBOLS", MH_SUBSECTIONS_VIA_SYMBOLS); flagsTypeBuilder.AddMemberWithValue("MH_CANONICAL", MH_CANONICAL); flagsTypeBuilder.AddMemberWithValue("MH_WEAK_DEFINES", MH_WEAK_DEFINES); flagsTypeBuilder.AddMemberWithValue("MH_BINDS_TO_WEAK", MH_BINDS_TO_WEAK); flagsTypeBuilder.AddMemberWithValue("MH_ALLOW_STACK_EXECUTION", MH_ALLOW_STACK_EXECUTION); flagsTypeBuilder.AddMemberWithValue("MH_ROOT_SAFE", MH_ROOT_SAFE); flagsTypeBuilder.AddMemberWithValue("MH_SETUID_SAFE", MH_SETUID_SAFE); flagsTypeBuilder.AddMemberWithValue("MH_NO_REEXPORTED_DYLIBS", MH_NO_REEXPORTED_DYLIBS); flagsTypeBuilder.AddMemberWithValue("MH_PIE", MH_PIE); flagsTypeBuilder.AddMemberWithValue("MH_DEAD_STRIPPABLE_DYLIB", MH_DEAD_STRIPPABLE_DYLIB); flagsTypeBuilder.AddMemberWithValue("MH_HAS_TLV_DESCRIPTORS", MH_HAS_TLV_DESCRIPTORS); flagsTypeBuilder.AddMemberWithValue("MH_NO_HEAP_EXECUTION", MH_NO_HEAP_EXECUTION); flagsTypeBuilder.AddMemberWithValue("MH_APP_EXTENSION_SAFE", _MH_APP_EXTENSION_SAFE); flagsTypeBuilder.AddMemberWithValue("MH_NLIST_OUTOFSYNC_WITH_DYLDINFO", _MH_NLIST_OUTOFSYNC_WITH_DYLDINFO); flagsTypeBuilder.AddMemberWithValue("MH_SIM_SUPPORT", _MH_SIM_SUPPORT); flagsTypeBuilder.AddMemberWithValue("MH_DYLIB_IN_CACHE", _MH_DYLIB_IN_CACHE); Ref flagsTypeEnum = flagsTypeBuilder.Finalize(); Ref flagsTypeEnumType = Type::EnumerationType(nullptr, flagsTypeEnum, 4, false); string flagsTypeEnumName = "flags_type_t"; string flagsTypeEnumId = Type::GenerateAutoTypeId("macho", flagsTypeEnumName); m_typeNames.flagsTypeEnumQualName = DefineType(flagsTypeEnumId, flagsTypeEnumName, flagsTypeEnumType); StructureBuilder machoHeaderBuilder; machoHeaderBuilder.AddMember(Type::IntegerType(4, false), "magic"); machoHeaderBuilder.AddMember(Type::NamedType(this, m_typeNames.cpuTypeEnumQualName), "cputype"); machoHeaderBuilder.AddMember(Type::IntegerType(4, false), "cpusubtype"); machoHeaderBuilder.AddMember(Type::NamedType(this, m_typeNames.fileTypeEnumQualName), "filetype"); machoHeaderBuilder.AddMember(Type::IntegerType(4, false), "ncmds"); machoHeaderBuilder.AddMember(Type::IntegerType(4, false), "sizeofcmds"); machoHeaderBuilder.AddMember(Type::NamedType(this, m_typeNames.flagsTypeEnumQualName), "flags"); if (m_addressSize == 8) machoHeaderBuilder.AddMember(Type::IntegerType(4, false), "reserved"); Ref machoHeaderStruct = machoHeaderBuilder.Finalize(); QualifiedName headerName = (m_addressSize == 8) ? string("mach_header_64") : string("mach_header"); string headerTypeId = Type::GenerateAutoTypeId("macho", headerName); Ref machoHeaderType = Type::StructureType(machoHeaderStruct); m_typeNames.headerQualName = DefineType(headerTypeId, headerName, machoHeaderType); EnumerationBuilder cmdTypeBuilder; cmdTypeBuilder.AddMemberWithValue("LC_REQ_DYLD", LC_REQ_DYLD); cmdTypeBuilder.AddMemberWithValue("LC_SEGMENT", LC_SEGMENT); cmdTypeBuilder.AddMemberWithValue("LC_SYMTAB", LC_SYMTAB); cmdTypeBuilder.AddMemberWithValue("LC_SYMSEG",LC_SYMSEG); cmdTypeBuilder.AddMemberWithValue("LC_THREAD", LC_THREAD); cmdTypeBuilder.AddMemberWithValue("LC_UNIXTHREAD", LC_UNIXTHREAD); cmdTypeBuilder.AddMemberWithValue("LC_LOADFVMLIB", LC_LOADFVMLIB); cmdTypeBuilder.AddMemberWithValue("LC_IDFVMLIB", LC_IDFVMLIB); cmdTypeBuilder.AddMemberWithValue("LC_IDENT", LC_IDENT); cmdTypeBuilder.AddMemberWithValue("LC_FVMFILE", LC_FVMFILE); cmdTypeBuilder.AddMemberWithValue("LC_PREPAGE", LC_PREPAGE); cmdTypeBuilder.AddMemberWithValue("LC_DYSYMTAB", LC_DYSYMTAB); cmdTypeBuilder.AddMemberWithValue("LC_LOAD_DYLIB", LC_LOAD_DYLIB); cmdTypeBuilder.AddMemberWithValue("LC_ID_DYLIB", LC_ID_DYLIB); cmdTypeBuilder.AddMemberWithValue("LC_LOAD_DYLINKER", LC_LOAD_DYLINKER); cmdTypeBuilder.AddMemberWithValue("LC_ID_DYLINKER", LC_ID_DYLINKER); cmdTypeBuilder.AddMemberWithValue("LC_PREBOUND_DYLIB", LC_PREBOUND_DYLIB); cmdTypeBuilder.AddMemberWithValue("LC_ROUTINES", LC_ROUTINES); cmdTypeBuilder.AddMemberWithValue("LC_SUB_FRAMEWORK", LC_SUB_FRAMEWORK); cmdTypeBuilder.AddMemberWithValue("LC_SUB_UMBRELLA", LC_SUB_UMBRELLA); cmdTypeBuilder.AddMemberWithValue("LC_SUB_CLIENT", LC_SUB_CLIENT); cmdTypeBuilder.AddMemberWithValue("LC_SUB_LIBRARY", LC_SUB_LIBRARY); cmdTypeBuilder.AddMemberWithValue("LC_TWOLEVEL_HINTS", LC_TWOLEVEL_HINTS); cmdTypeBuilder.AddMemberWithValue("LC_PREBIND_CKSUM", LC_PREBIND_CKSUM); cmdTypeBuilder.AddMemberWithValue("LC_LOAD_WEAK_DYLIB", LC_LOAD_WEAK_DYLIB);// (0x18 | LC_REQ_DYLD) cmdTypeBuilder.AddMemberWithValue("LC_SEGMENT_64", LC_SEGMENT_64); cmdTypeBuilder.AddMemberWithValue("LC_ROUTINES_64", LC_ROUTINES_64); cmdTypeBuilder.AddMemberWithValue("LC_UUID", LC_UUID); cmdTypeBuilder.AddMemberWithValue("LC_RPATH", LC_RPATH);// (0x1c | LC_REQ_DYLD) cmdTypeBuilder.AddMemberWithValue("LC_CODE_SIGNATURE", LC_CODE_SIGNATURE); cmdTypeBuilder.AddMemberWithValue("LC_SEGMENT_SPLIT_INFO", LC_SEGMENT_SPLIT_INFO); cmdTypeBuilder.AddMemberWithValue("LC_REEXPORT_DYLIB", LC_REEXPORT_DYLIB);// (0x1f | LC_REQ_DYLD) cmdTypeBuilder.AddMemberWithValue("LC_LAZY_LOAD_DYLIB", LC_LAZY_LOAD_DYLIB); cmdTypeBuilder.AddMemberWithValue("LC_ENCRYPTION_INFO", LC_ENCRYPTION_INFO); cmdTypeBuilder.AddMemberWithValue("LC_DYLD_INFO", LC_DYLD_INFO); cmdTypeBuilder.AddMemberWithValue("LC_DYLD_INFO_ONLY", LC_DYLD_INFO_ONLY);// (0x22 | LC_REQ_DYLD) cmdTypeBuilder.AddMemberWithValue("LC_LOAD_UPWARD_DYLIB", LC_LOAD_UPWARD_DYLIB);// (0x23 | LC_REQ_DYLD) cmdTypeBuilder.AddMemberWithValue("LC_VERSION_MIN_MACOSX", LC_VERSION_MIN_MACOSX); cmdTypeBuilder.AddMemberWithValue("LC_VERSION_MIN_IPHONEOS", LC_VERSION_MIN_IPHONEOS); cmdTypeBuilder.AddMemberWithValue("LC_FUNCTION_STARTS", LC_FUNCTION_STARTS); cmdTypeBuilder.AddMemberWithValue("LC_DYLD_ENVIRONMENT", LC_DYLD_ENVIRONMENT); cmdTypeBuilder.AddMemberWithValue("LC_MAIN", LC_MAIN);// (0x28 | LC_REQ_DYLD) cmdTypeBuilder.AddMemberWithValue("LC_DATA_IN_CODE", LC_DATA_IN_CODE); cmdTypeBuilder.AddMemberWithValue("LC_SOURCE_VERSION", LC_SOURCE_VERSION); cmdTypeBuilder.AddMemberWithValue("LC_DYLIB_CODE_SIGN_DRS", LC_DYLIB_CODE_SIGN_DRS); cmdTypeBuilder.AddMemberWithValue("LC_ENCRYPTION_INFO_64", _LC_ENCRYPTION_INFO_64); cmdTypeBuilder.AddMemberWithValue("LC_LINKER_OPTION", _LC_LINKER_OPTION); cmdTypeBuilder.AddMemberWithValue("LC_LINKER_OPTIMIZATION_HINT", _LC_LINKER_OPTIMIZATION_HINT); cmdTypeBuilder.AddMemberWithValue("LC_VERSION_MIN_TVOS", _LC_VERSION_MIN_TVOS); cmdTypeBuilder.AddMemberWithValue("LC_VERSION_MIN_WATCHOS", LC_VERSION_MIN_WATCHOS); cmdTypeBuilder.AddMemberWithValue("LC_NOTE", LC_NOTE); cmdTypeBuilder.AddMemberWithValue("LC_BUILD_VERSION", LC_BUILD_VERSION); cmdTypeBuilder.AddMemberWithValue("LC_DYLD_EXPORTS_TRIE", LC_DYLD_EXPORTS_TRIE); cmdTypeBuilder.AddMemberWithValue("LC_DYLD_CHAINED_FIXUPS", LC_DYLD_CHAINED_FIXUPS); cmdTypeBuilder.AddMemberWithValue("LC_FILESET_ENTRY", LC_FILESET_ENTRY); Ref cmdTypeEnum = cmdTypeBuilder.Finalize(); Ref cmdTypeEnumType = Type::EnumerationType(nullptr, cmdTypeEnum, 4, false); string cmdTypeEnumName = "load_command_type_t"; string cmdTypeEnumId = Type::GenerateAutoTypeId("macho", cmdTypeEnumName); m_typeNames.cmdTypeEnumQualName = DefineType(cmdTypeEnumId, cmdTypeEnumName, cmdTypeEnumType); StructureBuilder loadCommandBuilder; loadCommandBuilder.AddMember(Type::NamedType(this, m_typeNames.cmdTypeEnumQualName), "cmd"); loadCommandBuilder.AddMember(Type::IntegerType(4, false), "cmdsize"); Ref loadCommandStruct = loadCommandBuilder.Finalize(); QualifiedName loadCommandName = string("load_command"); string loadCommandTypeId = Type::GenerateAutoTypeId("macho", loadCommandName); Ref loadCommandType = Type::StructureType(loadCommandStruct); m_typeNames.loadCommandQualName = DefineType(loadCommandTypeId, loadCommandName, loadCommandType); EnumerationBuilder protTypeBuilder; protTypeBuilder.AddMemberWithValue("VM_PROT_NONE", MACHO_VM_PROT_NONE); protTypeBuilder.AddMemberWithValue("VM_PROT_READ", MACHO_VM_PROT_READ); protTypeBuilder.AddMemberWithValue("VM_PROT_WRITE", MACHO_VM_PROT_WRITE); protTypeBuilder.AddMemberWithValue("VM_PROT_EXECUTE", MACHO_VM_PROT_EXECUTE); // protTypeBuilder.AddMemberWithValue("VM_PROT_DEFAULT", MACHO_VM_PROT_DEFAULT); // protTypeBuilder.AddMemberWithValue("VM_PROT_ALL", MACHO_VM_PROT_ALL); protTypeBuilder.AddMemberWithValue("VM_PROT_NO_CHANGE", MACHO_VM_PROT_NO_CHANGE); protTypeBuilder.AddMemberWithValue("VM_PROT_COPY_OR_WANTS_COPY", MACHO_VM_PROT_COPY); //protTypeBuilder.AddMemberWithValue("VM_PROT_WANTS_COPY", MACHO_VM_PROT_WANTS_COPY); Ref protTypeEnum = protTypeBuilder.Finalize(); Ref protTypeEnumType = Type::EnumerationType(nullptr, protTypeEnum, 4, false); string protTypeEnumName = "vm_prot_t"; string protTypeEnumId = Type::GenerateAutoTypeId("macho", protTypeEnumName); m_typeNames.protTypeEnumQualName = DefineType(protTypeEnumId, protTypeEnumName, protTypeEnumType); EnumerationBuilder segFlagsTypeBuilder; segFlagsTypeBuilder.AddMemberWithValue("SG_HIGHVM", SG_HIGHVM); segFlagsTypeBuilder.AddMemberWithValue("SG_FVMLIB", SG_FVMLIB); segFlagsTypeBuilder.AddMemberWithValue("SG_NORELOC", SG_NORELOC); segFlagsTypeBuilder.AddMemberWithValue("SG_PROTECTED_VERSION_1", SG_PROTECTED_VERSION_1); Ref segFlagsTypeEnum = segFlagsTypeBuilder.Finalize(); Ref segFlagsTypeEnumType = Type::EnumerationType(nullptr, segFlagsTypeEnum, 4, false); string segFlagsTypeEnumName = "sg_flags_t"; string segFlagsTypeEnumId = Type::GenerateAutoTypeId("macho", segFlagsTypeEnumName); m_typeNames.segFlagsTypeEnumQualName = DefineType(segFlagsTypeEnumId, segFlagsTypeEnumName, segFlagsTypeEnumType); StructureBuilder loadSegmentCommandBuilder; loadSegmentCommandBuilder.AddMember(Type::NamedType(this, m_typeNames.cmdTypeEnumQualName), "cmd"); loadSegmentCommandBuilder.AddMember(Type::IntegerType(4, false), "cmdsize"); loadSegmentCommandBuilder.AddMember(Type::ArrayType(Type::IntegerType(1, true), 16), "segname"); loadSegmentCommandBuilder.AddMember(Type::IntegerType(4, false), "vmaddr"); loadSegmentCommandBuilder.AddMember(Type::IntegerType(4, false), "vmsize"); loadSegmentCommandBuilder.AddMember(Type::IntegerType(4, false), "fileoff"); loadSegmentCommandBuilder.AddMember(Type::IntegerType(4, false), "filesize"); loadSegmentCommandBuilder.AddMember(Type::NamedType(this, m_typeNames.protTypeEnumQualName), "maxprot"); loadSegmentCommandBuilder.AddMember(Type::NamedType(this, m_typeNames.protTypeEnumQualName), "initprot"); loadSegmentCommandBuilder.AddMember(Type::IntegerType(4, false), "nsects"); loadSegmentCommandBuilder.AddMember(Type::NamedType(this, m_typeNames.segFlagsTypeEnumQualName), "flags"); Ref loadSegmentCommandStruct = loadSegmentCommandBuilder.Finalize(); QualifiedName loadSegmentCommandName = string("segment_command"); string loadSegmentCommandTypeId = Type::GenerateAutoTypeId("macho", loadSegmentCommandName); Ref loadSegmentCommandType = Type::StructureType(loadSegmentCommandStruct); m_typeNames.loadSegmentCommandQualName = DefineType(loadSegmentCommandTypeId, loadSegmentCommandName, loadSegmentCommandType); StructureBuilder loadSegmentCommand64Builder; loadSegmentCommand64Builder.AddMember(Type::NamedType(this, m_typeNames.cmdTypeEnumQualName), "cmd"); loadSegmentCommand64Builder.AddMember(Type::IntegerType(4, false), "cmdsize"); loadSegmentCommand64Builder.AddMember(Type::ArrayType(Type::IntegerType(1, true), 16), "segname"); loadSegmentCommand64Builder.AddMember(Type::IntegerType(8, false), "vmaddr"); loadSegmentCommand64Builder.AddMember(Type::IntegerType(8, false), "vmsize"); loadSegmentCommand64Builder.AddMember(Type::IntegerType(8, false), "fileoff"); loadSegmentCommand64Builder.AddMember(Type::IntegerType(8, false), "filesize"); loadSegmentCommand64Builder.AddMember(Type::NamedType(this, m_typeNames.protTypeEnumQualName), "maxprot"); loadSegmentCommand64Builder.AddMember(Type::NamedType(this, m_typeNames.protTypeEnumQualName), "initprot"); loadSegmentCommand64Builder.AddMember(Type::IntegerType(4, false), "nsects"); loadSegmentCommand64Builder.AddMember(Type::NamedType(this, m_typeNames.segFlagsTypeEnumQualName), "flags"); Ref loadSegmentCommand64Struct = loadSegmentCommand64Builder.Finalize(); QualifiedName loadSegment64CommandName = string("segment_command_64"); string loadSegment64CommandTypeId = Type::GenerateAutoTypeId("macho", loadSegment64CommandName); Ref loadSegment64CommandType = Type::StructureType(loadSegmentCommand64Struct); m_typeNames.loadSegment64CommandQualName = DefineType(loadSegment64CommandTypeId, loadSegment64CommandName, loadSegment64CommandType); StructureBuilder sectionBuilder; sectionBuilder.AddMember(Type::ArrayType(Type::IntegerType(1, true), 16), "sectname"); sectionBuilder.AddMember(Type::ArrayType(Type::IntegerType(1, true), 16), "segname"); sectionBuilder.AddMember(Type::IntegerType(4, false), "addr"); sectionBuilder.AddMember(Type::IntegerType(4, false), "size"); sectionBuilder.AddMember(Type::IntegerType(4, false), "offset"); sectionBuilder.AddMember(Type::IntegerType(4, false), "align"); sectionBuilder.AddMember(Type::IntegerType(4, false), "reloff"); sectionBuilder.AddMember(Type::IntegerType(4, false), "nreloc"); sectionBuilder.AddMember(Type::IntegerType(4, false), "flags"); sectionBuilder.AddMember(Type::IntegerType(4, false), "reserved1"); sectionBuilder.AddMember(Type::IntegerType(4, false), "reserved2"); Ref sectionStruct = sectionBuilder.Finalize(); QualifiedName sectionName = string("section"); string sectionTypeId = Type::GenerateAutoTypeId("macho", sectionName); Ref sectionType = Type::StructureType(sectionStruct); m_typeNames.sectionQualName = DefineType(sectionTypeId, sectionName, sectionType); StructureBuilder section64Builder; section64Builder.AddMember(Type::ArrayType(Type::IntegerType(1, true), 16), "sectname"); section64Builder.AddMember(Type::ArrayType(Type::IntegerType(1, true), 16), "segname"); section64Builder.AddMember(Type::IntegerType(8, false), "addr"); section64Builder.AddMember(Type::IntegerType(8, false), "size"); section64Builder.AddMember(Type::IntegerType(4, false), "offset"); section64Builder.AddMember(Type::IntegerType(4, false), "align"); section64Builder.AddMember(Type::IntegerType(4, false), "reloff"); section64Builder.AddMember(Type::IntegerType(4, false), "nreloc"); section64Builder.AddMember(Type::IntegerType(4, false), "flags"); section64Builder.AddMember(Type::IntegerType(4, false), "reserved1"); section64Builder.AddMember(Type::IntegerType(4, false), "reserved2"); section64Builder.AddMember(Type::IntegerType(4, false), "reserved3"); Ref section64Struct = section64Builder.Finalize(); QualifiedName section64Name = string("section_64"); string section64TypeId = Type::GenerateAutoTypeId("macho", section64Name); Ref section64Type = Type::StructureType(section64Struct); m_typeNames.section64QualName = DefineType(section64TypeId, section64Name, section64Type); StructureBuilder symtabBuilder; symtabBuilder.AddMember(Type::NamedType(this, m_typeNames.cmdTypeEnumQualName), "cmd"); symtabBuilder.AddMember(Type::IntegerType(4, false), "cmdsize"); symtabBuilder.AddMember(Type::IntegerType(4, false), "symoff"); symtabBuilder.AddMember(Type::IntegerType(4, false), "nsyms"); symtabBuilder.AddMember(Type::IntegerType(4, false), "stroff"); symtabBuilder.AddMember(Type::IntegerType(4, false), "strsize"); Ref symtabStruct = symtabBuilder.Finalize(); QualifiedName symtabName = string("symtab"); string symtabTypeId = Type::GenerateAutoTypeId("macho", symtabName); Ref symtabType = Type::StructureType(symtabStruct); m_typeNames.symtabQualName = DefineType(symtabTypeId, symtabName, symtabType); StructureBuilder dynsymtabBuilder; dynsymtabBuilder.AddMember(Type::NamedType(this, m_typeNames.cmdTypeEnumQualName), "cmd"); dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "cmdsize"); dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "ilocalsym"); dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "nlocalsym"); dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "iextdefsym"); dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "nextdefsym"); dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "iundefsym"); dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "nundefsym"); dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "tocoff"); dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "ntoc"); dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "modtaboff"); dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "nmodtab"); dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "extrefsymoff"); dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "nextrefsyms"); dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "indirectsymoff"); dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "nindirectsyms"); dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "extreloff"); dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "nextrel"); dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "locreloff"); dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "nlocrel"); Ref dynsymtabStruct = dynsymtabBuilder.Finalize(); QualifiedName dynsymtabName = string("dynsymtab"); string dynsymtabTypeId = Type::GenerateAutoTypeId("macho", dynsymtabName); Ref dynsymtabType = Type::StructureType(dynsymtabStruct); m_typeNames.dynsymtabQualName = DefineType(dynsymtabTypeId, dynsymtabName, dynsymtabType); StructureBuilder uuidBuilder; uuidBuilder.AddMember(Type::NamedType(this, m_typeNames.cmdTypeEnumQualName), "cmd"); uuidBuilder.AddMember(Type::IntegerType(4, false), "cmdsize"); uuidBuilder.AddMember(Type::ArrayType(Type::IntegerType(1, false), 16), "uuid"); Ref uuidStruct = uuidBuilder.Finalize(); QualifiedName uuidName = string("uuid"); string uuidTypeId = Type::GenerateAutoTypeId("macho", uuidName); Ref uuidType = Type::StructureType(uuidStruct); m_typeNames.uuidQualName = DefineType(uuidTypeId, uuidName, uuidType); StructureBuilder linkeditDataBuilder; linkeditDataBuilder.AddMember(Type::NamedType(this, m_typeNames.cmdTypeEnumQualName), "cmd"); linkeditDataBuilder.AddMember(Type::IntegerType(4, false), "cmdsize"); linkeditDataBuilder.AddMember(Type::IntegerType(4, false), "dataoff"); linkeditDataBuilder.AddMember(Type::IntegerType(4, false), "datasize"); Ref linkeditDataStruct = linkeditDataBuilder.Finalize(); QualifiedName linkeditDataName = string("linkedit_data"); string linkeditDataTypeId = Type::GenerateAutoTypeId("macho", linkeditDataName); Ref linkeditDataType = Type::StructureType(linkeditDataStruct); m_typeNames.linkeditDataQualName = DefineType(linkeditDataTypeId, linkeditDataName, linkeditDataType); StructureBuilder encryptionInfoBuilder; encryptionInfoBuilder.AddMember(Type::NamedType(this, m_typeNames.cmdTypeEnumQualName), "cmd"); encryptionInfoBuilder.AddMember(Type::IntegerType(4, false), "cmdsize"); encryptionInfoBuilder.AddMember(Type::IntegerType(4, false), "cryptoff"); encryptionInfoBuilder.AddMember(Type::IntegerType(4, false), "cryptsize"); encryptionInfoBuilder.AddMember(Type::IntegerType(4, false), "cryptid"); Ref encryptionInfoStruct = encryptionInfoBuilder.Finalize(); QualifiedName encryptionInfoName = string("encryption_info"); string encryptionInfoTypeId = Type::GenerateAutoTypeId("macho", encryptionInfoName); Ref encryptionInfoType = Type::StructureType(encryptionInfoStruct); m_typeNames.encryptionInfoQualName = DefineType(encryptionInfoTypeId, encryptionInfoName, encryptionInfoType); StructureBuilder versionMinBuilder; versionMinBuilder.AddMember(Type::NamedType(this, m_typeNames.cmdTypeEnumQualName), "cmd"); versionMinBuilder.AddMember(Type::IntegerType(4, false), "cmdsize"); versionMinBuilder.AddMember(Type::IntegerType(4, false), "version"); versionMinBuilder.AddMember(Type::IntegerType(4, false), "sdk"); Ref versionMinStruct = versionMinBuilder.Finalize(); QualifiedName versionMinName = string("version_min"); string versionMinTypeId = Type::GenerateAutoTypeId("macho", versionMinName); Ref versionMinType = Type::StructureType(versionMinStruct); m_typeNames.versionMinQualName = DefineType(versionMinTypeId, versionMinName, versionMinType); StructureBuilder dyldInfoBuilder; dyldInfoBuilder.AddMember(Type::NamedType(this, m_typeNames.cmdTypeEnumQualName), "cmd"); dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "cmdsize"); dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "rebase_off"); dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "rebase_size"); dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "bind_off"); dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "bind_size"); dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "weak_bind_off"); dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "weak_bind_size"); dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "lazy_bind_off"); dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "lazy_bind_size"); dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "export_off"); dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "export_size"); Ref dyldInfoStruct = dyldInfoBuilder.Finalize(); QualifiedName dyldInfoName = string("dyld_info"); string dyldInfoTypeId = Type::GenerateAutoTypeId("macho", dyldInfoName); Ref dyldInfoType = Type::StructureType(dyldInfoStruct); m_typeNames.dyldInfoQualName = DefineType(dyldInfoTypeId, dyldInfoName, dyldInfoType); StructureBuilder dylibBuilder; dylibBuilder.AddMember(Type::IntegerType(4, false), "name"); dylibBuilder.AddMember(Type::IntegerType(4, false), "timestamp"); dylibBuilder.AddMember(Type::IntegerType(4, false), "current_version"); dylibBuilder.AddMember(Type::IntegerType(4, false), "compatibility_version"); Ref dylibStruct = dylibBuilder.Finalize(); QualifiedName dylibName = string("dylib"); string dylibTypeId = Type::GenerateAutoTypeId("macho", dylibName); Ref dylibType = Type::StructureType(dylibStruct); m_typeNames.dylibQualName = DefineType(dylibTypeId, dylibName, dylibType); StructureBuilder dylibCommandBuilder; dylibCommandBuilder.AddMember(Type::NamedType(this, m_typeNames.cmdTypeEnumQualName), "cmd"); dylibCommandBuilder.AddMember(Type::IntegerType(4, false), "cmdsize"); dylibCommandBuilder.AddMember(Type::NamedType(this, m_typeNames.dylibQualName), "dylib"); Ref dylibCommandStruct = dylibCommandBuilder.Finalize(); QualifiedName dylibCommandName = string("dylib_command"); string dylibCommandTypeId = Type::GenerateAutoTypeId("macho", dylibCommandName); Ref dylibCommandType = Type::StructureType(dylibCommandStruct); m_typeNames.dylibCommandQualName = DefineType(dylibCommandTypeId, dylibCommandName, dylibCommandType); StructureBuilder filesetEntryCommandBuilder; filesetEntryCommandBuilder.AddMember(Type::NamedType(this, m_typeNames.cmdTypeEnumQualName), "cmd"); filesetEntryCommandBuilder.AddMember(Type::IntegerType(4, false), "cmdsize"); filesetEntryCommandBuilder.AddMember(Type::IntegerType(8, false), "vmaddr"); filesetEntryCommandBuilder.AddMember(Type::IntegerType(8, false), "fileoff"); filesetEntryCommandBuilder.AddMember(Type::IntegerType(4, false), "entry_id"); filesetEntryCommandBuilder.AddMember(Type::IntegerType(4, false), "reserved"); Ref filesetEntryCommandStruct = filesetEntryCommandBuilder.Finalize(); QualifiedName filesetEntryCommandName = string("fileset_entry_command"); string filesetEntryCommandTypeId = Type::GenerateAutoTypeId("macho", filesetEntryCommandName); Ref filesetEntryCommandType = Type::StructureType(filesetEntryCommandStruct); m_typeNames.filesetEntryCommandQualName = DefineType(filesetEntryCommandTypeId, filesetEntryCommandName, filesetEntryCommandType); if (!InitializeHeader(m_header, true, preferredImageBase, preferredImageBaseDesc, platformSetByUser)) return false; for (auto& it : m_subHeaders) { if (!InitializeHeader(it.second, false, it.first, "", platformSetByUser)) return false; } std::chrono::steady_clock::time_point endTime = std::chrono::steady_clock::now(); double t = std::chrono::duration_cast(endTime - startTime).count() / 1000.0; m_logger->LogInfoF("Mach-O parsing took {:.3f} seconds", t); return true; } static Ref FindInternalSymbol(BinaryView* view, const std::string& name) { // When multiple symbols are defined with the same name, which can happen when a symbol is both // in the symbol table and self-bound, `GetSymbolByRawName` prefers the symbol with the lowest // type value. Since `ImportAddressSymbol` is a lower value than `DataSymbol`/`FunctionSymbol`, // it would return the import stub rather than the actual symbol definition. Filter it out. auto symbols = view->GetSymbolsByRawName(name, view->GetInternalNameSpace()); auto it = std::ranges::find_if(symbols, [](const Ref& sym) { return sym->GetType() != ImportAddressSymbol; }); return it != symbols.end() ? *it : nullptr; } static Ref ResolveBindSymbol(BinaryView* view, const MachOHeader& header, const std::string& name, int32_t ordinal) { switch (ordinal) { case BindSpecialDylibSelf: return FindInternalSymbol(view, name); case BindSpecialDylibMainExecutable: case BindSpecialDylibFlatLookup: case BindSpecialDylibWeakLookup: { Ref symbol; // Prefer internal symbols for executables, and external symbols for everything else. if (header.ident.filetype == MH_EXECUTE) symbol = FindInternalSymbol(view, name); if (!symbol) symbol = view->GetSymbolByRawName(name, view->GetExternalNameSpace()); if (!symbol && header.ident.filetype != MH_EXECUTE) symbol = FindInternalSymbol(view, name); return symbol; } default: if (ordinal > 0) return view->GetSymbolByRawName(name, view->GetExternalNameSpace()); return nullptr; } } bool MachoView::InitializeHeader(MachOHeader& header, bool isMainHeader, uint64_t preferredImageBase, std::string preferredImageBaseDesc, bool platformSetByUser) { Ref settings = GetLoadSettings(GetTypeName()); for (auto& i : header.segments) i.vmaddr += m_imageBaseAdjustment; for (auto& i : header.sections) i.addr += m_imageBaseAdjustment; for (auto& i : header.symbolStubSections) i.addr += m_imageBaseAdjustment; for (auto& i : header.symbolPointerSections) i.addr += m_imageBaseAdjustment; for (auto& entryPoint : header.entryPoints) entryPoint.first += (entryPoint.second ? 0 : m_imageBaseAdjustment); if (header.routinesPresent) header.routines64.init_address += m_imageBaseAdjustment; for (auto& segment : header.segments) { if ((segment.initprot == MACHO_VM_PROT_NONE) || (!segment.vmsize)) continue; if ((segment.initprot & MACHO_VM_PROT_EXECUTE) == MACHO_VM_PROT_EXECUTE) { if ((segment.fileoff == (0 + m_universalImageOffset)) && (segment.filesize != 0)) header.textBase = segment.vmaddr; for (auto& entryPoint : header.entryPoints) { uint64_t val = entryPoint.first + (entryPoint.second ? header.textBase : 0); if (find(header.m_entryPoints.begin(), header.m_entryPoints.end(), val) == header.m_entryPoints.end()) header.m_entryPoints.push_back(val); } } } // If the binary contains a __KLD segment, segment flags will be based on XNU's known // initial segment permissions rather than the flags stored in the Mach-O headers. bool isXNU = std::any_of(header.segments.begin(), header.segments.end(), [](const segment_command_64& seg) { return strncmp(seg.segname, "__KLD", 16) == 0; }); if (!(m_header.ident.filetype == MH_FILESET && isMainHeader)) \ { BeginBulkAddSegments(); for (auto &segment: header.segments) { if ((segment.initprot == MACHO_VM_PROT_NONE) || (!segment.vmsize)) continue; uint32_t flags = SegmentFlagsForSegment(isXNU, segment); // if we're positive we have an entry point for some reason, force the segment // executable. this helps with kernel images. for (auto &entryPoint: header.m_entryPoints) if (segment.vmaddr <= entryPoint && (entryPoint < (segment.vmaddr + segment.filesize))) flags |= SegmentExecutable; AddAutoSegment(segment.vmaddr, segment.vmsize, segment.fileoff, segment.filesize, flags); } EndBulkAddSegments(); if (auto memoryMap = GetMemoryMap()) { for (auto& segment : header.segments) { if (segment.initprot == MACHO_VM_PROT_NONE || !segment.vmsize) continue; auto region = memoryMap->GetActiveMemoryRegionAt(segment.vmaddr); if (region.empty()) continue; std::string segmentName(segment.segname, std::find(segment.segname, std::end(segment.segname), '\0')); memoryMap->SetMemoryRegionDisplayName(region, segmentName); if (segment.vmsize == segment.filesize) continue; uint64_t zeroFillStart = segment.vmaddr + segment.filesize; auto zeroFillRegion = memoryMap->GetActiveMemoryRegionAt(zeroFillStart); if (!zeroFillRegion.empty()) memoryMap->SetMemoryRegionDisplayName(zeroFillRegion, segmentName + " (zero fill)"); } } for (auto& section : header.sections) { char sectionName[17]; memcpy(sectionName, section.sectname, sizeof(section.sectname)); sectionName[16] = 0; if (header.identifierPrefix.empty()) header.sectionNames.push_back(sectionName); else header.sectionNames.push_back(header.identifierPrefix + "::" + sectionName); } header.sectionNames = GetUniqueSectionNames(header.sectionNames); } for (size_t i = 0; i < header.sections.size(); i++) { if (!header.sections[i].size) continue; string type; BNSectionSemantics semantics = DefaultSectionSemantics; switch (header.sections[i].flags & SECTION_TYPE) { case S_REGULAR: if (header.sections[i].flags & S_ATTR_PURE_INSTRUCTIONS) { type = "PURE_CODE"; semantics = ReadOnlyCodeSectionSemantics; } else if (header.sections[i].flags & S_ATTR_SOME_INSTRUCTIONS) { type = "CODE"; semantics = ReadOnlyCodeSectionSemantics; } else { type = "REGULAR"; } break; case S_ZEROFILL: type = "ZEROFILL"; semantics = ReadWriteDataSectionSemantics; break; case S_CSTRING_LITERALS: type = "CSTRING_LITERALS"; semantics = ReadOnlyDataSectionSemantics; break; case S_4BYTE_LITERALS: type = "4BYTE_LITERALS"; break; case S_8BYTE_LITERALS: type = "8BYTE_LITERALS"; break; case S_LITERAL_POINTERS: type = "LITERAL_POINTERS"; semantics = ReadOnlyDataSectionSemantics; break; case S_NON_LAZY_SYMBOL_POINTERS: type = "NON_LAZY_SYMBOL_POINTERS"; semantics = ReadOnlyDataSectionSemantics; break; case S_LAZY_SYMBOL_POINTERS: type = "LAZY_SYMBOL_POINTERS"; semantics = ReadOnlyDataSectionSemantics; break; case S_SYMBOL_STUBS: type = "SYMBOL_STUBS"; semantics = ReadOnlyCodeSectionSemantics; break; case S_MOD_INIT_FUNC_POINTERS: type = "MOD_INIT_FUNC_POINTERS"; semantics = ReadOnlyDataSectionSemantics; break; case S_MOD_TERM_FUNC_POINTERS: type = "MOD_TERM_FUNC_POINTERS"; semantics = ReadOnlyDataSectionSemantics; break; case S_COALESCED: type = "COALESCED"; break; case S_GB_ZEROFILL: type = "GB_ZEROFILL"; semantics = ReadWriteDataSectionSemantics; break; case S_INTERPOSING: type = "INTERPOSING"; break; case S_16BYTE_LITERALS: type = "16BYTE_LITERALS"; break; case S_DTRACE_DOF: type = "DTRACE_DOF"; break; case S_LAZY_DYLIB_SYMBOL_POINTERS: type = "LAZY_DYLIB_SYMBOL_POINTERS"; semantics = ReadOnlyDataSectionSemantics; break; case S_THREAD_LOCAL_REGULAR: type = "THREAD_LOCAL_REGULAR"; break; case S_THREAD_LOCAL_ZEROFILL: type = "THREAD_LOCAL_ZEROFILL"; break; case S_THREAD_LOCAL_VARIABLES: type = "THREAD_LOCAL_VARIABLES"; break; case S_THREAD_LOCAL_VARIABLE_POINTERS: type = "THREAD_LOCAL_VARIABLE_POINTERS"; break; case S_THREAD_LOCAL_INIT_FUNCTION_POINTERS: type = "THREAD_LOCAL_INIT_FUNCTION_POINTERS"; break; case S_INIT_FUNC_OFFSETS: type = "INIT_FUNC_OFFSETS"; semantics = ReadOnlyDataSectionSemantics; break; default: type = "UNKNOWN"; break; } if (i >= header.sectionNames.size()) break; if (strncmp(header.sections[i].sectname, "__text", sizeof(header.sections[i].sectname)) == 0) semantics = ReadOnlyCodeSectionSemantics; if (strncmp(header.sections[i].sectname, "__const", sizeof(header.sections[i].sectname)) == 0) semantics = ReadOnlyDataSectionSemantics; if (strncmp(header.sections[i].sectname, "__data", sizeof(header.sections[i].sectname)) == 0) semantics = ReadWriteDataSectionSemantics; if (strncmp(header.sections[i].segname, "__DATA_CONST", sizeof(header.sections[i].segname)) == 0) semantics = ReadOnlyDataSectionSemantics; if (strncmp(header.sections[i].sectname, "__chain_starts", sizeof(header.sections[i].sectname)) == 0 && header.sections[i].size < UINT32_MAX) { header.chainStartsPresent = true; header.chainStarts = header.sections[i]; } if (uint32_t overriddenSemantics = OverriddenSectionSemanticsForSection(isXNU, header.sections[i])) semantics = static_cast(overriddenSemantics); AddAutoSection(header.sectionNames[i], header.sections[i].addr, header.sections[i].size, semantics, type, header.sections[i].align); } if (isMainHeader) { // Validate architecture if (!m_arch) { // Parse only mode returns true, even if no arch support if (m_parseOnly) return true; bool is64Bit; string archName = UniversalTransform::ArchitectureToString(m_archId, 0, is64Bit); if (!archName.empty()) { #ifdef DEMO_EDITION if ((archName == "arm64") /* MACHO_CPU_TYPE_ARM64 */ || (archName == "arm64v8") || (archName == "arm64e") || (archName == "arm64_32") /* MACHO_CPU_TYPE_ARM64_32 */ || (archName == "arm64_32v8") || (archName == "ppc") /* MACHO_CPU_TYPE_POWERPC */ || (archName == "ppc601") || (archName == "ppc602") || (archName == "ppc603") || (archName == "ppc603e") || (archName == "ppc603ev") || (archName == "ppc604") || (archName == "ppc604e") || (archName == "ppc620") || (archName == "ppc750") || (archName == "ppc7400") || (archName == "ppc7450") || (archName == "ppc970") || (archName == "ppc64") /* MACHO_CPU_TYPE_POWERPC64 */ ) { m_logger->LogAlert( "Binary Ninja free does not support Mach-O architecture '%s'. " "Purchase Binary Ninja to unlock all features.", archName.c_str()); return false; } #endif m_logger->LogAlert( "Mach-O architecture '%s' is not explicitly supported. Try 'Open with Options' to manually select " "a compatible architecture.", archName.c_str()); } else { m_logger->LogAlert( "Mach-O architecture 0x%x is not explicitly supported. Try 'Open with Options' to manually select " "a compatible architecture.", m_archId); } return false; } // Apply architecture and platform Ref platform = m_plat ? m_plat : g_machoViewType->GetPlatform(0, m_arch); if (!platform) platform = m_arch->GetStandalonePlatform(); if (header.m_entryPoints.size() > 0 && !platformSetByUser) platform = platform->GetAssociatedPlatformByAddress(header.m_entryPoints[0]); SetDefaultPlatform(platform); SetDefaultArchitecture(platform->GetArchitecture()); // Finished for parse only mode if (m_parseOnly) return true; } if (m_parseOnly) return true; BinaryReader reader(GetParentView()); reader.SetEndianness(m_endian); reader.SetVirtualBase(m_universalImageOffset); BinaryReader virtualReader(this); virtualReader.SetEndianness(m_endian); if (m_file->IsBackedByDatabase(GetTypeName())) { // The `core.function.objectiveC` workflow has been removed in favor of the meta-analysis // workflow. If the file is backed by a database, we need to update any references to the // old workflow to refer to the meta-analysis workflow. Ref analysisSettings = Settings::Instance(); auto workflow = analysisSettings->Get("analysis.workflows.functionWorkflow", this); if (workflow == "core.function.objectiveC") analysisSettings->Set("analysis.workflows.functionWorkflow", "core.function.metaAnalysis", this); } bool parseObjCStructs = true; bool parseCFStrings = true; if (settings && settings->Contains("loader.macho.processObjectiveC")) parseObjCStructs = settings->Get("loader.macho.processObjectiveC", this); if (settings && settings->Contains("loader.macho.processCFStrings")) parseCFStrings = settings->Get("loader.macho.processCFStrings", this); if (!MachoObjCProcessor::ViewHasObjCMetadata(this)) parseObjCStructs = false; if (!GetSectionByName("__cfstring")) parseCFStrings = false; std::unique_ptr objcProcessor; if (parseObjCStructs || parseCFStrings) { objcProcessor = std::make_unique(this); } // parse thread starts section if available bool rebaseThreadStarts = false; auto theadStartSection = GetSectionByName("__thread_starts"); vector threadStarts; uint64_t stepMultiplier; if (theadStartSection) { size_t count = theadStartSection->GetLength() / 4; threadStarts.reserve(count); virtualReader.Seek(theadStartSection->GetStart()); stepMultiplier = virtualReader.Read32() & 0x1 ? 8 : 4; for (uint32_t i = 1; i < count; i++) threadStarts.push_back(virtualReader.Read32()); rebaseThreadStarts = true; if (settings && settings->Contains("loader.macho.rebaseThreadStarts")) rebaseThreadStarts = settings->Get("loader.macho.rebaseThreadStarts", this); } if (rebaseThreadStarts) RebaseThreadStarts(virtualReader, threadStarts, stepMultiplier); // Add module Init functions if they exist size_t modInitFuncCnt = 0; for (const auto& moduleInitSection : header.moduleInitSections) { // ignore mod_init functions that are rebased as part of thread starts if (find(threadStarts.begin(), threadStarts.end(), moduleInitSection.offset) != threadStarts.end()) continue; size_t i = 0; reader.Seek(moduleInitSection.offset); if ((moduleInitSection.flags & SECTION_TYPE) == S_MOD_INIT_FUNC_POINTERS) { // The mod_init section contains a list of function pointers called at initialization // if we don't have a defined entrypoint then use the first one in the list as the entrypoint for (; i < (moduleInitSection.size / m_addressSize); i++) { uint64_t target = (m_addressSize == 4) ? reader.Read32() : reader.Read64(); target += m_imageBaseAdjustment; if (m_header.ident.filetype == MH_FILESET) { // FIXME: This isn't a super robust way of detagging, // should look into xnu source and the tools used to build this cache (if they're public) // and see if anything better can be done // mask out top 8 bits uint64_t tag = 0xFFFFFFFF00000000 & header.textBase; // and combine them with bottom 8 of the original entry target = tag | (target & 0xFFFFFFFF); } Ref targetPlatform = GetDefaultPlatform()->GetAssociatedPlatformByAddress(target); auto name = "mod_init_func_" + to_string(modInitFuncCnt++); AddEntryPointForAnalysis(targetPlatform, target); auto symbol = new Symbol(FunctionSymbol, name, target, GlobalBinding); DefineAutoSymbol(symbol); } } else if ((moduleInitSection.flags & SECTION_TYPE) == S_INIT_FUNC_OFFSETS) { // The init_offsets section contains a list of 32-bit RVA offsets to functions called at initialization // if we don't have a defined entrypoint then use the first one in the list as the entrypoint for (; i < (moduleInitSection.size / 4); i++) { uint64_t target = reader.Read32(); target += header.textBase; Ref targetPlatform = GetDefaultPlatform()->GetAssociatedPlatformByAddress(target); auto name = "mod_init_func_" + to_string(modInitFuncCnt++); AddEntryPointForAnalysis(targetPlatform, target); auto symbol = new Symbol(FunctionSymbol, name, target, GlobalBinding); DefineAutoSymbol(symbol); // FIXME: i don't know how to define proper pointer type at this stage of analysis Ref pointerVar = TypeBuilder::PointerType(4, Type::VoidType()) .SetPointerBase(RelativeToConstantPointerBaseType, header.textBase) .Finalize(); DefineDataVariable(GetStart() + reader.GetOffset() - 4, pointerVar); } } } if (isMainHeader) { vector> libraries; vector> libraryFound; for (auto& [libName, libVersion] : header.dylibs) { if (!GetExternalLibrary(libName)) { AddExternalLibrary(libName, {}, true); } libraries.push_back(new Metadata(string(libName))); // Compose exact name with {install_name}.{platform}.{version} std::string libNameExact = fmt::format("{}.{}.{}", libName, GetDefaultPlatform()->GetName(), libVersion); Ref typeLib = GetTypeLibrary(libNameExact); if (!typeLib) { vector> typeLibs = GetDefaultPlatform()->GetTypeLibrariesByName(libNameExact); if (typeLibs.size()) { typeLib = typeLibs[0]; AddTypeLibrary(typeLib); m_logger->LogDebugF("mach-o: adding type library for {:?}: {} ({})", libName, typeLib->GetName(), typeLib->GetGuid()); } } if (!typeLib) { typeLib = GetTypeLibrary(libName); if (!typeLib) { vector> typeLibs = GetDefaultPlatform()->GetTypeLibrariesByName(libName); if (typeLibs.size()) { typeLib = typeLibs[0]; AddTypeLibrary(typeLib); m_logger->LogDebugF("mach-o: adding type library for {}: {} ({})", libName, typeLib->GetName(), typeLib->GetGuid()); } } } if (typeLib) libraryFound.push_back(new Metadata(typeLib->GetName())); else libraryFound.push_back(new Metadata(string(""))); } StoreMetadata("Libraries", new Metadata(libraries), true); StoreMetadata("LibraryFound", new Metadata(libraryFound), true); } bool first = true; for (auto entry : header.m_entryPoints) { if (m_header.ident.filetype == MH_FILESET) { // FIXME: This isn't a super robust way of detagging, // should look into xnu source and the tools used to build this cache (if they're public) // and see if anything better can be done // mask out top 8 bits uint64_t tag = 0xFFFFFFFF00000000 & header.textBase; // and combine them with bottom 8 of the original entry entry = tag | (entry & 0xFFFFFFFF); } // We set the BinaryView's default platform based on this already modified entry point address // so we do not need to (again) GetAssociatedPlatformByAddress here. AddEntryPointForAnalysis(GetDefaultPlatform(), entry); if (first) { first = false; DefineAutoSymbol(new Symbol(FunctionSymbol, "_start", entry)); } } vector indirectSymbols; try { // Handle indirect symbols if (header.dysymtab.nindirectsyms) { indirectSymbols.resize(header.dysymtab.nindirectsyms); reader.Seek(header.dysymtab.indirectsymoff); reader.Read(&indirectSymbols[0], header.dysymtab.nindirectsyms * sizeof(uint32_t)); } } catch (ReadException&) { m_logger->LogError("Failed to read indirect symbol data"); } bool parseFunctionStarts = true; if (settings && settings->Contains("loader.macho.processFunctionStarts")) parseFunctionStarts = settings->Get("loader.macho.processFunctionStarts", this); if (parseFunctionStarts) { m_logger->LogDebug("Parsing function starts\n"); if (header.functionStartsPresent) ParseFunctionStarts(GetDefaultPlatform(), header.textBase, header.functionStarts); } BulkSymbolModification bulkSymbolModification(this); m_symbolQueue = new SymbolQueue(); std::unordered_map symbolLibraryMapping; try { // Add functions for all function symbols m_logger->LogDebug("Parsing symbol table\n"); ParseSymbolTable(reader, header, header.symtab, indirectSymbols, objcProcessor.get(), symbolLibraryMapping); } catch (std::exception&) { m_logger->LogError("Failed to parse symbol table!"); } m_symbolQueue->Process(); delete m_symbolQueue; m_symbolQueue = nullptr; bulkSymbolModification.End(); for (auto& relocation : header.rebaseRelocations) { uint64_t relocationLocation = relocation.address; virtualReader.Seek(relocationLocation); uint64_t target = virtualReader.ReadPointer(); uint64_t slidTarget = target + m_imageBaseAdjustment; relocation.address = slidTarget; DefineRelocation(m_arch, relocation, slidTarget, relocationLocation); if (objcProcessor) objcProcessor->AddRelocatedPointer(relocationLocation, slidTarget); } for (auto& [relocation, name, ordinal] : header.bindingRelocations) { if (auto symbol = ResolveBindSymbol(this, header, name, ordinal); symbol) { DefineRelocation(m_arch, relocation, symbol, relocation.address); if (objcProcessor && symbol->GetNameSpace() == GetInternalNameSpace()) objcProcessor->AddRelocatedPointer(relocation.address, symbol->GetAddress()); } else { m_logger->LogErrorF("Failed to find symbol {:?} for bind at {:#x} (ordinal: {})", name, relocation.address, ordinal); } string libName; if (ordinal > 0 && ordinal - 1 < header.dylibs.size()) libName = header.dylibs[ordinal - 1].first; else if (ordinal == BindSpecialDylibMainExecutable) libName = kPseudoLibraryMainExecutable; else if (ordinal == BindSpecialDylibFlatLookup) libName = kPseudoLibraryFlatLookup; else if (ordinal == BindSpecialDylibWeakLookup) libName = kPseudoLibraryWeakLookup; if (!libName.empty()) { if (!GetExternalLibrary(libName)) AddExternalLibrary(libName, {}, true); symbolLibraryMapping[name] = libName; } } Ref symbolToLibraryMapping = new Metadata(KeyValueDataType); for (const auto& [name, libName] : symbolLibraryMapping) symbolToLibraryMapping->SetValueForKey(name, new Metadata(libName)); StoreMetadata("SymbolExternalLibraryMapping", std::move(symbolToLibraryMapping), true); auto relocationHandler = m_arch->GetRelocationHandler("Mach-O"); if (relocationHandler) { // FIXME: this if statement block really needs to be a function try { // For executables the relocations are attached to each of the sections // In libraries these are zeroed out and collected in the dysymtab vector infoList; for (auto& section : header.sections) { if (section.nreloc == 0) continue; char sectionName[17]; memcpy(sectionName, section.sectname, sizeof(section.sectname)); sectionName[16] = 0; m_logger->LogDebugF("Relocations for section {:?}", sectionName); auto sec = GetSectionByName(sectionName); if (!sec) { m_logger->LogErrorF("Can't find section for {:?}", sectionName); continue; } for (size_t i = 0; i < section.nreloc; i++) { relocation_info info; reader.Seek(section.reloff + (i * sizeof(relocation_info))); reader.Read(&info, sizeof(info)); BNRelocationInfo result; memset(&result, 0, sizeof(result)); if (ParseRelocationEntry(info, sec->GetStart(), result)) infoList.push_back(result); } } if (relocationHandler->GetRelocationInfo(this, m_arch, infoList)) { unordered_map>> symbolCache; for (auto& reloc: infoList) { if (reloc.symbolIndex >= m_symbols.size()) continue; // retrieve first symbol that is not a symbol relocation vector> symbols; auto symName = m_symbols[reloc.symbolIndex]; if (auto itr = symbolCache.find(symName); itr != symbolCache.end()) { symbols = itr->second; } else { symbols = GetSymbolsByName(symName); symbolCache[symName] = symbols; } for (const auto& symbol : symbols) { if (symbol->GetAddress() == reloc.address) continue; DefineRelocation(m_arch, reloc, symbol, reloc.address); break; } } } infoList.clear(); // Handle local relocations for dynamic libraries for (size_t i = 0; i < header.dysymtab.nlocrel; i++) { relocation_info info; reader.Seek(header.dysymtab.locreloff + (i * sizeof(relocation_info))); reader.Read(&info, sizeof(info)); BNRelocationInfo result; memset(&result, 0, sizeof(result)); if (ParseRelocationEntry(info, header.relocationBase, result)) infoList.push_back(result); } if (relocationHandler->GetRelocationInfo(this, m_arch, infoList)) { for (auto& reloc: infoList) { // TODO will matter once rebasing lands if (!reloc.external) continue; if (reloc.symbolIndex >= m_symbols.size()) continue; // retrieve first symbol that is not a symbol relocation auto symbols = GetSymbolsByName(m_symbols[reloc.symbolIndex]); for (const auto& symbol : symbols) { if (symbol->GetAddress() == reloc.address) continue; DefineRelocation(m_arch, reloc, symbol, reloc.address); break; } } } infoList.clear(); // Handle external relocations for dynamic libraries for (size_t i = 0; i < header.dysymtab.nextrel; i++) { relocation_info info; reader.Seek(header.dysymtab.extreloff + (i * sizeof(relocation_info))); reader.Read(&info, sizeof(info)); BNRelocationInfo result; memset(&result, 0, sizeof(result)); if (ParseRelocationEntry(info, header.relocationBase, result)) infoList.push_back(result); } if (relocationHandler->GetRelocationInfo(this, m_arch, infoList)) { for (auto& reloc: infoList) { // TODO will matter once rebasing lands if (!reloc.external) continue; if (reloc.symbolIndex >= m_symbols.size()) continue; // retrieve first symbol that is not a symbol relocation auto symbols = GetSymbolsByName(m_symbols[reloc.symbolIndex]); for (const auto& symbol : symbols) { if (symbol->GetAddress() == reloc.address) continue; DefineRelocation(m_arch, reloc, symbol, reloc.address); break; } } } infoList.clear(); } catch (ReadException&) { m_logger->LogError("Failed to read relocation data"); } } vector> machoHeaderStarts; if (isMainHeader) { machoHeaderStarts.emplace_back(header.textBase, ""); if (header.textBase != preferredImageBase) machoHeaderStarts.emplace_back(preferredImageBase, preferredImageBaseDesc); } else { machoHeaderStarts.emplace_back(preferredImageBase, preferredImageBaseDesc); } // Apply Mach-O header types for (auto [imageBase, imageDesc] : machoHeaderStarts) { string errorMsg; mach_header_64 mappedIdent; uint64_t loadCommandOffset; loadCommandOffset = g_machoViewType->ParseHeaders(this, imageBase, mappedIdent, nullptr, nullptr, errorMsg); if (!loadCommandOffset) continue; DefineDataVariable(imageBase, Type::NamedType(this, m_typeNames.headerQualName)); DefineAutoSymbol(new Symbol(DataSymbol, "__macho_header" + imageDesc, imageBase, LocalBinding)); try { virtualReader.Seek(imageBase + loadCommandOffset); size_t sectionNum = 0; for (size_t i = 0; i < mappedIdent.ncmds; i++) { load_command load; uint64_t curOffset = virtualReader.GetOffset(); load.cmd = virtualReader.Read32(); load.cmdsize = virtualReader.Read32(); uint64_t nextOffset = curOffset + load.cmdsize; switch (load.cmd) { case LC_SEGMENT: { DefineDataVariable(curOffset, Type::NamedType(this, m_typeNames.loadSegmentCommandQualName)); virtualReader.SeekRelative(5 * 8); size_t numSections = virtualReader.Read32(); virtualReader.SeekRelative(4); for (size_t j = 0; j < numSections; j++) { DefineDataVariable(virtualReader.GetOffset(), Type::NamedType(this, m_typeNames.sectionQualName)); DefineAutoSymbol(new Symbol(DataSymbol, "__macho_section" + imageDesc + "_[" + to_string(sectionNum++) + "]", virtualReader.GetOffset(), LocalBinding)); virtualReader.SeekRelative((8 * 8) + 4); } break; } case LC_SEGMENT_64: { DefineDataVariable(curOffset, Type::NamedType(this, m_typeNames.loadSegment64CommandQualName)); virtualReader.SeekRelative(7 * 8); size_t numSections = virtualReader.Read32(); virtualReader.SeekRelative(4); for (size_t j = 0; j < numSections; j++) { DefineDataVariable(virtualReader.GetOffset(), Type::NamedType(this, m_typeNames.section64QualName)); DefineAutoSymbol(new Symbol(DataSymbol, "__macho_section_64" + imageDesc + "_[" + to_string(sectionNum++) + "]", virtualReader.GetOffset(), LocalBinding)); virtualReader.SeekRelative(10 * 8); } break; } case LC_SYMTAB: DefineDataVariable(curOffset, Type::NamedType(this, m_typeNames.symtabQualName)); break; case LC_DYSYMTAB: DefineDataVariable(curOffset, Type::NamedType(this, m_typeNames.dynsymtabQualName)); break; case LC_UUID: DefineDataVariable(curOffset, Type::NamedType(this, m_typeNames.uuidQualName)); break; case LC_ID_DYLIB: case LC_LOAD_DYLIB: case LC_REEXPORT_DYLIB: case LC_LOAD_WEAK_DYLIB: case LC_LOAD_UPWARD_DYLIB: DefineDataVariable(curOffset, Type::NamedType(this, m_typeNames.dylibCommandQualName)); if (load.cmdsize-24 <= 150) DefineDataVariable(curOffset + 24, Type::ArrayType(Type::IntegerType(1, true), load.cmdsize-24)); break; case LC_CODE_SIGNATURE: case LC_SEGMENT_SPLIT_INFO: case LC_FUNCTION_STARTS: case LC_DATA_IN_CODE: case LC_DYLIB_CODE_SIGN_DRS: case LC_DYLD_EXPORTS_TRIE: case LC_DYLD_CHAINED_FIXUPS: DefineDataVariable(curOffset, Type::NamedType(this, m_typeNames.linkeditDataQualName)); break; case LC_ENCRYPTION_INFO: DefineDataVariable(curOffset, Type::NamedType(this, m_typeNames.encryptionInfoQualName)); break; case LC_VERSION_MIN_MACOSX: case LC_VERSION_MIN_IPHONEOS: DefineDataVariable(curOffset, Type::NamedType(this, m_typeNames.versionMinQualName)); break; case LC_DYLD_INFO: case LC_DYLD_INFO_ONLY: DefineDataVariable(curOffset, Type::NamedType(this, m_typeNames.dyldInfoQualName)); break; case LC_FILESET_ENTRY: DefineDataVariable(curOffset, Type::NamedType(this, m_typeNames.filesetEntryCommandQualName)); if (load.cmdsize-0x20 <= 150) DefineDataVariable(curOffset + 0x20, Type::ArrayType(Type::IntegerType(1, true), load.cmdsize-0x20)); break; default: DefineDataVariable(curOffset, Type::NamedType(this, m_typeNames.loadCommandQualName)); break; } DefineAutoSymbol(new Symbol(DataSymbol, "__macho_load_command" + imageDesc + "_[" + to_string(i) + "]", curOffset, LocalBinding)); virtualReader.Seek(nextOffset); } } catch (ReadException&) { LogError("Error when applying Mach-O header types at %" PRIx64, imageBase); } } if (parseCFStrings) { try { objcProcessor->ProcessObjCLiterals(); } catch (std::exception& ex) { m_logger->LogError("Failed to process CFStrings. Binary may be malformed"); m_logger->LogErrorF("Error: {:?}", ex.what()); } } if (parseObjCStructs) { try { objcProcessor->ProcessObjCData(); } catch (std::exception& ex) { m_logger->LogError("Failed to process Objective-C Metadata. Binary may be malformed"); m_logger->LogErrorF("Error: {:?}", ex.what()); } } return true; } Ref MachoView::DefineMachoSymbol( BNSymbolType type, const string& name, uint64_t addr, BNSymbolBinding binding, bool deferred) { // If name is empty, symbol is not valid if (name.size() == 0) return nullptr; if (type != ExternalSymbol) { // Don't create symbols that are present in the database snapshot now. If it is not a // deferrable symbol, it may be used in relocations, so don't ignore those. if (m_backedByDatabase && deferred) return nullptr; // Ensure symbol is within the executable bool ok = false; for (auto& segment : m_header.segments) { if ((segment.initprot == MACHO_VM_PROT_NONE) || (!segment.vmsize)) continue; if ((addr >= segment.vmaddr) && (addr < (segment.vmaddr + segment.vmsize))) { ok = true; break; } } if (!ok) return nullptr; } Ref symbolTypeRef; if ((type == ExternalSymbol) || (type == ImportAddressSymbol) || (type == ImportedDataSymbol)) { QualifiedName n(name); Ref appliedLib; symbolTypeRef = ImportTypeLibraryObject(appliedLib, n); if (symbolTypeRef) { m_logger->LogDebugF("mach-o: type Library {:?} found hit for {:?}", appliedLib->GetName(), name); RecordImportedObjectLibrary(GetDefaultPlatform(), addr, appliedLib, n); } } auto process = [=, this]() { // If name does not start with alphabetic character or symbol, prepend an underscore string rawName = name; if (!(((name[0] >= 'A') && (name[0] <= 'Z')) || ((name[0] >= 'a') && (name[0] <= 'z')) || (name[0] == '_') || (name[0] == '?') || (name[0] == '$') || (name[0] == '@'))) rawName = "_" + name; NameSpace nameSpace = GetInternalNameSpace(); if (type == ExternalSymbol) { nameSpace = GetExternalNameSpace(); } // Try to demangle any C++ symbols string shortName = rawName; string fullName = rawName; Ref typeRef = symbolTypeRef; if (m_arch) { QualifiedName demangledName; Ref demangledType; if (DemangleGeneric(m_arch, rawName, demangledType, demangledName, nullptr, m_simplifyTemplates)) { shortName = demangledName.GetString(); fullName = shortName; if (demangledType) fullName += demangledType->GetStringAfterName(); if (!typeRef && m_extractMangledTypes && !GetDefaultPlatform()->GetFunctionByName(rawName)) typeRef = demangledType; } } if ((type == ExternalSymbol || type == ImportAddressSymbol) && (name.find("_objc_retain_x") != std::string::npos || name.find("_objc_release_x") != std::string::npos)) { auto x = name.rfind('x'); auto num = name.substr(x + 1); auto cc = GetDefaultArchitecture()->GetCallingConventionByName("apple-arm64-objc-fast-arc-" + num); if (auto idType = GetTypeByName({"id"}); cc && idType) typeRef = Type::FunctionType(idType, cc, {{"obj", idType}}); } return std::pair, Ref>( new Symbol(type, shortName, fullName, rawName, addr, binding, nameSpace), typeRef); }; if (deferred) { m_symbolQueue->Append(process, [this](Symbol* symbol, const Confidence>& type) { DefineAutoSymbolAndVariableOrFunction(GetDefaultPlatform(), symbol, type); }); return nullptr; } auto result = process(); return DefineAutoSymbolAndVariableOrFunction(GetDefaultPlatform(), result.first, result.second); } bool MachoView::GetSegmentPermissions(MachOHeader& header, uint64_t address, uint32_t &flags) { for (auto& segment : header.segments) { if ((segment.initprot == MACHO_VM_PROT_NONE) || (!segment.vmsize)) continue; if (address >= segment.vmaddr && address < (segment.vmaddr + segment.vmsize)) { flags = segment.flags; return true; } } flags = 0; return false; } bool MachoView::GetSectionPermissions(MachOHeader& header, uint64_t address, uint32_t &flags) { for (auto& section : header.sections) { if (!section.size) continue; if (address >= section.addr && address < (section.addr + section.size)) { flags = section.flags; return true; } } flags = 0; return false; } bool MachoView::AddExportTerminalSymbol( const std::string& symbolName, uint64_t symbolFlags, uint64_t imageOffset) { if (symbolFlags & EXPORT_SYMBOL_FLAGS_REEXPORT) { m_logger->LogTraceF("Export symbol is a re-export, not supported: {:?}", symbolName); return false; } uint64_t symbolAddress = GetStart() + imageOffset; if (symbolName.empty() || symbolAddress == 0) { m_logger->LogTraceF("Export symbol is malformed: {:?}", symbolName); return false; } // Tries to get the symbol type based off the section containing it. auto sectionSymbolType = [&]() -> BNSymbolType { uint32_t sectionFlags = 0; for (const auto& section : m_allSections) { if (symbolAddress >= section.addr && symbolAddress < section.addr + section.size) { // Take the flags from the first containing section. sectionFlags = section.flags; break; } } // TODO: Is this enough to determine a function symbol? // TODO: Might be the cause of https://github.com/Vector35/binaryninja-api/issues/6526 // Check the sections flags to see if we actually have a function symbol instead. if (sectionFlags & S_ATTR_PURE_INSTRUCTIONS || sectionFlags & S_ATTR_SOME_INSTRUCTIONS) return FunctionSymbol; // FIXME: See above, no it is not. Fallback on old logic here to avoid breaking export symbols in __text on regular Mach-Os. auto symbolType = GetAnalysisFunctionsForAddress(GetStart() + imageOffset).size() ? FunctionSymbol : DataSymbol; return symbolType; }; switch (symbolFlags & EXPORT_SYMBOL_FLAGS_KIND_MASK) { case EXPORT_SYMBOL_FLAGS_KIND_REGULAR: case EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL: m_logger->LogTraceF("Export symbol is a regular or thread local symbol: {} {:?}", sectionSymbolType(), symbolName); DefineMachoSymbol(sectionSymbolType(), symbolName, symbolAddress, GlobalBinding, false); break; case EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE: m_logger->LogTraceF("Export symbol is an absolute symbol: {:?}", symbolName); DefineMachoSymbol(DataSymbol, symbolName, symbolAddress, GlobalBinding, false); break; default: m_logger->LogWarnF("Unhandled export symbol kind: {:#x}", symbolFlags & EXPORT_SYMBOL_FLAGS_KIND_MASK); return false; } m_logger->LogTraceF("Successfully added export symbol: {:?}", symbolName); return true; } void MachoView::ParseExportTrie(BinaryReader& reader, linkedit_data_command exportTrie) { try { uint32_t endGuard = exportTrie.datasize; DataBuffer buffer = GetParentView() ->ReadBuffer(m_universalImageOffset + exportTrie.dataoff, exportTrie.datasize); struct Node { uint64_t cursor; std::string text; }; std::vector stack; stack.reserve(64); stack.push_back({ /* cursor */ 0, /* text */ "" }); while (!stack.empty()) { m_logger->LogTraceF("Export Trie: Processing node {:?} with cursor {:#x}", stack.back().text, stack.back().cursor); Node node = std::move(stack.back()); stack.pop_back(); uint64_t cursor = node.cursor; const std::string currentText = std::move(node.text); if (cursor > endGuard) { m_logger->LogError("Export Trie: Cursor left trie during initial bounds check"); throw ReadException(); } size_t localCursor = cursor; uint64_t terminalSize = readValidULEB128(buffer, localCursor); uint64_t childOffset = localCursor + terminalSize; // If there's terminal data, define the symbol if (terminalSize != 0) { uint64_t flags = readValidULEB128(buffer, localCursor); uint64_t imageOffset = readValidULEB128(buffer, localCursor); m_logger->LogTraceF("Export Trie: Found terminal node {:?} with flags {:#x} and image offset {:#x}", currentText, flags, imageOffset); AddExportTerminalSymbol(currentText, flags, imageOffset); } localCursor = childOffset; if (localCursor > endGuard) { m_logger->LogError("Export Trie: Cursor left trie while moving to child offset"); throw ReadException(); } uint8_t childCount = buffer[localCursor++]; if (localCursor > endGuard) { m_logger->LogError("Export Trie: Cursor left trie while reading child count"); throw ReadException(); } std::vector children; children.reserve(childCount); for (uint8_t i = 0; i < childCount; ++i) { if (localCursor > endGuard) { m_logger->LogError("Export Trie: Cursor left trie while reading child count"); throw ReadException(); } std::string childText; while (localCursor <= endGuard && buffer[localCursor] != 0) { childText.push_back(buffer[localCursor++]); } localCursor++; // skip the `\0` if (localCursor > endGuard) { m_logger->LogError("Export Trie: Cursor left trie while reading child text"); throw ReadException(); } uint64_t nextOffset = readValidULEB128(buffer, localCursor); if (nextOffset == 0) { m_logger->LogError("Export Trie: Child offset is zero"); throw ReadException(); } children.push_back({ nextOffset, currentText + childText }); } // Push in reverse so that the first child is processed next for (auto it = children.rbegin(); it != children.rend(); ++it) { stack.push_back(*it); } } } catch (ReadException&) { m_logger->LogError("Export trie is malformed. Could not load Exported symbol names."); } } void MachoView::ParseRebaseTable(BinaryReader& reader, MachOHeader& header, uint32_t tableOffset, uint32_t tableSize) { if (tableSize == 0 || tableOffset == 0) return; std::function segmentActualLoadAddress = [&](uint64_t segmentIndex) { if (segmentIndex >= header.segments.size()) throw ReadException(); return header.segments[segmentIndex].vmaddr; }; std::function segmentActualEndAddress = [&](uint64_t segmentIndex) { if (segmentIndex >= header.segments.size()) throw ReadException(); return header.segments[segmentIndex].vmaddr + header.segments[segmentIndex].vmsize; }; try { reader.Seek(tableOffset); auto table = reader.Read(tableSize); BNRelocationInfo rebaseRelocation; uint64_t segmentIndex = 0; uint64_t address = segmentActualLoadAddress(0); uint64_t segmentStartAddress = segmentActualLoadAddress(0); uint64_t segmentEndAddress = segmentActualEndAddress(0); uint64_t count; uint64_t skip; bool done = false; size_t i = 0; while ( !done && (i < tableSize)) { uint8_t opAndIm = table[i]; uint8_t opcode = opAndIm & RebaseOpcodeMask; uint64_t immediate = opAndIm & RebaseImmediateMask; m_logger->LogTraceF("Rebase opcode {:#x} (im: {:#x})", opcode, immediate); i++; switch (opcode) { case RebaseOpcodeDone: done = true; break; case RebaseOpcodeSetTypeImmediate: break; case RebaseOpcodeSetSegmentAndOffsetUleb: segmentIndex = immediate; address = segmentActualLoadAddress(segmentIndex) + readLEB128(table, tableSize, i); segmentStartAddress = segmentActualLoadAddress(segmentIndex); segmentEndAddress = segmentActualEndAddress(segmentIndex); break; case RebaseOpcodeAddAddressUleb: address += readLEB128(table, tableSize, i); break; case RebaseOpcodeAddAddressImmediateScaled: address += immediate * m_addressSize; break; case RebaseOpcodeDoRebaseImmediateTimes: count = immediate; for (uint64_t j = 0; j < count; ++j) { m_logger->LogTraceF("Rebasing address {:#x}", address); if (address < segmentStartAddress || address >= segmentEndAddress) { m_logger->LogError("Rebase address out of segment bounds"); throw ReadException(); } memset(&rebaseRelocation, 0, sizeof(rebaseRelocation)); rebaseRelocation.nativeType = BINARYNINJA_MANUAL_RELOCATION; rebaseRelocation.address = address; rebaseRelocation.size = m_addressSize; rebaseRelocation.pcRelative = false; rebaseRelocation.external = false; header.rebaseRelocations.push_back(rebaseRelocation); address += m_addressSize; } break; case RebaseOpcodeDoRebaseUlebTimes: count = readLEB128(table, tableSize, i); for (uint64_t j = 0; j < count; ++j) { m_logger->LogTraceF("Rebasing address {:#x}", address); if (address < segmentStartAddress || address >= segmentEndAddress) { m_logger->LogError("Rebase address out of segment bounds"); throw ReadException(); } memset(&rebaseRelocation, 0, sizeof(rebaseRelocation)); rebaseRelocation.nativeType = BINARYNINJA_MANUAL_RELOCATION; rebaseRelocation.address = address; rebaseRelocation.size = m_addressSize; rebaseRelocation.pcRelative = false; rebaseRelocation.external = false; header.rebaseRelocations.push_back(rebaseRelocation); address += m_addressSize; } break; case RebaseOpcodeDoRebaseAddAddressUleb: m_logger->LogTraceF("Rebasing address {:#x}", address); if (address < segmentStartAddress || address >= segmentEndAddress) { m_logger->LogError("Rebase address out of segment bounds"); throw ReadException(); } memset(&rebaseRelocation, 0, sizeof(rebaseRelocation)); rebaseRelocation.nativeType = BINARYNINJA_MANUAL_RELOCATION; rebaseRelocation.address = address; rebaseRelocation.size = m_addressSize; rebaseRelocation.pcRelative = false; rebaseRelocation.external = false; header.rebaseRelocations.push_back(rebaseRelocation); address += readLEB128(table, tableSize, i) + m_addressSize; break; case RebaseOpcodeDoRebaseUlebTimesSkippingUleb: count = readLEB128(table, tableSize, i); skip = readLEB128(table, tableSize, i); for (uint64_t j = 0; j < count; ++j) { m_logger->LogTraceF("Rebasing address {:#x}", address); if (address < segmentStartAddress || address >= segmentEndAddress) { m_logger->LogError("Rebase address out of segment bounds"); throw ReadException(); } memset(&rebaseRelocation, 0, sizeof(rebaseRelocation)); rebaseRelocation.nativeType = BINARYNINJA_MANUAL_RELOCATION; rebaseRelocation.address = address; rebaseRelocation.size = m_addressSize; rebaseRelocation.pcRelative = false; rebaseRelocation.external = false; header.rebaseRelocations.push_back(rebaseRelocation); address += skip + m_addressSize; } break; default: m_logger->LogErrorF("Unknown rebase opcode {}", opcode); throw ReadException(); break; } } } catch (ReadException&) { m_logger->LogError("Error while parsing Rebase Table"); } } void MachoView::ParseDynamicTable(BinaryReader& reader, MachOHeader& header, BNSymbolType incomingType, uint32_t tableOffset, uint32_t tableSize, BNSymbolBinding binding) { try { reader.Seek(tableOffset); auto table = reader.Read(tableSize); BNRelocationInfo externReloc; // BNSymbolType symtype = incomingType; uint64_t ordinal = 0; // int64_t addend = 0; uint64_t segmentIndex = 0; uint64_t address = 0; uint64_t offset = 0; char* name = NULL; // uint32_t flags = 0; // uint32_t type = 0; size_t i = 0; //bool done = false; while (i < tableSize) { uint8_t opcode = table[i] & BindOpcodeMask; uint8_t imm = table[i] & BindImmediateMask; i++; switch (opcode) { case BindOpcodeDone: ordinal = 0; // addend = 0; segmentIndex = 0; address = 0; offset = 0; name = NULL; // flags = 0; // type = 0; break; case BindOpcodeSetDylibOrdinalImmediate: ordinal = imm;break; case BindOpcodeSetDylibOrdinalULEB: ordinal = readLEB128(table, tableSize, i); break; case BindOpcodeSetDylibSpecialImmediate: ordinal = -imm; break; case BindOpcodeSetSymbolTrailingFlagsImmediate: /* flags = imm; */ name = (char*)&table[i]; while (i < tableSize && table[i++] != '\0') {;} break; case BindOpcodeSetTypeImmediate: // type = imm; break; case BindOpcodeSetAddendSLEB: /* addend = */ readSLEB128(table, tableSize, i); break; case BindOpcodeSetSegmentAndOffsetULEB: segmentIndex = imm; offset = readLEB128(table, tableSize, i); if (segmentIndex >= header.segments.size()) throw MachoFormatException(); address = header.segments[segmentIndex].vmaddr + offset; break; case BindOpcodeAddAddressULEB: address += readLEB128(table, tableSize, i); break; case BindOpcodeDoBind: if (name == NULL) throw MachoFormatException(); memset(&externReloc, 0, sizeof(externReloc)); externReloc.nativeType = BINARYNINJA_MANUAL_RELOCATION; externReloc.address = address; externReloc.size = m_addressSize; externReloc.pcRelative = false; externReloc.external = true; header.bindingRelocations.emplace_back(externReloc, string(name), ordinal); address += m_addressSize; break; case BindOpcodeDoBindAddAddressULEB: if (name == NULL) throw MachoFormatException(); memset(&externReloc, 0, sizeof(externReloc)); externReloc.nativeType = BINARYNINJA_MANUAL_RELOCATION; externReloc.address = address; externReloc.size = m_addressSize; externReloc.pcRelative = false; externReloc.external = true; header.bindingRelocations.emplace_back(externReloc, string(name), ordinal); address += m_addressSize; address += readLEB128(table, tableSize, i); break; case BindOpcodeDoBindAddAddressImmediateScaled: if (name == NULL) throw MachoFormatException(); memset(&externReloc, 0, sizeof(externReloc)); externReloc.nativeType = BINARYNINJA_MANUAL_RELOCATION; externReloc.address = address; externReloc.size = m_addressSize; externReloc.pcRelative = false; externReloc.external = true; header.bindingRelocations.emplace_back(externReloc, string(name), ordinal); address += m_addressSize; address += (imm * m_addressSize); break; case BindOpcodeDoBindULEBTimesSkippingULEB: { if (name == NULL) throw MachoFormatException(); uint64_t count = readLEB128(table, tableSize, i); uint64_t skip = readLEB128(table, tableSize, i); for (; count > 0; count--) { memset(&externReloc, 0, sizeof(externReloc)); externReloc.nativeType = BINARYNINJA_MANUAL_RELOCATION; externReloc.address = address; externReloc.size = m_addressSize; externReloc.pcRelative = false; externReloc.external = true; header.bindingRelocations.emplace_back(externReloc, string(name), ordinal); address += skip + m_addressSize; } break; } // New Threaded Binding Opcode, not much info out about it. It encodes a ULEB after the op/imm. case 0xD0: readLEB128(table, tableSize, i); break; default: break; } } } catch (ReadException&) {;} } void MachoView::ParseSymbolTable(BinaryReader& reader, MachOHeader& header, const symtab_command& symtab, const vector& indirectSymbols, MachoObjCProcessor* objcProcessor, std::unordered_map& symbolLibraryMapping) { if (header.ident.filetype == MH_DSYM) { m_logger->LogDebug("Skipping symbol parsing"); return; } try { //First parse the imports if (header.dyldInfoPresent) { m_logger->LogDebug("Bind symbols"); ParseDynamicTable(reader, header, ImportAddressSymbol, header.dyldInfo.bind_off, header.dyldInfo.bind_size, GlobalBinding); m_logger->LogDebug("Weak symbols"); ParseDynamicTable(reader, header, ImportAddressSymbol, header.dyldInfo.weak_bind_off, header.dyldInfo.weak_bind_size, WeakBinding); m_logger->LogDebug("Lazy symbols"); ParseDynamicTable(reader, header, ImportAddressSymbol, header.dyldInfo.lazy_bind_off, header.dyldInfo.lazy_bind_size, GlobalBinding); m_logger->LogDebug("Parsing rebase table"); ParseRebaseTable(reader, header, header.dyldInfo.rebase_off, header.dyldInfo.rebase_size); } if (header.chainedFixupsPresent) { m_logger->LogDebug("Chained Fixups"); ParseChainedFixups(header, header.chainedFixups, objcProcessor); } else if (header.chainStartsPresent) { m_logger->LogDebug("Chained Starts"); ParseChainedStarts(header, header.chainStarts, objcProcessor); } if (header.exportTriePresent && header.isMainHeader) ParseExportTrie(reader, header.exportTrie); //Then process the symtab if (header.stringListSize == 0) return; reader.Seek(symtab.symoff); unordered_map>> stubSymbols; for (auto& symbolStubs : header.symbolStubSections) { if (!symbolStubs.reserved2) continue; size_t needed = symbolStubs.size / symbolStubs.reserved2; for (size_t j = 0; (j < needed) && ((j + symbolStubs.reserved1) < indirectSymbols.size()); j++) { auto symNum = indirectSymbols[j + symbolStubs.reserved1]; if (symNum == INDIRECT_SYMBOL_ABS) continue; else if (symNum == INDIRECT_SYMBOL_LOCAL) continue; stubSymbols[symNum].push_back( std::pair(&symbolStubs, j)); } } unordered_map>> pointerSymbols; for (auto& symbolPointerSection : header.symbolPointerSections) { size_t needed = symbolPointerSection.size / m_addressSize; for (size_t j = 0; (j < needed) && ((j + symbolPointerSection.reserved1) < indirectSymbols.size()); j++) { auto symNum = indirectSymbols[j + symbolPointerSection.reserved1]; if (symNum == INDIRECT_SYMBOL_ABS) continue; else if (symNum == INDIRECT_SYMBOL_LOCAL) continue; pointerSymbols[symNum].push_back( std::pair(&symbolPointerSection, j)); } } nlist_64 sym; memset(&sym, 0, sizeof(sym)); for (size_t i = 0; i < symtab.nsyms; i++) { sym.n_strx = reader.Read32(); sym.n_type = reader.Read8(); sym.n_sect = reader.Read8(); sym.n_desc = reader.Read16(); sym.n_value = (m_addressSize == 4) ? reader.Read32() : reader.Read64(); if (sym.n_value) sym.n_value += m_imageBaseAdjustment; if (sym.n_strx >= symtab.strsize || ((sym.n_type & N_TYPE) == N_INDR)) continue; string symbol((char*)header.stringList.GetDataAt(sym.n_strx)); m_symbols.push_back(symbol); //otool ignores symbols that end with ".o", startwith "ltmp" or are "gcc_compiled." so do we if (symbol == "gcc_compiled." || (symbol.length() > 2 && symbol.substr(symbol.length()-2, 2) == ".o") || (symbol.length() > 4 && symbol.substr(0, 4) == "ltmp")) { m_logger->LogDebugF("Skipping symbol: {:?}", symbol); continue; } //N_TYPE is only set when N_SECT is the integer count of the section the symbol is in //Note that we don't currently validate the section number, but just use the address. //Very curious to see how other tools handle it. BNSymbolType type = DataSymbol; uint32_t flags; if ((sym.n_type & N_TYPE) == N_SECT && sym.n_sect > 0 && (size_t)(sym.n_sect - 1) < header.sections.size()) { if (!GetSectionPermissions(header, sym.n_value, flags)) { if ((sym.n_type & N_EXT)) { if (!GetSegmentPermissions(header, sym.n_value, flags)) { m_logger->LogDebug("No valid segment for symbol %s. value:%" PRIx64, symbol.c_str(), sym.n_value); continue; } } else { m_logger->LogDebug("No valid section for symbol %s. value:%" PRIx64, symbol.c_str(), sym.n_value); continue; } } } else if ((sym.n_type & N_TYPE) == N_ABS) { //N_ABS symbols do not have a section. Fall back to segment permissions. if (!GetSegmentPermissions(header, sym.n_value, flags)) { m_logger->LogDebug("No valid segment for symbol %s. value:%" PRIx64, symbol.c_str(), sym.n_value); continue; } } else if ((sym.n_type & N_EXT)) { type = ExternalSymbol; // Record the owning library from the two-level-namespace ordinal in the high // byte of n_desc. See GET_LIBRARY_ORDINAL in . unsigned libraryOrdinal = (unsigned)(sym.n_desc >> 8) & 0xff; string libName; if (libraryOrdinal >= 1 && libraryOrdinal <= 0xfd && (size_t)(libraryOrdinal - 1) < header.dylibs.size()) libName = header.dylibs[libraryOrdinal - 1].first; else if (libraryOrdinal == 0xfe) // DYNAMIC_LOOKUP_ORDINAL libName = kPseudoLibraryFlatLookup; else if (libraryOrdinal == 0xff) // EXECUTABLE_ORDINAL libName = kPseudoLibraryMainExecutable; if (!libName.empty()) { if (!GetExternalLibrary(libName)) AddExternalLibrary(libName, {}, true); symbolLibraryMapping[symbol] = libName; } } else continue; if (type != ExternalSymbol) { if ((flags & S_ATTR_PURE_INSTRUCTIONS) == S_ATTR_PURE_INSTRUCTIONS || (flags & S_ATTR_SOME_INSTRUCTIONS) == S_ATTR_SOME_INSTRUCTIONS) type = FunctionSymbol; else type = DataSymbol; } if((sym.n_desc & N_ARM_THUMB_DEF) == N_ARM_THUMB_DEF) sym.n_value++; auto stubSymbolIter = stubSymbols.find(i); auto pointerSymbolIter = pointerSymbols.find(i); bool deferred = stubSymbolIter == stubSymbols.end() && pointerSymbolIter == pointerSymbols.end(); BNSymbolBinding binding = GlobalBinding; if (header.dysymtab.nlocalsym && i >= header.dysymtab.ilocalsym && i < header.dysymtab.ilocalsym + header.dysymtab.nlocalsym) binding = LocalBinding; else if (type == ExternalSymbol && (sym.n_desc & N_WEAK_REF)) binding = WeakBinding; Ref symbolObj = DefineMachoSymbol(type, symbol, sym.n_value, binding, deferred); if (!symbolObj) { continue; } if (stubSymbolIter != stubSymbols.end()) { for (auto& j : stubSymbolIter->second) { // m_logger->LogErrorF("STUB [{}] {:x} - {}", i, j.first->addr + (j * j.first->reserved2), symbol); BNRelocationInfo info; memset(&info, 0, sizeof(info)); info.nativeType = -1; info.size = j.first->reserved2; info.pcRelative = true; DefineRelocation(m_arch, info, symbolObj, j.first->addr + (j.second * j.first->reserved2)); DefineMachoSymbol(ImportedFunctionSymbol, symbol, j.first->addr + (j.second * j.first->reserved2), GlobalBinding, true); } } if (pointerSymbolIter != pointerSymbols.end()) { for (auto& j : pointerSymbolIter->second) { // m_logger->LogErrorF("POINTER [{}] {:x} - {}", i, j.first->addr + (j.second * m_addressSize), // symbol); BNRelocationInfo info; memset(&info, 0, sizeof(info)); info.nativeType = BINARYNINJA_MANUAL_RELOCATION; info.size = m_addressSize; info.pcRelative = false; DefineRelocation(m_arch, info, symbolObj, j.first->addr + (j.second * m_addressSize)); DefineMachoSymbol( ImportAddressSymbol, symbol, j.first->addr + (j.second * m_addressSize), GlobalBinding, true); } } } } catch (ReadException&) {;} return; } void MachoView::ParseChainedFixups( MachOHeader& header, linkedit_data_command chainedFixups, MachoObjCProcessor* objcProcessor) { if (!chainedFixups.dataoff) return; try { std::unordered_map segmentVMAddrToFileOffset; segmentVMAddrToFileOffset.reserve(m_allSegments.size()); for (const auto& segment : m_allSegments) { // 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; } 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; if (fixup.type == FixupType::Rebase) { reloc.type = StandardRelocationType; DefineRelocation(m_arch, reloc, GetStart() + fixup.rebase.target, reloc.address); 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; } 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 (std::exception& e) { m_logger->LogErrorForExceptionF(e, "Failed to parse chained fixups: {}", e.what()); } } void MachoView::ParseChainedStarts(MachOHeader& header, section_64 chainedStarts, MachoObjCProcessor* objcProcessor) { 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 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 { uint64_t fixupHeaderAddress = m_universalImageOffset + chainedStarts.offset; parentReader.Seek(fixupHeaderAddress); uint32_t pointerFormat = parentReader.Read32(); uint32_t startsCount = parentReader.Read32(); std::vector startsOffsets; for (size_t i = 0; i < startsCount; i++) { startsOffsets.push_back(parentReader.Read32()); } 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 (pointerFormat) { 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: case DYLD_CHAINED_PTR_64_KERNEL_CACHE: 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_X86_64_KERNEL_CACHE: strideSize = 1; format = Generic64FixupFormat; break; default: { m_logger->LogErrorF("Chained Starts: Unknown or unsupported pointer format {}, " "unable to process chain starts", pointerFormat); return; } } for (uint32_t offset : startsOffsets) { uint64_t chainEntryAddress = m_universalImageOffset + offset; 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; } m_logger->LogTraceF("Chained Starts: @ {:#x} ( {:#x} ) - {} {:#x}", chainEntryAddress, GetStart() + (chainEntryAddress - m_universalImageOffset), bind, nextEntryStrideCount); if (bind && processBinds) { m_logger->LogWarn("Chained Starts: Bind pointers not supported in Chained Starts"); chainEntryAddress += (nextEntryStrideCount * strideSize); if (nextEntryStrideCount == 0) fixupsDone = true; continue; } else if (!bind) { uint64_t entryOffset = 0; switch (pointerFormat) { 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 ( pointerFormat != 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); m_logger->LogTraceF("Chained Starts: Adding relocated pointer {:#x} -> {:#x}", reloc.address, entryOffset); if (objcProcessor) { objcProcessor->AddRelocatedPointer(reloc.address, entryOffset); } } chainEntryAddress += (nextEntryStrideCount * strideSize); if (nextEntryStrideCount == 0) fixupsDone = true; } } } catch (ReadException&) { m_logger->LogError("Chained Starts parsing failed"); } } uint64_t MachoView::PerformGetEntryPoint() const { if (m_header.m_entryPoints.size() == 0) return 0; return m_header.m_entryPoints[0]; } BNEndianness MachoView::PerformGetDefaultEndianness() const { return LittleEndian; } bool MachoView::PerformIsRelocatable() const { return m_relocatable; } size_t MachoView::PerformGetAddressSize() const { return m_addressSize; } MachoViewType::MachoViewType(): BinaryViewType("Mach-O", "Mach-O") { m_logger = LogRegistry::CreateLogger("BinaryView.MachoViewType"); } Ref MachoViewType::Create(BinaryView* data) { try { return new MachoView("Mach-O", data); } catch (std::exception& e) { m_logger->LogErrorForExceptionF(e, "{} failed to create view! {:?}", GetName(), e.what()); return nullptr; } } Ref MachoViewType::Parse(BinaryView* data) { try { return new MachoView("Mach-O", data, true); } catch (std::exception& e) { m_logger->LogErrorForExceptionF(e, "{} failed to create view! {:?}", GetName(), e.what()); return nullptr; } } bool MachoViewType::IsTypeValidForData(BinaryView* data) { if (!data) { // Can't be valid if we don't have any data return false; } DataBuffer header = data->ReadBuffer(data->GetStart(), sizeof(mach_header)); if (header.GetLength() < sizeof(mach_header)) { // Can't be valid if we don't have enough data for a header return false; } auto mh = (const mach_header*)header.GetData(); if (mh->magic == MH_MAGIC || mh->magic == MH_MAGIC_64) { // MH_FILESET is now handled by the KernelCache view instead if (mh->filetype != MH_FILESET) return true; else return false; } else if (mh->magic == MH_CIGAM || mh->magic == MH_CIGAM_64) { // MH_FILESET is now handled by the KernelCache view instead if (ToBE32(mh->filetype) != MH_FILESET) return true; else return false; } else if (ToBE32(mh->magic) == FAT_MAGIC || ToBE32(mh->magic) == FAT_MAGIC_64) { // Fat Mach-O files are valid, but have different parsing logic return true; } return data->GetLoadSettings(GetName()) ? true : false; } uint64_t MachoViewType::ParseHeaders(BinaryView* data, uint64_t imageOffset, mach_header_64& ident, Ref* arch, Ref* plat, string& errorMsg) { DataBuffer sig = data->ReadBuffer(imageOffset, 4); uint32_t magic = *(uint32_t*)sig.GetData(); if ((sig.GetLength() != 4) || !(magic == MH_CIGAM || magic == MH_CIGAM_64 || magic == MH_MAGIC || magic == MH_MAGIC_64)) { errorMsg = "invalid signature"; return 0; } BinaryReader reader(data); reader.SetVirtualBase(imageOffset); ident.magic = reader.Read32(); BNEndianness endianness; if (ident.magic == MH_MAGIC || ident.magic == MH_MAGIC_64) endianness = LittleEndian; else if (ident.magic == MH_CIGAM || ident.magic == MH_CIGAM_64) endianness = BigEndian; else { errorMsg = "invalid file class"; return 0; } reader.SetEndianness(endianness); ident.cputype = reader.Read32(); ident.cpusubtype = reader.Read32(); ident.filetype = reader.Read32(); ident.ncmds = reader.Read32(); ident.sizeofcmds = reader.Read32(); ident.flags = reader.Read32(); if ((ident.cputype & MachOABIMask) == MachOABI64) // address size == 8 { ident.reserved = reader.Read32(); } if (!(ident.filetype == MH_OBJECT || ident.filetype == MH_EXECUTE || ident.filetype == MH_DYLIB || ident.filetype == MH_DYLINKER || ident.filetype == MH_BUNDLE || ident.filetype == MH_KEXT_BUNDLE || ident.filetype == MH_CORE || ident.filetype == MH_PRELOAD || ident.filetype == MH_DSYM || ident.filetype == MH_FILESET)) { m_logger->LogErrorF("Unhandled Macho file class: {:#x}", ident.filetype); errorMsg = "invalid file class"; return 0; } uint64_t loadCommandStart = reader.GetOffset(); uint32_t cmd; uint32_t cmdsize; MachoPlatform machoPlat = MachoPlatform::MACHO_PLATFORM_MACOS; // Default to macOS. // Quickly determine the OS from commands. for (uint32_t i = 0; i < ident.ncmds; i++) { cmd = reader.Read32(); cmdsize = reader.Read32(); if (cmd == LC_BUILD_VERSION) { machoPlat = MachoPlatform(reader.Read32()); break; } else if (cmd == LC_VERSION_MIN_MACOSX) { machoPlat = MachoPlatform(1); break; } else if (cmd == LC_VERSION_MIN_IPHONEOS) { machoPlat = MachoPlatform(2); break; } else if (cmd == _LC_VERSION_MIN_TVOS) { machoPlat = MachoPlatform(3); break; } else if (cmd == LC_VERSION_MIN_WATCHOS) { machoPlat = MachoPlatform(4); break; } reader.SeekRelative(cmdsize - 8); } map> metadataMap = { {"cputype", new Metadata((uint64_t) ident.cputype)}, {"cpusubtype", new Metadata((uint64_t) ident.cpusubtype)}, {"flags", new Metadata((uint64_t) ident.flags)}, {"machoplatform", new Metadata((uint64_t) machoPlat)}, }; Ref metadata = new Metadata(metadataMap); // retrieve architecture // FIXME: Architecture registration methods should perhaps be virtual and take the raw data, or some additional opaque information. Ref recognizedPlatform = g_machoViewType->RecognizePlatform(ident.cputype, endianness, data, metadata); if (recognizedPlatform) { if (plat) *plat = recognizedPlatform; if (arch) *arch = recognizedPlatform->GetArchitecture(); } else if (arch) { *arch = g_machoViewType->GetArchitecture(ident.cputype, endianness); } return loadCommandStart; } Ref MachoViewType::GetLoadSettingsForData(BinaryView* data) { Ref viewRef = Parse(data); if (!viewRef || !viewRef->Init()) { m_logger->LogWarnF("Failed to initialize view of type '{}'. Generating default load settings.", GetName()); viewRef = data; } Ref settings = GetDefaultLoadSettingsForData(viewRef); // specify default load settings that can be overridden vector overrides = {"loader.imageBase", "loader.platform"}; if (!viewRef->IsRelocatable()) settings->UpdateProperty("loader.imageBase", "message", "Note: File indicates image is not relocatable."); for (const auto& override : overrides) { if (settings->Contains(override)) settings->UpdateProperty(override, "readOnly", false); } if (MachoObjCProcessor::ViewHasObjCMetadata(viewRef)) { settings->RegisterSetting("loader.macho.processObjectiveC", R"({ "title" : "Process Objective-C metadata", "type" : "boolean", "default" : true, "description" : "Processes Objective-C structures, applying method names and types from encoded metadata" })"); } if (viewRef->GetSectionByName("__cfstring")) { settings->RegisterSetting("loader.macho.processCFStrings", R"({ "title" : "Process CFString Metadata", "type" : "boolean", "default" : true, "description" : "Processes CoreFoundation strings, applying string values from encoded metadata" })"); } // register additional settings settings->RegisterSetting("loader.macho.processFunctionStarts", R"({ "title" : "Process Mach-O Function Starts Table", "type" : "boolean", "default" : true, "description" : "Add function starts sourced from the Function Starts table to the core for analysis." })"); if (viewRef->GetSectionByName("__thread_starts")) { settings->RegisterSetting("loader.macho.rebaseThreadStarts", R"({ "title" : "Rebase Mach-O Kernelcache Thread Starts", "type" : "boolean", "default" : true, "description" : "Add relocation entries to rebase chained thread starts located in the __thread_starts section." })"); } // Merge existing load settings if they exist. This allows for the selection of a specific object file from a Mach-O Universal file. // The 'Universal' BinaryViewType generates a schema with 'loader.universal.architectures'. This schema contains an appropriate // 'Mach-O' load schema for selecting a specific object file. The embedded schema contains 'loader.macho.universalImageOffset'. Ref loadSettings = viewRef->GetLoadSettings(GetName()); if (loadSettings && !loadSettings->IsEmpty()) settings->DeserializeSchema(loadSettings->SerializeSchema()); return settings; } extern "C" { BN_DECLARE_CORE_ABI_VERSION #ifdef DEMO_EDITION bool MachoPluginInit() #else BINARYNINJAPLUGIN bool CorePluginInit() #endif { InitMachoViewType(); InitFatMachoViewType(); InitUniversalViewType(); InitUniversalTransform(); return true; } }