
An implementation of the Fusee Gelee exploit (CVE-2018-6242) for the Nintendo Switch, along with a custom payload.
Реализация эксплойта Fusee Gelee (CVE-2018-6242) для Nintendo Switch, основанная на уязвимости, раскрытой Kate Temkin / ReSwitched в марте 2018 года.
Запускает произвольную полезную нагрузку (например, hekate) на устройстве с Tegra X1 в режиме USB Recovery (RCM), полностью обходя проверку подписи загрузочного ПЗУ.
RCM (Recovery Mode) — это USB-протокол восстановления, встроенный в загрузочное ПЗУ Tegra X1. NVIDIA разработала его для загрузки небольших программ («апплетов») на устройство с целью диагностики или ремонта — например, когда Switch не может найти действительный загрузчик на своём накопителе.
На Nintendo Switch вход в RCM осуществляется путём замыкания контактов 1 и 10 на направляющей правого контроллера при включении. В обычном режиме использовать RCM может только NVIDIA — все команды должны быть подписаны закрытым ключом RSA NVIDIA, и загрузочное ПЗУ проверяет подписи перед выполнением.
Эксплойт работает в двух независимых протокольных уровнях, которые разделяют IRAM (внутреннюю память на кристалле):
USB-уровень (стандартный, EP0): Каждое USB-устройство имеет управляющую конечную точку (EP0), которая обрабатывает стандартные запросы, такие как GET_STATUS, GET_DESCRIPTOR, SET_ADDRESS. Загрузочное ПЗУ реализует их в соответствии с требованиями спецификации USB 2.0. EP0 неявна — она не отображается в дескрипторах конечных точек устройства.
RCM-уровень (собственный протокол NVIDIA, EP1): NVIDIA определяет bulk-конечную точку (EP1) для передачи команд и полезных нагрузок RCM. EP1 отображается в дескрипторе устройства как два направления:
0x01 = OUT (хост отправляет данные устройству)0x81 = IN (устройство отправляет данные хосту)Данные, отправляемые через EP1, имеют следующую структуру:
[680-байтовый заголовок RCM-команды] [байты полезной нагрузки]
680-байтовый заголовок представляет собой структуру rcm_msg_t — собственную структуру NVIDIA,
содержащую модуль/подпись RSA, ECID, код операции и другие поля. Этот размер был
определён путём обратной разработки загрузочного ПЗУ Tegra X1 (см.
базу данных IDA от q3k).
Открытый исходный код NVIDIA tegrarcm
документирует только до 644 байт (Tegra124); вариант T210 на 36 байт больше.
В обработчике управляющих запросов EP0 загрузочного ПЗУ есть ошибка в реализации GET_STATUS для получателей ENDPOINT. Из технического описания Temkin:
// ОШИБКА: должно быть size_to_tx = sizeof(status), т.е. 2 байта
size_to_tx = length_read; // задаётся атакующим через wLength, до 65535
data_to_tx = &status; // uint16_t на стеке
memcpy(dma_buffer, data_to_tx, size_to_tx);
memcpy читает из &status (стековая переменная чуть ниже 0x40010000) и записывает
в DMA-буфер (по адресу 0x40009000). При чрезмерной длине:
&status, через остальную часть стека, до
контролируемой атакующим области полезной нагрузки по адресу 0x40010000+ (размещённой там через bulk-запись EP1).Данные источника включают «stack spray» (повторяющийся 0x40010000), который записывается
поверх адресов возврата стека. Когда обработчик возвращается, выполнение переходит на
0x40010000 — где мы разместили небольшой заглушку-релокатор под названием intermezzo.
Это происходит во время цикла приёма RCM (внутри handle_control_requests),
до того, как загрузочное ПЗУ вообще проверит подписи. Протокол RCM — это механизм доставки;
обработчик USB-управления — триггер.
0x40005000 +------------------+
| DMA-буфер LOW | Контроллер USB записывает сюда нечётные пакеты
0x40009000 +------------------+
| DMA-буфер HIGH | Контроллер USB записывает сюда чётные пакеты
+------------------+
| стек выполнения | растёт вниз к DMA-буферам
0x40010000 +------------------+ <-- стек заканчивается / начинается полезная нагрузка
| intermezzo | маленькая заглушка-релокатор (124 байта)
0x40010E40 +------------------+
| полезная нагрузка pt1 | первые ~16 КБ полезной нагрузки
0x40014E40 +------------------+
| stack spray | 0x40010000 повторяется (8640 байт)
0x40017000 +------------------+
| полезная нагрузка pt2 | оставшаяся часть полезной нагрузки
+------------------+
Полезная нагрузка разделена вокруг stack-spray, потому что spray должен располагаться так,
чтобы переполнение скопировало его поверх адресов возврата стека. Intermezzo собирает две
половины в непрерывный блок по адресу 0x40010000 и переходит к нему.
0x0955:0x7321) через стандартное USB-перечисление0x40010000+, размещая наш intermezzo, полезную нагрузку и stack spray0x40009000)wLength=0x7000 — это запускает уязвимый memcpy, stack spray перезаписывает адреса возврата, и обработчик возвращается в intermezzopip install pyusb
python launcher.py
Требуется Switch в режиме RCM, подключённый через USB. На macOS может потребоваться
brew install libusb.
Поместите бинарный файл полезной нагрузки в binaries/payload.bin. Включённый intermezzo.bin
обрабатывает перемещение полезной нагрузки и замены не требует.
launcher.py — скрипт эксплойтаbinaries/intermezzo.bin — заглушка-релокатор (124 байта), собирает разделённую полезную нагрузкуbinaries/payload.bin — исполняемая полезная нагрузка (например, hekate)