From dd9d8dd0a5f37248faa34c8657243aea747b7112 Mon Sep 17 00:00:00 2001 From: Fabian Freyer Date: Fri, 29 Apr 2022 03:33:27 +0200 Subject: [PATCH] Initial support for analyzing categories. --- CMakeLists.txt | 2 + Core/AnalysisProvider.cpp | 2 + Core/Analyzers/CategoryAnalyzer.cpp | 81 +++++++++++++++++++ Include/ObjectiveNinjaCore/AnalysisInfo.h | 20 +++++ .../Analyzers/CategoryAnalyzer.h | 29 +++++++ Plugin/CustomTypes.cpp | 9 +++ Plugin/CustomTypes.h | 1 + Plugin/InfoHandler.cpp | 57 +++++++++++++ 8 files changed, 201 insertions(+) create mode 100644 Core/Analyzers/CategoryAnalyzer.cpp create mode 100644 Include/ObjectiveNinjaCore/Analyzers/CategoryAnalyzer.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 53ca009..83199b4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,6 +26,7 @@ add_subdirectory(Vendor/BinaryNinjaAPI) # Core library ----------------------------------------------------------------- set(CORE_SOURCE + Include/ObjectiveNinjaCore/Analyzers/CategoryAnalyzer.h Include/ObjectiveNinjaCore/Analyzers/CFStringAnalyzer.h Include/ObjectiveNinjaCore/Analyzers/ClassAnalyzer.h Include/ObjectiveNinjaCore/Analyzers/SelectorAnalyzer.h @@ -36,6 +37,7 @@ set(CORE_SOURCE Include/ObjectiveNinjaCore/AnalysisProvider.h Include/ObjectiveNinjaCore/Analyzer.h Include/ObjectiveNinjaCore/TypeParser.h + Core/Analyzers/CategoryAnalyzer.cpp Core/Analyzers/CFStringAnalyzer.cpp Core/Analyzers/ClassAnalyzer.cpp Core/Analyzers/SelectorAnalyzer.cpp diff --git a/Core/AnalysisProvider.cpp b/Core/AnalysisProvider.cpp index 45d1ee4..2ad294d 100644 --- a/Core/AnalysisProvider.cpp +++ b/Core/AnalysisProvider.cpp @@ -9,6 +9,7 @@ #include #include +#include #include namespace ObjectiveNinja { @@ -20,6 +21,7 @@ SharedAnalysisInfo AnalysisProvider::infoForFile(SharedAbstractFile file) std::vector> analyzers; analyzers.emplace_back(new SelectorAnalyzer(info, file)); analyzers.emplace_back(new ClassAnalyzer(info, file)); + analyzers.emplace_back(new CategoryAnalyzer(info, file)); analyzers.emplace_back(new CFStringAnalyzer(info, file)); for (const auto& analyzer : analyzers) diff --git a/Core/Analyzers/CategoryAnalyzer.cpp b/Core/Analyzers/CategoryAnalyzer.cpp new file mode 100644 index 0000000..80c7f36 --- /dev/null +++ b/Core/Analyzers/CategoryAnalyzer.cpp @@ -0,0 +1,81 @@ +#include +#include + +#include + +using namespace ObjectiveNinja; + + +CategoryAnalyzer::CategoryAnalyzer(SharedAnalysisInfo info, + SharedAbstractFile file) + : Analyzer(std::move(info), std::move(file)) +{ +} + +MethodListInfo CategoryAnalyzer::analyzeMethodList(uint64_t address) +{ + MethodListInfo mli; + mli.address = address; + mli.flags = m_file->readInt(mli.address); + + auto methodCount = m_file->readInt(mli.address + 0x4); + auto methodSize = mli.hasRelativeOffsets() ? 12 : 24; + + for (unsigned i = 0; i < methodCount; ++i) { + MethodInfo mi; + mi.address = mli.address + 8 + (i * methodSize); + + m_file->seek(mi.address); + + if (mli.hasRelativeOffsets()) { + mi.nameAddress = mi.address + static_cast(m_file->readInt()); + mi.typeAddress = mi.address + 4 + static_cast(m_file->readInt()); + mi.implAddress = mi.address + 8 + static_cast(m_file->readInt()); + } else { + mi.nameAddress = arp(m_file->readLong()); + mi.typeAddress = arp(m_file->readLong()); + mi.implAddress = arp(m_file->readLong()); + } + + if (!mli.hasRelativeOffsets() || mli.hasDirectSelectors()) { + mi.selector = m_file->readStringAt(mi.nameAddress); + } else { + auto selectorNamePointer = arp(m_file->readLong(mi.nameAddress)); + mi.selector = m_file->readStringAt(selectorNamePointer); + } + + mi.type = m_file->readStringAt(mi.typeAddress); + + m_info->methodImpls[mi.nameAddress] = mi.implAddress; + + mli.methods.emplace_back(mi); + } + + return mli; +} + +void CategoryAnalyzer::run() +{ + const auto sectionStart = m_file->sectionStart("__objc_catlist"); + const auto sectionEnd = m_file->sectionEnd("__objc_catlist"); + if (sectionStart == 0 || sectionEnd == 0) + return; + + for (auto address = sectionStart; address < sectionEnd; address += 8) { + CategoryInfo ci; + ci.listPointer = address; + ci.address = arp(m_file->readLong(address)); + ci.nameAddress = arp(m_file->readLong(ci.address)); + ci.name = m_file->readStringAt(ci.nameAddress); + ci.instanceMethodListAddress = arp(m_file->readLong(ci.address + 0x10)); + ci.classMethodListAddress = arp(m_file->readLong(ci.address + 0x18)); + + if (ci.instanceMethodListAddress) + ci.instanceMethods = analyzeMethodList(ci.instanceMethodListAddress); + + if (ci.classMethodListAddress) + ci.classMethods = analyzeMethodList(ci.classMethodListAddress); + + m_info->categories.emplace_back(ci); + } +} diff --git a/Include/ObjectiveNinjaCore/AnalysisInfo.h b/Include/ObjectiveNinjaCore/AnalysisInfo.h index a872185..10438dd 100644 --- a/Include/ObjectiveNinjaCore/AnalysisInfo.h +++ b/Include/ObjectiveNinjaCore/AnalysisInfo.h @@ -92,6 +92,21 @@ struct ClassInfo { uint64_t methodListAddress {}; }; +/** + * A description of an Objective-C category. + */ +struct CategoryInfo { + uint64_t address {}; + std::string name {}; + MethodListInfo instanceMethods {}; + MethodListInfo classMethods {}; + + uint64_t listPointer {}; + uint64_t nameAddress {}; + uint64_t instanceMethodListAddress {}; + uint64_t classMethodListAddress {}; +}; + /** * Analysis info storage. * @@ -106,6 +121,7 @@ struct AnalysisInfo { std::unordered_map selectorRefsByKey {}; std::vector classes {}; + std::vector categories {}; std::unordered_map methodImpls; std::string dump() const; @@ -123,4 +139,8 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(MethodListInfo, address, flags, methods) NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(ClassInfo, listPointer, address, dataAddress, nameAddress, name, methodListAddress, methodList) + +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(CategoryInfo, listPointer, address, nameAddress, + name, instanceMethodListAddress, instanceMethods, classMethodListAddress, + classMethods) } diff --git a/Include/ObjectiveNinjaCore/Analyzers/CategoryAnalyzer.h b/Include/ObjectiveNinjaCore/Analyzers/CategoryAnalyzer.h new file mode 100644 index 0000000..41c0efd --- /dev/null +++ b/Include/ObjectiveNinjaCore/Analyzers/CategoryAnalyzer.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2022 Fabian Freyer. All rights reserved. + * + * Use of this source code is governed by the BSD 3-Clause license; the full + * terms of the license can be found in the LICENSE.txt file. + */ + +#pragma once + +#include + +namespace ObjectiveNinja { + +/** + * Analyzer for extracting Objective-C class information. + */ +class CategoryAnalyzer : public Analyzer { + /** + * Analyze a method list. + */ + MethodListInfo analyzeMethodList(uint64_t); + +public: + CategoryAnalyzer(SharedAnalysisInfo, SharedAbstractFile); + + void run() override; +}; + +} diff --git a/Plugin/CustomTypes.cpp b/Plugin/CustomTypes.cpp index 7ca63e5..6132164 100644 --- a/Plugin/CustomTypes.cpp +++ b/Plugin/CustomTypes.cpp @@ -73,6 +73,15 @@ struct objc_class_t { void* vtable; const fptr_t data; }; + +struct objc_category_t { + const char* name; + void* class; + const tptr_t instance_methods; + const tptr_t class_methods; + const tptr_t protocols; + const tptr_t instance_properties; +} )"; namespace CustomTypes { diff --git a/Plugin/CustomTypes.h b/Plugin/CustomTypes.h index 5d690ec..4cd3f3a 100644 --- a/Plugin/CustomTypes.h +++ b/Plugin/CustomTypes.h @@ -28,6 +28,7 @@ const std::string Method = "objc_method_t"; const std::string MethodListEntry = "objc_method_entry_t"; const std::string Class = "objc_class_t"; const std::string ClassRO = "objc_class_ro_t"; +const std::string Category = "objc_category_t"; /** * Define all Objective-C-related types for a view. diff --git a/Plugin/InfoHandler.cpp b/Plugin/InfoHandler.cpp index 92b2f37..361366c 100644 --- a/Plugin/InfoHandler.cpp +++ b/Plugin/InfoHandler.cpp @@ -125,6 +125,7 @@ void InfoHandler::applyInfoToView(SharedAnalysisInfo info, BinaryViewRef bv) BinaryReader reader(bv); auto taggedPointerType = namedType(bv, CustomTypes::TaggedPointer); + auto categoryType = namedType(bv, CustomTypes::Category); auto cfStringType = namedType(bv, CustomTypes::CFString); auto classType = namedType(bv, CustomTypes::Class); auto classDataType = namedType(bv, CustomTypes::ClassRO); @@ -198,6 +199,62 @@ void InfoHandler::applyInfoToView(SharedAnalysisInfo info, BinaryViewRef bv) defineSymbol(bv, ci.methodListAddress, ci.name, "ml_"); } + // Create data variables and symbols for the analyzed Categories. + for (const auto& ci : info->categories) { + defineVariable(bv, ci.listPointer, taggedPointerType); + defineVariable(bv, ci.address, categoryType); + + defineSymbol(bv, ci.listPointer, ci.name, "catp_"); + defineSymbol(bv, ci.address, ci.name, "cat_"); + + defineReference(bv, ci.listPointer, ci.address); + + if (ci.instanceMethods.address && !ci.instanceMethods.methods.empty()) { + auto methodType = ci.instanceMethods.hasRelativeOffsets() + ? bv->GetTypeByName(CustomTypes::MethodListEntry) + : bv->GetTypeByName(CustomTypes::Method); + + // Create data variables for each method in the method list. + for (const auto& mi : ci.instanceMethods.methods) { + defineVariable(bv, mi.address, methodType); + defineSymbol(bv, mi.address, sanitizeSelector(mi.selector), "mt_"); + defineVariable(bv, mi.typeAddress, stringType(mi.type.size())); + + defineReference(bv, ci.instanceMethods.address, mi.address); + defineReference(bv, mi.address, mi.nameAddress); + defineReference(bv, mi.address, mi.typeAddress); + defineReference(bv, mi.address, mi.implAddress); + } + + // Create a data variable and symbol for the method list header. + defineVariable(bv, ci.instanceMethodListAddress, methodListType); + defineSymbol(bv, ci.instanceMethodListAddress, ci.name, "mli_"); + } + + + if (ci.classMethods.address && !ci.classMethods.methods.empty()) { + auto methodType = ci.classMethods.hasRelativeOffsets() + ? bv->GetTypeByName(CustomTypes::MethodListEntry) + : bv->GetTypeByName(CustomTypes::Method); + + // Create data variables for each method in the method list. + for (const auto& mi : ci.classMethods.methods) { + defineVariable(bv, mi.address, methodType); + defineSymbol(bv, mi.address, sanitizeSelector(mi.selector), "mt_"); + defineVariable(bv, mi.typeAddress, stringType(mi.type.size())); + + defineReference(bv, ci.classMethods.address, mi.address); + defineReference(bv, mi.address, mi.nameAddress); + defineReference(bv, mi.address, mi.typeAddress); + defineReference(bv, mi.address, mi.implAddress); + } + + // Create a data variable and symbol for the method list header. + defineVariable(bv, ci.classMethodListAddress, methodListType); + defineSymbol(bv, ci.classMethodListAddress, ci.name, "mlc_"); + } + } + bv->CommitUndoActions(); bv->UpdateAnalysis(); }