diff --git a/view/sharedcache/core/MachOProcessor.cpp b/view/sharedcache/core/MachOProcessor.cpp index df2fcfdef0..281f40c26c 100644 --- a/view/sharedcache/core/MachOProcessor.cpp +++ b/view/sharedcache/core/MachOProcessor.cpp @@ -84,50 +84,58 @@ void SharedCacheMachOProcessor::ApplyHeader(const SharedCache& cache, SharedCach } // Apply symbols from the .symbols cache files. - for (const auto &entry: cache.GetEntries()) - { - // NOTE: We check addr size as we only support 64bit .symbols files currently. - if (entry.GetType() != CacheEntryType::Symbols && m_vm->GetAddressSize() == 8) - continue; - const auto& entryHeader = entry.GetHeader(); + ApplyUnmappedLocalSymbols(cache, header, std::move(typeLib)); +} - // This is where we get the symbol and string table information from in the .symbols file. - dyld_cache_local_symbols_info localSymbolsInfo = {}; - auto localSymbolsInfoAddr = entry.GetMappedAddress(entryHeader.localSymbolsOffset); - if (!localSymbolsInfoAddr.has_value()) - continue; - m_vm->Read(&localSymbolsInfo, *localSymbolsInfoAddr, sizeof(dyld_cache_local_symbols_info)); +void SharedCacheMachOProcessor::ApplyUnmappedLocalSymbols(const SharedCache& cache, const SharedCacheMachOHeader& header, Ref typeLib) +{ + const auto& localSymbolsCacheEntry = cache.GetLocalSymbolsEntry(); + auto localSymbolsVM = cache.GetLocalSymbolsVM(); + if (!localSymbolsCacheEntry || !localSymbolsVM) + return; + + // NOTE: We check addr size as we only support 64bit .symbols files currently. + // TODO: Support 32-bit nlist + if (localSymbolsVM->GetAddressSize() != 8) + return; + + const auto& entryHeader = localSymbolsCacheEntry->GetHeader(); + + // This is where we get the symbol and string table information from in the .symbols file. + dyld_cache_local_symbols_info localSymbolsInfo = {}; + auto localSymbolsInfoAddr = entryHeader.localSymbolsOffset; + + localSymbolsVM->Read(&localSymbolsInfo, localSymbolsInfoAddr, sizeof(dyld_cache_local_symbols_info)); - // Read each symbols entry, looking for the current image entry. - uint64_t localEntriesAddr = *localSymbolsInfoAddr + localSymbolsInfo.entriesOffset; - uint64_t localSymbolsAddr = *localSymbolsInfoAddr + localSymbolsInfo.nlistOffset; - uint64_t localStringsAddr = *localSymbolsInfoAddr + localSymbolsInfo.stringsOffset; + // Read each symbols entry, looking for the current image entry. + uint64_t localEntriesAddr = localSymbolsInfoAddr + localSymbolsInfo.entriesOffset; + uint64_t localSymbolsAddr = localSymbolsInfoAddr + localSymbolsInfo.nlistOffset; + uint64_t localStringsAddr = localSymbolsInfoAddr + localSymbolsInfo.stringsOffset; + for (uint32_t i = 0; i < localSymbolsInfo.entriesCount; i++) + { dyld_cache_local_symbols_entry_64 localSymbolsEntry = {}; - for (uint32_t i = 0; i < localSymbolsInfo.entriesCount; i++) + localSymbolsVM->Read(&localSymbolsEntry, localEntriesAddr + i * sizeof(dyld_cache_local_symbols_entry_64), + sizeof(dyld_cache_local_symbols_entry_64)); + + // The dylibOffset is the offset from the cache base address to the image header. + const auto imageAddr = cache.GetBaseAddress() + localSymbolsEntry.dylibOffset; + if (imageAddr != header.textBase) + continue; + + // We have found the entry to read! + uint64_t symbolTableStart = localSymbolsAddr + (localSymbolsEntry.nlistStartIndex * sizeof(nlist_64)); + TableInfo symbolInfo = {symbolTableStart, localSymbolsEntry.nlistCount}; + TableInfo stringInfo = {localStringsAddr, localSymbolsInfo.stringsSize}; + m_view->BeginBulkModifySymbols(); + const auto symbols = header.ReadSymbolTable(*localSymbolsVM, symbolInfo, stringInfo); + for (const auto &sym: symbols) { - m_vm->Read(&localSymbolsEntry, localEntriesAddr + i * sizeof(dyld_cache_local_symbols_entry_64), - sizeof(dyld_cache_local_symbols_entry_64)); - // The dylibOffset is the offset from the cache base address to the image header. - const auto imageAddr = cache.GetBaseAddress() + localSymbolsEntry.dylibOffset; - if (imageAddr == header.textBase) - { - // We have found the entry to read! - // TODO: Support 32bit nlist - uint64_t symbolTableStart = localSymbolsAddr + (localSymbolsEntry.nlistStartIndex * sizeof(nlist_64)); - TableInfo symbolInfo = {symbolTableStart, localSymbolsEntry.nlistCount}; - TableInfo stringInfo = {localStringsAddr, localSymbolsInfo.stringsSize}; - m_view->BeginBulkModifySymbols(); - const auto symbols = header.ReadSymbolTable(*m_vm, symbolInfo, stringInfo); - for (const auto &sym: symbols) - { - auto [symbol, symbolType] = sym.GetBNSymbolAndType(*m_view); - ApplySymbol(m_view, typeLib, symbol, symbolType); - } - m_view->EndBulkModifySymbols(); - break; - } + auto [symbol, symbolType] = sym.GetBNSymbolAndType(*m_view); + ApplySymbol(m_view, typeLib, std::move(symbol), std::move(symbolType)); } + m_view->EndBulkModifySymbols(); + return; } } diff --git a/view/sharedcache/core/MachOProcessor.h b/view/sharedcache/core/MachOProcessor.h index 4b014f4ed4..86772c9901 100644 --- a/view/sharedcache/core/MachOProcessor.h +++ b/view/sharedcache/core/MachOProcessor.h @@ -21,4 +21,6 @@ class SharedCacheMachOProcessor uint64_t ApplyHeaderSections(SharedCacheMachOHeader& header); void ApplyHeaderDataVariables(SharedCacheMachOHeader& header); + + void ApplyUnmappedLocalSymbols(const SharedCache& cache, const SharedCacheMachOHeader& header, BinaryNinja::Ref typeLib); }; diff --git a/view/sharedcache/core/ObjC.cpp b/view/sharedcache/core/ObjC.cpp index b6de8ba6c0..b411ed8752 100644 --- a/view/sharedcache/core/ObjC.cpp +++ b/view/sharedcache/core/ObjC.cpp @@ -136,17 +136,52 @@ std::optional GetObjCOptimizationHeader(SharedCache& cac return header; } +std::optional> GetLegacyObjCOptimizationHeader(SharedCache& cache, VirtualMemoryReader& reader) +{ + // In older versions the header lives in the `__TEXT,__objc_opt_ro` section within /usr/lib/libobjc.A.dylib + auto libObjC = cache.GetImageWithName("/usr/lib/libobjc.A.dylib"); + if (!libObjC) + return std::nullopt; + + // Convert the header's `char[16]` to a `string_view`. + auto AsStringView = [](const char (&arr)[N]) { + const char* end = std::find(arr, arr + N, '\0'); + return std::string_view(arr, end - arr); + }; + + for (auto section : libObjC->header->sections) { + if (AsStringView(section.segname) != "__TEXT" || AsStringView(section.sectname) != "__objc_opt_ro") + continue; + + LegacyObjCOptimizationHeader header = {}; + reader.Read(&header, section.addr, sizeof(LegacyObjCOptimizationHeader)); + + // The `relativeMethodSelectorBaseAddressOffset` field was added in version 16 (the final version of this struct). + if (header.version >= 16) + return {{section.addr, header}}; + + break; + } + + return std::nullopt; +} + uint64_t SharedCacheObjCProcessor::GetObjCRelativeMethodBaseAddress(ObjCReader* reader) { // Try and retrieve the base address of the selector stuff. if (const auto controller = DSC::SharedCacheController::FromView(*m_data)) { - auto baseAddress = controller->GetCache().GetBaseAddress(); auto dangerReader = dynamic_cast(reader)->GetVMReader(); if (const auto header = GetObjCOptimizationHeader(controller->GetCache(), dangerReader); header.has_value()) { + auto baseAddress = controller->GetCache().GetBaseAddress(); m_customRelativeMethodSelectorBase = baseAddress + header->relativeMethodSelectorBaseAddressOffset; } + else if (const auto info = GetLegacyObjCOptimizationHeader(controller->GetCache(), dangerReader); info.has_value()) + { + const auto [optSectionAddr, header] = *info; + m_customRelativeMethodSelectorBase = optSectionAddr + header.relativeMethodSelectorBaseAddressOffset; + } } return m_customRelativeMethodSelectorBase.value_or(0); diff --git a/view/sharedcache/core/ObjC.h b/view/sharedcache/core/ObjC.h index a30b9de49b..e8c47e70fe 100644 --- a/view/sharedcache/core/ObjC.h +++ b/view/sharedcache/core/ObjC.h @@ -16,6 +16,22 @@ struct ObjCOptimizationHeader uint64_t relativeMethodSelectorBaseAddressOffset; }; +// `objc_opt_t` from dyld/include/objc-shared-cache.h +struct LegacyObjCOptimizationHeader +{ + uint32_t version; + uint32_t flags; + int32_t selopt_offset; + int32_t headeropt_ro_offset; + int32_t unused_clsopt_offset; + int32_t unused_protocolopt_offset; + int32_t headeropt_rw_offset; + int32_t unused_protocolopt2_offset; + int32_t largeSharedCachesClassOffset; + int32_t largeSharedCachesProtocolOffset; + int64_t relativeMethodSelectorBaseAddressOffset; +}; + namespace DSCObjC { class SharedCacheObjCReader : public BinaryNinja::ObjCReader { diff --git a/view/sharedcache/core/SharedCache.cpp b/view/sharedcache/core/SharedCache.cpp index 0a14cfafde..c084c6920d 100644 --- a/view/sharedcache/core/SharedCache.cpp +++ b/view/sharedcache/core/SharedCache.cpp @@ -91,10 +91,8 @@ CacheEntry CacheEntry::FromFile(const std::string& filePath, const std::string& { // We found a single symbols cache entry file. Mark it as such! type = CacheEntryType::Symbols; - // Adjust the mapping for the symbol file, they seem to be only for the header. - // If we do not adjust the mapping than we will not be able to read the symbol table through the virtual memory. - mappings[0].fileOffset = 0; - mappings[0].size = file->Length(); + // Symbol files are not mapped into the address space. + mappings.clear(); } else if (mappings.size() == 1 && header.imagesCountOld == 0 && header.imagesCount == 0 && header.imagesTextOffset == 0) @@ -231,6 +229,17 @@ void SharedCache::AddEntry(CacheEntry entry) // Get the file accessor to associate with the virtual memory region. auto fileAccessor = FileAccessorCache::Global().Open(entry.GetFilePath()); + if (entry.GetType() == CacheEntryType::Symbols) + { + m_localSymbolsEntry = std::move(entry); + // Map the entire file into its own virtual memory space. + // This is necessary due to code that processes symbols being written in terms of a `VirtualMemory` + // rather than something more generic. + m_localSymbolsVM = std::make_shared(m_vm->GetAddressSize()); + m_localSymbolsVM->MapRegion(fileAccessor, {0, fileAccessor.lock()->Length()}, 0); + return; + } + // Populate virtual memory using the entry mappings, by doing so we can now // read the memory of the mapped regions of the cache entry file. const auto& mappings = entry.GetMappings(); diff --git a/view/sharedcache/core/SharedCache.h b/view/sharedcache/core/SharedCache.h index efef394c72..58f1f38ca2 100644 --- a/view/sharedcache/core/SharedCache.h +++ b/view/sharedcache/core/SharedCache.h @@ -188,6 +188,11 @@ class SharedCache // NOTE: Wrapped in unique_ptr to keep SharedCache movable. std::unique_ptr m_namedSymMutex; + // Local symbols entry and its mapping, used to read symbol tables from the .symbols file. + // These are handled separately as they are not mapped into the main virtual memory of the cache. + std::optional m_localSymbolsEntry; + std::shared_ptr m_localSymbolsVM; + bool ProcessEntryImage(const std::string& path, const dyld_cache_image_info& info); // Add a region known not to overlap with another, otherwise use AddRegion. @@ -211,6 +216,9 @@ class SharedCache const std::unordered_map& GetImages() const { return m_images; } const std::unordered_map& GetSymbols() const { return m_symbols; } + const std::optional& GetLocalSymbolsEntry() const { return m_localSymbolsEntry; } + std::shared_ptr GetLocalSymbolsVM() const { return m_localSymbolsVM; } + void AddImage(CacheImage&& image); // Add a region that may overlap with another. diff --git a/view/sharedcache/core/SharedCacheBuilder.cpp b/view/sharedcache/core/SharedCacheBuilder.cpp index 5422d75139..15b4b7ac91 100644 --- a/view/sharedcache/core/SharedCacheBuilder.cpp +++ b/view/sharedcache/core/SharedCacheBuilder.cpp @@ -37,6 +37,9 @@ bool SharedCacheBuilder::AddFile( // Skip bndb files! if (fileName.find(".bndb") != std::string::npos) return false; + // Skip a2s files + if (fileName.find(".a2s") != std::string::npos) + return false; try {