From f35e3ae4504d039df5d99c9f156a523edc6059b2 Mon Sep 17 00:00:00 2001 From: Mark Rowe Date: Sun, 24 Nov 2024 22:34:40 -0800 Subject: [PATCH 1/2] [SharedCache] Track whether non-image regions are data vs code `BackingCache` now tracks the `dyld_cache_mapping_info` for its mappings so it has access to the memory protections for the region. This means it can avoid marking some regions as containing code when they don't, reducing the amount of analysis work that has to be done. Using `dyld_cache_mapping_info` also makes references to mappings easier to understand due to its named fields vs the nested `std::pair`s that were previously in use. --- view/sharedcache/core/SharedCache.cpp | 112 +++++++++----------------- view/sharedcache/core/SharedCache.h | 74 ++++++++++++----- 2 files changed, 94 insertions(+), 92 deletions(-) diff --git a/view/sharedcache/core/SharedCache.cpp b/view/sharedcache/core/SharedCache.cpp index a7c7ffb01..5df7b56a6 100644 --- a/view/sharedcache/core/SharedCache.cpp +++ b/view/sharedcache/core/SharedCache.cpp @@ -94,6 +94,24 @@ std::string base_name(std::string const& path) return path.substr(path.find_last_of("/\\") + 1); } +BNSegmentFlag 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 (BNSegmentFlag)flags; +} + #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" @@ -266,11 +284,7 @@ void SharedCache::PerformInitialLoad() for (size_t i = 0; i < primaryCacheHeader.mappingCount; i++) { baseFile->Read(&mapping, primaryCacheHeader.mappingOffset + (i * sizeof(mapping)), sizeof(mapping)); - std::pair> mapRawToAddrAndSize; - mapRawToAddrAndSize.first = mapping.fileOffset; - mapRawToAddrAndSize.second.first = mapping.address; - mapRawToAddrAndSize.second.second = mapping.size; - cache.mappings.push_back(mapRawToAddrAndSize); + cache.mappings.push_back(mapping); } m_backingCaches.push_back(cache); @@ -334,11 +348,7 @@ void SharedCache::PerformInitialLoad() for (size_t i = 0; i < primaryCacheHeader.mappingCount; i++) { baseFile->Read(&mapping, primaryCacheHeader.mappingOffset + (i * sizeof(mapping)), sizeof(mapping)); - std::pair> mapRawToAddrAndSize; - mapRawToAddrAndSize.first = mapping.fileOffset; - mapRawToAddrAndSize.second.first = mapping.address; - mapRawToAddrAndSize.second.second = mapping.size; - cache.mappings.push_back(mapRawToAddrAndSize); + cache.mappings.push_back(mapping); } m_backingCaches.push_back(cache); @@ -409,11 +419,7 @@ void SharedCache::PerformInitialLoad() { subCacheFile->Read(&subCacheMapping, subCacheHeader.mappingOffset + (j * sizeof(subCacheMapping)), sizeof(subCacheMapping)); - std::pair> mapRawToAddrAndSize; - mapRawToAddrAndSize.first = subCacheMapping.fileOffset; - mapRawToAddrAndSize.second.first = subCacheMapping.address; - mapRawToAddrAndSize.second.second = subCacheMapping.size; - subCache.mappings.push_back(mapRawToAddrAndSize); + subCache.mappings.push_back(subCacheMapping); } if (subCacheHeader.mappingCount == 1 && subCacheHeader.imagesCountOld == 0 && subCacheHeader.imagesCount == 0 @@ -445,11 +451,7 @@ void SharedCache::PerformInitialLoad() for (size_t i = 0; i < primaryCacheHeader.mappingCount; i++) { baseFile->Read(&mapping, primaryCacheHeader.mappingOffset + (i * sizeof(mapping)), sizeof(mapping)); - std::pair> mapRawToAddrAndSize; - mapRawToAddrAndSize.first = mapping.fileOffset; - mapRawToAddrAndSize.second.first = mapping.address; - mapRawToAddrAndSize.second.second = mapping.size; - cache.mappings.push_back(mapRawToAddrAndSize); + cache.mappings.push_back(mapping); } m_backingCaches.push_back(cache); @@ -504,11 +506,7 @@ void SharedCache::PerformInitialLoad() { subCacheFile->Read(&subCacheMapping, subCacheHeader.mappingOffset + (j * sizeof(subCacheMapping)), sizeof(subCacheMapping)); - std::pair> mapRawToAddrAndSize; - mapRawToAddrAndSize.first = subCacheMapping.fileOffset; - mapRawToAddrAndSize.second.first = subCacheMapping.address; - mapRawToAddrAndSize.second.second = subCacheMapping.size; - subCache.mappings.push_back(mapRawToAddrAndSize); + subCache.mappings.push_back(subCacheMapping); } m_backingCaches.push_back(subCache); @@ -550,11 +548,7 @@ void SharedCache::PerformInitialLoad() { subCacheFile->Read(&subCacheMapping, subCacheHeader.mappingOffset + (j * sizeof(subCacheMapping)), sizeof(subCacheMapping)); - std::pair> mapRawToAddrAndSize; - mapRawToAddrAndSize.first = subCacheMapping.fileOffset; - mapRawToAddrAndSize.second.first = subCacheMapping.address; - mapRawToAddrAndSize.second.second = subCacheMapping.size; - subCache.mappings.push_back(mapRawToAddrAndSize); + subCache.mappings.push_back(subCacheMapping); } m_backingCaches.push_back(subCache); @@ -571,11 +565,7 @@ void SharedCache::PerformInitialLoad() for (size_t i = 0; i < primaryCacheHeader.mappingCount; i++) { baseFile->Read(&mapping, primaryCacheHeader.mappingOffset + (i * sizeof(mapping)), sizeof(mapping)); - std::pair> mapRawToAddrAndSize; - mapRawToAddrAndSize.first = mapping.fileOffset; - mapRawToAddrAndSize.second.first = mapping.address; - mapRawToAddrAndSize.second.second = mapping.size; - cache.mappings.push_back(mapRawToAddrAndSize); + cache.mappings.push_back(mapping); } m_backingCaches.push_back(cache); @@ -652,12 +642,7 @@ void SharedCache::PerformInitialLoad() { subCacheFile->Read(&subCacheMapping, subCacheHeader.mappingOffset + (j * sizeof(subCacheMapping)), sizeof(subCacheMapping)); - - std::pair> mapRawToAddrAndSize; - mapRawToAddrAndSize.first = subCacheMapping.fileOffset; - mapRawToAddrAndSize.second.first = subCacheMapping.address; - mapRawToAddrAndSize.second.second = subCacheMapping.size; - subCache.mappings.push_back(mapRawToAddrAndSize); + subCache.mappings.push_back(subCacheMapping); if (subCachePath.find(".dylddata") != std::string::npos) { @@ -714,11 +699,7 @@ void SharedCache::PerformInitialLoad() { subCacheFile->Read(&subCacheMapping, subCacheHeader.mappingOffset + (j * sizeof(subCacheMapping)), sizeof(subCacheMapping)); - std::pair> mapRawToAddrAndSize; - mapRawToAddrAndSize.first = subCacheMapping.fileOffset; - mapRawToAddrAndSize.second.first = subCacheMapping.address; - mapRawToAddrAndSize.second.second = subCacheMapping.size; - subCache.mappings.push_back(mapRawToAddrAndSize); + subCache.mappings.push_back(subCacheMapping); } m_backingCaches.push_back(subCache); @@ -765,19 +746,7 @@ void SharedCache::PerformInitialLoad() sectionRegion.prettyName = imageHeader.value().identifierPrefix + "::" + std::string(segName); sectionRegion.start = segment.vmaddr; sectionRegion.size = segment.vmsize; - uint32_t flags = 0; - if (segment.initprot & MACHO_VM_PROT_READ) - flags |= SegmentReadable; - if (segment.initprot & MACHO_VM_PROT_WRITE) - flags |= SegmentWritable; - if (segment.initprot & MACHO_VM_PROT_EXECUTE) - flags |= SegmentExecutable; - if (((segment.initprot & MACHO_VM_PROT_WRITE) == 0) && - ((segment.maxprot & MACHO_VM_PROT_WRITE) == 0)) - flags |= SegmentDenyWrite; - if (((segment.initprot & MACHO_VM_PROT_EXECUTE) == 0) && - ((segment.maxprot & MACHO_VM_PROT_EXECUTE) == 0)) - flags |= SegmentDenyExecute; + uint32_t flags = SegmentFlagsFromMachOProtections(segment.initprot, segment.maxprot); // if we're positive we have an entry point for some reason, force the segment // executable. this helps with kernel images. @@ -809,12 +778,11 @@ void SharedCache::PerformInitialLoad() for (const auto& mapping : cache.mappings) { MemoryRegion region; - region.start = mapping.second.first; - region.size = mapping.second.second; + region.start = mapping.address; + region.size = mapping.size; region.prettyName = base_name(cache.path) + "::" + std::to_string(i); - // FIXME flags!!! BackingCache.mapping needs refactored to store this information! - region.flags = (BNSegmentFlag)(BNSegmentFlag::SegmentReadable | BNSegmentFlag::SegmentExecutable); - m_nonImageRegions.push_back(region); + region.flags = SegmentFlagsFromMachOProtections(mapping.initProt, mapping.maxProt); + m_nonImageRegions.push_back(std::move(region)); i++; } } @@ -963,7 +931,7 @@ std::shared_ptr SharedCache::GetVMMap(bool mapPages) { for (const auto& mapping : cache.mappings) { - vm->MapPages(m_dscView, m_dscView->GetFile()->GetSessionId(), mapping.second.first, mapping.first, mapping.second.second, cache.path, + vm->MapPages(m_dscView, m_dscView->GetFile()->GetSessionId(), mapping.address, mapping.fileOffset, mapping.size, cache.path, [this, vm=vm](std::shared_ptr mmap){ ParseAndApplySlideInfoForFile(mmap); }); @@ -1038,9 +1006,9 @@ void SharedCache::ParseAndApplySlideInfoForFile(std::shared_ptrGetParentView()->WriteBuffer(rawViewEnd, buff); m_dscView->GetParentView()->AddAutoSegment(rawViewEnd, region.size, rawViewEnd, region.size, region.flags); m_dscView->AddUserSegment(region.start, region.size, rawViewEnd, region.size, region.flags); - m_dscView->AddUserSection(name, region.start, region.size, ReadOnlyCodeSectionSemantics); + m_dscView->AddUserSection(name, region.start, region.size, region.flags & SegmentDenyExecute ? ReadOnlyDataSectionSemantics : ReadOnlyCodeSectionSemantics); m_dscView->WriteBuffer(region.start, buff); region.loaded = true; @@ -3174,11 +3142,11 @@ extern "C" mappings = (BNDSCBackingCacheMapping*)malloc(sizeof(BNDSCBackingCacheMapping) * viewCaches[i].mappings.size()); size_t j = 0; - for (const auto& [fileOffset, mapping] : viewCaches[i].mappings) + for (const auto& mapping : viewCaches[i].mappings) { - mappings[j].vmAddress = mapping.first; - mappings[j].size = mapping.second; - mappings[j].fileOffset = fileOffset; + mappings[j].vmAddress = mapping.address; + mappings[j].size = mapping.size; + mappings[j].fileOffset = mapping.fileOffset; j++; } caches[i].mappings = mappings; diff --git a/view/sharedcache/core/SharedCache.h b/view/sharedcache/core/SharedCache.h index 81c026203..327604006 100644 --- a/view/sharedcache/core/SharedCache.h +++ b/view/sharedcache/core/SharedCache.h @@ -91,26 +91,6 @@ namespace SharedCacheCore { } }; - struct BackingCache : public MetadataSerializable - { - std::string path; - bool isPrimary = false; - std::vector>> mappings; - - void Store() override - { - MSS(path); - MSS(isPrimary); - MSS(mappings); - } - void Load() override - { - MSL(path); - MSL(isPrimary); - MSL(mappings); - } - }; - #if defined(__GNUC__) || defined(__clang__) #define PACKED_STRUCT __attribute__((packed)) #else @@ -132,6 +112,60 @@ namespace SharedCacheCore { uint32_t initProt; }; + struct BackingCache : public MetadataSerializable + { + std::string path; + bool isPrimary = false; + std::vector mappings; + + void Store() + { + MSS(path); + MSS(isPrimary); + MSS_SUBCLASS(mappings); + } + void Load() + { + MSL(path); + MSL(isPrimary); + MSL_SUBCLASS(mappings); + } + + void Serialize(const std::string& name, const std::vector& mappings) + { + S(); + rapidjson::Value key(name.c_str(), m_activeContext.allocator); + rapidjson::Value mappingsArr(rapidjson::kArrayType); + for (auto& mapping : mappings) + { + rapidjson::Value mappingArr(rapidjson::kArrayType); + mappingArr.PushBack(mapping.address, m_activeContext.allocator); + mappingArr.PushBack(mapping.size, m_activeContext.allocator); + mappingArr.PushBack(mapping.fileOffset, m_activeContext.allocator); + mappingArr.PushBack(mapping.maxProt, m_activeContext.allocator); + mappingArr.PushBack(mapping.initProt, m_activeContext.allocator); + mappingsArr.PushBack(mappingArr, m_activeContext.allocator); + } + m_activeContext.doc.AddMember(key, mappingsArr, m_activeContext.allocator); + } + + void Deserialize(const std::string& name, std::vector& b) + { + auto bArr = m_activeDeserContext.doc[name.data()].GetArray(); + for (auto& s : bArr) + { + dyld_cache_mapping_info mapping; + auto s2 = s.GetArray(); + mapping.address = s2[0].GetUint64(); + mapping.size = s2[1].GetUint64(); + mapping.fileOffset = s2[2].GetUint64(); + mapping.maxProt = s2[3].GetUint(); + mapping.initProt = s2[4].GetUint(); + b.push_back(mapping); + } + } + }; + struct LoadedMapping { std::shared_ptr backingFile; From 54e33b28f68545349d37b8a4aab440abf47576d0 Mon Sep 17 00:00:00 2001 From: Mark Rowe Date: Sun, 24 Nov 2024 22:28:04 -0800 Subject: [PATCH 2/2] [SharedCache] Fix handling of relative selectors in macOS shared caches Find the relative selector base address in the Objective-C optimization data pointed to by the shared cache header, rather than via `__objc_scoffs`. This is only present on iOS, and not for every iOS version that encodes selectors via direct offsets. This also includes some related improvements: 1. Direct selectors get their own pointer type so they're rendered correctly in the view. 2. Method lists encoded as lists of lists are now handled. 3. The `dyld_cache_header` type added to the view is truncated to the length in the loaded cache. This ensures it is applied to the view. 4. A couple of methods that process method IMPs and selectors are updated to check whether the address is valid before attempting to process them. They would otherwise fail by throwing an exception if they proceed, but checking for validity is quicker and makes exception breakpoints usable. --- view/sharedcache/core/DSCView.cpp | 18 +- view/sharedcache/core/ObjC.cpp | 116 ++++++------ view/sharedcache/core/ObjC.h | 9 +- view/sharedcache/core/SharedCache.cpp | 43 +++++ view/sharedcache/core/SharedCache.h | 165 ++++++++++-------- .../workflow/SharedCacheWorkflow.cpp | 2 +- 6 files changed, 228 insertions(+), 125 deletions(-) diff --git a/view/sharedcache/core/DSCView.cpp b/view/sharedcache/core/DSCView.cpp index 31af3bd73..1ef6198b3 100644 --- a/view/sharedcache/core/DSCView.cpp +++ b/view/sharedcache/core/DSCView.cpp @@ -200,6 +200,15 @@ bool DSCView::Init() "\t\tuint64_t rosettaReadWriteSize;\t// maximum size of the Rosetta read-write region\n" "\t\tuint32_t imagesOffset;\t\t\t// file offset to first dyld_cache_image_info\n" "\t\tuint32_t imagesCount;\t\t\t// number of dyld_cache_image_info entries\n" + "\t\tuint32_t cacheSubType; // 0 for development, 1 for production, when cacheType is multi-cache(2)\n" + "\t\tuint64_t objcOptsOffset; // VM offset from cache_header* to ObjC optimizations header\n" + "\t\tuint64_t objcOptsSize; // size of ObjC optimizations header\n" + "\t\tuint64_t cacheAtlasOffset; // VM offset from cache_header* to embedded cache atlas for process introspection\n" + "\t\tuint64_t cacheAtlasSize; // size of embedded cache atlas\n" + "\t\tuint64_t dynamicDataOffset; // VM offset from cache_header* to the location of dyld_cache_dynamic_data_header\n" + "\t\tuint64_t dynamicDataMaxSize; // maximum size of space reserved from dynamic data\n" + "\t\tuint32_t tproMappingsOffset; // file offset to first dyld_cache_tpro_mapping_info\n" + "\t\tuint32_t tproMappingsCount; // number of dyld_cache_tpro_mapping_info entries\n" "\t};", headerType, err); Ref settings = GetLoadSettings(GetTypeName()); @@ -732,8 +741,13 @@ bool DSCView::Init() return false; } - AddAutoSegment(primaryBase, 0x200, 0, 0x200, SegmentReadable); - AddAutoSection("__dsc_header", primaryBase, 0x200, ReadOnlyCodeSectionSemantics); + uint64_t headerSize = std::min(basePointer, headerType.type->GetWidth()); + // Truncate the `dyld_cache_header` structure to the structure present in the cache file. + auto newStructure = StructureBuilder(headerType.type->GetStructure()).SetWidth(headerSize).Finalize(); + headerType.type = TypeBuilder::StructureType(newStructure).Finalize(); + + AddAutoSegment(primaryBase, headerSize, 0, headerSize, SegmentReadable); + AddAutoSection("__dsc_header", primaryBase, headerSize, ReadOnlyDataSectionSemantics); DefineType("dyld_cache_header", headerType.name, headerType.type); DefineAutoSymbolAndVariableOrFunction(GetDefaultPlatform(), new Symbol(DataSymbol, "primary_cache_header", primaryBase), headerType.type); diff --git a/view/sharedcache/core/ObjC.cpp b/view/sharedcache/core/ObjC.cpp index 271e5c941..0b721220a 100644 --- a/view/sharedcache/core/ObjC.cpp +++ b/view/sharedcache/core/ObjC.cpp @@ -775,22 +775,57 @@ void DSCObjCProcessor::LoadProtocols(VMReader* reader, Ref
listSection) } } -void DSCObjCProcessor::ReadMethodList(VMReader* reader, ClassBase& cls, std::string name, view_ptr_t start) +void DSCObjCProcessor::ReadListOfMethodLists(VMReader* reader, ClassBase& cls, std::string_view name, view_ptr_t start) { reader->Seek(start); method_list_t head; head.entsizeAndFlags = reader->Read32(); head.count = reader->Read32(); + if (head.count > 0x1000) + { + m_logger->LogError("List of method lists at 0x%llx has an invalid count of 0x%x", start, head.count); + return; + } + + for (size_t i = 0; i < head.count; ++i) { + relative_list_list_entry_t list_entry; + reader->Read(&list_entry, sizeof(list_entry)); + + ReadMethodList(reader, cls, name, reader->GetOffset() - sizeof(list_entry) + list_entry.listOffset); + // Reset the cursor to immediately past the list entry. + reader->Seek(start + sizeof(method_list_t) + ((i + 1) * sizeof(relative_list_list_entry_t))); + } +} + +void DSCObjCProcessor::ReadMethodList(VMReader* reader, ClassBase& cls, std::string_view name, view_ptr_t start) +{ + // Lower two bits indicate the type of method list. + switch (start & 0b11) { + case 0: + break; + case 1: + return ReadListOfMethodLists(reader, cls, name, start - 1); + default: + m_logger->LogDebug("ReadMethodList: Unknown method list type at 0x%llx: %d", start, start & 0x3); + return; + } + + reader->Seek(start); + method_list_t head; + head.entsizeAndFlags = reader->Read32(); + head.count = reader->Read32(); + if (head.count > 0x1000) { m_logger->LogError("Method list at 0x%llx has an invalid count of 0x%x", start, head.count); return; } + uint64_t pointerSize = m_data->GetAddressSize(); bool relativeOffsets = (head.entsizeAndFlags & 0xFFFF0000) & 0x80000000; bool directSelectors = (head.entsizeAndFlags & 0xFFFF0000) & 0x40000000; auto methodSize = relativeOffsets ? 12 : pointerSize * 3; - DefineObjCSymbol(DataSymbol, m_typeNames.methodList, "method_list_" + name, start, true); + DefineObjCSymbol(DataSymbol, m_typeNames.methodList, "method_list_" + std::string(name), start, true); for (unsigned i = 0; i < head.count; i++) { @@ -806,18 +841,14 @@ void DSCObjCProcessor::ReadMethodList(VMReader* reader, ClassBase& cls, std::str // -- if (relativeOffsets) { - if (m_customRelativeMethodSelectorBase.has_value()) - { - meth.name = m_customRelativeMethodSelectorBase.value() + reader->ReadS32(); - meth.types = reader->GetOffset() + reader->ReadS32(); - meth.imp = reader->GetOffset() + reader->ReadS32(); - } - else - { - meth.name = reader->GetOffset() + reader->ReadS32(); - meth.types = reader->GetOffset() + reader->ReadS32(); - meth.imp = reader->GetOffset() + reader->ReadS32(); + auto selectorBaseOffset = reader->GetOffset(); + if (directSelectors && m_customRelativeMethodSelectorBase.has_value()) { + selectorBaseOffset = m_customRelativeMethodSelectorBase.value(); } + + meth.name = selectorBaseOffset + reader->Read32(); + meth.types = reader->GetOffset() + reader->ReadS32(); + meth.imp = reader->GetOffset() + reader->ReadS32(); } else { @@ -881,14 +912,14 @@ void DSCObjCProcessor::ReadMethodList(VMReader* reader, ClassBase& cls, std::str } } -void DSCObjCProcessor::ReadIvarList(VMReader* reader, ClassBase& cls, std::string name, view_ptr_t start) +void DSCObjCProcessor::ReadIvarList(VMReader* reader, ClassBase& cls, std::string_view name, view_ptr_t start) { reader->Seek(start); ivar_list_t head; head.entsizeAndFlags = reader->Read32(); head.count = reader->Read32(); auto addressSize = m_data->GetAddressSize(); - DefineObjCSymbol(DataSymbol, m_typeNames.ivarList, "ivar_list_" + name, start, true); + DefineObjCSymbol(DataSymbol, m_typeNames.ivarList, "ivar_list_" + std::string(name), start, true); if (head.count > 0x1000) { m_logger->LogError("Ivar list at 0x%llx has an invalid count of 0x%llx", start, head.count); @@ -1010,6 +1041,10 @@ void DSCObjCProcessor::GenerateClassTypes() bool DSCObjCProcessor::ApplyMethodType(Class& cls, Method& method, bool isInstanceMethod) { + if (!method.imp || !m_data->IsValidOffset(method.imp)) { + return false; + } + std::stringstream r(method.name); std::string token; @@ -1221,6 +1256,19 @@ void DSCObjCProcessor::ProcessObjCData(std::shared_ptr vm, std::string baseN m_typeNames.nsuInteger = defineTypedef(m_data, {"NSUInteger"}, Type::IntegerType(addrSize, false)); m_typeNames.cgFloat = defineTypedef(m_data, {"CGFloat"}, Type::FloatType(addrSize)); + Ref relativeSelectorPtr; + auto reader = VMReader(vm); + if (auto objCRelativeMethodsBaseAddr = m_cache->GetObjCRelativeMethodBaseAddress(reader)) { + m_logger->LogDebug("RelativeMethodSelector Base: 0x%llx", objCRelativeMethodsBaseAddr); + m_customRelativeMethodSelectorBase = objCRelativeMethodsBaseAddr; + + auto type = TypeBuilder::PointerType(4, Type::PointerType(addrSize, Type::IntegerType(1, false))) + .SetPointerBase(RelativeToConstantPointerBaseType, objCRelativeMethodsBaseAddr) + .Finalize(); + auto relativeSelectorPtrName = defineTypedef(m_data, {"relative_SEL"}, type); + relativeSelectorPtr = Type::NamedType(m_data, relativeSelectorPtrName); + } + // https://github.com/apple-oss-distributions/objc4/blob/196363c165b175ed925ef6b9b99f558717923c47/runtime/objc-abi.h EnumerationBuilder imageInfoFlagBuilder; imageInfoFlagBuilder.AddMemberWithValue("IsReplacement", 1 << 0); @@ -1256,7 +1304,7 @@ void DSCObjCProcessor::ProcessObjCData(std::shared_ptr vm, std::string baseN m_typeNames.imageInfo = imageInfoType.first; StructureBuilder methodEntry; - methodEntry.AddMember(rptr_t, "name"); + methodEntry.AddMember(relativeSelectorPtr ? relativeSelectorPtr : rptr_t, "name"); methodEntry.AddMember(rptr_t, "types"); methodEntry.AddMember(rptr_t, "imp"); auto type = finalizeStructureBuilder(m_data, methodEntry, "objc_method_entry_t"); @@ -1360,42 +1408,6 @@ void DSCObjCProcessor::ProcessObjCData(std::shared_ptr vm, std::string baseN protocolBuilder.AddMember(Type::IntegerType(4, false), "flags"); m_typeNames.protocol = finalizeStructureBuilder(m_data, protocolBuilder, "objc_protocol_t").first; - auto reader = VMReader(vm); - - if (auto addr = m_cache->GetImageStart("/usr/lib/libobjc.A.dylib")) - { - auto header = m_cache->HeaderForAddress(addr.value()); - uint64_t scoffs_addr = 0; - size_t scoffs_size = 0; - - for (const auto& section : header->sections) - { - char name[17]; - memcpy(name, section.sectname, 16); - name[16] = 0; - if (std::string(name) == "__objc_scoffs") - { - scoffs_addr = section.addr; - scoffs_size = section.size; - break; - } - } - - if (scoffs_size && scoffs_addr) - { - if (scoffs_size == 0x20) - { - m_customRelativeMethodSelectorBase = reader.ReadULong(scoffs_addr); - } - else - { - m_customRelativeMethodSelectorBase = reader.ReadULong(scoffs_addr + 8); - } - m_logger->LogDebug("RelativeMethodSelector Base: 0x%llx", m_customRelativeMethodSelectorBase.value()); - } - } - - m_data->BeginBulkModifySymbols(); if (auto classList = m_data->GetSectionByName(baseName + "::__objc_classlist")) LoadClasses(&reader, classList); diff --git a/view/sharedcache/core/ObjC.h b/view/sharedcache/core/ObjC.h index 40f2441bb..ffc69b5f5 100644 --- a/view/sharedcache/core/ObjC.h +++ b/view/sharedcache/core/ObjC.h @@ -58,6 +58,10 @@ namespace DSCObjC { typedef struct { uint64_t count; } protocol_list_t; + struct relative_list_list_entry_t { + uint64_t imageIndex: 16; + int64_t listOffset: 48; + }; typedef struct { view_ptr_t isa; view_ptr_t mangledName; @@ -214,8 +218,9 @@ namespace DSCObjC { std::vector ParseEncodedType(const std::string& type); void DefineObjCSymbol(BNSymbolType symbolType, QualifiedName typeName, const std::string& name, uint64_t addr, bool deferred); void DefineObjCSymbol(BNSymbolType symbolType, Ref type, const std::string& name, uint64_t addr, bool deferred); - void ReadIvarList(VMReader* reader, ClassBase& cls, std::string name, view_ptr_t start); - void ReadMethodList(VMReader* reader, ClassBase& cls, std::string name, view_ptr_t start); + void ReadIvarList(VMReader* reader, ClassBase& cls, std::string_view name, view_ptr_t start); + void ReadMethodList(VMReader* reader, ClassBase& cls, std::string_view name, view_ptr_t start); + void ReadListOfMethodLists(VMReader* reader, ClassBase& cls, std::string_view name, view_ptr_t start); void LoadClasses(VMReader* reader, Ref
listSection); void LoadCategories(VMReader* reader, Ref
listSection); void LoadProtocols(VMReader* reader, Ref
listSection); diff --git a/view/sharedcache/core/SharedCache.cpp b/view/sharedcache/core/SharedCache.cpp index 5df7b56a6..7b23e69c7 100644 --- a/view/sharedcache/core/SharedCache.cpp +++ b/view/sharedcache/core/SharedCache.cpp @@ -73,6 +73,7 @@ struct ViewStateCacheStore { std::unordered_map>>> m_exportInfos; std::unordered_map>>> m_symbolInfos; + std::optional> m_objcOptimizationDataRange; }; static std::recursive_mutex viewStateMutex; @@ -272,6 +273,10 @@ void SharedCache::PerformInitialLoad() m_cacheFormat = iOS16CacheFormat; } + if (primaryCacheHeader.objcOptsOffset && primaryCacheHeader.objcOptsSize) { + m_objcOptimizationDataRange = {primaryCacheHeader.objcOptsOffset, primaryCacheHeader.objcOptsSize}; + } + switch (m_cacheFormat) { case RegularCacheFormat: @@ -3288,3 +3293,41 @@ void InitDSCViewType() g_dscViewType = &type; g_dscRawViewType = &rawType; } + +size_t SharedCache::GetBaseAddress() const { + if (m_backingCaches.empty()) { + return 0; + } + + const BackingCache& primaryCache = m_backingCaches[0]; + if (!primaryCache.isPrimary) { + abort(); + return 0; + } + + if (primaryCache.mappings.empty()) { + return 0; + } + + return primaryCache.mappings[0].address; +} + +// Intentionally takes a copy to avoid modifying the cursor position in the original reader. +std::optional SharedCache::GetObjCOptimizationHeader(VMReader reader) const { + if (!m_objcOptimizationDataRange) { + return {}; + } + + ObjCOptimizationHeader header{}; + // Ignoring `objcOptsSize` in favor of `sizeof(ObjCOptimizationHeader)` matches dyld's behavior. + reader.Read(&header, GetBaseAddress() + m_objcOptimizationDataRange->first, sizeof(ObjCOptimizationHeader)); + + return header; +} + +size_t SharedCache::GetObjCRelativeMethodBaseAddress(const VMReader& reader) const { + if (auto header = GetObjCOptimizationHeader(reader); header.has_value()) { + return GetBaseAddress() + header->relativeMethodSelectorBaseAddressOffset; + } + return 0; +} diff --git a/view/sharedcache/core/SharedCache.h b/view/sharedcache/core/SharedCache.h index 327604006..6228fd7cc 100644 --- a/view/sharedcache/core/SharedCache.h +++ b/view/sharedcache/core/SharedCache.h @@ -301,74 +301,84 @@ namespace SharedCacheCore { struct PACKED_STRUCT dyld_cache_header { - char magic[16]; // e.g. "dyld_v0 i386" - uint32_t mappingOffset; // file offset to first dyld_cache_mapping_info - uint32_t mappingCount; // number of dyld_cache_mapping_info entries - uint32_t imagesOffsetOld; // UNUSED: moved to imagesOffset to prevent older dsc_extarctors from crashing - uint32_t imagesCountOld; // UNUSED: moved to imagesCount to prevent older dsc_extarctors from crashing - uint64_t dyldBaseAddress; // base address of dyld when cache was built - uint64_t codeSignatureOffset; // file offset of code signature blob - uint64_t codeSignatureSize; // size of code signature blob (zero means to end of file) - uint64_t slideInfoOffsetUnused; // unused. Used to be file offset of kernel slid info - uint64_t slideInfoSizeUnused; // unused. Used to be size of kernel slid info - uint64_t localSymbolsOffset; // file offset of where local symbols are stored - uint64_t localSymbolsSize; // size of local symbols information - uint8_t uuid[16]; // unique value for each shared cache file - uint64_t cacheType; // 0 for development, 1 for production // Kat: , 2 for iOS 16? - uint32_t branchPoolsOffset; // file offset to table of uint64_t pool addresses - uint32_t branchPoolsCount; // number of uint64_t entries - uint64_t accelerateInfoAddr; // (unslid) address of optimization info - uint64_t accelerateInfoSize; // size of optimization info - uint64_t imagesTextOffset; // file offset to first dyld_cache_image_text_info - uint64_t imagesTextCount; // number of dyld_cache_image_text_info entries - uint64_t patchInfoAddr; // (unslid) address of dyld_cache_patch_info - uint64_t patchInfoSize; // Size of all of the patch information pointed to via the dyld_cache_patch_info - uint64_t otherImageGroupAddrUnused; // unused - uint64_t otherImageGroupSizeUnused; // unused - uint64_t progClosuresAddr; // (unslid) address of list of program launch closures - uint64_t progClosuresSize; // size of list of program launch closures - uint64_t progClosuresTrieAddr; // (unslid) address of trie of indexes into program launch closures - uint64_t progClosuresTrieSize; // size of trie of indexes into program launch closures - uint32_t platform; // platform number (macOS=1, etc) - uint32_t formatVersion : 8, // dyld3::closure::kFormatVersion - dylibsExpectedOnDisk : 1, // dyld should expect the dylib exists on disk and to compare inode/mtime to see if cache is valid - simulator : 1, // for simulator of specified platform - locallyBuiltCache : 1, // 0 for B&I built cache, 1 for locally built cache - builtFromChainedFixups : 1, // some dylib in cache was built using chained fixups, so patch tables must be used for overrides - padding : 20; // TBD - uint64_t sharedRegionStart; // base load address of cache if not slid - uint64_t sharedRegionSize; // overall size required to map the cache and all subCaches, if any - uint64_t maxSlide; // runtime slide of cache can be between zero and this value - uint64_t dylibsImageArrayAddr; // (unslid) address of ImageArray for dylibs in this cache - uint64_t dylibsImageArraySize; // size of ImageArray for dylibs in this cache - uint64_t dylibsTrieAddr; // (unslid) address of trie of indexes of all cached dylibs - uint64_t dylibsTrieSize; // size of trie of cached dylib paths - uint64_t otherImageArrayAddr; // (unslid) address of ImageArray for dylibs and bundles with dlopen closures - uint64_t otherImageArraySize; // size of ImageArray for dylibs and bundles with dlopen closures - uint64_t otherTrieAddr; // (unslid) address of trie of indexes of all dylibs and bundles with dlopen closures - uint64_t otherTrieSize; // size of trie of dylibs and bundles with dlopen closures - uint32_t mappingWithSlideOffset; // file offset to first dyld_cache_mapping_and_slide_info - uint32_t mappingWithSlideCount; // number of dyld_cache_mapping_and_slide_info entries - uint64_t dylibsPBLStateArrayAddrUnused; // unused - uint64_t dylibsPBLSetAddr; // (unslid) address of PrebuiltLoaderSet of all cached dylibs - uint64_t programsPBLSetPoolAddr; // (unslid) address of pool of PrebuiltLoaderSet for each program - uint64_t programsPBLSetPoolSize; // size of pool of PrebuiltLoaderSet for each program - uint64_t programTrieAddr; // (unslid) address of trie mapping program path to PrebuiltLoaderSet - uint32_t programTrieSize; - uint32_t osVersion; // OS Version of dylibs in this cache for the main platform - uint32_t altPlatform; // e.g. iOSMac on macOS - uint32_t altOsVersion; // e.g. 14.0 for iOSMac - uint64_t swiftOptsOffset; // file offset to Swift optimizations header - uint64_t swiftOptsSize; // size of Swift optimizations header - uint32_t subCacheArrayOffset; // file offset to first dyld_subcache_entry - uint32_t subCacheArrayCount; // number of subCache entries - uint8_t symbolFileUUID[16]; // unique value for the shared cache file containing unmapped local symbols - uint64_t rosettaReadOnlyAddr; // (unslid) address of the start of where Rosetta can add read-only/executable data - uint64_t rosettaReadOnlySize; // maximum size of the Rosetta read-only/executable region - uint64_t rosettaReadWriteAddr; // (unslid) address of the start of where Rosetta can add read-write data - uint64_t rosettaReadWriteSize; // maximum size of the Rosetta read-write region - uint32_t imagesOffset; // file offset to first dyld_cache_image_info - uint32_t imagesCount; // number of dyld_cache_image_info entries + char magic[16]; // e.g. "dyld_v0 i386" + uint32_t mappingOffset; // file offset to first dyld_cache_mapping_info + uint32_t mappingCount; // number of dyld_cache_mapping_info entries + uint32_t imagesOffsetOld; // UNUSED: moved to imagesOffset to prevent older dsc_extarctors from crashing + uint32_t imagesCountOld; // UNUSED: moved to imagesCount to prevent older dsc_extarctors from crashing + uint64_t dyldBaseAddress; // base address of dyld when cache was built + uint64_t codeSignatureOffset; // file offset of code signature blob + uint64_t codeSignatureSize; // size of code signature blob (zero means to end of file) + uint64_t slideInfoOffsetUnused; // unused. Used to be file offset of kernel slid info + uint64_t slideInfoSizeUnused; // unused. Used to be size of kernel slid info + uint64_t localSymbolsOffset; // file offset of where local symbols are stored + uint64_t localSymbolsSize; // size of local symbols information + uint8_t uuid[16]; // unique value for each shared cache file + uint64_t cacheType; // 0 for development, 1 for production, 2 for multi-cache + uint32_t branchPoolsOffset; // file offset to table of uint64_t pool addresses + uint32_t branchPoolsCount; // number of uint64_t entries + uint64_t dyldInCacheMH; // (unslid) address of mach_header of dyld in cache + uint64_t dyldInCacheEntry; // (unslid) address of entry point (_dyld_start) of dyld in cache + uint64_t imagesTextOffset; // file offset to first dyld_cache_image_text_info + uint64_t imagesTextCount; // number of dyld_cache_image_text_info entries + uint64_t patchInfoAddr; // (unslid) address of dyld_cache_patch_info + uint64_t patchInfoSize; // Size of all of the patch information pointed to via the dyld_cache_patch_info + uint64_t otherImageGroupAddrUnused; // unused + uint64_t otherImageGroupSizeUnused; // unused + uint64_t progClosuresAddr; // (unslid) address of list of program launch closures + uint64_t progClosuresSize; // size of list of program launch closures + uint64_t progClosuresTrieAddr; // (unslid) address of trie of indexes into program launch closures + uint64_t progClosuresTrieSize; // size of trie of indexes into program launch closures + uint32_t platform; // platform number (macOS=1, etc) + uint32_t formatVersion : 8, // dyld3::closure::kFormatVersion + dylibsExpectedOnDisk : 1, // dyld should expect the dylib exists on disk and to compare inode/mtime to see if cache is valid + simulator : 1, // for simulator of specified platform + locallyBuiltCache : 1, // 0 for B&I built cache, 1 for locally built cache + builtFromChainedFixups : 1, // some dylib in cache was built using chained fixups, so patch tables must be used for overrides + padding : 20; // TBD + uint64_t sharedRegionStart; // base load address of cache if not slid + uint64_t sharedRegionSize; // overall size required to map the cache and all subCaches, if any + uint64_t maxSlide; // runtime slide of cache can be between zero and this value + uint64_t dylibsImageArrayAddr; // (unslid) address of ImageArray for dylibs in this cache + uint64_t dylibsImageArraySize; // size of ImageArray for dylibs in this cache + uint64_t dylibsTrieAddr; // (unslid) address of trie of indexes of all cached dylibs + uint64_t dylibsTrieSize; // size of trie of cached dylib paths + uint64_t otherImageArrayAddr; // (unslid) address of ImageArray for dylibs and bundles with dlopen closures + uint64_t otherImageArraySize; // size of ImageArray for dylibs and bundles with dlopen closures + uint64_t otherTrieAddr; // (unslid) address of trie of indexes of all dylibs and bundles with dlopen closures + uint64_t otherTrieSize; // size of trie of dylibs and bundles with dlopen closures + uint32_t mappingWithSlideOffset; // file offset to first dyld_cache_mapping_and_slide_info + uint32_t mappingWithSlideCount; // number of dyld_cache_mapping_and_slide_info entries + uint64_t dylibsPBLStateArrayAddrUnused; // unused + uint64_t dylibsPBLSetAddr; // (unslid) address of PrebuiltLoaderSet of all cached dylibs + uint64_t programsPBLSetPoolAddr; // (unslid) address of pool of PrebuiltLoaderSet for each program + uint64_t programsPBLSetPoolSize; // size of pool of PrebuiltLoaderSet for each program + uint64_t programTrieAddr; // (unslid) address of trie mapping program path to PrebuiltLoaderSet + uint32_t programTrieSize; + uint32_t osVersion; // OS Version of dylibs in this cache for the main platform + uint32_t altPlatform; // e.g. iOSMac on macOS + uint32_t altOsVersion; // e.g. 14.0 for iOSMac + uint64_t swiftOptsOffset; // VM offset from cache_header* to Swift optimizations header + uint64_t swiftOptsSize; // size of Swift optimizations header + uint32_t subCacheArrayOffset; // file offset to first dyld_subcache_entry + uint32_t subCacheArrayCount; // number of subCache entries + uint8_t symbolFileUUID[16]; // unique value for the shared cache file containing unmapped local symbols + uint64_t rosettaReadOnlyAddr; // (unslid) address of the start of where Rosetta can add read-only/executable data + uint64_t rosettaReadOnlySize; // maximum size of the Rosetta read-only/executable region + uint64_t rosettaReadWriteAddr; // (unslid) address of the start of where Rosetta can add read-write data + uint64_t rosettaReadWriteSize; // maximum size of the Rosetta read-write region + uint32_t imagesOffset; // file offset to first dyld_cache_image_info + uint32_t imagesCount; // number of dyld_cache_image_info entries + uint32_t cacheSubType; // 0 for development, 1 for production, when cacheType is multi-cache(2) + uint32_t padding2; + uint64_t objcOptsOffset; // VM offset from cache_header* to ObjC optimizations header + uint64_t objcOptsSize; // size of ObjC optimizations header + uint64_t cacheAtlasOffset; // VM offset from cache_header* to embedded cache atlas for process introspection + uint64_t cacheAtlasSize; // size of embedded cache atlas + uint64_t dynamicDataOffset; // VM offset from cache_header* to the location of dyld_cache_dynamic_data_header + uint64_t dynamicDataMaxSize; // maximum size of space reserved from dynamic data + uint32_t tproMappingsOffset; // file offset to first dyld_cache_tpro_mapping_info + uint32_t tproMappingsCount; // number of dyld_cache_tpro_mapping_info entries }; struct PACKED_STRUCT dyld_subcache_entry @@ -384,6 +394,18 @@ namespace SharedCacheCore { char fileExtension[32]; }; + struct ObjCOptimizationHeader + { + uint32_t version; + uint32_t flags; + uint64_t headerInfoROCacheOffset; + uint64_t headerInfoRWCacheOffset; + uint64_t selectorHashTableCacheOffset; + uint64_t classHashTableCacheOffset; + uint64_t protocolHashTableCacheOffset; + uint64_t relativeMethodSelectorBaseAddressOffset; + }; + #if defined(_MSC_VER) #pragma pack(pop) #else @@ -1172,6 +1194,7 @@ namespace SharedCacheCore { std::vector m_stubIslandRegions; std::vector m_dyldDataRegions; std::vector m_nonImageRegions; + std::optional> m_objcOptimizationDataRange; /* VIEWSTATE END -- NOTHING PAST THIS IS SERIALIZED */ @@ -1222,6 +1245,9 @@ namespace SharedCacheCore { explicit SharedCache(BinaryNinja::Ref rawView); ~SharedCache() override; + size_t GetObjCRelativeMethodBaseAddress(const VMReader& reader) const; + +private: std::optional LoadHeaderForAddress( std::shared_ptr vm, uint64_t address, std::string installName); void InitializeHeader( @@ -1230,6 +1256,9 @@ namespace SharedCacheCore { const std::string& currentText, size_t cursor, uint32_t endGuard); std::vector> ParseExportTrie( std::shared_ptr linkeditFile, SharedCacheMachOHeader header); + + size_t GetBaseAddress() const; + std::optional GetObjCOptimizationHeader(VMReader reader) const; }; diff --git a/view/sharedcache/workflow/SharedCacheWorkflow.cpp b/view/sharedcache/workflow/SharedCacheWorkflow.cpp index 88353bf73..f3feab855 100644 --- a/view/sharedcache/workflow/SharedCacheWorkflow.cpp +++ b/view/sharedcache/workflow/SharedCacheWorkflow.cpp @@ -468,7 +468,7 @@ void fixObjCCallTypes(Ref ctx) const auto selectorRegister = params[0].GetParameterExprs()[1].GetSourceSSARegister(); rawSelector = ssa->GetSSARegisterValue(selectorRegister).value; } - if (rawSelector == 0) + if (!rawSelector || !bv->IsValidOffset(rawSelector)) return; // -- Do callsite override