
un framework de Ghidra para la ingeniería inversa del kernelcache de iOS
# ghidra_kernelcache: un framework de Ghidra para el kernelcache de iOS para ingeniería inversa
Este framework es el producto final de mi experiencia en la ingeniería inversa del Kernelcache. Normalmente busco vulnerabilidades auditando manualmente el kernel y sus extensiones, y he automatizado la mayoría de las cosas que realmente quería ver en Ghidra para agilizar el proceso de reversión, lo que ha demostrado ser eficaz y ahorra mucho tiempo.
El framework funciona en iOS 12/13/14/15 y en macOS 11/12 (tanto kernelcache como KEXT individual) y se ha hecho público con la intención de ayudar a las personas a comenzar a investigar sobre el kernel de iOS sin la dificultad de preparar su propio entorno.
Como creo, este framework (incluyendo el conjunto de herramientas que proporciona y con algunos conocimientos básicos de IOKit) es suficiente para empezar a hackear el Kernelcache.
El framework está completamente escrito en Python y se puede extender para construir otras herramientas; proporciona algunas API básicas que se pueden usar en casi cualquier proyecto y ahorrar tiempo de leer el manual detallado. Eres bienvenido a leer las funcionalidades principales en el directorio **[utils/](https://github.com/0x36/ghidra_kernelcache/tree/master/utils)**.
Ghidra es bueno cuando se trata de analizar Kernelcaches, pero como otras herramientas de RE, requiere cierto trabajo manual; `ghidra_kernelcache` proporciona un buen punto de entrada para corregir cosas al inicio e incluso mientras se realiza la ingeniería inversa, proporcionando así una salida del descompilador de buen aspecto.
Hay un proyecto similar realizado por [@_bazad](https://twitter.com/_bazad) en IDAPro llamado [ida_kernelcache](https://github.com/bazad/ida_kernelcache) que proporciona un buen punto de entrada para investigadores que quieran trabajar con la imagen del kernel en IDA. Mi framework se parece un poco al trabajo de Brandon y va más allá al proporcionar muchas más características para hacer que el proceso de trabajar con el kernelcache sea menos tedioso.
## Características:
- Simbolización del kernelcache de *OS.
- Reconstrucción de jerarquías de clases C++ y tablas virtuales.
- Referencias a llamadas a métodos virtuales.
- Corrección automática de la tabla de despacho de métodos externos tanto para `::externalMethod()` como para `::getTargetAndMethodForIndex()`.
- Aplicación de espacios de nombres a métodos de clase.
- Propagación de nombres de símbolos y tipos sobre argumentos de funciones.
- Aplicación de firmas de funciones para funciones conocidas del kernel.
- Importación de estructuras y clases antiguas de un proyecto anterior a un proyecto nuevo.
Estas características se implementan como herramientas separadas que se pueden ejecutar mediante atajos de teclado o haciendo clic en sus iconos en la barra de herramientas.
## Instalación
Clona el repositorio:```sh
git clone https://github.com/0x36/ghidra_kernelcache.git
```
**Nota importante**: El proyecto ha sido probado en Ghidra 10.1_PUBLIC y 10.2_DEV y no es compatible con versiones anteriores.
Ve a *`Windows → Script Manager`,* haz clic en *`script Directory` ,* luego agrega *`ghidra_kernelcache`* a la lista de rutas de directorios.
Ve a *`Windows → Script Manager`,* en el listado de *scripts*, ve a la categoría *`iOS→kernel`* y marca los plugins que se ven allí, aparecerán en la Barra de Herramientas de GHIDRA.
En el directorio [logos/](https://github.com/0x36/ghidra_kernelcache/tree/master/logos), puedes poner tus propios logos para cada herramienta.
## Simbolización de iOS kernelcache
`ghidra_kernelcache` requiere en la primera etapa [iometa](https://github.com/Siguza/iometa/) (creado por [@s1guza](https://twitter.com/s1guza)), una herramienta potente que proporciona información de clases C++ en el binario del kernel, lo bueno es que funciona como un binario independiente, por lo que la salida se puede importar a tu framework de RE favorito simplemente analizándola. Mi framework toma la salida de iometa y la analiza para simbolizar y arreglar tablas virtuales.
### Uso
Después de descomprimir el kernel, ejecuta los siguientes comandos :```sh
$ iometa -n -A /tmp/kernel A10-legacy.txt > /tmp/kernel.txt
# if you want also to symbolicate using jtool2
$ jtool2 --analyze /tmp/kernel
```
Cargue el kernelcache en Ghidra, ***NO USE IMPORTACIÓN POR LOTES*** , cárguelo como imagen Mach-O.
Después de que el kernelcache se haya cargado y analizado automáticamente, haga clic en el icono mostrado en la barra de herramientas o simplemente presione **Meta-Shift-K**, luego coloque la ruta completa de la salida de iometa que es `/tmp/kernel.txt` en nuestro caso.
Si desea usar símbolos de `jtool2`, puede usar `jsymbol.py` ubicado en la categoría `iOS→kernel`.
### API de kernelcache de iOS
Ejemplos completos de la API están en [`ghidra_kernelcache/kc.py`](https://github.com/0x36/ghidra_kernelcache/blob/master/KC.py)
→ Aquí hay algunos ejemplos de manipulación de objetos de clase:```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)
# symbolicate the kernel
kc.process_all_classes()
# symbolicate the classes under com.apple.iokit.IOSurface bundle
kc.process_classes_for_bundle("com.apple.iokit.IOSurface")
# symbolicate the classes under __kernel__ bundle
kc.process_classes_for_bundle("__kernel__")
# Process one class (including its parents)
kc.process_class("IOGraphicsAccelerator2")
# Clears the content of the class structures (vtables are excluded)
kc.clear_class_structures()
# Overwrite the old vtable structure definition and resymbolicate it again
kc.update_classes_vtable()
# Reconstructing function call trees by enumerating all pac references and find their corresponding virtual method call
kc.explore_pac()
```
Como puedes ver, puedes simbolizar completa o parcialmente el kernelcache, si se eligió la simbolización parcial, `ghidra_kernelcache` construirá automáticamente todas las dependencias de clases antes de continuar.
Si ejecutas el script contra todo el kernelcache (simbolización completa), `ghidra_kernelcache` tardará varios minutos en analizar la imagen del kernel.
Una vez terminado, Ghidra proporcionará lo siguiente :
→ Se ha añadido una nueva categoría en el Filtro de Marcadores llamada "iOS":
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image1.png" alt="image1" width="200"/>
→ Las tablas virtuales de clases IOKit se añaden al Marcador 'iOS' para una búsqueda de tabla virtual más rápida y mejor; puedes buscar una kext o una clase proporcionando letras, palabras o el bundle de la kext en la barra de búsqueda.
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image2.png" alt="image2"/>
→ Corrección de la tabla virtual: desensambla/compila código desconocido, corrige espacios de nombres, resimboliza los métodos de clase y aplica la definición de función a cada método:
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image3.png" alt="image3"/>
→ Creación de espacios de nombres de clase y coloca cada método en su propio espacio de nombres correspondiente:
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image4.png" alt="image4"/>
→ Creación de la estructura de clase respetando la jerarquía de clases:
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image5.png" alt="image5"/>
→ Creación de tablas virtuales de clase, y cada método tiene su propia definición de método para una mejor salida de descompilación:
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image6.png" alt="image6"/>
La implementación completa se puede encontrar en [`utils/class.py.`](https://github.com/0x36/ghidra_kernelcache/blob/master/utils/class.py)
Aquí hay algunas capturas de pantalla de antes/después de simbolizar usando `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 symbolication
---
El soporte de `ghidra_kernelcache` para macOS es tanto para la simbolización del kernelcache como de una sola KEXT para las arquitecturas ARM64e y x86_64.
**IMPORTANTE:** Al momento de escribir esto, Ghidra no puede analizar todo el kernelcache de macOS, pero es posible cargarlo en IDA para un análisis inicial y luego importar la base de datos (idb a xml) a Ghidra más tarde, pero esto está fuera del alcance. Si logras hacerlo, `ghidra_kernelcache` se encargará del resto.
Hay algunos pasos que deben tomarse antes de simbolizar cualquier Extensión de Kernel de macOS, ya que el objetivo principal de `ghidra_kernelcache` es reconstruir la jerarquía de clases y gestionar todas las estructuras de clases en una sola base de datos, una extensión de kernel no cumple con esos requisitos, lo que significa que la simbolización de una sola KEXT requiere una simbolización del kernel y posiblemente de otras Extensiones de Kernel de las que depende, por lo que se necesita un trabajo adicional aquí.
`ghidra_kernelcache` ahora proporciona una forma potente de simbolizar Extensiones de Kernel, incluido el kernel, gestionando y compartiendo estructuras de clases y definiciones de métodos virtuales a través del potente *DataType Project Archive* de Ghidra.
### Pasos para simbolizar una Extensión de Kernel
- Crea una nueva carpeta en tu proyecto de Ghidra, luego carga ` /System/Library/Kernels/kernel.release.XXXXX` en esa carpeta y deja que Ghidra lo analice.
- Crea un nuevo *Project Archive*: Ve a `DataType Provider` → Haz clic en la flecha en la parte superior derecha de la ventana → `New Project Archive` → Colócalo dentro de la carpeta recién creada → Ponle un nombre (por ejemplo, macOS_12.1).
- Ahora simboliza el kernel usando `ghidra_kernelcache`, el proceso es bastante similar a la simbolización de iOS *kernelcache*.```bash
$ iometa -n -A /System/Library/Kernels/kernel.release.t8101 > /tmp/kernel.txt
```
- En la consola de Python, o puedes encontrar la implementación completa del script en `KM.py` script :```py
>>> from 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()
```
- Una vez terminado, se ha creado una asociación de base de datos entre el archivo del kernel y el archivo `macOS_12.1`. Ahora, haga clic derecho en `kernel.release.t8001` → `Commit DataTypes To` → `macOS_12.1`.
- Luego `Right Click` →`Select All` → `Commit`.
- Guarde el archivo del proyecto: `Right click` → `Save Archive`.
Acabamos de crear un archivo de proyecto que se puede compartir entre todas las Extensiones del Kernel.
Tomemos un ejemplo de `IOSurface` Kext para Apple Silicon :```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
```
- Cargue el Kext en la misma ruta de carpeta donde se encuentran la base de datos del kernel y el archivo del proyecto, y deje que Ghidra termine el análisis
- Cargue el archivo del proyecto que creamos anteriormente `macOS_12.1`: vaya a `Data Type Manager` → `Open Project Archive`, luego seleccione `macOS_12.1`
- Ejecute los siguientes métodos, el script completo se encuentra en `KM.py`:```python
from utils.helpers import *
from utils.kext import *
kc = Kext(Obj,shared_p="macOS_12.1")
# This method fixes LC_DYLD_CHAINED_FIXUPS for M1 Kernel extension
kc.depac()
# This method reconstructs class hierarchy and builds virtual table for each class
kc.process_kernel_kext()
```
**Nota importante** : a veces `kc.process_kernel_kext()` falla porque Ghidra no pudo desofuscar algunos símbolos de C++. Para solucionarlo, ve al administrador de scripts y ejecuta el script `DemangleAllScript.java`, luego reinicia `kc.process_kernel_kext()` nuevamente.
### Clases personalizadas
Hay algunos casos en los que algunas clases de C++ que `ghidra_kernelcache` e `iometa` no pueden simbolizar, por lo que se ha añadido una nueva funcionalidad para manejar esto.
La reconstrucción de clases `Custom()` itera a través de todos los símbolos `::vtable` y verifica si la clase ya está definida o no; si no lo está, crea automáticamente una estructura de clase, definiciones de funciones para cada método de clase identificado, un espacio de nombres y una tabla virtual para cada clase.
La creación de clases personalizadas solo es compatible con macOS por el momento.```bash
$ iometa -n -A /System/Library/Kernels/kernel.release.t8101 > /tmp/kernel.txt
$ iometa -n -A <kext_path> >> /tmp/kernel.txt
```
[No input provided]```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()
```
## Scripts varios
---
### Importando el Dwarf4 de KDK
Ghidra falla de alguna manera al cargar el directorio `.dsym` correspondiente. He creado un pequeño script para solucionarlo. Se puede encontrar [aquí](https://github.com/0x36/ghidra_kernelcache/blob/master/dwarf4_fix.py).
**Uso**: carga el kernel desde tu ruta de KDK, deja que Ghidra termine el análisis, luego ejecuta `dwarf_fix.py`, cargará los símbolos y el proceso puede tardar varios minutos.
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image12.png" alt="image12"/>
### Resolviendo referencias de llamadas a métodos virtuales
`ghidra_kernelcache` proporciona dos formas de resolver llamadas virtuales mediante `kernelCache.explore_pac` o `fix_extra_refs`
**kernelCache.explore_pac()**
Si estás trabajando con un binario arm64e, `ghidra_kernelcache` puede reconocer llamadas a métodos virtuales buscando el valor del `Código de Autenticación de Puntero`. El proceso es directo y a diferencia de `fix_extra_refs()`, `kernelCache.explore_pac` no depende de la identificación de `Pcode` o `varnode`, simplemente itera a través de todas las instrucciones del programa, busca instrucciones `MOVK`, obtiene el segundo operando y busca su valor correspondiente en la base de datos.
Uso:
Crea una instancia de *KernelCache* mediante **kernelCache**, **Kext** o **Custom**, luego llama al método `explore_pac()`.```py
from utils.helpers import *
from utils.kext import *
if __name__ == "__main__":
default = "/tmp/kernel.txt"
ff = askString("iometa symbol file","Symbol file: ",default)
iom = ParseIOMeta(ff)
Obj = iom.getObjects()
kc = Kext(Obj)
kc.explore_pac()
```
**fix_extra_refs()**
Esta función se basa en un análisis básico de flujo de datos para encontrar todos los métodos de llamada virtual y resolver sus implementaciones automáticamente; funciona en todas las arquitecturas y tiene la capacidad de reconocer el tipo de datos de origen a partir de la salida del descompilador y resolver todas las referencias de llamada virtual dentro de la función, de modo que el usuario pueda saltar hacia adelante/atrás directamente desde/hacia la implementación sin tener que buscarla manualmente.
La característica más útil que proporciona `fix_extra_refs` es que mantiene las referencias sincronizadas en cada ejecución. Por ejemplo, si cambias el tipo de datos de una variable a un tipo de datos de clase, `fix_extra_refs` reconocerá automáticamente el cambio y recorrerá recursivamente todos los sitios de llamada para resolver sus referencias, y se detendrá solo cuando la cola de sitios de llamada esté vacía.
Hay otras características que proporciona `fix_extra_refs`, como:
- Detecta automáticamente las llamadas `_ptmf2ptf()` y resuelve su método de llamada tanto para offsets como para direcciones completas de función.
- Identifica el espacio de nombres de un nombre de función no resuelto (funciones que comienzan con `FUN_`) y lo resuelve colocando la función objetivo en su propio espacio de nombres (por ejemplo, agregando el puntero **this** de la clase correspondiente).
Puedes encontrar la implementación en **utils/references.py**. `fix_extra_refs` analiza las operaciones [pcode](https://ghidra.re/ghidra_docs/api/ghidra/program/model/pcode/package-summary.html) y busca los códigos de operación `CALLIND` y `CALL`, luego obtiene todos los [varnodes](https://ghidra.re/ghidra_docs/api/ghidra/program/model/pcode/Varnode.html) involucrados en la operación; una vez que se identifica una definición de Varnode, recupera su [HighVariable](https://ghidra.re/ghidra_docs/api/ghidra/program/model/pcode/HighVariable.html) para identificar el tipo de objeto de clase; si el tipo es desconocido (es decir, no parece ser una estructura de clase), simplemente lo ignora; de lo contrario, tomará el nombre de la clase, buscará en su tabla de llamadas virtuales y, utilizando el offset proporcionado por el Varnode, puede obtener la llamada al método virtual correcta y colocar una referencia en la instrucción de llamada.```py
fix_extra_refs(toAddr(address))
```
Aquí hay un ejemplo de salida al usar `fix_extra_refs` :
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image9.png" alt="image9"/>
Tenga en cuenta que ha resuelto con éxito las llamadas virtuales **IOService::isOpen()**, **OSArray:getNextIndexOfObject()** y **IOStream::removeBuffer()** sin ninguna modificación manual.
A continuación, `fix_extra_refs` descompilará **IOStream::removeBuffer()**, obtiene todas las HighVariables de este método, luego resuelve sus referencias como el método anterior... y así sucesivamente.
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image10.png" alt="image10"/>
### Corrección automática de tablas de métodos externos
Creo que cada investigador tiene algún script para manejar esta parte, ya que es la superficie de ataque principal de IOKit; hacerlo manualmente es una carga, y debe automatizarse de manera que el investigador pueda profundizar en múltiples tablas de métodos externos.
Hay dos scripts proporcionados por `ghidra_kernelcache` : **fix_methodForIndex.py** y **fix_extMethod.py.** Puede habilitarlos como los otros scripts, como se muestra arriba.
***Uso***: Coloque el cursor al inicio de la tabla de despacho externa, ejecute el script: proporcione el destino y el número de selectores.
Ejemplo para `IOStreamUserClient::getTargetAndMethodForIndex()` :
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image11.png" alt="image11"/>
### namespace.py : corregir espacios de nombres de métodos…
Este es un script útil para asignar el tipo de clase a todos los métodos encontrados, y es una dependencia para el script `extra_refs.py` con el fin de explorar recursivamente las funciones llamadas y resolver sus referencias.
***Uso***: Coloque el cursor en la salida del descompilador de la función deseada, ejecute el script desde la barra de herramientas o presione **Meta-Shift-N** .
### Propagación de nombres de símbolos y tipos
`ghidra_kernelcache` proporciona soporte de propagación de tipos para operaciones básicas de Pcode, pero probablemente fallará para algunas variables que utilizan casting complejo.
Si alguien quiere ayudar, o quiere empezar a trabajar con cosas de bajo nivel en Ghidra, esta es la oportunidad para hacerlo.
La implementación se puede encontrar en [ghidra_kernelcache/propagate.py](https://github.com/0x36/ghidra_kernelcache/blob/master/propagate.py)
### Carga de firmas de funciones
No es posible analizar archivos de cabecera de C++ en Ghidra, y tener firmas de funciones del kernel en kernelcache puede mejorar muchas cosas en la salida del descompilador.
Por ejemplo, supongamos que hemos añadido `virtual IOMemoryMap * map(IOOptionBits options = 0 );`, Ghidra volverá a tipificar automáticamente el valor de retorno como un puntero `IOMemoryMap` tanto para la definición de la función como para las firmas de funciones.
Puede agregar cualquier símbolo de C++ en el directorio **signatures/** respetando la sintaxis, y puede encontrar firmas de funciones definidas en este directorio.```c++
// Defining an instance class method
IOMemoryDescriptor * withPersistentMemoryDescriptor(IOMemoryDescriptor *originalMD);
// Defining a virtual method, it must start with "virtual" keyword
virtual IOMemoryMap * createMappingInTask(task_t intoTask, mach_vm_address_t atAddress, IOOptionBits options, mach_vm_size_t offset = 0, mach_vm_size_t length = 0);
// Defining a structure
struct task_t;
// typedef'ing a type
typedef typedef uint IOOptionBits;
// Lines begining with '//' are ignored
```
***Uso***: Después de simbolizar el kernel, es muy recomendable ejecutar el script `load_sigatnures.py` para cargar todas las firmas de funciones disponibles. Como la mayoría de las herramientas anteriores, ejecute este script agregándolo en la barra de herramientas o desde el Administrador de complementos, o simplemente presione **Meta-Shift-S**.
### Cargando estructuras antiguas:
Este script es sencillo: importa todas las estructuras, clases, typedefs y definiciones de funciones con `SourceType.USER_DEFINED` de un proyecto antiguo a uno nuevo.
***Uso***: Abra los proyectos antiguo y nuevo de Ghidra en la misma herramienta, vaya al script [`load_structs.py`](https://github.com/0x36/ghidra_kernelcache/blob/master/load_structs.py), coloque el nombre del programa antiguo en la variable **src_prog_string** y el nuevo en la variable **dst_prog_string**, luego ejecute el script.
## Contribuir
Si encuentra el proyecto interesante y desea contribuir, simplemente haga un PR y lo revisaré; mientras tanto, me gustaría ver alguna contribución en las siguientes áreas:
* [ghidra_kernelcache/signatures/kernel.txt](https://github.com/0x36/ghidra_kernelcache/tree/master/signatures): siga importando funciones del kernel XNU, es muy simple: solo copie/pegue la definición de la función.
* [ghidra_kernelcache/propagate.py](https://github.com/0x36/ghidra_kernelcache/blob/master/propagate.py): soporte para opcodes no manejados para una mejor propagación de símbolos.
## Créditos
Me gustaría agradecer a [@s1guza](https://twitter.com/s1guza) por su increíble [iometa](https://github.com/Siguza/iometa.git) del cual depende ghidra_kernelcache.