Путешествие с Xbox 360: дорога к RGH3

Путешествие с Xbox 360: дорога к RGH3

Всем привет из Positive Labs! Мы исследуем и разрабатываем различные интересные железки. Как обычно бывает, NDA накладывает ограничения на самое вкусное. И все же очень хочется познакомить вас с тем самым исследовательским духом. К счастью, рабочий процесс очень похож на то, чем я занимался в свободное время (и что вечно откладывал из-за нехватки оного), а значит, пришло время продолжить цикл статей по Xbox 360! Да-да, сегодня речь пойдет о том, как появился самый популярный метод модификации «X-коробки» в наши дни — RGH3.

Предыстория

Несколько лет назад я подумал, что эра Xbox 360 прошла, исследования забиыты, и потихоньку начал избавляться от наследия студенческих времен: приставки продал, запчасти раздал, на форумы залезать перестал. Но у вселенной были совершенно другие планы, стоившие мне нескольких месяцев бессонных ночей. Началось все с известной онлайн-барахолки. Друг попросил подобрать что-нибудь для игр: ПК слабоват, денег нет, а поиграть хочется. Первая мысля — самое время для Xbox 360. Совсем недорого и тянет даже GTA V!

Если долго копаться на барахолке, можно найти всё
Если долго копаться на барахолке, можно найти всё

Приставку мы купили, прошили, дело сделано. Все? Как бы не так. Во-первых, накатила ностальгия по прошивкам. Во-вторых, барахолка продолжила мне подсовывать рекомендовать объявления с приставками и запчастями по интересной цене, как будто дразня.

А потом произошло вот это.

Зарождение Жмых Индастриз
Зарождение Жмых Индастриз

Жмых Парень за копейки продавал две приставки после неудачной попытки прошивки. Все можно было починить в пару движений паяльником, что я и предложил. Показал, как чинить, рассказал в целом про приставки и, сам того не заметив, вернулся на сцену.

Дальше все пошло вообще не по плану: в дискорд-чате по Xbox 360 кто-то выложил купленные на eBay схемы плат для всех ревизий консоли. На этих схемах Josh Davidson (Octal450) неожиданно для всех обнаружил вывод CPU_PLL_BYPASS в Slim-версиях Xbox 360:

Кто вообще додумался продавать такое на eBay?!
Кто вообще додумался продавать такое на eBay?!

Несмотря на то что контактов для этой сигнальной линии на плате нет, разработчики оставили переходное отверстие, к которому после зашкуривания можно припаяться:

Возможно, так процессорный пад лучше держится на плате
Возможно, так процессорный пад лучше держится на плате

Всю важность этой находки можно понять, погрузившись глубже в историю разработки Reset Glitch Hack (RGH). Детально с особенностями RGH можно ознакомиться в третьей части этого цикла статей, а я же кратко введу вас в курс дела.

RGH: историческая справка

RGH, как и другие глитч-атаки, использует факт некорректной работы процессора в определенных условиях. Это может быть слишком высокое или слишком низкое напряжение питания, помехи на опорной частоте процессора или, как в нашем случае, очень короткий импульс низкого уровня на линии сброса (CPU_RESET) процессора.

При обычной установке низкого уровня на этой линии процессор должен перезагрузиться, сбросить свое состояние и начать выполнять код с самого начала, с ROM. Но из-за очень короткой длительности сигнала этого не происходит: процессор продолжает выполнять текущую программу, немного глюкнув при этом, например пропустив выполнение команды или «забыв» записать регистр.

Для такого контролируемого глюка мало подать очень короткий импульс, нужно еще и подать его в нужный момент времени. Чтобы повысить точность попадания и шансы на успех, процессор замедляют. В первом варианте RGH для ревизий Fat замедление происходило через подачу высокого уровня на ту самую линию CPU_PLL_BYPASS, после чего процессор начинал работать в 128 раз медленнее. В этом замедленном режиме тем самым импульсом можно было обойти проверку цифровой подписи:

Проверка контрольной суммы в загрузчике приставки
Проверка контрольной суммы в загрузчике приставки

До недавнего времени считалось, что у Slim-версий приставок нет CPU_PLL_BYPASS, и замедление приходилось выполнять через уменьшение опорной частоты CPU конфигурацией по шине I2C. Но таким образом получалось замедлить процессор всего лишь в 3–4 раза (метод RGH2). К сожалению, после очередного обновления системы Microsoft перевели Fat-приставки на «двойной» режим загрузки, как у Slim-версий, после чего для них тоже пришлось использовать этот метод, так как в режиме 128-кратного замедления время ожидания нужного POST-кода стало непозволительно велико.

Код почти такой же, а вот этап загрузки уже другой
Код почти такой же, а вот этап загрузки уже другой

RGH2 остался единственным методом для Fat-приставок на долгие шесть лет. Работал он нестабильно, запуска приставки можно было ждать несколько минут (а то и не дождаться вовсе). Были попытки увеличить точность отправки глитч-импульса — чипы CR3 Pro (и его клон x360ace) использовали внешний генератор 150 МГц (против 48 МГц в первых решениях), и даже это не всегда спасало:

Нашел фото только на Slim-плате
Нашел фото только на Slim-плате

Но не подумайте, что сцена пустовала, — были и интересные проекты. В чипах R-JTAG и CR4 XL от тех же Team Xecuter был реализован другой, революционный подход — глитч проверки фьюзов для запуска старого загрузчика, из которого уже работал проверенный SMC JTAG Hack (про который можно почитать в первой части серии). И если R-JTAG использовал замедление по опорной частоте, то в CR4 XL применили тот самый CPU_PLL_BYPASS, что снова вернуло стабильность Fat-приставкам. Но чипы были дорогие, в ограниченном количестве, и широкого распространения не получили.

Задумка «все в одном» была неплохая, но китайские x360ace и x360run банально дешевле
Задумка «все в одном» была неплохая, но китайские x360ace и x360run банально дешевле

В конце концов примерно одновременно произошло два события: у меня появилось свободное время (дипломная пора), а DrSchottky решил сделать свой аналог для CR4 XL. За месяц-другой вечерних посиделок были разработаны методы RGH 1.2 и R-JTOP (конкуренция мотивирует). R-JTOP повторял идею CR4 XL — запуск JTAG Hack с помощью глитча проверки фьюзов и CPU_PLL_BYPASS. А вот RGH 1.2 объединил плюсы RGH1 и RGH2, запуская «двойную» загрузку одновременно с использованием CPU_PLL_BYPASS, снова давая идеальную стабильность и в то же время совместимость с RGH2-софтом.

Как это было реализовано? Основной вопрос, заинтересовавший меня тогда, — почему CPU_PLL_BYPASS перестали использовать? Информации про это нигде не было, может, его заблокировали на аппаратном уровне? Быстрая проверка на логическом анализаторе показала, что нет, все еще замедляет в 128 раз. Почему бы тогда не использовать его? Да, на таком замедлении ожидание этапа подсчета контрольной суммы займет очень много времени. Но зачем включать замедление так рано? Нам ведь главное «покрыть» замедлением только момент проверки цифровой подписи (и начало POST-кода перед ним для референса). Поэтому можно немного отложить включение CPU_PLL_BYPASS, чтобы оно произошло как раз перед проверкой, и получить следующий алгоритм глитча приставки:

  • ожидание POST-кода 0xD9 (или 0xD8, если смотреть по линии POST_1);

  • ожидание некоторого числа миллисекунд;

  • включение PLL_BYPASS;

  • ожидание POST-кода 0xDA;

  • ожидание другого некоторого числа миллисекунд;

  • глитч-импульс на RST.

Правильный подбор значений длился несколько дней, но все получилось.

Как сказал один из ревьюверов статьи: «Зачетный ковер»
Как сказал один из ревьюверов статьи: «Зачетный ковер»

Проект я с тех пор подзабросил, а исходники передал тому самом Джошу aka Octal450, который продолжил работу и сделал:

  • обновленную версию RGH 1.2 для других модчипов;

  • версию с CPU_EXT_CLK (замедляет примерно в 10,7 раза, а не в 128) для Fat-ревизий Xenon и Zephyr, где использование CPU_PLL_BYPASS приводит к зависанию;

  • расширенный набор функций для утилиты J-Runner (до сих пор поддерживает и добавляет фичи).

И вот теперь в его руки попадает тот самый даташит с расположением CPU_PLL_BYPASS для Slim-приставок. Теперь понимаете грандиозность момента? Конечно же, он сразу пошел портировать наработки и подбирать параметры замедления (глитча) уже для Slim-версии. В отличие от Fat-ревизий, использование CPU_PLL_BYPASS на Slim давало замедление не в 128 раз, а в целых 640. Из-за этого подбор параметров затянулся.

Здесь уже и я подключился к исследованию. Чтобы ускорить подбор параметров, я собрал особый загрузчик, который повторял код в бесконечном цикле, и уже на это непотребство натравил чип. С таким стендом параметры довольно быстро удалось определить, после чего я передал их Джошу для доработки.

Один из чипов — для анализа POST шины, второй — для глитча
Один из чипов — для анализа POST шины, второй — для глитча

И тут у меня возникла безумная идея. Если есть настолько крутое замедление, можно ли обойтись совсем без чипа с его высокой точностью? В приставке есть System Management Controller (SMC) — независимый микроконтроллер на ядре Intel 8051, работающий на частоте 48 МГц. На его основе делали JTAG SMC Hack, да еще и линия CPU_RESET на него идет…

Где-то на этом моменте я накупил различных материнок от Xbox 360, обложился логическими анализаторами и с головой ушел в очередное исследование внутренностей приставки…

Что в SMC тебе моём?

Как и во многих других системах, помимо основного CPU, в Xbox 360 имеется дополнительный чип SMC, который выполняет вспомогательные задачи:

  • реакция на кнопки (питания, извлечения диска);

  • подача питания на компоненты приставки;

  • согласование вывода изображения (AV или HDMI);

  • контроль температуры и кулеров.

В ноутбуках такая штука зовется Embedded Controller (EC), но идеи схожи. SMC всегда включен, потребляет немного тока и у него есть своя прошивка!

Забавно выходит. Копетти ссылается на мои статьи, а я — на его
Забавно выходит. Копетти ссылается на мои статьи, а я — на его

Конечно, прошивка эта зашифрована и имеет контроль целостности, но алгоритм давно известен, а никакой проверки цифровой подписи сюда не завезли:

def decrypt_smc(data):
    key = [0x42, 0x75, 0x4e, 0x79]
    res = bytearray()
    for i in range(len(data)):
        j = data[i]
        mod = j * 0xFB
        res.append(j ^ (key[i & 3] & 0xFF))
        key[(i + 1) & 3] += mod
        key[(i + 2) & 3] += mod >> 8
    return bytes(res)

Интересно, что алгоритм есть и в коде гипервизора Xbox 360.

Вероятно, система так проверяет корректность SMC при обновлении
Вероятно, система так проверяет корректность SMC при обновлении

Читать или писать прошивку тоже не проблема — интерфейс SPI + два дополнительных контакта.

Широко известный лет 10 назад Super NAND Flasher
Широко известный лет 10 назад Super NAND Flasher

Один из контактов — Reset-линия SMC, а другой — DBG_EN.

И снова схема помогает найти детали
И снова схема помогает найти детали

При перезагрузке с удержанием DBG-линии SMC входит в специальный режим, когда по SPI можно читать и писать NAND приставки. Кстати, это нашли благодаря изучению Sidecar от девелоперской версии Xbox 360 (XDK).

Всё самое интересное — в этой нашлепке
Всё самое интересное — в этой нашлепке

Как раз к ней подключены те самые разъемы на плате, куда паяют программатор. Такая архитектура помогает разработчику восстановить приставку, даже если NAND целиком стерт.

Вид на нашлепку изнутри приставки
Вид на нашлепку изнутри приставки

К этому еще вернемся в самом конце, а пока что — дизасм и раскрытие секретов!

Привет из SMC

Если сходу загрузить в дизассемблер расшифрованный SMC, то в самом начале будет некая непонятная белиберда, не похожая на адекватный программный код.

Прошивка 8051 явно не так должна начинаться
Прошивка 8051 явно не так должна начинаться

На самом деле, для дополнительной защиты от анализа первые 4 байта SMC — случайные, чтобы код в каждой приставке был зашифрован уникальным образом. «Настоящие» 4 начальных байта находятся в самом конце прошивки.

Почему-то все утилиты по работе с SMC не переносят эти 4 байта при расшифровке
Почему-то все утилиты по работе с SMC не переносят эти 4 байта при расшифровке

Если их поместить в начало, получим гораздо более осмысленный код с прыжками на различные обработчики прерываний. .

Уже больше похоже на начало прошивки 8051
Уже больше похоже на начало прошивки 8051

Одна тайна разгадана, осталось много. Как встроиться в прошивку? Как управлять периферией? Как вообще написать что-то под этот чип?!

Часть информации можно почерпнуть из двух других проектов — SMC JTAG Hack и CR4 XL (пропатченные прошивки для SMC с их кодом можно найти в комплекте с утилитой J-Runner). Известно, что код в первом проекте использует GPIO для конфигурации GPU по JTAG:

Именовал функции сам, может отличаться от задумки автора
Именовал функции сам, может отличаться от задумки автора

А код CR4 XL, в свою очередь, следит за состоянием GPIO и отправляет по I2C соответствующие команды.

Здесь кода было поменьше, зато сам код понятнее
Здесь кода было поменьше, зато сам код понятнее

Если сопоставить номера битов в аппаратных регистрах со схемой приставки, то можно заметить, что они соответствуют портам GPIO, к которым подключены соответствующие линии.

Как же удобно иметь схематик под рукой
Как же удобно иметь схематик под рукой

Копаясь тем же образом в коде дальше, можно найти все регистры, отвечающие за GPIO:

  • SFR_80,90,A0,C0,C8 = GPIO_P0..P4 — значения на выводах GPIO

  • SFR_A2..A6 = GPIO_P0..P4_DIR — режим пина (input или output)

  • SFR_9D..9F,A1,A7 = GPIO_P0..P4_OD — настройка open-drain-режима

Теперь можно шевелить пинами и выводить что-то осмысленное на логический анализатор! Но для этого сначала нужно скомпилировать код. Поскольку придется встраиваться в существующую прошивку, писать нужно прямо на ассемблере. К примеру, ниже — код простейшей функции, которая на короткое время взведет один из неиспользуемых пинов SMC в единицу. Функция будет размещена по смещению 0x31E0 — это первый неиспользуемый адрес в оригинальной прошивке SMC для ревизии Corona.

DBG_PIN equ 080h.5

org 031E0h
    setb DBG_PIN
    clr  DBG_PIN
    ret
end

Минималистично? А то! После сборки получилось всего 5 байт.

Теперь его нужно каким-то образом засунуть в оригинальную прошивку. Удобнее всего это сделать, если встроиться в основной цикл исполнения. В микроконтроллерах вся логика обычно реализуется через finite-state machine (FSM, конечные автоматы). И все эти автоматы по очереди выполняются в главном цикле.

Ну ладно, в двух главных циклах
Ну ладно, в двух главных циклах

Малый цикл содержит задачи, требующие немедленной реакции (например, обработка I2C). В большом же расположены задачи, которые требуется запускать с определенным интервалом, так они отсчитывают время до тайм-аута (например, обработка кнопок, переключение состояний питания, моргание светодиодом).

Думаю, ничего страшного не случится, если заменить задачу моргания диода (которого на магазинной плате, кстати, не бывает) на вызов нашей процедуры:

import struct

def patch(data, offset, new):
    return data[:offset] + new + data[offset + len(new):]

def lcall(offset):
    return b"\x12" + struct.pack(">H", offset)

CODE_START = 0x31E0

smc = open("smc_corona.bin", "rb").read()
code = open("build.bin", "rb").read()[CODE_START:]
smc = patch(smc, CODE_START, code)
smc = patch(smc, 0x829, lcall(CODE_START))
smc = patch(smc, 0x256B, b'\x90') # P0.5 OUT dir

open("new_smc.bin", "wb").write(smc)

В результате на паде DB3R4, который соответствует порту 80h.5, можно видеть сигнал с периодом 20 мс.

Заодно и период определили, с которым выполняется «большой» цикл
Заодно и период определили, с которым выполняется «большой» цикл

Теперь хоть какой-то, но дебаг имеется. Неплохо было бы иметь дебаг получше, например, UART. И — бинго — на схеме платы он есть:

А еще чуть выше есть системный UART от CPU
А еще чуть выше есть системный UART от CPU

Обычно UART соответствует некоторый аппаратный регистр, запись в который приводит к посылке данных. И чтобы найти этот самый нужный регистр, я, конечно же, писал во все регистры подряд и смотрел, что происходит на аппаратной линии (так, конечно, лучше не делать: за время брутфорса я успел повключать регуляторы питания, наткнуться на регистр watchdog и пару других вещей). И таки нашел. Запись данных в регистр E7 приводит к импульсам на линии.

UART, ты ли это?
UART, ты ли это?

Похоже на UART, но нет, картина всегда одинаковая, что бы я в регистр ни писал.

Дальнейшие эксперименты с брутфорсом соседних регистров принесли результаты. Оказывается, нужно дополнительно включить UART (SFR_E8 = 0xC0) и настроить его скорость (SFR_E9 = 0xFF, максимальная скорость — 1 500 000 бод). После этого уже получаем нормальный вывод UART.

Вот теперь точно UART
Вот теперь точно UART

В регистрах есть биты состояния, по которым можно определить, завершился ли вывод текущего символа и можно ли печатать следующий. Но для простоты реализации я подобрал значение задержки, за которое символ гарантированно успевает выводиться для текущей конфигурации скорости:

put_uart:
    mov     0E7h, A
    mov     A, #09h
loop_uart:
    djnz    0E0h, loop_uart
    ret

Благодаря этому в UART уже можно писать более осмысленные вещи:

Да, почему-то аппаратно нет завершающего бита, но это не мешает работе
Да, почему-то аппаратно нет завершающего бита, но это не мешает работе

I2C

Одного лишь CPU_PLL_BYPASS для стабильного глитч-хака из SMC, по моим предположениям, было недостаточно. Даже с замедлением в 640 раз длина импульса, который порождается кодом ниже, была слишком велика (по сравнению с импульсом в RGH1):

    clr     080h.6
    setb    080h.6

Значит, помимо активации CPU_PLL_BYPASS, нужно дополнительно замедлять процессор по I2C, как это делали в RGH2, снизив опорную частоту 100 МГц, которую выдает HANA.

Схема от Xenon. В более новых ревизиях вместо ANA используется HANA (HDMI ANA)
Схема от Xenon. В более новых ревизиях вместо ANA используется HANA (HDMI ANA)

Увы, в открытом доступе нет информации, как именно в RGH2 происходит замедление. С помощью логического анализатора удалось достать только несколько значений, которые использовались в модчипах и проекте CR4 XL:

  • [CF] = 0x40’44’AC’C0 — Corona, 100 МГц, обычный режим;

  • [CF] = 0x08’44’40’40 — Corona, 33,3 МГц, замедленный режим (CR3 Pro / CR4);

  • [CF] = 0x08’54’54’30 — Corona, 25 МГц, замедленный режим (RGH2);

  • [CD] = 0x02’0C’80’4E — Falcon, Jasper, Trinity, 100 МГц, обычный режим;

  • [CD] = 0x03’80’08’4E — Falcon, Jasper, Trinity, 31,5 МГц, замедленный режим.

Почему значения именно такие? Какие другие частоты можно задать? В обсуждениях упоминалась информация, что на совсем низких частотах процессор работает нестабильно, — правда ли это? Давайте узнаем!

Для этого нужно проверить частоту опорного сигнала, поступающего от HANA к процессору. Логично было бы сделать это с помощью осциллографа, и таковой у меня имелся — DSCope U2P20.

Купил на AliExpress вместе с анализатором DSLogic. Пригодился впервые.
Купил на AliExpress вместе с анализатором DSLogic. Пригодился впервые.

Одна загвоздка: на процессор поступает 100 МГц, сигнал дифференциальный и низковольтажный, а осциллограф работает только с частотами до 50 МГц, и то — если повезет. Чтобы производить замер частоты, нужно либо более крутое оборудование (это сейчас в Positive Labs у меня появилась огромная лаборатория под боком, а тогда приходилось довольствоваться имеющимся), либо каким-то образом поделить частоту, допустим, раз в 10, а затем измерить осциллографом, что я и сделал.

Чем же можно поделить частоту? Под рукой было несколько модчипов для Xbox 360 на базе CPLD Xilinx CoolRunner II, как на рисунке ниже.

Как вы уже догадались, использовать я их буду не совсем по назначению
Как вы уже догадались, использовать я их буду не совсем по назначению

Обратите внимание, что на фотографии на чипе нет маркировки. И нет, она не затерта. Изначально эти модчипы делались на именно таких 44-ногих чипах, XC2C64A-VQ44. Народ привык, покупал именно эти, но вот незадача: они закончились. Зато остались те же самые XC2C64A в других корпусах.

Корпуса QFN-48, BGA-56, QFP-100 одного и того же CPLD
Корпуса QFN-48, BGA-56, QFP-100 одного и того же CPLD

Увы, люди берут то, к чему привыкли, и, несмотря на почти полную идентичность (у них был другой ID и не весь софт их поддерживал), популярностью эти модчипы не пользовались. И китайцы придумали решение … Они подумали: «我們只需要在板子前面裝上塑膠誘餌,然後把真正的晶片藏在背面就可以了 Давайте спереди на плате будет просто пластиковая обманка, а настоящий чип спрячем сзади!»

Чип стыдливо прикрывается толстым двусторонним скотчем со специальным вырезом
Чип стыдливо прикрывается толстым двусторонним скотчем со специальным вырезом

Вот на таком модчипе я и соорудил «делитель частоты» (болванку отпаял, чтоб не мешалась).

Слева — линейный регулятор для VCCIO, настроенный примерно на 1,3 В
Слева — линейный регулятор для VCCIO, настроенный примерно на 1,3 В

Несмотря на то что в этом CPLD нет поддержки дифференциальных линий, благодаря тому, что в Xbox 360 диффпары HANA формируются двумя линиями push-pull, работающими в противофазе, получилось корректно снимать уровни относительно GND. Правда, пришлось понизить напряжение I/O Bank до 1,3 В. Вот такой VHDL-проект с уровнями LVCMOS12 отлично делит частоту на 10:

process(PIN_B) is
  begin
    if PIN_B'event then
	     divcnt <= divcnt + 1;
		  if divcnt = 9 then
				temp <= not temp;
				divcnt <= 0;
		  end if;
		  PIN_D <= temp;
    end if;
  end process;
Интерфейс DSCope прост как топор
Интерфейс DSCope прост как топор

Ну, почти отлично: при 100 МГц на выходе получается неравномерная колбасня, оцениваемая осциллографом на 9,31 МГц, но на чуть меньших частотах все в порядке. .

Вот такие прямоугольные сигналы мне предстояло наблюдать следующие пару дней
Вот такие прямоугольные сигналы мне предстояло наблюдать следующие пару дней

Zephyr, Falcon, Jasper, Trinity

На большей части ревизий Xbox 360 (Zephyr, Falcon, Jasper, Trinity) механизм опорной частоты работает одинаково, поэтому разумно начать анализ именно с них. В RGH2 для замедления нужно сконфигурировать регистр 0xCD. Для этого я начал с некоторого значения, а затем менял каждый из битов регистра и фиксировал получаемую с помощью осциллографа частоту. Для работы с I2C я воспользовался открытым проектом pyFTDI.

Радиаторов без кулеров хватает минут на 6–7 до перегрева
Радиаторов без кулеров хватает минут на 6–7 до перегрева
from pyftdi.i2c import I2cController
import struct

i2c = I2cController()

i2c.configure('ftdi://ftdi:2232h/1')
slave = i2c.get_port(0x70)

def read_cd():
    (_, result) = struct.unpack("

Тщательно замерив результаты для комбинаций битов, я получил вот такую табличку:

# xxxxxx 010 000110 00000100001001110 - 12.0 MHz
# xxxxxx 100 000110 00000100001001110 - 7.17 MHz
# xxxxxx 111 000110 00000100001001110 - 4.48 MHz
# xxxxxx 110 100110 00000100001001110 - 0.92 MHz
# xxxxxx 110 010110 00000100001001110 - 1.56 MHz
# xxxxxx 110 001110 00000100001001110 - 2.40 MHz
# xxxxxx 110 000010 00000100001001110 - 12.0 MHz
# xxxxxx 110 000100 00000100001001110 - 7.17 MHz
# xxxxxx 110 000111 00000100001001110 - 4.48 MHz
# xxxxxx 110 000110 10000100001001110 - ???  MHz
# xxxxxx 110 000110 01000100001001110 - 75.6 MHz
# xxxxxx 110 000110 001001000 01001110 - 40.4 MHz
# xxxxxx 110 000110 000101000 01001110 - 28.8 MHz
# xxxxxx 110 000110 000011000 01001110 - 13.9 MHz
# xxxxxx 110 000110 000000000 01001110 - ??? MHz
# xxxxxx 110 000110 000001100 01001110 - 7.33 MHz
# xxxxxx 110 000110 000001010 01001110 - 6.22 MHz
# xxxxxx 110 000110 000001001 01001110 - 5.67 MHz
# xxxxxx 110 000110 000001000 11001110 - 5.40 MHz
# xxxxxx 110 000110 000001000 00001110 - 4.98 MHz
# xxxxxx 110 000110 000001000 01101110 - 5.19 MHz
# xxxxxx 110 000110 000001000 01011110 - 5.16 MHz
# xxxxxx 110 000110 000001000 01011010 - 5.10 MHz
# xxxxxx 110 000110 000001000 01001100 - 5.12 MHz
# xxxxxx 110 000110 000001000 01001111 - 5.12 MHz

Итого получается:

  • первые 6 бит не влияют на частоту;

  • следующие 3 бита — делитель № 1 (минус единица);

  • следующие 6 бит — делитель № 2 (минус единица);

  • следующие 9 бит — множитель (минус единица);

  • последние 8 бит — коррекция.

Например, если взять конфигурацию по умолчанию (0x20C804E), то можно получить:

  • 0x20C804E = 100  000110  010000000  01001110

  • D1 = 4 + 1

  • D2 = 6 + 1

  • B = 128 + 1

  • O = 78

Тогда итоговая частота равна N × 129 / 5 / 7 + 78 × D, где

  • N — частота системного кварца (27 МГц)

  • D — множитель коррекции (~0,00214 МГц)

Занимательный факт: по формуле выходит, что опорная частота Xbox 360 этих ревизий равна около 99,68 МГц.

В ходе экспериментов с различными частотами выяснилось, что менять коэффициенты PLL «на ходу» затея не очень хорошая: при очень низких значениях частота не меняется либо меняется неправильно, при очень высоких — частота перестает генерироваться совсем. Но самое нехорошее — что при одних и тех же параметрах и самых идеальных условиях частота может совсем незначительно, но отличаться!

Напомню, что для высокой точности глитч-хака обязательно нужно попадать в один и тот же момент. А здесь он «плавает». Теперь становится понятно, почему результаты RGH2 были такие рандомные. Чтобы исправить этот недочет, я начал искать, каким же регистром можно перезагрузить PLL: обычно в оборудовании смена конфигурации требует выключения и последующего включения аппаратного модуля — вдруг поможет.

Для этого я начал менять биты соседних регистров и одновременно смотреть на вывод осциллографа. И такой брутфорс принес результаты! Правда, нашел совсем не то, что искал, но вышло даже лучше. Старшие 8 бит регистра 0xCE управляют источником опорной частоты CPU:

  • 0x08E84014 — стандартный режим, PLL;

  • 0x28E84014 — Bypass EXT (27 МГц) — частота напрямую с внешнего кварца;

  • 0x48E84014 — смена режима регистра 0xCD, делители и множители работают как-то странно (не изучал, возможно, это PLL_INT);

  • 0x88E84014 — Bypass INT (предположительно, от 3 до10 МГц) — вероятно, частота напрямую с внутреннего генератора, сильно плавает;

  • 0xC8E84014 — None (нет генерации).

И режим Bypass EXT отлично подходит для глитча: частота всегда стабильна, включается и выключается одной командой без каких-либо последствий, в отличие от перенастройки PLL.

Corona

В ревизии Corona чип HANA отсутствует — его функциональность теперь находится в южном мосте. Регистры генератора частот тоже поменяли свое назначение.

Первая строчка обозначает поколение южного моста. XSB (Xenon), PSB (Panda), KSB (Koala)
Первая строчка обозначает поколение южного моста. XSB (Xenon), PSB (Panda), KSB (Koala)

С помощью аналогичного перебора и экспериментов с регистром 0xCF была получена следующая табличка:

#0x100 - 132 MHz
#0x0C0 - 100 MHz
#0x040 - 33 MHz
#0x030 - 25 MHz
#0x020 - 16.6 MHz
#0x021 - 8.3 MHz
#0x022 - ?
#0x023 - 5.55 MHz
#0x024 - 18.7 MHz
#0x025 - 9.3 MHz
#0x026 - 4.68 MHz
#0x027 - ?
#0x028 - 20.8 MHz
#0x029 - 10.4 MHz
#0x02a - 5.2 MHz
#0x02b - 6.9 MHz
#0x02c - 22.9 MHz
#0x02d - 11.4 MHz
#0x02E - 5.7 MHz
#0x02F - 7.6 MHz
#0x116 - 36 MHz
#0x319 - 180 MHz
#0x219 - 140 MHz

Из этого выходит, что последние 3 бита отвечают за делитель, но несколько необычным образом:

  • 00 — x1

  • 01 — x2

  • 10 — x4

  • 11 — x3

А следующие за ними 8 бит — множитель. И формула для конечной частоты имеет вид M / D × 100/48, гд.

  • M — множитель,

  • D — соответствующий делитель.

Наименьшая стабильная частота, которую удалось выставить,— 3,6 МГц, что очень даже неплохо. Увы, ускоряется и замедляется вообще все: GPU, CPU и даже SMC (это легко заметить по изменившейся частоте работы UART). А раз SMC замедляется, замедлится и код, который выполняется внутри и реализует глитч-хак, поэтому замедление через HANA PLL, увы, не годится.

Интересно

При таком замедлении вращающийся кулер начинает издавать специфический звук, причем тон звука зависит от заданной частоты. Device Orchestra, ловите идею! .

К счастью, как и на предыдущих ревизиях, брутфорсом регистров HANA был обнаружен режим Bypass и для ревизии Corona. Для этого достаточно в регистре 0xDB установить 3 бит:

  • 0x01F001F8 — Bypass EXT (25 МГц),

  • 0x01F001F0 — стандартный режим, PLL.

После этого на CPU вместо частоты 100 МГц начинает поступать 25 МГц напрямую с системного кварца, чего вполне достаточно для замедления.

В процессе перебора также был обнаружен аналогичный Bypass для GPU (регистр 0xDB, 0x01F801F0) и для pixel_clk (регистр 0xDC, 0x000001F8).

Проблемные Fat’ки

Как я писал выше, на старых Fat-ревизиях отключение PLL замедляет процессор всего в 128 раз — в 5 раз меньше, чем на Slim-версии. К сожалению, такая величина замедления оказалась все еще недостаточной и глитч зачастую не срабатывает из-за тормознутости SMC. То в нужный момент не попадает, то импульс недостаточно короткий. Вариантов решения тут два: либо использовать конфигурируемое замедление через I2C (чревато нестабильностью из-за особенностей HANA PLL), либо… разогнать южный мост!

Изначально он работает на частоте 48 МГц, но ведь эта опорная частота тоже генерируется HANA и может быть изменена! В итоге снова был выполнен брутфорс регистров и было определено, что за частоту южного моста отвечает регистр 0xD4. Требуемый уровень разгона был с легкостью достигнут через изменение младших битов:

  • 0x990e00e — изначальное значение (48 МГц);

  • 0x990e001e — двукратный разгон (96 МГц);

  • 0x990e026 — максимальное значение, которое смог осилить мой HANA (120 МГц).

Определить полный расклад регистра по битам (делитель, Bypass, множитель) можете самостоятельно на досуге 🙂

А что глитчить-то?

С замедлением разобрались, теперь нужно бы определиться, в какой момент работы приставки нужно послать тот самый глитч-импульс.

Процесс загрузки Xbox 360 (на последних версиях системы) происходит следующим образом:

  • 1BL — инициализация FSB, загрузка, расшифровка и проверка CB через RSA-2048;

  • CB_A — загрузка, расшифровка и проверка CB_B через HMAC-SHA1;

  • CB_B — инициализация DRAM, загрузка, расшифровка и проверка CD через HMAC-SHA1;

  • CD — инициализация GPU и прочего «железа», дальнейшая загрузка системы.

И эту цепочку доверия хотелось бы оборвать где-нибудь ближе к началу. В методе RGH2 глитчем атакуют процедуру сравнения хеш-суммы в загрузчике CB_A после POST-кода 0xDA.

То самое место, где решается, загрузится бутлоадер или нет
То самое место, где решается, загрузится бутлоадер или нет

Точнее, все думали, что глитчили именно это место. Тщательные эксперименты показали, что на cmpwi и beq reset-глитч не влияет. Зато он влияет на команду addi (mr): .

Реальное место глитча
Реальное место глитча

После глитч-импульса вместо реального значения из регистра-источника используется значение 0, что в итоге принимается за успех сравнения. Знание этого факта позволяет найти еще одно интересное место для глитч-атаки, чуть раньше, возле пост-кода 0xD5. И даже не одно, а целых четыре.

Место еще чуть раньше, перед чтением бутлоадера
Место еще чуть раньше, перед чтением бутлоадера

Здесь при вмешательстве в любую из выделенных команд получится переполнение при чтении CB_B и перезапись текущего выполняемого кода данными из NAND. Это место удобно тем, что находится недалеко от чтения NAND и в теории позволяет обойтись даже без сигнала POST_OUT в качестве референсной точки. Эта идея была реализована в самой первой пробной версии RGH3, однако реализация не показала достаточной стабильности по сравнению со стандартной и была заброшена до лучших времен.

Занятный факт

balika011 заинтересовался этим методом, порасспрашивал детали и сделал свою реализацию «беспостового глитч-хака» (к сожалению, без указания первоисточника).

Извините, можно там побыстрее? У меня собака ждет!

Итоговый алгоритм атаки через глитч сравнения хеша выглядит приблизительно так:

  • ожидание POST 0xD6;

  • включение замедления по HANA;

  • ожидание POST 0xD8;

  • задержка перед замедлением X ms;

  • включение замедления PLL;

  • ожидание POST 0xDA;

  • задержка перед импульсом Y ms;

  • глитч-импульс;

  • выключение замедления PLL;

  • выключение замедления HANA;

  • проверка успешности атаки.

Почему именно такие значения POST-кодов, а не D8, D9, DA? Изначально в RGH1 POST-коды шли с пропусками (0x37, 0x39), и определить их можно было только по первому биту. В RGH2 это упрощало реализацию на не слишком ресурсоемком CPLD (счетчик меньшей размерности). Теперь же, из-за того, что на новых материнках ревизии Corona нет разведенной POST-шины, приходится подсовывать подпружиненный контакт под процессор, и этим способом можно достать POST_OUT_1, но не POST_OUT_0 (0 и 1 — номера битов значения POST-кода):

Рамка с подпружиненным контактом как замена POST_OUT_1
Рамка с подпружиненным контактом как замена POST_OUT_1

В отличие от FPGA, на которых изначально реализовывали RGH, микроконтроллер SMC плохо подходит для высокоточных замеров времени. Процесс выполнения может быть потревожен прерываниями, а настройка системного таймера проблематична — нет документации. Самый простой вариант — отключить все прерывания и зависнуть в while(--count), контролируя время ожидания начальным значением обратного счетчика.

Но если долго находиться в цикле, сработает сторожевая псина watchdog и перезагрузит SMC. Можно, конечно, периодически пинать псину обнулять его, но это уменьшает точность и все равно увеличивает время, затрачиваемое на одну попытку запуска. Поэтому чем меньше время ожидания, тем лучше. А что делается между POST-кодами 0xD8 и 0xDA? Расшифровка и подсчет хеш-суммы CB_B. И чем меньше размер CB_B, тем быстрее закончится этот процесс. Дело за малым: нужно уменьшить CB_B до минимального возможного размера!

Из кода CB_A видно критерии корректности CB_B:

  • размер не более 0xC000;

  • entry point не менее 0x3D0;

  • размер кратен 4;

  • entry point не выходит за пределы размера.

Дизасм проверки корректности CB_B
Дизасм проверки корректности CB_B

Если дополнительно учесть, что SHA-1 считается блоками по 0x40 байт, то получится, что размер CB_B делать менее 0x400 нецелесообразно: последний блок вычислений все равно будет 0x40 байт. Несмотря на требование о размещении entry point не ранее адреса 0x3D0, остальной код может быть размещен в произвольной точке. Поэтому, если не удается уложиться в 0x400 − 0x3D0 = 0x30 байт, никто не запрещает использовать и меньшие адреса.

Итого получается следующая цепочка загрузки:

  • 1BL загружает и расшифровывает CB_A;

  • CB_A загружает и расшифровывает CB_X (новый промежуточный загрузчик вместо CB_B);

  • CB_X загружает уже расшифрованный CB_B;

  • CB_B загружает уже расшифрованный CD.

Как можно видеть, CB_X добавлен в цепочку только ради уменьшения времени внутри CB_A. Теперь нужно сделать этот самый CB_X, который выполнит единственную задачу — загрузит следующий бутлоадер в цепочке, CB_B.

Создание своего бутлоадера

Чтобы создать свой минимальный, но вполне рабочий бутлоадер, нужно иметь представление о соглашениях вызова этих самых бутлоадеров. Из реверса 2BL можно увидеть, что он:

  • при старте копирует себя по адресу 0xC000;

  • берет из r31 данные, где расположено то, что нужно загрузить;

  • копирует BL из NAND в начало SRAM;

  • расшифровывает и проверяет данные;

  • перед следующим переходом устанавливает указатель на следующий BL в r31.

Таким образом возникает некая эстафета. Каждый загрузчик проверяет своего соседа и одновременно передает в r31 указатель уже на соседа соседа.

Поскольку заморачиваться с шифрованием и проверкой не требуется, при создании бутлоадера нужно обеспечить:

  • копирование создаваемого бутлоадера в 0xC000;

  • загрузку CB_B;

  • увеличение указателя в r31 на размер кода (CB_X);

  • передачу управления CB_B.

Первая часть реализуется довольно просто — достаточно знать нужные адреса:

sub_3D0:                            # lowest possible entry point
            li        r3, 0x200
            oris      r3, r3, 0x8000
            sldi      r3, r3, 32
            oris      r4, r3, 1     # 0x8000020000010000, SRAM address
            addi      r5, r4, -4    # will be used in the second part
            ori       r6, r4, 0xC000 # 0x800002000001C000, where to copy
            li        r2, 0x7F      # 0x80 * 8 bytes
            mtctr     r2
copy_second_stage:                  # memcpy cycle
            ldu       r2, 8(r4)    
            stdu      r2, 8(r6)
            bdnz      copy_second_stage
            b         0xC350        # jump to the second part

После этого нужно передать информацию SMC о том, что загрузчик успешно запустился через выставление POST-кода. Делать это нужно с небольшой паузой, чтобы SMC успел это увидеть. Предыдущий POST-код 0xDB имеет единицу в первом бите, соответственно, чтобы SMC увидел изменение, нужно установить нолик. Значение 0x54 отлично подойдет:

        oris      r6, r3, 6        # 0x8000020000060000, I/O
        li        r2, 0x54          # bit ‘1’ must be 0 here
        sldi      r2, r2, 56
        std       r2, 0x1010(r6)   # POST 0x54 to tell SMC we're done

        lis       r2, 1             # small delay for the POST 
        mtctr     r2
sleep_cycle:
        nop                   
        bdnz      sleep_cycle

В конце нужно выполнить копирование и запуск CB_B. Код можно спереть подсмотреть в дизасме официальных бутлоадеров:

        oris      r6, r3, 0xC800 # 0x8000020000C80000, mapped NAND
        addi      r6, r6, -4
        clrldi    r2, r31, 32
        add       r6, r6, r2    # + provided r31 offset
        lwz       r4, 0x10(r6)  # CB_B size
        lwz       r3, 0xC(r6)   # CB_B entry point
        add       r31, r31, r4  # update r31 offset
        srdi      r4, r4, 2
        mtctr     r4

copy_cbb:                       # copy CB_B to the SRAM        
        lwzu      r2, 4(r6)
        stwu      r2, 4(r5)
        bdnz      copy_cbb

        clrlwi    r3, r3, 16    # prepare jump address
        addis     r3, r3, 0x200
        mtlr      r3
        blr                     # jump to the CB_B

Итого полученный код занял лишь 144 байта из примерно 1000 доступных! Неплохо.

Сборка образа для приставки

Чтобы все правильно загрузилось, нужно корректно состыковать все кусочки пазла, необходимые для запуска приставки: SMC, CB_A, CB_X, CB_B (под нужную материнку, пропатченный), CD (кстати, опенсорсный) и XeLL (Xenon Linux Loader). В качестве базового источника информации и функций я использовал существующие сборщики подобных образов для Xbox 360 (например, build.py для RGH1 и RGH2, исходники Xebuild GUI, JRunner).

Начнем с заголовка. Он расположен в самом начале NAND и в нем указана информация, откуда нужно загружать код SMC для южного моста и 2BL для CPU. Ну и копирайт производителя приставки.

Формат заголовка NAND приставки
Формат заголовка NAND приставки

Как шифруется SMC, я рассказывал в начале статьи. Его нужно разместить по указанному адресу — тут ничего сложного. CB_A зашифрован алгоритмом RC4 с использованием ключа из 1BL и рандомного набора данных из заголовка 2BL. CB_B зашифрован тем же RC4, но уже с использованием ключа процессора (нулевой, если используем сервисный CB_A), ключа шифрования CB_A и, опять же, рандомных данных из заголовка CB_B. Остальное не шифруется просто потому, что перехват управления уже выполнен и можно делать что хочется.

import struct, secrets, sys, hmac, hashlib
import Crypto.Cipher.ARC4 as RC4

key_1BL = "\xDD\x88\xAD\x0C\x9E\xD6\x69\xE7\xB5\x67\x94\xFB\x68\x56\x3E\xFA"

def encrypt_smc(data):
    key = [0x42, 0x75, 0x4e, 0x79]
    res = bytearray()
    for i in range(len(data)):
        j = data[i] ^ (key[i&3] & 0xFF)
        mod = j * 0xFB
        res += struct.pack("B", j)
        key[(i+1)&3] += mod
        key[(i+2)&3] += mod >> 8
    return bytes(res)

def encrypt_cba(cba):
     rnd = secrets.token_bytes(16)
     key = hmac.new(key_1BL, rnd, hashlib.sha1).digest()[0:0x10]
     return (key, cba[0:0x10] + rnd + RC4.new(key).encrypt(cba[0x20:]))

def encrypt_cbb(cbb, cba_key, cpu_key=b"\x00"*16):
    rnd = secrets.token_bytes(16)
    key = hmac.new(cba_key, rnd + cpu_key, hashlib.sha1).digest()[0:0x10]
    return cba[0:0x10] + rnd + RC4.new(key).encrypt(cba[0x20:])

def insert(image, data, offset=None):
    if offset is None:
        offset = len(image)
    if offset > len(image):
        image += b"\xFF" * (offset - len(image))
    return image[:offset] + data + image[offset + len(data):]

# SMC
smc = open("smc.bin", "rb").read()
smc_ptr = 0x800

# BLs
cba_ptr = 0x8000
cba = open("cba.bin", "rb").read()
cbx = open("cbx.bin", "rb").read()
cbb = open("cbb.bin", "rb").read()
cd  = open("cd.bin", "rb").read()

# make header
image = struct.pack(">HHLLL64s40xLL", 0xFF4F, 1888, 0, cba_ptr, 0, b"RGH3",  len(smc), smc_ptr)

# add SMC
image = insert(image, crypt_smc(smc), smc_ptr)

# add BLs
(key, cba_enc) = encrypt_cba(cba)
image = insert(image, cba_enc, cba_ptr)
image = insert(image, encrypt_cbb(cbx, key)) # MFG cba, so zero cpu_key
image = insert(image, cbb) # decrypted due to load via cbx
image = insert(image, cd) # decrypted due to patched cbb

# add XeLL
xell  = open("xell.bin", "rb").read()
image = insert(image, xell, 0xC0000)
image = insert(image, xell, 0x100000)

# save image
open("image.bin", "wb").write(image)

В результате будет получен готовый для глитчинга образ (без ECC-сумм, но это легко добавляется через утилиту nandpro). Осталось записать его в приставку и… Подождите-ка, я что-то забыл. Ах да, реализовать всю основную логику глитч-хака в SMC. Расскажу, наконец, об этом.

Подключение сигналов

Поскольку изнутри SMC-кода придется следить за сигналами POST-шины, а еще рулить замедлением CPU через PLL_BYPASS, нужно задействовать два дополнительных GPIO. Как POST_OUT, так и PLL_BYPASS — сигналы low voltage (1,8 В на Slim, 1,2 В на Fat), поэтому хотелось бы использовать низковольтный порт (SMC_P0_GPIO). К счастью, на Corona на нем есть два свободных пина. .

Свободные пины низковольтного порта SMC (Corona)
Свободные пины низковольтного порта SMC (Corona)

GPIO5 вообще не используется, а GPIO4 переключает некоторую конфигурацию, это можно и в коде пропатчить, тем самым освободив себе пин. Поэтому для POST_OUT я использовал DB3R3, а для PLL_BYPASS — DB3R4.

То, что раньше делали модчипами, теперь делается двумя проводками
То, что раньше делали модчипами, теперь делается двумя проводками

На других материнках не все так радужно: все пины используются для довольно важных дел.

Все линии GPIO низковольтного банка SMC
Все линии GPIO низковольтного банка SMC

Если PLL_BYPASS еще можно прикрутить к 3.3v выводу DBG_LED0 (даже если припаять без резистора, это просадит порт I/O SMC, но жить будет), то POST_OUT обязательно должен быть низковольтным, иначе входной сигнал увидеть не удастся (конечно, можно сделать через транзистор или преобразователь уровней, но сами знаете: чем проще, тем лучше).

Из низковольтных GPIO входной только один — GPU_RST_DONE, по которому SMC определяет, ожил ли GPU после подачи питания. Что поделать, придется задействовать его и пропатчить SMC, чтобы он считал, что GPU всегда в порядке. В итоге DBG_LED0 я пустил на PLL_BYPASS (через резистор), а POST_OUT соединил напрямую c P0_GPIO7 до резистора на GPU_RST_DONE. Так и выпаивать ничего не нужно, и сигнал можно заменить на свой.

Это не провод толстый, там резистор в термоусадке спрятан
Это не провод толстый, там резистор в термоусадке спрятан

Для Fat-приставок, где получается, что на вход 1,8 В идет сигнал 1,2 В, лучше немного сместить уровни последовательно установленным резистором на 470 Ом. Благодаря «подтяжке» от сигнала с GPU как нижний, так и верхний уровни немного поднимутся, что повысит стабильность распознавания сигнала (да, здесь напрашивается нормальный преобразователь уровней в этом месте, но опять же — чем проще, тем лучше).

Патчинг прошивки

Понадобится немало патчей, чтобы прошивка заработала как полагается. Например, для хранения некоторых своих данных нужны свободные ячейки памяти. В архитектуре Intel 8051 есть несколько видов памяти и доступов к ним:

  • iRAM — оперативная память, 256 байт;

    • mov A, XXh — прямое обращение к iRAM, только для первых 128 байт;

    • mov A, @R0, непрямое обращение к iRAM по индексу;

  • CODE — область кода, 65 536 байт;

    • movc A, @DPTR — непрямое чтение кода;

  • xRAM — внешняя память (не обязательно ОЗУ), область 65 536 байт;

    • movx A, @R0 — непрямое чтение xRAM (8-битный адрес);

    • movx A, @DPTR — непрямое чтение xRAM (16-битный адрес).

В нашем случае нужна iRAM, поэтому я отжал позаимствовал ее у стека:

smc = patch(smc, 0x7E5, b'\xC2') # own bytes BF..C2 at init
smc = patch(smc, 0x804, b'\xC2') # own bytes BF..C2 at main

Немного магии. Чтобы вызываться максимально часто, при этом не мешая работать основному коду, можно встроиться в каждую процедуру в main() и заменить их на вызовы своего обработчика:

for pos in range(0x805, 0x84d, 3):
    if pos == 0x829: # remove dbg LED FSM
        smc = patch(smc, pos, b"\x00" * 3)
        continue
    my_call = lcall(CODE_START) + ljmp(orig_addr)
    rom_end -= len(my_call)
    smc = patch(smc, rom_end, my_call)
    smc = patch(smc, pos, lcall(rom_end))

Отдельно нужно добавить код инициализации своих данных с помощью перехвата самой последней функции перед главным циклом. Сам код можно разместить в месте, где находится та самая выкушенная удаленная FSM для светодиода (также важно не забыть в конце перейти к функции, которая была заменена):

smc = patch (smc, INIT_START, init_bin + ljmp(0x293C))
smc = patch (smc, 0x7FD, lcall(INIT_START))

Остались патчи по GPIO — нужно поменять один пин с IN на OUT и пропатчить место, где он читался:

smc = patch(smc, 0x256B, b'\x90') # P0.5 OUT dir
smc = patch(smc, 0x2539, b'\xC2') # force EXT_JTM to 0

Теперь можно перейти к написанию самого кода!

Отправка по I2C

Чтобы замедлить процессор, нужно отправить команду по I2C и убедиться, что она успешно выполнилась. В своем проекте CR4 XL команда Team Xecuter частично использовала код прошивки SMC, в связи с чем в блоке добавленного ими кода появилась следующая последовательность вызовов уже существующих функций:

  • заполнение параметров (куда, кому и что отправлять);

  • ожидание завершения предыдущих транзакций;

  • сброс параметров I2C-пинов;

  • проверка, что линия I2C не занята передачей;

  • запуск аппаратной передачи данных.

Код CR4 XL, где запускается выполнение I2C-команды
Код CR4 XL, где запускается выполнение I2C-команды

Выглядит несложно, но это костыль. Как же оно должно работать на самом деле? Давайте разбираться и делать правильно свой костыль.

Отправка I2C в прошивке разбита на три подсистемы:

  • конечный автомат, принимающий запросы от других частей прошивки;

  • виртуальная машина для подготовки последовательности запросов;

  • прерывание I2C.

Первая часть очень простая: как только где-нибудь в прошивке устанавливается один из флажков «я скучаю, давай общаться по аське айтусишке» (таких флажков 10), FSM задает указатель на старт байт-кода, пинает виртуальную машину, а сама тем временем (если все хорошо) переходит в уникальное состояние ожидания.

Проверка флагов и переход в состояние ожидания
Проверка флагов и переход в состояние ожидания
Запуск виртуальной машины начиная с байт-кода по смещению 0x8A
Запуск виртуальной машины начиная с байт-кода по смещению 0x8A
Обработчик байт-кода
Обработчик байт-кода

Вот и сам байт-код, точнее его часть, которая будет выполнена в этой ветке.

Или, если разбить по командам:

  • 0x00 — инициализировать I2C-шину;

  • 0x0E — записать HANA (0xD9 = [00 03 80 00]);

  • 0x0E — записать HANA (0xDC = [00 00 01 E2]);

  • 0x0E — записать HANA (0xDB = [01 E2 01 E2]);

  • 0x0E — записать HANA (0xCD = [00 00 00 67]);

  • 0x0B — обнулить HANA (0xDF = [00 00 00 00]);

  • 0x03 — освободить шину I2C.

Виртуальная машина раскидывает параметры (данные, адреса) из своего же байт-кода по ячейкам памяти, ставит флажок RAM_2Dh.1 и запускает аппаратный таймер. После этого периодически (с частотой 100 КГц) возникает прерывание, во время которого из этих ячеек данные извлекаются и побитово отправляются на пины I2C. В конце концов данные заканчиваются, флажок RAM_2Dh.1 сбрасывается, таймер выключается, виртуальная машина идет на следующий шаг. Цикл продолжается, пока не достигнет кода завершения.

Короче, все, что Team Xecuter накодили, здесь делается автоматически. Как бы использовать эту систему для своих целей? Нужно добавить свой байт-код! В качестве начального вектора для виртуальной машины можно задать 1 байт от 00h до FFh, в оригинальной прошивке максимально используемая ячейка — E2h. Для этого нужно 1 (Init) + 6 (Write) + 1 (Exit) = 8 байт на каждую из двух посылок, итого 16 байт. Значит, реально дописать свое, ничего не сломав. Правда, после байт-кода располагается парочка функций, но их можно немного переместить. Главное не забыть не только переместить функции, но и пропатчить места, где они вызывались:

# i2c re-arrange
smc = patch(smc, rom_end - 0x10, smc[0x2e49:0x2e59])
rom_end -= 0x10
smc = patch(smc, 0x2E9A, lcall(rom_end))
smc = patch(smc, 0x2EA0, lcall(rom_end + 0xA))

Вот теперь вместо них можно поместить две записи нового байт-кода для ускорения и замедления процессора: .

#               IN  W   DB=[01  F0  01  Fx] EX
fast_data = b"\x00\x0E\xDB\x01\xF0\x01\xF0\x03"
slow_data = b"\x00\x0E\xDB\x01\xF0\x01\xF8\x03")
smc  = patch(smc, 0x2e49, fast_data + slow_data)

Теперь для того, чтобы ускорить или замедлить процессор, нужно всего ничего: задать начальный вектор и дать сигнал виртуальной машине (поскольку это делается извне I2C FSM, нужно предварительно проверить, что FSM ничем не занята):

...    
    mov   R0, #SMC_I2C_STATE
    cjne  @R0, #0, return      ; check smc i2c FSM, must be idle

    mov   79h, #0E3h  ; fast sequence
    jс    skip_slow
    mov   79h, #0EBh  ; slow sequence

skip_slow:
    lcall  startup_i2c ; initiate the transfer
    jb    0D0h.5, return ; failed to execute the i2c command
    mov   R0, #SMC_I2C_STATE
    mov   @R0, 4       ; move i2c FSM to the waiting state
...

В случае с Fat-ревизиями используются чуть другие константы команд, а еще нужно не только замедлять процессор, но и ускорять южный мост, поэтому и байт-код длиннее, и перетаскивать нужно больше, но в целом все очень похоже: .

# i2c re-arrange
CUT_START = 0x2a38
CUT_END   = 0x2a62
CUT_LEN   = CUT_END - CUT_START
smc = patch(smc, rom_end - CUT_LEN, smc[CUT_START:CUT_END])
rom_end -= CUT_LEN
# delay
for off in [0x2681, 0x2687, 0x26C9, 0x26CE, 0x2A67, 0x2A6C]:
    smc = patch(smc, off, lcall(rom_end))
# line status
smc = patch(smc, 0x26BB, lcall(rom_end + 0x10))
smc = patch(smc, 0x2A6F, lcall(rom_end + 0x10))
# hw trigger
smc = patch(smc, 0x26D1, lcall(rom_end + 0x14))

#               IN  W   CE=[x8  E8  40  14] W   D4=[09  90  e0  xx] EX
slow_data = b"\x00\x0B\xCE\x28\xE8\x40\x14\x0B\xD4\x09\x90\xE0\x1e\x03"
fast_data = b"\x00\x0B\xCE\x08\xE8\x40\x14\x0B\xD4\x09\x90\xE0\x0E\x03"
smc  = patch(smc, CUT_START, slow_data + fast_data)

Замеры времени

Глитчинг целиком и полностью завязан на измерении времени, и если высокоточные задержки можно выполнять просто в цикле вида while(cnt -= 1), то ожидание более длительных событий было бы неплохо измерять уже в миллисекундах. Например, близко подобраться к POST-коду, подождать устаканивания замедления и т.д.

И в SMC есть такой механизм! Помните, в main-цикле часть функций выполнялась с периодичностью в 20 мс? Такая точность достигается за счет недо-RTC (почему «недо»? потому что не умеет тикать без розетки) и его прерываний, специальный аппаратный модуль «дергает» SMC раз в миллисекунду, а тот уже ведет отсчет времени.

Увеличение счетчика миллисекунд в прерывании
Увеличение счетчика миллисекунд в прерывании
Отсчет тех самых 20 миллисекунд
Отсчет тех самых 20 миллисекунд

Как раз видно, что SMC отсчитывает до 20 и идет на круг периодических функций. Чтобы не вмешиваться в само прерывание, я написал функцию, которая анализирует переменную-счетчик и выдает результат, а был ли мальчик «тик» миллисекунд (и нужно ли уменьшать какой-нибудь счетчик замера времени):

msec_passed:
    mov R0, #MSEC_REG
    mov A, @R0          ; previous saved ms value
    orl INT_CNTRL, #01h ; disable interrupts
    cjne A, SMC_MSECS, msec_differs
    sjmp msec_same
msec_differs:
    mov @R0, SMC_MSECS
    cjne A, #015h, msec_setc
    cjne @R0, #001h, msec_setc ; do not track the 15h -> 01h
    sjmp msec_same
msec_setc:
    setb C
msec_same:
    anl INT_CNTRL, #0FEh; enable interrupts
    ret

Кажется, вот теперь все готово к основному коду, который и будет заниматься реализацией глитч-атаки.

Собственно, код

Для простоты я разбил код на две части: одна занимается замедлением I2C, другая — слежением и глитчингом процессора. В инициализации все просто: нужно задать начальное состояние будущей FSM + добавить инициализацию UART для отладки. Без ret, поскольку сразу за init будет расположен переход к замененной функции, который и сыграет роль возврата.

org 014C0h
start:
    mov R0,   #RAM_START
    mov @R0,  #000h

    mov 0E9h, #0FFh  ; init UART speed
end

Простейшая заготовка для точки входа:

start:
    lcall i2c_fsm   ; i2c slowdown related stuff
    lcall rgh_fsm   ; glitch & timing related stuff
return:
    ret

Дальше необходимо продумать конечный автомат для I2C. Пусть в регистре состояний первый бит означает, что именно требуется сделать (1 — замедлить, 0 — ускорить), в нулевом отображается текущее состояние (1 — замедлено, 2 — ускорено), а остальные могут быть использованы под служебные нужды (ожидание операции и сохраненное значение запроса). При входе — проверка наличия незавершенной операции и проверка ее завершения. Если операция завершена, сообщить об этом изменением состояния замедления и при необходимости запустить ожидающую команду на выполнение в виртуальной машине:

i2c_fsm:
    mov   R0, #I2C_ST_REG
    mov   A, @R0
    jnb   I2_BIT_WAI, try_send_i2c

; waiting for completion processing
    jb    SMC_I2C_S, return  ; not completed
    mov   C, I2_BIT_SAV      ; move saved request
    mov   I2_BIT_NOW, C      ; to the real state
    clr   I2_BIT_WAI         ; clear waiting for completion
    mov   @R0, A             ; update rgh i2c state
    ret

try_send_i2c:
    mov   C, I2_BIT_NOW      ; compare request bit and real state
    jc    check_if_1
    jnb   I2_BIT_REQ, return ; we are fast already, nothing to do
    sjmp  check_state
check_if_1:
    jb    I2_BIT_REQ, return ; we are slow already, nothing to do

check_state:
    mov   R0, #SMC_I2C_STATE
    cjne  @R0, #0, return      ; check smc i2c FSM, must be idle

    mov   79h, #0E3h  ; VM_POS = fast sequence
    jnb   I2_BIT_REQ, skip_slow
    mov   79h, #0EBh  ; VM_POS = slow sequence
skip_slow:
    lcall  startup_i2c ; initiate the transfer
    jb    0D0h.5, return ; failed to execute the i2c command
i2c_success:
    mov   R0, #SMC_I2C_STATE ; set the i2c FSM into waiting state
    mov   @R0, 4       ; use the state 4 as the least problematic one
    mov   R0, #I2C_ST_REG
    mov   A, @R0
    setb  I2_BIT_WAI    ; set 'waiting for completion'
    mov   C, I2_BIT_REQ 
    mov   I2_BIT_SAV, C ; save the requested speed
    mov   @R0, A
    ret

Теперь самое интересное — конечный автомат для глитчинга. Я разбил алгоритм на девять состояний.

STATE_IDLE

Начальное состояние, переход сюда выполняется, если CPU выключен. Здесь нужно выключить все виды замедления, подготовиться к ожиданию POST-кода 0x1C, от которого будет вестись отсчет (он последний из относительно долгих), а также убедиться, что все действительно выключено, прежде чем продолжать:

rgh_fsm:
    mov    R0, #RGH_ST_REG
    mov    R1, #I2C_ST_REG
    mov    A, @R1
    jb     CPU_RST, check_state_0
    mov    @R0, #STATE_IDLE ; reset the glitch FSM when CPU is off
check_state_0:
    cjne   @R0, #STATE_IDLE, check_state_1
    clr    CPU_PLL          ; disable PLL slowdown
    clr    I2_BIT_REQ       ; disable I2C slowdown
    mov    @R1, A           ; update rgh i2c state
    jb     I2_BIT_NOW, __return  ; wait till I2C done
    jnb    CPU_RST, __return    ; don't setup anything when off
    mov    R1, #DELAY_REG
    mov    @R1, #255   ; 1C waiting delay in ms
    sjmp   go_next_step

STATE_WAIT_1C

Ожидание, пока команда выполнится, а замедление по HANA устаканится. Ничего сложного — отсчитывается заданное ранее число миллисекунд:

check_state_4:
    cjne   @R0, #STATE_WAIT_SLOW, check_state_5
    sjmp   wait_for_smth

STATE_GLITCH

Основная логика атаки сосредоточена именно в этом шаге. Здесь происходит много всего:

  • прекращение замедления процессора по PLL, раз замедление I2C завершено (по-хорошему на это тоже нужна проверка, но ведь можно понадеяться на вторую I2C FSM);

  • отключение прерываний, чтобы выполнению никто не мешал в критический момент;

  • предварительное задание двух значений, которые будут ожидать - задержка перед замедлением PLL и задержка перед глитч-импульсом;

  • отсчет нужных задержек в цикле, замедление через PLL, короткий импульс по линии reset, сброс для псины watchdog;

  • обратное включение прерываний, отключение замедления и ожидание результата.

check_state_5:
    cjne   @R0, #STATE_GLITCH, check_state_6
    clr    CPU_PLL           ; remove PLL slowdown, cuz i2c is done
    orl    INT_CNTRL, #01h   ; disable interrupts
wait_post_d67:
    jb     POSTBIT, wait_post_d67
    lcall   reset_watchdog
    mov     R2, #PLL_DELAY_0
    mov     R3, #PLL_DELAY_1
    mov     R4, #PLL_DELAY_2
    mov     R5, #GLI_PULSE_0
    mov     R6, #GLI_PULSE_1
    mov     R7, #GLI_PULSE_2
wait_for_slowdown:
    djnz    R2, wait_for_slowdown
    djnz    R3, wait_for_slowdown
    djnz    R4, wait_for_slowdown
    setb    CPU_PLL
    lcall   reset_watchdog
wait_post_d89:
    jnb     POSTBIT, wait_post_d89
    ; here is the post DA happened, final route to the glitch!
    lcall   reset_watchdog ; we need really much time here
wait_for_reset: ; wait for 145.41 ms
    djnz    R5, wait_for_reset
    djnz    R6, wait_for_reset
    djnz    R7, wait_for_reset
    clr     CPU_RST   ; reset pulse
    setb    CPU_RST
    lcall   reset_watchdog
    clr     CPU_PLL
    anl     INT_CNTRL, #0FEh ; enable interrupts
    clr    I2_BIT_REQ        ; disable I2C slowdown
    mov    @R1, A            ; update rgh i2c state
    mov    R1, #DELAY_REG
    mov    @R1, #10 ; about 10 ms to check the glitch result
    sjmp   go_next_step

STATE_WAIT_SUCC

Ожидание успеха глитча. Здесь требуется как можно точнее попасть во временной промежуток, когда кастомный загрузчик взводит POST-сигнал, сообщая, что взлом успешно выполнен. Снова простое ожидание заданного ранее числа миллисекунд.

check_state_6:
    cjne   @R0, #STATE_WAIT_SUCC, check_state_7
    sjmp   wait_for_smth

STATE_TEST_SUCC

Здесь проверяется POST-сигнал 0x54 от загрузчика CB_X. Если не вышло — выполняется перезагрузка приставки.

check_state_7:
    cjne   @R0, #STATE_TEST_SUCC, check_state_8
    mov    R1, #DELAY_REG
    mov    @R1, #00 ; 256 ms to check the hardware init result
    setb   SMC_ARG_E        ; enable argon processing
    jnb    POSTBIT, go_next_step
go_reset:
    mov    @R0, #STATE_FINISH  ; set halt step to avoid multiple resets
    ljmp   prepare_reset

STATE_WAIT_HW, STATE_TEST_HW

Последняя проверка, что загрузчик успешно инициировал оборудование (полезно для Fat-приставок). Проверяется по поднятому POST_OUT_1 пину примерно через 200 мс:

check_state_8:
    cjne   @R0, #STATE_WAIT_HW, check_state_9
    sjmp   wait_for_smth ; go wait
check_state_9:
    cjne   @R0, #STATE_TEST_HW, _return
    jb     POSTBIT, go_next_step
    sjmp   go_reset

Все значения задержек были подобраны экспериментальным образом с использованием логического анализатора. Глитч-тайминги были подобраны банальным перебором значений. Я примерно прикинул оценил, где должен располагаться импульс (на основе прошлых исследований), закодил брутфорс, оставил на несколько дней и посмотрел результаты по логам UART.

На закуску

В самом начале я сказал, что к прошивке NAND в приставку мы еще вернемся. Так вот. XDK Sidecar записывает флешку приставки по интерфейсу SPI, манипулируя через него несколькими аппаратными регистрами. Протокол изучили, отреверсили и воспроизвели еще в 2006 году. Например, ниже — реализация стирания блока флешки.

int xbox_nand_erase_block(uint32_t lba)
{
	xbox_nand_clear_status();

	spiex_write_reg(0x00, spiex_read_reg(0x00) | 0x08);

	spiex_write_reg(0x0C, lba << 9);

	spiex_write_reg(0x08, 0xAA);
	spiex_write_reg(0x08, 0x55);
	spiex_write_reg(0x08, 0x05);

	if (xbox_nand_wait_ready(0x1000))
		return 0x8000 | xbox_nand_get_status();

	return 0;
}

Когда дошли до взаимодействия с NAND уже из-под самой системы, быстро выяснилось, что интерфейс — тот же самый. Только обращения выполняются уже не по SPI, а через PCIe на южный мост.

int sfcx_erase_block(int address)
{
	sfcx_writereg(SFCX_CONFIG, sfcx_readreg(SFCX_CONFIG) | CFG_WP_EN);
	sfcx_writereg(SFCX_ADDRESS, address);

	while (sfcx_readreg(SFCX_STATUS) & STATUS_BUSY);

	sfcx_writereg(SFCX_COMMAND, UNLOCK_CMD_1);
	sfcx_writereg(SFCX_COMMAND, UNLOCK_CMD_0);

	while (sfcx_readreg(SFCX_STATUS) & STATUS_BUSY);

	sfcx_writereg(SFCX_COMMAND, BLOCK_ERASE);

	while (sfcx_readreg(SFCX_STATUS) & STATUS_BUSY);

	int status = sfcx_readreg(SFCX_STATUS);
	sfcx_writereg(SFCX_CONFIG,sfcx_readreg(SFCX_CONFIG) & ~CFG_WP_EN);

	return status;
}

Благодаря этому в разрабатываемый сообществом загрузчик XeLL была добавлена возможность восстановить приставку из состояния «брика» (запись образа NAND с USB-носителя):

Процесс записи NAND с USB-флешки
Процесс записи NAND с USB-флешки

Но потом вышли материнки Corona с eMMC вместо NAND.

Не совсем eMMC, а только контроллер, но у следующих ревизий именно полноценный eMMC
Не совсем eMMC, а только контроллер, но у следующих ревизий именно полноценный eMMC

Уже известный протокол для доступа к ним не подходил. Вместо этого чтение выполняли подключившись картридером напрямую к отладочным площадкам самой eMMC.

«Читалка» памяти консоли через картридер
«Читалка» памяти консоли через картридер

Конечно же, отвалилась и возможность «анбрика» таких приставок из XeLL — заниматься реверс-инжинирингом всего этого было банально некому. Да, вы все правильно поняли: сегодня, спустя почти 12 лет, будет решена и эта несправедливость!

«Отладочный стенд» на базе материнки Corona первой версии
«Отладочный стенд» на базе материнки Corona первой версии

Если попытаться прочитать те же регистры по SPI в eMMC-режиме, то можно увидеть, что набор регистров совершенно другой и их содержимое совершенно не бьется с тем, что было при чтении NAND:

0x00: 0xC0462002 <= config?
0x04: 0x00000200 <= nand status?
0x08: 0x40FF8000 <= exec?
0x0C: 0x01020000 <= LBA?
0x10: 0x00FF8080 <= data?
0x14: 0x00000000 <= ???
0x18: 0x00000000
0x1C: 0x00000000
0x20: 0x00001921
0x24: 0x00F20001
0x28: 0x00000000
0x2C: 0x000E4007
0x30: 0x00000000
0x34: 0x1400040A
0x38: 0x00C50000
0x3C: 0x10003FFF

Поэтому придется лезть в реверс ядра системы. Проще всего взять готовое ядро с символьной информацией из слитого XDK, в нашем случае это архив 21256.18_Xenon_Recovery_with_Symbols.

Если быстро пробежаться по функциям, то можно найти самое интересное — конечный метод, посылающий команды на eMMC как раз-таки через регистры южного моста.

Один из вызовов функции выполнения MMC-команд
Один из вызовов функции выполнения MMC-команд

А еще целый конечный автомат для перезагрузки eMMC (MmcxContinueResetStateMachine).

Огромная процедура сброса eMMC
Огромная процедура сброса eMMC

Из анализа кода (и дальнейших исследований) можно узнать назначение некоторых регистров:

  • 0x04 — размер блока данных;

  • 0x08 — параметр, передаваемый в MMC-команде;

  • 0x0C — командный регистр (номер команды и параметры выполнения);

  • 0x10..0x1C — данные ответа по линии CMD;

  • 0x20 — FIFO буфера данных (чтение и отправка по DAT-линиям);

  • 0x24 — вероятно, статус выполнения команд;

  • 0x2C — управление питанием (контроллером) eMMC;

  • 0x30 — статус прерываний;

  • 0x3C — вероятно, статус инициализации.

Кроме того, можно узнать конфигурацию регистров для некоторых команд, которые умеет слать система:

GO_IDLE:

  • [0x08] = 0

  • [0x0C] = 0x1800

SET_BLOCK_COUNT:

  • [0x04] = 0x0

  • [0x08] = count

  • [0x0C] = 0x171A0010

SELECT_CARD:

  • [0x04] = 0x200

  • [0x08] = 0xffff0000

  • [0x0C] = 0x71a0000

DESELECT_CARD:

  • [0x04] = 0x200

  • [0x08] = 0x0

  • [0x0C] = 0x7000000

А также READ_MULTIPLE и WRITE_MULTIPLE, реализованные с использованием DMA режима. Этот режим, увы, не подходит, и не только из-за того, что доступа к оперативной памяти со стороны SPI интерфейса нет и получить считанные данные будет невозможно, но и потому что регистры, отвечающие за конфигурацию DMA (0x58 / 0x5C), недоступны (по SPI наибольший доступный регистр — 0x3C).

Зато из процедуры настройки можно примерно понять, как конфигурируется регистр 0x0C; далее битовой маской указаны конкретные биты и их назначение:

  • 0xFF000000 — номер команды MMC;

  • 0x180000 — биты, выставляемые для выполнения команды;

  • 0x000010 — направление чтения (1 — read, 0 — write);

  • 0x030000 — тип команды;

  • 0x200000 — использовать буфер передачи;

  • 0x000001 — использовать DMA;

  • 0x000004 — ожидать заполнения буфера записи перед подачей команды;

  • 0x000022 — что-то про чтение или запись.

А еще информацию можно почерпнуть у самого южного моста! При запуске он самостоятельно производит инициализацию eMMC.

Инициализация eMMC на логическом анализаторе выглядит красиво
Инициализация eMMC на логическом анализаторе выглядит красиво

И что бы вы думали? Все команды, что он посылает, можно прочитать из SPI. Поэтому я сделал небольшой проект, считывающий в цикле значения регистров, и много раз перезапускал приставку, пытаясь поймать все изменения в этих регистрах. Вот такие значения 0x0C удалось поймать (упорядочены в порядке использования):

  • 0x1 02 0000

  • 0x2 01 0000

  • 0x3 1a 0000

  • 0x7 1a 0000

  • 0x6 1b 0000

  • 0x8 3a 0010

  • 0x6 1b 0000

  • 0x7 00 0000

  • 0x6 1a 0000

  • x10 1a 0000

  • 0x3 01 0000

А вот значения для регистра 0x08 (в порядке использования):

  • 0x40ff8000 <= аргумент cmd1, send_op_cond

  • 0x0

  • 0xffff0000 <= аргумент cmd3, send relative addr

  • 0x3b90100 <= аргумент cmd6, switch func

  • 0x0

  • 0x200 <= аргумент cmd16, set blocklen

  • 0x3bb0000

  • 0x3b70200

  • 0x0

Аналогичным образом были перехвачены и другие регистры, но там не оказалось ничего интересного. На основе результатов анализа удалось реализовать достаточно полный набор MMC команд для чтения и записи eMMC по SPI. Покажу самые важные:

def read_csd():
    spi_reg_write(0x04, 0x00)
    spi_reg_write(0x08, 0xffff0000)
    spi_reg_write(0x0C, 0xA010000) # 0x9010000 for CID
    wait_simple_int()
    data = b""
    for i in range(0x10, 0x20, 4):
        data += struct.pack("
def read_block(block):
    select_card()
    set_blocklen(0x200)
    spi_reg_write(0x04, 0x200 | (1 << 16))
    spi_reg_write(0x08, block << 9)
    spi_reg_write(0x0C, 0x113a0010) #0x83A0010 for EXT_CSD
    wait_int(1)
    wait_int(0x20)
    data = b""
    for i in range(0x80):
        data += struct.pack("
def write_block(block, data):
    select_card()
    set_blocklen()
    spi_reg_write(0x04, 0x200 | (1 << 16))
    spi_reg_write(0x08, block << 9)
    spi_reg_write(0x0C, 0x183a0000)
    wait_int(1)
    for i in range(0, 0x80):
        spi_reg_write(0x20, struct.unpack("

Теперь осталось это закодить на C в проект программатора и можно пользоваться. Больше никаких проблем с подпайкой картридера. А еще можно наконец-то сделать XeLL и утилиту rawflash с поддержкой «анбрика» eMMC. Красота!

Результаты

На Slim (Corona или Trinity) запуск очень стабильный — практически всегда с первого раза. На некоторых приставках Jasper и Falcon запуск может длиться около минуты, но в большинстве случаев работает стабильно. И никаких чипов!

Оно работает!
Оно работает!

В релизе на GitHub можно найти:

  • исходники и собранные файлы RGH3 XeLL (загрузчик Linux / Homebrew);

  • образы glitch2m (полноценная система с подменой фьюзов — залил и работает);

  • проект программатора на базе Raspberry Pi Pico с поддержкой NAND и eMMC;

  • модифицированные libxenon и xell-reloaded с поддержкой записи eMMC.

Для Zephyr и Xenon, к сожалению, метод замедления с помощью PLL часто приводит к зависанию и сильно зависит от процессора и удачи. К тому же, эти ревизии приставок очень ненадежные и в живых их осталось мало, так что в релизе не участвуют

Что до консолей ревизии Winchester — они неуязвимы к RGH, а магазинные версии еще и имеют заблокированные на аппаратном уровне POST_OUT, CPU_EXT_CLK и CPU_PLL_BYPASS. Их исследование — тема для отдельной статьи 🙂

 

Источник

Читайте также