
un framework Ghidra pour le reverse engineering du kernelcache iOS
Ce framework est le produit final de mon expérience en reverse engineering de Kernelcache. Je cherche généralement des vulnérabilités en auditant manuellement le noyau et ses extensions, et j'ai automatisé la plupart des choses que je voulais vraiment voir dans Ghidra pour accélérer le processus de reverse engineering, ce qui s'est avéré efficace et permet de gagner beaucoup de temps. Le framework fonctionne sur iOS 12/13/14/15 et sur macOS 11/12 (à la fois kernelcache et KEXT unique) et a été rendu public dans le but d'aider les gens à commencer à rechercher sur le noyau iOS sans avoir à préparer leur propre environnement. À mon avis, ce framework (incluant l'ensemble d'outils qu'il fournit et avec quelques connaissances de base en IOKit) est suffisant pour commencer à bidouiller le Kernelcache.
Le framework est entièrement écrit en Python et peut être étendu pour construire d'autres outils. Il fournit quelques API de base que vous pouvez utiliser dans presque tous les projets et gagner du temps en évitant de lire le manuel verbeux. Vous êtes invité à lire les fonctionnalités principales dans le répertoire utils/.
Ghidra est bon pour analyser les Kernelcaches, mais comme d'autres outils de reverse engineering, il nécessite un certain travail manuel. ghidra_kernelcache fournit un bon point d'entrée pour corriger les choses au début et même pendant le reverse engineering, offrant ainsi une sortie de décompilateur agréable.
Il existe un projet similaire créé par @_bazad dans IDAPro appelé ida_kernelcache qui fournit un bon point d'entrée pour les chercheurs souhaitant travailler avec l'image du noyau dans IDA. Mon framework ressemble un peu au travail de Brandon, et va plus loin en fournissant beaucoup plus de fonctionnalités pour rendre le processus de travail avec le kernelcache moins pénible.
::externalMethod() et ::getTargetAndMethodForIndex().Ces fonctionnalités sont réalisées sous forme d'outils séparés qui peuvent être exécutés soit par des raccourcis clavier, soit en cliquant sur leurs icônes dans la barre d'outils.
Clonez le dépôt :```sh git clone https://github.com/0x36/ghidra_kernelcache.git
**Note importante** : Le projet a été testé sur Ghidra 10.1_PUBLIC et 10.2_DEV et n'est pas rétrocompatible.
Allez dans *`Windows → Script Manager`,* cliquez sur *`script Directory`*, puis ajoutez *`ghidra_kernelcache`* à la liste des chemins de répertoires.
Allez dans *`Windows → Script Manager`,* dans la liste *scripts*, allez dans la catégorie *`iOS→kernel`* et cochez les plugins qui y apparaissent, ils apparaîtront dans la barre d'outils GHIDRA.
dans le répertoire [logos/](https://github.com/0x36/ghidra_kernelcache/tree/master/logos), vous pouvez placer vos propres logos pour chaque outil.
## iOS kernelcache symbolication
`ghidra_kernelcache` nécessite dans un premier temps [iometa](https://github.com/Siguza/iometa/) (réalisé par [@s1guza](https://twitter.com/s1guza)), un outil puissant fournissant des informations sur les classes C++ dans le binaire du noyau. Le grand avantage est qu'il fonctionne comme un binaire autonome, donc la sortie peut être importée dans votre framework RE favori en la parsant simplement. Mon framework prend la sortie d'iometa et la parse pour symboliser et corriger les tables virtuelles.
### Utilisation
Après avoir décompressé le noyau, exécutez les commandes suivantes :```sh
$ iometa -n -A /tmp/kernel A10-legacy.txt > /tmp/kernel.txt
# if you want also to symbolicate using jtool2
$ jtool2 --analyze /tmp/kernel
Chargez le kernelcache dans Ghidra, N'UTILISEZ PAS l'import BATCH, chargez-le comme une image Mach-O.
Après que le Kernelcache soit chargé et auto-analysé, cliquez sur l'icône affichée dans la barre d'outils ou appuyez simplement sur Meta-Shift-K, puis insérez le chemin complet de la sortie d'iometa, qui est /tmp/kernel.txt dans notre cas.
si vous souhaitez utiliser les symboles jtool2, vous pouvez également utiliser jsymbol.py situé dans la catégorie iOS→kernel.
Des exemples complets de l'API se trouvent dans ghidra_kernelcache/kc.py
→ Voici quelques exemples de manipulation des objets de classe :```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()
Comme vous pouvez le voir, vous pouvez symboliser entièrement ou partiellement le kernelcache ; si la symbolisation partielle est choisie, `ghidra_kernelcache` construira automatiquement toutes les dépendances de classes avant de continuer. Si vous exécutez le script sur l'ensemble du kernelcache (symbolisation complète), `ghidra_kernelcache` prendra plusieurs minutes pour analyser l'image du noyau.
Une fois terminé, Ghidra fournira ce qui suit :
→ Une nouvelle catégorie a été ajoutée dans le filtre de signets appelée « iOS » :
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image1.png" alt="image1" width="200"/>
→ Les tables virtuelles de classes IOKit sont ajoutées au signet « iOS » pour une recherche plus rapide et plus efficace de tables virtuelles ; vous pouvez simplement chercher un kext ou une classe en fournissant des lettres, des mots ou le bundle du kext dans la barre de recherche.
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image2.png" alt="image2"/>
→ Correction de la table virtuelle : désassemble/compile le code inconnu, corrige les espaces de noms, re-symbolise les méthodes de classe et applique une définition de fonction à chaque méthode :
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image3.png" alt="image3"/>
→ Création d'espaces de noms de classe et placement de chaque méthode dans son propre espace de noms correspondant :
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image4.png" alt="image4"/>
→ Création de la structure de classe en respectant la hiérarchie des classes :
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image5.png" alt="image5"/>
→ Création des tables virtuelles (vtables) de classe, et chaque méthode possède sa propre définition de méthode pour une meilleure sortie de décompilation :
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image6.png" alt="image6"/>
L'implémentation complète se trouve dans [`utils/class.py.`](https://github.com/0x36/ghidra_kernelcache/blob/master/utils/class.py)
Voici quelques captures d'écran avant/après la symbolisation avec `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"/>
## Symbolisation des Kext macOS
---
Le support macOS de `ghidra_kernelcache` est à la fois pour la symbolisation du kernelcache et des KEXT individuels pour les architectures ARM64e et x86_64.
**IMPORTANT :** Au moment de la rédaction, Ghidra n'est pas capable d'analyser l'ensemble du kernelcache macOS, mais il est possible de le charger dans IDA pour une analyse initiale puis d'importer la base de données (idb vers xml) dans Ghidra ultérieurement, mais cela dépasse le cadre de cet outil. Si vous parvenez à le faire, `ghidra_kernelcache` se chargera du reste.
Quelques étapes doivent être suivies avant de symboliser une extension de noyau macOS, car l'objectif principal de `ghidra_kernelcache` est de reconstruire la hiérarchie des classes et de gérer toutes les structures de classes dans une base de données unique ; une extension de noyau ne remplit pas ces conditions, ce qui signifie que la symbolisation d'un seul KEXT nécessite une symbolisation du noyau et peut-être d'autres extensions de noyau dont il dépend, d'où un travail supplémentaire à réaliser ici.
`ghidra_kernelcache` propose désormais un moyen puissant de symboliser les extensions de noyau, y compris le noyau, en gérant et en partageant les structures de classes et les définitions de méthodes virtuelles via le puissant *DataType Project Archive* de Ghidra.
### Étapes pour symboliser une extension de noyau
- Créez un nouveau dossier dans votre projet Ghidra, puis chargez ` /System/Library/Kernels/kernel.release.XXXXX` dans ce dossier et laissez Ghidra l'analyser.
- Créez une nouvelle *Archive de projet* : allez dans `DataType Provider` → Cliquez sur la flèche en haut à droite de la fenêtre → `New Project Archive` → Placez-la dans le dossier nouvellement créé → Nommez-la (par exemple macOS_12.1).
- Maintenant, symbolisez le noyau avec `ghidra_kernelcache` ; le processus est assez similaire à la symbolisation du *kernelcache* iOS.```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()
Maintenant, faites un clic droit sur `kernel.release.t8001` → `Commit DataTypes To` → `macOS_12.1`.
- Ensuite, `Right Click` → `Select All` → `Commit`.
- Sauvegardez l'archive du projet : `Right click` → `Save Archive`.
Nous venons de créer une archive de projet qui peut être partagée entre toutes les extensions du noyau.
Prenons l'exemple du Kext `IOSurface` pour 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
macOS_12.1 : allez dans Data Type Manager → Open Project Archive, puis sélectionnez 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()
**Note importante** : parfois `kc.process_kernel_kext()` échoue car Ghidra n'a pas pu démarseler certains symboles C++. Pour résoudre ce problème, allez dans le gestionnaire de scripts et exécutez le script `DemangleAllScript.java`, puis relancez `kc.process_kernel_kext()`.
### Classes personnalisées
Il existe certains cas où des classes C++ que `ghidra_kernelcache` et `iometa` ne peuvent pas symboliser, donc une nouvelle fonctionnalité a été ajoutée pour gérer cela.
La reconstruction de classe `Custom()` parcourt tous les symboles `::vtable` et vérifie si la classe est déjà définie ou non. Si elle ne l'est pas, elle crée automatiquement une structure de classe, des définitions de fonction pour chaque méthode de classe identifiée, un espace de noms et une table virtuelle pour chaque classe.
La création de classes personnalisées n'est prise en charge que sur macOS pour l'instant.```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 divers
---
### Importation du Dwarf4 de KDK
Ghidra échoue parfois à charger le répertoire `.dsym` correspondant, j'ai créé un petit script pour résoudre ce problème. Il peut être trouvé [ici](https://github.com/0x36/ghidra_kernelcache/blob/master/dwarf4_fix.py).
**Utilisation** : chargez le noyau depuis votre chemin KDK, laissez Ghidra terminer l'analyse, puis exécutez `dwarf_fix.py`, cela chargera les symboles et le processus peut prendre plusieurs minutes.
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image12.png" alt="image12"/>
### Résolution des références d'appels de méthodes virtuelles
`ghidra_kernelcache` propose deux moyens de résoudre les appels virtuels via `kernelCache.explore_pac` ou `fix_extra_refs`
**kernelCache.explore_pac()**
Si vous travaillez sur un binaire arm64e, `ghidra_kernelcache` peut reconnaître les appels de méthodes virtuelles en recherchant la valeur `Pointer Authentication Code`. Le processus est simple et contrairement à `fix_extra_refs()`, `kernelCache.explore_pac` ne repose pas sur l'identification `Pcode` ou `varnode`, il parcourt simplement toutes les instructions du programme, recherche les instructions `MOVK`, récupère le second opérande et cherche sa valeur correspondante dans la base de données.
Utilisation :
Créez une instance *KernelCache* via **kernelCache** , **Kext** ou **Custom**, puis appelez la méthode `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() Cette fonction repose sur une analyse de flux de données basique pour trouver toutes les méthodes d'appel virtuel et résoudre automatiquement leurs implémentations. Elle fonctionne sur toutes les architectures et a la capacité de reconnaître le type de donnée source à partir de la sortie du décompilateur et de résoudre toutes les références d'appel virtuel à l'intérieur de la fonction, permettant ainsi à l'utilisateur de naviguer directement vers/depuis l'implémentation sans avoir à la chercher manuellement.
La fonctionnalité la plus utile fournie par fix_extra_refs est qu'elle maintient les références synchronisées à chaque exécution. Par exemple, si vous modifiez le type de données d'une variable pour un type de classe, fix_extra_refs reconnaîtra automatiquement le changement, parcourra récursivement tous les sites d'appel pour résoudre leurs références et s'arrêtera seulement lorsque la file d'attente des sites d'appel sera vide.
Il existe d'autres fonctionnalités fournies par fix_extra_refs telles que :
_ptmf2ptf() et résout leur méthode d'appel pour les décalages et l'adresse complète de la fonction.Vous pouvez trouver l'implémentation dans utils/references.py, fix_extra_refs analyse les opérations pcode et recherche les opcodes CALLIND et CALL, puis récupère tous les varnodes impliqués dans l'opération. Une fois qu'une définition de Varnode est identifiée, il récupère son HighVariable pour identifier le type d'objet de la classe. Si le type est inconnu (c'est-à-dire ne semble pas être une structure de classe), il l'ignore simplement. Sinon, il prend le nom de la classe, consulte sa table d'appel virtuel, et en utilisant le décalage fourni par le Varnode, il peut récupérer l'appel de méthode virtuelle correct et place une référence sur l'instruction d'appel.```py
fix_extra_refs(toAddr(address))
Voici un exemple de sortie de l'utilisation de `fix_extra_refs` :
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image9.png" alt="image9"/>
Notez qu'il a résolu avec succès les appels virtuels **IOService::isOpen()**, **OSArray:getNextIndexOfObject()** et **IOStream::removeBuffer()** sans aucune modification manuelle.
Ensuite, `fix_extra_refs` décompilera **IOStream::removeBuffer()**, obtiendra toutes les HighVariables de cette méthode, puis résoudra leurs références comme la méthode précédente... et ainsi de suite.
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image10.png" alt="image10"/>
### Correction automatique des tables de méthodes externes
Je pense que chaque chercheur a un script pour gérer cette partie, car c'est la principale surface d'attaque d'IOKit, le faire manuellement est une charge, et elle doit être automatisée d'une manière qui permette au chercheur de plonger dans plusieurs tables de méthodes externes.
Il y a deux scripts fournis par `ghidra_kernelcache` : **fix_methodForIndex.py** et **fix_extMethod.py.** Vous pouvez les activer comme les autres scripts comme indiqué ci-dessus.
***Utilisation*** : Placez le curseur au début de la table de répartition externe, exécutez le script : fournissez la cible et le nombre de sélecteurs.
Exemple pour `IOStreamUserClient::getTargetAndMethodForIndex()` :
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image11.png" alt="image11"/>
### namespace.py : corriger les espaces de noms des méthodes …
Ce script est utile pour attribuer le type de classe à toutes les méthodes rencontrées, et c'est une dépendance pour le script `extra_refs.py` afin d'explorer récursivement les fonctions appelées et de résoudre leurs références.
***Utilisation*** : Placez le curseur dans la sortie du décompilateur de la fonction souhaitée, exécutez le script depuis la barre d'outils ou appuyez sur **Meta-Shift-N** .
### Propagation des noms de symboles et des types
`ghidra_kernelcache` prend en charge la propagation des types pour les opérations Pcode de base, mais échouera probablement pour certaines variables utilisant des conversions complexes.
Si quelqu'un veut aider, ou veut commencer à travailler avec du bas niveau dans Ghidra, c'est l'occasion.
L'implémentation se trouve dans [ghidra_kernelcache/propagate.py](https://github.com/0x36/ghidra_kernelcache/blob/master/propagate.py)
### Chargement des signatures de fonctions
L'analyse des fichiers d'en-tête C++ dans Ghidra n'est pas possible, et avoir des signatures de fonctions du noyau dans kernelcache peut améliorer beaucoup de choses dans la sortie du décompilateur.
Par exemple, disons que nous avons ajouté `virtual IOMemoryMap * map(IOOptionBits options = 0 );`, Ghidra retypera automatiquement la valeur de retour en pointeur `IOMemoryMap` pour la définition et les signatures de fonctions.
Vous pouvez ajouter n'importe quel symbole C++ dans le répertoire **signatures/** en respectant la syntaxe et vous trouverez des signatures de fonctions définies dans ce répertoire.```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
Utilisation: Après avoir symboliqué le noyau, il est fortement recommandé d'exécuter le script load_sigatnures.py pour charger toutes les signatures de fonctions disponibles. Comme la plupart des outils précédents, exécutez ce script en l'ajoutant dans la barre d'outils ou depuis le gestionnaire de plugins ou appuyez simplement sur Meta-Shift-S .
Ce script est simple, il importe toutes les structures, classes, typedefs et définitions de fonctions ainsi que tout ce qui a SourceType.USER_DEFINED d'un ancien projet vers un nouveau.
Utilisation: Ouvrez les anciens et nouveaux projets Ghidra dans le même outil, allez au script load_structs.py, mettez le nom de l'ancien programme dans la variable src_prog_string, et le nouveau dans la variable dst_prog_string, puis exécutez le script.
Si vous trouvez le projet intéressant et souhaitez contribuer, faites une PR et je la réviserai. En attendant, j'aimerais voir des contributions dans les domaines suivants :
Je tiens à remercier @s1guza pour son formidable iometa dont dépend ghidra_kernelcache.