Skip to content
This repository has been archived by the owner on Jul 11, 2022. It is now read-only.

Commit

Permalink
Initial support for analyzing categories.
Browse files Browse the repository at this point in the history
  • Loading branch information
fabianfreyer committed May 1, 2022
1 parent b983689 commit 3daacc9
Show file tree
Hide file tree
Showing 8 changed files with 201 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 2 additions & 0 deletions Core/AnalysisProvider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include <ObjectiveNinjaCore/Analyzers/CFStringAnalyzer.h>
#include <ObjectiveNinjaCore/Analyzers/ClassAnalyzer.h>
#include <ObjectiveNinjaCore/Analyzers/CategoryAnalyzer.h>
#include <ObjectiveNinjaCore/Analyzers/SelectorAnalyzer.h>

namespace ObjectiveNinja {
Expand All @@ -20,6 +21,7 @@ SharedAnalysisInfo AnalysisProvider::infoForFile(SharedAbstractFile file)
std::vector<std::unique_ptr<ObjectiveNinja::Analyzer>> 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)
Expand Down
81 changes: 81 additions & 0 deletions Core/Analyzers/CategoryAnalyzer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#include <ObjectiveNinjaCore/Analyzers/ClassAnalyzer.h>
#include <ObjectiveNinjaCore/Analyzers/CategoryAnalyzer.h>

#include <ObjectiveNinjaCore/TypeParser.h>

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<int32_t>(m_file->readInt());
mi.typeAddress = mi.address + 4 + static_cast<int32_t>(m_file->readInt());
mi.implAddress = mi.address + 8 + static_cast<int32_t>(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);
}
}
20 changes: 20 additions & 0 deletions Include/ObjectiveNinjaCore/AnalysisInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand All @@ -106,6 +121,7 @@ struct AnalysisInfo {
std::unordered_map<uint64_t, SharedSelectorRefInfo> selectorRefsByKey {};

std::vector<ClassInfo> classes {};
std::vector<CategoryInfo> categories {};
std::unordered_map<uint64_t, uint64_t> methodImpls;

std::string dump() const;
Expand All @@ -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)
}
29 changes: 29 additions & 0 deletions Include/ObjectiveNinjaCore/Analyzers/CategoryAnalyzer.h
Original file line number Diff line number Diff line change
@@ -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 <ObjectiveNinjaCore/Analyzer.h>

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;
};

}
9 changes: 9 additions & 0 deletions Plugin/CustomTypes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions Plugin/CustomTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
57 changes: 57 additions & 0 deletions Plugin/InfoHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,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);
Expand Down Expand Up @@ -195,4 +196,60 @@ void InfoHandler::applyInfoToView(SharedAnalysisInfo info, BinaryViewRef bv)
defineVariable(bv, ci.methodListAddress, methodListType);
defineSymbol(bv, ci.methodListAddress, ci.name, "ml_");
}

// Create data variables and symbols for the analyzed protocols.
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_");
}
}
}

0 comments on commit 3daacc9

Please sign in to comment.