
An implementation of the Fusee Gelee exploit (CVE-2018-6242) for the Nintendo Switch, along with a custom payload.
An implementation of the Fusee Gelee exploit (CVE-2018-6242) for the Nintendo Switch, based on the vulnerability disclosed by Kate Temkin / ReSwitched in March 2018.
Launches an arbitrary payload (e.g. hekate) on a Tegra X1 device in USB Recovery Mode (RCM), bypassing the boot ROM's signature verification entirely.
RCM (Recovery Mode) is a USB-based recovery protocol built into the Tegra X1's boot ROM. NVIDIA designed it so they can load small programs ("applets") onto a device for diagnostics or repair — e.g. when a Switch can't find a valid bootloader on its storage.
On the Nintendo Switch, RCM is entered by bridging pins 1 and 10 on the right joycon rail while booting. In normal operation, only NVIDIA can use RCM — all commands must be signed with NVIDIA's RSA private key, and the boot ROM verifies signatures before executing anything.
The exploit operates across two independent protocol layers that share IRAM (on-chip RAM):
USB layer (standard, EP0): Every USB device has a control endpoint (EP0) that handles standard requests like GET_STATUS, GET_DESCRIPTOR, SET_ADDRESS. The boot ROM implements these as required by the USB 2.0 spec. EP0 is implicit — it doesn't appear in the device's endpoint descriptors.
RCM layer (NVIDIA proprietary, EP1): NVIDIA defines a bulk endpoint (EP1) for transferring RCM commands and payloads. EP1 appears in the device descriptor as two directions:
0x01 = OUT (host sends data to device)0x81 = IN (device sends data to host)Data sent over EP1 is structured as:
[680-byte RCM command header] [payload bytes]
The 680-byte header is the rcm_msg_t struct — an NVIDIA-proprietary structure
containing RSA modulus/signature, ECID, opcode, and other fields. This size was
determined by reverse engineering the Tegra X1 boot ROM (see
q3k's IDA database).
NVIDIA's open-source tegrarcm
only documents up to 644 bytes (Tegra124); the T210 variant is 36 bytes larger.
The boot ROM's EP0 control request handler has a bug in its GET_STATUS implementation for ENDPOINT recipients. From Temkin's whitepaper:
// 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);
The memcpy reads from &status (a stack variable just below 0x40010000) and writes
to the DMA buffer (at 0x40009000). With an oversized length:
&status, through the rest of the stack, and into the
attacker-controlled payload area at 0x40010000+ (placed there via EP1 bulk writes).The source data includes a "stack spray" (0x40010000 repeated), which gets written
over the stack's return addresses. When the handler returns, execution jumps to
0x40010000 — where we placed a small relocator stub called intermezzo.
This all happens during the RCM receive loop (inside handle_control_requests),
before the boot ROM ever validates signatures. The RCM protocol is the delivery
mechanism; the USB control handler is the trigger.
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
+------------------+
The user payload is split around the stack spray because the spray must be positioned
so the overflow copies it over the stack's return addresses. Intermezzo reassembles the
two halves into a contiguous block at 0x40010000 and jumps to it.
0x0955:0x7321) via standard USB enumeration0x40010000+, placing our intermezzo, user payload, and stack
spray0x40009000)wLength=0x7000 — this triggers the
vulnerable memcpy, the stack spray overwrites return addresses, and the handler
returns into intermezzopip install pyusb
python launcher.py
Requires a Switch in RCM mode connected via USB. On macOS, you may need
brew install libusb.
Place your payload binary at binaries/payload.bin. The included intermezzo.bin
handles payload relocation and should not need to be replaced.
launcher.py — the exploit scriptbinaries/intermezzo.bin — relocator stub (124 bytes), reassembles the split payloadbinaries/payload.bin — the user payload to execute (e.g. hekate)