From 11dc8b216d0852c83ff5c9bfd7d3ca146a93fe84 Mon Sep 17 00:00:00 2001 From: Mark Rowe Date: Fri, 12 Sep 2025 10:01:37 -0700 Subject: [DSC] Support relative direct selectors in older shared cache versions Prior to macOS 13 / iOS 16, the base offset to use for relative direct selector references within Objective-C message lists was stored within the `__TEXT,__objc_opt_ro` section of /usr/lib/libobjc.A.dylib. --- view/sharedcache/core/ObjC.cpp | 37 ++++++++++++++++++++++++++++++++++++- view/sharedcache/core/ObjC.h | 16 ++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) (limited to 'view/sharedcache') diff --git a/view/sharedcache/core/ObjC.cpp b/view/sharedcache/core/ObjC.cpp index b6de8ba6..b411ed87 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 a30b9de4..e8c47e70 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 { -- cgit v1.3.1