Dopo TonyHAX, un nuovo exploit si affaccia sulla scena PSX, la prima console prodotta da Sony ha catturato l’interesse degli hacker. L’exploit consente di caricare codice arbitrario utilizzando solo una scheda di memoria.
A differenza di TonyHAX, FreePSXBoot (questo il nome dell’exploit) non necessita di alcun entrypoint. Attualmente sfrutta la memoria non inizializzata nello spazio del kernel che deve essere pari a 0 per poter funzionare correttamente.
I chip SDRAM hanno una velocità di decadimento piuttosto bassa e questo exploit sarà attualmente affidabile solo se la macchina è stata spenta per un tempo sufficientemente lungo.
Per il momento l’exploit funziona solo su console PSX modello SCPH-9002 (quella che non presenta la porta parallela per intenderci).
[spoiler title=”Dettagli tecnici”]
Dettagli exploit
FreePSXBoot sfrutta un bug nel BIOS PSX per caricare codice arbitrario. Il bug è nel codice che legge la directory della scheda di memoria.
Questo exploit è attualmente disponibile solo per il BIOS SCPH-9002, ma dovrebbe essere portabile su ogni altra versione (a meno che Sony non abbia in qualche modo corretto il bug nelle versioni successive del BIOS).
Introduzione
Una scheda di memoria PSX ha 128 kB di memoria, divisa in 16 blocchi da 8 kB ciascuno. Il primo blocco è riservato per scopi speciali, lasciando 15 blocchi utilizzabili per i giochi.
Ogni blocco è diviso in 64 settori (o frame) di 128 byte ciascuno. Il settore è l’unità minima con cui può lavorare PSX, ovvero la PSX legge e scrive multipli di 128 byte.
Il primo blocco occupa i settori da 0 a 63. Contiene 15 voci di directory, che descrivono il contenuto dei blocchi seguenti (ad es. Nome file, dimensione, ecc…), e un elenco di settori danneggiati e la loro sostituzione, e altre cose non importanti. I dettagli sul formato dei dati possono essere trovati qui.
La struttura delle voci della rubrica è la seguente:
- 4 byte: stato di allocazione del blocco.
- 4 byte: dimensione del file, in byte.
- 2 byte: index del numero di blocco successivo se il file occupa più blocchi; 0xffff altrimenti.
- 21 byte: nome del file in ASCII (terminato da null).
- Rest: inutilizzato (tranne l’ultimo byte che è un checksum).
Il bug
Gestione della scheda di memoria
La shell PSX (ovvero il software avviato all’avvio di PSX senza CD) può gestire le schede di memoria (copiare ed eliminare file). Quando analizza le voci di directory, copia i primi 32 byte di ciascuna voce di directory nella RAM e controlla se sono sani:
- Copia tutte le voci della directory nella RAM (in un array di 15 elementi di 32 byte).
- Passa attraverso ogni voce che rappresenta il primo blocco di un file; le ripara se necessario.
- Passa attraverso ogni voce che non rappresenta il primo blocco di un file; le ripara se necessario.
Questo viene fatto in una funzione che viene chiamata ReadMemCardDirectory, situata in 0xbfc08b3c
in SCPH-9002.
Controlli limite mancanti
La shell PSX presume che la dimensione del file e l’index del blocco next in ciascuna voce siano validi e non esegue alcun controllo limite su di essi. Per il passaggio 3, passa attraverso ogni voce di directory e segue ciecamente il blocco next nel file:
entry* next = &entries[first->next_index];
Manipolando next index, possiamo creare il punto next
ovunque, dalle entries
alle entries + 0x20 * 0xfffe
. Tutti i dati presenti verranno elaborati come voce next.
Inoltre, la shell PSX non corregge le voci immediatamente: se si fa riferimento al blocco, verrà contrassegnato come tale e le riparazioni vengono elaborate dopo che tutte le voci sono state verificate. In pseudocodice:
uint32_t is_referenced[15] = { 0 }; for (int i = 0; i < 15; i++) { entry* ent = &entries[i]; if (is_beginning_of_file(entry)) { for (int i = 0; i < ent->size / 0x2000; i++) { uint16_t next_index = ent->next_index; is_referenced[next_index]++; if (next_index == 0xffff) { break; } } } }
Controlliamo next_index
e controlliamo anche ent->size
: possiamo incrementare qualsiasi variabile a 32 bit nell’intervallo (limitato) di next_index
. Ma dobbiamo assicurarci che ent
punti a blocchi noti di memoria, altrimenti perderemo il controllo di dove punta.
Fortunatamente, c’è una grande porzione di 00 byte poco dopo l’array entries
che verranno interpretate come puntanti all’index 0, facendo in modo che ent
punti nuovamente a una voce di directory che controlliamo.
In pratica, questo significa che possiamo incrementare di un valore controllato la memoria in una posizione scelta (ma limitata). e inizia a suonare come se il gioco fosse finito, ma c’è un problema …
is_referenced
è un array allocato nello stack e sembra che potremmo usarlo per sovrascrivere un indirizzo di ritorno e prendere il controllo del contatore del programma. Tuttavia, ciò non è possibile perché l’array si trova in cima allo stack e tutto ciò che segue sono solo dati inutilizzati.
Infatti, nella mappa di memoria PSX, lo stack è mappato agli ultimi indirizzi della RAM: l’array is_referenced
è a 0x801ffcd0
, solo 816 byte prima della fine della RAM, e questi 816 byte contengono solo dati non utilizzati …
Il mirroring della RAM
Per qualche ragione, i 2 megabyte di RAM della PSX (cioè 0x200000 byte, mappati da 0x80000000 a 0x80200000) vengono specchiati 4 volte. Ciò significa che la lettura (e la scrittura) all’indirizzo 0x80200000 in realtà leggerà/scriverà a 0x80000000.
Questo è l’inizio della RAM e contiene, tra le altre cose, una serie di puntatori a funzioni che possiamo andare a modificare.
Payload
Sovrascrivere il puntatore di funzione destro
ReadMemCardDirectory viene chiamato due volte, una per ogni slot della scheda di memoria. La prima cosa che fa è chiamare una funzione del BIOS tramite un puntatore a funzione, allow_new_card
(questo imposta solo un flag per dopo). Questo è il puntatore a funzione perfetto per sovrascrivere:
- Fa qualcosa di banale e relativamente inutile: è facilmente replicabile in un payload.
- Viene chiamato al momento perfetto: subito dopo che il nostro exploit ha sovrascritto il puntatore alla funzione.
- Può essere facilmente ripristinato dal payload.
Quindi il piano è trovare un index e una dimensione per la prima voce di directory, con i seguenti vincoli:
0x801ffcd0 + index * 4 == 0x802009b4
. Il primo indirizzo è l’arrayis_referenced
; il secondo è il puntatore alla funzioneallow_new_card
, nella RAM con mirroring: l’index deve essere 0x0339.entries + 0x20 * index
punta alla memoria che farà tornare il codice mostrato sopra alla prima voce della directory, permettendoci di controllare quante volte il valore viene incrementato. Fortunatamente, la memoria in quella regione è tutta 0, grazie al decadimento della SDRAM.- Il nostro carico utile si trova in 0xa000be48, che è un mirror di 0x0000be48. La funzione originale è a 0x00004d3c: dobbiamo incrementarla di 0x710c volte. Ciò significa che la dimensione della nostra voce di directory deve essere
0x710c * 2 * 0x2000 = 0x1c430000
. Questo numero deve essere positivo (ovvero inferiore a 0x80000000) o il codice PSX non lo elaborerà.
Ecco come deve apparire la nostra prima voce di directory (settore 1 della scheda di memoria, offset 0x80):
00000080 51 00 00 00 00 00 43 1C 39 03 46 52 45 45 50 53 Q.....C.9.FREEPS
00000090 58 42 4F 4F 54 00 00 00 00 00 00 00 00 00 00 00 XBOOT...........
000000A0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000B0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000C0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 6D ...............m
Loader
Come specificato il payload si trova a 0xa000be48. Questo indirizzo viene utilizzato come un buffer che contiene l’ultimo settore letto di una scheda di memoria nello slot 0.
Dopo aver eseguito le riparazioni, ReadMemCardDirectory leggerà l’elenco dei settori danneggiati (cioè i settori da 16 a 35 nella scheda di memoria). L’ultimo byte di ciascuno di questi settori è un checksum (tutti gli altri byte XORed).
Se un qualsiasi checksum di un settore letto è errato, ReadMemCardDirectory salta e cancella le voci della directory. Usiamo questo a nostro vantaggio:
- Metteremo il payload nel settore 16; avrà un checksum errato, che impedirà ulteriori letture, assicurandosi che rimanga nel buffer a 0xa000be48.
- La cancellazione delle voci della directory garantisce che nessun altro codice esegua una strana manipolazione della memoria quando lo attraversa.
- Questo è (trascurabilmente) più veloce.
Il payload deve trovarsi nel settore 16 (offset 0x800) ed è limitato a 128 byte. Questo è molto piccolo: solo 32 istruzioni MIPS. Fortunatamente, questo è appena sufficiente per creare un caricatore che caricherà più codice dalla scheda di memoria (vedere loader.S, che è grande esattamente 128 byte).
Problemi noti
- L’exploit non funziona sempre sull’hardware reale, a causa del decadimento della SDRAM e del fatto che il kernel non inizializza esplicitamente la sua memoria a tutti gli zeri, è probabile che questo venga migliorato con le versioni future, ma richiede un exploit più complicato.
- L’exploit è disponibile solo per SCPH-9002. Questo è quello che ho. Dovrebbe essere relativamente facile portarlo su altri sistemi.
[/spoiler]
Vedere il generatore di cartelle per uno strumento che può essere utilizzato per generare i propri payload e le schede di memoria.
Le immagini della scheda di memoria sono dati grezzi: la scheda di memoria deve avere lo stesso identico contenuto dei file. Usa Memcarduino o qualcosa di simile; non utilizzare un file manager per schede di memoria, poiché tenterà di correggere i dati che stiamo modificando.
Se l’exploit ha successo, vedrai lo schermo lampeggiare in arancione. Altrimenti, spegni e riaccendi la PSX e riprova dopo circa un minuto. Potrebbero essere necessari alcuni tentativi.
L’exploit funziona anche negli emulatori e funziona sempre perché la memoria è sempre inizializzata a 0. Testato con no$psx e pcsx-redux.
Fonte: github.com