
Une implémentation de l'exploit Fusee Gelee (CVE-2018-6242) pour la Nintendo Switch, ainsi qu'un payload personnalisé.
Une implémentation de l'exploit Fusee Gelee (CVE-2018-6242) pour la Nintendo Switch, basée sur la vulnérabilité divulguée par Kate Temkin / ReSwitched en mars 2018.
Lance une charge utile arbitraire (par exemple hekate) sur un appareil Tegra X1 en mode de récupération USB (RCM), contournant entièrement la vérification de signature de la ROM d'amorçage.
RCM (Recovery Mode) est un protocole de récupération basé sur USB intégré à la ROM d'amorçage du Tegra X1. NVIDIA l'a conçu pour pouvoir charger de petits programmes (« applets ») sur un appareil à des fins de diagnostic ou de réparation — par exemple lorsqu'une Switch ne trouve pas de bootloader valide sur son stockage.
Sur la Nintendo Switch, le RCM est activé en reliant les broches 1 et 10 du rail du Joy-Con droit pendant le démarrage. En fonctionnement normal, seul NVIDIA peut utiliser le RCM — toutes les commandes doivent être signées avec la clé privée RSA de NVIDIA, et la ROM d'amorçage vérifie les signatures avant d'exécuter quoi que ce soit.
L'exploit opère à travers deux couches de protocole indépendantes qui partagent l'IRAM (RAM sur puce) :
Couche USB (standard, EP0) : Chaque périphérique USB possède un endpoint de contrôle (EP0) qui gère les requêtes standard comme GET_STATUS, GET_DESCRIPTOR, SET_ADDRESS. La ROM d'amorçage les implémente comme l'exige la spécification USB 2.0. EP0 est implicite — il n'apparaît pas dans les descripteurs d'endpoints du périphérique.
Couche RCM (propriétaire NVIDIA, EP1) : NVIDIA définit un endpoint bulk (EP1) pour transférer les commandes et charges utiles RCM. EP1 apparaît dans le descripteur de périphérique sous deux directions :
0x01 = OUT (l'hôte envoie des données au périphérique)0x81 = IN (le périphérique envoie des données à l'hôte)Les données envoyées via EP1 sont structurées comme suit :
[680-byte RCM command header] [payload bytes]
L'en-tête de 680 octets est la structure rcm_msg_t — une structure propriétaire NVIDIA
contenant le module RSA/la signature, l'ECID, l'opcode et d'autres champs. Cette taille a été
déterminée par rétro-ingénierie de la ROM d'amorçage du Tegra X1 (voir
la base de données IDA de q3k).
Le tegrarcm open-source de NVIDIA
ne documente que jusqu'à 644 octets (Tegra124) ; la variante T210 est 36 octets plus grande.
Le gestionnaire de requêtes de contrôle EP0 de la ROM d'amorçage contient un bug dans son implémentation de GET_STATUS pour les destinataires ENDPOINT. Extrait du livre blanc de Temkin :
// BUG: should be size_to_tx = sizeof(status), i.e. 2 bytes
size_to_tx = length_read; // attacker-controlled via wLength, up to 65535
data_to_tx = &status; // a uint16_t on the stack
memcpy(dma_buffer, data_to_tx, size_to_tx);
Le memcpy lit à partir de &status (une variable de pile juste en dessous de 0x40010000) et écrit
dans le tampon DMA (à 0x40009000). Avec une longueur surdimensionnée :
&status, à travers le reste de la pile, et jusque dans la
zone de charge utile contrôlée par l'attaquant à 0x40010000+ (placée là via des écritures bulk EP1).Les données source incluent un « stack spray » (0x40010000 répété), qui est écrit
sur les adresses de retour de la pile. Lorsque le gestionnaire retourne, l'exécution saute à
0x40010000 — où nous avons placé un petit stub de relocalisation appelé intermezzo.
Tout cela se produit pendant la boucle de réception RCM (à l'intérieur de handle_control_requests),
avant que la ROM d'amorçage ne valide les signatures. Le protocole RCM est le mécanisme de
livraison ; le gestionnaire de contrôle USB est le déclencheur.
0x40005000 +------------------+
| DMA buffer LOW | USB controller writes odd packets here
0x40009000 +------------------+
| DMA buffer HIGH | USB controller writes even packets here
+------------------+
| execution stack | grows downward toward DMA buffers
0x40010000 +------------------+ <-- stack ends here / payload starts here
| intermezzo | small relocator stub (124 bytes)
0x40010E40 +------------------+
| user payload pt1 | first ~16KB of the user payload
0x40014E40 +------------------+
| stack spray | 0x40010000 repeated (8640 bytes)
0x40017000 +------------------+
| user payload pt2 | remainder of user payload
+------------------+
La charge utile utilisateur est divisée autour du stack spray car le spray doit être positionné
de sorte que le débordement le copie sur les adresses de retour de la pile. Intermezzo réassemble les
deux moitiés en un bloc contigu à 0x40010000 et y saute.
0x0955:0x7321) via l'énumération USB standard0x40010000+, plaçant notre intermezzo, la charge utile utilisateur et le stack spray0x40009000)wLength=0x7000 — cela déclenche le
memcpy vulnérable, le stack spray écrase les adresses de retour, et le gestionnaire retourne dans intermezzopip install pyusb
python launcher.py
Nécessite une Switch en mode RCM connectée via USB. Sur macOS, vous pourriez avoir besoin de
brew install libusb.
Placez votre binaire de charge utile dans binaries/payload.bin. Le intermezzo.bin inclus
gère la relocalisation de la charge utile et ne devrait pas avoir besoin d'être remplacé.
launcher.py — le script de l'exploitbinaries/intermezzo.bin — stub de relocalisation (124 octets), réassemble la charge utile diviséebinaries/payload.bin — la charge utile utilisateur à exécuter (par exemple hekate)