
An implementation of the Fusee Gelee exploit (CVE-2018-6242) for the Nintendo Switch, along with a custom payload.
Uma implementação do exploit Fusee Gelee (CVE-2018-6242) para o Nintendo Switch, baseada na vulnerabilidade divulgada por Kate Temkin / ReSwitched em março de 2018.
Lança uma carga arbitrária (por exemplo, hekate) em um dispositivo Tegra X1 no Modo de Recuperação USB (RCM), ignorando completamente a verificação de assinatura da ROM de inicialização.
RCM (Modo de Recuperação) é um protocolo de recuperação baseado em USB incorporado na ROM de inicialização do Tegra X1. A NVIDIA o projetou para que possa carregar pequenos programas ("applets") em um dispositivo para diagnóstico ou reparo — por exemplo, quando um Switch não encontra um bootloader válido em seu armazenamento.
No Nintendo Switch, o RCM é acessado conectando os pinos 1 e 10 do trilho do joycon direito durante a inicialização. Em operação normal, apenas a NVIDIA pode usar o RCM — todos os comandos devem ser assinados com a chave privada RSA da NVIDIA, e a ROM de inicialização verifica as assinaturas antes de executar qualquer coisa.
O exploit opera em duas camadas de protocolo independentes que compartilham IRAM (RAM no chip):
Camada USB (padrão, EP0): Todo dispositivo USB possui um endpoint de controle (EP0) que lida com requisições padrão como GET_STATUS, GET_DESCRIPTOR, SET_ADDRESS. A ROM de inicialização implementa essas conforme exigido pela especificação USB 2.0. EP0 é implícito — ele não aparece nos descritores de endpoint do dispositivo.
Camada RCM (proprietária da NVIDIA, EP1): A NVIDIA define um endpoint bulk (EP1) para transferir comandos e cargas do RCM. EP1 aparece no descritor do dispositivo em duas direções:
0x01 = OUT (host envia dados para o dispositivo)0x81 = IN (dispositivo envia dados para o host)Os dados enviados via EP1 são estruturados como:
[680-byte RCM command header] [payload bytes]
O cabeçalho de 680 bytes é a struct rcm_msg_t — uma estrutura proprietária da NVIDIA contendo módulo/assinatura RSA, ECID, opcode e outros campos. Esse tamanho foi determinado por engenharia reversa da ROM de inicialização do Tegra X1 (veja o banco de dados IDA do q3k). O tegrarcm de código aberto da NVIDIA documenta apenas até 644 bytes (Tegra124); a variante T210 é 36 bytes maior.
O manipulador de requisições de controle EP0 da ROM de inicialização tem um bug na implementação do GET_STATUS para destinatários ENDPOINT. Do whitepaper 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);
O memcpy lê de &status (uma variável de pilha logo abaixo de 0x40010000) e escreve no buffer DMA (em 0x40009000). Com um comprimento superdimensionado:
&status, pelo restante da pilha, e para dentro da área de carga controlada pelo atacante em 0x40010000+ (colocada lá via escritas bulk EP1).Os dados de origem incluem um "stack spray" (0x40010000 repetido), que é escrito sobre os endereços de retorno da pilha. Quando o manipulador retorna, a execução salta para 0x40010000 — onde colocamos um pequeno stub de realocação chamado intermezzo.
Tudo isso acontece durante o loop de recebimento do RCM (dentro de handle_control_requests), antes que a ROM de inicialização valide assinaturas. O protocolo RCM é o mecanismo de entrega; o manipulador de controle USB é o gatilho.
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
+------------------+
A carga do usuário é dividida ao redor do stack spray porque o spray deve ser posicionado de modo que o overflow o copie sobre os endereços de retorno da pilha. O Intermezzo remonta as duas metades em um bloco contíguo em 0x40010000 e salta para ele.
0x0955:0x7321) via enumeração USB padrão0x40010000+, colocando nosso intermezzo, carga do usuário e stack spray0x40009000)wLength=0x7000 — isso aciona o memcpy vulnerável, o stack spray sobrescreve os endereços de retorno, e o manipulador retorna para intermezzopip install pyusb
python launcher.py
Requer um Switch no modo RCM conectado via USB. No macOS, pode ser necessário brew install libusb.
Coloque seu binário de carga em binaries/payload.bin. O intermezzo.bin incluso lida com a realocação da carga e não precisa ser substituído.
launcher.py — o script do exploitbinaries/intermezzo.bin — stub de realocação (124 bytes), remonta a carga divididabinaries/payload.bin — a carga do usuário a ser executada (ex.: hekate)