首页
社区
课程
招聘
[原创]Hikari源码分析 - AntiClassDump
2024-1-8 16:53 6553

[原创]Hikari源码分析 - AntiClassDump

2024-1-8 16:53
6553

之前的文章总结了混淆过程中遇到的问题,以及最终的解决方案,接下来将会用三四篇文章来逐一介绍下各种PASS的实现细节与原理,基本的控制流伪造、虚假控制流、字节替换等将不再复述,这些PASS已经有很多成熟的解析文章参考。该系列文章主要探讨Hikari中如何实现反class dump、反debug、反hook等,本文章分析AntiClassDump的实现细节。以下源码参考来自https://github.com/61bcdefg/Hikari-LLVM15 ,感谢Hikari原作者以及更多贡献者的付出。

一、前置知识

1. 类方法、实例方法

Objective-C 代码中以 - 开头的方法是实例方法。它属于类的某一个或某几个实例对象,类对象必须实例化后才可以使用。

+ 开头的方法是类方法。Objc中的类方法类似Java中的static方法,它是属于类本身的方法,不需要实例化类,用类名即可使用。

用 Hopper 查看反编译的 Object-C 项目,对照源代码和 struct __objc_datastruct __objc_methodstruct __objc_method_list 这几个结构体,可以看到:

  • 实例方法(-)对应 _OBJC_CLASS_$_XXX 中的data域里的 method_list;
  • 类方法(+)对应 _OBJC_METACLASS_$_XXX 中的data域里的 method_list。

2. +initialize 和 +load 方法

+initialize 和 +load 方法都是 NSObject 类的初始化方法,调用顺序均为:基类->子类。区别是 +load 在类被添加到 runtime 时调用,+initialize 在类接收到第一条消息时调用。

3. Objective-C 的 Category

Category 的作用是为已经存在的类添加方法。Category 声明文件和实现文件统一采用“原类名+Category名”的方式命名。下面的代码定义了一个Category MyAddition,向类 MyClass 添加了函数 printNameAddition

1
2
3
4
5
6
7
@interface MyClass : NSObject
- (void)printName;
@end
 
@interface MyClass(MyAddition)
- (void)printNameAddition;
@end

实现相应方法并编译为bitcode assembly查看,发现多了下面一些结构体:

1
2
3
4
5
@"\01l_OBJC_$_CATEGORY_INSTANCE_METHODS_MyClass_$_MyAddition" = ...
@"\01l_OBJC_$_CATEGORY_MyClass_$_MyAddition" =
    private global %struct._category_t { ... }, section "__DATA, __objc_const", align 8
...
@"OBJC_LABEL_CATEGORY_$" = private global ...

其中,全局变量 @"\01l_OBJC_$_CATEGORY_MyClass_$_MyAddition" 是一个结构体 struct _category_t,其定义如下:

1
2
3
4
5
6
7
8
9
10
struct _category_t {
  const char * const name; // CategoryName
  struct _class_t *const cls;  // the class to be extended
  const struct _method_list_t * const instance_methods;
  const struct _method_list_t * const class_methods;
  const struct _protocol_list_t * const protocols;
  const struct _prop_list_t * const properties;
  const struct _prop_list_t * const class_properties;
  const uint32_t size;
}

将程序编译为二进制后再查看,可以发现已经找不到 MyAddition 这一名称,而函数 printNameAddition() 已经在 MyClass 的方法列表中。Category的方法被放到了方法列表的前面。因此由于查找方法时是顺序查找的,category的方法会“覆盖”掉原来类的同名方法。

二、实现原理

整体来讲,该PASS的实现原理主要是在ObjC类的初始化函数中插入相关代码以防止反编译,大致步骤如下:

  1. 遍历模块中的ObjC类,获取类的方法列表。
  2. 为每个类生成一个初始化函数,并向其中插入代码来保护类的信息不被泄露。
  3. 在初始化函数中,可以插入一些代码来增加反编译的难度和复杂性,例如:
  • 对类的方法进行重命名:可以通过替换类的方法实现,给方法添加新的名字。这样反编译工具在分析代码时会遇到重命名的方法,增加了阅读和理解代码的困难度。

  • 修改类的方法实现:可以向方法的实现中插入一些无用的代码片段,或者进行代码混淆,使得反编译工具难以还原出原始的代码逻辑。

  • 加入代码验证逻辑:可以向方法的实现中插入一些验证逻辑,例如检查函数参数、返回值等,对代码进行验证,防止反编译工具逆向分析。

  • 使用编译器提供的安全特性:例如Apple的ptrauth和Opaque Pointers,可以使得函数指针和类指针更难以被破解和篡改。

  1. 通过LLVM的API,可以在初始化函数中生成相应的代码,并将修改后的函数实现替换原始的方法实现。
  2. 插入的代码可以根据需求和安全要求进行定制,以达到增加反编译难度和保护代码的目的。

总体来说,通过在ObjC类的初始化函数中插入代码,可以增加反编译的难度,使得反编译工具难以还原出原始的代码逻辑和结构,保护代码的安全性和保密性。

三、代码分析

0. doInitialization

doInitialization是Pass的初始化函数,执行时会进行以下操作:

  1. 获取和检查目标三元组:
    代码首先获取了模块(Module)的目标三元组信息,并存储在变量triple中,这通常包括了架构、厂商和操作系统信息。然后,代码检查了这个三元组是否代表苹果公司的架构,因为此Pass专门用于处理苹果的Objective-C实现。

    1
    2
    3
    4
    5
    6
    triple = Triple(M.getTargetTriple());
    if (triple.getVendor() != Triple::VendorType::Apple) {
      // 只支持苹果的ObjC实现,对于GNU Step等其它实现给出警告
      // ...
      return false;
    }
  2. 定义基础类型:
    接下来,定义了一个指向int8类型指针的LLVM类型(Int8PtrTy),这个类型在Objective-C运行时函数声明中会用到。

    1
    Type *Int8PtrTy = Type::getInt8PtrTy(M.getContext());
  3. 添加Objective-C运行时函数声明:
    然后,代码创建了各种Objective-C运行时函数的类型,并使用这些类型来声明这些函数。这在后续的Pass执行中,可能会用于插入或修改这些函数的调用。

    例如,声明了class_replaceMethodsel_registerName函数,这些函数分别用于替换类中的方法和注册方法的选择器(selector)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 添加ObjC运行时函数的声明
        FunctionType *IMPType =
            FunctionType::get(Int8PtrTy, {Int8PtrTy, Int8PtrTy}, true);
        PointerType *IMPPointerType = PointerType::getUnqual(IMPType);
        FunctionType *class_replaceMethod_type = FunctionType::get(
            IMPPointerType, {Int8PtrTy, Int8PtrTy, IMPPointerType, Int8PtrTy},
            false);
        M.getOrInsertFunction("class_replaceMethod", class_replaceMethod_type);
        FunctionType *sel_registerName_type =
            FunctionType::get(Int8PtrTy, {Int8PtrTy}, false);
        M.getOrInsertFunction("sel_registerName", sel_registerName_type);
    // ...
  4. 判断特性支持:
    最后,代码检查当前的模块是否支持Apple的指针认证(appleptrauth)和LLVM上下文是否支持不透明指针(opaquepointers)。通过这两个布尔变量将决定后续Pass的行为,尤其是在处理指针和类型时。

    1
    2
    3
      // 判断是否支持ApplePtrauth和Opaque Pointers
    appleptrauth = hasApplePtrauth(&M);
    opaquepointers = !M.getContext().supportsTypedPointers();
  5. 返回值:
    如果以上步骤成功执行,函数返回true,表示初始化成功。如果检测到不支持苹果的架构,则输出错误信息并返回false

综上所述,doInitialization函数负责为一个LLVM Pass做准备工作,包括确认目标平台、声明所需的运行时函数,并检查相关特性支持,以便Pass在后续的操作中可以正确地处理Objective-C代码。

1. runOnModule

runOnModule是PASS的通用框架函数,在Pass运行时执行,这个函数的逻辑如下:

  1. 获取全局变量OBJC_LABEL_CLASS_$
    尝试获得一个名为OBJC_LABEL_CLASS_$的全局变量,它包含Objective-C类的信息。

    1
    GlobalVariable *OLCGV = M.getGlobalVariable("OBJC_LABEL_CLASS_$", true);
  2. 检查全局变量:
    检查是否成功获取到了这个全局变量,如果没有,输出错误消息并返回false

    1
    2
    3
    4
    5
    if (!OLCGV) {
      errs() << "No ObjC Class Found in :" << M.getSourceFileName() << "\n";
       // 没有找到ObjC类,Pass不进行处理
      return false;
    }
  3. 获取全局变量的初始化值:
    验证全局变量OLCGV是否有初始化器,并将其转换为常量数组OBJC_LABEL_CLASS_CDS

    1
    2
    3
    4
    5
    6
    assert(OLCGV->hasInitializer() &&
               "OBJC_LABEL_CLASS_$ Doesn't Have Initializer.");
               
    // 获取OBJC_LABEL_CLASS_$的初始化值,即包含所有类信息的常量数组
    ConstantArray *OBJC_LABEL_CLASS_CDS =
            dyn_cast<ConstantArray>(OLCGV->getInitializer());
  4. 类信息处理:
    定义容器来存储类信息:readyclses存储可处理的类,tmpclses临时存储有依赖关系的类,dependency存储类与父类的依赖关系,GVMapping将类名映射到相应的全局变量。

    1
    2
    3
    4
    std::vector<std::string> readyclses;  // 存储可以处理的类
    std::deque<std::string> tmpclses; // 临时存储类的队列,用于处理依赖关系
    std::map<std::string /*class*/, std::string /*super class*/> dependency; // 存储类与其父类的对应关系
    std::map<std::string /*Class*/, GlobalVariable *> GVMapping; // 存储类名与对应的全局变量的映射
  5. 遍历处理类信息:
    遍历OBJC_LABEL_CLASS_CDS数组,提取每个类的名称和父类信息,然后根据是否有父类及父类是否已经初始化来决定将类名放入哪个容器。

    1
    2
    3
    4
    5
    6
    7
    8
    for (...) {
      ...
      if (supclsName == "" || (SuperClassGV && !SuperClassGV->hasInitializer())) {
        readyclses.emplace_back(clsName);// 父类为空或者父类未初始化,说明该类是可以处理的
      } else {
        tmpclses.emplace_back(clsName);// 父类不为空,暂时无法处理,加入临时队列
      }
    }
  6. 顺序处理类依赖:
    使用一个循环,根据dependency中的依赖关系对类进行排序,以确保在处理子类之前,其父类已经处理。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 根据依赖关系排序,保证父类在子类之前处理
    while (tmpclses.size()) {
      std::string clstmp = tmpclses.front();
      tmpclses.pop_front();
      std::string SuperClassName = dependency[clstmp];
      if (SuperClassName != "" &&
          std::find(readyclses.begin(), readyclses.end(), SuperClassName) ==
              readyclses.end()) {
        // 父类还未处理,将该类重新加入临时队列
        tmpclses.emplace_back(clstmp);
      } else {
        // 父类已经处理,将该类加入可以处理的队列
        readyclses.emplace_back(clstmp);
      }
    }
  7. 处理每个类:
    对于readyclses中的每个类,调用handleClass函数进行进一步处理。

    1
    2
    3
    for (std::string className : readyclses) {
      handleClass(GVMapping[className], &M);
    }
  8. 返回值:
    函数返回true,表示模块处理完成。

总结来说,runOnModule函数的目的是处理Objective-C模块中所有的类。它首先会找到一个包含类定义的全局变量,然后遍历该变量中的所有类,并根据每个类是否有父类和父类是否已初始化来建立处理顺序。最后,函数会按照确定的顺序处理每个类。

2. handleClass

接下来就是核心函数handleClass,该函数主要处理Objective-C类的信息,我们分步骤来进行分析:

  1. 类初始化器检查:
    首先,确认传入的GlobalVariable参数GV(代表一个Objective-C类)有一个初始化器。如果没有,断言将失败。

    1
    2
    3
    // 判断类的全局变量是否有初始值,如果没有则直接返回
    assert(GV->hasInitializer() &&
           "ObjC Class Structure's Initializer Missing");
  2. 获取类名和父类名:
    读取GV的初始化值,该值为一个ConstantStruct类型的常量。接着提取类名和父类名,这些信息用于输出日志,以及后续处理。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    ConstantStruct *CS = dyn_cast<ConstantStruct>(GV->getInitializer());
    StringRef ClassName = GV->getName();
    ClassName = ClassName.substr(strlen("OBJC_CLASS_$_"));
    StringRef SuperClassName =
        readPtrauth(
            cast<GlobalVariable>(CS->getOperand(1)->stripPointerCasts()))
            ->getName();
    SuperClassName = SuperClassName.substr(strlen("OBJC_CLASS_$_"));
    errs() << "Handling Class:" << ClassName
           << " With SuperClass:" << SuperClassName << "\n";
  3. 元类和类只读结构提取:
    ConstantStruct中提取出元类(metaclassGV)和只读类信息(class_ro),这些可能包含方法列表等重要数据。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    GlobalVariable *metaclassGV = readPtrauth(
        cast<GlobalVariable>(CS->getOperand(0)->stripPointerCasts()));
    GlobalVariable *class_ro = readPtrauth(
        cast<GlobalVariable>(CS->getOperand(4)->stripPointerCasts()));
    assert(metaclassGV->hasInitializer() && "MetaClass GV Initializer Missing");
    GlobalVariable *metaclass_ro = readPtrauth(cast<GlobalVariable>(
        metaclassGV->getInitializer()
            ->getOperand(metaclassGV->getInitializer()->getNumOperands() - 1)
            ->stripPointerCasts()));
  4. 方法列表检查:

    如果找到了元类中的方法列表,遍历列表并提取方法的名称、类型和实现。

    如果方法的名称是initializeload,这意味着找到了现有的初始化器方法,将记录该方法的入口基本块(EntryBB)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    if (Info.find("METHODLIST") != Info.end()) {
      ConstantArray *method_list = cast<ConstantArray>(Info["METHODLIST"]);
      for (unsigned i = 0; i < method_list->getNumOperands(); i++) {
        ConstantStruct *methodStruct =
            cast<ConstantStruct>(method_list->getOperand(i));
        // 从方法结构体中提取方法的名称、类型和实现
        GlobalVariable *SELNameGV = cast<GlobalVariable>(
            opaquepointers ? methodStruct->getOperand(0)
                           : methodStruct->getOperand(0)->getOperand(0));
        ConstantDataSequential *SELNameCDS =
            cast<ConstantDataSequential>(SELNameGV->getInitializer());
        StringRef selname = SELNameCDS->getAsCString();
        if ((selname == "initialize" && UseInitialize) ||
            (selname == "load" && !UseInitialize)) {
          // 如果找到了现有的初始化方法(initialize或load),则记录EntryBB为该方法的入口基本块
          Function *IMPFunc = cast<Function>(readPtrauth(cast<GlobalVariable>(
              methodStruct->getOperand(2)->stripPointerCasts())));
          errs() << "Found Existing initializer\n";
          EntryBB = &(IMPFunc->getEntryBlock());
        }
      }
    } else {
      errs() << "Didn't Find ClassMethod List\n";
    }
  5. 初始化器方法创建:
    如果没有找到现有的初始化器方法,函数将创建一个新的初始化方法,创建一个新的函数,并将其基本块设置为EntryBB

    1
    2
    3
    4
    5
    6
    7
    8
    9
    if (!EntryBB) {
        FunctionType *InitializerType = FunctionType::get(
            Type::getVoidTy(M->getContext()), ArrayRef<Type *>(), false);
        Function *Initializer = Function::Create(
            InitializerType, GlobalValue::LinkageTypes::PrivateLinkage,
            "AntiClassDumpInitializer", M);
        EntryBB = BasicBlock::Create(M->getContext(), "", Initializer);
        ReturnInst::Create(M->getContext(), EntryBB);
    }
  6. IRBuilder初始化:
    使用找到或创建的基本块EntryBB初始化IRBuilder,用于构建LLVM中间表示(IR)指令。

    1
    IRBuilder<> *IRB = new IRBuilder<>(EntryBB, EntryBB->getFirstInsertionPt());
  7. 准备Objective-C API定义:
    获取objc_getClass函数的声明,它将在后续的处理中使用。

    1
    2
    3
    4
    5
    6
    7
    // We now prepare ObjC API Definitions
    Function *objc_getClass = M->getFunction("objc_getClass");
    // End of ObjC API Definitions
    Value *ClassNameGV = IRB->CreateGlobalStringPtr(ClassName);
    // Now Scan For Props and Ivars in OBJC_CLASS_RO AND OBJC_METACLASS_RO
    // Note that class_ro_t's structure is different for 32 and 64bit runtime
    CallInst *Class = IRB->CreateCall(objc_getClass, {ClassNameGV});
  8. 处理类和元类的方法列表:

    对于元类和类的方法列表,调用HandleMethods函数添加和处理方法。

    如果方法列表不为空,处理实例方法(对于元类)和类方法(对于类本身)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // Add Methods
    ConstantStruct *metaclassCS =
        cast<ConstantStruct>(class_ro->getInitializer());
    ConstantStruct *classCS =
        cast<ConstantStruct>(metaclass_ro->getInitializer());
    if (!metaclassCS->getAggregateElement(5)->isNullValue()) {
      errs() << "Handling Instance Methods For Class:" << ClassName << "\n";
      HandleMethods(metaclassCS, IRB, M, Class, false);
     
      // ...
    }
     
    // 继续处理实例方法和类方法
    // 这里只处理类方法,实例方法的处理类似
    GlobalVariable *methodListGV = nullptr;// 存储类方法列表的全局变量
    if (!classCS->getAggregateElement(5)->isNullValue()) {
      errs() << "Handling Class Methods For Class:" << ClassName << "\n";
      HandleMethods(classCS, IRB, M, Class, true);
      methodListGV = readPtrauth(cast<GlobalVariable>(
          classCS->getAggregateElement(5)->stripPointerCasts()));
    }
  9. 创建新方法列表:
    创建新的方法结构体以及方法列表,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //定义方法类型
    Type *objc_method_type =
        StructType::getTypeByName(M->getContext(), "struct._objc_method");
     
    //创建新方法的结构体
    Constant *newMethod = ConstantStruct::get(
        cast<StructType>(objc_method_type),
        ArrayRef<Constant *>(methodStructContents)); // objc_method_t
     
    //创建新方法的列表
    ArrayType *AT = ArrayType::get(objc_method_type, 1);
    Constant *newMethodList = ConstantArray::get(
        AT, ArrayRef<Constant *>(newMethod)); // Container of objc_method_t
  10. 更新类方法映射:
    更新类方法映射主要是将新创建的方法列表添加到类的结构体中。这一过程的关键代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    //1. 为新方法列表创建结构体类型和实例
    std::vector<Type *> newStructType;
    std::vector<Constant *> newStructValue;
    // ...(省略初始化newStructType和newStructValue的代码)
     
    StructType *newType = StructType::get(M->getContext(), ArrayRef<Type *>(newStructType));
    Constant *newMethodStruct = ConstantStruct::get(newType, ArrayRef<Constant *>(newStructValue)); // l_OBJC_$_CLASS_METHODS_
     
     
    //2. 创建并注册新的全局变量:
    GlobalVariable *newMethodStructGV = new GlobalVariable(
        *M, newType, true, GlobalValue::LinkageTypes::PrivateLinkage,
        newMethodStruct, "ACDNewClassMethodMap");
    appendToCompilerUsed(*M, {newMethodStructGV});
     
    //3. 替换旧的方法列表引用并删除旧的全局变量:
    /**
    ConstantExpr::getBitCast和replaceAllUsesWith负责将新的方法列表安全地转换成所需类型并用其替换旧的方法列表,完成对类结构的更新。
    通过dropAllReferences和eraseFromParent从模块中移除旧的全局变量,确保不会有内存泄漏或其他问题。
    */
    Constant *bitcastExpr = ConstantExpr::getBitCast(
        newMethodStructGV, PointerType::getUnqual(StructType::getTypeByName(
                               M->getContext(), "struct.__method_list_t")));
     
    opaquepointers ? classCS->setOperand(5, bitcastExpr)
                   : classCS->handleOperandChange(
                         classCS->getAggregateElement(5), bitcastExpr);
     
    if (methodListGV) {
        methodListGV->replaceAllUsesWith(ConstantExpr::getBitCast(
            newMethodStructGV,
            methodListGV->getType()));
        methodListGV->dropAllReferences();
        methodListGV->eraseFromParent();
    }

该函数主要包括类结构的验证、类信息的提取、方法列表的处理、新方法的创建和注入,主要是创建新的方法结构并更新类的方法映射,使用bitcast操作将新的方法结构体变量的地址转换为所需的类型,并更新类结构中的方法列表指针,替换旧的方法列表。

3.HandleMethods

最后我们分析HandleMethods函数,该函数处理Objective-C类(或元类)的方法列表,并且可以替换现有的方法实现。

当然,我们将根据提供的代码逐步分析 HandleMethods 函数的实现,并在每个步骤中突出关键代码。

  1. 获取Objective-C运行时函数

这里代码获取Objective-C运行时的关键函数指针,这些函数将用来注册选择器、替换方法以及获取类名和元类。

1
2
3
4
5
// 获取函数指针 `sel_registerName`、`class_replaceMethod`、`class_getName`、`objc_getMetaClass`
Function *sel_registerName = M->getFunction("sel_registerName");
Function *class_replaceMethod = M->getFunction("class_replaceMethod");
Function *class_getName = M->getFunction("class_getName");
Function *objc_getMetaClass = M->getFunction("objc_getMetaClass");
  1. 获取类型 objc_method_list_t_type

获取表示Objective-C方法列表的结构类型信息

1
2
StructType *objc_method_list_t_type =
    StructType::getTypeByName(M->getContext(), "struct.__method_list_t");
  1. 遍历类结构体的元素并跳过空值元素

遍历 class_ro 结构体的每个元素,寻找方法列表,并跳过空值元素。

1
2
3
4
5
6
7
for (unsigned int i = 0; i < class_ro->getType()->getNumElements(); i++) {
  Constant *tmp = dyn_cast<Constant>(class_ro->getAggregateElement(i));
  if (tmp->isNullValue()) {
    continue; // 跳过空值元素
  }
  // ...
}
  1. 检查并处理方法列表

代码检查当前元素是否为方法列表的类型,并处理相应的方法列表。

1
2
3
4
5
6
7
Type *type = tmp->getType();
if ((type == PointerType::getUnqual(objc_method_list_t_type)) ||
    (tmp->getName().startswith("_OBJC_$_INSTANCE_METHODS") ||
     tmp->getName().startswith("_OBJC_$_CLASS_METHODS"))) {
  // 处理方法列表
  // ...
}
  1. 提取并验证方法列表

获取方法列表结构体并验证其是否包含有效的方法。如果方法列表为空,则返回不做进一步处理。

1
2
3
4
5
6
7
8
9
// 读取全局变量,获取方法列表的全局变量 `methodListGV`
GlobalVariable *methodListGV =
    readPtrauth(cast<GlobalVariable>(tmp->stripPointerCasts()));
//检查方法列表的全局变量是否有初始化器,如果没有,则返回
assert(methodListGV->hasInitializer() &&
       "MethodListGV doesn't have initializer");
// 获取方法列表结构体 `methodListStruct`
ConstantStruct *methodListStruct =
    cast<ConstantStruct>(methodListGV->getInitializer());
  1. 遍历方法列表的方法

遍历方法列表中的每个方法。对于每个方法,结构体 methodStruct 包含了方法名、类型签名和方法的实现(IMP)。

1
2
3
4
5
6
7
8
// 从方法列表结构体中提取 `methodList`
ConstantArray *methodList = cast<ConstantArray>(methodListStruct->getOperand(2));
// 遍历方法列表 `methodList` 的每个元素
for (unsigned int i = 0; i < methodList->getNumOperands(); i++) {
  // 获取方法结构体 `methodStruct`
  ConstantStruct *methodStruct = cast<ConstantStruct>(methodList->getOperand(i));
  // ...
}
  1. 提取并注册方法的选择器

对于每个方法,提取方法名并使用 sel_registerName 注册其选择器(SEL)。这是Objective-C的消息发送机制中标识方法的关键。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 提取方法名称
Constant *SELName = IRB->CreateGlobalStringPtr(
    cast<ConstantDataSequential>(
        cast<GlobalVariable>(
            opaquepointers
                ? methodStruct->getOperand(0)
                : cast<ConstantExpr>(methodStruct->getOperand(0))
                      ->getOperand(0))
            ->getInitializer())
        ->getAsCString());
 
// 注册SEL
CallInst *SEL = IRB->CreateCall(sel_registerName, {SELName});
  1. 提取并转换方法实现

提取该方法的实现(IMP),并将其转换为适当的类型以便进一步使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 提取方法实现
Value *BitCastedIMP = IRB->CreateBitCast(
    appleptrauth
        ? opaquepointers
              ? cast<GlobalVariable>(methodStruct->getOperand(2))
                    ->getInitializer()
                    ->getOperand(0)
              : cast<ConstantExpr>(
                    cast<GlobalVariable>(methodStruct->getOperand(2))
                        ->getInitializer()
                        ->getOperand(0))
                    ->getOperand(0)
        : methodStruct->getOperand(2),
    IMPType);
  1. 准备 class_replaceMethod 调用的参数

根据是否是元类,使用 class_getNameobjc_getMetaClass 或直接使用 Class 参数,来准备调用 class_replaceMethod 的参数列表。

1
2
3
4
5
6
7
8
9
10
11
12
std::vector<Value *> replaceMethodArgs;
if (isMetaClass) {
  // 如果是元类(Meta Class),则获取类名并调用objc_getMetaClass函数获取元类
  CallInst *className = IRB->CreateCall(class_getName, {Class});
  CallInst *MetaClass =
      IRB->CreateCall(objc_getMetaClass, {className});
  replaceMethodArgs.emplace_back(MetaClass); // Class
} else {
  replaceMethodArgs.emplace_back(Class); // Class
}
replaceMethodArgs.emplace_back(SEL);          // SEL
replaceMethodArgs.emplace_back(BitCastedIMP); // imp
  1. 提取方法类型

methodStruct 提取方法类型,并将其转换为全局字符串指针。

1
2
3
4
5
6
7
8
9
10
// 提取方法类型
replaceMethodArgs.emplace_back(IRB->CreateGlobalStringPtr(
    cast<ConstantDataSequential>(
        cast<GlobalVariable>(
            opaquepointers
                ? methodStruct->getOperand(1)
                : cast<ConstantExpr>(methodStruct->getOperand(1))
                      ->getOperand(0))
            ->getInitializer())
        ->getAsCString())); // type
  1. 调用 class_replaceMethod 替换方法实现

使用准备好的参数调用 class_replaceMethod,实际替换或注册类中的方法实现。

1
2
// 调用class_replaceMethod函数进行方法的替换和注册
IRB->CreateCall(class_replaceMethod,ArrayRef<Value *>(replaceMethodArgs));
  1. 如果需要,重命名方法实现

如果启用了 RenameMethodIMP 选项,则将方法实现重命名为 "ACDMethodIMP"

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if (RenameMethodIMP) {
// 如果需要重命名方法实现
Function *MethodIMP = cast<Function>(
    appleptrauth
        ? opaquepointers
              ? cast<GlobalVariable>(methodStruct->getOperand(2))
                    ->getInitializer()
                    ->getOperand(0)
              : cast<ConstantExpr>(
                    cast<GlobalVariable>(
                        methodStruct->getOperand(2)->getOperand(0))
                        ->getInitializer()
                        ->getOperand(0))
                    ->getOperand(0)
    : opaquepointers ? methodStruct->getOperand(2)
                     : methodStruct->getOperand(2)->getOperand(0));
MethodIMP->setName("ACDMethodIMP");
}

在整个 HandleMethods 函数中,分析了如何操作Objective-C的运行时以动态修改类的方法列表。显示了如何构建并使用IRBuilder来创建和调用LLVM IR指令,如何使用Objective-C运行时函数,以及如何处理Objective-C类结构体中的方法列表数据。

三、总结

该文章以函数为单位进行了逻辑梳理,并列出了对应的关键代码,仅为个人理解,如果存在理解有误或意义不达的地方还请指正。接下来会针对反HOOK、反Debug进行源码分析,感谢。


[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

最后于 2024-1-9 17:07 被ElainaDaemon编辑 ,原因: 添加原创标签
收藏
点赞4
打赏
分享
最新回复 (4)
雪    币: 291
活跃值: (213372)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
shinratensei 1 2024-1-8 17:19
2
0
tql
雪    币: 19349
活跃值: (28971)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
秋狝 2024-1-9 10:10
3
1
感谢分享
雪    币:
活跃值: (91)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
mb_kclqqlmy 2024-1-9 17:23
4
0
好文, mark
雪    币: 136
活跃值: (549)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
naville 1 2024-1-19 10:01
5
0
不错, 但是原版的实现的一个问题是关键信息基于名称查找, strip过的BitCode会导致解析失败。这部分可能需要优化一下
游客
登录 | 注册 方可回帖
返回