ida_kernelcache 是一个适用于 IDA Pro 的 IDAPython 模块,旨在简化对 iOS kernelcache 的分析工作。 该模块提供以下功能:
__PRELINK_INFO 段解析为 Python 字典。__got 段中的偏移量和 __stubs 段中的桩函数。主处理函数被设计为在任何手动分析或逆向工程之前运行。在默认设置下,IDA 往往会遗漏 kernelcache 中的大量有用信息。这些脚本通过利用 kernelcache 的已知结构自动传播有用信息,从而辅助 IDA。
除了模块自带的标准功能外,ida_kernelcache 还包含多个脚本,使分析 iOS kernelcache 更加容易。例如,您可以使用这些脚本自动生成某个函数所用的 C 结构体。
ida_kernelcache 中使用的许多技术都是为 memctl 开发并直接借鉴而来的。
ida_kernelcache 已在 IDA Pro 6.95 上针对 iOS 10.1.1、11.0、11.2、11.3.1 和 12.0 beta 的 kernelcache 进行了测试。目前仅支持 iOS 10 及更高版本的 Arm64 kernelcache。
您需要先将一个已解压的 kernelcache 文件加载到 IDA 中。您可以在网上找到从 Apple 下载特定 IPSW 的 URL,并且有许多公开工具(包括 memctl)能够解压 kernelcache。
在 IDA 中,从菜单栏选择 "File" -> "Script file...",然后选择主目录中的 ida_kernelcache.py 脚本。这会将 ida_kernelcache 模块以 ida_kernelcache 和 kc 的名称加载到 IDAPython 解释器中。在 IDAPython 提示符下,输入 kc.kernelcache_process() 并按 Enter 开始分析 kernelcache。该函数会执行 ida_kernelcache 支持的所有主要分析。当 IDA 识别并分析新函数时,该函数将运行数分钟。
ida_kernelcache 会尽量避免覆盖地址的用户命名。这意味着,如果在使用 kernelcache_process 初始化之前已对 kernelcache 进行过手动分析,由于用户指定的名称可能会阻止自动名称传播,结果可能不够彻底。但同样无法保证 ida_kernelcache 不会破坏之前的分析结果,因此如果您决定对已经分析过的 kernelcache 文件运行 kernelcache_process,请先进行备份。
ida_kernelcache 旨在通过 ida_kernelcache.py 加载;ida_kernelcache 目录中的子模块并不打算被直接加载。不过,ida_kernelcache 暴露了其中许多子模块的功能。以下是每个子模块的作用:
ida_utilities:
该模块封装了 IDA 的部分函数,以提供更易用的 API。特别有用的是 is_mapped、read_word、read_struct、force_function 和 ReadWords。is_mapped 检查某个地址是否已映射,并可选择检查其是否包含已知值。read_word 从某个地址读取可变大小的字。read_struct 将结构体类型读取到 Python 字典或 Python 访问器对象中,这使解析数据结构变得容易得多。force_function 尝试多种技巧将地址转换为 IDA 中函数的起始位置。ReadWords 是一个生成器,用于在某个范围内迭代数据字及其地址。
build_struct: 该内部模块包含根据对结构体的一系列访问自动填充 IDA 结构体的实用工具。
class_struct:
该模块提供生成表示 C++ 虚方法表和类的 IDA 结构体的函数。initialize_vtable_structs 扫描(已符号化的)虚方法表,并创建 IDA 结构体来保存虚方法指针。initialize_class_structs 对虚方法执行数据流分析,以识别对每个类字段的访问,然后构建表示这些类的 IDA 结构体。看似引用字段的指令也会被转换为结构体偏移引用。更多细节请参阅模块文档字符串。
ida_kernelcache_reload.py 脚本与 ida_kernelcache.py 相同,只是它强制重新加载 ida_kernelcache 模块及其所有子模块。它主要用于开发。
scripts 目录包含使用 ida_kernelcache 执行某种分析的脚本。这些脚本过于具体,不适合作为主 ida_kernelcache 模块的一部分,但在逆向 kernelcache 时非常有用。其中包括:
find_virtual_method_overrides.py: 一个用于查找某个类的后代中覆盖了包含指定字符串的虚方法的脚本。匹配的覆盖方法会打印到控制台。
populate_struct.py: 通过从当前地址开始执行数据流分析,为 C++ 类或 C 结构体填充字段。
process_external_methods.py:
将 IOExternalMethod 或 IOExternalMethodDispatch 数组处理为标准形式,以供模糊测试工具使用。
如果您使用 Hex-Rays 反编译器,ida_kernelcache 更有趣的功能之一是自动 C++ 类重建,它会利用 OSMetaClass 信息和数据流分析来创建表示 kernelcache 中类的 IDA 结构体。这些表示可以显著提高伪代码表示的可读性。要了解更多信息,请参阅文章 Reconstructing C++ classes in the iOS kernelcache using IDA Pro。
在 iOS 12 中,Apple 在某些设备上引入了一种新的 kernelcache 格式。在各项更改中,这种新 kernelcache 的内核指针带有标签以将其链接到列表中,大概是为了让 iBoot 无需 prelink 字典中的 _PrelinkLinkKASLROffsets 数据即可对内核进行滑动。在 IDA 中尝试分析使用这种格式的原版 kernelcache 因缺少交叉引用而十分困难。详情请参阅文章 Analyzing the iOS 12 kernelcache's tagged pointers。
如果您只想去除 kernelcache 中指针的标签而不执行任何其他处理,请运行 kc.tagged_pointers.untag_pointers()。
这里的一些功能很可能不仅适用于 Apple kernelcache(例如 vtable 分析和符号传播,或 ida_utilities.py 中的大部分函数)。尽管如此,我将导入范围限制为仅 ida_kernelcache 模块,因为我尚未在其他类型的二进制文件上测试过这些功能。
ida_kernelcache 以 MIT 许可证发布。
ida_kernelcache 中的许多功能借鉴自同样以 MIT 许可证发布的 memctl。其他来源在相应文件的注释中注明。
Brandon Azad
classes:
该模块定义了 ClassInfo 类型,用于保存 kernelcache 中 C++ 类的信息,并提供 collect_class_info 函数来扫描 kernelcache 中的类,并将类名到 ClassInfo 对象的映射填充到全局 class_info 字典中。ClassInfo 类型记录每个 C++ 类的类名、OSMetaClass 实例、虚方法表和父类名称。此外,每个 ClassInfo 对象都存储对父类 ClassInfo 以及所有直接子类 ClassInfo 的引用,从而便于检查和遍历类层次结构。collect_class_info 还会将所有虚方法表集合存储到全局 vtables 集合中。
data_flow: 该内部模块包含 ida_kernelcache 其余部分所使用的数据流操作。
kernel:
该模块提供 base 和 prelink_info 全局变量。base 是内核映像的基地址(内核 Mach-O 头的起始位置)。prelink_info 是解析后的 __PRELINK_INFO 字典。
kplist:
该模块提供 kplist_parse 函数来解析内核风格的 plist。
metaclass:
该模块提供 initialize_metaclass_symbols 函数,为每个已知的 OSMetaClass 实例添加符号。
offset:
该模块提供 initialize_data_offsets 和 initialize_offset_symbols 函数。前者扫描各个段,查找可转换为偏移量的指针。后者在偏移目标具有符号的情况下,对每个 kext 的 __got 段中的偏移量进行符号化。
segment:
该模块提供 initialize_segments 函数,用于将 IDA 的段重命名为更有用的名称。默认情况下,IDA 似乎通过将 Bundle Identifier 的猜测值与描述该区域的 Mach-O section 组合来创建段名。initialize_segments 从 __PRELINK_INFO 字典中提取真正的 Bundle Identifier,并重命名每个段以包含 Bundle Identifier、Mach-O 段和 Mach-O section。例如,这使我们能够区分 __TEXT.__const 和 __DATA_CONST.__const。该模块还提供 kernelcache_kext 函数(在顶层重新导出),用于确定包含指定地址的 kext(仅适用于旧的 iOS 11 split-kext kernelcache 格式)。
stub:
kernelcache 中的许多 kext 在 __stubs 段中包含跳转到内核主体函数的桩函数。不幸的是,这些桩函数阻碍了交叉引用和类型信息的传播。该模块并不能解决这些问题,但通过自动重命名桩函数使目标函数名可见,确实让查看桩函数变得更容易。桩函数及其目标会被强制转换为 IDA 中的函数,这有助于使 IDA 中的函数与原始源代码中的函数对应起来。
tagged_pointers: 新的 iOS 12 合并 kernelcache 格式中,每个指针的高 2 字节带有一个偏移量标签,以便将指针以链表形式串联起来。该模块包含处理和恢复这些标记指针的函数。
vtable:
该模块提供了许多用于处理虚方法表的有用函数,包括 vtable_length、convert_vtable_to_offsets、vtable_overrides、initialize_vtable_symbols 和 initialize_vtable_method_symbols。vtable_length 检查指定地址是否可能是 vtable,并返回 vtable 长度。生成器 vtable_overrides 枚举类中覆盖父类虚方法的那些虚方法。initialize_vtable_symbols 函数为每个识别出的 vtable 的起始位置添加符号。initialize_vtable_method_symbols 遍历每个 vtable 中被覆盖的方法,并将符号从父类传播到子类。这是可行的,因为 IOKit 中的大多数基类都是在 XNU 中定义的,并且具有相对完整的符号信息。子类 vtable 中的每个方法覆盖都必须与父类中的方法具有相同的接口,这意味着我们可以通过将子类名替换到父类虚方法符号中的父类名位置来生成该覆盖方法的符号。例如,如果我们不知道 AppleKeyStore 类中索引 7 处的虚方法名称,但我们知道其父类 IOService 中索引 7 处的虚方法叫做 __ZNK9IOService12getMetaClassEv,那么我们可以推断子类中索引 7 应该叫做 __ZNK13AppleKeyStore12getMetaClassEv。这种技术可用于为大多数类中的大多数虚方法进行符号化。