diff options
| author | WeiN76LQh <WeiN76LQh@github.com> | 2025-01-03 17:55:10 +0000 |
|---|---|---|
| committer | Mason Reed <mason@vector35.com> | 2025-04-02 05:36:54 -0400 |
| commit | ee500220baa1a46f3d7ca1486fa43dc1587f20d0 (patch) | |
| tree | ee3e1549997e60849f040f5745cdac8840f25303 | |
| parent | 90e9ff91f02f2cb902bf0564de1f403e23172f36 (diff) | |
[ObjC] Create a shared ObjC processor for Macho and DSC views
Both the Macho and DSC views need to process Objective-C but have separate processor classes. It would appear that the DSC version was largely a copy and paste of the Macho view one, with some modifications. The majority of code overlaps between the 2 so it doesn't make sense to maintain 2 and copy and paste improvements/fixes between them.
This commit fixes that by creating a base Objective-C processor that contains the shared code. View specific code is implemented in the respective subclasses for the views. Although there is very little view specific code for each.
| -rw-r--r-- | objectivec/objc.cpp | 1549 | ||||
| -rw-r--r-- | objectivec/objc.h | 337 | ||||
| -rw-r--r-- | view/macho/CMakeLists.txt | 4 | ||||
| -rw-r--r-- | view/macho/machoview.cpp | 10 | ||||
| -rw-r--r-- | view/macho/machoview.h | 2 | ||||
| -rw-r--r-- | view/macho/objc.cpp | 1485 | ||||
| -rw-r--r-- | view/macho/objc.h | 228 | ||||
| -rw-r--r-- | view/sharedcache/core/CMakeLists.txt | 7 | ||||
| -rw-r--r-- | view/sharedcache/core/ObjC.cpp | 1518 | ||||
| -rw-r--r-- | view/sharedcache/core/ObjC.h | 242 | ||||
| -rw-r--r-- | view/sharedcache/core/SharedCache.cpp | 4 |
11 files changed, 2032 insertions, 3354 deletions
diff --git a/objectivec/objc.cpp b/objectivec/objc.cpp new file mode 100644 index 00000000..4267ae23 --- /dev/null +++ b/objectivec/objc.cpp @@ -0,0 +1,1549 @@ +#include "objc.h" +#include "inttypes.h" + +using namespace BinaryNinja; + +Ref<Metadata> ObjCProcessor::SerializeMethod(uint64_t loc, const Method& method) +{ + std::map<std::string, Ref<Metadata>> methodMeta; + + methodMeta["loc"] = new Metadata(loc); + methodMeta["name"] = new Metadata(method.name); + methodMeta["types"] = new Metadata(method.types); + methodMeta["imp"] = new Metadata(method.imp); + + return new Metadata(methodMeta); +} + + +Ref<Metadata> ObjCProcessor::SerializeClass(uint64_t loc, const Class& cls) +{ + std::map<std::string, Ref<Metadata>> clsMeta; + + clsMeta["loc"] = new Metadata(loc); + clsMeta["name"] = new Metadata(cls.name); + clsMeta["typeName"] = new Metadata(cls.associatedName.GetString()); + + std::vector<uint64_t> instanceMethods; + std::vector<uint64_t> classMethods; + instanceMethods.reserve(cls.instanceClass.methodList.size()); + classMethods.reserve(cls.metaClass.methodList.size()); + for (const auto& [location, _] : cls.instanceClass.methodList) + instanceMethods.push_back(location); + + clsMeta["instanceMethods"] = new Metadata(instanceMethods); + clsMeta["classMethods"] = new Metadata(classMethods); + + return new Metadata(clsMeta); +} + +Ref<Metadata> ObjCProcessor::SerializeMetadata() +{ + std::map<std::string, Ref<Metadata>> viewMeta; + viewMeta["version"] = new Metadata((uint64_t)1); + + std::vector<Ref<Metadata>> classes; + classes.reserve(m_classes.size()); + std::vector<Ref<Metadata>> categories; + categories.reserve(m_categories.size()); + std::vector<Ref<Metadata>> methods; + methods.reserve(m_localMethods.size()); + + for (const auto& [clsLoc, cls] : m_classes) + classes.push_back(SerializeClass(clsLoc, cls)); + viewMeta["classes"] = new Metadata(classes); + for (const auto& [catLoc, cat] : m_categories) + categories.push_back(SerializeClass(catLoc, cat)); + viewMeta["categories"] = new Metadata(categories); + for (const auto& [methodLoc, method] : m_localMethods) + methods.push_back(SerializeMethod(methodLoc, method)); + viewMeta["methods"] = new Metadata(methods); + + // Required for workflow_objc type guessing, should be removed when that is no longer a thing. + std::vector<Ref<Metadata>> selRefToImps; + selRefToImps.reserve(m_selRefToImplementations.size()); + for (const auto& [selRef, imps] : m_selRefToImplementations) + { + std::vector<Ref<Metadata>> mapBase = {new Metadata(selRef), new Metadata(imps)}; + Ref<Metadata> mapObject = new Metadata(mapBase); + selRefToImps.push_back(mapObject); + } + viewMeta["selRefImplementations"] = new Metadata(selRefToImps); + + std::vector<Ref<Metadata>> selToImps; + selToImps.reserve(m_selToImplementations.size()); + for (const auto& [selRef, imps] : m_selToImplementations) + { + std::vector<Ref<Metadata>> mapBase = {new Metadata(selRef), new Metadata(imps)}; + Ref<Metadata> mapObject = new Metadata(mapBase); + selToImps.push_back(mapObject); + } + viewMeta["selImplementations"] = new Metadata(selToImps); + + std::vector<Ref<Metadata>> selRefToName; + selRefToName.reserve(m_selRefToName.size()); + for (const auto& [selRef, name] : m_selRefToName) + { + std::vector<Ref<Metadata>> mapBase = {new Metadata(selRef), new Metadata(name)}; + Ref<Metadata> mapObject = new Metadata(mapBase); + selRefToName.push_back(mapObject); + } + viewMeta["selRefToName"] = new Metadata(selRefToName); + // --- + + + return new Metadata(viewMeta); +} + +std::vector<QualifiedNameOrType> ObjCProcessor::ParseEncodedType(const std::string& encodedType) +{ + std::vector<QualifiedNameOrType> result; + int pointerDepth = 0; + + bool readingNamedType = false; + std::string namedType; + int readingStructDepth = 0; + std::string structType; + char last; + + for (char c : encodedType) + { + if (readingNamedType && c != '"') + { + namedType.push_back(c); + last = c; + continue; + } + else if (readingStructDepth > 0 && c != '{' && c != '}') + { + structType.push_back(c); + last = c; + continue; + } + + if (std::isdigit(c)) + continue; + + QualifiedNameOrType nameOrType; + std::string qualifiedName; + + switch (c) + { + case '^': + pointerDepth++; + last = c; + continue; + + case '"': + if (!readingNamedType) + { + readingNamedType = true; + if (last == '@') + result.pop_back(); // We added an 'id' in the last cycle, remove it + last = c; + continue; + } + else + { + readingNamedType = false; + nameOrType.name = QualifiedName(namedType); + nameOrType.ptrCount = 1; + break; + } + case '{': + readingStructDepth++; + last = c; + continue; + case '}': + readingStructDepth--; + if (readingStructDepth < 0) + return {}; // seriously malformed type. + + if (readingStructDepth == 0) + { + // TODO: Emit real struct types + nameOrType.type = Type::PointerType(m_data->GetAddressSize(), Type::VoidType()); + break; + } + last = c; + continue; + case 'v': + nameOrType.type = Type::VoidType(); + break; + case 'c': + nameOrType.type = Type::IntegerType(1, true); + break; + case 'A': + case 'C': + nameOrType.type = Type::IntegerType(1, false); + break; + case 's': + nameOrType.type = Type::IntegerType(2, true); + break; + case 'S': + nameOrType.type = Type::IntegerType(1, false); + break; + case 'i': + nameOrType.type = Type::IntegerType(4, true); + break; + case 'I': + nameOrType.type = Type::IntegerType(4, false); + break; + case 'l': + nameOrType.type = Type::IntegerType(8, true); + break; + case 'L': + nameOrType.type = Type::IntegerType(8, true); + break; + case 'f': + nameOrType.type = Type::IntegerType(4, true); + break; + case 'b': + case 'B': + nameOrType.type = Type::BoolType(); + break; + case 'q': + qualifiedName = "NSInteger"; + break; + case 'Q': + qualifiedName = "NSUInteger"; + break; + case 'd': + qualifiedName = "CGFloat"; + break; + case '*': + nameOrType.type = Type::PointerType(m_data->GetAddressSize(), Type::IntegerType(1, true)); + break; + case '@': + qualifiedName = "id"; + // There can be a type after this, like @"NSString", that overrides this + // The handler for " will catch it and drop this "id" entry. + break; + case ':': + qualifiedName = "SEL"; + break; + case '#': + qualifiedName = "objc_class_t"; + break; + case '?': + case 'T': + nameOrType.type = Type::PointerType(8, Type::VoidType()); + break; + default: + // BNLogWarn("Unknown type specifier %c", c); + last = c; + continue; + } + + while (pointerDepth) + { + if (nameOrType.type) + nameOrType.type = Type::PointerType(8, nameOrType.type); + else + nameOrType.ptrCount++; + + pointerDepth--; + } + + if (!qualifiedName.empty()) + nameOrType.name = QualifiedName(qualifiedName); + + if (nameOrType.type == nullptr && nameOrType.name.IsEmpty()) + { + nameOrType.type = Type::VoidType(); + } + + result.push_back(nameOrType); + last = c; + } + + return result; +} + +void ObjCProcessor::DefineObjCSymbol( + BNSymbolType type, QualifiedName typeName, const std::string& name, uint64_t addr, bool deferred) +{ + DefineObjCSymbol(type, m_data->GetTypeByName(typeName), name, addr, deferred); +} + +void ObjCProcessor::DefineObjCSymbol( + BNSymbolType type, Ref<Type> typeRef, const std::string& name, uint64_t addr, bool deferred) +{ + if (name.size() == 0 || addr == 0) + return; + + auto process = [=]() { + NameSpace nameSpace = m_data->GetInternalNameSpace(); + if (type == ExternalSymbol) + { + nameSpace = m_data->GetExternalNameSpace(); + } + + std::string shortName = name; + std::string fullName = name; + + QualifiedName varName; + + return std::pair<Ref<Symbol>, Ref<Type>>( + new Symbol(type, shortName, fullName, name, addr, GlobalBinding, nameSpace), typeRef); + }; + + if (deferred) + { + m_symbolQueue->Append(process, [this, addr = addr](Symbol* symbol, Type* type) { + // Armv7/Thumb: This will rewrite the symbol's address. + // e.g. We pass in 0xc001, it will rewrite it to 0xc000 and create the function w/ the "thumb2" arch. + if (Ref<Symbol> existingSymbol = m_data->GetSymbolByAddress(addr)) + m_data->UndefineAutoSymbol(existingSymbol); + auto funcSym = m_data->DefineAutoSymbolAndVariableOrFunction(m_data->GetDefaultPlatform(), symbol, type); + if (funcSym->GetType() == FunctionSymbol) + { + uint64_t target = symbol->GetAddress(); + Ref<Platform> targetPlatform = + m_data->GetDefaultPlatform()->GetAssociatedPlatformByAddress(target); // rewrites target. + if (Ref<Function> targetFunction = m_data->GetAnalysisFunction(targetPlatform, target)) + { + if (!m_isBackedByDatabase) + targetFunction->SetUserType(type); + } + } + }); + return; + } + + if (Ref<Symbol> existingSymbol = m_data->GetSymbolByAddress(addr)) + m_data->UndefineAutoSymbol(existingSymbol); + auto result = process(); + auto sym = m_data->DefineAutoSymbolAndVariableOrFunction(m_data->GetDefaultPlatform(), result.first, result.second); + if (sym->GetType() == FunctionSymbol) + { + uint64_t target = result.first->GetAddress(); + Ref<Platform> targetPlatform = m_data->GetDefaultPlatform()->GetAssociatedPlatformByAddress(target); // rewrites + // target. + if (Ref<Function> targetFunction = m_data->GetAnalysisFunction(targetPlatform, target)) + { + if (!m_isBackedByDatabase) + targetFunction->SetUserType(result.second); + } + } +} + +void ObjCProcessor::LoadClasses(ObjCReader* reader, Ref<Section> classPtrSection) +{ + if (!classPtrSection) + return; + auto size = classPtrSection->GetEnd() - classPtrSection->GetStart(); + if (size == 0) + return; + auto ptrSize = m_data->GetAddressSize(); + auto ptrCount = size / ptrSize; + + auto classPtrSectionStart = classPtrSection->GetStart(); + for (size_t i = 0; i < ptrCount; i++) + { + Class cls; + + view_ptr_t classPtr; + class_t clsStruct; + class_ro_t classRO; + + bool hasValidMetaClass = false; + bool hasValidMetaClassRO = false; + class_t metaClsStruct; + class_ro_t metaClassRO; + + view_ptr_t classPointerLocation = classPtrSectionStart + (i * m_data->GetAddressSize()); + reader->Seek(classPointerLocation); + + classPtr = ReadPointerAccountingForRelocations(reader); + reader->Seek(classPtr); + try + { + clsStruct.isa = ReadPointerAccountingForRelocations(reader); + clsStruct.super = reader->ReadPointer(); + clsStruct.cache = reader->ReadPointer(); + clsStruct.vtable = reader->ReadPointer(); + clsStruct.data = ReadPointerAccountingForRelocations(reader); + } + catch (...) + { + m_logger->LogError("Failed to read class data at 0x%llx pointed to by @ 0x%llx", reader->GetOffset(), + classPointerLocation); + continue; + } + if (clsStruct.data & 1) + { + m_logger->LogInfo("Skipping class at 0x%llx as it contains swift types", classPtr); + continue; + } + // unset first two bits + view_ptr_t classROPtr = clsStruct.data & ~3; + reader->Seek(classROPtr); + try + { + classRO.flags = reader->Read32(); + classRO.instanceStart = reader->Read32(); + classRO.instanceSize = reader->Read32(); + if (m_data->GetAddressSize() == 8) + classRO.reserved = reader->Read32(); + classRO.ivarLayout = ReadPointerAccountingForRelocations(reader); + classRO.name = ReadPointerAccountingForRelocations(reader); + classRO.baseMethods = ReadPointerAccountingForRelocations(reader); + classRO.baseProtocols = ReadPointerAccountingForRelocations(reader); + classRO.ivars = ReadPointerAccountingForRelocations(reader); + classRO.weakIvarLayout = ReadPointerAccountingForRelocations(reader); + classRO.baseProperties = ReadPointerAccountingForRelocations(reader); + } + catch (...) + { + m_logger->LogError("Failed to read class RO data at 0x%llx. 0x%llx, objc_class_t @ 0x%llx", + reader->GetOffset(), classPointerLocation, classROPtr); + continue; + } + + auto namePtr = classRO.name; + + std::string name; + + reader->Seek(namePtr); + try + { + name = reader->ReadCString(); + } + catch (...) + { + m_logger->LogWarn( + "Failed to read class name at 0x%llx. Class has been given the placeholder name \"0x%llx\" ", namePtr, + classPtr); + char hexString[9]; + hexString[8] = 0; + snprintf(hexString, sizeof(hexString), "%" PRIx64, classPtr); + name = "0x" + std::string(hexString); + } + + cls.name = name; + + DefineObjCSymbol(BNSymbolType::DataSymbol, + Type::PointerType(m_data->GetAddressSize(), m_data->GetTypeByName(m_typeNames.cls)), "clsPtr_" + name, + classPointerLocation, true); + DefineObjCSymbol(BNSymbolType::DataSymbol, m_typeNames.cls, "cls_" + name, classPtr, true); + DefineObjCSymbol(BNSymbolType::DataSymbol, m_typeNames.classRO, "cls_ro_" + name, classROPtr, true); + DefineObjCSymbol(BNSymbolType::DataSymbol, Type::ArrayType(Type::IntegerType(1, true), name.size() + 1), + "clsName_" + name, classRO.name, true); + if (classRO.baseProtocols && !m_skipClassBaseProtocols) + { + DefineObjCSymbol(BNSymbolType::DataSymbol, Type::NamedType(m_data, m_typeNames.protocolList), + "clsProtocols_" + name, classRO.baseProtocols, true); + reader->Seek(classRO.baseProtocols); + uint32_t count = reader->Read64(); + view_ptr_t addr = reader->GetOffset(); + for (uint32_t j = 0; j < count; j++) + { + m_data->DefineDataVariable( + addr, Type::PointerType(ptrSize, Type::NamedType(m_data, m_typeNames.protocol))); + addr += ptrSize; + } + } + + if (clsStruct.isa) + { + reader->Seek(clsStruct.isa); + try + { + metaClsStruct.isa = ReadPointerAccountingForRelocations(reader); + metaClsStruct.super = reader->ReadPointer(); + metaClsStruct.cache = reader->ReadPointer(); + metaClsStruct.vtable = reader->ReadPointer(); + metaClsStruct.data = ReadPointerAccountingForRelocations(reader) & ~1; + DefineObjCSymbol(BNSymbolType::DataSymbol, m_typeNames.cls, "metacls_" + name, clsStruct.isa, true); + hasValidMetaClass = true; + } + catch (...) + { + m_logger->LogWarn("Failed to read metaclass data at 0x%llx pointed to by objc_class_t @ 0x%llx", + reader->GetOffset(), classPtr); + } + } + if (hasValidMetaClass && (metaClsStruct.data & 1)) + { + m_logger->LogInfo("Skipping metaclass at 0x%llx as it contains swift types", classPtr); + hasValidMetaClass = false; + } + if (hasValidMetaClass) + { + reader->Seek(metaClsStruct.data); + try + { + metaClassRO.flags = reader->Read32(); + metaClassRO.instanceStart = reader->Read32(); + metaClassRO.instanceSize = reader->Read32(); + if (m_data->GetAddressSize() == 8) + metaClassRO.reserved = reader->Read32(); + metaClassRO.ivarLayout = ReadPointerAccountingForRelocations(reader); + metaClassRO.name = ReadPointerAccountingForRelocations(reader); + metaClassRO.baseMethods = ReadPointerAccountingForRelocations(reader); + metaClassRO.baseProtocols = ReadPointerAccountingForRelocations(reader); + metaClassRO.ivars = ReadPointerAccountingForRelocations(reader); + metaClassRO.weakIvarLayout = ReadPointerAccountingForRelocations(reader); + metaClassRO.baseProperties = ReadPointerAccountingForRelocations(reader); + DefineObjCSymbol( + BNSymbolType::DataSymbol, m_typeNames.classRO, "metacls_ro_" + name, metaClsStruct.data, true); + hasValidMetaClassRO = true; + } + catch (...) + { + m_logger->LogWarn("Failed to read metaclass RO data at 0x%llx pointed to by meta objc_class_t @ 0x%llx", + reader->GetOffset(), clsStruct.isa); + } + } + + if (classRO.baseMethods) + { + try + { + ReadMethodList(reader, cls.instanceClass, name, classRO.baseMethods); + } + catch (...) + { + m_logger->LogError("Failed to read the method list for class pointed to by 0x%llx", clsStruct.data); + } + } + if (hasValidMetaClassRO && metaClassRO.baseMethods) + { + try + { + ReadMethodList(reader, cls.metaClass, name, metaClassRO.baseMethods); + } + catch (...) + { + m_logger->LogError("Failed to read the method list for metaclass pointed to by 0x%llx", clsStruct.data); + } + } + + if (classRO.ivars) + { + try + { + ReadIvarList(reader, cls.instanceClass, name, classRO.ivars); + } + catch (...) + { + m_logger->LogError("Failed to process ivars for class at 0x%llx", clsStruct.data); + } + } + m_classes[classPtr] = cls; + } +} + +void ObjCProcessor::LoadCategories(ObjCReader* reader, Ref<Section> classPtrSection) +{ + if (!classPtrSection) + return; + auto size = classPtrSection->GetEnd() - classPtrSection->GetStart(); + if (size == 0) + return; + auto ptrSize = m_data->GetAddressSize(); + + auto classPtrSectionStart = classPtrSection->GetStart(); + auto classPtrSectionEnd = classPtrSection->GetEnd(); + + auto catType = Type::NamedType(m_data, m_typeNames.category); + auto ptrType = Type::PointerType(m_data->GetDefaultArchitecture(), catType); + for (size_t i = classPtrSectionStart; i < classPtrSectionEnd; i += ptrSize) + { + Class category; + category_t cat; + + reader->Seek(i); + auto catLocation = ReadPointerAccountingForRelocations(reader); + reader->Seek(catLocation); + + try + { + cat.name = ReadPointerAccountingForRelocations(reader); + cat.cls = ReadPointerAccountingForRelocations(reader); + cat.instanceMethods = ReadPointerAccountingForRelocations(reader); + cat.classMethods = ReadPointerAccountingForRelocations(reader); + cat.protocols = ReadPointerAccountingForRelocations(reader); + cat.instanceProperties = ReadPointerAccountingForRelocations(reader); + } + catch (...) + { + m_logger->LogError("Failed to read category pointed to by 0x%llx", i); + continue; + } + + std::string categoryAdditionsName; + std::string categoryBaseClassName; + + if (const auto& it = m_classes.find(cat.cls); it != m_classes.end()) + { + categoryBaseClassName = it->second.name; + category.associatedName = it->second.associatedName; + } + else if (auto symbol = m_data->GetSymbolByAddress(catLocation + m_data->GetAddressSize())) + { + if (symbol->GetType() == ImportedDataSymbol || symbol->GetType() == ImportAddressSymbol) + { + const auto& symbolName = symbol->GetFullName(); + if (symbolName.size() > 14 && symbolName.rfind("_OBJC_CLASS_$_", 0) == 0) + categoryBaseClassName = symbolName.substr(14, symbolName.size() - 14); + } + } + if (categoryBaseClassName.empty()) + { + m_logger->LogError( + "Failed to determine base classname for category at 0x%llx. Using base address as stand-in classname", + catLocation); + categoryBaseClassName = std::to_string(catLocation); + } + try + { + reader->Seek(cat.name); + categoryAdditionsName = reader->ReadCString(); + } + catch (...) + { + m_logger->LogError( + "Failed to read category name for category at 0x%llx. Using base address as stand-in category name", + catLocation); + categoryAdditionsName = std::to_string(catLocation); + } + category.name = categoryBaseClassName + " (" + categoryAdditionsName + ")"; + DefineObjCSymbol(BNSymbolType::DataSymbol, ptrType, "categoryPtr_" + category.name, i, true); + DefineObjCSymbol(BNSymbolType::DataSymbol, catType, "category_" + category.name, catLocation, true); + + if (cat.instanceMethods) + { + try + { + ReadMethodList(reader, category.instanceClass, category.name, cat.instanceMethods); + } + catch (...) + { + m_logger->LogError( + "Failed to read the instance method list for category pointed to by 0x%llx", catLocation); + } + } + if (cat.classMethods) + { + try + { + ReadMethodList(reader, category.metaClass, category.name, cat.classMethods); + } + catch (...) + { + m_logger->LogError( + "Failed to read the class method list for category pointed to by 0x%llx", catLocation); + } + } + m_categories[catLocation] = category; + } +} + +void ObjCProcessor::LoadProtocols(ObjCReader* reader, Ref<Section> listSection) +{ + if (!listSection) + return; + auto size = listSection->GetEnd() - listSection->GetStart(); + if (size == 0) + return; + auto ptrSize = m_data->GetAddressSize(); + + auto listSectionStart = listSection->GetStart(); + auto listSectionEnd = listSection->GetEnd(); + + auto protocolType = Type::NamedType(m_data, m_typeNames.protocol); + auto ptrType = Type::PointerType(m_data->GetDefaultArchitecture(), protocolType); + for (size_t i = listSectionStart; i < listSectionEnd; i += ptrSize) + { + protocol_t protocol; + reader->Seek(i); + auto protocolLocation = ReadPointerAccountingForRelocations(reader); + reader->Seek(protocolLocation); + + try + { + protocol.isa = ReadPointerAccountingForRelocations(reader); + protocol.mangledName = ReadPointerAccountingForRelocations(reader); + protocol.protocols = ReadPointerAccountingForRelocations(reader); + protocol.instanceMethods = ReadPointerAccountingForRelocations(reader); + protocol.classMethods = ReadPointerAccountingForRelocations(reader); + protocol.optionalInstanceMethods = ReadPointerAccountingForRelocations(reader); + protocol.optionalClassMethods = ReadPointerAccountingForRelocations(reader); + protocol.instanceProperties = ReadPointerAccountingForRelocations(reader); + } + catch (...) + { + m_logger->LogError("Failed to read protocol pointed to by 0x%llx", i); + continue; + } + + std::string protocolName; + try + { + reader->Seek(protocol.mangledName); + protocolName = reader->ReadCString(); + DefineObjCSymbol(BNSymbolType::DataSymbol, + Type::ArrayType(Type::IntegerType(1, true), protocolName.size() + 1), "protocolName_" + protocolName, + protocol.mangledName, true); + } + catch (...) + { + m_logger->LogError( + "Failed to read protocol name for protocol at 0x%llx. Using base address as stand-in protocol name", + protocolLocation); + protocolName = std::to_string(protocolLocation); + } + + Protocol protocolClass; + protocolClass.name = protocolName; + DefineObjCSymbol(BNSymbolType::DataSymbol, ptrType, "protocolPtr_" + protocolName, i, true); + DefineObjCSymbol(BNSymbolType::DataSymbol, protocolType, "protocol_" + protocolName, protocolLocation, true); + if (protocol.protocols) + { + DefineObjCSymbol(BNSymbolType::DataSymbol, Type::NamedType(m_data, m_typeNames.protocolList), + "protoProtocols_" + protocolName, protocol.protocols, true); + reader->Seek(protocol.protocols); + uint32_t count = reader->Read64(); + view_ptr_t addr = reader->GetOffset(); + for (uint32_t j = 0; j < count; j++) + { + m_data->DefineDataVariable( + addr, Type::PointerType(ptrSize, Type::NamedType(m_data, m_typeNames.protocol))); + addr += ptrSize; + } + } + + if (protocol.instanceMethods) + { + try + { + ReadMethodList(reader, protocolClass.instanceMethods, protocolName, protocol.instanceMethods); + } + catch (...) + { + m_logger->LogError( + "Failed to read the instance method list for protocol pointed to by 0x%llx", protocolLocation); + } + } + if (protocol.classMethods) + { + try + { + ReadMethodList(reader, protocolClass.classMethods, protocolName, protocol.classMethods); + } + catch (...) + { + m_logger->LogError( + "Failed to read the class method list for protocol pointed to by 0x%llx", protocolLocation); + } + } + if (protocol.optionalInstanceMethods) + { + try + { + ReadMethodList( + reader, protocolClass.optionalInstanceMethods, protocolName, protocol.optionalInstanceMethods); + } + catch (...) + { + m_logger->LogError("Failed to read the optional instance method list for protocol pointed to by 0x%llx", + protocolLocation); + } + } + if (protocol.optionalClassMethods) + { + try + { + ReadMethodList(reader, protocolClass.optionalClassMethods, protocolName, protocol.optionalClassMethods); + } + catch (...) + { + m_logger->LogError("Failed to read the optional class method list for protocol pointed to by 0x%llx", + protocolLocation); + } + } + m_protocols[protocolLocation] = protocolClass; + } +} + +void ObjCProcessor::GetRelativeMethod(ObjCReader* reader, method_t& meth) +{ + meth.name = reader->GetOffset() + reader->ReadS32(); + meth.types = reader->GetOffset() + reader->ReadS32(); + meth.imp = reader->GetOffset() + reader->ReadS32(); +} + +void ObjCProcessor::ReadListOfMethodLists(ObjCReader* 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(); + + // TODO(WeiN76LQh): probably can be removed at this point + 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 ObjCProcessor::ReadMethodList(ObjCReader* 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(); + + // TODO(WeiN76LQh): probably can be removed at this point + 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_" + std::string(name), start, true); + + for (unsigned i = 0; i < head.count; i++) + { + try + { + Method method; + auto cursor = start + sizeof(method_list_t) + (i * methodSize); + reader->Seek(cursor); + method_t meth; + // workflow_objc support + uint64_t selRefAddr = 0; + uint64_t selAddr = 0; + // -- + if (relativeOffsets) + { + GetRelativeMethod(reader, meth); + } + else + { + meth.name = ReadPointerAccountingForRelocations(reader); + meth.types = ReadPointerAccountingForRelocations(reader); + meth.imp = ReadPointerAccountingForRelocations(reader); + } + if (!relativeOffsets || directSelectors) + { + reader->Seek(meth.name); + selAddr = meth.name; + method.name = reader->ReadCString(); + reader->Seek(meth.types); + method.types = reader->ReadCString(); + DefineObjCSymbol(DataSymbol, Type::ArrayType(Type::IntegerType(1, true), method.name.size() + 1), + "sel_" + method.name, meth.name, true); + DefineObjCSymbol(DataSymbol, Type::ArrayType(Type::IntegerType(1, true), method.types.size() + 1), + "selTypes_" + method.name, meth.types, true); + } + else + { + std::string sel; + view_ptr_t selRef; + reader->Seek(meth.name); + selRefAddr = meth.name; + selRef = ReadPointerAccountingForRelocations(reader); + reader->Seek(meth.types); + method.types = reader->ReadCString(); + selAddr = selRef; + if (const auto& it = m_selectorCache.find(selRef); it != m_selectorCache.end()) + method.name = it->second; + else + { + reader->Seek(selRef); + method.name = reader->ReadCString(); + m_selectorCache[selRef] = method.name; + } + auto selType = Type::ArrayType(Type::IntegerType(1, true), method.name.size() + 1); + DefineObjCSymbol(DataSymbol, selType, "sel_" + method.name, selRef, true); + DefineObjCSymbol(DataSymbol, Type::ArrayType(Type::IntegerType(1, true), method.types.size() + 1), + "selTypes_" + method.name, meth.types, true); + DefineObjCSymbol(DataSymbol, Type::PointerType(m_data->GetAddressSize(), selType), + "selRef_" + method.name, meth.name, true); + } + // workflow objc support + if (selAddr) + m_selToImplementations[selAddr].push_back(meth.imp); + if (selRefAddr) + m_selRefToImplementations[selRefAddr].push_back(meth.imp); + // -- + + DefineObjCSymbol(DataSymbol, relativeOffsets ? m_typeNames.methodEntry : m_typeNames.method, + "method_" + method.name, cursor, true); + method.imp = meth.imp; + cls.methodList[cursor] = method; + m_localMethods[cursor] = method; + } + catch (...) + { + m_logger->LogError( + "Failed to process a method at offset 0x%llx", start + sizeof(method_list_t) + (i * methodSize)); + } + } +} + +void ObjCProcessor::ReadIvarList(ObjCReader* 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_" + std::string(name), start, true); + for (unsigned i = 0; i < head.count; i++) + { + try + { + Ivar ivar; + ivar_t ivarStruct; + uint64_t cursor = start + (sizeof(ivar_list_t)) + (i * ((addressSize * 3) + 8)); + reader->Seek(cursor); + ivarStruct.offset = ReadPointerAccountingForRelocations(reader); + ivarStruct.name = ReadPointerAccountingForRelocations(reader); + ivarStruct.type = ReadPointerAccountingForRelocations(reader); + ivarStruct.alignmentRaw = reader->Read32(); + ivarStruct.size = reader->Read32(); + + reader->Seek(ivarStruct.offset); + ivar.offset = reader->Read32(); + reader->Seek(ivarStruct.name); + ivar.name = reader->ReadCString(); + reader->Seek(ivarStruct.type); + ivar.type = reader->ReadCString(); + + DefineObjCSymbol(DataSymbol, m_typeNames.ivar, "ivar_" + ivar.name, cursor, true); + DefineObjCSymbol(DataSymbol, Type::ArrayType(Type::IntegerType(1, true), ivar.name.size() + 1), + "ivarName_" + ivar.name, ivarStruct.name, true); + DefineObjCSymbol(DataSymbol, Type::ArrayType(Type::IntegerType(1, true), ivar.type.size() + 1), + "ivarType_" + ivar.name, ivarStruct.type, true); + + cls.ivarList[cursor] = ivar; + } + catch (...) + { + m_logger->LogError("Failed to process an ivar at offset 0x%llx", + start + (sizeof(ivar_list_t)) + (i * ((addressSize * 3) + 8))); + } + } +} + + +std::pair<QualifiedName, Ref<Type>> finalizeStructureBuilder( + Ref<BinaryView> m_data, StructureBuilder sb, std::string name) +{ + auto classTypeStruct = sb.Finalize(); + + QualifiedName classTypeName(name); + auto classTypeId = Type::GenerateAutoTypeId("objc", classTypeName); + auto classType = Type::StructureType(classTypeStruct); + auto classQualName = m_data->DefineType(classTypeId, classTypeName, classType); + + return {classQualName, classType}; +} + +std::pair<QualifiedName, Ref<Type>> finalizeEnumerationBuilder( + Ref<BinaryView> m_data, EnumerationBuilder eb, uint64_t size, QualifiedName name) +{ + auto enumTypeStruct = eb.Finalize(); + + auto enumTypeId = Type::GenerateAutoTypeId("objc", name); + auto enumType = Type::EnumerationType(enumTypeStruct, size); + auto enumQualName = m_data->DefineType(enumTypeId, name, enumType); + + return {enumQualName, enumType}; +} + +inline QualifiedName defineTypedef(Ref<BinaryView> m_data, const QualifiedName name, Ref<Type> type) +{ + auto typeID = Type::GenerateAutoTypeId("objc", name); + m_data->DefineType(typeID, name, type); + return m_data->GetTypeNameById(typeID); +} + +void ObjCProcessor::GenerateClassTypes() +{ + for (auto& [_, cls] : m_classes) + { + QualifiedName typeName; + StructureBuilder classTypeBuilder; + bool failedToDecodeType = false; + for (const auto& [ivarLoc, ivar] : cls.instanceClass.ivarList) + { + auto encodedTypeList = ParseEncodedType(ivar.type); + if (encodedTypeList.empty()) + { + failedToDecodeType = true; + break; + } + auto encodedType = encodedTypeList.at(0); + + Ref<Type> type; + + if (encodedType.type) + type = encodedType.type; + else + { + type = Type::NamedType(encodedType.name, Type::PointerType(m_data->GetAddressSize(), Type::VoidType())); + for (size_t i = encodedType.ptrCount; i > 0; i--) + type = Type::PointerType(m_data->GetAddressSize(), type); + } + + if (!type) + type = Type::PointerType(m_data->GetAddressSize(), Type::VoidType()); + + classTypeBuilder.AddMemberAtOffset(type, ivar.name, ivar.offset); + } + if (failedToDecodeType) + continue; + auto classTypeStruct = classTypeBuilder.Finalize(); + QualifiedName classTypeName = cls.name; + std::string classTypeId = Type::GenerateAutoTypeId("objc", classTypeName); + Ref<Type> classType = Type::StructureType(classTypeStruct); + QualifiedName classQualName = m_data->DefineType(classTypeId, classTypeName, classType); + cls.associatedName = classTypeName; + } +} + +bool ObjCProcessor::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; + std::vector<std::string> selectorTokens; + while (std::getline(r, token, ':')) + selectorTokens.push_back(token); + + std::vector<QualifiedNameOrType> typeTokens = ParseEncodedType(method.types); + if (typeTokens.empty()) + return false; + + auto typeForQualifiedNameOrType = [this](QualifiedNameOrType nameOrType) { + Ref<Type> type; + + if (nameOrType.type) + { + type = nameOrType.type; + if (!type) + type = Type::PointerType(m_data->GetAddressSize(), Type::VoidType()); + } + else + { + type = Type::NamedType(nameOrType.name, Type::PointerType(m_data->GetAddressSize(), Type::VoidType())); + for (size_t i = nameOrType.ptrCount; i > 0; i--) + type = Type::PointerType(m_data->GetAddressSize(), type); + } + + return type; + }; + + BinaryNinja::QualifiedNameAndType nameAndType; + std::set<BinaryNinja::QualifiedName> typesAllowRedefinition; + + auto retType = typeForQualifiedNameOrType(typeTokens[0]); + + std::vector<BinaryNinja::FunctionParameter> params; + auto cc = m_data->GetDefaultPlatform()->GetDefaultCallingConvention(); + + params.push_back({"self", + cls.associatedName.IsEmpty() ? + Type::NamedType(m_data, {"id"}) : + Type::PointerType(m_data->GetAddressSize(), Type::NamedType(m_data, cls.associatedName)), + true, BinaryNinja::Variable()}); + + params.push_back({"sel", Type::NamedType(m_data, {"SEL"}), true, BinaryNinja::Variable()}); + + for (size_t i = 3; i < typeTokens.size(); i++) + { + std::string suffix; + + params.push_back({selectorTokens.size() > i - 3 ? selectorTokens[i - 3] : "arg", + typeForQualifiedNameOrType(typeTokens[i]), true, BinaryNinja::Variable()}); + } + + auto funcType = BinaryNinja::Type::FunctionType(retType, cc, params); + + // Search for the method's implementation function; apply the type if found. + std::string prefix = isInstanceMethod ? "-" : "+"; + auto name = prefix + "[" + cls.name + " " + method.name + "]"; + + DefineObjCSymbol(FunctionSymbol, funcType, name, method.imp, true); + + return true; +} + +void ObjCProcessor::ApplyMethodTypes(Class& cls) +{ + for (auto& [_, method] : cls.instanceClass.methodList) + { + ApplyMethodType(cls, method, true); + } + for (auto& [_, method] : cls.metaClass.methodList) + { + ApplyMethodType(cls, method, false); + } +} + +Ref<Section> ObjCProcessor::GetSectionForImage(std::optional<std::string> imageName, const char* sectionName) +{ + if (imageName) + { + return m_data->GetSectionByName(*imageName + "::" + sectionName); + } + else + { + return m_data->GetSectionByName(sectionName); + } +} + +void ObjCProcessor::PostProcessObjCSections(ObjCReader* reader, std::optional<std::string> imageName) +{ + auto ptrSize = m_data->GetAddressSize(); + if (auto imageInfo = GetSectionForImage(imageName, "__objc_imageinfo")) + { + auto start = imageInfo->GetStart(); + auto type = Type::NamedType(m_data, m_typeNames.imageInfo); + m_data->DefineDataVariable(start, type); + } + if (auto selrefs = GetSectionForImage(imageName, "__objc_selrefs")) + { + auto start = selrefs->GetStart(); + auto end = selrefs->GetEnd(); + auto type = Type::PointerType(ptrSize, Type::IntegerType(1, false)); + for (view_ptr_t i = start; i < end; i += ptrSize) + { + reader->Seek(i); + auto selLoc = ReadPointerAccountingForRelocations(reader); + std::string sel; + if (const auto& it = m_selectorCache.find(selLoc); it != m_selectorCache.end()) + sel = it->second; + else + { + reader->Seek(selLoc); + sel = reader->ReadCString(); + m_selectorCache[selLoc] = sel; + DefineObjCSymbol(DataSymbol, Type::ArrayType(Type::IntegerType(1, true), sel.size() + 1), "sel_" + sel, + selLoc, true); + } + DefineObjCSymbol(DataSymbol, type, "selRef_" + sel, i, true); + } + } + if (auto superRefs = GetSectionForImage(imageName, "__objc_classrefs")) + { + auto start = superRefs->GetStart(); + auto end = superRefs->GetEnd(); + auto type = Type::PointerType(ptrSize, Type::NamedType(m_data, m_typeNames.cls)); + for (view_ptr_t i = start; i < end; i += ptrSize) + { + reader->Seek(i); + auto clsLoc = ReadPointerAccountingForRelocations(reader); + if (const auto& it = m_classes.find(clsLoc); it != m_classes.end()) + { + auto& cls = it->second; + std::string name = cls.name; + if (!name.empty()) + DefineObjCSymbol(DataSymbol, type, "clsRef_" + name, i, true); + } + } + } + if (auto superRefs = GetSectionForImage(imageName, "__objc_superrefs")) + { + auto start = superRefs->GetStart(); + auto end = superRefs->GetEnd(); + auto type = Type::PointerType(ptrSize, Type::NamedType(m_data, m_typeNames.cls)); + for (view_ptr_t i = start; i < end; i += ptrSize) + { + reader->Seek(i); + auto clsLoc = ReadPointerAccountingForRelocations(reader); + if (const auto& it = m_classes.find(clsLoc); it != m_classes.end()) + { + auto& cls = it->second; + std::string name = cls.name; + if (!name.empty()) + DefineObjCSymbol(DataSymbol, type, "superRef_" + name, i, true); + } + } + } + if (auto protoRefs = GetSectionForImage(imageName, "__objc_protorefs")) + { + auto start = protoRefs->GetStart(); + auto end = protoRefs->GetEnd(); + auto type = Type::PointerType(ptrSize, Type::NamedType(m_data, m_typeNames.protocol)); + for (view_ptr_t i = start; i < end; i += ptrSize) + { + reader->Seek(i); + auto protoLoc = ReadPointerAccountingForRelocations(reader); + if (const auto& it = m_protocols.find(protoLoc); it != m_protocols.end()) + { + auto& proto = it->second; + std::string name = proto.name; + if (!name.empty()) + DefineObjCSymbol(DataSymbol, type, "protoRef_" + name, i, true); + } + } + } + if (auto ivars = GetSectionForImage(imageName, "__objc_ivar")) + { + auto start = ivars->GetStart(); + auto end = ivars->GetEnd(); + auto ivarSectionEntryTypeBuilder = new TypeBuilder(Type::IntegerType(8, false)); + ivarSectionEntryTypeBuilder->SetConst(true); + auto type = ivarSectionEntryTypeBuilder->Finalize(); + for (view_ptr_t i = start; i < end; i += ptrSize) + { + m_data->DefineDataVariable(i, type); + } + } +} + +uint64_t ObjCProcessor::ReadPointerAccountingForRelocations(ObjCReader* reader) +{ + if (auto it = m_relocationPointerRewrites.find(reader->GetOffset()); it != m_relocationPointerRewrites.end()) + { + reader->SeekRelative(m_data->GetAddressSize()); + return it->second; + } + return reader->ReadPointer(); +} + + +ObjCProcessor::ObjCProcessor(BinaryView* data, const char* loggerName, bool isBackedByDatabase, bool skipClassBaseProtocols) : + m_isBackedByDatabase(isBackedByDatabase), m_skipClassBaseProtocols(skipClassBaseProtocols), m_data(data) +{ + m_logger = m_data->CreateLogger(loggerName); +} + +uint64_t ObjCProcessor::GetObjCRelativeMethodBaseAddress(ObjCReader* reader) +{ + return 0; +} + +void ObjCProcessor::ProcessObjCData(std::optional<std::string> imageName) +{ + m_symbolQueue = new SymbolQueue(); + auto addrSize = m_data->GetAddressSize(); + + m_typeNames.relativePtr = defineTypedef(m_data, {"rptr_t"}, Type::IntegerType(4, true)); + auto rptr_t = Type::NamedType(m_data, m_typeNames.relativePtr); + + m_typeNames.id = defineTypedef(m_data, {"id"}, Type::PointerType(addrSize, Type::VoidType())); + m_typeNames.sel = defineTypedef(m_data, {"SEL"}, Type::PointerType(addrSize, Type::IntegerType(1, false))); + + m_typeNames.BOOL = defineTypedef(m_data, {"BOOL"}, Type::IntegerType(1, false)); + m_typeNames.nsInteger = defineTypedef(m_data, {"NSInteger"}, Type::IntegerType(addrSize, true)); + m_typeNames.nsuInteger = defineTypedef(m_data, {"NSUInteger"}, Type::IntegerType(addrSize, false)); + m_typeNames.cgFloat = defineTypedef(m_data, {"CGFloat"}, Type::FloatType(addrSize)); + + Ref<Type> relativeSelectorPtr; + auto reader = GetReader(); + if (auto objCRelativeMethodsBaseAddr = GetObjCRelativeMethodBaseAddress(reader.get())) { + m_logger->LogDebug("RelativeMethodSelector Base: 0x%llx", 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); + imageInfoFlagBuilder.AddMemberWithValue("SupportsGC", 1 << 1); + imageInfoFlagBuilder.AddMemberWithValue("RequiresGC", 1 << 2); + imageInfoFlagBuilder.AddMemberWithValue("OptimizedByDyld", 1 << 3); + imageInfoFlagBuilder.AddMemberWithValue("CorrectedSynthesize", 1 << 4); + imageInfoFlagBuilder.AddMemberWithValue("IsSimulated", 1 << 5); + imageInfoFlagBuilder.AddMemberWithValue("HasCategoryClassProperties", 1 << 6); + imageInfoFlagBuilder.AddMemberWithValue("OptimizedByDyldClosure", 1 << 7); + imageInfoFlagBuilder.AddMemberWithValue("SwiftUnstableVersionMask", 0xff << 8); + imageInfoFlagBuilder.AddMemberWithValue("SwiftStableVersionMask", 0xFFFF << 16); + auto imageInfoFlagType = finalizeEnumerationBuilder(m_data, imageInfoFlagBuilder, 4, {"objc_image_info_flags"}); + m_typeNames.imageInfoFlags = imageInfoFlagType.first; + + EnumerationBuilder swiftVersionBuilder; + swiftVersionBuilder.AddMemberWithValue("SwiftVersion1", 1); + swiftVersionBuilder.AddMemberWithValue("SwiftVersion1_2", 2); + swiftVersionBuilder.AddMemberWithValue("SwiftVersion2", 3); + swiftVersionBuilder.AddMemberWithValue("SwiftVersion3", 4); + swiftVersionBuilder.AddMemberWithValue("SwiftVersion4", 5); + swiftVersionBuilder.AddMemberWithValue("SwiftVersion4_1", 6); // [sic] + swiftVersionBuilder.AddMemberWithValue("SwiftVersion4_2", 6); + swiftVersionBuilder.AddMemberWithValue("SwiftVersion5", 7); + auto swiftVersionType = + finalizeEnumerationBuilder(m_data, swiftVersionBuilder, 4, {"objc_image_info_swift_version"}); + m_typeNames.imageInfoSwiftVersion = swiftVersionType.first; + + StructureBuilder imageInfoBuilder; + imageInfoBuilder.AddMember(Type::IntegerType(4, false), "version"); + imageInfoBuilder.AddMember(Type::NamedType(m_data, m_typeNames.imageInfoFlags), "flags"); + auto imageInfoType = finalizeStructureBuilder(m_data, imageInfoBuilder, "objc_image_info_t"); + m_typeNames.imageInfo = imageInfoType.first; + + StructureBuilder methodEntry; + 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"); + m_typeNames.methodEntry = type.first; + + StructureBuilder method; + method.AddMember(Type::PointerType(addrSize, Type::IntegerType(1, true)), "name"); + method.AddMember(Type::PointerType(addrSize, Type::IntegerType(1, true)), "types"); + method.AddMember(Type::PointerType(addrSize, Type::VoidType()), "imp"); + type = finalizeStructureBuilder(m_data, method, "objc_method_t"); + m_typeNames.method = type.first; + + StructureBuilder methList; + methList.AddMember(Type::IntegerType(4, false), "obsolete"); + methList.AddMember(Type::IntegerType(4, false), "count"); + type = finalizeStructureBuilder(m_data, methList, "objc_method_list_t"); + m_typeNames.methodList = type.first; + + StructureBuilder ivarBuilder; + ivarBuilder.AddMember(Type::PointerType(addrSize, Type::IntegerType(4, false)), "offset"); + ivarBuilder.AddMember(Type::PointerType(addrSize, Type::IntegerType(1, true)), "name"); + ivarBuilder.AddMember(Type::PointerType(addrSize, Type::IntegerType(1, true)), "type"); + ivarBuilder.AddMember(Type::IntegerType(4, false), "alignment"); + ivarBuilder.AddMember(Type::IntegerType(4, false), "size"); + type = finalizeStructureBuilder(m_data, ivarBuilder, "objc_ivar_t"); + m_typeNames.ivar = type.first; + + StructureBuilder ivarList; + ivarList.AddMember(Type::IntegerType(4, false), "entsize"); + ivarList.AddMember(Type::IntegerType(4, false), "count"); + type = finalizeStructureBuilder(m_data, ivarList, "objc_ivar_list_t"); + m_typeNames.ivarList = type.first; + + StructureBuilder protocolListBuilder; + protocolListBuilder.AddMember(Type::IntegerType(addrSize, false), "count"); + m_typeNames.protocolList = finalizeStructureBuilder(m_data, protocolListBuilder, "objc_protocol_list_t").first; + + StructureBuilder classROBuilder; + classROBuilder.AddMember(Type::IntegerType(4, false), "flags"); + classROBuilder.AddMember(Type::IntegerType(4, false), "start"); + classROBuilder.AddMember(Type::IntegerType(4, false), "size"); + if (addrSize == 8) + classROBuilder.AddMember(Type::IntegerType(4, false), "reserved"); + classROBuilder.AddMember(Type::PointerType(addrSize, Type::VoidType()), "ivar_layout"); + classROBuilder.AddMember(Type::PointerType(addrSize, Type::IntegerType(1, true)), "name"); + classROBuilder.AddMember(Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.methodList)), "methods"); + classROBuilder.AddMember( + Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.protocolList)), "protocols"); + classROBuilder.AddMember(Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.ivarList)), "ivars"); + classROBuilder.AddMember(Type::PointerType(addrSize, Type::VoidType()), "weak_ivar_layout"); + classROBuilder.AddMember(Type::PointerType(addrSize, Type::VoidType()), "properties"); + type = finalizeStructureBuilder(m_data, classROBuilder, "objc_class_ro_t"); + m_typeNames.classRO = type.first; + + QualifiedName classTypeName("objc_class_t"); + auto classTypeId = Type::GenerateAutoTypeId("objc", classTypeName); + auto isaType = Type::PointerType(m_data->GetDefaultArchitecture(), + TypeBuilder::NamedType( + new NamedTypeReferenceBuilder(StructNamedTypeClass, "", classTypeName), m_data->GetAddressSize(), 4) + .Finalize()); + + StructureBuilder classBuilder; + classBuilder.AddMember(isaType, "isa"); + classBuilder.AddMember(Type::PointerType(addrSize, Type::VoidType()), "super"); + classBuilder.AddMember(Type::PointerType(addrSize, Type::VoidType()), "cache"); + classBuilder.AddMember(Type::PointerType(addrSize, Type::VoidType()), "vtable"); + classBuilder.AddMember(Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.classRO)), "data"); + + auto classTypeStruct = classBuilder.Finalize(); + auto classType = Type::StructureType(classTypeStruct); + auto classQualName = m_data->DefineType(classTypeId, classTypeName, classType); + + m_typeNames.cls = classQualName; + + StructureBuilder categoryBuilder; + categoryBuilder.AddMember(Type::PointerType(addrSize, Type::IntegerType(1, true)), "category_name"); + categoryBuilder.AddMember(Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.cls)), "class"); + categoryBuilder.AddMember( + Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.methodList)), "inst_methods"); + categoryBuilder.AddMember( + Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.methodList)), "class_methods"); + categoryBuilder.AddMember(Type::PointerType(addrSize, Type::VoidType()), "protocols"); + categoryBuilder.AddMember(Type::PointerType(addrSize, Type::VoidType()), "properties"); + m_typeNames.category = finalizeStructureBuilder(m_data, categoryBuilder, "objc_category_t").first; + + StructureBuilder protocolBuilder; + protocolBuilder.AddMember(Type::PointerType(addrSize, Type::VoidType()), "isa"); + protocolBuilder.AddMember(Type::PointerType(addrSize, Type::IntegerType(1, true)), "mangledName"); + protocolBuilder.AddMember( + Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.protocolList)), "protocols"); + protocolBuilder.AddMember( + Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.methodList)), "instanceMethods"); + protocolBuilder.AddMember( + Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.methodList)), "classMethods"); + protocolBuilder.AddMember( + Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.methodList)), "optionalInstanceMethods"); + protocolBuilder.AddMember( + Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.methodList)), "optionalClassMethods"); + protocolBuilder.AddMember(Type::PointerType(addrSize, Type::VoidType()), "instanceProperties"); + protocolBuilder.AddMember(Type::IntegerType(4, false), "size"); + protocolBuilder.AddMember(Type::IntegerType(4, false), "flags"); + m_typeNames.protocol = finalizeStructureBuilder(m_data, protocolBuilder, "objc_protocol_t").first; + + m_data->BeginBulkModifySymbols(); + if (auto classList = GetSectionForImage(imageName, "__objc_classlist")) + LoadClasses(reader.get(), classList); + if (auto nonLazyClassList = GetSectionForImage(imageName, "__objc_nlclslist")) + LoadClasses(reader.get(), nonLazyClassList); // See: https://stackoverflow.com/a/15318325 + + GenerateClassTypes(); + for (auto& [_, cls] : m_classes) + ApplyMethodTypes(cls); + + if (auto catList = GetSectionForImage(imageName, "__objc_catlist")) // Do this after loading class type data. + LoadCategories(reader.get(), catList); + if (auto nonLazyCatList = GetSectionForImage(imageName, "__objc_nlcatlist")) // Do this after loading class type data. + LoadCategories(reader.get(), nonLazyCatList); + for (auto& [_, cat] : m_categories) + ApplyMethodTypes(cat); + + if (auto protoList = GetSectionForImage(imageName, "__objc_protolist")) + LoadProtocols(reader.get(), protoList); + + PostProcessObjCSections(reader.get(), imageName); + + auto id = m_data->BeginUndoActions(); + m_symbolQueue->Process(); + m_data->EndBulkModifySymbols(); + delete m_symbolQueue; + m_data->ForgetUndoActions(id); + + auto meta = SerializeMetadata(); + m_data->StoreMetadata("Objective-C", meta, true); + + m_relocationPointerRewrites.clear(); +} + + +void ObjCProcessor::ProcessCFStrings(std::optional<std::string> imageName) +{ + m_symbolQueue = new SymbolQueue(); + uint64_t ptrSize = m_data->GetAddressSize(); + // https://github.com/apple/llvm-project/blob/next/clang/lib/CodeGen/CodeGenModule.cpp#L6129 + // See also ASTContext.cpp ctrl+f __NSConstantString_tag + + // The place these flags are used is unclear, along with any clear flag definitions, but they are useful for + // introspection + EnumerationBuilder __cfStringFlagBuilder; + __cfStringFlagBuilder.AddMemberWithValue("SwiftABI", 0b1); + __cfStringFlagBuilder.AddMemberWithValue("Swift4_1", 0b100); + // LLVM also sets 0x7c0 (0b11111000000) on both UTF8 and UTF16 strings however it is unclear what this denotes. + __cfStringFlagBuilder.AddMemberWithValue("UTF8", 0b1000); + __cfStringFlagBuilder.AddMemberWithValue("UTF16", 0b10000); + auto type = finalizeEnumerationBuilder(m_data, __cfStringFlagBuilder, ptrSize, {"CFStringFlag"}); + m_typeNames.cfStringFlag = type.first; + + StructureBuilder __cfStringStructBuilder; + __cfStringStructBuilder.AddMember(Type::PointerType(ptrSize, Type::VoidType()), "isa"); + __cfStringStructBuilder.AddMember(Type::NamedType(m_data, m_typeNames.cfStringFlag), "flags"); + __cfStringStructBuilder.AddMember(Type::PointerType(ptrSize, Type::IntegerType(1, true)), "data"); + __cfStringStructBuilder.AddMember(Type::IntegerType(ptrSize, false), "length"); + type = finalizeStructureBuilder(m_data, __cfStringStructBuilder, "__NSConstantString"); + m_typeNames.cfString = type.first; + + StructureBuilder __cfStringUTF16StructBuilder; + __cfStringUTF16StructBuilder.AddMember(Type::PointerType(ptrSize, Type::VoidType()), "isa"); + __cfStringUTF16StructBuilder.AddMember(Type::NamedType(m_data, m_typeNames.cfStringFlag), "flags"); + __cfStringUTF16StructBuilder.AddMember(Type::PointerType(ptrSize, Type::IntegerType(2, true)), "data"); + __cfStringUTF16StructBuilder.AddMember(Type::IntegerType(ptrSize, false), "length"); + type = finalizeStructureBuilder(m_data, __cfStringUTF16StructBuilder, "__NSConstantString_UTF16"); + m_typeNames.cfStringUTF16 = type.first; + + auto reader = GetReader(); + if (auto cfstrings = GetSectionForImage(imageName, "__cfstring")) + { + auto start = cfstrings->GetStart(); + auto end = cfstrings->GetEnd(); + auto typeWidth = Type::NamedType(m_data, m_typeNames.cfString)->GetWidth(); + m_data->BeginBulkModifySymbols(); + for (view_ptr_t i = start; i < end; i += typeWidth) + { + reader->Seek(i + ptrSize); + uint64_t flags = reader->ReadPointer(); + auto strLoc = ReadPointerAccountingForRelocations(reader.get()); + auto size = reader->ReadPointer(); + std::string str; + if (flags & 0b10000) // UTF16 + { + auto data = m_data->ReadBuffer(strLoc, size * 2); + + str = ""; + for (uint64_t bufferOff = 0; bufferOff < size * 2; bufferOff += 2) + { + uint8_t* rawData = static_cast<uint8_t*>(data.GetData()); + uint8_t* offsetAddress = rawData + bufferOff; + uint16_t c = *reinterpret_cast<uint16_t*>(offsetAddress); + if (c == 0x20) + str.push_back('_'); + else if (c < 0x80) + str.push_back(c); + else + str.push_back('?'); + } + DefineObjCSymbol( + DataSymbol, Type::ArrayType(Type::WideCharType(2), size + 1), "ustr_" + str, strLoc, true); + DefineObjCSymbol( + DataSymbol, Type::NamedType(m_data, m_typeNames.cfStringUTF16), "cfstr_" + str, i, true); + } + else // UTF8 / ASCII + { + reader->Seek(strLoc); + str = reader->ReadCString(); + for (auto& c : str) + { + if (c == ' ') + c = '_'; + } + DefineObjCSymbol(DataSymbol, Type::ArrayType(Type::IntegerType(1, true), str.size() + 1), "cstr_" + str, + strLoc, true); + DefineObjCSymbol(DataSymbol, Type::NamedType(m_data, m_typeNames.cfString), "cfstr_" + str, 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 new file mode 100644 index 00000000..2939a3e5 --- /dev/null +++ b/objectivec/objc.h @@ -0,0 +1,337 @@ +#pragma once + +#include <binaryninjaapi.h> + +namespace BinaryNinja { + // This set of structs is based on the objc4 source, + // however pointers have been replaced with view_ptr_t + + // Used for pointers within BinaryView, primarily to make it far more clear in typedefs + // whether the size of a field can vary between architectures. + // These should _not_ be used in sizeof or direct Read() calls. + typedef uint64_t view_ptr_t; + + typedef struct { + view_ptr_t name; + view_ptr_t types; + view_ptr_t imp; + } method_t; + typedef struct { + uint32_t name; + uint32_t types; + uint32_t imp; + } method_entry_t; + typedef struct { + view_ptr_t offset; + view_ptr_t name; + view_ptr_t type; + uint32_t alignmentRaw; + uint32_t size; + } ivar_t; + typedef struct { + view_ptr_t name; + view_ptr_t attributes; + } property_t; + typedef struct { + uint32_t entsizeAndFlags; + uint32_t count; + } method_list_t; + typedef struct { + uint32_t entsizeAndFlags; + uint32_t count; + } ivar_list_t; + typedef struct { + uint32_t entsizeAndFlags; + uint32_t count; + } property_list_t; + 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; + view_ptr_t protocols; + view_ptr_t instanceMethods; + view_ptr_t classMethods; + view_ptr_t optionalInstanceMethods; + view_ptr_t optionalClassMethods; + view_ptr_t instanceProperties; + uint32_t size; + uint32_t flags; + } protocol_t; + typedef struct { + uint32_t flags; + uint32_t instanceStart; + uint32_t instanceSize; + uint32_t reserved; + view_ptr_t ivarLayout; + view_ptr_t name; + view_ptr_t baseMethods; + view_ptr_t baseProtocols; + view_ptr_t ivars; + view_ptr_t weakIvarLayout; + view_ptr_t baseProperties; + } class_ro_t; + typedef struct { + view_ptr_t isa; + view_ptr_t super; + view_ptr_t cache; + view_ptr_t vtable; + view_ptr_t data; + } class_t; + typedef struct { + view_ptr_t name; + view_ptr_t cls; + view_ptr_t instanceMethods; + view_ptr_t classMethods; + view_ptr_t protocols; + view_ptr_t instanceProperties; + } category_t; + typedef struct { + view_ptr_t receiver; + view_ptr_t current_class; + } objc_super2; + typedef struct { + view_ptr_t imp; + view_ptr_t sel; + } message_ref_t; + + struct Method { + std::string name; + std::string types; + view_ptr_t imp; + }; + + struct Ivar { + uint32_t offset; + std::string name; + std::string type; + uint32_t alignment; + uint32_t size; + }; + + struct Property { + std::string name; + std::string attributes; + }; + + struct ClassBase { + std::map<uint64_t, Method> methodList; + std::map<uint64_t, Ivar> ivarList; + }; + + struct Class { + std::string name; + ClassBase instanceClass; + ClassBase metaClass; + + // Loaded by type processing + QualifiedName associatedName; + }; + + class Protocol { + public: + std::string name; + std::vector<QualifiedName> protocols; + ClassBase instanceMethods; + ClassBase classMethods; + ClassBase optionalInstanceMethods; + ClassBase optionalClassMethods; + }; + + struct QualifiedNameOrType { + BinaryNinja::Ref<BinaryNinja::Type> type = nullptr; + BinaryNinja::QualifiedName name; + size_t ptrCount = 0; + }; + + class ObjCReader { + public: + virtual ~ObjCReader() = default; + + /*! Read from the current cursor position into buffer `dest` and advance the cursor that many bytes + + \throws Exception + \param dest Address to write the read bytes to + \param len Number of bytes to write + */ + virtual void Read(void* dest, size_t len) = 0; + + /*! Read a null-terminated string from the current cursor position + + \throws Exception + \return the string + */ + virtual std::string ReadCString() = 0; + + /*! Read a uint8_t from the current cursor position and advance the cursor by 1 byte + + \throws Exception + \return The read value + */ + virtual uint8_t Read8() = 0; + + /*! Read a uint16_t from the current cursor position and advance the cursor by 2 bytes + + \throws Exception + \return The read value + */ + virtual uint16_t Read16() = 0; + + /*! Read a uint32_t from the current cursor position and advance the cursor by 4 bytes + + \throws Exception + \return The read value + */ + virtual uint32_t Read32() = 0; + + /*! Read a uint64_t from the current cursor position and advance the cursor by 8 bytes + + \throws Exception + \return The read value + */ + virtual uint64_t Read64() = 0; + + /*! Read a int8_t from the current cursor position and advance the cursor by 1 byte + + \throws Exception + \return The read value + */ + virtual int8_t ReadS8() = 0; + + /*! Read a int16_t from the current cursor position and advance the cursor by 2 bytes + + \throws Exception + \return The read value + */ + virtual int16_t ReadS16() = 0; + + /*! Read a int32_t from the current cursor position and advance the cursor by 4 bytes + + \throws Exception + \return The read value + */ + virtual int32_t ReadS32() = 0; + + /*! Read a int64_t from the current cursor position and advance the cursor by 8 bytes + + \throws Exception + \return The read value + */ + virtual int64_t ReadS64() = 0; + + /*! Read a pointer from the current cursor position and advance it that many bytes + + \throws Exception + \return The value that was read + */ + virtual uint64_t ReadPointer() = 0; + + /*! Get the current cursor position + + \return The current cursor position + */ + virtual uint64_t GetOffset() const = 0; + + /*! Set the cursor position + + \param offset The new cursor position + */ + virtual void Seek(uint64_t offset) = 0; + + /*! Set the cursor position, relative to the current position + + \param offset Offset to the current cursor position + */ + virtual void SeekRelative(int64_t offset) = 0; + }; + + class ObjCProcessor { + struct Types { + QualifiedName relativePtr; + QualifiedName id; + QualifiedName sel; + QualifiedName BOOL; + QualifiedName nsInteger; + QualifiedName nsuInteger; + QualifiedName cgFloat; + QualifiedName cfStringFlag; + QualifiedName cfString; + QualifiedName cfStringUTF16; + QualifiedName imageInfoFlags; + QualifiedName imageInfoSwiftVersion; + QualifiedName imageInfo; + QualifiedName methodEntry; + QualifiedName method; + QualifiedName methodList; + QualifiedName classRO; + QualifiedName cls; + QualifiedName category; + QualifiedName protocol; + QualifiedName protocolList; + QualifiedName ivar; + QualifiedName ivarList; + } m_typeNames; + + bool m_isBackedByDatabase; + // TODO(WeiN76LQh): this is to avoid a bug with defining a classes protocol list in the DSC plugin. Remove once fixed + bool m_skipClassBaseProtocols; + + SymbolQueue* m_symbolQueue = nullptr; + std::map<uint64_t, Class> m_classes; + std::map<uint64_t, Class> m_categories; + std::map<uint64_t, Protocol> m_protocols; + std::unordered_map<uint64_t, std::string> m_selectorCache; + std::unordered_map<uint64_t, Method> m_localMethods; + + // Required for workflow_objc type heuristics, should be removed when that is no longer a thing. + std::map<uint64_t, std::string> m_selRefToName; + std::map<uint64_t, std::vector<uint64_t>> m_selRefToImplementations; + std::map<uint64_t, std::vector<uint64_t>> m_selToImplementations; + // -- + + uint64_t ReadPointerAccountingForRelocations(ObjCReader* reader); + std::unordered_map<uint64_t, uint64_t> m_relocationPointerRewrites; + + static Ref<Metadata> SerializeMethod(uint64_t loc, const Method& method); + static Ref<Metadata> SerializeClass(uint64_t loc, const Class& cls); + Ref<Metadata> SerializeMetadata(); + + std::vector<QualifiedNameOrType> 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> type, const std::string& name, uint64_t addr, bool deferred); + void ReadIvarList(ObjCReader* reader, ClassBase& cls, std::string_view name, view_ptr_t start); + void ReadMethodList(ObjCReader* reader, ClassBase& cls, std::string_view name, view_ptr_t start); + void ReadListOfMethodLists(ObjCReader* reader, ClassBase& cls, std::string_view name, view_ptr_t start); + void LoadClasses(ObjCReader* reader, Ref<Section> listSection); + void LoadCategories(ObjCReader* reader, Ref<Section> listSection); + void LoadProtocols(ObjCReader* reader, Ref<Section> listSection); + void GenerateClassTypes(); + bool ApplyMethodType(Class& cls, Method& method, bool isInstanceMethod); + void ApplyMethodTypes(Class& cls); + + Ref<Section> GetSectionForImage(std::optional<std::string> imageName, const char* sectionName); + void PostProcessObjCSections(ObjCReader* reader, std::optional<std::string> imageName); + + protected: + Ref<BinaryView> m_data; + Ref<Logger> m_logger; + + virtual uint64_t GetObjCRelativeMethodBaseAddress(ObjCReader* reader); + virtual void GetRelativeMethod(ObjCReader* reader, method_t& meth); + virtual std::shared_ptr<ObjCReader> GetReader() = 0; + + public: + virtual ~ObjCProcessor() = default; + + ObjCProcessor(BinaryView* data, const char* loggerName, bool isBackedByDatabase, bool skipClassBaseProtocols = false); + void ProcessObjCData(std::optional<std::string> imageName); + void ProcessCFStrings(std::optional<std::string> imageName); + void AddRelocatedPointer(uint64_t location, uint64_t rewrite); + }; +} + diff --git a/view/macho/CMakeLists.txt b/view/macho/CMakeLists.txt index 6f89250a..fc8a8f7b 100644 --- a/view/macho/CMakeLists.txt +++ b/view/macho/CMakeLists.txt @@ -6,9 +6,7 @@ if(NOT BN_INTERNAL_BUILD) add_subdirectory(${PROJECT_SOURCE_DIR}/../.. ${PROJECT_BINARY_DIR}/api) endif() -file(GLOB SOURCES - *.cpp - *.h) +file(GLOB SOURCES *.cpp *.h ../../objectivec/*) if(DEMO) add_library(view_macho STATIC ${SOURCES}) diff --git a/view/macho/machoview.cpp b/view/macho/machoview.cpp index 2a927671..8a320545 100644 --- a/view/macho/machoview.cpp +++ b/view/macho/machoview.cpp @@ -1840,13 +1840,13 @@ bool MachoView::InitializeHeader(MachOHeader& header, bool isMainHeader, uint64_ parseObjCStructs = settings->Get<bool>("loader.macho.processObjectiveC", this); if (settings && settings->Contains("loader.macho.processCFStrings")) parseCFStrings = settings->Get<bool>("loader.macho.processCFStrings", this); - if (!ObjCProcessor::ViewHasObjCMetadata(this)) + if (!MachoObjCProcessor::ViewHasObjCMetadata(this)) parseObjCStructs = false; if (!GetSectionByName("__cfstring")) parseCFStrings = false; if (parseObjCStructs || parseCFStrings) { - m_objcProcessor = new ObjCProcessor(this, m_backedByDatabase); + m_objcProcessor = new MachoObjCProcessor(this, m_backedByDatabase); } if (parseObjCStructs) { @@ -2332,7 +2332,7 @@ bool MachoView::InitializeHeader(MachOHeader& header, bool isMainHeader, uint64_ if (parseCFStrings) { try { - m_objcProcessor->ProcessCFStrings(); + m_objcProcessor->ProcessCFStrings(std::nullopt); } catch (std::exception& ex) { @@ -2344,7 +2344,7 @@ bool MachoView::InitializeHeader(MachOHeader& header, bool isMainHeader, uint64_ if (parseObjCStructs) { try { - m_objcProcessor->ProcessObjCData(); + m_objcProcessor->ProcessObjCData(std::nullopt); } catch (std::exception& ex) { @@ -3872,7 +3872,7 @@ Ref<Settings> MachoViewType::GetLoadSettingsForData(BinaryView* data) settings->UpdateProperty(override, "readOnly", false); } - if (ObjCProcessor::ViewHasObjCMetadata(viewRef)) + if (MachoObjCProcessor::ViewHasObjCMetadata(viewRef)) { settings->RegisterSetting("loader.macho.processObjectiveC", R"({ diff --git a/view/macho/machoview.h b/view/macho/machoview.h index e7927383..11a17bff 100644 --- a/view/macho/machoview.h +++ b/view/macho/machoview.h @@ -1446,7 +1446,7 @@ namespace BinaryNinja QualifiedName filesetEntryCommandQualName; } m_typeNames; - ObjCProcessor* m_objcProcessor = nullptr; + MachoObjCProcessor* m_objcProcessor = nullptr; uint64_t m_universalImageOffset; bool m_parseOnly, m_backedByDatabase; diff --git a/view/macho/objc.cpp b/view/macho/objc.cpp index 5dec6e90..6663a44b 100644 --- a/view/macho/objc.cpp +++ b/view/macho/objc.cpp @@ -1,1506 +1,93 @@ #include "objc.h" -#include "machoview.h" -#include "inttypes.h" -#include "rapidjson/rapidjson.h" -#include "rapidjson/document.h" -#include "rapidjson/stringbuffer.h" -#include "rapidjson/prettywriter.h" using namespace BinaryNinja; -Ref<Metadata> ObjCProcessor::SerializeMethod(uint64_t loc, const Method& method) +MachoObjCReader::MachoObjCReader(BinaryView* data) : m_reader(BinaryReader(data)) { - std::map<std::string, Ref<Metadata>> methodMeta; - - methodMeta["loc"] = new Metadata(loc); - methodMeta["name"] = new Metadata(method.name); - methodMeta["types"] = new Metadata(method.types); - methodMeta["imp"] = new Metadata(method.imp); - - return new Metadata(methodMeta); -} - - -Ref<Metadata> ObjCProcessor::SerializeClass(uint64_t loc, const Class& cls) -{ - std::map<std::string, Ref<Metadata>> clsMeta; - - clsMeta["loc"] = new Metadata(loc); - clsMeta["name"] = new Metadata(cls.name); - clsMeta["typeName"] = new Metadata(cls.associatedName.GetString()); - - std::vector<uint64_t> instanceMethods; - std::vector<uint64_t> classMethods; - instanceMethods.reserve(cls.instanceClass.methodList.size()); - classMethods.reserve(cls.metaClass.methodList.size()); - for (const auto& [location, _] : cls.instanceClass.methodList) - instanceMethods.push_back(location); - - clsMeta["instanceMethods"] = new Metadata(instanceMethods); - clsMeta["classMethods"] = new Metadata(classMethods); - - return new Metadata(clsMeta); -} - -Ref<Metadata> ObjCProcessor::SerializeMetadata() -{ - std::map<std::string, Ref<Metadata>> viewMeta; - viewMeta["version"] = new Metadata((uint64_t)1); - - std::vector<Ref<Metadata>> classes; - classes.reserve(m_classes.size()); - std::vector<Ref<Metadata>> categories; - categories.reserve(m_categories.size()); - std::vector<Ref<Metadata>> methods; - methods.reserve(m_localMethods.size()); - - for (const auto& [clsLoc, cls] : m_classes) - classes.push_back(SerializeClass(clsLoc, cls)); - viewMeta["classes"] = new Metadata(classes); - for (const auto& [catLoc, cat] : m_categories) - categories.push_back(SerializeClass(catLoc, cat)); - viewMeta["categories"] = new Metadata(categories); - for (const auto& [methodLoc, method] : m_localMethods) - methods.push_back(SerializeMethod(methodLoc, method)); - viewMeta["methods"] = new Metadata(methods); - - // Required for workflow_objc type guessing, should be removed when that is no longer a thing. - std::vector<Ref<Metadata>> selRefToImps; - selRefToImps.reserve(m_selRefToImplementations.size()); - for (const auto& [selRef, imps] : m_selRefToImplementations) - { - std::vector<Ref<Metadata>> mapBase = {new Metadata(selRef), new Metadata(imps)}; - Ref<Metadata> mapObject = new Metadata(mapBase); - selRefToImps.push_back(mapObject); - } - viewMeta["selRefImplementations"] = new Metadata(selRefToImps); - - std::vector<Ref<Metadata>> selToImps; - selToImps.reserve(m_selToImplementations.size()); - for (const auto& [selRef, imps] : m_selToImplementations) - { - std::vector<Ref<Metadata>> mapBase = {new Metadata(selRef), new Metadata(imps)}; - Ref<Metadata> mapObject = new Metadata(mapBase); - selToImps.push_back(mapObject); - } - viewMeta["selImplementations"] = new Metadata(selToImps); - - std::vector<Ref<Metadata>> selRefToName; - selRefToName.reserve(m_selRefToName.size()); - for (const auto& [selRef, name] : m_selRefToName) - { - std::vector<Ref<Metadata>> mapBase = {new Metadata(selRef), new Metadata(name)}; - Ref<Metadata> mapObject = new Metadata(mapBase); - selRefToName.push_back(mapObject); - } - viewMeta["selRefToName"] = new Metadata(selRefToName); - // --- - - - return new Metadata(viewMeta); -} - -std::vector<QualifiedNameOrType> ObjCProcessor::ParseEncodedType(const std::string& encodedType) -{ - std::vector<QualifiedNameOrType> result; - int pointerDepth = 0; - - bool readingNamedType = false; - std::string namedType; - int readingStructDepth = 0; - std::string structType; - char last; - - for (char c : encodedType) - { - if (readingNamedType && c != '"') - { - namedType.push_back(c); - last = c; - continue; - } - else if (readingStructDepth > 0 && c != '{' && c != '}') - { - structType.push_back(c); - last = c; - continue; - } - - if (std::isdigit(c)) - continue; - - QualifiedNameOrType nameOrType; - std::string qualifiedName; - - switch (c) - { - case '^': - pointerDepth++; - last = c; - continue; - - case '"': - if (!readingNamedType) - { - readingNamedType = true; - if (last == '@') - result.pop_back(); // We added an 'id' in the last cycle, remove it - last = c; - continue; - } - else - { - readingNamedType = false; - nameOrType.name = QualifiedName(namedType); - nameOrType.ptrCount = 1; - break; - } - case '{': - readingStructDepth++; - last = c; - continue; - case '}': - readingStructDepth--; - if (readingStructDepth < 0) - return {}; // seriously malformed type. - - if (readingStructDepth == 0) - { - // TODO: Emit real struct types - nameOrType.type = Type::PointerType(m_data->GetAddressSize(), Type::VoidType()); - break; - } - last = c; - continue; - case 'v': - nameOrType.type = Type::VoidType(); - break; - case 'c': - nameOrType.type = Type::IntegerType(1, true); - break; - case 'A': - case 'C': - nameOrType.type = Type::IntegerType(1, false); - break; - case 's': - nameOrType.type = Type::IntegerType(2, true); - break; - case 'S': - nameOrType.type = Type::IntegerType(1, false); - break; - case 'i': - nameOrType.type = Type::IntegerType(4, true); - break; - case 'I': - nameOrType.type = Type::IntegerType(4, false); - break; - case 'l': - nameOrType.type = Type::IntegerType(8, true); - break; - case 'L': - nameOrType.type = Type::IntegerType(8, true); - break; - case 'f': - nameOrType.type = Type::IntegerType(4, true); - break; - case 'b': - case 'B': - nameOrType.type = Type::BoolType(); - break; - case 'q': - qualifiedName = "NSInteger"; - break; - case 'Q': - qualifiedName = "NSUInteger"; - break; - case 'd': - qualifiedName = "CGFloat"; - break; - case '*': - nameOrType.type = Type::PointerType(m_data->GetAddressSize(), Type::IntegerType(1, true)); - break; - case '@': - qualifiedName = "id"; - // There can be a type after this, like @"NSString", that overrides this - // The handler for " will catch it and drop this "id" entry. - break; - case ':': - qualifiedName = "SEL"; - break; - case '#': - qualifiedName = "objc_class_t"; - break; - case '?': - case 'T': - nameOrType.type = Type::PointerType(8, Type::VoidType()); - break; - default: - // BNLogWarn("Unknown type specifier %c", c); - last = c; - continue; - } - - while (pointerDepth) - { - if (nameOrType.type) - nameOrType.type = Type::PointerType(8, nameOrType.type); - else - nameOrType.ptrCount++; - - pointerDepth--; - } - - if (!qualifiedName.empty()) - nameOrType.name = QualifiedName(qualifiedName); - - if (nameOrType.type == nullptr && nameOrType.name.IsEmpty()) - { - nameOrType.type = Type::VoidType(); - } - - result.push_back(nameOrType); - last = c; - } - - return result; } -void ObjCProcessor::DefineObjCSymbol( - BNSymbolType type, QualifiedName typeName, const std::string& name, uint64_t addr, bool deferred) +void MachoObjCReader::Read(void* dest, size_t len) { - DefineObjCSymbol(type, m_data->GetTypeByName(typeName), name, addr, deferred); + m_reader.Read(dest, len); } -void ObjCProcessor::DefineObjCSymbol( - BNSymbolType type, Ref<Type> typeRef, const std::string& name, uint64_t addr, bool deferred) +std::string MachoObjCReader::ReadCString() { - if (name.size() == 0 || addr == 0) - return; - - auto process = [=]() { - NameSpace nameSpace = m_data->GetInternalNameSpace(); - if (type == ExternalSymbol) - { - nameSpace = m_data->GetExternalNameSpace(); - } - - std::string shortName = name; - std::string fullName = name; - - QualifiedName varName; - - return std::pair<Ref<Symbol>, Ref<Type>>( - new Symbol(type, shortName, fullName, name, addr, GlobalBinding, nameSpace), typeRef); - }; - - if (deferred) - { - m_symbolQueue->Append(process, [this, addr = addr](Symbol* symbol, Type* type) { - // Armv7/Thumb: This will rewrite the symbol's address. - // e.g. We pass in 0xc001, it will rewrite it to 0xc000 and create the function w/ the "thumb2" arch. - if (Ref<Symbol> existingSymbol = m_data->GetSymbolByAddress(addr)) - m_data->UndefineAutoSymbol(existingSymbol); - auto funcSym = m_data->DefineAutoSymbolAndVariableOrFunction(m_data->GetDefaultPlatform(), symbol, type); - if (funcSym->GetType() == FunctionSymbol) - { - uint64_t target = symbol->GetAddress(); - Ref<Platform> targetPlatform = - m_data->GetDefaultPlatform()->GetAssociatedPlatformByAddress(target); // rewrites target. - if (Ref<Function> targetFunction = m_data->GetAnalysisFunction(targetPlatform, target)) - { - if (!m_isBackedByDatabase) - targetFunction->SetUserType(type); - } - } - }); - return; - } - - if (Ref<Symbol> existingSymbol = m_data->GetSymbolByAddress(addr)) - m_data->UndefineAutoSymbol(existingSymbol); - auto result = process(); - auto sym = m_data->DefineAutoSymbolAndVariableOrFunction(m_data->GetDefaultPlatform(), result.first, result.second); - if (sym->GetType() == FunctionSymbol) - { - uint64_t target = result.first->GetAddress(); - Ref<Platform> targetPlatform = m_data->GetDefaultPlatform()->GetAssociatedPlatformByAddress(target); // rewrites - // target. - if (Ref<Function> targetFunction = m_data->GetAnalysisFunction(targetPlatform, target)) - { - if (!m_isBackedByDatabase) - targetFunction->SetUserType(result.second); - } - } -} - -void ObjCProcessor::LoadClasses(BinaryReader* reader, Ref<Section> classPtrSection) -{ - if (!classPtrSection) - return; - auto size = classPtrSection->GetEnd() - classPtrSection->GetStart(); - if (size == 0) - return; - auto ptrSize = m_data->GetAddressSize(); - auto ptrCount = size / ptrSize; - - auto classPtrSectionStart = classPtrSection->GetStart(); - for (size_t i = 0; i < ptrCount; i++) - { - Class cls; - - view_ptr_t classPtr; - class_t clsStruct; - class_ro_t classRO; - - bool hasValidMetaClass = false; - bool hasValidMetaClassRO = false; - class_t metaClsStruct; - class_ro_t metaClassRO; - - view_ptr_t classPointerLocation = classPtrSectionStart + (i * m_data->GetAddressSize()); - reader->Seek(classPointerLocation); - - classPtr = ReadPointerAccountingForRelocations(reader); - reader->Seek(classPtr); - try - { - clsStruct.isa = ReadPointerAccountingForRelocations(reader); - clsStruct.super = reader->ReadPointer(); - clsStruct.cache = reader->ReadPointer(); - clsStruct.vtable = reader->ReadPointer(); - clsStruct.data = ReadPointerAccountingForRelocations(reader); - } - catch (ReadException& ex) - { - m_logger->LogError("Failed to read class data at 0x%llx pointed to by @ 0x%llx", reader->GetOffset(), - classPointerLocation); - continue; - } - if (clsStruct.data & 1) - { - m_logger->LogInfo("Skipping class at 0x%llx as it contains swift types", classPtr); - continue; - } - // unset first two bits - view_ptr_t classROPtr = clsStruct.data & ~3; - reader->Seek(classROPtr); - try - { - classRO.flags = reader->Read32(); - classRO.instanceStart = reader->Read32(); - classRO.instanceSize = reader->Read32(); - if (m_data->GetAddressSize() == 8) - classRO.reserved = reader->Read32(); - classRO.ivarLayout = ReadPointerAccountingForRelocations(reader); - classRO.name = ReadPointerAccountingForRelocations(reader); - classRO.baseMethods = ReadPointerAccountingForRelocations(reader); - classRO.baseProtocols = ReadPointerAccountingForRelocations(reader); - classRO.ivars = ReadPointerAccountingForRelocations(reader); - classRO.weakIvarLayout = ReadPointerAccountingForRelocations(reader); - classRO.baseProperties = ReadPointerAccountingForRelocations(reader); - } - catch (ReadException& ex) - { - m_logger->LogError("Failed to read class RO data at 0x%llx. 0x%llx, objc_class_t @ 0x%llx", - reader->GetOffset(), classPointerLocation, classROPtr); - continue; - } - - auto namePtr = classRO.name; - - std::string name; - - reader->Seek(namePtr); - try - { - name = reader->ReadCString(500); - } - catch (ReadException& ex) - { - m_logger->LogWarn( - "Failed to read class name at 0x%llx. Class has been given the placeholder name \"0x%llx\" ", namePtr, - classPtr); - char hexString[9]; - hexString[8] = 0; - snprintf(hexString, sizeof(hexString), "%" PRIx64, classPtr); - name = "0x" + std::string(hexString); - } - - cls.name = name; - - DefineObjCSymbol(BNSymbolType::DataSymbol, - Type::PointerType(m_data->GetAddressSize(), m_data->GetTypeByName(m_typeNames.cls)), "clsPtr_" + name, - classPointerLocation, true); - DefineObjCSymbol(BNSymbolType::DataSymbol, m_typeNames.cls, "cls_" + name, classPtr, true); - DefineObjCSymbol(BNSymbolType::DataSymbol, m_typeNames.classRO, "cls_ro_" + name, classROPtr, true); - DefineObjCSymbol(BNSymbolType::DataSymbol, Type::ArrayType(Type::IntegerType(1, true), name.size() + 1), - "clsName_" + name, classRO.name, true); - if (classRO.baseProtocols) - { - DefineObjCSymbol(BNSymbolType::DataSymbol, Type::NamedType(m_data, m_typeNames.protocolList), - "clsProtocols_" + name, classRO.baseProtocols, true); - reader->Seek(classRO.baseProtocols); - uint32_t count = reader->Read64(); - view_ptr_t addr = reader->GetOffset(); - for (uint32_t j = 0; j < count; j++) - { - m_data->DefineDataVariable( - addr, Type::PointerType(ptrSize, Type::NamedType(m_data, m_typeNames.protocol))); - addr += ptrSize; - } - } - - if (clsStruct.isa) - { - reader->Seek(clsStruct.isa); - try - { - metaClsStruct.isa = ReadPointerAccountingForRelocations(reader); - metaClsStruct.super = reader->ReadPointer(); - metaClsStruct.cache = reader->ReadPointer(); - metaClsStruct.vtable = reader->ReadPointer(); - metaClsStruct.data = ReadPointerAccountingForRelocations(reader) & ~1; - DefineObjCSymbol(BNSymbolType::DataSymbol, m_typeNames.cls, "metacls_" + name, clsStruct.isa, true); - hasValidMetaClass = true; - } - catch (ReadException& ex) - { - m_logger->LogWarn("Failed to read metaclass data at 0x%llx pointed to by objc_class_t @ 0x%llx", - reader->GetOffset(), classPtr); - } - } - if (hasValidMetaClass && (metaClsStruct.data & 1)) - { - m_logger->LogInfo("Skipping metaclass at 0x%llx as it contains swift types", classPtr); - hasValidMetaClass = false; - } - if (hasValidMetaClass) - { - reader->Seek(metaClsStruct.data); - try - { - metaClassRO.flags = reader->Read32(); - metaClassRO.instanceStart = reader->Read32(); - metaClassRO.instanceSize = reader->Read32(); - if (m_data->GetAddressSize() == 8) - metaClassRO.reserved = reader->Read32(); - metaClassRO.ivarLayout = ReadPointerAccountingForRelocations(reader); - metaClassRO.name = ReadPointerAccountingForRelocations(reader); - metaClassRO.baseMethods = ReadPointerAccountingForRelocations(reader); - metaClassRO.baseProtocols = ReadPointerAccountingForRelocations(reader); - metaClassRO.ivars = ReadPointerAccountingForRelocations(reader); - metaClassRO.weakIvarLayout = ReadPointerAccountingForRelocations(reader); - metaClassRO.baseProperties = ReadPointerAccountingForRelocations(reader); - DefineObjCSymbol( - BNSymbolType::DataSymbol, m_typeNames.classRO, "metacls_ro_" + name, metaClsStruct.data, true); - hasValidMetaClassRO = true; - } - catch (ReadException& ex) - { - m_logger->LogWarn("Failed to read metaclass RO data at 0x%llx pointed to by meta objc_class_t @ 0x%llx", - reader->GetOffset(), clsStruct.isa); - } - } - - if (classRO.baseMethods) - { - try - { - ReadMethodList(reader, cls.instanceClass, name, classRO.baseMethods); - } - catch (ReadException& ex) - { - m_logger->LogError("Failed to read the method list for class pointed to by 0x%llx", clsStruct.data); - } - } - if (hasValidMetaClassRO && metaClassRO.baseMethods) - { - try - { - ReadMethodList(reader, cls.metaClass, name, metaClassRO.baseMethods); - } - catch (ReadException& ex) - { - m_logger->LogError("Failed to read the method list for metaclass pointed to by 0x%llx", clsStruct.data); - } - } - - if (classRO.ivars) - { - try - { - ReadIvarList(reader, cls.instanceClass, name, classRO.ivars); - } - catch (ReadException& ex) - { - m_logger->LogError("Failed to process ivars for class at 0x%llx", clsStruct.data); - } - } - m_classes[classPtr] = cls; - } -} - -void ObjCProcessor::LoadCategories(BinaryReader* reader, Ref<Section> classPtrSection) -{ - if (!classPtrSection) - return; - auto size = classPtrSection->GetEnd() - classPtrSection->GetStart(); - if (size == 0) - return; - auto ptrSize = m_data->GetAddressSize(); - - auto classPtrSectionStart = classPtrSection->GetStart(); - auto classPtrSectionEnd = classPtrSection->GetEnd(); - - auto catType = Type::NamedType(m_data, m_typeNames.category); - auto ptrType = Type::PointerType(m_data->GetDefaultArchitecture(), catType); - for (size_t i = classPtrSectionStart; i < classPtrSectionEnd; i += ptrSize) - { - Class category; - category_t cat; - - reader->Seek(i); - auto catLocation = ReadPointerAccountingForRelocations(reader); - reader->Seek(catLocation); - - try - { - cat.name = ReadPointerAccountingForRelocations(reader); - cat.cls = ReadPointerAccountingForRelocations(reader); - cat.instanceMethods = ReadPointerAccountingForRelocations(reader); - cat.classMethods = ReadPointerAccountingForRelocations(reader); - cat.protocols = ReadPointerAccountingForRelocations(reader); - cat.instanceProperties = ReadPointerAccountingForRelocations(reader); - } - catch (ReadException& ex) - { - m_logger->LogError("Failed to read category pointed to by 0x%llx", i); - continue; - } - - std::string categoryAdditionsName; - std::string categoryBaseClassName; - - if (const auto& it = m_classes.find(cat.cls); it != m_classes.end()) - { - categoryBaseClassName = it->second.name; - category.associatedName = it->second.associatedName; - } - else if (auto symbol = m_data->GetSymbolByAddress(catLocation + m_data->GetAddressSize())) - { - if (symbol->GetType() == ImportedDataSymbol || symbol->GetType() == ImportAddressSymbol) - { - const auto& symbolName = symbol->GetFullName(); - if (symbolName.size() > 14 && symbolName.rfind("_OBJC_CLASS_$_", 0) == 0) - categoryBaseClassName = symbolName.substr(14, symbolName.size() - 14); - } - } - if (categoryBaseClassName.empty()) - { - m_logger->LogError( - "Failed to determine base classname for category at 0x%llx. Using base address as stand-in classname", - catLocation); - categoryBaseClassName = std::to_string(catLocation); - } - try - { - reader->Seek(cat.name); - categoryAdditionsName = reader->ReadCString(); - } - catch (ReadException& ex) - { - m_logger->LogError( - "Failed to read category name for category at 0x%llx. Using base address as stand-in category name", - catLocation); - categoryAdditionsName = std::to_string(catLocation); - } - category.name = categoryBaseClassName + " (" + categoryAdditionsName + ")"; - DefineObjCSymbol(BNSymbolType::DataSymbol, ptrType, "categoryPtr_" + category.name, i, true); - DefineObjCSymbol(BNSymbolType::DataSymbol, catType, "category_" + category.name, catLocation, true); - - if (cat.instanceMethods) - { - try - { - ReadMethodList(reader, category.instanceClass, category.name, cat.instanceMethods); - } - catch (ReadException& ex) - { - m_logger->LogError( - "Failed to read the instance method list for category pointed to by 0x%llx", catLocation); - } - } - if (cat.classMethods) - { - try - { - ReadMethodList(reader, category.metaClass, category.name, cat.classMethods); - } - catch (ReadException& ex) - { - m_logger->LogError( - "Failed to read the class method list for category pointed to by 0x%llx", catLocation); - } - } - m_categories[catLocation] = category; - } -} - -void ObjCProcessor::LoadProtocols(BinaryReader* reader, Ref<Section> listSection) -{ - if (!listSection) - return; - auto size = listSection->GetEnd() - listSection->GetStart(); - if (size == 0) - return; - auto ptrSize = m_data->GetAddressSize(); - - auto listSectionStart = listSection->GetStart(); - auto listSectionEnd = listSection->GetEnd(); - - auto protocolType = Type::NamedType(m_data, m_typeNames.protocol); - auto ptrType = Type::PointerType(m_data->GetDefaultArchitecture(), protocolType); - for (size_t i = listSectionStart; i < listSectionEnd; i += ptrSize) - { - protocol_t protocol; - reader->Seek(i); - auto protocolLocation = ReadPointerAccountingForRelocations(reader); - reader->Seek(protocolLocation); - - try - { - protocol.isa = ReadPointerAccountingForRelocations(reader); - protocol.mangledName = ReadPointerAccountingForRelocations(reader); - protocol.protocols = ReadPointerAccountingForRelocations(reader); - protocol.instanceMethods = ReadPointerAccountingForRelocations(reader); - protocol.classMethods = ReadPointerAccountingForRelocations(reader); - protocol.optionalInstanceMethods = ReadPointerAccountingForRelocations(reader); - protocol.optionalClassMethods = ReadPointerAccountingForRelocations(reader); - protocol.instanceProperties = ReadPointerAccountingForRelocations(reader); - } - catch (ReadException& ex) - { - m_logger->LogError("Failed to read protocol pointed to by 0x%llx", i); - continue; - } - - std::string protocolName; - try - { - reader->Seek(protocol.mangledName); - protocolName = reader->ReadCString(); - DefineObjCSymbol(BNSymbolType::DataSymbol, - Type::ArrayType(Type::IntegerType(1, true), protocolName.size() + 1), "protocolName_" + protocolName, - protocol.mangledName, true); - } - catch (ReadException& ex) - { - m_logger->LogError( - "Failed to read protocol name for protocol at 0x%llx. Using base address as stand-in protocol name", - protocolLocation); - protocolName = std::to_string(protocolLocation); - } - - Protocol protocolClass; - protocolClass.name = protocolName; - DefineObjCSymbol(BNSymbolType::DataSymbol, ptrType, "protocolPtr_" + protocolName, i, true); - DefineObjCSymbol(BNSymbolType::DataSymbol, protocolType, "protocol_" + protocolName, protocolLocation, true); - if (protocol.protocols) - { - DefineObjCSymbol(BNSymbolType::DataSymbol, Type::NamedType(m_data, m_typeNames.protocolList), - "protoProtocols_" + protocolName, protocol.protocols, true); - reader->Seek(protocol.protocols); - uint32_t count = reader->Read64(); - view_ptr_t addr = reader->GetOffset(); - for (uint32_t j = 0; j < count; j++) - { - m_data->DefineDataVariable( - addr, Type::PointerType(ptrSize, Type::NamedType(m_data, m_typeNames.protocol))); - addr += ptrSize; - } - } - - if (protocol.instanceMethods) - { - try - { - ReadMethodList(reader, protocolClass.instanceMethods, protocolName, protocol.instanceMethods); - } - catch (ReadException& ex) - { - m_logger->LogError( - "Failed to read the instance method list for protocol pointed to by 0x%llx", protocolLocation); - } - } - if (protocol.classMethods) - { - try - { - ReadMethodList(reader, protocolClass.classMethods, protocolName, protocol.classMethods); - } - catch (ReadException& ex) - { - m_logger->LogError( - "Failed to read the class method list for protocol pointed to by 0x%llx", protocolLocation); - } - } - if (protocol.optionalInstanceMethods) - { - try - { - ReadMethodList( - reader, protocolClass.optionalInstanceMethods, protocolName, protocol.optionalInstanceMethods); - } - catch (ReadException& ex) - { - m_logger->LogError("Failed to read the optional instance method list for protocol pointed to by 0x%llx", - protocolLocation); - } - } - if (protocol.optionalClassMethods) - { - try - { - ReadMethodList(reader, protocolClass.optionalClassMethods, protocolName, protocol.optionalClassMethods); - } - catch (ReadException& ex) - { - m_logger->LogError("Failed to read the optional class method list for protocol pointed to by 0x%llx", - protocolLocation); - } - } - m_protocols[protocolLocation] = protocolClass; - } + return m_reader.ReadCString(); } -void ObjCProcessor::ReadMethodList(BinaryReader* reader, ClassBase& cls, std::string name, view_ptr_t start) +uint8_t MachoObjCReader::Read8() { - reader->Seek(start); - method_list_t head; - head.entsizeAndFlags = reader->Read32(); - head.count = reader->Read32(); - 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); - - for (unsigned i = 0; i < head.count; i++) - { - try - { - Method method; - auto cursor = start + sizeof(method_list_t) + (i * methodSize); - reader->Seek(cursor); - method_t meth; - // workflow_objc support - uint64_t selRefAddr = 0; - uint64_t selAddr = 0; - // -- - if (relativeOffsets) - { - meth.name = cursor + static_cast<int32_t>(reader->Read32()); - meth.types = cursor + 4 + static_cast<int32_t>(reader->Read32()); - meth.imp = cursor + 8 + static_cast<int32_t>(reader->Read32()); - } - else - { - meth.name = ReadPointerAccountingForRelocations(reader); - meth.types = ReadPointerAccountingForRelocations(reader); - meth.imp = ReadPointerAccountingForRelocations(reader); - } - if (!relativeOffsets || directSelectors) - { - reader->Seek(meth.name); - selAddr = meth.name; - method.name = reader->ReadCString(); - reader->Seek(meth.types); - method.types = reader->ReadCString(); - DefineObjCSymbol(DataSymbol, Type::ArrayType(Type::IntegerType(1, true), method.name.size() + 1), - "sel_" + method.name, meth.name, true); - DefineObjCSymbol(DataSymbol, Type::ArrayType(Type::IntegerType(1, true), method.types.size() + 1), - "selTypes_" + method.name, meth.types, true); - } - else - { - std::string sel; - view_ptr_t selRef; - reader->Seek(meth.name); - selRefAddr = meth.name; - selRef = ReadPointerAccountingForRelocations(reader); - reader->Seek(meth.types); - method.types = reader->ReadCString(); - selAddr = selRef; - if (const auto& it = m_selectorCache.find(selRef); it != m_selectorCache.end()) - method.name = it->second; - else - { - reader->Seek(selRef); - method.name = reader->ReadCString(selRef); - m_selectorCache[selRef] = method.name; - } - auto selType = Type::ArrayType(Type::IntegerType(1, true), method.name.size() + 1); - DefineObjCSymbol(DataSymbol, selType, "sel_" + method.name, selRef, true); - DefineObjCSymbol(DataSymbol, Type::ArrayType(Type::IntegerType(1, true), method.types.size() + 1), - "selTypes_" + method.name, meth.types, true); - DefineObjCSymbol(DataSymbol, Type::PointerType(m_data->GetAddressSize(), selType), - "selRef_" + method.name, meth.name, true); - } - // workflow objc support - if (selAddr) - m_selToImplementations[selAddr].push_back(meth.imp); - if (selRefAddr) - m_selRefToImplementations[selRefAddr].push_back(meth.imp); - // -- - - DefineObjCSymbol(DataSymbol, relativeOffsets ? m_typeNames.methodEntry : m_typeNames.method, - "method_" + method.name, cursor, true); - method.imp = meth.imp; - cls.methodList[cursor] = method; - m_localMethods[cursor] = method; - } - catch (ReadException& ex) - { - m_logger->LogError( - "Failed to process a method at offset 0x%llx", start + sizeof(method_list_t) + (i * methodSize)); - } - } + return m_reader.Read8(); } -void ObjCProcessor::ReadIvarList(BinaryReader* reader, ClassBase& cls, std::string name, view_ptr_t start) +uint16_t MachoObjCReader::Read16() { - 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); - for (unsigned i = 0; i < head.count; i++) - { - try - { - Ivar ivar; - ivar_t ivarStruct; - uint64_t cursor = start + (sizeof(ivar_list_t)) + (i * ((addressSize * 3) + 8)); - reader->Seek(cursor); - ivarStruct.offset = ReadPointerAccountingForRelocations(reader); - ivarStruct.name = ReadPointerAccountingForRelocations(reader); - ivarStruct.type = ReadPointerAccountingForRelocations(reader); - ivarStruct.alignmentRaw = reader->Read32(); - ivarStruct.size = reader->Read32(); - - reader->Seek(ivarStruct.offset); - ivar.offset = reader->Read32(); - reader->Seek(ivarStruct.name); - ivar.name = reader->ReadCString(); - reader->Seek(ivarStruct.type); - ivar.type = reader->ReadCString(); - - DefineObjCSymbol(DataSymbol, m_typeNames.ivar, "ivar_" + ivar.name, cursor, true); - DefineObjCSymbol(DataSymbol, Type::ArrayType(Type::IntegerType(1, true), ivar.name.size() + 1), - "ivarName_" + ivar.name, ivarStruct.name, true); - DefineObjCSymbol(DataSymbol, Type::ArrayType(Type::IntegerType(1, true), ivar.type.size() + 1), - "ivarType_" + ivar.name, ivarStruct.type, true); - - cls.ivarList[cursor] = ivar; - } - catch (ReadException& ex) - { - m_logger->LogError("Failed to process an ivar at offset 0x%llx", - start + (sizeof(ivar_list_t)) + (i * ((addressSize * 3) + 8))); - } - } + return m_reader.Read16(); } -bool ObjCProcessor::ViewHasObjCMetadata(BinaryNinja::BinaryView* data) +uint32_t MachoObjCReader::Read32() { - return (data->GetSectionByName("__objc_classlist") || data->GetSectionByName("__objc_catlist") - || data->GetSectionByName("__objc_protolist")); + return m_reader.Read32(); } - -std::pair<QualifiedName, Ref<Type>> finalizeStructureBuilder( - Ref<BinaryView> m_data, StructureBuilder sb, std::string name) +uint64_t MachoObjCReader::Read64() { - auto classTypeStruct = sb.Finalize(); - - QualifiedName classTypeName(name); - auto classTypeId = Type::GenerateAutoTypeId("objc", classTypeName); - auto classType = Type::StructureType(classTypeStruct); - auto classQualName = m_data->DefineType(classTypeId, classTypeName, classType); - - return {classQualName, classType}; + return m_reader.Read64(); } -std::pair<QualifiedName, Ref<Type>> finalizeEnumerationBuilder( - Ref<BinaryView> m_data, EnumerationBuilder eb, uint64_t size, QualifiedName name) +int8_t MachoObjCReader::ReadS8() { - auto enumTypeStruct = eb.Finalize(); - - auto enumTypeId = Type::GenerateAutoTypeId("objc", name); - auto enumType = Type::EnumerationType(enumTypeStruct, size); - auto enumQualName = m_data->DefineType(enumTypeId, name, enumType); - - return {enumQualName, enumType}; + return static_cast<int8_t>(m_reader.Read8()); } -inline QualifiedName defineTypedef(Ref<BinaryView> m_data, const QualifiedName name, Ref<Type> type) +int16_t MachoObjCReader::ReadS16() { - auto typeID = Type::GenerateAutoTypeId("objc", name); - m_data->DefineType(typeID, name, type); - return m_data->GetTypeNameById(typeID); + return static_cast<int16_t>(m_reader.Read16()); } -void ObjCProcessor::GenerateClassTypes() +int32_t MachoObjCReader::ReadS32() { - for (auto& [_, cls] : m_classes) - { - QualifiedName typeName; - StructureBuilder classTypeBuilder; - bool failedToDecodeType = false; - for (const auto& [ivarLoc, ivar] : cls.instanceClass.ivarList) - { - auto encodedTypeList = ParseEncodedType(ivar.type); - if (encodedTypeList.empty()) - { - failedToDecodeType = true; - break; - } - auto encodedType = encodedTypeList.at(0); - - Ref<Type> type; - - if (encodedType.type) - type = encodedType.type; - else - { - type = Type::NamedType(encodedType.name, Type::PointerType(m_data->GetAddressSize(), Type::VoidType())); - for (size_t i = encodedType.ptrCount; i > 0; i--) - type = Type::PointerType(m_data->GetAddressSize(), type); - } - - if (!type) - type = Type::PointerType(m_data->GetAddressSize(), Type::VoidType()); - - classTypeBuilder.AddMemberAtOffset(type, ivar.name, ivar.offset); - } - if (failedToDecodeType) - continue; - auto classTypeStruct = classTypeBuilder.Finalize(); - QualifiedName classTypeName = cls.name; - std::string classTypeId = Type::GenerateAutoTypeId("objc", classTypeName); - Ref<Type> classType = Type::StructureType(classTypeStruct); - QualifiedName classQualName = m_data->DefineType(classTypeId, classTypeName, classType); - cls.associatedName = classTypeName; - } + return static_cast<int32_t>(m_reader.Read32()); } -bool ObjCProcessor::ApplyMethodType(Class& cls, Method& method, bool isInstanceMethod) +int64_t MachoObjCReader::ReadS64() { - std::stringstream r(method.name); - - std::string token; - std::vector<std::string> selectorTokens; - while (std::getline(r, token, ':')) - selectorTokens.push_back(token); - - std::vector<QualifiedNameOrType> typeTokens = ParseEncodedType(method.types); - if (typeTokens.empty()) - return false; - - auto typeForQualifiedNameOrType = [this](QualifiedNameOrType nameOrType) { - Ref<Type> type; - - if (nameOrType.type) - { - type = nameOrType.type; - if (!type) - type = Type::PointerType(m_data->GetAddressSize(), Type::VoidType()); - } - else - { - type = Type::NamedType(nameOrType.name, Type::PointerType(m_data->GetAddressSize(), Type::VoidType())); - for (size_t i = nameOrType.ptrCount; i > 0; i--) - type = Type::PointerType(m_data->GetAddressSize(), type); - } - - return type; - }; - - BinaryNinja::QualifiedNameAndType nameAndType; - std::set<BinaryNinja::QualifiedName> typesAllowRedefinition; - - auto retType = typeForQualifiedNameOrType(typeTokens[0]); - - std::vector<BinaryNinja::FunctionParameter> params; - auto cc = m_data->GetDefaultPlatform()->GetDefaultCallingConvention(); - - params.push_back({"self", - cls.associatedName.IsEmpty() ? - Type::NamedType(m_data, {"id"}) : - Type::PointerType(m_data->GetAddressSize(), Type::NamedType(m_data, cls.associatedName)), - true, BinaryNinja::Variable()}); - - params.push_back({"sel", Type::NamedType(m_data, {"SEL"}), true, BinaryNinja::Variable()}); - - for (size_t i = 3; i < typeTokens.size(); i++) - { - std::string suffix; - - params.push_back({selectorTokens.size() > i - 3 ? selectorTokens[i - 3] : "arg", - typeForQualifiedNameOrType(typeTokens[i]), true, BinaryNinja::Variable()}); - } - - auto funcType = BinaryNinja::Type::FunctionType(retType, cc, params); - - // Search for the method's implementation function; apply the type if found. - std::string prefix = isInstanceMethod ? "-" : "+"; - auto name = prefix + "[" + cls.name + " " + method.name + "]"; - - DefineObjCSymbol(FunctionSymbol, funcType, name, method.imp, true); - - return true; + return static_cast<int64_t>(m_reader.Read64()); } -void ObjCProcessor::ApplyMethodTypes(Class& cls) +uint64_t MachoObjCReader::ReadPointer() { - for (auto& [_, method] : cls.instanceClass.methodList) - { - ApplyMethodType(cls, method, true); - } - for (auto& [_, method] : cls.metaClass.methodList) - { - ApplyMethodType(cls, method, false); - } + return m_reader.ReadPointer(); } -void ObjCProcessor::PostProcessObjCSections(BinaryReader* reader) +uint64_t MachoObjCReader::GetOffset() const { - auto ptrSize = m_data->GetAddressSize(); - if (auto imageInfo = m_data->GetSectionByName("__objc_imageinfo")) - { - auto start = imageInfo->GetStart(); - auto type = Type::NamedType(m_data, m_typeNames.imageInfo); - m_data->DefineDataVariable(start, type); - } - if (auto selrefs = m_data->GetSectionByName("__objc_selrefs")) - { - auto start = selrefs->GetStart(); - auto end = selrefs->GetEnd(); - auto type = Type::PointerType(ptrSize, Type::IntegerType(1, false)); - for (view_ptr_t i = start; i < end; i += ptrSize) - { - reader->Seek(i); - auto selLoc = ReadPointerAccountingForRelocations(reader); - std::string sel; - if (const auto& it = m_selectorCache.find(selLoc); it != m_selectorCache.end()) - sel = it->second; - else - { - reader->Seek(selLoc); - sel = reader->ReadCString(); - m_selectorCache[selLoc] = sel; - DefineObjCSymbol(DataSymbol, Type::ArrayType(Type::IntegerType(1, true), sel.size() + 1), "sel_" + sel, - selLoc, true); - } - DefineObjCSymbol(DataSymbol, type, "selRef_" + sel, i, true); - } - } - if (auto superRefs = m_data->GetSectionByName("__objc_classrefs")) - { - auto start = superRefs->GetStart(); - auto end = superRefs->GetEnd(); - auto type = Type::PointerType(ptrSize, Type::NamedType(m_data, m_typeNames.cls)); - for (view_ptr_t i = start; i < end; i += ptrSize) - { - reader->Seek(i); - auto clsLoc = ReadPointerAccountingForRelocations(reader); - if (const auto& it = m_classes.find(clsLoc); it != m_classes.end()) - { - auto& cls = it->second; - std::string name = cls.name; - if (!name.empty()) - DefineObjCSymbol(DataSymbol, type, "clsRef_" + name, i, true); - } - } - } - if (auto superRefs = m_data->GetSectionByName("__objc_superrefs")) - { - auto start = superRefs->GetStart(); - auto end = superRefs->GetEnd(); - auto type = Type::PointerType(ptrSize, Type::NamedType(m_data, m_typeNames.cls)); - for (view_ptr_t i = start; i < end; i += ptrSize) - { - reader->Seek(i); - auto clsLoc = ReadPointerAccountingForRelocations(reader); - if (const auto& it = m_classes.find(clsLoc); it != m_classes.end()) - { - auto& cls = it->second; - std::string name = cls.name; - if (!name.empty()) - DefineObjCSymbol(DataSymbol, type, "superRef_" + name, i, true); - } - } - } - if (auto protoRefs = m_data->GetSectionByName("__objc_protorefs")) - { - auto start = protoRefs->GetStart(); - auto end = protoRefs->GetEnd(); - auto type = Type::PointerType(ptrSize, Type::NamedType(m_data, m_typeNames.protocol)); - for (view_ptr_t i = start; i < end; i += ptrSize) - { - reader->Seek(i); - auto protoLoc = ReadPointerAccountingForRelocations(reader); - if (const auto& it = m_protocols.find(protoLoc); it != m_protocols.end()) - { - auto& proto = it->second; - std::string name = proto.name; - if (!name.empty()) - DefineObjCSymbol(DataSymbol, type, "protoRef_" + name, i, true); - } - } - } - if (auto ivars = m_data->GetSectionByName("__objc_ivar")) - { - auto start = ivars->GetStart(); - auto end = ivars->GetEnd(); - auto ivarSectionEntryTypeBuilder = new TypeBuilder(Type::IntegerType(8, false)); - ivarSectionEntryTypeBuilder->SetConst(true); - auto type = ivarSectionEntryTypeBuilder->Finalize(); - for (view_ptr_t i = start; i < end; i += ptrSize) - { - m_data->DefineDataVariable(i, type); - } - } + return m_reader.GetOffset(); } -uint64_t ObjCProcessor::ReadPointerAccountingForRelocations(BinaryReader* reader) +void MachoObjCReader::Seek(uint64_t offset) { - if (auto it = m_relocationPointerRewrites.find(reader->GetOffset()); it != m_relocationPointerRewrites.end()) - { - reader->SeekRelative(m_data->GetAddressSize()); - return it->second; - } - return reader->ReadPointer(); + m_reader.Seek(offset); } - -ObjCProcessor::ObjCProcessor(BinaryNinja::BinaryView* data, bool isBackedByDatabase) : - m_isBackedByDatabase(isBackedByDatabase), m_data(data) +void MachoObjCReader::SeekRelative(int64_t offset) { - m_logger = m_data->CreateLogger("macho.objc"); + m_reader.SeekRelative(offset); } -void ObjCProcessor::ProcessObjCData() +std::shared_ptr<ObjCReader> MachoObjCProcessor::GetReader() { - m_symbolQueue = new SymbolQueue(); - auto addrSize = m_data->GetAddressSize(); - - // m_typeNames.relativePtr = defineTypedef(m_data, {"rptr_t"}, Type::IntegerType(4, true)); - // auto rptr_t = Type::NamedType(m_data, m_typeNames.relativePtr); - auto rptr_t = Type::NamedType(m_data, defineTypedef(m_data, {"rptr_t"}, Type::IntegerType(4, true))); - - // m_typeNames.id = defineTypedef(m_data, {"id"}, Type::PointerType(addrSize, Type::VoidType())); - // m_typeNames.sel = defineTypedef(m_data, {"SEL"}, Type::PointerType(addrSize, Type::IntegerType(1, false))); - // - // m_typeNames.BOOL = defineTypedef(m_data, {"BOOL"}, Type::IntegerType(1, false)); - // m_typeNames.nsInteger = defineTypedef(m_data, {"NSInteger"}, Type::IntegerType(addrSize, true)); - // m_typeNames.nsuInteger = defineTypedef(m_data, {"NSUInteger"}, Type::IntegerType(addrSize, false)); - // m_typeNames.cgFloat = defineTypedef(m_data, {"CGFloat"}, Type::FloatType(addrSize)); - - // https://github.com/apple-oss-distributions/objc4/blob/196363c165b175ed925ef6b9b99f558717923c47/runtime/objc-abi.h - EnumerationBuilder imageInfoFlagBuilder; - imageInfoFlagBuilder.AddMemberWithValue("IsReplacement", 1 << 0); - imageInfoFlagBuilder.AddMemberWithValue("SupportsGC", 1 << 1); - imageInfoFlagBuilder.AddMemberWithValue("RequiresGC", 1 << 2); - imageInfoFlagBuilder.AddMemberWithValue("OptimizedByDyld", 1 << 3); - imageInfoFlagBuilder.AddMemberWithValue("CorrectedSynthesize", 1 << 4); - imageInfoFlagBuilder.AddMemberWithValue("IsSimulated", 1 << 5); - imageInfoFlagBuilder.AddMemberWithValue("HasCategoryClassProperties", 1 << 6); - imageInfoFlagBuilder.AddMemberWithValue("OptimizedByDyldClosure", 1 << 7); - imageInfoFlagBuilder.AddMemberWithValue("SwiftUnstableVersionMask", 0xff << 8); - imageInfoFlagBuilder.AddMemberWithValue("SwiftStableVersionMask", 0xFFFF << 16); - auto imageInfoFlagType = finalizeEnumerationBuilder(m_data, imageInfoFlagBuilder, 4, {"objc_image_info_flags"}); - m_typeNames.imageInfoFlags = imageInfoFlagType.first; - - EnumerationBuilder swiftVersionBuilder; - swiftVersionBuilder.AddMemberWithValue("SwiftVersion1", 1); - swiftVersionBuilder.AddMemberWithValue("SwiftVersion1_2", 2); - swiftVersionBuilder.AddMemberWithValue("SwiftVersion2", 3); - swiftVersionBuilder.AddMemberWithValue("SwiftVersion3", 4); - swiftVersionBuilder.AddMemberWithValue("SwiftVersion4", 5); - swiftVersionBuilder.AddMemberWithValue("SwiftVersion4_1", 6); // [sic] - swiftVersionBuilder.AddMemberWithValue("SwiftVersion4_2", 6); - swiftVersionBuilder.AddMemberWithValue("SwiftVersion5", 7); - auto swiftVersionType = - finalizeEnumerationBuilder(m_data, swiftVersionBuilder, 4, {"objc_image_info_swift_version"}); - m_typeNames.imageInfoSwiftVersion = swiftVersionType.first; - - StructureBuilder imageInfoBuilder; - imageInfoBuilder.AddMember(Type::IntegerType(4, false), "version"); - imageInfoBuilder.AddMember(Type::NamedType(m_data, m_typeNames.imageInfoFlags), "flags"); - auto imageInfoType = finalizeStructureBuilder(m_data, imageInfoBuilder, "objc_image_info_t"); - m_typeNames.imageInfo = imageInfoType.first; - - StructureBuilder methodEntry; - methodEntry.AddMember(rptr_t, "name"); - methodEntry.AddMember(rptr_t, "types"); - methodEntry.AddMember(rptr_t, "imp"); - auto type = finalizeStructureBuilder(m_data, methodEntry, "objc_method_entry_t"); - m_typeNames.methodEntry = type.first; - - StructureBuilder method; - method.AddMember(Type::PointerType(addrSize, Type::IntegerType(1, true)), "name"); - method.AddMember(Type::PointerType(addrSize, Type::IntegerType(1, true)), "types"); - method.AddMember(Type::PointerType(addrSize, Type::VoidType()), "imp"); - type = finalizeStructureBuilder(m_data, method, "objc_method_t"); - m_typeNames.method = type.first; - - StructureBuilder methList; - methList.AddMember(Type::IntegerType(4, false), "obsolete"); - methList.AddMember(Type::IntegerType(4, false), "count"); - type = finalizeStructureBuilder(m_data, methList, "objc_method_list_t"); - m_typeNames.methodList = type.first; - - StructureBuilder ivarBuilder; - ivarBuilder.AddMember(Type::PointerType(addrSize, Type::IntegerType(4, false)), "offset"); - ivarBuilder.AddMember(Type::PointerType(addrSize, Type::IntegerType(1, true)), "name"); - ivarBuilder.AddMember(Type::PointerType(addrSize, Type::IntegerType(1, true)), "type"); - ivarBuilder.AddMember(Type::IntegerType(4, false), "alignment"); - ivarBuilder.AddMember(Type::IntegerType(4, false), "size"); - type = finalizeStructureBuilder(m_data, ivarBuilder, "objc_ivar_t"); - m_typeNames.ivar = type.first; - - StructureBuilder ivarList; - ivarList.AddMember(Type::IntegerType(4, false), "entsize"); - ivarList.AddMember(Type::IntegerType(4, false), "count"); - type = finalizeStructureBuilder(m_data, ivarList, "objc_ivar_list_t"); - m_typeNames.ivarList = type.first; - - StructureBuilder protocolListBuilder; - protocolListBuilder.AddMember(Type::IntegerType(addrSize, false), "count"); - m_typeNames.protocolList = finalizeStructureBuilder(m_data, protocolListBuilder, "objc_protocol_list_t").first; - - StructureBuilder classROBuilder; - classROBuilder.AddMember(Type::IntegerType(4, false), "flags"); - classROBuilder.AddMember(Type::IntegerType(4, false), "start"); - classROBuilder.AddMember(Type::IntegerType(4, false), "size"); - if (addrSize == 8) - classROBuilder.AddMember(Type::IntegerType(4, false), "reserved"); - classROBuilder.AddMember(Type::PointerType(addrSize, Type::VoidType()), "ivar_layout"); - classROBuilder.AddMember(Type::PointerType(addrSize, Type::IntegerType(1, true)), "name"); - classROBuilder.AddMember(Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.methodList)), "methods"); - classROBuilder.AddMember( - Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.protocolList)), "protocols"); - classROBuilder.AddMember(Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.ivarList)), "ivars"); - classROBuilder.AddMember(Type::PointerType(addrSize, Type::VoidType()), "weak_ivar_layout"); - classROBuilder.AddMember(Type::PointerType(addrSize, Type::VoidType()), "properties"); - type = finalizeStructureBuilder(m_data, classROBuilder, "objc_class_ro_t"); - m_typeNames.classRO = type.first; - - QualifiedName classTypeName("objc_class_t"); - auto classTypeId = Type::GenerateAutoTypeId("objc", classTypeName); - auto isaType = Type::PointerType(m_data->GetDefaultArchitecture(), - TypeBuilder::NamedType( - new NamedTypeReferenceBuilder(StructNamedTypeClass, "", classTypeName), m_data->GetAddressSize(), 4) - .Finalize()); - - StructureBuilder classBuilder; - classBuilder.AddMember(isaType, "isa"); - classBuilder.AddMember(Type::PointerType(addrSize, Type::VoidType()), "super"); - classBuilder.AddMember(Type::PointerType(addrSize, Type::VoidType()), "cache"); - classBuilder.AddMember(Type::PointerType(addrSize, Type::VoidType()), "vtable"); - classBuilder.AddMember(Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.classRO)), "data"); - - auto classTypeStruct = classBuilder.Finalize(); - auto classType = Type::StructureType(classTypeStruct); - auto classQualName = m_data->DefineType(classTypeId, classTypeName, classType); - - m_typeNames.cls = classQualName; - - StructureBuilder categoryBuilder; - categoryBuilder.AddMember(Type::PointerType(addrSize, Type::IntegerType(1, true)), "category_name"); - categoryBuilder.AddMember(Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.cls)), "class"); - categoryBuilder.AddMember( - Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.methodList)), "inst_methods"); - categoryBuilder.AddMember( - Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.methodList)), "class_methods"); - categoryBuilder.AddMember(Type::PointerType(addrSize, Type::VoidType()), "protocols"); - categoryBuilder.AddMember(Type::PointerType(addrSize, Type::VoidType()), "properties"); - m_typeNames.category = finalizeStructureBuilder(m_data, categoryBuilder, "objc_category_t").first; - - StructureBuilder protocolBuilder; - protocolBuilder.AddMember(Type::PointerType(addrSize, Type::VoidType()), "isa"); - protocolBuilder.AddMember(Type::PointerType(addrSize, Type::IntegerType(1, true)), "mangledName"); - protocolBuilder.AddMember( - Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.protocolList)), "protocols"); - protocolBuilder.AddMember( - Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.methodList)), "instanceMethods"); - protocolBuilder.AddMember( - Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.methodList)), "classMethods"); - protocolBuilder.AddMember( - Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.methodList)), "optionalInstanceMethods"); - protocolBuilder.AddMember( - Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.methodList)), "optionalClassMethods"); - protocolBuilder.AddMember(Type::PointerType(addrSize, Type::VoidType()), "instanceProperties"); - protocolBuilder.AddMember(Type::IntegerType(4, false), "size"); - protocolBuilder.AddMember(Type::IntegerType(4, false), "flags"); - m_typeNames.protocol = finalizeStructureBuilder(m_data, protocolBuilder, "objc_protocol_t").first; - - auto reader = BinaryReader(m_data); - m_data->BeginBulkModifySymbols(); - if (auto classList = m_data->GetSectionByName("__objc_classlist")) - LoadClasses(&reader, classList); - if (auto nonLazyClassList = m_data->GetSectionByName("__objc_nlclslist")) - LoadClasses(&reader, nonLazyClassList); // See: https://stackoverflow.com/a/15318325 - - GenerateClassTypes(); - for (auto& [_, cls] : m_classes) - ApplyMethodTypes(cls); - - if (auto catList = m_data->GetSectionByName("__objc_catlist")) // Do this after loading class type data. - LoadCategories(&reader, catList); - if (auto nonLazyCatList = m_data->GetSectionByName("__objc_nlcatlist")) // Do this after loading class type data. - LoadCategories(&reader, nonLazyCatList); - for (auto& [_, cat] : m_categories) - ApplyMethodTypes(cat); - - if (auto protoList = m_data->GetSectionByName("__objc_protolist")) - LoadProtocols(&reader, protoList); - - PostProcessObjCSections(&reader); - - auto id = m_data->BeginUndoActions(); - m_symbolQueue->Process(); - m_data->EndBulkModifySymbols(); - delete m_symbolQueue; - m_data->ForgetUndoActions(id); - - auto meta = SerializeMetadata(); - m_data->StoreMetadata("Objective-C", meta, true); - - m_relocationPointerRewrites.clear(); + return std::make_shared<MachoObjCReader>(m_data); } - -void ObjCProcessor::ProcessCFStrings() +bool MachoObjCProcessor::ViewHasObjCMetadata(BinaryView* data) { - m_symbolQueue = new SymbolQueue(); - uint64_t ptrSize = m_data->GetAddressSize(); - // https://github.com/apple/llvm-project/blob/next/clang/lib/CodeGen/CodeGenModule.cpp#L6129 - // See also ASTContext.cpp ctrl+f __NSConstantString_tag - - // The place these flags are used is unclear, along with any clear flag definitions, but they are useful for - // introspection - EnumerationBuilder __cfStringFlagBuilder; - __cfStringFlagBuilder.AddMemberWithValue("SwiftABI", 0b1); - __cfStringFlagBuilder.AddMemberWithValue("Swift4_1", 0b100); - // LLVM also sets 0x7c0 (0b11111000000) on both UTF8 and UTF16 strings however it is unclear what this denotes. - __cfStringFlagBuilder.AddMemberWithValue("UTF8", 0b1000); - __cfStringFlagBuilder.AddMemberWithValue("UTF16", 0b10000); - auto type = finalizeEnumerationBuilder(m_data, __cfStringFlagBuilder, ptrSize, {"CFStringFlag"}); - m_typeNames.cfStringFlag = type.first; - - StructureBuilder __cfStringStructBuilder; - __cfStringStructBuilder.AddMember(Type::PointerType(ptrSize, Type::VoidType()), "isa"); - __cfStringStructBuilder.AddMember(Type::NamedType(m_data, m_typeNames.cfStringFlag), "flags"); - __cfStringStructBuilder.AddMember(Type::PointerType(ptrSize, Type::IntegerType(1, true)), "data"); - __cfStringStructBuilder.AddMember(Type::IntegerType(ptrSize, false), "length"); - type = finalizeStructureBuilder(m_data, __cfStringStructBuilder, "__NSConstantString"); - m_typeNames.cfString = type.first; - - StructureBuilder __cfStringUTF16StructBuilder; - __cfStringUTF16StructBuilder.AddMember(Type::PointerType(ptrSize, Type::VoidType()), "isa"); - __cfStringUTF16StructBuilder.AddMember(Type::NamedType(m_data, m_typeNames.cfStringFlag), "flags"); - __cfStringUTF16StructBuilder.AddMember(Type::PointerType(ptrSize, Type::IntegerType(2, true)), "data"); - __cfStringUTF16StructBuilder.AddMember(Type::IntegerType(ptrSize, false), "length"); - type = finalizeStructureBuilder(m_data, __cfStringUTF16StructBuilder, "__NSConstantString_UTF16"); - m_typeNames.cfStringUTF16 = type.first; - - auto reader = BinaryReader(m_data); - if (auto cfstrings = m_data->GetSectionByName("__cfstring")) - { - auto start = cfstrings->GetStart(); - auto end = cfstrings->GetEnd(); - auto typeWidth = Type::NamedType(m_data, m_typeNames.cfString)->GetWidth(); - m_data->BeginBulkModifySymbols(); - for (view_ptr_t i = start; i < end; i += typeWidth) - { - reader.Seek(i + ptrSize); - uint64_t flags = reader.ReadPointer(); - auto strLoc = ReadPointerAccountingForRelocations(&reader); - auto size = reader.ReadPointer(); - std::string str; - if (flags & 0b10000) // UTF16 - { - auto data = m_data->ReadBuffer(strLoc, size * 2); - - str = ""; - for (uint64_t bufferOff = 0; bufferOff < size * 2; bufferOff += 2) - { - uint8_t* rawData = static_cast<uint8_t*>(data.GetData()); - uint8_t* offsetAddress = rawData + bufferOff; - uint16_t c = *reinterpret_cast<uint16_t*>(offsetAddress); - if (c == 0x20) { - str.push_back('_'); - } else if (c == '\r') { - str.push_back('\\'); - str.push_back('r'); - } else if (c == '\n') { - str.push_back('\\'); - str.push_back('n'); - } else if (c == '\t') { - str.push_back('\\'); - str.push_back('t'); - } else if (c > 0x20 && c < 0x80) { - str.push_back(c); - } else { - str.push_back('?'); - } - } - DefineObjCSymbol( - DataSymbol, Type::ArrayType(Type::WideCharType(2), size + 1), "ustr_" + str, strLoc, true); - DefineObjCSymbol( - DataSymbol, Type::NamedType(m_data, m_typeNames.cfStringUTF16), "cfstr_" + str, i, true); - } - else // UTF8 / ASCII - { - reader.Seek(strLoc); - std::string rawStr = reader.ReadCString(size + 1); - str = ""; - for (signed char c : rawStr) - { - if (c == 0x20) { - str.push_back('_'); - } else if (c == '\r') { - str.push_back('\\'); - str.push_back('r'); - } else if (c == '\n') { - str.push_back('\\'); - str.push_back('n'); - } else if (c == '\t') { - str.push_back('\\'); - str.push_back('t'); - } else if (c > 0x20 || c < 0) { - str.push_back(c); - } else { - str.push_back('?'); - } - } - DefineObjCSymbol(DataSymbol, Type::ArrayType(Type::IntegerType(1, true), str.size() + 1), "cstr_" + str, - strLoc, true); - DefineObjCSymbol(DataSymbol, Type::NamedType(m_data, m_typeNames.cfString), "cfstr_" + str, i, true); - } - } - auto id = m_data->BeginUndoActions(); - m_symbolQueue->Process(); - m_data->EndBulkModifySymbols(); - m_data->ForgetUndoActions(id); - } - delete m_symbolQueue; + return (data->GetSectionByName("__objc_classlist") || data->GetSectionByName("__objc_catlist") + || data->GetSectionByName("__objc_protolist")); } -void ObjCProcessor::AddRelocatedPointer(uint64_t location, uint64_t rewrite) +MachoObjCProcessor::MachoObjCProcessor(BinaryView* data, bool isBackedByDatabase) : + ObjCProcessor(data, "macho.objc", isBackedByDatabase) { - m_relocationPointerRewrites[location] = rewrite; } diff --git a/view/macho/objc.h b/view/macho/objc.h index 452cfed0..42aec8d0 100644 --- a/view/macho/objc.h +++ b/view/macho/objc.h @@ -1,219 +1,39 @@ #pragma once #include <binaryninjaapi.h> +#include <objectivec/objc.h> namespace BinaryNinja { - // This set of structs is based on the objc4 source, - // however pointers have been replaced with view_ptr_t + class MachoObjCReader : public ObjCReader { + private: + BinaryReader m_reader; - // Used for pointers within BinaryView, primarily to make it far more clear in typedefs - // whether the size of a field can vary between architectures. - // These should _not_ be used in sizeof or direct Read() calls. - typedef uint64_t view_ptr_t; - - typedef struct { - view_ptr_t name; - view_ptr_t types; - view_ptr_t imp; - } method_t; - typedef struct { - uint32_t name; - uint32_t types; - uint32_t imp; - } method_entry_t; - typedef struct { - view_ptr_t offset; - view_ptr_t name; - view_ptr_t type; - uint32_t alignmentRaw; - uint32_t size; - } ivar_t; - typedef struct { - view_ptr_t name; - view_ptr_t attributes; - } property_t; - typedef struct { - uint32_t entsizeAndFlags; - uint32_t count; - } method_list_t; - typedef struct { - uint32_t entsizeAndFlags; - uint32_t count; - } ivar_list_t; - typedef struct { - uint32_t entsizeAndFlags; - uint32_t count; - } property_list_t; - typedef struct { - uint64_t count; - } protocol_list_t; - typedef struct { - view_ptr_t isa; - view_ptr_t mangledName; - view_ptr_t protocols; - view_ptr_t instanceMethods; - view_ptr_t classMethods; - view_ptr_t optionalInstanceMethods; - view_ptr_t optionalClassMethods; - view_ptr_t instanceProperties; - uint32_t size; - uint32_t flags; - } protocol_t; - typedef struct { - uint32_t flags; - uint32_t instanceStart; - uint32_t instanceSize; - uint32_t reserved; - view_ptr_t ivarLayout; - view_ptr_t name; - view_ptr_t baseMethods; - view_ptr_t baseProtocols; - view_ptr_t ivars; - view_ptr_t weakIvarLayout; - view_ptr_t baseProperties; - } class_ro_t; - typedef struct { - view_ptr_t isa; - view_ptr_t super; - view_ptr_t cache; - view_ptr_t vtable; - view_ptr_t data; - } class_t; - typedef struct { - view_ptr_t name; - view_ptr_t cls; - view_ptr_t instanceMethods; - view_ptr_t classMethods; - view_ptr_t protocols; - view_ptr_t instanceProperties; - } category_t; - typedef struct { - view_ptr_t receiver; - view_ptr_t current_class; - } objc_super2; - typedef struct { - view_ptr_t imp; - view_ptr_t sel; - } message_ref_t; - - struct Method { - std::string name; - std::string types; - view_ptr_t imp; - }; - - struct Ivar { - uint32_t offset; - std::string name; - std::string type; - uint32_t alignment; - uint32_t size; - }; - - struct Property { - std::string name; - std::string attributes; - }; - - struct ClassBase { - std::map<uint64_t, Method> methodList; - std::map<uint64_t, Ivar> ivarList; - }; - - struct Class { - std::string name; - ClassBase instanceClass; - ClassBase metaClass; - - // Loaded by type processing - QualifiedName associatedName; - }; - - class Protocol { public: - std::string name; - std::vector<QualifiedName> protocols; - ClassBase instanceMethods; - ClassBase classMethods; - ClassBase optionalInstanceMethods; - ClassBase optionalClassMethods; - }; + void Read(void* dest, size_t len) override; + std::string ReadCString() override; + uint8_t Read8() override; + uint16_t Read16() override; + uint32_t Read32() override; + uint64_t Read64() override; + int8_t ReadS8() override; + int16_t ReadS16() override; + int32_t ReadS32() override; + int64_t ReadS64() override; + uint64_t ReadPointer() override; + uint64_t GetOffset() const override; + void Seek(uint64_t offset) override; + void SeekRelative(int64_t offset) override; - struct QualifiedNameOrType { - BinaryNinja::Ref<BinaryNinja::Type> type = nullptr; - BinaryNinja::QualifiedName name; - size_t ptrCount = 0; + MachoObjCReader(BinaryView* data); }; - class ObjCProcessor { - struct Types { - // QualifiedName relativePtr; - // QualifiedName id; - // QualifiedName sel; - // QualifiedName BOOL; - // QualifiedName nsInteger; - // QualifiedName nsuInteger; - // QualifiedName cgFloat; - QualifiedName cfStringFlag; - QualifiedName cfString; - QualifiedName cfStringUTF16; - QualifiedName imageInfoFlags; - QualifiedName imageInfoSwiftVersion; - QualifiedName imageInfo; - QualifiedName methodEntry; - QualifiedName method; - QualifiedName methodList; - QualifiedName classRO; - QualifiedName cls; - QualifiedName category; - QualifiedName protocol; - QualifiedName protocolList; - QualifiedName ivar; - QualifiedName ivarList; - } m_typeNames; - - bool m_isBackedByDatabase; - - BinaryView* m_data; - SymbolQueue* m_symbolQueue = nullptr; - Ref<Logger> m_logger; - std::map<uint64_t, Class> m_classes; - std::map<uint64_t, Class> m_categories; - std::map<uint64_t, Protocol> m_protocols; - std::unordered_map<uint64_t, std::string> m_selectorCache; - std::unordered_map<uint64_t, Method> m_localMethods; - - // Required for workflow_objc type heuristics, should be removed when that is no longer a thing. - std::map<uint64_t, std::string> m_selRefToName; - std::map<uint64_t, std::vector<uint64_t>> m_selRefToImplementations; - std::map<uint64_t, std::vector<uint64_t>> m_selToImplementations; - // -- - - uint64_t ReadPointerAccountingForRelocations(BinaryReader* reader); - std::unordered_map<uint64_t, uint64_t> m_relocationPointerRewrites; - - static Ref<Metadata> SerializeMethod(uint64_t loc, const Method& method); - static Ref<Metadata> SerializeClass(uint64_t loc, const Class& cls); - - Ref<Metadata> SerializeMetadata(); - std::vector<QualifiedNameOrType> 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> type, const std::string& name, uint64_t addr, bool deferred); - void ReadIvarList(BinaryReader* reader, ClassBase& cls, std::string name, view_ptr_t start); - void ReadMethodList(BinaryReader* reader, ClassBase& cls, std::string name, view_ptr_t start); - void LoadClasses(BinaryReader* reader, Ref<Section> listSection); - void LoadCategories(BinaryReader* reader, Ref<Section> listSection); - void LoadProtocols(BinaryReader* reader, Ref<Section> listSection); - void GenerateClassTypes(); - bool ApplyMethodType(Class& cls, Method& method, bool isInstanceMethod); - void ApplyMethodTypes(Class& cls); - void PostProcessObjCSections(BinaryReader* reader); + class MachoObjCProcessor : public ObjCProcessor { + std::shared_ptr<ObjCReader> GetReader() override; + public: + MachoObjCProcessor(BinaryView* data, bool isBackedByDatabase); + static bool ViewHasObjCMetadata(BinaryView* data); - ObjCProcessor(BinaryView* data, bool isBackedByDatabase); - void ProcessObjCData(); - void ProcessCFStrings(); - void AddRelocatedPointer(uint64_t location, uint64_t rewrite); }; } diff --git a/view/sharedcache/core/CMakeLists.txt b/view/sharedcache/core/CMakeLists.txt index 22c7ea28..03766920 100644 --- a/view/sharedcache/core/CMakeLists.txt +++ b/view/sharedcache/core/CMakeLists.txt @@ -8,11 +8,8 @@ if((NOT BN_API_PATH) AND (NOT BN_INTERNAL_BUILD)) message(FATAL_ERROR "Provide path to Binary Ninja API source in BN_API_PATH") endif() endif() -file(GLOB COMMON_SOURCES - *.cpp - *.h - ) -set(SOURCES ${COMMON_SOURCES}) + +file(GLOB SOURCES *.cpp *.h ../../../objectivec/*) add_library(sharedcachecore OBJECT ${SOURCES}) diff --git a/view/sharedcache/core/ObjC.cpp b/view/sharedcache/core/ObjC.cpp index fa0e603c..c763b321 100644 --- a/view/sharedcache/core/ObjC.cpp +++ b/view/sharedcache/core/ObjC.cpp @@ -1,1540 +1,116 @@ #include "ObjC.h" -#include "inttypes.h" -#include "rapidjson/rapidjson.h" -#include "rapidjson/document.h" -#include "rapidjson/stringbuffer.h" -#include "rapidjson/prettywriter.h" using namespace BinaryNinja; using namespace DSCObjC; using namespace SharedCacheCore; -Ref<Metadata> DSCObjCProcessor::SerializeMethod(uint64_t loc, const Method& method) +DSCObjCReader::DSCObjCReader(SharedCache* cache, size_t addressSize) : + m_reader(VMReader(cache->GetVMMap())), m_addressSize(addressSize) { - std::map<std::string, Ref<Metadata>> methodMeta; - - methodMeta["loc"] = new Metadata(loc); - methodMeta["name"] = new Metadata(method.name); - methodMeta["types"] = new Metadata(method.types); - methodMeta["imp"] = new Metadata(method.imp); - - return new Metadata(methodMeta); } - -Ref<Metadata> DSCObjCProcessor::SerializeClass(uint64_t loc, const Class& cls) +void DSCObjCReader::Read(void* dest, size_t len) { - std::map<std::string, Ref<Metadata>> clsMeta; - - clsMeta["loc"] = new Metadata(loc); - clsMeta["name"] = new Metadata(cls.name); - clsMeta["typeName"] = new Metadata(cls.associatedName.GetString()); - - std::vector<uint64_t> instanceMethods; - std::vector<uint64_t> classMethods; - instanceMethods.reserve(cls.instanceClass.methodList.size()); - classMethods.reserve(cls.metaClass.methodList.size()); - for (const auto& [location, _] : cls.instanceClass.methodList) - instanceMethods.push_back(location); - - clsMeta["instanceMethods"] = new Metadata(instanceMethods); - clsMeta["classMethods"] = new Metadata(classMethods); - - return new Metadata(clsMeta); + m_reader.Read(dest, len); } -Ref<Metadata> DSCObjCProcessor::SerializeMetadata() +std::string DSCObjCReader::ReadCString() { - std::map<std::string, Ref<Metadata>> viewMeta; - viewMeta["version"] = new Metadata((uint64_t)1); - - std::vector<Ref<Metadata>> classes; - classes.reserve(m_classes.size()); - std::vector<Ref<Metadata>> categories; - categories.reserve(m_categories.size()); - std::vector<Ref<Metadata>> methods; - methods.reserve(m_localMethods.size()); - - for (const auto& [clsLoc, cls] : m_classes) - classes.push_back(SerializeClass(clsLoc, cls)); - viewMeta["classes"] = new Metadata(classes); - for (const auto& [catLoc, cat] : m_categories) - categories.push_back(SerializeClass(catLoc, cat)); - viewMeta["categories"] = new Metadata(categories); - for (const auto& [methodLoc, method] : m_localMethods) - methods.push_back(SerializeMethod(methodLoc, method)); - viewMeta["methods"] = new Metadata(methods); - - // Required for workflow_objc type guessing, should be removed when that is no longer a thing. - std::vector<Ref<Metadata>> selRefToImps; - selRefToImps.reserve(m_selRefToImplementations.size()); - for (const auto& [selRef, imps] : m_selRefToImplementations) - { - std::vector<Ref<Metadata>> mapBase = {new Metadata(selRef), new Metadata(imps)}; - Ref<Metadata> mapObject = new Metadata(mapBase); - selRefToImps.push_back(mapObject); - } - viewMeta["selRefImplementations"] = new Metadata(selRefToImps); - - std::vector<Ref<Metadata>> selToImps; - selToImps.reserve(m_selToImplementations.size()); - for (const auto& [selRef, imps] : m_selToImplementations) - { - std::vector<Ref<Metadata>> mapBase = {new Metadata(selRef), new Metadata(imps)}; - Ref<Metadata> mapObject = new Metadata(mapBase); - selToImps.push_back(mapObject); - } - viewMeta["selImplementations"] = new Metadata(selToImps); - - std::vector<Ref<Metadata>> selRefToName; - selRefToName.reserve(m_selRefToName.size()); - for (const auto& [selRef, name] : m_selRefToName) - { - std::vector<Ref<Metadata>> mapBase = {new Metadata(selRef), new Metadata(name)}; - Ref<Metadata> mapObject = new Metadata(mapBase); - selRefToName.push_back(mapObject); - } - viewMeta["selRefToName"] = new Metadata(selRefToName); - // --- - - - return new Metadata(viewMeta); + return m_reader.ReadCString(m_reader.GetOffset()); } -std::vector<DSCObjC::QualifiedNameOrType> DSCObjCProcessor::ParseEncodedType(const std::string& encodedType) +uint8_t DSCObjCReader::Read8() { - std::vector<QualifiedNameOrType> result; - int pointerDepth = 0; - - bool readingNamedType = false; - std::string namedType; - int readingStructDepth = 0; - std::string structType; - char last; - - for (char c : encodedType) - { - if (readingNamedType && c != '"') - { - namedType.push_back(c); - last = c; - continue; - } - else if (readingStructDepth > 0 && c != '{' && c != '}') - { - structType.push_back(c); - last = c; - continue; - } - - if (std::isdigit(c)) - continue; - - QualifiedNameOrType nameOrType; - std::string qualifiedName; - - switch (c) - { - case '^': - pointerDepth++; - last = c; - continue; - - case '"': - if (!readingNamedType) - { - readingNamedType = true; - if (last == '@') - result.pop_back(); // We added an 'id' in the last cycle, remove it - last = c; - continue; - } - else - { - readingNamedType = false; - nameOrType.name = QualifiedName(namedType); - nameOrType.ptrCount = 1; - break; - } - case '{': - readingStructDepth++; - last = c; - continue; - case '}': - readingStructDepth--; - if (readingStructDepth < 0) - return {}; // seriously malformed type. - - if (readingStructDepth == 0) - { - // TODO: Emit real struct types - nameOrType.type = Type::PointerType(m_data->GetAddressSize(), Type::VoidType()); - break; - } - last = c; - continue; - case 'v': - nameOrType.type = Type::VoidType(); - break; - case 'c': - nameOrType.type = Type::IntegerType(1, true); - break; - case 'A': - case 'C': - nameOrType.type = Type::IntegerType(1, false); - break; - case 's': - nameOrType.type = Type::IntegerType(2, true); - break; - case 'S': - nameOrType.type = Type::IntegerType(1, false); - break; - case 'i': - nameOrType.type = Type::IntegerType(4, true); - break; - case 'I': - nameOrType.type = Type::IntegerType(4, false); - break; - case 'l': - nameOrType.type = Type::IntegerType(8, true); - break; - case 'L': - nameOrType.type = Type::IntegerType(8, true); - break; - case 'f': - nameOrType.type = Type::IntegerType(4, true); - break; - case 'b': - case 'B': - nameOrType.type = Type::BoolType(); - break; - case 'q': - qualifiedName = "NSInteger"; - break; - case 'Q': - qualifiedName = "NSUInteger"; - break; - case 'd': - qualifiedName = "CGFloat"; - break; - case '*': - nameOrType.type = Type::PointerType(m_data->GetAddressSize(), Type::IntegerType(1, true)); - break; - case '@': - qualifiedName = "id"; - // There can be a type after this, like @"NSString", that overrides this - // The handler for " will catch it and drop this "id" entry. - break; - case ':': - qualifiedName = "SEL"; - break; - case '#': - qualifiedName = "objc_class_t"; - break; - case '?': - case 'T': - nameOrType.type = Type::PointerType(8, Type::VoidType()); - break; - default: - // BNLogWarn("Unknown type specifier %c", c); - last = c; - continue; - } - - while (pointerDepth) - { - if (nameOrType.type) - nameOrType.type = Type::PointerType(8, nameOrType.type); - else - nameOrType.ptrCount++; - - pointerDepth--; - } - - if (!qualifiedName.empty()) - nameOrType.name = QualifiedName(qualifiedName); - - if (nameOrType.type == nullptr && nameOrType.name.IsEmpty()) - { - nameOrType.type = Type::VoidType(); - } - - result.push_back(nameOrType); - last = c; - } - - return result; + return m_reader.Read8(); } -void DSCObjCProcessor::DefineObjCSymbol( - BNSymbolType type, QualifiedName typeName, const std::string& name, uint64_t addr, bool deferred) +uint16_t DSCObjCReader::Read16() { - DefineObjCSymbol(type, m_data->GetTypeByName(typeName), name, addr, deferred); + return m_reader.Read16(); } -void DSCObjCProcessor::DefineObjCSymbol( - BNSymbolType type, Ref<Type> typeRef, const std::string& name, uint64_t addr, bool deferred) +uint32_t DSCObjCReader::Read32() { - if (name.size() == 0 || addr == 0) - return; - - auto process = [=]() { - NameSpace nameSpace = m_data->GetInternalNameSpace(); - if (type == ExternalSymbol) - { - nameSpace = m_data->GetExternalNameSpace(); - } - - std::string shortName = name; - std::string fullName = name; - - QualifiedName varName; - - return std::pair<Ref<Symbol>, Ref<Type>>( - new Symbol(type, shortName, fullName, name, addr, GlobalBinding, nameSpace), typeRef); - }; - - if (deferred) - { - m_symbolQueue->Append(process, [this, addr = addr](Symbol* symbol, Type* type) { - // Armv7/Thumb: This will rewrite the symbol's address. - // e.g. We pass in 0xc001, it will rewrite it to 0xc000 and create the function w/ the "thumb2" arch. - if (Ref<Symbol> existingSymbol = m_data->GetSymbolByAddress(addr)) - m_data->UndefineAutoSymbol(existingSymbol); - auto funcSym = m_data->DefineAutoSymbolAndVariableOrFunction(m_data->GetDefaultPlatform(), symbol, type); - if (funcSym->GetType() == FunctionSymbol) - { - uint64_t target = symbol->GetAddress(); - Ref<Platform> targetPlatform = - m_data->GetDefaultPlatform()->GetAssociatedPlatformByAddress(target); // rewrites target. - if (Ref<Function> targetFunction = m_data->GetAnalysisFunction(targetPlatform, target)) - { - if (!m_isBackedByDatabase) - targetFunction->SetUserType(type); - } - } - }); - return; - } - - if (Ref<Symbol> existingSymbol = m_data->GetSymbolByAddress(addr)) - m_data->UndefineAutoSymbol(existingSymbol); - auto result = process(); - auto sym = m_data->DefineAutoSymbolAndVariableOrFunction(m_data->GetDefaultPlatform(), result.first, result.second); - if (sym->GetType() == FunctionSymbol) - { - uint64_t target = result.first->GetAddress(); - Ref<Platform> targetPlatform = m_data->GetDefaultPlatform()->GetAssociatedPlatformByAddress(target); // rewrites - // target. - if (Ref<Function> targetFunction = m_data->GetAnalysisFunction(targetPlatform, target)) - { - if (!m_isBackedByDatabase) - targetFunction->SetUserType(result.second); - } - } + return m_reader.Read32(); } -void DSCObjCProcessor::LoadClasses(VMReader* reader, Ref<Section> classPtrSection) +uint64_t DSCObjCReader::Read64() { - if (!classPtrSection) - return; - auto size = classPtrSection->GetEnd() - classPtrSection->GetStart(); - if (size == 0) - return; - auto ptrSize = m_data->GetAddressSize(); - auto ptrCount = size / ptrSize; - - auto classPtrSectionStart = classPtrSection->GetStart(); - for (size_t i = 0; i < ptrCount; i++) - { - Class cls; - - view_ptr_t classPtr; - class_t clsStruct; - class_ro_t classRO; - - bool hasValidMetaClass = false; - bool hasValidMetaClassRO = false; - class_t metaClsStruct; - class_ro_t metaClassRO; - - view_ptr_t classPointerLocation = classPtrSectionStart + (i * m_data->GetAddressSize()); - reader->Seek(classPointerLocation); - - classPtr = ReadPointerAccountingForRelocations(reader); - reader->Seek(classPtr); - try - { - clsStruct.isa = ReadPointerAccountingForRelocations(reader); - clsStruct.super = reader->ReadPointer(); - clsStruct.cache = reader->ReadPointer(); - clsStruct.vtable = reader->ReadPointer(); - clsStruct.data = ReadPointerAccountingForRelocations(reader); - } - catch (...) - { - m_logger->LogError("Failed to read class data at 0x%llx pointed to by @ 0x%llx", reader->GetOffset(), - classPointerLocation); - continue; - } - if (clsStruct.data & 1) - { - m_logger->LogInfo("Skipping class at 0x%llx as it contains swift types", classPtr); - continue; - } - // unset first two bits - view_ptr_t classROPtr = clsStruct.data & ~3; - reader->Seek(classROPtr); - try - { - classRO.flags = reader->Read32(); - classRO.instanceStart = reader->Read32(); - classRO.instanceSize = reader->Read32(); - if (m_data->GetAddressSize() == 8) - classRO.reserved = reader->Read32(); - classRO.ivarLayout = ReadPointerAccountingForRelocations(reader); - classRO.name = ReadPointerAccountingForRelocations(reader); - classRO.baseMethods = ReadPointerAccountingForRelocations(reader); - classRO.baseProtocols = ReadPointerAccountingForRelocations(reader); - classRO.ivars = ReadPointerAccountingForRelocations(reader); - classRO.weakIvarLayout = ReadPointerAccountingForRelocations(reader); - classRO.baseProperties = ReadPointerAccountingForRelocations(reader); - } - catch (...) - { - m_logger->LogError("Failed to read class RO data at 0x%llx. 0x%llx, objc_class_t @ 0x%llx", - reader->GetOffset(), classPointerLocation, classROPtr); - continue; - } - - auto namePtr = classRO.name; - - std::string name; - - reader->Seek(namePtr); - try - { - name = reader->ReadCString(namePtr); - } - catch (...) - { - m_logger->LogWarn( - "Failed to read class name at 0x%llx. Class has been given the placeholder name \"0x%llx\" ", namePtr, - classPtr); - char hexString[9]; - hexString[8] = 0; - snprintf(hexString, sizeof(hexString), "%llx", classPtr); - name = "0x" + std::string(hexString); - } - - cls.name = name; - - DefineObjCSymbol(BNSymbolType::DataSymbol, - Type::PointerType(m_data->GetAddressSize(), m_data->GetTypeByName(m_typeNames.cls)), "clsPtr_" + name, - classPointerLocation, true); - DefineObjCSymbol(BNSymbolType::DataSymbol, m_typeNames.cls, "cls_" + name, classPtr, true); - DefineObjCSymbol(BNSymbolType::DataSymbol, m_typeNames.classRO, "cls_ro_" + name, classROPtr, true); - DefineObjCSymbol(BNSymbolType::DataSymbol, Type::ArrayType(Type::IntegerType(1, true), name.size() + 1), - "clsName_" + name, classRO.name, true); - if (0 && classRO.baseProtocols) - { - DefineObjCSymbol(BNSymbolType::DataSymbol, Type::NamedType(m_data, m_typeNames.protocolList), - "clsProtocols_" + name, classRO.baseProtocols, true); - reader->Seek(classRO.baseProtocols); - uint32_t count = reader->Read64(); - view_ptr_t addr = reader->GetOffset(); - for (uint32_t j = 0; j < count; j++) - { - m_data->DefineDataVariable( - addr, Type::PointerType(ptrSize, Type::NamedType(m_data, m_typeNames.protocol))); - addr += ptrSize; - } - } - - if (clsStruct.isa) - { - reader->Seek(clsStruct.isa); - try - { - metaClsStruct.isa = ReadPointerAccountingForRelocations(reader); - metaClsStruct.super = reader->ReadPointer(); - metaClsStruct.cache = reader->ReadPointer(); - metaClsStruct.vtable = reader->ReadPointer(); - metaClsStruct.data = ReadPointerAccountingForRelocations(reader) & ~1; - DefineObjCSymbol(BNSymbolType::DataSymbol, m_typeNames.cls, "metacls_" + name, clsStruct.isa, true); - hasValidMetaClass = true; - } - catch (...) - { - m_logger->LogWarn("Failed to read metaclass data at 0x%llx pointed to by objc_class_t @ 0x%llx", - reader->GetOffset(), classPtr); - } - } - if (hasValidMetaClass && (metaClsStruct.data & 1)) - { - m_logger->LogInfo("Skipping metaclass at 0x%llx as it contains swift types", classPtr); - hasValidMetaClass = false; - } - if (hasValidMetaClass) - { - reader->Seek(metaClsStruct.data); - try - { - metaClassRO.flags = reader->Read32(); - metaClassRO.instanceStart = reader->Read32(); - metaClassRO.instanceSize = reader->Read32(); - if (m_data->GetAddressSize() == 8) - metaClassRO.reserved = reader->Read32(); - metaClassRO.ivarLayout = ReadPointerAccountingForRelocations(reader); - metaClassRO.name = ReadPointerAccountingForRelocations(reader); - metaClassRO.baseMethods = ReadPointerAccountingForRelocations(reader); - metaClassRO.baseProtocols = ReadPointerAccountingForRelocations(reader); - metaClassRO.ivars = ReadPointerAccountingForRelocations(reader); - metaClassRO.weakIvarLayout = ReadPointerAccountingForRelocations(reader); - metaClassRO.baseProperties = ReadPointerAccountingForRelocations(reader); - DefineObjCSymbol( - BNSymbolType::DataSymbol, m_typeNames.classRO, "metacls_ro_" + name, metaClsStruct.data, true); - hasValidMetaClassRO = true; - } - catch (...) - { - m_logger->LogWarn("Failed to read metaclass RO data at 0x%llx pointed to by meta objc_class_t @ 0x%llx", - reader->GetOffset(), clsStruct.isa); - } - } - - if (classRO.baseMethods) - { - try - { - ReadMethodList(reader, cls.instanceClass, name, classRO.baseMethods); - } - catch (...) - { - m_logger->LogError("Failed to read the method list for class pointed to by 0x%llx", clsStruct.data); - } - } - if (hasValidMetaClassRO && metaClassRO.baseMethods) - { - try - { - ReadMethodList(reader, cls.metaClass, name, metaClassRO.baseMethods); - } - catch (...) - { - m_logger->LogError("Failed to read the method list for metaclass pointed to by 0x%llx", clsStruct.data); - } - } - - if (classRO.ivars) - { - try - { - ReadIvarList(reader, cls.instanceClass, name, classRO.ivars); - } - catch (...) - { - m_logger->LogError("Failed to process ivars for class at 0x%llx", clsStruct.data); - } - } - m_classes[classPtr] = cls; - } -} - -void DSCObjCProcessor::LoadCategories(VMReader* reader, Ref<Section> classPtrSection) -{ - if (!classPtrSection) - return; - auto size = classPtrSection->GetEnd() - classPtrSection->GetStart(); - if (size == 0) - return; - auto ptrSize = m_data->GetAddressSize(); - - auto classPtrSectionStart = classPtrSection->GetStart(); - auto classPtrSectionEnd = classPtrSection->GetEnd(); - - auto catType = Type::NamedType(m_data, m_typeNames.category); - auto ptrType = Type::PointerType(m_data->GetDefaultArchitecture(), catType); - for (size_t i = classPtrSectionStart; i < classPtrSectionEnd; i += ptrSize) - { - Class category; - category_t cat; - - reader->Seek(i); - auto catLocation = ReadPointerAccountingForRelocations(reader); - reader->Seek(catLocation); - - try - { - cat.name = ReadPointerAccountingForRelocations(reader); - cat.cls = ReadPointerAccountingForRelocations(reader); - cat.instanceMethods = ReadPointerAccountingForRelocations(reader); - cat.classMethods = ReadPointerAccountingForRelocations(reader); - cat.protocols = ReadPointerAccountingForRelocations(reader); - cat.instanceProperties = ReadPointerAccountingForRelocations(reader); - } - catch (...) - { - m_logger->LogError("Failed to read category pointed to by 0x%llx", i); - continue; - } - - std::string categoryAdditionsName; - std::string categoryBaseClassName; - - if (const auto& it = m_classes.find(cat.cls); it != m_classes.end()) - { - categoryBaseClassName = it->second.name; - category.associatedName = it->second.associatedName; - } - else if (auto symbol = m_data->GetSymbolByAddress(catLocation + m_data->GetAddressSize())) - { - if (symbol->GetType() == ImportedDataSymbol || symbol->GetType() == ImportAddressSymbol) - { - const auto& symbolName = symbol->GetFullName(); - if (symbolName.size() > 14 && symbolName.rfind("_OBJC_CLASS_$_", 0) == 0) - categoryBaseClassName = symbolName.substr(14, symbolName.size() - 14); - } - } - if (categoryBaseClassName.empty()) - { - m_logger->LogError( - "Failed to determine base classname for category at 0x%llx. Using base address as stand-in classname", - catLocation); - categoryBaseClassName = std::to_string(catLocation); - } - try - { - reader->Seek(cat.name); - categoryAdditionsName = reader->ReadCString(cat.name); - } - catch (...) - { - m_logger->LogError( - "Failed to read category name for category at 0x%llx. Using base address as stand-in category name", - catLocation); - categoryAdditionsName = std::to_string(catLocation); - } - category.name = categoryBaseClassName + " (" + categoryAdditionsName + ")"; - DefineObjCSymbol(BNSymbolType::DataSymbol, ptrType, "categoryPtr_" + category.name, i, true); - DefineObjCSymbol(BNSymbolType::DataSymbol, catType, "category_" + category.name, catLocation, true); - - if (cat.instanceMethods) - { - try - { - ReadMethodList(reader, category.instanceClass, category.name, cat.instanceMethods); - } - catch (...) - { - m_logger->LogError( - "Failed to read the instance method list for category pointed to by 0x%llx", catLocation); - } - } - if (cat.classMethods) - { - try - { - ReadMethodList(reader, category.metaClass, category.name, cat.classMethods); - } - catch (...) - { - m_logger->LogError( - "Failed to read the class method list for category pointed to by 0x%llx", catLocation); - } - } - m_categories[catLocation] = category; - } + return m_reader.Read64(); } -void DSCObjCProcessor::LoadProtocols(VMReader* reader, Ref<Section> listSection) +int8_t DSCObjCReader::ReadS8() { - if (!listSection) - return; - auto size = listSection->GetEnd() - listSection->GetStart(); - if (size == 0) - return; - auto ptrSize = m_data->GetAddressSize(); - - auto listSectionStart = listSection->GetStart(); - auto listSectionEnd = listSection->GetEnd(); - - auto protocolType = Type::NamedType(m_data, m_typeNames.protocol); - auto ptrType = Type::PointerType(m_data->GetDefaultArchitecture(), protocolType); - for (size_t i = listSectionStart; i < listSectionEnd; i += ptrSize) - { - protocol_t protocol; - reader->Seek(i); - auto protocolLocation = ReadPointerAccountingForRelocations(reader); - reader->Seek(protocolLocation); - - try - { - protocol.isa = ReadPointerAccountingForRelocations(reader); - protocol.mangledName = ReadPointerAccountingForRelocations(reader); - protocol.protocols = ReadPointerAccountingForRelocations(reader); - protocol.instanceMethods = ReadPointerAccountingForRelocations(reader); - protocol.classMethods = ReadPointerAccountingForRelocations(reader); - protocol.optionalInstanceMethods = ReadPointerAccountingForRelocations(reader); - protocol.optionalClassMethods = ReadPointerAccountingForRelocations(reader); - protocol.instanceProperties = ReadPointerAccountingForRelocations(reader); - } - catch (...) - { - m_logger->LogError("Failed to read protocol pointed to by 0x%llx", i); - continue; - } - - std::string protocolName; - try - { - reader->Seek(protocol.mangledName); - protocolName = reader->ReadCString(protocol.mangledName); - DefineObjCSymbol(BNSymbolType::DataSymbol, - Type::ArrayType(Type::IntegerType(1, true), protocolName.size() + 1), "protocolName_" + protocolName, - protocol.mangledName, true); - } - catch (...) - { - m_logger->LogError( - "Failed to read protocol name for protocol at 0x%llx. Using base address as stand-in protocol name", - protocolLocation); - protocolName = std::to_string(protocolLocation); - } - - Protocol protocolClass; - protocolClass.name = protocolName; - DefineObjCSymbol(BNSymbolType::DataSymbol, ptrType, "protocolPtr_" + protocolName, i, true); - DefineObjCSymbol(BNSymbolType::DataSymbol, protocolType, "protocol_" + protocolName, protocolLocation, true); - if (protocol.protocols) - { - DefineObjCSymbol(BNSymbolType::DataSymbol, Type::NamedType(m_data, m_typeNames.protocolList), - "protoProtocols_" + protocolName, protocol.protocols, true); - reader->Seek(protocol.protocols); - uint32_t count = reader->Read64(); - view_ptr_t addr = reader->GetOffset(); - for (uint32_t j = 0; j < count; j++) - { - m_data->DefineDataVariable( - addr, Type::PointerType(ptrSize, Type::NamedType(m_data, m_typeNames.protocol))); - addr += ptrSize; - } - } - - if (protocol.instanceMethods) - { - try - { - ReadMethodList(reader, protocolClass.instanceMethods, protocolName, protocol.instanceMethods); - } - catch (...) - { - m_logger->LogError( - "Failed to read the instance method list for protocol pointed to by 0x%llx", protocolLocation); - } - } - if (protocol.classMethods) - { - try - { - ReadMethodList(reader, protocolClass.classMethods, protocolName, protocol.classMethods); - } - catch (...) - { - m_logger->LogError( - "Failed to read the class method list for protocol pointed to by 0x%llx", protocolLocation); - } - } - if (protocol.optionalInstanceMethods) - { - try - { - ReadMethodList( - reader, protocolClass.optionalInstanceMethods, protocolName, protocol.optionalInstanceMethods); - } - catch (...) - { - m_logger->LogError("Failed to read the optional instance method list for protocol pointed to by 0x%llx", - protocolLocation); - } - } - if (protocol.optionalClassMethods) - { - try - { - ReadMethodList(reader, protocolClass.optionalClassMethods, protocolName, protocol.optionalClassMethods); - } - catch (...) - { - m_logger->LogError("Failed to read the optional class method list for protocol pointed to by 0x%llx", - protocolLocation); - } - } - m_protocols[protocolLocation] = protocolClass; - } + return m_reader.ReadS8(); } -void DSCObjCProcessor::ReadListOfMethodLists(VMReader* reader, ClassBase& cls, std::string_view name, view_ptr_t start) +int16_t DSCObjCReader::ReadS16() { - 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))); - } + return m_reader.ReadS16(); } -void DSCObjCProcessor::ReadMethodList(VMReader* reader, ClassBase& cls, std::string_view name, view_ptr_t start) +int32_t DSCObjCReader::ReadS32() { - // 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_" + std::string(name), start, true); - - for (unsigned i = 0; i < head.count; i++) - { - try - { - Method method; - auto cursor = start + sizeof(method_list_t) + (i * methodSize); - reader->Seek(cursor); - method_t meth; - // workflow_objc support - uint64_t selRefAddr = 0; - uint64_t selAddr = 0; - // -- - if (relativeOffsets) - { - 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 - { - meth.name = ReadPointerAccountingForRelocations(reader); - meth.types = ReadPointerAccountingForRelocations(reader); - meth.imp = ReadPointerAccountingForRelocations(reader); - } - if (!relativeOffsets || directSelectors) - { - selAddr = meth.name; - method.name = reader->ReadCString(meth.name); - reader->Seek(meth.types); - method.types = reader->ReadCString(meth.types); - DefineObjCSymbol(DataSymbol, Type::ArrayType(Type::IntegerType(1, true), method.name.size() + 1), - "sel_" + method.name, meth.name, true); - DefineObjCSymbol(DataSymbol, Type::ArrayType(Type::IntegerType(1, true), method.types.size() + 1), - "selTypes_" + method.name, meth.types, true); - } - else - { - std::string sel; - view_ptr_t selRef; - reader->Seek(meth.name); - selRefAddr = meth.name; - selRef = ReadPointerAccountingForRelocations(reader); - reader->Seek(meth.types); - method.types = reader->ReadCString(meth.types); - selAddr = selRef; - if (const auto& it = m_selectorCache.find(selRef); it != m_selectorCache.end()) - method.name = it->second; - else - { - reader->Seek(selRef); - method.name = reader->ReadCString(selRef); - m_selectorCache[selRef] = method.name; - } - auto selType = Type::ArrayType(Type::IntegerType(1, true), method.name.size() + 1); - DefineObjCSymbol(DataSymbol, selType, "sel_" + method.name, selRef, true); - DefineObjCSymbol(DataSymbol, Type::ArrayType(Type::IntegerType(1, true), method.types.size() + 1), - "selTypes_" + method.name, meth.types, true); - DefineObjCSymbol(DataSymbol, Type::PointerType(m_data->GetAddressSize(), selType), - "selRef_" + method.name, meth.name, true); - } - // workflow objc support - if (selAddr) - m_selToImplementations[selAddr].push_back(meth.imp); - if (selRefAddr) - m_selRefToImplementations[selRefAddr].push_back(meth.imp); - // -- - - DefineObjCSymbol(DataSymbol, relativeOffsets ? m_typeNames.methodEntry : m_typeNames.method, - "method_" + method.name, cursor, true); - method.imp = meth.imp; - cls.methodList[cursor] = method; - m_localMethods[cursor] = method; - } - catch (...) - { - // m_logger->LogError("Failed to process a method at offset 0x%llx", start + sizeof(method_list_t) + (i * methodSize)); - } - } + return m_reader.ReadS32(); } -void DSCObjCProcessor::ReadIvarList(VMReader* reader, ClassBase& cls, std::string_view name, view_ptr_t start) +int64_t DSCObjCReader::ReadS64() { - 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_" + 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); - return; - } - for (unsigned i = 0; i < head.count; i++) - { - try - { - Ivar ivar; - ivar_t ivarStruct; - uint64_t cursor = start + (sizeof(ivar_list_t)) + (i * ((addressSize * 3) + 8)); - reader->Seek(cursor); - ivarStruct.offset = ReadPointerAccountingForRelocations(reader); - ivarStruct.name = ReadPointerAccountingForRelocations(reader); - ivarStruct.type = ReadPointerAccountingForRelocations(reader); - ivarStruct.alignmentRaw = reader->Read32(); - ivarStruct.size = reader->Read32(); - - reader->Seek(ivarStruct.offset); - ivar.offset = reader->Read32(); - reader->Seek(ivarStruct.name); - ivar.name = reader->ReadCString(ivarStruct.name); - reader->Seek(ivarStruct.type); - ivar.type = reader->ReadCString(ivarStruct.type); - - DefineObjCSymbol(DataSymbol, m_typeNames.ivar, "ivar_" + ivar.name, cursor, true); - DefineObjCSymbol(DataSymbol, Type::ArrayType(Type::IntegerType(1, true), ivar.name.size() + 1), - "ivarName_" + ivar.name, ivarStruct.name, true); - DefineObjCSymbol(DataSymbol, Type::ArrayType(Type::IntegerType(1, true), ivar.type.size() + 1), - "ivarType_" + ivar.name, ivarStruct.type, true); - - cls.ivarList[cursor] = ivar; - } - catch (...) - { - m_logger->LogError("Failed to process an ivar at offset 0x%llx", - start + (sizeof(ivar_list_t)) + (i * ((addressSize * 3) + 8))); - } - } + return m_reader.ReadS64(); } - -std::pair<QualifiedName, Ref<Type>> finalizeStructureBuilder( - Ref<BinaryView> m_data, StructureBuilder sb, std::string name) +uint64_t DSCObjCReader::ReadPointer() { - auto classTypeStruct = sb.Finalize(); - - QualifiedName classTypeName(name); - auto classTypeId = Type::GenerateAutoTypeId("objc", classTypeName); - auto classType = Type::StructureType(classTypeStruct); - auto classQualName = m_data->DefineType(classTypeId, classTypeName, classType); - - return {classQualName, classType}; + return m_reader.ReadPointer(); } -std::pair<QualifiedName, Ref<Type>> finalizeEnumerationBuilder( - Ref<BinaryView> m_data, EnumerationBuilder eb, uint64_t size, QualifiedName name) +uint64_t DSCObjCReader::GetOffset() const { - auto enumTypeStruct = eb.Finalize(); - - auto enumTypeId = Type::GenerateAutoTypeId("objc", name); - auto enumType = Type::EnumerationType(enumTypeStruct, size); - auto enumQualName = m_data->DefineType(enumTypeId, name, enumType); - - return {enumQualName, enumType}; + return m_reader.GetOffset(); } -inline QualifiedName defineTypedef(Ref<BinaryView> m_data, const QualifiedName name, Ref<Type> type) +void DSCObjCReader::Seek(uint64_t offset) { - auto typeID = Type::GenerateAutoTypeId("objc", name); - m_data->DefineType(typeID, name, type); - return m_data->GetTypeNameById(typeID); + m_reader.Seek(offset); } -void DSCObjCProcessor::GenerateClassTypes() +void DSCObjCReader::SeekRelative(int64_t offset) { - for (auto& [_, cls] : m_classes) - { - QualifiedName typeName; - StructureBuilder classTypeBuilder; - bool failedToDecodeType = false; - for (const auto& [ivarLoc, ivar] : cls.instanceClass.ivarList) - { - auto encodedTypeList = ParseEncodedType(ivar.type); - if (encodedTypeList.empty()) - { - failedToDecodeType = true; - break; - } - auto encodedType = encodedTypeList.at(0); - - Ref<Type> type; - - if (encodedType.type) - type = encodedType.type; - else - { - type = Type::NamedType(encodedType.name, Type::PointerType(m_data->GetAddressSize(), Type::VoidType())); - for (size_t i = encodedType.ptrCount; i > 0; i--) - type = Type::PointerType(m_data->GetAddressSize(), type); - } - - if (!type) - type = Type::PointerType(m_data->GetAddressSize(), Type::VoidType()); - - classTypeBuilder.AddMemberAtOffset(type, ivar.name, ivar.offset); - } - if (failedToDecodeType) - continue; - auto classTypeStruct = classTypeBuilder.Finalize(); - QualifiedName classTypeName = cls.name; - std::string classTypeId = Type::GenerateAutoTypeId("objc", classTypeName); - Ref<Type> classType = Type::StructureType(classTypeStruct); - QualifiedName classQualName = m_data->DefineType(classTypeId, classTypeName, classType); - cls.associatedName = classTypeName; - } + m_reader.SeekRelative(offset); } -bool DSCObjCProcessor::ApplyMethodType(Class& cls, Method& method, bool isInstanceMethod) +VMReader& DSCObjCReader::GetVMReader() { - if (!method.imp || !m_data->IsValidOffset(method.imp)) { - return false; - } - - std::stringstream r(method.name); - - std::string token; - std::vector<std::string> selectorTokens; - while (std::getline(r, token, ':')) - selectorTokens.push_back(token); - - std::vector<QualifiedNameOrType> typeTokens = ParseEncodedType(method.types); - if (typeTokens.empty()) - return false; - - auto typeForQualifiedNameOrType = [this](QualifiedNameOrType nameOrType) { - Ref<Type> type; - - if (nameOrType.type) - { - type = nameOrType.type; - if (!type) - type = Type::PointerType(m_data->GetAddressSize(), Type::VoidType()); - } - else - { - type = Type::NamedType(nameOrType.name, Type::PointerType(m_data->GetAddressSize(), Type::VoidType())); - for (size_t i = nameOrType.ptrCount; i > 0; i--) - type = Type::PointerType(m_data->GetAddressSize(), type); - } - - return type; - }; - - BinaryNinja::QualifiedNameAndType nameAndType; - std::set<BinaryNinja::QualifiedName> typesAllowRedefinition; - - auto retType = typeForQualifiedNameOrType(typeTokens[0]); - - std::vector<BinaryNinja::FunctionParameter> params; - auto cc = m_data->GetDefaultPlatform()->GetDefaultCallingConvention(); - - params.push_back({"self", - cls.associatedName.IsEmpty() ? - Type::NamedType(m_data, {"id"}) : - Type::PointerType(m_data->GetAddressSize(), Type::NamedType(m_data, cls.associatedName)), - true, BinaryNinja::Variable()}); - - params.push_back({"sel", Type::NamedType(m_data, {"SEL"}), true, BinaryNinja::Variable()}); - - for (size_t i = 3; i < typeTokens.size(); i++) - { - std::string suffix; - - params.push_back({selectorTokens.size() > i - 3 ? selectorTokens[i - 3] : "arg", - typeForQualifiedNameOrType(typeTokens[i]), true, BinaryNinja::Variable()}); - } - - auto funcType = BinaryNinja::Type::FunctionType(retType, cc, params); - - // Search for the method's implementation function; apply the type if found. - std::string prefix = isInstanceMethod ? "-" : "+"; - auto name = prefix + "[" + cls.name + " " + method.name + "]"; - - DefineObjCSymbol(FunctionSymbol, funcType, name, method.imp, true); - - return true; + return m_reader; } -void DSCObjCProcessor::ApplyMethodTypes(Class& cls) +std::shared_ptr<ObjCReader> DSCObjCProcessor::GetReader() { - for (auto& [_, method] : cls.instanceClass.methodList) - { - ApplyMethodType(cls, method, true); - } - for (auto& [_, method] : cls.metaClass.methodList) - { - ApplyMethodType(cls, method, false); - } + return std::make_shared<DSCObjCReader>(m_cache, m_data->GetAddressSize()); } -void DSCObjCProcessor::PostProcessObjCSections(VMReader* reader, std::string baseName) +void DSCObjCProcessor::GetRelativeMethod(ObjCReader* reader, method_t& meth) { - auto ptrSize = m_data->GetAddressSize(); - if (auto imageInfo = m_data->GetSectionByName(baseName + "::__objc_imageinfo")) - { - auto start = imageInfo->GetStart(); - auto type = Type::NamedType(m_data, m_typeNames.imageInfo); - m_data->DefineDataVariable(start, type); - } - if (auto selrefs = m_data->GetSectionByName(baseName + "::__objc_selrefs")) - { - auto start = selrefs->GetStart(); - auto end = selrefs->GetEnd(); - auto type = Type::PointerType(ptrSize, Type::IntegerType(1, false)); - for (view_ptr_t i = start; i < end; i += ptrSize) - { - reader->Seek(i); - auto selLoc = ReadPointerAccountingForRelocations(reader); - std::string sel; - if (const auto& it = m_selectorCache.find(selLoc); it != m_selectorCache.end()) - sel = it->second; - else - { - reader->Seek(selLoc); - sel = reader->ReadCString(selLoc); - m_selectorCache[selLoc] = sel; - DefineObjCSymbol(DataSymbol, Type::ArrayType(Type::IntegerType(1, true), sel.size() + 1), "sel_" + sel, - selLoc, true); - } - DefineObjCSymbol(DataSymbol, type, "selRef_" + sel, i, true); - } - } - if (auto superRefs = m_data->GetSectionByName(baseName + "::__objc_classrefs")) - { - auto start = superRefs->GetStart(); - auto end = superRefs->GetEnd(); - auto type = Type::PointerType(ptrSize, Type::NamedType(m_data, m_typeNames.cls)); - for (view_ptr_t i = start; i < end; i += ptrSize) - { - reader->Seek(i); - auto clsLoc = ReadPointerAccountingForRelocations(reader); - if (const auto& it = m_classes.find(clsLoc); it != m_classes.end()) - { - auto& cls = it->second; - std::string name = cls.name; - if (!name.empty()) - DefineObjCSymbol(DataSymbol, type, "clsRef_" + name, i, true); - } - } - } - if (auto superRefs = m_data->GetSectionByName(baseName + "::__objc_superrefs")) - { - auto start = superRefs->GetStart(); - auto end = superRefs->GetEnd(); - auto type = Type::PointerType(ptrSize, Type::NamedType(m_data, m_typeNames.cls)); - for (view_ptr_t i = start; i < end; i += ptrSize) - { - reader->Seek(i); - auto clsLoc = ReadPointerAccountingForRelocations(reader); - if (const auto& it = m_classes.find(clsLoc); it != m_classes.end()) - { - auto& cls = it->second; - std::string name = cls.name; - if (!name.empty()) - DefineObjCSymbol(DataSymbol, type, "superRef_" + name, i, true); - } - } - } - if (auto protoRefs = m_data->GetSectionByName(baseName + "::__objc_protorefs")) + if (m_customRelativeMethodSelectorBase.has_value()) { - auto start = protoRefs->GetStart(); - auto end = protoRefs->GetEnd(); - auto type = Type::PointerType(ptrSize, Type::NamedType(m_data, m_typeNames.protocol)); - for (view_ptr_t i = start; i < end; i += ptrSize) - { - reader->Seek(i); - auto protoLoc = ReadPointerAccountingForRelocations(reader); - if (const auto& it = m_protocols.find(protoLoc); it != m_protocols.end()) - { - auto& proto = it->second; - std::string name = proto.name; - if (!name.empty()) - DefineObjCSymbol(DataSymbol, type, "protoRef_" + name, i, true); - } - } + meth.name = m_customRelativeMethodSelectorBase.value() + reader->ReadS32(); + meth.types = reader->GetOffset() + reader->ReadS32(); + meth.imp = reader->GetOffset() + reader->ReadS32(); } - if (auto ivars = m_data->GetSectionByName(baseName + "::__objc_ivar")) + else { - auto start = ivars->GetStart(); - auto end = ivars->GetEnd(); - auto ivarSectionEntryTypeBuilder = new TypeBuilder(Type::IntegerType(8, false)); - ivarSectionEntryTypeBuilder->SetConst(true); - auto type = ivarSectionEntryTypeBuilder->Finalize(); - for (view_ptr_t i = start; i < end; i += ptrSize) - { - m_data->DefineDataVariable(i, type); - } + ObjCProcessor::GetRelativeMethod(reader, meth); } } -uint64_t DSCObjCProcessor::ReadPointerAccountingForRelocations(VMReader* reader) +uint64_t DSCObjCProcessor::GetObjCRelativeMethodBaseAddress(ObjCReader* reader) { - if (auto it = m_relocationPointerRewrites.find(reader->GetOffset()); it != m_relocationPointerRewrites.end()) - { - reader->SeekRelative(m_data->GetAddressSize()); - return it->second; - } - // hack - return reader->ReadPointer(); -} - - -DSCObjCProcessor::DSCObjCProcessor(BinaryNinja::BinaryView* data, SharedCache* cache, bool isBackedByDatabase) : - m_isBackedByDatabase(isBackedByDatabase), m_data(data), m_cache(cache) -{ - m_logger = LogRegistry::GetLogger("SharedCache.ObjC", m_data->GetFile()->GetSessionId()); -} - -void DSCObjCProcessor::ProcessObjCData(std::shared_ptr<VM> vm, std::string baseName) -{ - m_symbolQueue = new SymbolQueue(); - auto addrSize = m_data->GetAddressSize(); - - // m_typeNames.relativePtr = ); - auto rptr_t = Type::NamedType(m_data, defineTypedef(m_data, {"rptr_t"}, Type::IntegerType(4, true))); - // - // m_typeNames.id = defineTypedef(m_data, {"id"}, Type::PointerType(addrSize, Type::VoidType())); - // m_typeNames.sel = defineTypedef(m_data, {"SEL"}, Type::PointerType(addrSize, Type::IntegerType(1, false))); - // - // m_typeNames.BOOL = defineTypedef(m_data, {"BOOL"}, Type::IntegerType(1, false)); - // m_typeNames.nsInteger = defineTypedef(m_data, {"NSInteger"}, Type::IntegerType(addrSize, true)); - // m_typeNames.nsuInteger = defineTypedef(m_data, {"NSUInteger"}, Type::IntegerType(addrSize, false)); - // m_typeNames.cgFloat = defineTypedef(m_data, {"CGFloat"}, Type::FloatType(addrSize)); - - Ref<Type> 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); - imageInfoFlagBuilder.AddMemberWithValue("SupportsGC", 1 << 1); - imageInfoFlagBuilder.AddMemberWithValue("RequiresGC", 1 << 2); - imageInfoFlagBuilder.AddMemberWithValue("OptimizedByDyld", 1 << 3); - imageInfoFlagBuilder.AddMemberWithValue("CorrectedSynthesize", 1 << 4); - imageInfoFlagBuilder.AddMemberWithValue("IsSimulated", 1 << 5); - imageInfoFlagBuilder.AddMemberWithValue("HasCategoryClassProperties", 1 << 6); - imageInfoFlagBuilder.AddMemberWithValue("OptimizedByDyldClosure", 1 << 7); - imageInfoFlagBuilder.AddMemberWithValue("SwiftUnstableVersionMask", 0xff << 8); - imageInfoFlagBuilder.AddMemberWithValue("SwiftStableVersionMask", 0xFFFF << 16); - auto imageInfoFlagType = finalizeEnumerationBuilder(m_data, imageInfoFlagBuilder, 4, {"objc_image_info_flags"}); - m_typeNames.imageInfoFlags = imageInfoFlagType.first; - - EnumerationBuilder swiftVersionBuilder; - swiftVersionBuilder.AddMemberWithValue("SwiftVersion1", 1); - swiftVersionBuilder.AddMemberWithValue("SwiftVersion1_2", 2); - swiftVersionBuilder.AddMemberWithValue("SwiftVersion2", 3); - swiftVersionBuilder.AddMemberWithValue("SwiftVersion3", 4); - swiftVersionBuilder.AddMemberWithValue("SwiftVersion4", 5); - swiftVersionBuilder.AddMemberWithValue("SwiftVersion4_1", 6); // [sic] - swiftVersionBuilder.AddMemberWithValue("SwiftVersion4_2", 6); - swiftVersionBuilder.AddMemberWithValue("SwiftVersion5", 7); - auto swiftVersionType = - finalizeEnumerationBuilder(m_data, swiftVersionBuilder, 4, {"objc_image_info_swift_version"}); - m_typeNames.imageInfoSwiftVersion = swiftVersionType.first; - - StructureBuilder imageInfoBuilder; - imageInfoBuilder.AddMember(Type::IntegerType(4, false), "version"); - imageInfoBuilder.AddMember(Type::NamedType(m_data, m_typeNames.imageInfoFlags), "flags"); - auto imageInfoType = finalizeStructureBuilder(m_data, imageInfoBuilder, "objc_image_info_t"); - m_typeNames.imageInfo = imageInfoType.first; - - StructureBuilder methodEntry; - 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"); - m_typeNames.methodEntry = type.first; - - StructureBuilder method; - method.AddMember(Type::PointerType(addrSize, Type::IntegerType(1, true)), "name"); - method.AddMember(Type::PointerType(addrSize, Type::IntegerType(1, true)), "types"); - method.AddMember(Type::PointerType(addrSize, Type::VoidType()), "imp"); - type = finalizeStructureBuilder(m_data, method, "objc_method_t"); - m_typeNames.method = type.first; - - StructureBuilder methList; - methList.AddMember(Type::IntegerType(4, false), "obsolete"); - methList.AddMember(Type::IntegerType(4, false), "count"); - type = finalizeStructureBuilder(m_data, methList, "objc_method_list_t"); - m_typeNames.methodList = type.first; - - StructureBuilder ivarBuilder; - ivarBuilder.AddMember(Type::PointerType(addrSize, Type::IntegerType(4, false)), "offset"); - ivarBuilder.AddMember(Type::PointerType(addrSize, Type::IntegerType(1, true)), "name"); - ivarBuilder.AddMember(Type::PointerType(addrSize, Type::IntegerType(1, true)), "type"); - ivarBuilder.AddMember(Type::IntegerType(4, false), "alignment"); - ivarBuilder.AddMember(Type::IntegerType(4, false), "size"); - type = finalizeStructureBuilder(m_data, ivarBuilder, "objc_ivar_t"); - m_typeNames.ivar = type.first; - - StructureBuilder ivarList; - ivarList.AddMember(Type::IntegerType(4, false), "entsize"); - ivarList.AddMember(Type::IntegerType(4, false), "count"); - type = finalizeStructureBuilder(m_data, ivarList, "objc_ivar_list_t"); - m_typeNames.ivarList = type.first; - - StructureBuilder protocolListBuilder; - protocolListBuilder.AddMember(Type::IntegerType(addrSize, false), "count"); - m_typeNames.protocolList = finalizeStructureBuilder(m_data, protocolListBuilder, "objc_protocol_list_t").first; - - StructureBuilder classROBuilder; - classROBuilder.AddMember(Type::IntegerType(4, false), "flags"); - classROBuilder.AddMember(Type::IntegerType(4, false), "start"); - classROBuilder.AddMember(Type::IntegerType(4, false), "size"); - if (addrSize == 8) - classROBuilder.AddMember(Type::IntegerType(4, false), "reserved"); - classROBuilder.AddMember(Type::PointerType(addrSize, Type::VoidType()), "ivar_layout"); - classROBuilder.AddMember(Type::PointerType(addrSize, Type::IntegerType(1, true)), "name"); - classROBuilder.AddMember(Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.methodList)), "methods"); - classROBuilder.AddMember( - Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.protocolList)), "protocols"); - classROBuilder.AddMember(Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.ivarList)), "ivars"); - classROBuilder.AddMember(Type::PointerType(addrSize, Type::VoidType()), "weak_ivar_layout"); - classROBuilder.AddMember(Type::PointerType(addrSize, Type::VoidType()), "properties"); - type = finalizeStructureBuilder(m_data, classROBuilder, "objc_class_ro_t"); - m_typeNames.classRO = type.first; - - QualifiedName classTypeName("objc_class_t"); - auto classTypeId = Type::GenerateAutoTypeId("objc", classTypeName); - auto isaType = Type::PointerType(m_data->GetDefaultArchitecture(), - TypeBuilder::NamedType( - new NamedTypeReferenceBuilder(StructNamedTypeClass, "", classTypeName), m_data->GetAddressSize(), 4) - .Finalize()); - - StructureBuilder classBuilder; - classBuilder.AddMember(isaType, "isa"); - classBuilder.AddMember(Type::PointerType(addrSize, Type::VoidType()), "super"); - classBuilder.AddMember(Type::PointerType(addrSize, Type::VoidType()), "cache"); - classBuilder.AddMember(Type::PointerType(addrSize, Type::VoidType()), "vtable"); - classBuilder.AddMember(Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.classRO)), "data"); - - auto classTypeStruct = classBuilder.Finalize(); - auto classType = Type::StructureType(classTypeStruct); - auto classQualName = m_data->DefineType(classTypeId, classTypeName, classType); - - m_typeNames.cls = classQualName; - - StructureBuilder categoryBuilder; - categoryBuilder.AddMember(Type::PointerType(addrSize, Type::IntegerType(1, true)), "category_name"); - categoryBuilder.AddMember(Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.cls)), "class"); - categoryBuilder.AddMember( - Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.methodList)), "inst_methods"); - categoryBuilder.AddMember( - Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.methodList)), "class_methods"); - categoryBuilder.AddMember(Type::PointerType(addrSize, Type::VoidType()), "protocols"); - categoryBuilder.AddMember(Type::PointerType(addrSize, Type::VoidType()), "properties"); - m_typeNames.category = finalizeStructureBuilder(m_data, categoryBuilder, "objc_category_t").first; - - StructureBuilder protocolBuilder; - protocolBuilder.AddMember(Type::PointerType(addrSize, Type::VoidType()), "isa"); - protocolBuilder.AddMember(Type::PointerType(addrSize, Type::IntegerType(1, true)), "mangledName"); - protocolBuilder.AddMember( - Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.protocolList)), "protocols"); - protocolBuilder.AddMember( - Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.methodList)), "instanceMethods"); - protocolBuilder.AddMember( - Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.methodList)), "classMethods"); - protocolBuilder.AddMember( - Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.methodList)), "optionalInstanceMethods"); - protocolBuilder.AddMember( - Type::PointerType(addrSize, Type::NamedType(m_data, m_typeNames.methodList)), "optionalClassMethods"); - protocolBuilder.AddMember(Type::PointerType(addrSize, Type::VoidType()), "instanceProperties"); - protocolBuilder.AddMember(Type::IntegerType(4, false), "size"); - protocolBuilder.AddMember(Type::IntegerType(4, false), "flags"); - m_typeNames.protocol = finalizeStructureBuilder(m_data, protocolBuilder, "objc_protocol_t").first; - - m_data->BeginBulkModifySymbols(); - if (auto classList = m_data->GetSectionByName(baseName + "::__objc_classlist")) - LoadClasses(&reader, classList); - if (auto nonLazyClassList = m_data->GetSectionByName(baseName + "::__objc_nlclslist")) - LoadClasses(&reader, nonLazyClassList); // See: https://stackoverflow.com/a/15318325 - - GenerateClassTypes(); - for (auto& [_, cls] : m_classes) - ApplyMethodTypes(cls); - - if (auto catList = m_data->GetSectionByName(baseName + "::__objc_catlist")) // Do this after loading class type data. - LoadCategories(&reader, catList); - if (auto nonLazyCatList = m_data->GetSectionByName(baseName + "::__objc_nlcatlist")) // Do this after loading class type data. - LoadCategories(&reader, nonLazyCatList); - for (auto& [_, cat] : m_categories) - ApplyMethodTypes(cat); - - if (auto protoList = m_data->GetSectionByName(baseName + "::__objc_protolist")) - LoadProtocols(&reader, protoList); - - PostProcessObjCSections(&reader, baseName); - - auto id = m_data->BeginUndoActions(); - m_symbolQueue->Process(); - m_data->EndBulkModifySymbols(); - delete m_symbolQueue; - m_data->ForgetUndoActions(id); - - auto meta = SerializeMetadata(); - m_data->StoreMetadata("Objective-C", meta, true); - - m_relocationPointerRewrites.clear(); -} - - -void DSCObjCProcessor::ProcessCFStrings(std::shared_ptr<VM> vm, std::string baseName) -{ - m_symbolQueue = new SymbolQueue(); - uint64_t ptrSize = m_data->GetAddressSize(); - // https://github.com/apple/llvm-project/blob/next/clang/lib/CodeGen/CodeGenModule.cpp#L6129 - // See also ASTContext.cpp ctrl+f __NSConstantString_tag - - // The place these flags are used is unclear, along with any clear flag definitions, but they are useful for - // introspection - EnumerationBuilder __cfStringFlagBuilder; - __cfStringFlagBuilder.AddMemberWithValue("SwiftABI", 0b1); - __cfStringFlagBuilder.AddMemberWithValue("Swift4_1", 0b100); - // LLVM also sets 0x7c0 (0b11111000000) on both UTF8 and UTF16 strings however it is unclear what this denotes. - __cfStringFlagBuilder.AddMemberWithValue("UTF8", 0b1000); - __cfStringFlagBuilder.AddMemberWithValue("UTF16", 0b10000); - auto type = finalizeEnumerationBuilder(m_data, __cfStringFlagBuilder, ptrSize, {"CFStringFlag"}); - m_typeNames.cfStringFlag = type.first; - - StructureBuilder __cfStringStructBuilder; - __cfStringStructBuilder.AddMember(Type::PointerType(ptrSize, Type::VoidType()), "isa"); - __cfStringStructBuilder.AddMember(Type::NamedType(m_data, m_typeNames.cfStringFlag), "flags"); - __cfStringStructBuilder.AddMember(Type::PointerType(ptrSize, Type::IntegerType(1, true)), "data"); - __cfStringStructBuilder.AddMember(Type::IntegerType(ptrSize, false), "length"); - type = finalizeStructureBuilder(m_data, __cfStringStructBuilder, "__NSConstantString"); - m_typeNames.cfString = type.first; - - StructureBuilder __cfStringUTF16StructBuilder; - __cfStringUTF16StructBuilder.AddMember(Type::PointerType(ptrSize, Type::VoidType()), "isa"); - __cfStringUTF16StructBuilder.AddMember(Type::NamedType(m_data, m_typeNames.cfStringFlag), "flags"); - __cfStringUTF16StructBuilder.AddMember(Type::PointerType(ptrSize, Type::IntegerType(2, true)), "data"); - __cfStringUTF16StructBuilder.AddMember(Type::IntegerType(ptrSize, false), "length"); - type = finalizeStructureBuilder(m_data, __cfStringUTF16StructBuilder, "__NSConstantString_UTF16"); - m_typeNames.cfStringUTF16 = type.first; - - auto reader = VMReader(vm); - if (auto cfstrings = m_data->GetSectionByName(baseName + "::__cfstring")) - { - auto start = cfstrings->GetStart(); - auto end = cfstrings->GetEnd(); - auto typeWidth = Type::NamedType(m_data, m_typeNames.cfString)->GetWidth(); - m_data->BeginBulkModifySymbols(); - for (view_ptr_t i = start; i < end; i += typeWidth) - { - reader.Seek(i + ptrSize); - uint64_t flags = reader.ReadPointer(); - auto strLoc = ReadPointerAccountingForRelocations(&reader); - auto size = reader.ReadPointer(); - std::string str; - if (flags & 0b10000) // UTF16 - { - auto data = m_data->ReadBuffer(strLoc, size * 2); - - str = ""; - for (uint64_t bufferOff = 0; bufferOff < size * 2; bufferOff += 2) - { - uint8_t* rawData = static_cast<uint8_t*>(data.GetData()); - uint8_t* offsetAddress = rawData + bufferOff; - uint16_t c = *reinterpret_cast<uint16_t*>(offsetAddress); - if (c == 0x20) - str.push_back('_'); - else if (c < 0x80) - str.push_back(c); - else - str.push_back('?'); - } - DefineObjCSymbol( - DataSymbol, Type::ArrayType(Type::WideCharType(2), size + 1), "ustr_" + str, strLoc, true); - DefineObjCSymbol( - DataSymbol, Type::NamedType(m_data, m_typeNames.cfStringUTF16), "cfstr_" + str, i, true); - } - else // UTF8 / ASCII - { - reader.Seek(strLoc); - str = reader.ReadCString(strLoc); - for (auto& c : str) - { - if (c == ' ') - c = '_'; - } - DefineObjCSymbol(DataSymbol, Type::ArrayType(Type::IntegerType(1, true), str.size() + 1), "cstr_" + str, - strLoc, true); - DefineObjCSymbol(DataSymbol, Type::NamedType(m_data, m_typeNames.cfString), "cfstr_" + str, i, true); - } - } - auto id = m_data->BeginUndoActions(); - m_symbolQueue->Process(); - m_data->EndBulkModifySymbols(); - m_data->ForgetUndoActions(id); - } - delete m_symbolQueue; + auto objCRelativeMethodsBaseAddr = m_cache->GetObjCRelativeMethodBaseAddress(static_cast<DSCObjCReader*>(reader)->GetVMReader()); + m_customRelativeMethodSelectorBase = objCRelativeMethodsBaseAddr; + return objCRelativeMethodsBaseAddr; } -void DSCObjCProcessor::AddRelocatedPointer(uint64_t location, uint64_t rewrite) +DSCObjCProcessor::DSCObjCProcessor(BinaryView* data, SharedCache* cache, bool isBackedByDatabase) : + ObjCProcessor(data, "SharedCache.ObjC", isBackedByDatabase, true), m_cache(cache) { - m_relocationPointerRewrites[location] = rewrite; } diff --git a/view/sharedcache/core/ObjC.h b/view/sharedcache/core/ObjC.h index a308c23a..13790364 100644 --- a/view/sharedcache/core/ObjC.h +++ b/view/sharedcache/core/ObjC.h @@ -6,233 +6,47 @@ #define SHAREDCACHE_OBJC_H #include <binaryninjaapi.h> -#include "VM.h" +#include <objectivec/objc.h> #include "SharedCache.h" -using namespace BinaryNinja; - - namespace DSCObjC { + class DSCObjCReader : public ObjCReader { + private: + VMReader m_reader; + size_t m_addressSize; - // This set of structs is based on the objc4 source, - // however pointers have been replaced with view_ptr_t - - // Used for pointers within BinaryView, primarily to make it far more clear in typedefs - // whether the size of a field can vary between architectures. - // These should _not_ be used in sizeof or direct Read() calls. - typedef uint64_t view_ptr_t; - - typedef struct { - view_ptr_t name; - view_ptr_t types; - view_ptr_t imp; - } method_t; - typedef struct { - uint32_t name; - uint32_t types; - uint32_t imp; - } method_entry_t; - typedef struct { - view_ptr_t offset; - view_ptr_t name; - view_ptr_t type; - uint32_t alignmentRaw; - uint32_t size; - } ivar_t; - typedef struct { - view_ptr_t name; - view_ptr_t attributes; - } property_t; - typedef struct { - uint32_t entsizeAndFlags; - uint32_t count; - } method_list_t; - typedef struct { - uint32_t entsizeAndFlags; - uint32_t count; - } ivar_list_t; - typedef struct { - uint32_t entsizeAndFlags; - uint32_t count; - } property_list_t; - 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; - view_ptr_t protocols; - view_ptr_t instanceMethods; - view_ptr_t classMethods; - view_ptr_t optionalInstanceMethods; - view_ptr_t optionalClassMethods; - view_ptr_t instanceProperties; - uint32_t size; - uint32_t flags; - } protocol_t; - typedef struct { - uint32_t flags; - uint32_t instanceStart; - uint32_t instanceSize; - uint32_t reserved; - view_ptr_t ivarLayout; - view_ptr_t name; - view_ptr_t baseMethods; - view_ptr_t baseProtocols; - view_ptr_t ivars; - view_ptr_t weakIvarLayout; - view_ptr_t baseProperties; - } class_ro_t; - typedef struct { - view_ptr_t isa; - view_ptr_t super; - view_ptr_t cache; - view_ptr_t vtable; - view_ptr_t data; - } class_t; - typedef struct { - view_ptr_t name; - view_ptr_t cls; - view_ptr_t instanceMethods; - view_ptr_t classMethods; - view_ptr_t protocols; - view_ptr_t instanceProperties; - } category_t; - typedef struct { - view_ptr_t receiver; - view_ptr_t current_class; - } objc_super2; - typedef struct { - view_ptr_t imp; - view_ptr_t sel; - } message_ref_t; - - struct Method { - std::string name; - std::string types; - view_ptr_t imp; - }; - - struct Ivar { - uint32_t offset; - std::string name; - std::string type; - uint32_t alignment; - uint32_t size; - }; - - struct Property { - std::string name; - std::string attributes; - }; - - struct ClassBase { - std::map<uint64_t, Method> methodList; - std::map<uint64_t, Ivar> ivarList; - }; - - struct Class { - std::string name; - ClassBase instanceClass; - ClassBase metaClass; - - // Loaded by type processing - QualifiedName associatedName; - }; - - class Protocol { public: - std::string name; - std::vector<QualifiedName> protocols; - ClassBase instanceMethods; - ClassBase classMethods; - ClassBase optionalInstanceMethods; - ClassBase optionalClassMethods; - }; - - struct QualifiedNameOrType { - BinaryNinja::Ref<BinaryNinja::Type> type = nullptr; - BinaryNinja::QualifiedName name; - size_t ptrCount = 0; - }; + void Read(void* dest, size_t len) override; + std::string ReadCString() override; + uint8_t Read8() override; + uint16_t Read16() override; + uint32_t Read32() override; + uint64_t Read64() override; + int8_t ReadS8() override; + int16_t ReadS16() override; + int32_t ReadS32() override; + int64_t ReadS64() override; + uint64_t ReadPointer() override; + uint64_t GetOffset() const override; + void Seek(uint64_t offset) override; + void SeekRelative(int64_t offset) override; - class DSCObjCProcessor { - struct Types { - // QualifiedName relativePtr; - // QualifiedName id; - // QualifiedName sel; - // QualifiedName BOOL; - // QualifiedName nsInteger; - // QualifiedName nsuInteger; - // QualifiedName cgFloat; - QualifiedName cfStringFlag; - QualifiedName cfString; - QualifiedName cfStringUTF16; - QualifiedName imageInfoFlags; - QualifiedName imageInfoSwiftVersion; - QualifiedName imageInfo; - QualifiedName methodEntry; - QualifiedName method; - QualifiedName methodList; - QualifiedName classRO; - QualifiedName cls; - QualifiedName category; - QualifiedName protocol; - QualifiedName protocolList; - QualifiedName ivar; - QualifiedName ivarList; - } m_typeNames; - - bool m_isBackedByDatabase; - - BinaryView* m_data; - SymbolQueue* m_symbolQueue = nullptr; - Ref<Logger> m_logger; - std::map<uint64_t, Class> m_classes; - std::map<uint64_t, Class> m_categories; - std::map<uint64_t, Protocol> m_protocols; - std::unordered_map<uint64_t, std::string> m_selectorCache; - std::unordered_map<uint64_t, Method> m_localMethods; - - // Required for workflow_objc type heuristics, should be removed when that is no longer a thing. - std::map<uint64_t, std::string> m_selRefToName; - std::map<uint64_t, std::vector<uint64_t>> m_selRefToImplementations; - std::map<uint64_t, std::vector<uint64_t>> m_selToImplementations; - // -- + VMReader& GetVMReader(); + DSCObjCReader(SharedCacheCore::SharedCache* cache, size_t addressSize); + }; + class DSCObjCProcessor : public ObjCProcessor { std::optional<uint64_t> m_customRelativeMethodSelectorBase = std::nullopt; SharedCacheCore::SharedCache* m_cache; - uint64_t ReadPointerAccountingForRelocations(VMReader* reader); - std::unordered_map<uint64_t, uint64_t> m_relocationPointerRewrites; - - static Ref<Metadata> SerializeMethod(uint64_t loc, const Method& method); - static Ref<Metadata> SerializeClass(uint64_t loc, const Class& cls); - - Ref<Metadata> SerializeMetadata(); - std::vector<QualifiedNameOrType> 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> type, const std::string& name, uint64_t addr, bool deferred); - 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<Section> listSection); - void LoadCategories(VMReader* reader, Ref<Section> listSection); - void LoadProtocols(VMReader* reader, Ref<Section> listSection); - void GenerateClassTypes(); - bool ApplyMethodType(Class& cls, Method& method, bool isInstanceMethod); - void ApplyMethodTypes(Class& cls); - void PostProcessObjCSections(VMReader* reader, std::string baseName); + std::shared_ptr<ObjCReader> GetReader() override; + void GetRelativeMethod(ObjCReader* reader, method_t& meth) override; + public: DSCObjCProcessor(BinaryView* data, SharedCacheCore::SharedCache* cache, bool isBackedByDatabase); - void ProcessObjCData(std::shared_ptr<VM> vm, std::string baseName); - void ProcessCFStrings(std::shared_ptr<VM> vm, std::string baseName); - void AddRelocatedPointer(uint64_t location, uint64_t rewrite); + + uint64_t GetObjCRelativeMethodBaseAddress(ObjCReader* reader) override; }; } #endif //SHAREDCACHE_OBJC_H diff --git a/view/sharedcache/core/SharedCache.cpp b/view/sharedcache/core/SharedCache.cpp index 5709759b..2b18c064 100644 --- a/view/sharedcache/core/SharedCache.cpp +++ b/view/sharedcache/core/SharedCache.cpp @@ -1673,9 +1673,9 @@ static void ProcessObjCSectionsForImageWithName(std::string baseName, std::share try { if (processObjCMetadata) - objc->ProcessObjCData(vm, baseName); + objc->ProcessObjCData(baseName); if (processCFStrings) - objc->ProcessCFStrings(vm, baseName); + objc->ProcessCFStrings(baseName); } catch (const std::exception& ex) { |
