diff --git a/binaryview.cpp b/binaryview.cpp index 8dfd5f6898..e98a96d7c9 100644 --- a/binaryview.cpp +++ b/binaryview.cpp @@ -1384,15 +1384,39 @@ BinaryView::BinaryView(BNBinaryView* view) bool BinaryView::InitCallback(void* ctxt) { - CallbackRef view(ctxt); - return view->Init(); + try + { + CallbackRef view(ctxt); + return view->Init(); + } + catch (const std::exception& e) + { + LogError("BinaryView::Init failed: %s", e.what()); + return false; + } + catch (...) + { + LogError("BinaryView::Init failed with unknown exception"); + return false; + } } void BinaryView::OnAfterSnapshotDataAppliedCallback(void* ctxt) { - CallbackRef view(ctxt); - view->OnAfterSnapshotDataApplied(); + try + { + CallbackRef view(ctxt); + view->OnAfterSnapshotDataApplied(); + } + catch (const std::exception& e) + { + LogError("BinaryView::OnAfterSnapshotDataApplied failed: %s", e.what()); + } + catch (...) + { + LogError("BinaryView::OnAfterSnapshotDataApplied failed with unknown exception"); + } } @@ -1531,9 +1555,22 @@ size_t BinaryView::GetAddressSizeCallback(void* ctxt) bool BinaryView::SaveCallback(void* ctxt, BNFileAccessor* file) { - CallbackRef view(ctxt); - CoreFileAccessor accessor(file); - return view->PerformSave(&accessor); + try + { + CallbackRef view(ctxt); + CoreFileAccessor accessor(file); + return view->PerformSave(&accessor); + } + catch (const std::exception& e) + { + LogError("BinaryView::Save failed: %s", e.what()); + return false; + } + catch (...) + { + LogError("BinaryView::Save failed with unknown exception"); + return false; + } } diff --git a/view/elf/elfview.cpp b/view/elf/elfview.cpp index bfb5c68b2c..70ea3d69c8 100644 --- a/view/elf/elfview.cpp +++ b/view/elf/elfview.cpp @@ -51,6 +51,28 @@ void BinaryNinja::InitElfViewType() "ignore" : ["SettingsProjectScope", "SettingsResourceScope"] })~"); + settings->RegisterSetting("files.elf.maxMipsGotMB", + R"({ + "title" : "Maximum MIPS GOT Data Size in MB", + "type" : "number", + "default" : 4, + "minValue" : 0, + "maxValue" : 64, + "description" : "Maximum total GOT data size in megabytes to process in MIPS ELF files", + "ignore" : ["SettingsProjectScope"] + })"); + + settings->RegisterSetting("files.elf.maxStringTableTypeSizeMB", + R"~({ + "title" : "Maximum ELF String Table Size for Type Application (MB)", + "type" : "number", + "default" : 4, + "minValue" : 0, + "maxValue" : 256, + "description" : "Maximum string table size in megabytes when applying types from string tables", + "ignore" : ["SettingsProjectScope"] + })~"); + } @@ -1149,14 +1171,34 @@ bool ElfView::Init() { if (mipsSymValid && (gotStart != 0)) { - for (size_t i = 2; i < localMipsSyms; i++) + const uint64_t entrySize = m_elf32 ? 4 : 8; + Ref viewSettings = Settings::Instance(); + const uint64_t mbBudget = viewSettings->Get("files.elf.maxMipsGotMB", this); + const uint64_t entryBudget = (mbBudget * 1024 * 1024) / entrySize; + + // Find the file-backed region containing gotStart once, so the loop needs no per-entry check. + uint64_t gotFileEnd = gotStart; + for (const auto& hdr : m_programHeaders) { - m_gotEntryLocations.emplace(gotStart + i * (m_elf32 ? 4 : 8)); + if (hdr.type != ELF_PT_LOAD || hdr.fileSize == 0 || hdr.fileSize > UINT64_MAX - hdr.virtualAddress) + continue; + uint64_t segEnd = hdr.virtualAddress + hdr.fileSize; + if (hdr.virtualAddress > gotStart || gotStart >= segEnd) + continue; + // Only trust this segment's declared extent up to what the file actually backs. + if (hdr.fileSize > UINT64_MAX - hdr.offset || hdr.offset + hdr.fileSize > (uint64_t)GetParentView()->GetLength()) + continue; + gotFileEnd = segEnd; + break; } + const uint64_t maxFromFile = (gotFileEnd > gotStart) ? (gotFileEnd - gotStart) / entrySize : 0; + const uint64_t limit = std::min({localMipsSyms, entryBudget, maxFromFile}); + for (size_t i = 2; i < limit; i++) + m_gotEntryLocations.emplace(gotStart + i * entrySize); for (uint64_t i = firstMipsSym; i < (m_auxSymbolTable.size / (m_elf32 ? 16 : 24)); i++) { - uint64_t gotEntry = gotStart + ((localMipsSyms + i - firstMipsSym) * (m_elf32 ? 4 : 8)); - if (!IsValidOffset(gotEntry)) + uint64_t gotEntry = gotStart + ((localMipsSyms + i - firstMipsSym) * entrySize); + if (!IsRangeBackedByFile(gotEntry, entrySize)) { m_logger->LogWarn("ELF GOT entry %" PRIx64 " is invalid", gotEntry); break; @@ -1796,7 +1838,7 @@ bool ElfView::Init() } // Perform fixup processing on the local GOT entries if the view is relocatable. - if (m_relocatable) + if (m_relocatable && localMipsSyms > 0) { uint64_t lastLocalGotEntry = gotStart + (localMipsSyms - 1) * (m_elf32 ? 4 : 8); for (auto gotEntry : m_gotEntryLocations) @@ -2665,6 +2707,14 @@ void ElfView::DefineElfSymbol(BNSymbolType type, const string& incomingName, uin void ElfView::ApplyTypesToParentStringTable(const Elf64SectionHeader& section, const bool offset) { m_logger->LogInfo("Found string table of size %p at offset %p", section.size, section.offset); + if (section.size == 0 || + section.size > UINT64_MAX - section.offset || + section.offset + section.size > GetParentView()->GetLength()) + return; + Ref viewSettings = Settings::Instance(); + const uint64_t sizeBudget = viewSettings->Get("files.elf.maxStringTableTypeSizeMB", this) * 1024 * 1024; + if (section.size > sizeBudget) + return; DataBuffer buffer = GetParentView()->ReadBuffer(section.offset, section.size); if (buffer.GetLength() != section.size) return; @@ -2734,7 +2784,7 @@ void ElfView::ApplyTypesToStringTable(const Elf64SectionHeader& section, const i string ElfView::ReadStringTable(BinaryReader& reader, const Elf64SectionHeader& section, uint64_t offset) { - if (offset == 0 || offset > section.size) + if (offset == 0 || offset >= section.size || section.size > GetParentView()->GetLength()) return ""; auto itr = m_stringTableCache.find(section.offset); @@ -2754,7 +2804,27 @@ string ElfView::ReadStringTable(BinaryReader& reader, const Elf64SectionHeader& } const std::vector& tableCache = itr->second; - return std::string(&tableCache[offset], strlen(tableCache.data() + offset)); + if (offset >= tableCache.size()) { + m_logger->LogError("Unable to read string from table cache offset: 0x%" PRIx64 " size: 0x%" PRIx64, section.offset, section.size); + return ""; + } + return std::string(&tableCache[offset], strnlen(tableCache.data() + offset, tableCache.size() - offset)); +} + + +// Returns true only if the entire range [start, start+size) is backed by file data. +bool ElfView::IsRangeBackedByFile(uint64_t start, uint64_t size) const +{ + if (size == 0) + return false; + if (size > UINT64_MAX - start) + return false; + for (uint64_t offset = start; offset < start + size; offset++) + { + if (!IsOffsetBackedByFile(offset)) + return false; + } + return true; } diff --git a/view/elf/elfview.h b/view/elf/elfview.h index f81fa832ea..a0b56fda44 100644 --- a/view/elf/elfview.h +++ b/view/elf/elfview.h @@ -527,6 +527,8 @@ namespace BinaryNinja void DefineElfSymbol(BNSymbolType type, const std::string& name, uint64_t addr, bool gotEntry, BNSymbolBinding binding, size_t size = 0, const Confidence>& typeObj = nullptr); + bool IsRangeBackedByFile(uint64_t start, uint64_t size) const; + void ApplyTypesToParentStringTable(const Elf64SectionHeader& section, const bool offset = true); void ApplyTypesToStringTable(const Elf64SectionHeader& section, const int64_t imageBaseAdjustment, const bool offset = true); std::string ReadStringTable(BinaryReader& view, const Elf64SectionHeader& section, uint64_t offset); diff --git a/view/macho/chained_fixups.cpp b/view/macho/chained_fixups.cpp index e71b34b800..76ca15d684 100644 --- a/view/macho/chained_fixups.cpp +++ b/view/macho/chained_fixups.cpp @@ -240,12 +240,21 @@ auto FixupReaderForFormat(int format) -> std::pair(*)(Binar throw std::invalid_argument("Unknown chained pointer format: " + std::to_string(format)); } +// Returns the NUL-terminated string starting at `offset` within `symbolData`, or an +// empty view if `offset` does not fall within `symbolData`. +std::string_view SymbolNameAt(std::span symbolData, uint32_t offset) +{ + if (symbolData.size() <= offset) + return std::string_view(); + return std::string_view(&symbolData[offset], strnlen(&symbolData[offset], symbolData.size() - offset)); +} + ImportEntry ReadChainedImport32(BinaryReader& reader, std::span symbolData) { dyld_chained_import import; reader.Read(&import, sizeof(import)); return { - std::string_view(&symbolData[import.name_offset]), + SymbolNameAt(symbolData, import.name_offset), 0, import.lib_ordinal > 0xF0 ? static_cast(import.lib_ordinal) : static_cast(import.lib_ordinal), (bool)import.weak_import, @@ -257,7 +266,7 @@ ImportEntry ReadChainedImportAddend32(BinaryReader& reader, std::span(import.addend), import.lib_ordinal > 0xF0 ? static_cast(import.lib_ordinal) : static_cast(import.lib_ordinal), (bool)import.weak_import, @@ -269,7 +278,7 @@ ImportEntry ReadChainedImportAddend64(BinaryReader& reader, std::span 0xFFF0 ? static_cast(import.lib_ordinal) : static_cast(import.lib_ordinal), (bool)import.weak_import, @@ -304,6 +313,8 @@ std::vector ChainedFixupProcessor::ProcessImports() const auto header = ReadHeader(reader); + if (header.symbols_offset >= m_fixupsSize) + return imports; uint64_t symbolDataSize = m_fixupsSize - header.symbols_offset; m_symbolData.resize(symbolDataSize); m_raw->Read(&m_symbolData[0], OffsetInFixups(header.symbols_offset), symbolDataSize); @@ -435,7 +446,7 @@ void ChainedFixupProcessor::ProcessChainsInSegment(const dyld_chained_starts_in_ bool done = false; while (!done) - { + { uint64_t position = reader.GetOffset(); auto [raw, fixupInfo] = fixupReader(reader); diff --git a/view/macho/machoview.cpp b/view/macho/machoview.cpp index 7613918806..c3eebc7377 100644 --- a/view/macho/machoview.cpp +++ b/view/macho/machoview.cpp @@ -363,6 +363,16 @@ void BinaryNinja::InitMachoViewType() static MachoViewType type; BinaryViewType::Register(&type); g_machoViewType = &type; + + Settings::Instance()->RegisterSetting("loader.macho.maxRebaseBindEntriesMultiplier", + R"~({ + "title" : "Mach-O Rebase/Bind Table Entry Count Limit Multiplier", + "type" : "number", + "default" : 1.0, + "minValue" : 0.01, + "maxValue" : 10.0, + "description" : "Multiplier applied to the maximum number of rebase/bind entries permitted per table, which is derived from the size of the Mach-O slice divided by its pointer size" + })~"); } MachoView::MachoView(const string& typeName, BinaryView* data, bool parseOnly): BinaryView(typeName, data->GetFile(), data), @@ -2268,9 +2278,22 @@ bool MachoView::InitializeHeader(MachOHeader& header, bool isMainHeader, uint64_ // 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)); + // Clamp to the number of 4-byte entries that fit between indirectsymoff and + // end-of-file; GetParentView()->GetLength() is the OS-reported file size and + // is not derived from any field inside the binary. indirectsymoff is relative + // to the start of this slice (reader has m_universalImageOffset as its virtual + // base), so that offset must be added here to compute the real file position. + const uint64_t readOffset = m_universalImageOffset + header.dysymtab.indirectsymoff; + const uint64_t fileRemaining = (GetParentView()->GetLength() > readOffset) + ? (GetParentView()->GetLength() - readOffset) / sizeof(uint32_t) + : 0; + const uint32_t indirectSymCount = (uint32_t)std::min((uint64_t)header.dysymtab.nindirectsyms, fileRemaining); + if (indirectSymCount) + { + indirectSymbols.resize(indirectSymCount); + reader.Seek(header.dysymtab.indirectsymoff); + reader.Read(&indirectSymbols[0], indirectSymCount * sizeof(uint32_t)); + } } } catch (ReadException&) @@ -2862,9 +2885,11 @@ bool MachoView::AddExportTerminalSymbol( 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); + if (buffer.GetLength() == 0) + return; + size_t endIndex = buffer.GetLength() - 1; struct Node { @@ -2875,7 +2900,11 @@ void MachoView::ParseExportTrie(BinaryReader& reader, linkedit_data_command expo stack.reserve(64); stack.push_back({ /* cursor */ 0, /* text */ "" }); - while (!stack.empty()) + // Each trie node consumes at least one byte from the buffer, so buffer.GetLength() + // is a sound upper bound on the number of node visits and also prevents + // infinite traversal when the trie contains cycles. + size_t visitCount = 0; + while (!stack.empty() && visitCount++ < buffer.GetLength()) { m_logger->LogTraceF("Export Trie: Processing node {:?} with cursor {:#x}", stack.back().text, stack.back().cursor); Node node = std::move(stack.back()); @@ -2884,7 +2913,7 @@ void MachoView::ParseExportTrie(BinaryReader& reader, linkedit_data_command expo uint64_t cursor = node.cursor; const std::string currentText = std::move(node.text); - if (cursor > endGuard) + if (cursor > endIndex) { m_logger->LogError("Export Trie: Cursor left trie during initial bounds check"); throw ReadException(); @@ -2905,14 +2934,14 @@ void MachoView::ParseExportTrie(BinaryReader& reader, linkedit_data_command expo } localCursor = childOffset; - if (localCursor > endGuard) + if (localCursor > endIndex) { m_logger->LogError("Export Trie: Cursor left trie while moving to child offset"); throw ReadException(); } uint8_t childCount = buffer[localCursor++]; - if (localCursor > endGuard) + if (localCursor > endIndex) { m_logger->LogError("Export Trie: Cursor left trie while reading child count"); throw ReadException(); @@ -2922,18 +2951,18 @@ void MachoView::ParseExportTrie(BinaryReader& reader, linkedit_data_command expo children.reserve(childCount); for (uint8_t i = 0; i < childCount; ++i) { - if (localCursor > endGuard) + if (localCursor > endIndex) { m_logger->LogError("Export Trie: Cursor left trie while reading child count"); throw ReadException(); } std::string childText; - while (localCursor <= endGuard && buffer[localCursor] != 0) { + while (localCursor <= endIndex && buffer[localCursor] != 0) { childText.push_back(buffer[localCursor++]); } localCursor++; // skip the `\0` - if (localCursor > endGuard) + if (localCursor > endIndex) { m_logger->LogError("Export Trie: Cursor left trie while reading child text"); throw ReadException(); @@ -2962,11 +2991,26 @@ void MachoView::ParseExportTrie(BinaryReader& reader, linkedit_data_command expo } } +// Scale the entry limit by the size of this Mach-O slice divided by its pointer size, +// the maximum number of pointer-sized slots the slice can contain. +uint64_t MachoView::GetRebaseBindEntryLimit() +{ + uint64_t sliceSize = (GetParentView()->GetLength() > m_universalImageOffset) + ? (GetParentView()->GetLength() - m_universalImageOffset) + : 0; + uint64_t structuralLimit = sliceSize / m_addressSize; + double entryLimitMultiplier = Settings::Instance()->Get("loader.macho.maxRebaseBindEntriesMultiplier", this); + return (uint64_t)(structuralLimit * entryLimitMultiplier); +} + + void MachoView::ParseRebaseTable(BinaryReader& reader, MachOHeader& header, uint32_t tableOffset, uint32_t tableSize) { if (tableSize == 0 || tableOffset == 0) return; + uint64_t remainingIterations = GetRebaseBindEntryLimit(); + std::function segmentActualLoadAddress = [&](uint64_t segmentIndex) { if (segmentIndex >= header.segments.size()) throw ReadException(); @@ -2990,9 +3034,35 @@ void MachoView::ParseRebaseTable(BinaryReader& reader, MachOHeader& header, uint uint64_t segmentEndAddress = segmentActualEndAddress(0); uint64_t count; uint64_t skip; - bool done = false; + bool tableDone = false; size_t i = 0; - while ( !done && (i < tableSize)) + + // Records a rebase at the current address and consumes one unit of the entry + // budget. Returns false once the budget is exhausted, without recording anything. + auto emitRebase = [&]() -> bool { + m_logger->LogTraceF("Rebasing address {:#x}", address); + if (address < segmentStartAddress || address >= segmentEndAddress) + { + m_logger->LogError("Rebase address out of segment bounds"); + throw ReadException(); + } + if (remainingIterations == 0) + { + m_logger->LogWarn("Rebase table encodes more entries than the configured limit allows; ignoring the remainder"); + return false; + } + remainingIterations--; + 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); + return true; + }; + + while ( !tableDone && (i < tableSize)) { uint8_t opAndIm = table[i]; uint8_t opcode = opAndIm & RebaseOpcodeMask; @@ -3002,7 +3072,7 @@ void MachoView::ParseRebaseTable(BinaryReader& reader, MachOHeader& header, uint switch (opcode) { case RebaseOpcodeDone: - done = true; + tableDone = true; break; case RebaseOpcodeSetTypeImmediate: break; @@ -3019,22 +3089,14 @@ void MachoView::ParseRebaseTable(BinaryReader& reader, MachOHeader& header, uint address += immediate * m_addressSize; break; case RebaseOpcodeDoRebaseImmediateTimes: - count = immediate; - for (uint64_t j = 0; j < count; ++j) + // immediate is the low 4 bits of the opcode byte; its value is 0-15. + for (uint64_t j = 0; j < immediate; ++j) { - m_logger->LogTraceF("Rebasing address {:#x}", address); - if (address < segmentStartAddress || address >= segmentEndAddress) + if (!emitRebase()) { - m_logger->LogError("Rebase address out of segment bounds"); - throw ReadException(); + tableDone = true; + break; } - 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; @@ -3042,56 +3104,32 @@ void MachoView::ParseRebaseTable(BinaryReader& reader, MachOHeader& header, uint 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) + if (!emitRebase()) { - m_logger->LogError("Rebase address out of segment bounds"); - throw ReadException(); + tableDone = true; + break; } - 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) + if (!emitRebase()) { - m_logger->LogError("Rebase address out of segment bounds"); - throw ReadException(); + tableDone = true; + break; } - 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); + 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) + if (!emitRebase()) { - m_logger->LogError("Rebase address out of segment bounds"); - throw ReadException(); + tableDone = true; + break; } - 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; @@ -3112,6 +3150,8 @@ void MachoView::ParseRebaseTable(BinaryReader& reader, MachOHeader& header, uint void MachoView::ParseDynamicTable(BinaryReader& reader, MachOHeader& header, BNSymbolType incomingType, uint32_t tableOffset, uint32_t tableSize, BNSymbolBinding binding) { + uint64_t remainingIterations = GetRebaseBindEntryLimit(); + try { reader.Seek(tableOffset); auto table = reader.Read(tableSize); @@ -3127,8 +3167,28 @@ void MachoView::ParseDynamicTable(BinaryReader& reader, MachOHeader& header, BNS // uint32_t flags = 0; // uint32_t type = 0; size_t i = 0; - //bool done = false; - while (i < tableSize) + bool tableDone = false; + + // Records a bind at the current address and consumes one unit of the entry + // budget. Returns false once the budget is exhausted, without recording anything. + auto emitBind = [&]() -> bool { + if (remainingIterations == 0) + { + m_logger->LogWarn("Bind table encodes more entries than the configured limit allows; ignoring the remainder"); + return false; + } + remainingIterations--; + 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); + return true; + }; + + while (i < tableSize && !tableDone) { uint8_t opcode = table[i] & BindOpcodeMask; uint8_t imm = table[i] & BindImmediateMask; @@ -3171,42 +3231,32 @@ void MachoView::ParseDynamicTable(BinaryReader& reader, MachOHeader& header, BNS 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); + if (!emitBind()) + { + tableDone = true; + break; + } 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); - + if (!emitBind()) + { + tableDone = true; + break; + } 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); + if (!emitBind()) + { + tableDone = true; + break; + } address += m_addressSize; address += (imm * m_addressSize); break; @@ -3216,17 +3266,14 @@ void MachoView::ParseDynamicTable(BinaryReader& reader, MachOHeader& header, BNS throw MachoFormatException(); uint64_t count = readLEB128(table, tableSize, i); - uint64_t skip = 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); - + if (!emitBind()) + { + tableDone = true; + break; + } address += skip + m_addressSize; } break; @@ -3335,10 +3382,14 @@ void MachoView::ParseSymbolTable(BinaryReader& reader, MachOHeader& header, cons 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)) + // Use GetLength() rather than symtab.strsize because Read() may return + // fewer bytes than requested; checking strsize would allow n_strx to pass + // while still being past the end of the actual buffer. + if (sym.n_strx >= header.stringList.GetLength() || ((sym.n_type & N_TYPE) == N_INDR)) continue; - string symbol((char*)header.stringList.GetDataAt(sym.n_strx)); + const char* symbolName = (const char*)header.stringList.GetDataAt(sym.n_strx); + string symbol(symbolName, strnlen(symbolName, header.stringList.GetLength() - 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." || @@ -3825,8 +3876,13 @@ bool MachoViewType::IsTypeValidForData(BinaryView* data) 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); + if (sig.GetLength() != 4) + { + errorMsg = "signature too small"; + return 0; + } 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)) + if (!(magic == MH_CIGAM || magic == MH_CIGAM_64 || magic == MH_MAGIC || magic == MH_MAGIC_64)) { errorMsg = "invalid signature"; return 0; diff --git a/view/macho/machoview.h b/view/macho/machoview.h index 0e0216f96b..b997af750f 100644 --- a/view/macho/machoview.h +++ b/view/macho/machoview.h @@ -1514,6 +1514,7 @@ namespace BinaryNinja void ReadExportNode(uint64_t viewStart, DataBuffer& buffer, const std::string& currentText, size_t cursor, uint32_t endGuard); + uint64_t GetRebaseBindEntryLimit(); void ParseRebaseTable(BinaryReader& reader, MachOHeader& header, uint32_t tableOffset, uint32_t tableSize); void ParseDynamicTable(BinaryReader& reader, MachOHeader& header, BNSymbolType type, uint32_t tableOffset, uint32_t tableSize, BNSymbolBinding binding); diff --git a/view/pe/peview.cpp b/view/pe/peview.cpp index 6af6283eaf..9541b32e59 100644 --- a/view/pe/peview.cpp +++ b/view/pe/peview.cpp @@ -783,7 +783,7 @@ bool PEView::Init() else { uint64_t sizeOfImage = opt.sizeOfImage; - if (opt.sizeOfImage % resolvedSectionAlignment) + if (resolvedSectionAlignment && opt.sizeOfImage % resolvedSectionAlignment) sizeOfImage = (opt.sizeOfImage + resolvedSectionAlignment) & ~(resolvedSectionAlignment - 1); uint64_t dataLength = GetParentView()->GetEnd(); dataLength = std::min(std::max((uint64_t)m_sizeOfHeaders, sizeOfImage), dataLength); @@ -1525,7 +1525,10 @@ bool PEView::Init() while (true) { // Read in next directory entry - reader.Seek(RVAToFileOffset(dir.virtualAddress + (numImportEntries * 20))); + uint64_t entryRva = dir.virtualAddress + (uint64_t)numImportEntries * 20; + if (!IsRVARangeBackedByFile(entryRva, 20)) + break; + reader.Seek(RVAToFileOffset(entryRva)); PEImportDirectoryEntry importDirEntry; importDirEntry.lookup = reader.Read32(); importDirEntry.timestamp = reader.Read32(); @@ -1614,8 +1617,26 @@ bool PEView::Init() // We should make this second unused data a structure containing this information information // and default it to collapsed...IDA Just doesn't show anything at all m_logger->LogDebug("Name: %s\n", dllName.c_str()); + const uint32_t importEntrySize = m_is64 ? 8 : 4; + + // Find the section containing entryOffset once so the inner loop needs no per-entry section scan. + uint64_t importSecEnd = 0; + for (const auto& sec : m_sections) + { + if ((uint64_t)entryOffset >= sec.virtualAddress && + (uint64_t)entryOffset < (uint64_t)sec.virtualAddress + sec.sizeOfRawData && + sec.virtualSize != 0) + { + // Only trust this section's declared extent up to what the file actually backs. + if ((uint64_t)sec.pointerToRawData + sec.sizeOfRawData <= (uint64_t)GetParentView()->GetLength()) + importSecEnd = (uint64_t)sec.virtualAddress + sec.sizeOfRawData; + break; + } + } while (true) { + if ((uint64_t)entryOffset + importEntrySize > importSecEnd) + break; uint64_t entry; bool isOrdinal; if (m_is64) @@ -3451,6 +3472,36 @@ uint32_t PEView::GetRVACharacteristics(uint64_t offset) } +// Returns true only if the entire range [rva, rva+size) maps to file-backed data. +bool PEView::IsRVARangeBackedByFile(uint64_t rva, uint64_t size) const +{ + if (size == 0) + return false; + if (size > UINT64_MAX - rva) + return false; + for (uint64_t offset = rva; offset < rva + size; offset++) + { + bool found = false; + for (const auto& i : m_sections) + { + if (offset >= (uint64_t)i.virtualAddress && + offset < (uint64_t)i.virtualAddress + (uint64_t)i.sizeOfRawData && + i.virtualSize != 0) + { + uint64_t fileOfs = (uint64_t)i.pointerToRawData + (offset - (uint64_t)i.virtualAddress); + if (!GetParentView()->IsOffsetBackedByFile(fileOfs)) + return false; + found = true; + break; + } + } + if (!found) + return false; + } + return true; +} + + string PEView::ReadString(uint64_t rva) { uint64_t offset = RVAToFileOffset(rva); diff --git a/view/pe/peview.h b/view/pe/peview.h index ed79a02d16..13407afaa7 100644 --- a/view/pe/peview.h +++ b/view/pe/peview.h @@ -467,6 +467,7 @@ namespace BinaryNinja uint64_t RVAToFileOffset(uint64_t rva, bool except = true); uint32_t GetRVACharacteristics(uint64_t rva); + bool IsRVARangeBackedByFile(uint64_t rva, uint64_t size) const; std::string ReadString(uint64_t rva); uint16_t Read16(uint64_t rva); uint32_t Read32(uint64_t rva);