Вводная
ns2jb — первый программный, привязанный к компьютеру jailbreak для Switch 2. Он позволяет запускать произвольный нативный код в качестве обычного системного модуля и встроенного модуля fs. Это открывает возможность запуска homebrew, модификации игр Switch 2, резервного копирования сохранений и доступа к периферии консоли через MMIO.
Репозиторий с кодом эксплуатации доступен на GitLab.
Для демонстрации возможностей была модифицирована эксклюзивная для Switch 2 игра Mario Kart World — добавлены собственные персонажи и проверена функциональность геймплея.
Подготовка оборудования
Первым шагом была разработка установки, позволяющей AI-агенту Codex взломать Switch 2 без непосредственного участия: два JetKVM для отправки управления джойстиком и получения видео, а также умный выключатель для перезагрузки консоли при падении системы.
Bluetooth-переключатель решает критическую проблему: при краше Switch 2 нужно нажать кнопку питания для перезагрузки. Когда рычаг опускается, он нажимает кнопку, а затем его вращательный момент поднимает механизм обратно.
Получение исполнения кода
Выполнение кода через браузер
WebKit — традиционная мишень для jailbreak консолей. Codex нашла в браузере Switch 2 уязвимость (n-day), загружаемую через пользовательский портал Wi-Fi captive portal, и использовала её для контроля потока выполнения в нативном коде через манипуляцию объектами JavaScript (Math.expm1 и XHR).
Однако для работы в сервисах микроядра нужен новый подход — этот способ ограничен существующими функциями JavaScript. Прямое выполнение произвольного IPC для системных модулей сталкивается с тремя преградами: выполняемая-только память (XOM) в WebKit, подписи указателей (PAC) и контроль потока выполнения (CFI) на вызовах.
Параллельно Codex пыталась пробиться через WebKit, но пока это не принесло результата. Поиск продолжился в других направлениях.
Выполнение кода через SmileBASIC
Альтернативный подход — использовать игры для Switch 1, которые работают на Switch 2. На Switch 1 нет PAC/CFI, а их защита уже скомпрометирована. Особое внимание привлекла SmileBASIC — она ранее уже подвергалась атакам в проекте smilehax для 3DS.
В интерпретаторе SmileBASIC найдена ошибка: при удалении элементов из массива происходит переполнение 32-битного счётчика. Проверка position + count <= array.length может переполниться и стать отрицательной. Это позволяет скопировать данные из памяти, предшествующей массиву — а там находятся указатели на сами массивы.
После этого можно использовать переполненный массив как указатель и читать/писать в произвольные адреса памяти. В итоге получена ROP цепь и исполнение кода на Switch 2.
Получение прав системного модуля
Следующий этап — скомпрометировать более привилегированный процесс. Основное внимание сосредоточено на встроенном модуле fs (файловая система), потому что:
- Код Switch 1 показывает, что
fsспособна раздавать расшифрованный код - Модуль содержит большую кодовую базу
- API файловой системы почти не изменилась между поколениями консолей
Переполнение буфера при нормализации пути
IPC команда GetEntryType принимает строку пути и выполняет её нормализацию. Если путь содержит компонент родительской директории с обратным слешем (например, /..\), выделяется временный буфер размером 0x301 байта. Если передать больший путь — происходит переполнение буфера.
Codex выбрала в качестве жертвы объект BufferedStorage
Через произвольное чтение/запись найдена структура подключения SmileBASIC к модулю fs, и её PID переписан с целью получить полные права доступа. Затем дампирована внутренняя память консоли и весь код обычных системных модулей для офлайн-анализа.
Исполнение кода как встроенного системного модуля
Единственный обычный модуль с правом отображать исполняемый код — это ro. Все модули имеют CFI, PAC и XOM, поэтому логичнее искать логическую ошибку, чем взлом памяти.
При загрузке программы ldr просит fs открыть ExeFS (файловую систему внутри контейнера NCA). Модуль fs возвращает два криптографических элемента: хеш заголовка NCA и подпись разработчика этого хеша. Загрузчик проверяет:
- валидна ли подпись Nintendo для прав доступа процесса и публичного ключа разработчика
- валидна ли подпись разработчика для хеша заголовка NCA
На бумаге это выглядит надёжно: сигнатура NPDM фиксирует права процесса и ключ, ключ фиксирует сигнатуру NCA, сигнатура фиксирует хеш, хеш фиксирует содержимое заголовка. Но на практике есть проблема: ответственность за совпадение файловой системы с хешем из заголовка NCA лежит на модуле fs, а у нас уже есть произвольное чтение/запись в fs.
От произвольного чтения/записи к исполнению нативного кода
Решение: заменить объект ExeFS, стоящий за IPC дескриптором загрузчика, на собственную временную файловую систему. Она должна содержать оригинальный main.npdm (чтобы прошла первая проверка подписи), но исполняемый файл main может быть произвольным. Вторая проверка пройдёт, потому что fs уже отправила валидный хеш и подпись оригинальной файловой системы.
Нужны две записи:
- Поместить временный объект файловой системы в таблицу доменов для ExeFS загрузчика
- Поместить оригинальный ExeFS в таблицу доменов временной файловой системы (для корректного подсчёта ссылок и стабильности)
Записи должны произойти после запроса загрузчиком ExeFS, но до обработки fs последующих запросов к файлам. Контроль над временем даёт замена дескриптора события, который fs использует для ожидания IPC сообщений от загрузчика, на событие, контролируемое из SmileBASIC.
Таким образом получено исполнение произвольного кода с правами любой программы, для которой известны данные main.npdm. А они известны для всех: благодаря произвольному чтению/записи можно скопировать NPDM любого модуля или игры.
Использован main.npdm модуля ro, так как его NPDM даёт права на вызовы системных функций: SetProcessMemoryPermission, MapProcessCodeMemory, MapProcessMemory. Команда MapProcessMemory создаёт записываемое отображение существующего кода fs. Для получения дескриптора на процесс fs использован трюк: перезаписан 4-байтовый дескриптор в IEventNotifier на значение 0xffff8001 (интерпретируется ядром как дескриптор текущего процесса), и после вызова GetEventHandle из SmileBASIC получен дескриптор на KProcess модуля fs.
Этот дескриптор передан собственному процессу ro, который патчит код fs.
Итог
Произвольное исполнение кода с правами обычного системного модуля открывает значительные возможности. Можно исследовать железо консоли без необходимости эксплуатировать драйверы каждого девайса отдельно. Это должно ускорить разработку полнофункционального CFW сообществом.
В ближайшее время стоит рассмотреть несколько направлений:
- Перенос эксплуатации
fsна WebKit. Теперь, когда известна целевая ошибка, произвольные IPC вызовы менее критичны — можно попробовать вызвать ARW вfsчерез модификацию данных, используемых кодом работы с файловой системой в WebKit. - Jailbreak в текущей форме привязан к компьютеру и не компрометирует встроенные системные модули кроме
fs. Поиск ошибок в других встроенных модулях мог бы расширить возможности. - С исполнением нативного кода в
fsоткрывается доступ к криптографическому оборудованию консоли для работы, например, с дешифровкой NCA4. - Эксплуатация протестирована и на Switch 1, но её упаковка в готовый вид требует дополнительной работы. Она не обеспечит возможностей модчипа, но работает без внешнего оборудования.
Предстоит увидеть, какой homebrew, эмуляцию и моды создаст сообщество для Switch 2.
Примечания
-
WebKit-эксплуатация не удалась. ↩
-
Попытка WebKit-эксплуатации не бесполезна как таковая. После обнаружения ошибки в известном ценном сисмодуле может имеет смысл вернуться и попробовать эксплуатировать её в WebKit через data-only атаку. WebKit доступна офлайн, и Nintendo не может отозвать её как eShop-титул. Но на этом этапе исследования временные затраты не окупаются итерационной скоростью. ↩
-
Интересно, перейдёт ли Nintendo когда-то на загрузку обычных модулей и кода игр через зашифрованные блобы, расшифровываемые только ядром или secure monitor. Тогда ни один пользовательский процесс не сможет увидеть код — не будет исполняемо-читаемых отображений в userspace. Даже загрузчик, запускающий новые процессы, просил бы более высокий уровень исключения загружать поставляемые им зашифрованные блобы. Будут компромиссы: игры не смогут читать свой собственный код в рантайме. Но Nintendo уже начала удалять динамическую трансляцию: была отключена JIT. Похоже, консоль движется в этом направлении. Но до этого ещё далеко. ↩
-
Изначально Codex повторяла переполнение буфера на объекте кэша каждый раз, когда нужно было изменить указатель на данные. Это снижало стабильность, особенно при цепочке разыменований, так как каждое переполнение — это шанс краша. Здесь применён трюк: использовать переполнения для перенаправления указателя на данные первой записи кэша на метаданные второй записи. Затем можно изменить указатель второй записи без риска новых переполнений. ↩