1. Mach-O文件中的__objc_classlist节解析
在逆向工程和Objective-C运行时分析中,Mach-O文件的__objc_classlist节是一个关键数据结构。这个节区存储了二进制文件中所有Objective-C类的引用信息,是理解iOS/macOS应用类结构的重要入口。
1.1 Mach-O文件基础结构回顾
Mach-O是macOS和iOS系统使用的可执行文件格式,由多个加载命令(Load Command)和节(Section)组成。每个__objc_开头的节都对应Objective-C运行时的特定数据结构:
- __objc_classlist:类引用列表
- __objc_protolist:协议列表
- __objc_selrefs:方法选择器引用
- __objc_classrefs:类引用
- __objc_superrefs:父类引用
这些节区在链接时由编译器生成,在运行时被dyld动态加载器解析。通过分析这些数据结构,我们可以还原出应用的完整类继承关系和方法调用链路。
1.2 __objc_classlist节的结构解析
__objc_classlist节本质上是一个指针数组,每个指针指向一个objc_class结构体。在64位系统上,每个指针占8字节。结构示意如下:
__objc_classlist: 0x100000000 -> ClassA 0x100000008 -> ClassB 0x100000010 -> ClassC对应的objc_class结构体包含以下关键字段(以arm64为例):
struct objc_class { uint64_t isa; // 元类指针 uint64_t superclass; // 父类指针 uint64_t cache; // 方法缓存 uint64_t vtable; // 虚函数表 uint64_t data; // 指向class_ro_t结构 };其中data字段指向的class_ro_t结构包含类名、方法列表、属性列表等核心信息。这个结构在运行时会被复制并修改为class_rw_t。
2. 从二进制角度解析类信息
2.1 使用otool分析__objc_classlist
macOS自带的otool工具可以直接查看Mach-O文件的节区信息:
otool -s __OBJC __objc_classlist MyApp对于加密的AppStore应用,需要先进行解密操作。更直观的方法是使用MachOView等GUI工具,可以直接看到节区的十六进制内容和对应的反解析结构。
2.2 类信息的完整解析流程
- 定位__objc_classlist节的文件偏移和大小
- 遍历其中的每个指针,找到对应的objc_class结构
- 解析data字段指向的class_ro_t结构:
- 获取类名(指向__objc_classname节的字符串)
- 解析方法列表(指向__objc_method_list)
- 解析属性列表(指向__objc_proplist)
- 解析协议列表(指向__objc_protolist)
- 递归解析父类信息
注意:在iOS 12及以上系统,部分数据结构采用了Relative Method Lists等优化技术,指针存储的是相对偏移量而非绝对地址。
3. 运行时与静态分析的差异
3.1 动态注册的类
通过objc_allocateClassPair动态创建的类不会出现在__objc_classlist中。这类类通常用于:
- 插件系统动态加载
- KVO生成的中间类
- 某些桥接技术实现
3.2 类别(Category)的处理
类别信息存储在__objc_catlist节,不会修改原始类的__objc_classlist条目。但运行时会将类别方法"附加"到原始类的方法列表中。
3.3 懒加载类
标记为__DATA.__objc_lazy_class的类会在首次使用时才初始化,但仍在__objc_classlist中有记录。
4. 实际案例分析
4.1 识别未使用的类
通过交叉分析__objc_classlist和__objc_classrefs,可以找出定义了但从未被引用的"僵尸类":
- 收集__objc_classlist中的所有类
- 收集__objc_classrefs中所有被引用的类
- 取差集得到未使用的类
4.2 类继承关系重建
通过递归解析每个类的superclass指针,可以构建完整的类继承树。典型工具如class-dump就是基于这个原理。
4.3 Swift类的特殊处理
Swift类也会出现在__objc_classlist中,但有以下特点:
- 类名带有模块前缀(如_TtC5MyApp10MyViewController)
- 方法名经过名称重整
- 可能包含额外的Swift特定数据段
5. 常见问题排查
5.1 指针解析错误
在手动解析时常见的错误包括:
- 未考虑PIE(位置无关代码)导致的偏移计算错误
- 混淆文件偏移和虚拟地址
- 未处理加密的Mach-O文件
解决方案:
- 使用dyldinfo命令查看正确的加载地址
- 对加密文件先用frida-ios-dump等工具解密
5.2 数据结构版本差异
不同iOS版本的数据结构可能有变化:
- iOS 10之前:直接指针
- iOS 10-12:部分相对偏移
- iOS 13+:更多压缩优化
建议使用最新的objc4源码作为参考。
5.3 工具链选择建议
- 静态分析:MachOView、otool、objc4源码
- 动态分析:Cycript、Frida、LLDB
- 自动化工具:class-dump、Hopper Disassembler
我个人在实际逆向工程中发现,结合静态分析和runtime introspection(如method_getImplementation)能获得最准确的结果。对于大型应用,建议先通过__objc_classlist筛选出目标类,再针对性地深入分析。