
Всем привет из 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:

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

Всю важность этой находки можно понять, погрузившись глубже в историю разработки 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 МГц в первых решениях), и даже это не всегда спасало:

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

В конце концов примерно одновременно произошло два события: у меня появилось свободное время (дипломная пора), а 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. Из-за этого подбор параметров затянулся.
Здесь уже и я подключился к исследованию. Чтобы ускорить подбор параметров, я собрал особый загрузчик, который повторял код в бесконечном цикле, и уже на это непотребство натравил чип. С таким стендом параметры довольно быстро удалось определить, после чего я передал их Джошу для доработки.

И тут у меня возникла безумная идея. Если есть настолько крутое замедление, можно ли обойтись совсем без чипа с его высокой точностью? В приставке есть 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.

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

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

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

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

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

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

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

Одна тайна разгадана, осталось много. Как встроиться в прошивку? Как управлять периферией? Как вообще написать что-то под этот чип?!
Часть информации можно почерпнуть из двух других проектов — 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 соответствует некоторый аппаратный регистр, запись в который приводит к посылке данных. И чтобы найти этот самый нужный регистр, я, конечно же, писал во все регистры подряд и смотрел, что происходит на аппаратной линии (так, конечно, лучше не делать: за время брутфорса я успел повключать регуляторы питания, наткнуться на регистр watchdog и пару других вещей). И таки нашел. Запись данных в регистр E7 приводит к импульсам на линии.

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

Увы, в открытом доступе нет информации, как именно в 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.

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

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

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

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

Несмотря на то что в этом 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;
Ну, почти отлично: при 100 МГц на выходе получается неравномерная колбасня, оцениваемая осциллографом на 9,31 МГц, но на чуть меньших частотах все в порядке.

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

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("<BI", slave.exchange([0xCD], 5))
return result
def write_cd(val):
slave.write_to(0xCD, struct.pack("<BI", 4, val))Тщательно замерив результаты для комбинаций битов, я получил вот такую табличку:
# xxxxxx 010 000110 00000100001001110 - 12.0 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 отсутствует — его функциональность теперь находится в южном мосте. Регистры генератора частот тоже поменяли свое назначение.

С помощью аналогичного перебора и экспериментов с регистром 0xCF была получена следующая табличка:
#0x100 - 132 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, множитель) можете самостоятельно на досуге
