本框架是我在 Kernelcache 逆向工程中的最终成果。我通常通过手动审计内核及其扩展来寻找漏洞,并自动化了大多数我真正希望在 Ghidra 中看到的功能,以加速逆向过程。事实证明,这种方法非常有效且节省了大量时间。该框架适用于 iOS 12/13/14/15 和 macOS 11/12(包括 kernelcache 和单个 KEXT),并将其公开,旨在帮助人们无需费力准备自己的环境即可开始研究 iOS 内核。我相信,该框架(包括其提供的工具集以及一些 IOKit 基础知识)足以入门 Kernelcache 破解。
该框架完全使用 Python 编写,并且可以扩展以构建其他工具。它提供了一些基本 API,你几乎可以在任何项目中使用它们,从而节省阅读冗长手册的时间。欢迎阅读 utils/ 目录中的核心功能。
Ghidra 在分析 Kernelcache 时表现出色,但与其他逆向工程工具一样,它需要一些手动工作。ghidra_kernelcache 提供了一个良好的起点,可以在开始时甚至在进行逆向工程时修复问题,从而提供整洁的反编译器输出。
有一个由 @_bazad 在 IDAPro 中创建的类似项目,名为 ida_kernelcache,它为希望使用内核镜像在 IDA 中工作的研究人员提供了一个良好的起点。我的框架看起来与 Brandon 的工作有些相似,但通过提供更多功能来减轻使用 kernelcache 的痛点,从而超越了它。
::externalMethod() 和 ::getTargetAndMethodForIndex() 的外部方法调度表。这些功能被实现为独立的工具,可以通过快捷键或点击工具栏中的图标来执行。
克隆仓库:```sh git clone https://github.com/0x36/ghidra_kernelcache.git
**重要提示**:该项目已在 Ghidra 10.1_PUBLIC 和 10.2_DEV 上测试过,且不向后兼容。
转到 *`Windows → 脚本管理器`*,点击 *`脚本目录`*,然后将 *`ghidra_kernelcache`* 添加到目录路径列表中。
转到 *`Windows → 脚本管理器`*,在 *脚本* 列表中,转到 *`iOS→kernel`* 类别,勾选那里的插件,它们将出现在 GHIDRA 工具栏中。
在 [logos/](https://github.com/0x36/ghidra_kernelcache/tree/master/logos) 目录中,你可以为每个工具放置自己的 logo。
## iOS kernelcache 符号化
`ghidra_kernelcache` 在第一阶段需要 [iometa](https://github.com/Siguza/iometa/)(由 [@s1guza](https://twitter.com/s1guza) 制作),这是一个强大的工具,提供内核二进制中的 C++ 类信息。它的优点是作为一个独立二进制运行,因此只需解析其输出即可导入到你喜欢的逆向工程框架中。我的框架接收 iometa 的输出并进行解析,以进行符号化并修复虚函数表。
### 使用说明
解压内核后,运行以下命令:```sh
$ iometa -n -A /tmp/kernel A10-legacy.txt > /tmp/kernel.txt
# if you want also to symbolicate using jtool2
$ jtool2 --analyze /tmp/kernel
在 Ghidra 中加载内核缓存,不要使用批量导入,作为 Mach-O 镜像加载。
在加载并自动分析内核缓存后,点击工具栏中显示的图标,或直接按 Meta-Shift-K,然后输入 iometa 输出的完整路径,在本例中为 /tmp/kernel.txt。
如果你想使用 jtool2 符号,也可以使用位于 iOS→kernel 类别中的 jsymbol.py。
完整的 API 示例位于 ghidra_kernelcache/kc.py
→ 以下是一些操作类对象的示例:```py from utils.helpers import * from utils.class import * from utils.iometa import ParseIOMeta
ff = "/Users/mg/ghidra_ios/kernel.txt" iom = ParseIOMeta(ff) Obj = iom.getObjects() kc = kernelCache(Obj)
kc.process_all_classes()
kc.process_classes_for_bundle("com.apple.iokit.IOSurface")
kc.process_classes_for_bundle("kernel")
kc.process_class("IOGraphicsAccelerator2")
kc.clear_class_structures()
kc.update_classes_vtable()
kc.explore_pac()
如你所见,你可以完全或部分符号化内核缓存(kernelcache),如果选择了部分符号化,`ghidra_kernelcache` 会在继续之前自动构建所有类的依赖关系。
如果你对整个内核缓存运行脚本(完全符号化),`ghidra_kernelcache` 将花费几分钟来分析内核映像。
完成后,Ghidra 将提供以下内容:
→ 在书签过滤器(Bookmark Filter)中新增了一个名为 "iOS" 的类别:
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image1.png" alt="image1" width="200"/>
→ IOKit 类的虚函数表被添加到 "iOS" 书签中,以便更快、更好地查找虚函数表,你只需在搜索栏中输入字母、单词或 kext 包名即可查找特定的 kext 或类。
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image2.png" alt="image2"/>
→ 修复虚函数表:反汇编/编译未知代码,修复命名空间,重新符号化类方法,并为每个方法应用函数定义:
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image3.png" alt="image3"/>
→ 创建类命名空间,并将每个方法放入其对应的命名空间:
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image4.png" alt="image4"/>
→ 根据类层次结构创建类结构:
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image5.png" alt="image5"/>
→ 创建类虚函数表,并且每个方法都有其自己的方法定义,以获得更好的反编译输出:
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image6.png" alt="image6"/>
完整实现可在 [`utils/class.py.`](https://github.com/0x36/ghidra_kernelcache/blob/master/utils/class.py) 中找到。
以下是使用 `ghidra_kernelcache` 符号化前后的截图对比:
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image7.png" alt="image7"/>
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image8.png" alt="image8"/>
## macOS Kext 符号化
---
`ghidra_kernelcache` 对 macOS 的支持包括对整个内核缓存(kernelcache)和单个 KEXT 的符号化,支持 ARM64e 和 x86_64 架构。
**重要提示:** 在撰写本文时,Ghidra 无法解析整个 macOS 内核缓存,但可以将其加载到 IDA 中进行初步分析,然后将数据库(idb 转 xml)导入 Ghidra,但这超出了本文范围。如果你成功做到了,`ghidra_kernelcache` 将处理其余部分。
在符号化任何 macOS 内核扩展之前,需要完成一些步骤,因为 `ghidra_kernelcache` 的主要目标是重建类层次结构并将所有类结构管理到单个数据库中,而一个内核扩展无法满足这些要求,这意味着对单个 KEXT 的符号化需要对内核(可能还有其他依赖的内核扩展)进行符号化,因此这里需要做一些额外的工作。
`ghidra_kernelcache` 现在提供了一种强大的方法来符号化内核扩展(包括内核本身),通过 Ghidra 强大的 *DataType Project Archive* 来管理和共享类结构及虚方法定义。
### 符号化内核扩展的步骤
- 在 Ghidra 项目中创建一个新文件夹,然后将 `/System/Library/Kernels/kernel.release.XXXXX` 加载到该文件夹中,让 Ghidra 进行分析。
- 创建新的 *Project Archive*:进入 `DataType Provider` → 点击窗口右上角的箭头 → `New Project Archive` → 将其放置在新创建的文件夹中 → 命名(例如 macOS_12.1)。
- 现在使用 `ghidra_kernelcache` 符号化内核,该过程与 iOS *kernelcache* 符号化非常相似。```bash
$ iometa -n -A /System/Library/Kernels/kernel.release.t8101 > /tmp/kernel.txt
KM.py 脚本中找到完整的脚本实现:```pyfrom utils.helpers import * from utils.kext import * iom = ParseIOMeta("/tmp/kernel.txt") Obj = iom.getObjects() kc = Kext(Obj,shared_p="macOS_12.1") kc.process_kernel_kext()
- 完成后,内核归档与 `macOS_12.1` 归档之间已建立数据库关联。现在,右键点击 `kernel.release.t8001` → `Commit DataTypes To` → `macOS_12.1`。
- 然后 `右键点击` → `全选` → `提交`。
- 保存项目归档:`右键点击` → `保存归档`。
我们刚刚创建了一个项目归档,可在所有内核扩展之间共享。
以 Apple Silicon 的 `IOSurface` Kext 为例:```bash
$ lipo /System/Library/Extensions/IOSurface.kext/Contents/MacOS/IOSurface -thin arm64e -output /tmp/iosurface.arm64e
$ iometa -n -A /tmp/iosurface.arm64e > /tmp/iosurface.txt
macOS_12.1:转到 Data Type Manager → Open Project Archive,然后选择 macOS_12.1。KM.py 中找到:```python
from utils.helpers import *
from utils.kext import *kc = Kext(Obj,shared_p="macOS_12.1")
kc.depac()
kc.process_kernel_kext()
**重要提示**:有时候 `kc.process_kernel_kext()` 会失败,因为 Ghidra 无法解析一些 C++ 符号。要解决此问题,请前往脚本管理器并运行 `DemangleAllScript.java` 脚本,然后 重新启动 `kc.process_kernel_kext()` 再次。
### 自定义类
某些情况下,有些 C++ 类 `ghidra_kernelcache` 和 `iometa` 无法符号化,因此添加了一个新功能来处理这种情况。
`Custom()` 类重建会遍历所有 `::vtable` 符号,并检查该类是否已定义,如果未定义,则会自动创建类结构、每个识别出的类方法的函数定义、一个命名空间以及每个类的虚函数表。
目前仅在 macOS 上支持自定义类的创建。```bash
$ iometa -n -A /System/Library/Kernels/kernel.release.t8101 > /tmp/kernel.txt
$ iometa -n -A <kext_path> >> /tmp/kernel.txt
INPUT:```py
from utils.helpers import * from utils.custom_kc import *
if name == "main": default = "/tmp/kernel.txt" ff = askString("iometa symbol file","Symbol file: ",default) iom = ParseIOMeta(ff) Obj = iom.getObjects()
kc = Custom(Obj)
kc.process_all_classes()
kc.explore_pac()
## 杂项脚本
---
### 导入 KDK 的 Dwarf4
Ghidra 有时无法加载对应的 `.dsym` 目录,我编写了一个小脚本来解决此问题。该脚本可在[此处](https://github.com/0x36/ghidra_kernelcache/blob/master/dwarf4_fix.py)找到。
**用法**:从 KDK 路径加载内核,让 Ghidra 完成分析,然后运行 `dwarf_fix.py`,它将加载符号,此过程可能需要几分钟。
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image12.png" alt="image12"/>