Skip to content

Commit 11dc8b2

Browse files
committed
[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.
1 parent bdb6285 commit 11dc8b2

File tree

2 files changed

+52
-1
lines changed

2 files changed

+52
-1
lines changed

view/sharedcache/core/ObjC.cpp

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,17 +136,52 @@ std::optional<ObjCOptimizationHeader> GetObjCOptimizationHeader(SharedCache& cac
136136
return header;
137137
}
138138

139+
std::optional<std::pair<uint64_t, LegacyObjCOptimizationHeader>> GetLegacyObjCOptimizationHeader(SharedCache& cache, VirtualMemoryReader& reader)
140+
{
141+
// In older versions the header lives in the `__TEXT,__objc_opt_ro` section within /usr/lib/libobjc.A.dylib
142+
auto libObjC = cache.GetImageWithName("/usr/lib/libobjc.A.dylib");
143+
if (!libObjC)
144+
return std::nullopt;
145+
146+
// Convert the header's `char[16]` to a `string_view`.
147+
auto AsStringView = []<size_t N>(const char (&arr)[N]) {
148+
const char* end = std::find(arr, arr + N, '\0');
149+
return std::string_view(arr, end - arr);
150+
};
151+
152+
for (auto section : libObjC->header->sections) {
153+
if (AsStringView(section.segname) != "__TEXT" || AsStringView(section.sectname) != "__objc_opt_ro")
154+
continue;
155+
156+
LegacyObjCOptimizationHeader header = {};
157+
reader.Read(&header, section.addr, sizeof(LegacyObjCOptimizationHeader));
158+
159+
// The `relativeMethodSelectorBaseAddressOffset` field was added in version 16 (the final version of this struct).
160+
if (header.version >= 16)
161+
return {{section.addr, header}};
162+
163+
break;
164+
}
165+
166+
return std::nullopt;
167+
}
168+
139169
uint64_t SharedCacheObjCProcessor::GetObjCRelativeMethodBaseAddress(ObjCReader* reader)
140170
{
141171
// Try and retrieve the base address of the selector stuff.
142172
if (const auto controller = DSC::SharedCacheController::FromView(*m_data))
143173
{
144-
auto baseAddress = controller->GetCache().GetBaseAddress();
145174
auto dangerReader = dynamic_cast<SharedCacheObjCReader*>(reader)->GetVMReader();
146175
if (const auto header = GetObjCOptimizationHeader(controller->GetCache(), dangerReader); header.has_value())
147176
{
177+
auto baseAddress = controller->GetCache().GetBaseAddress();
148178
m_customRelativeMethodSelectorBase = baseAddress + header->relativeMethodSelectorBaseAddressOffset;
149179
}
180+
else if (const auto info = GetLegacyObjCOptimizationHeader(controller->GetCache(), dangerReader); info.has_value())
181+
{
182+
const auto [optSectionAddr, header] = *info;
183+
m_customRelativeMethodSelectorBase = optSectionAddr + header.relativeMethodSelectorBaseAddressOffset;
184+
}
150185
}
151186

152187
return m_customRelativeMethodSelectorBase.value_or(0);

view/sharedcache/core/ObjC.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,22 @@ struct ObjCOptimizationHeader
1616
uint64_t relativeMethodSelectorBaseAddressOffset;
1717
};
1818

19+
// `objc_opt_t` from dyld/include/objc-shared-cache.h
20+
struct LegacyObjCOptimizationHeader
21+
{
22+
uint32_t version;
23+
uint32_t flags;
24+
int32_t selopt_offset;
25+
int32_t headeropt_ro_offset;
26+
int32_t unused_clsopt_offset;
27+
int32_t unused_protocolopt_offset;
28+
int32_t headeropt_rw_offset;
29+
int32_t unused_protocolopt2_offset;
30+
int32_t largeSharedCachesClassOffset;
31+
int32_t largeSharedCachesProtocolOffset;
32+
int64_t relativeMethodSelectorBaseAddressOffset;
33+
};
34+
1935
namespace DSCObjC {
2036
class SharedCacheObjCReader : public BinaryNinja::ObjCReader
2137
{

0 commit comments

Comments
 (0)