论坛首页 逆向工程技术区 阅读主题

[原创]Hikari源码分析 - AntiClassDump

235 浏览 6 回复
#1 楼主 2026-06-01 21:09:18
之前的文章总结了混淆过程中遇到的问题,以及最终的解决方案,接下来将会用三四篇文章来逐一介绍下各种PASS的实现细节与原理,基本的控制流伪造、虚假控制流、字节替换等将不再复述,这些PASS已经有很多成熟的解析文章参考。该系列文章主要探讨Hikari中如何实现反class dump、反debug、反hook等,本文章分析AntiClassDump的实现细节。以下源码参考来自9a3K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1j5$3x3h3u0U0k6r3g2X3k6#2)9J5c8V1S2A6K9$3q4J5K9g2)9J5k6p5I4x3g2V1@1I4y4b7`.`. ,感谢Hikari原作者以及更多贡献者的付出。Objective-C 代码中以 - 开头的方法是实例方法。它属于类的某一个或某几个实例对象,类对象必须实例化后才可以使用。以 + 开头的方法是类方法。Objc中的类方法类似Java中的static方法,它是属于类本身的方法,不需要实例化类,用类名即可使用。用 Hopper 查看反编译的 Object-C 项目,对照源代码和 struct __objc_data、 struct __objc_method、 struct __objc_method_list 这几个结构体,可以看到:+initialize 和 +load 方法都是 NSObject 类的初始化方法,调用顺序均为:基类->子类。区别是 +load 在类被添加到 runtime 时调用,+initialize 在类接收到第一条消息时调用。Category 的作用是为已经存在的类添加方法。Category 声明文件和实现文件统一采用“原类名+Category名”的方式命名。下面的代码定义了一个Category MyAddition,向类 MyClass 添加了函数 printNameAddition:实现相应方法并编译为bitcode assembly查看,发现多了下面一些结构体:其中,全局变量 @"\01l_OBJC_$_CATEGORY_MyClass_$_MyAddition" 是一个结构体 struct _category_t,其定义如下:将程序编译为二进制后再查看,可以发现已经找不到 MyAddition 这一名称,而函数 printNameAddition() 已经在 MyClass 的方法列表中。Category的方法被放到了方法列表的前面。因此由于查找方法时是顺序查找的,category的方法会“覆盖”掉原来类的同名方法。整体来讲,该PASS的实现原理主要是在ObjC类的初始化函数中插入相关代码以防止反编译,大致步骤如下:对类的方法进行重命名:可以通过替换类的方法实现,给方法添加新的名字。这样反编译工具在分析代码时会遇到重命名的方法,增加了阅读和理解代码的困难度。修改类的方法实现:可以向方法的实现中插入一些无用的代码片段,或者进行代码混淆,使得反编译工具难以还原出原始的代码逻辑。加入代码验证逻辑:可以向方法的实现中插入一些验证逻辑,例如检查函数参数、返回值等,对代码进行验证,防止反编译工具逆向分析。使用编译器提供的安全特性:例如Apple的ptrauth和Opaque Pointers,可以使得函数指针和类指针更难以被破解和篡改。总体来说,通过在ObjC类的初始化函数中插入代码,可以增加反编译的难度,使得反编译工具难以还原出原始的代码逻辑和结构,保护代码的安全性和保密性。doInitialization是Pass的初始化函数,执行时会进行以下操作:获取和检查目标三元组:
代码首先获取了模块(Module)的目标三元组信息,并存储在变量triple中,这通常包括了架构、厂商和操作系统信息。然后,代码检查了这个三元组是否代表苹果公司的架构,因为此Pass专门用于处理苹果的Objective-C实现。定义基础类型:
接下来,定义了一个指向int8类型指针的LLVM类型(Int8PtrTy),这个类型在Objective-C运行时函数声明中会用到。添加Objective-C运行时函数声明:
然后,代码创建了各种Objective-C运行时函数的类型,并使用这些类型来声明这些函数。这在后续的Pass执行中,可能会用于插入或修改这些函数的调用。例如,声明了class_replaceMethod和sel_registerName函数,这些函数分别用于替换类中的方法和注册方法的选择器(selector)。判断特性支持:
最后,代码检查当前的模块是否支持Apple的指针认证(appleptrauth)和LLVM上下文是否支持不透明指针(opaquepointers)。通过这两个布尔变量将决定后续Pass的行为,尤其是在处理指针和类型时。返回值:
如果以上步骤成功执行,函数返回true,表示初始化成功。如果检测到不支持苹果的架构,则输出错误信息并返回false。综上所述,doInitialization函数负责为一个LLVM Pass做准备工作,包括确认目标平台、声明所需的运行时函数,并检查相关特性支持,以便Pass在后续的操作中可以正确地处理Objective-C代码。runOnModule是PASS的通用框架函数,在Pass运行时执行,这个函数的逻辑如下:获取全局变量OBJC_LABEL_CLASS_$:
尝试获得一个名为OBJC_LABEL_CLASS_$的全局变量,它包含Objective-C类的信息。检查全局变量:
检查是否成功获取到了这个全局变量,如果没有,输出错误消息并返回false。获取全局变量的初始化值:
验证全局变量OLCGV是否有初始化器,并将其转换为常量数组OBJC_LABEL_CLASS_CDS。类信息处理:
定义容器来存储类信息:readyclses存储可处理的类,tmpclses临时存储有依赖关系的类,dependency存储类与父类的依赖关系,GVMapping将类名映射到相应的全局变量。遍历处理类信息:
遍历OBJC_LABEL_CLASS_CDS数组,提取每个类的名称和父类信息,然后根据是否有父类及父类是否已经初始化来决定将类名放入哪个容器。顺序处理类依赖:
使用一个循环,根据dependency中的依赖关系对类进行排序,以确保在处理子类之前,其父类已经处理。处理每个类:
对于readyclses中的每个类,调用handleClass函数进行进一步处理。返回值:
函数返回true,表示模块处理完成。总结来说,runOnModule函数的目的是处理Objective-C模块中所有的类。它首先会找到一个包含类定义的全局变量,然后遍历该变量中的所有类,并根据每个类是否有父类和父类是否已初始化来建立处理顺序。最后,函数会按照确定的顺序处理每个类。接下来就是核心函数handleClass,该函数主要处理Objective-C类的信息,我们分步骤来进行分析:类初始化器检查:
首先,确认传入的GlobalVariable参数GV(代表一个Objective-C类)有一个初始化器。如果没有,断言

...(已截断)

---
来源: 看雪论坛
原文链接: https://bbs.kanxue.com/thread-280139.htm
#2 2026-06-01 21:09:18
tql
#3 2026-06-01 21:09:18
感谢分享
#4 2026-06-01 21:09:18
好文, mark
#5 2026-06-01 21:09:18
不错, 但是原版的实现的一个问题是关键信息基于名称查找, strip过的BitCode会导致解析失败。这部分可能需要优化一下
#6 2026-06-01 21:09:18
师傅,仓库好像跑路了,方便发一份源码吗,网址找了半天没看到,麻烦啦
#7 2026-06-01 21:09:18
您好,寻ios系统擅长漏洞、逆向技术,手机方向业务;高回报,有兴趣可加Q:10290011

请登录后参与讨论

立即登录 注册账号