From b90b45b3cb507ca93353980924df36f48ef294d2 Mon Sep 17 00:00:00 2001 From: Mark Rowe Date: Mon, 17 Feb 2025 11:04:35 -0800 Subject: [PATCH] Parse sections containing Objective-C constants This adds support for the `__objc_arrayobj`, `_objc_dictobj`, `__objc_intobj`, `__objc_floatobj`, `__objc_doubleobj` and `__objc_dateobj` sections that contain Objective-C constants. These are emitted by Apple's versions of Clang for `const` literals, amongst other things. --- objectivec/objc.cpp | 277 ++++++++++++++++++ objectivec/objc.h | 17 +- view/macho/machoview.cpp | 2 +- .../core/SharedCacheController.cpp | 2 +- 4 files changed, 294 insertions(+), 4 deletions(-) diff --git a/objectivec/objc.cpp b/objectivec/objc.cpp index 7d1104505..99ec3c8a5 100644 --- a/objectivec/objc.cpp +++ b/objectivec/objc.cpp @@ -1452,6 +1452,15 @@ void ObjCProcessor::ProcessObjCData() m_relocationPointerRewrites.clear(); } +void ObjCProcessor::ProcessObjCLiterals() +{ + ProcessCFStrings(); + ProcessNSConstantArrays(); + ProcessNSConstantDictionaries(); + ProcessNSConstantIntegerNumbers(); + ProcessNSConstantFloatingPointNumbers(); + ProcessNSConstantDatas(); +} void ObjCProcessor::ProcessCFStrings() { @@ -1570,6 +1579,274 @@ void ObjCProcessor::ProcessCFStrings() delete m_symbolQueue; } +void ObjCProcessor::ProcessNSConstantArrays() +{ + m_symbolQueue = new SymbolQueue(); + uint64_t ptrSize = m_data->GetAddressSize(); + + auto idType = Type::NamedType(m_data, m_typeNames.id); + StructureBuilder nsConstantArrayBuilder; + nsConstantArrayBuilder.AddMember(Type::PointerType(ptrSize, Type::VoidType()), "isa"); + nsConstantArrayBuilder.AddMember(Type::IntegerType(ptrSize, false), "count"); + nsConstantArrayBuilder.AddMember(Type::PointerType(ptrSize, idType), "objects"); + auto type = finalizeStructureBuilder(m_data, nsConstantArrayBuilder, "__NSConstantArray"); + m_typeNames.nsConstantArray = type.first; + + auto reader = GetReader(); + if (auto arrays = GetSectionWithName("__objc_arrayobj")) + { + auto start = arrays->GetStart(); + auto end = arrays->GetEnd(); + auto typeWidth = Type::NamedType(m_data, m_typeNames.nsConstantArray)->GetWidth(); + m_data->BeginBulkModifySymbols(); + for (view_ptr_t i = start; i < end; i += typeWidth) + { + reader->Seek(i + ptrSize); + uint64_t count = reader->ReadPointer(); + auto dataLoc = ReadPointerAccountingForRelocations(reader.get()); + DefineObjCSymbol( + DataSymbol, Type::ArrayType(idType, count), fmt::format("nsarray_{:x}_data", i), dataLoc, true); + DefineObjCSymbol(DataSymbol, Type::NamedType(m_data, m_typeNames.nsConstantArray), + fmt::format("nsarray_{:x}", i), i, true); + } + auto id = m_data->BeginUndoActions(); + m_symbolQueue->Process(); + m_data->EndBulkModifySymbols(); + m_data->ForgetUndoActions(id); + } + delete m_symbolQueue; +} + +void ObjCProcessor::ProcessNSConstantDictionaries() +{ + m_symbolQueue = new SymbolQueue(); + uint64_t ptrSize = m_data->GetAddressSize(); + + auto idType = Type::NamedType(m_data, m_typeNames.id); + StructureBuilder nsConstantDictionaryBuilder; + nsConstantDictionaryBuilder.AddMember(Type::PointerType(ptrSize, Type::VoidType()), "isa"); + nsConstantDictionaryBuilder.AddMember(Type::IntegerType(ptrSize, false), "options"); + nsConstantDictionaryBuilder.AddMember(Type::IntegerType(ptrSize, false), "count"); + nsConstantDictionaryBuilder.AddMember(Type::PointerType(ptrSize, idType), "keys"); + nsConstantDictionaryBuilder.AddMember(Type::PointerType(ptrSize, idType), "objects"); + auto type = finalizeStructureBuilder(m_data, nsConstantDictionaryBuilder, "__NSConstantDictionary"); + m_typeNames.nsConstantDictionary = type.first; + + auto reader = GetReader(); + if (auto dicts = GetSectionWithName("__objc_dictobj")) + { + auto start = dicts->GetStart(); + auto end = dicts->GetEnd(); + auto typeWidth = Type::NamedType(m_data, m_typeNames.nsConstantDictionary)->GetWidth(); + m_data->BeginBulkModifySymbols(); + for (view_ptr_t i = start; i < end; i += typeWidth) + { + reader->Seek(i + (ptrSize * 2)); + // TODO: Do we need to do anything with `options`? It appears to always be 1. + uint64_t count = reader->ReadPointer(); + auto keysLoc = ReadPointerAccountingForRelocations(reader.get()); + auto objectsLoc = ReadPointerAccountingForRelocations(reader.get()); + DefineObjCSymbol( + DataSymbol, Type::ArrayType(idType, count), fmt::format("nsdict_{:x}_keys", i), keysLoc, true); + DefineObjCSymbol( + DataSymbol, Type::ArrayType(idType, count), fmt::format("nsdict_{:x}_objects", i), objectsLoc, true); + DefineObjCSymbol(DataSymbol, Type::NamedType(m_data, m_typeNames.nsConstantDictionary), + fmt::format("nsdict_{:x}", i), i, true); + } + auto id = m_data->BeginUndoActions(); + m_symbolQueue->Process(); + m_data->EndBulkModifySymbols(); + m_data->ForgetUndoActions(id); + } + delete m_symbolQueue; +} + +void ObjCProcessor::ProcessNSConstantIntegerNumbers() +{ + m_symbolQueue = new SymbolQueue(); + uint64_t ptrSize = m_data->GetAddressSize(); + + StructureBuilder nsConstantIntegerNumberBuilder; + nsConstantIntegerNumberBuilder.AddMember(Type::PointerType(ptrSize, Type::VoidType()), "isa"); + nsConstantIntegerNumberBuilder.AddMember(Type::PointerType(ptrSize, Type::IntegerType(1, true)), "encoding"); + nsConstantIntegerNumberBuilder.AddMember(Type::IntegerType(ptrSize, true), "value"); + auto type = finalizeStructureBuilder(m_data, nsConstantIntegerNumberBuilder, "__NSConstantIntegerNumber"); + m_typeNames.nsConstantIntegerNumber = type.first; + + auto reader = GetReader(); + if (auto numbers = GetSectionWithName("__objc_intobj")) + { + auto start = numbers->GetStart(); + auto end = numbers->GetEnd(); + auto typeWidth = Type::NamedType(m_data, m_typeNames.nsConstantIntegerNumber)->GetWidth(); + m_data->BeginBulkModifySymbols(); + for (view_ptr_t i = start; i < end; i += typeWidth) + { + reader->Seek(i + ptrSize); + uint64_t encodingLoc = ReadPointerAccountingForRelocations(reader.get()); + uint64_t value = reader->Read64(); + reader->Seek(encodingLoc); + uint8_t encoding = reader->Read8(); + + switch (encoding) + { + case 'c': + case 's': + case 'i': + case 'l': + case 'q': + DefineObjCSymbol(DataSymbol, Type::NamedType(m_data, m_typeNames.nsConstantIntegerNumber), + fmt::format("nsint_{:x}_{}", i, (int64_t)value), i, true); + break; + case 'C': + case 'S': + case 'I': + case 'L': + case 'Q': + DefineObjCSymbol(DataSymbol, Type::NamedType(m_data, m_typeNames.nsConstantIntegerNumber), + fmt::format("nsint_{:x}_{}", i, value), i, true); + break; + default: + m_logger->LogWarn("Unknown type encoding '%c' in number literal object at %p", encoding, i); + continue; + } + } + auto id = m_data->BeginUndoActions(); + m_symbolQueue->Process(); + m_data->EndBulkModifySymbols(); + m_data->ForgetUndoActions(id); + } + delete m_symbolQueue; +} + +void ObjCProcessor::ProcessNSConstantFloatingPointNumbers() +{ + uint64_t ptrSize = m_data->GetAddressSize(); + + StructureBuilder nsConstantFloatNumberBuilder; + nsConstantFloatNumberBuilder.AddMember(Type::PointerType(ptrSize, Type::VoidType()), "isa"); + nsConstantFloatNumberBuilder.AddMember(Type::FloatType(4), "value"); + auto type = finalizeStructureBuilder(m_data, nsConstantFloatNumberBuilder, "__NSConstantFloatNumber"); + m_typeNames.nsConstantFloatNumber = type.first; + + StructureBuilder nsConstantDoubleNumberBuilder; + nsConstantDoubleNumberBuilder.AddMember(Type::PointerType(ptrSize, Type::VoidType()), "isa"); + nsConstantDoubleNumberBuilder.AddMember(Type::FloatType(8), "value"); + type = finalizeStructureBuilder(m_data, nsConstantDoubleNumberBuilder, "__NSConstantDoubleNumber"); + m_typeNames.nsConstantDoubleNumber = type.first; + + StructureBuilder nsConstantDateBuilder; + nsConstantDateBuilder.AddMember(Type::PointerType(ptrSize, Type::VoidType()), "isa"); + nsConstantDateBuilder.AddMember(Type::FloatType(8), "ti"); + type = finalizeStructureBuilder(m_data, nsConstantDateBuilder, "__NSConstantDate"); + m_typeNames.nsConstantDate = type.first; + + enum SectionType + { + Float, + Double, + Date, + }; + + constexpr std::pair sections[] = { + {"__objc_floatobj", Float}, + {"__objc_doubleobj", Double}, + {"__objc_dateobj", Date}, + }; + + auto reader = GetReader(); + for (auto& [sectionName, sectionType] : sections) + { + auto numbers = GetSectionWithName(sectionName.data()); + if (!numbers) + continue; + + m_symbolQueue = new SymbolQueue(); + auto start = numbers->GetStart(); + auto end = numbers->GetEnd(); + auto typeWidth = Type::NamedType(m_data, m_typeNames.nsConstantDoubleNumber)->GetWidth(); + m_data->BeginBulkModifySymbols(); + for (view_ptr_t i = start; i < end; i += typeWidth) + { + reader->Seek(i + ptrSize); + + QualifiedName* typeName = nullptr; + std::string name; + + switch (sectionType) + { + case Float: + { + float value = 0; + reader->Read(&value, sizeof(value)); + name = fmt::format("nsfloat_{:x}_{}", i, value); + typeName = &m_typeNames.nsConstantFloatNumber; + break; + } + case Double: + { + double value = 0; + reader->Read(&value, sizeof(value)); + name = fmt::format("nsdouble_{:x}_{}", i, value); + typeName = &m_typeNames.nsConstantDoubleNumber; + break; + } + case Date: + { + double value = 0; + reader->Read(&value, sizeof(value)); + name = fmt::format("nsdate_{:x}_{}", i, value); + typeName = &m_typeNames.nsConstantDate; + break; + } + } + DefineObjCSymbol(DataSymbol, Type::NamedType(m_data, *typeName), name, i, true); + } + auto id = m_data->BeginUndoActions(); + m_symbolQueue->Process(); + m_data->EndBulkModifySymbols(); + m_data->ForgetUndoActions(id); + delete m_symbolQueue; + } +} + +void ObjCProcessor::ProcessNSConstantDatas() +{ + m_symbolQueue = new SymbolQueue(); + uint64_t ptrSize = m_data->GetAddressSize(); + + StructureBuilder nsConstantDataBuilder; + nsConstantDataBuilder.AddMember(Type::PointerType(ptrSize, Type::VoidType()), "isa"); + nsConstantDataBuilder.AddMember(Type::IntegerType(ptrSize, false), "length"); + nsConstantDataBuilder.AddMember(Type::PointerType(ptrSize, Type::IntegerType(1, false)), "bytes"); + auto type = finalizeStructureBuilder(m_data, nsConstantDataBuilder, "__NSConstantData"); + m_typeNames.nsConstantData = type.first; + + auto reader = GetReader(); + if (auto datas = GetSectionWithName("__objc_dataobj")) + { + auto start = datas->GetStart(); + auto end = datas->GetEnd(); + auto typeWidth = Type::NamedType(m_data, m_typeNames.nsConstantData)->GetWidth(); + m_data->BeginBulkModifySymbols(); + for (view_ptr_t i = start; i < end; i += typeWidth) + { + reader->Seek(i + ptrSize); + uint64_t length = reader->ReadPointer(); + auto dataLoc = ReadPointerAccountingForRelocations(reader.get()); + DefineObjCSymbol(DataSymbol, Type::ArrayType(Type::IntegerType(1, false), length), + fmt::format("nsdata_{:x}_data", i), dataLoc, true); + DefineObjCSymbol( + DataSymbol, Type::NamedType(m_data, m_typeNames.nsConstantData), fmt::format("nsdata_{:x}", i), i, true); + } + auto id = m_data->BeginUndoActions(); + m_symbolQueue->Process(); + m_data->EndBulkModifySymbols(); + m_data->ForgetUndoActions(id); + } + delete m_symbolQueue; +} + void ObjCProcessor::AddRelocatedPointer(uint64_t location, uint64_t rewrite) { m_relocationPointerRewrites[location] = rewrite; diff --git a/objectivec/objc.h b/objectivec/objc.h index dad86f080..74444f40d 100644 --- a/objectivec/objc.h +++ b/objectivec/objc.h @@ -275,6 +275,13 @@ namespace BinaryNinja { QualifiedName protocolList; QualifiedName ivar; QualifiedName ivarList; + QualifiedName nsConstantArray; + QualifiedName nsConstantDictionary; + QualifiedName nsConstantDoubleNumber; + QualifiedName nsConstantFloatNumber; + QualifiedName nsConstantIntegerNumber; + QualifiedName nsConstantDate; + QualifiedName nsConstantData; } m_typeNames; bool m_isBackedByDatabase; @@ -314,6 +321,13 @@ namespace BinaryNinja { bool ApplyMethodType(Class& cls, Method& method, bool isInstanceMethod); void ApplyMethodTypes(Class& cls); + void ProcessCFStrings(); + void ProcessNSConstantArrays(); + void ProcessNSConstantDictionaries(); + void ProcessNSConstantIntegerNumbers(); + void ProcessNSConstantFloatingPointNumbers(); + void ProcessNSConstantDatas(); + void PostProcessObjCSections(ObjCReader* reader); protected: @@ -332,9 +346,8 @@ namespace BinaryNinja { virtual ~ObjCProcessor() = default; ObjCProcessor(BinaryView* data, const char* loggerName, bool isBackedByDatabase, bool skipClassBaseProtocols = false); - // TODO: Instead of passing in image name the processor must be given section refs in a structure that outlines all objc sections. void ProcessObjCData(); - void ProcessCFStrings(); + void ProcessObjCLiterals(); void AddRelocatedPointer(uint64_t location, uint64_t rewrite); }; } diff --git a/view/macho/machoview.cpp b/view/macho/machoview.cpp index cda602add..8e1abfd73 100644 --- a/view/macho/machoview.cpp +++ b/view/macho/machoview.cpp @@ -2336,7 +2336,7 @@ bool MachoView::InitializeHeader(MachOHeader& header, bool isMainHeader, uint64_ if (parseCFStrings) { try { - m_objcProcessor->ProcessCFStrings(); + m_objcProcessor->ProcessObjCLiterals(); } catch (std::exception& ex) { diff --git a/view/sharedcache/core/SharedCacheController.cpp b/view/sharedcache/core/SharedCacheController.cpp index 8d036b4ac..abd20bfda 100644 --- a/view/sharedcache/core/SharedCacheController.cpp +++ b/view/sharedcache/core/SharedCacheController.cpp @@ -222,7 +222,7 @@ bool SharedCacheController::ApplyImage(BinaryView& view, const CacheImage& image if (m_processObjC) objcProcessor.ProcessObjCData(); if (m_processCFStrings) - objcProcessor.ProcessCFStrings(); + objcProcessor.ProcessObjCLiterals(); } catch (std::exception& e) {