
un framework Ghidra per il reverse engineering del kernelcache di iOS
Questo framework è il prodotto finale della mia esperienza nel reverse engineering dei kernelcache. Di solito cerco vulnerabilità controllando manualmente il kernel e le sue estensioni e ho automatizzato la maggior parte delle cose che volevo vedere in Ghidra per velocizzare il processo di reverse engineering, e questo si è dimostrato efficace e fa risparmiare molto tempo. Il framework funziona su iOS 12/13/14/15 e su macOS 11/12 (sia kernelcache che singolo KEXT) ed è stato reso pubblico con l'intenzione di aiutare le persone a iniziare a fare ricerca sul kernel iOS senza la fatica di preparare il proprio ambiente. Secondo me, questo framework (incluso il set di strumenti che fornisce e con una conoscenza di base di IOKit) è sufficiente per iniziare a fare hacking nel Kernelcache.
Il framework è interamente scritto in Python e può essere esteso per costruire altri strumenti; fornisce alcune API di base che puoi usare in quasi tutti i progetti e risparmiare tempo leggendo il manuale verboso. Sei invitato a leggere le funzionalità principali nella directory utils/.
Ghidra è bravo nell'analisi dei kernelcache, ma come altri strumenti di reverse engineering richiede un po' di lavoro manuale. ghidra_kernelcache fornisce un buon punto di partenza per sistemare le cose all'inizio e anche durante il reverse engineering, fornendo quindi un output del decompilatore di bell'aspetto.
Esiste un progetto simile realizzato da @_bazad in IDAPro chiamato ida_kernelcache che fornisce un buon punto di partenza per i ricercatori che vogliono lavorare con l'immagine del kernel in IDA. Il mio framework assomiglia un po' al lavoro di Brandon e va oltre fornendo molte più funzionalità per rendere meno doloroso il processo di lavoro con il kernelcache.
::externalMethod() che per ::getTargetAndMethodForIndex().Queste caratteristiche sono realizzate come strumenti separati che possono essere eseguiti tramite scorciatoie da tastiera o facendo clic sulle loro icone nella barra degli strumenti.
Clona il repository :```sh git clone https://github.com/0x36/ghidra_kernelcache.git
**Nota importante**: Il progetto è stato testato su Ghidra 10.1_PUBLIC e 10.2_DEV e non è retrocompatibile.
Vai su *`Windows → Script Manager`,* clicca su *`script Directory` ,* quindi aggiungi *`ghidra_kernelcache`* all'elenco dei percorsi delle directory.
Vai su *`Windows → Script Manager`,* nell'elenco degli *scripts*, vai alla categoria *`iOS→kernel`* e seleziona i plugin che vedi lì; appariranno nella barra degli strumenti di GHIDRA .
nella directory [logos/](https://github.com/0x36/ghidra_kernelcache/tree/master/logos), puoi inserire i tuoi loghi per ogni strumento.
## Simbolizzazione del kernelcache iOS
`ghidra_kernelcache` richiede nella prima fase [iometa](https://github.com/Siguza/iometa/) (realizzato da [@s1guza](https://twitter.com/s1guza)), un potente strumento che fornisce informazioni sulle classi C++ nel binario del kernel. La cosa grandiosa è che funziona come binario autonomo, quindi l'output può essere importato nel tuo framework di reverse engineering preferito semplicemente analizzandolo. Il mio framework prende l'output di iometa e lo analizza per simbolizzare e correggere le tabelle virtuali.
### Utilizzo
Dopo aver decompresso il kernel, esegui i seguenti comandi :```sh
$ iometa -n -A /tmp/kernel A10-legacy.txt > /tmp/kernel.txt
# if you want also to symbolicate using jtool2
$ jtool2 --analyze /tmp/kernel
Carica il kernelcache in Ghidra, NON USARE l'importazione BATCH, caricalo come immagine Mach-O.
Dopo che il kernelcache è stato caricato e analizzato automaticamente, fai clic sull'icona mostrata nella barra degli strumenti o premi Meta-Shift-K, quindi inserisci il percorso completo dell'output di iometa che, nel nostro caso, è /tmp/kernel.txt.
Se vuoi utilizzare i simboli di jtool2, puoi usare anche jsymbol.py situato nella categoria iOS→kernel.
Esempi completi dell'API sono in ghidra_kernelcache/kc.py
→ Ecco alcuni esempi di manipolazione di oggetti 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()
Come puoi vedere, puoi simbolizzare completamente o parzialmente il kernelcache; se viene scelta la simbolizzazione parziale, `ghidra_kernelcache` costruirà automaticamente tutte le dipendenze delle classi prima di procedere.
Se esegui lo script sull'intero kernelcache (simbolizzazione completa), `ghidra_kernelcache` impiegherà diversi minuti per analizzare l'immagine del kernel.
Una volta terminato, Ghidra fornirà quanto segue:
→ Una nuova categoria è stata aggiunta in Bookmark Filter chiamata "iOS":
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image1.png" alt="image1" width="200"/>
→ Le tabelle virtuali delle classi IOKit vengono aggiunte al segnalibro 'iOS' per una ricerca delle tabelle virtuali più rapida e semplice; puoi semplicemente cercare un kext o una classe inserendo lettere, parole o il bundle del kext nella barra di ricerca.
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image2.png" alt="image2"/>
→ Correzione della tabella virtuale: disassembla/compila codice sconosciuto, corregge i namespace, risimbolizza i metodi della classe e applica la definizione di funzione a ciascun metodo:
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image3.png" alt="image3"/>
→ Creazione dei namespace delle classi e inserimento di ciascun metodo nel proprio namespace corrispondente:
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image4.png" alt="image4"/>
→ Creazione della struttura della classe rispettando la gerarchia delle classi:
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image5.png" alt="image5"/>
→ Creazione delle vtable delle classi, e ciascun metodo ha la propria definizione di metodo per un miglior output di decompilazione:
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image6.png" alt="image6"/>
L'implementazione completa si trova in [`utils/class.py.`](https://github.com/0x36/ghidra_kernelcache/blob/master/utils/class.py)
Ecco alcuni screenshot di prima/dopo la simbolizzazione con `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"/>
## Simbolizzazione di KEXT su macOS
---
Il supporto macOS di `ghidra_kernelcache` è sia per la simbolizzazione del kernelcache che di singoli KEXT per le architetture ARM64e e x86_64.
**IMPORTANTE:** Al momento della scrittura, Ghidra non è in grado di analizzare l'intero kernelcache di macOS, ma è possibile caricarlo in IDA per un'analisi iniziale e poi importare il database (idb in xml) in Ghidra successivamente, ma questo esula dallo scopo. Se riesci a farlo, `ghidra_kernelcache` si occuperà del resto.
Ci sono alcuni passaggi da eseguire prima di simbolizzare qualsiasi estensione del kernel macOS, poiché l'obiettivo principale di `ghidra_kernelcache` è ricostruire la gerarchia delle classi e gestire tutte le strutture delle classi in un unico database; un'estensione del kernel non soddisfa questi requisiti, il che significa che la simbolizzazione di un singolo KEXT richiede una simbolizzazione del kernel e forse di altre estensioni del kernel da cui dipende, quindi è necessario un lavoro extra.
`ghidra_kernelcache` ora fornisce un potente modo per simbolizzare le estensioni del kernel, incluso il kernel, gestendo e condividendo le strutture delle classi e le definizioni dei metodi virtuali tramite il potente *DataType Project Archive* di Ghidra.
### Passaggi per simbolizzare un'estensione del kernel
- Crea una nuova cartella nel tuo progetto Ghidra, quindi carica `/System/Library/Kernels/kernel.release.XXXXX` in quella cartella e lascia che Ghidra lo analizzi.
- Crea un nuovo *Project Archive*: Vai a `DataType Provider` → Clicca sulla freccia in alto a destra della finestra → `New Project Archive` → Posizionalo all'interno della cartella appena creata → Dagli un nome (ad esempio macOS_12.1).
- Ora simbolizza il kernel usando `ghidra_kernelcache`; il processo è abbastanza simile alla simbolizzazione del *kernelcache* di iOS.```bash
$ iometa -n -A /System/Library/Kernels/kernel.release.t8101 > /tmp/kernel.txt
In python console, oppure puoi trovare l'implementazione completa dello script in 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 volta terminato, è stata creata un'associazione di database tra l'archivio del kernel e l'archivio `macOS_12.1`. Ora, fai clic destro su `kernel.release.t8001` → `Commit DataTypes To` → `macOS_12.1`.
- Quindi `Right Click` → `Select All` → `Commit`.
- Salva l'archivio del progetto: `Right click` → `Save Archive`.
Abbiamo appena creato un archivio di progetto che può essere condiviso tra tutte le estensioni del kernel.
Prendiamo come esempio il Kext `IOSurface` per 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: vai su Data Type Manager → Open Project Archive, quindi seleziona macOS_12.1KM.py:```python
from utils.helpers import *
from utils.kext import *kc = Kext(Obj,shared_p="macOS_12.1")
kc.depac()
kc.process_kernel_kext()
**Nota importante** : a volte `kc.process_kernel_kext()` fallisce perché Ghidra non è riuscito a demancherare alcuni simboli C++. Per risolvere, vai nel gestore degli script ed esegui lo script `DemangleAllScript.java`, quindi riavvia `kc.process_kernel_kext()` di nuovo.
### Classi personalizzate
Ci sono alcuni casi in cui alcune classi C++ che `ghidra_kernelcache` e `iometa` non possono simbolizzare, quindi è stata aggiunta una nuova funzionalità per gestire questo.
La ricostruzione della classe `Custom()` itera attraverso tutti i simboli `::vtable` e verifica se la classe è già definita o meno; in caso contrario, crea automaticamente una struttura di classe, definizioni di funzioni per ogni metodo di classe identificato, un namespace e una tabella virtuale per ogni classe.
La creazione di classi personalizzate è supportata al momento solo su macOS.```bash
$ iometa -n -A /System/Library/Kernels/kernel.release.t8101 > /tmp/kernel.txt
$ iometa -n -A <kext_path> >> /tmp/kernel.txt
Nessun contenuto da tradurre.```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()
## Script vari
---
### Importazione del Dwarf4 di KDK
Ghidra, per qualche motivo, non riesce a caricare la directory `.dsym` corrispondente; ho creato un piccolo script per risolvere questo problema. Si trova [qui](https://github.com/0x36/ghidra_kernelcache/blob/master/dwarf4_fix.py).
**Utilizzo** : carica il kernel dal tuo percorso KDK, lascia che Ghidra completi l'analisi, poi esegui `dwarf_fix.py`. Caricherà i simboli e il processo potrebbe richiedere diversi minuti.
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image12.png" alt="image12"/>
### Risoluzione dei riferimenti alle chiamate di metodi virtuali
`ghidra_kernelcache` fornisce due modi per risolvere le chiamate virtuali tramite `kernelCache.explore_pac` o `fix_extra_refs`
**kernelCache.explore_pac()**
Se stai lavorando su un binario arm64e, `ghidra_kernelcache` può riconoscere le chiamate di metodi virtuali cercando il valore del `Pointer Authentication Code`. Il processo è semplice e, a differenza di `fix_extra_refs()`, `kernelCache.explore_pac` non si basa sull'identificazione di `Pcode` o `varnode`; itera semplicemente attraverso tutte le istruzioni nel programma, cerca le istruzioni `MOVK`, recupera il secondo operando e cerca il valore corrispondente nel database.
Utilizzo :
Crea un'istanza di *KernelCache* tramite **kernelCache**, **Kext** o **Custom**, quindi chiama il metodo `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() Questa funzione si basa su un'analisi di flusso dei dati di base per trovare tutti i metodi di chiamata virtuale e risolvere automaticamente le loro implementazioni. Funziona su tutte le architetture ed è in grado di riconoscere il tipo di dato sorgente dall'output del decompilatore e risolvere tutti i riferimenti di chiamata virtuale all'interno della funzione, consentendo all'utente di saltare avanti/indietro direttamente verso/da l'implementazione senza doverla cercare manualmente.
La funzionalità più utile fornita da fix_extra_refs è che mantiene sincronizzati i riferimenti a ogni esecuzione. Ad esempio, se cambi il tipo di dato di una variabile in un tipo di dato di classe, fix_extra_refs riconoscerà automaticamente il cambiamento e attraverserà ricorsivamente tutti i siti di chiamata per risolvere i loro riferimenti, e si fermerà solo quando la coda dei siti di chiamata sarà vuota.
Ci sono alcune altre funzionalità fornite da fix_extra_refs come:
_ptmf2ptf() e risolve il loro metodo di chiamata sia per gli offset che per l'indirizzo completo della funzionePuoi trovare l'implementazione in utils/references.py, fix_extra_refs analizza le operazioni pcode e cerca gli opcode CALLIND e CALL, quindi ottiene tutti i varnodes coinvolti nell'operazione. Una volta identificata una definizione di Varnode, recupera il suo HighVariable per identificare il tipo di oggetto della classe. Se il tipo è sconosciuto (cioè non appare come una struttura di classe) lo ignora; altrimenti prende il nome della classe, cerca la sua tabella di chiamate virtuali e, utilizzando l'offset fornito dal Varnode, può ottenere la corretta chiamata del metodo virtuale e inserisce un riferimento sull'istruzione di chiamata.```py
fix_extra_refs(toAddr(address))
Ecco un esempio di output dell'uso di `fix_extra_refs` :
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image9.png" alt="image9"/>
Si noti che ha risolto con successo le chiamate virtuali di **IOService::isOpen()**, **OSArray:getNextIndexOfObject()** e **IOStream::removeBuffer()** senza alcuna modifica manuale.
Successivamente, `fix_extra_refs` decompilerà **IOStream::removeBuffer()**, otterrà tutti gli HighVariables di questo metodo, poi risolverà i loro riferimenti come il metodo precedente ... e così via.
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image10.png" alt="image10"/>
### Correzione automatica delle tabelle dei metodi esterni
Credo che ogni ricercatore abbia qualche script per gestire questa parte, poiché è la principale superficie di attacco di IOKit; farlo manualmente è un peso, e deve essere automatizzato in modo che il ricercatore possa approfondire più tabelle di metodi esterni.
Ci sono due script forniti da`ghidra_kernelcache` : **fix_methodForIndex.py** e **fix_extMethod.py.** Puoi abilitarli come gli altri script mostrati sopra.
***Uso***: Posiziona il cursore all'inizio della tabella di dispatch esterna, esegui lo script: fornisci il target e il numero di selettori.
Esempio per `IOStreamUserClient::getTargetAndMethodForIndex()` :
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image11.png" alt="image11"/>
### namespace.py : correzione dei namespace dei metodi …
Questo è uno script utile per popolare il tipo di classe a tutti i metodi incontrati, ed è una dipendenza per lo script `extra_refs.py` per esplorare ricorsivamente le funzioni chiamate e risolvere i loro riferimenti.
***Uso***: Posiziona il cursore nell'output del decompilatore della funzione desiderata, esegui lo script dalla barra degli strumenti o premi **Meta-Shift-N** .
### Propagazione di nomi e tipi di simboli
`ghidra_kernelcache` fornisce supporto per la propagazione dei tipi per le operazioni Pcode di base, ma probabilmente fallirà per alcune variabili che utilizzano casting complessi.
Se qualcuno vuole aiutare, o vuole iniziare a lavorare con cose di basso livello in Ghidra, questa è l'opportunità per farlo.
L'implementazione si trova in [ghidra_kernelcache/propagate.py](https://github.com/0x36/ghidra_kernelcache/blob/master/propagate.py)
### Caricamento delle firme delle funzioni
Non è possibile analizzare i file header C++ in Ghidra, e avere le firme delle funzioni del kernel in kernelcache può migliorare molte cose nell'output del decompilatore.
Ad esempio, supponiamo di aver aggiunto `virtual IOMemoryMap * map(IOOptionBits options = 0 );`, Ghidra reimposterà automaticamente il tipo di ritorno in puntatore `IOMemoryMap` automaticamente per entrambi la definizione della funzione e le firme delle funzioni.
Puoi aggiungere qualsiasi simbolo C++ nella directory **signatures/** rispettando la sintassi e puoi trovare le firme delle funzioni definite in questa directory.```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
Utilizzo: Dopo aver simbolizzato il kernel, è altamente consigliato eseguire lo script load_sigatnures.py per caricare tutte le firme di funzioni disponibili. Come per la maggior parte degli strumenti precedenti, esegui questo script aggiungendolo nella barra degli strumenti o dal Plugin manager o premi semplicemente Meta-Shift-S .
Questo script è semplice: importa tutte le strutture, classi, typedef e definizioni di funzioni e tutto ciò che ha SourceType.USER_DEFINED da un vecchio progetto a uno nuovo.
Utilizzo: Apri i progetti Ghidra vecchio e nuovo sullo stesso strumento, vai allo script load_structs.py, inserisci il nome del programma vecchio nella variabile src_prog_string e quello nuovo nella variabile dst_prog_string, quindi esegui lo script.
Se il progetto ti sembra interessante e vuoi contribuire, fai una PR e la esaminerò; nel frattempo, mi piacerebbe vedere contributi nelle seguenti aree:
Vorrei ringraziare @s1guza per il suo fantastico iometa da cui ghidra_kernelcache dipende.