|
Professor Seleznov
|
 В прошлом году я работал над эксплойтом для консоли Xbox 360 (который позже превратился в столь ожидавшийся программный мод), и мне оказалось нужно найти способ модификации прошивки HDD, чтобы обеспечить эксплойт состояние гонки. Для этого я начал пытаться модифицировать прошивки HDD и SSD разных брендов, которые у меня имелись. В этой серии постов я опишу всю проделанную мной работу, в том числе дампинг и анализ прошивки, интерактивную отладку HDD при помощи JTAG, модификацию прошивки накопителя, а также применение ИИ для анализа и идентификации неизвестной архитектуры микроконтроллеров. В этом первом посте я расскажу о дампинге, анализе и модификации прошивок HDD. Вся работа выполнялась без помощи ИИ. В следующем посте я опишу, как использовал ИИ для выполнения похожей работы с другими HDD/SSD, а также для реверс-инжиниринга «чёрного ящика» неизвестной архитектуры набора команд, предоставив Claude доступ для отладки моего жёсткого диска. Предыстория Баг, эксплойт которого я пытался создать — это состояние гонки, возникающее, когда консоль считывает данные с HDD. Для успешного срабатывания моего эксплойта было нужно, чтобы между отправкой запроса чтения и ответом накопителя на него прошло определённое количество времени. На тот момент я ещё не полностью понимал все переменные, которые в этом задействованы, и испытывал трудности с использованием состояния гонки в то время, которое необходимо HDD для ответа. Изначально я думал о том, чтобы изменить прошивку HDD, добавив задержку в несколько сотен миллисекунд при чтении определённого сектора с диска, что дало бы достаточно времени для успешного срабатывания эксплойта. За годы своих упражнений я прочитал несколько постов и статей о модификации прошивок HDD, но не нашёл ничего, с чем бы сразу можно было бы работать. Тем не менее, я знал, что эта концепция не нова и что мне нужно лишь найти накопитель, с которого легко начать освоение. На тот момент мне требовался лишь один HDD, который можно использовать для завершения разработки эксплойта Xbox 360, а затем я бы уже занялся реализацией модификаций прошивок для других моделей накопителей. В дальнейшем я нашёл другие способы реализации моей атаки с состоянием гонки, поэтому мне вообще не понадобилось модифицировать прошивку HDD. Тема модификации прошивки HDD/SSD очень меня интересует, особенно с точки зрения нападающего и пентестера. Однако раньше я никогда не думал погружаться в этот процесс, потому что встраиваемые устройства обычно устроены очень сложно, и для их реверс-инжиниринга требуется куча времени. Вы знаете, как работают жёсткие диски? Принцип работы понятен: диски вращаются на высокой скорости, магниты считывают с них данные; но знаете ли вы по-настоящему, как они работают на уровне микроконтроллера? Я понятия не имел, как устроены жёсткие диски изнутри, но знал, что нашёл ещё один баг, с невозможностью эксплойта которого я мириться не намерен. Если единственное, что стоит на пути к эксплойту этого бага, — это жёсткий диск, то я взломаю жёсткий диск. Подопытные Для этого эксплойта мне просто нужен был любой HDD или SSD, у которого можно легко достать, модифицировать и перезаписать прошивку. Однако в основном меня интересовали те бренды HDD, которые устанавливались в Xbox 360, потому что у пользователей эксплойта, скорее всего, уже будет один из них. Также я раздобыл несколько накопителей Western Digital, потому что из предыдущего опыта знал о бэкдор-командах, которые можно использовать для получения низкоуровневого доступа. И ещё я взял пару SSD Samsung, потому что они имелись у меня в наличии. Вот наши смелые подопытные, которые переживут (надеюсь) все эксперименты, которые я над ними проведу:
 Перечислю здесь производителей и модели:
- Samsung HM020GI
- Hitachi HTS545032B9A300
- Western Digital WD3200BEVT
- Samsung PM871a
Можно заметить, что на одном из накопителей написано оскорбление; однажды он расстроил меня, но потом оказалось, что я подключил его к неисправному USB-адаптеру, а затем к компьютеру со сбойным каналом SATA… На самом деле, диск абсолютно рабочий. Раскручиваемся Первым делом я провёл онлайн-исследование этих моделей, чтобы проверить, найдутся ли дампы их прошивок и любая информация от тех, кто уже проходил этот путь до меня. Довольно много времени я изучал форумы HDD Guru и нашёл много информации о накопителях Western Digital (WD) и накопителе Hitachi. Также я обнаружил серию постов MalwareTech, в которых он пытается модифицировать прошивку HDD; в процессе моих исследований во мне сильнее всего отозвалась эта фраза:
Перед началом хакинга я решил почитать исследования других людей, чтобы знать, с чего начинать. Вроде бы разумно, правда? На самом деле, оказалось, что основная часть исследований, на которые я опирался, была или ошибочной, или просто неприменимой к этому жёсткому диску.
В точности таким же был и мой опыт: основную часть найденной мной информации составляли посты на форумах, написанные пятнадцать с лишним лет назад; они или были ошибочными, или неприменимыми к моей модели HDD. Однако мне удалось собрать множество фрагментов информации, которые в сумме начали образовывать общую картину. Мой план атаки на каждый накопитель был таким:
- Получить дамп прошивки, или найдя образ прошивки онлайн, или сдампив её самостоятельно.
- Загрузить прошивку в IDA для анализа; этот этап также может включать в себя обход сжатия или шифрования. Если я не смогу проанализировать прошивку, то практически никак не смогу и вносить изменения в неё.
- Найти способ записи модифицированной прошивки обратно в диск. Это будет или ручное программирование флэш-чипа на плате логики HDD, или применение стандартных/бэкдор-команд. Невозможность записи модифицированной прошивки мгновенно ставит крест на дальнейшей работе с диском.
- Проанализировать прошивку и попытаться найти код, отвечающий за обработку запросов чтения. Меня интересовала команда DMA READ EXT, которую консоль использует для запросов чтения. Скорее всего, где-то в прошивке есть таблица функций-обработчиков команд, применяемая для обработки различных команд ATA, поддерживаемых диском. Нахождение этой команды или приведёт меня прямиком к обработчику команды DMA READ EXT, или станет отправной точкой для трассирования и обнаружения её. Наверно, это будет самой сложной частью процесса.
- Написать патчи, добавляющие задержку в несколько сотен миллисекунд при чтении с диска конкретного сектора.
- Записать модифицированное ПО обратно на диск.
Получаем прошивку диска Одной из находок на форумах HDD Guru стал раздел, в котором люди делятся дампами прошивок различных HDD, полученных при помощи PC-3000. PC-3000 — это инструмент восстановления данных профессионального уровня, использующий проприетарные команды изготовителей для диагностики и ремонта HDD, а также для дампинга прошивок из них. Мне удалось найти дамп прошивки диска Western Digital (WD), а когда я написал об этом в Twitter, один пользователь сообщил мне, что при помощи имеющегося у него PC-3000 он смог получить дамп прошивки Samsung HM020GI. Прошивку для SSD Samsung PM871a я нашёл на веб-сайте Lenovo в комплекте утилиты обновления прошивок, и это оказалось двойной победой. Я не только получил образ прошивки, но и с помощью реверс-инжиниринга утилиты обновления смог разобраться какие команды нужны для записи в диск новой прошивки. Мне так и не удалось найти прошивку для диска Hitachi, но начало было неплохим. Western Digital Начав с накопителя WD, я обнаружил на форумах HDD Guru дополнительную информацию о формате образа прошивки, а после нескольких минут изучения его в шестнадцатеричном редакторе смог разобраться в следующем:
 Формат очень простой: по сути, это всего лишь список статических исполняемых разделов/разделов данных в файле без форматирования, начинающемся с заголовков разделов. Кроме того, у каждого заголовка раздела и блока данных есть собственная контрольная сумма (8-битная) для верификации валидности данных/корректности копирования. Я написал небольшой плагин загрузчика IDA, чтобы можно было загружать образ прошивки и начать его анализировать. Затем я осознал, что все разделы этого образа, за исключением первого, зашифрованы. Один из фрагментов найденной на форумах информации заключался в том, что первый раздел — это фиктивный модуль загрузчика, используемый загрузчиком микроконтроллера для распаковки и загрузки в память остальных разделов. Однако автор поста отказался сообщать алгоритм сжатия. Сначала я пропустил один из сжатых блоков через несколько инструментов, чтобы попытаться идентифицировать алгоритм, но никаких вменяемых результатов не получил. Поэтому я загрузил первый раздел в IDA как код ARM и приступил к реверс-инжинирингу его работы. Многие микроконтроллеры HDD/SSD основаны на архитектуре ARM, и многие из них имеют несколько ядер, отвечающих за выполнение различных задач. К счастью, у этого HDD WD имелось лишь одно ядро ARM, что сильно упрощало работу с ним. Спустя несколько минут реверс-инжиниринга мне удалось разметить основную часть цикла загрузки раздела и идентифицировать функцию, отвечающую за распаковку данных:
 Поработав ещё какое-то время над реверс-инжинирингом подпрограммы распаковки, я в конечном итоге получил её рабочую реализацию. В ней использовался алгоритм LZHUF, но с парой изменений, из-за чего я не смог определить его, пропустив один из сжатых блоков через утилиту идентификации. Значение константы N изменили с 2048 до 4096, а при вычислении длин последовательностей THRESHOLD вычитается, а не прибавляется:
 Обновив свой скрипт загрузчика IDA, я смог загрузить весь образ прошивки со всеми разделами по корректным базовым адресам. Прошивка WD была готова для анализа. Samsung PM871a Дальше я взялся за Samsung PM871a, для которого нашёл прошивку и утилиту обновления прошивок на веб-сайте Lenovo. Оказалось, что это отличная стратегия: искать утилиты обновления прошивок на веб-сайтах OEM; таким образом мы получаем и прошивку, и утилиту, которая:
- Расшифровывает/деобфусцирует прошивку, если она защищена.
- Записывает её на HDD.
Прошивки SSD Samsung обычно каким-то образом зашифрованы/обфусцированы, а утилиты обновления прошивок обычно расшифровывают/деобфусцируют прошивку перед отправкой её в диск для записи. Прошивка для SSD, с которым я работал, была обфусцирована каким-то алгоритмом модификации бит; мне удалось выполнить его реверс-инжиниринг из утилиты обновления:
void DecodeFirmware(unsigned char* pBuffer, unsigned int Length) { // Циклический обход всего буфера прошивки. for (unsigned int i = 0; i < Length; i++) { // Получение старшего ниббла текущего байта. unsigned char nibbleHi = (pBuffer >> 4) & 0xF; // Выполнять модификацию бит? if ((nibbleHi & 1) != 0) nibbleHi >>= 1; else nibbleHi = 0xF - (nibbleHi >> 1); // Маска в новом значение старшего ниббла. pBuffer = (pBuffer & 0xF) | (nibbleHi << 4); } }
Конкретно эту утилиту обновления прошивок можно использовать в более чем двух десятках разных моделей SSD Samsung (а также в нескольких десятках различных моделей DVD), поэтому извлечение информации из этих утилит крайне полезно для дальнейшего масштабирования исследований. Затем я открыл деобфусцированный образ прошивки в шестнадцатеричном редакторе, чтобы разобраться, с чем я имею дело. Первым делом я заметил какие-то подозрительные данные в первых нескольких килобайтах файла, которые походили на криптографическую подпись (а это означало, что у меня, возможно, не получится изменять прошивку). Однако, сравнив два файла прошивок, я понял, что за исключением небольших изменений в данных/коде, единственной заметной разницей были какие-то 28 байт в самом начале файла:

Comparing of the two firmware files Хоть из этого и нельзя сделать окончательные выводы, это хороший показатель того, что файл прошивки, скорее всего, не подписан каким-то сильным криптоалгоритмом с публичным ключом наподобие RSA или ECDSA. Стоит также отметить, что два файла прошивки имеют одну версию, но для разных форм-факторов Samsung PM871a: один для 2,5-дюймовой модели SATA, другой для модели M.2. То есть, несмотря на небольшие отличия в них, всё равно есть вероятность, что они подписаны. 28 байт — странная длина для хэша или подписи, это может быть SHA-224 или обрезанный SHA-256, но с этим мы разберёмся потом. Следующим этапом будет определение местоположения заголовков разделов (если они вообще есть). Загрузив файл в IDA в виде двоичного, я сразу увидел, что он разбит на разделы с разными базовыми адресами; значит, чтобы всё загрузилось правильно, нам нужны заголовки разделов. Похоже, первые 8 килобайт файла — это какие-то блоки метаданных, после которых мы видим следующее:
 Выделенные красным байты — это почти наверняка адреса памяти сегментов кода/данных. Откуда я это знаю? Потому что смотрел в шестнадцатеричные редакторы бездну уже больше двадцати лет, и невероятно хорошо справляюсь с разбором форматов двоичных файлов. Кроме того, эти адреса достаточно точно соответствуют распределению памяти ядер ARM Cortex-M3:
 Вглядываясь в бездну ещё несколько минут, я смог разобраться, что смещение и размер каждого раздела располагаются интервалами блоков по 16 КБ, а после написания ещё одного скрипта загрузчика IDA прошивка полностью загрузилась и была готова к анализу. Samsung HM020GI Последним остался Samsung HM020GI. Изучая дамп прошивки этого накопителя в шестнадцатеричном редакторе, я чётко видел незашифрованные строки и то, что походило на машинный код. Однако, несмотря на все мои попытки, мне не удалось найти архитектуры, для которой мог бы дизассемблироваться этот код. Выделялось в этом файле то, что во всём нём порядок слов как будто был изменён:
 Это начинало походить на крайне эзотерическую архитектуру набора команд или даже специализированный байт-код, который выполняется через встроенную в микроконтроллер виртуальную машину. Я решил пока отложить этот HDD, но мы вернёмся к нему во второй части! Запись модифицированной прошивки Ниже я буду рассказывать о своей работе только с жёстким диском Western Digital, потому что на него я потратил больше всего времени. К тому же читателям будет не очень интересно, если я просто буду повторять одни и те же шаги для каждого диска, с которым работал. Другие накопители вернутся во второй части, где я опишу уникальное исследование, которое провёл для одного из них. Существует три основных способа записи новой прошивки в накопитель:
- При помощи команды ATA DOWNLOAD MICROCODE. Это самый распространённый способ, поддерживаемый большинством (или даже всеми?) накопителями.
- Через бэкдор-команды производителя, обычно используемые для ремонта/диагностики или в накопителях, где в качестве средств патчинга/обновления прошивок в основном применяются оверлеи служебной области.
- Через последовательный интерфейс на печатной плате накопителя, обычно предназначенный для ремонта/диагностики.
Команда DOWNLOAD MICROCODE Все HDD и SSD общаются с устройством-хостом при помощи той или иной версии спецификации ATA, описывающей все команды и ответы, используемые при обмене информацией. В них входят команды чтения и записи данных, запросов и записи информации диска и так далее. Одна из таких команд — команда скачивания микрокода, применяемая для загрузки в диск нового кода/прошивки. Обычно эта команда отправляется вместе с дополнительными значениями регистров, указывающими размер прошивки, способ передачи новой прошивки в диск (или блоками, или через DMA), после чего накопитель потенциально может валидировать полученную прошивку и записать её в постоянное хранилище. Если обновление прошивки выполнено успешно, то можно выполнить цикл отключения-включения, после чего процесс завершится; в противном случае ваш диск превратится в дорогое пресс-папье. Обычно после этого диск можно восстановить, но это требует хакинга разного уровня, чем обычный пользователь вряд ли станет заниматься. Большинство накопителей (если не все) должно поддерживать эту команду, и именно с её помощью выполняется обновление прошивок самых новых накопителей. Все утилиты обновления прошивок, которые можно найти на веб-сайтах OEM-поставщиков, просто отправляют новую прошивку в накопитель при помощи этой команды. Бэкдор-команды производителя У многих жёстких дисков Western Digital вообще не было полных обновлений прошивок; вместо этого в них применяются модули оверлеев, расположенные в служебной области пластин. Служебная область — это специальный блок на пластине диска, доступ к которому невозможен обычным образом; он содержит такую информацию, как данные о конфигурации диска (информация о модели/серийном, номере данные о геометрии и так далее), данные статуса SMART и обновлённые части кода прошивки. Обычно их называют модулями или оверлеями; в служебной области их бывают десятки. В частности, производитель дисков Western Digital любит использовать некоторые из них под обновлённые части кода, которые загружаются в память при запуске диска.

Модули служебной области HDD WD в программе PC-3000 Для доступа к этим областям применяются передаваемые диску бэкдор-команды, позволяющие считывать, а иногда и записывать эти модули. Western Digital использует команду SMART READ/WRITE LOG, которая обычно применяется для чтения (и изредка для записи) значений статуса SMART. Эти команды получают параметр, называемый log address — это 8-битное значение, указывающее, какую страницу данных SMART нужно считать/записать. У многих из них есть заранее заданное предназначение согласно спецификации ATA, но существует и «задаваемый производителем» диапазон, который, как мы увидим ниже, можно использовать для доступа ко всевозможным командам ремонта и диагностики:

Значения log address в спецификации ATA Физический последовательный интерфейс У многих HDD есть физический последовательный интерфейс в виде четырёх контактов рядом с разъёмом SATA, который вы, вероятно, уже видели. Эти контакты предназначены для последовательного порта RS232, к которому можно подключиться и отправлять в диск ремонтные/диагностические команды. Каждый производитель диска/модель поддерживают разные команды; по некоторым из них можно найти онлайн документацию, составленную реверс-разработчиками, в случае других придётся анализировать прошивки. Пока мы не будем заниматься последовательным портом и вернёмся к нему во второй части.
 Western Digital SPI Flash Перепрошивку накопителя WD я планировал выполнить при помощи бэкдор-команд производителя. У меня уже был готов скрипт на Python для распаковки, патчинга и повторной упаковки образа прошивки; оставалось только написать инструмент для записи образа в диск. Однако я опасался, что даже если смогу успешно записать новую прошивку, высока вероятность того, что первые несколько патчей будут неправильными, и это вполне может привести диск в состояние, не позволяющее использовать бэкдор-команды для перепрошивки и восстановления. Мне требовалось надёжный способ восстановления, иначе бы я мог запросто окирпичить с десяток накопителей, прежде чем добьюсь реального прогресса. К счастью, в накопителях WD основной образ прошивки хранится в двух местах. Первое — это внутренняя флэш-память микроконтроллера, именно её использовал мой диск WD. Второе — это флэш-чип SPI, который есть на некоторых моделях; он замещает внутреннюю флэш-память микроконтроллера. Хоть в моей модели диска и не был установлен флэш-чип SPI, контактные площадки под него всё равно присутствовали, поэтому к ним можно было припаять чип и пару резисторов, чтобы приказать микроконтроллеру загружаться с него, а не с внутренней флэш-памяти.
 Я планировал использовать SPI для тестирования модифицированных прошивок, а если я запишу что-то, мешающее накопителю загружаться или перезаписывать новые образы, то при помощи внешнего программатора смогу перезаписать прошивку в цепи. Я заказал пару подходящих флэш-чипов SPI, а затем, пока их не доставили, приступил к анализу прошивки. Анализируем прошивку Следующий этап будет самой сложной частью нашего проекта: нужно будет найти код, отвечающий за обработку запросов чтения. Эта прошивка очень низкоуровневая, в ней нет строк или полезных подсказок, из которых можно извлечь информацию; к тому же она разбросана по множеству разных сегментов памяти, часть из которых находится вне образа прошивки. Чтобы найти этот блок кода, придётся быть очень изобретательным. Вы когда-нибудь уже отлаживали жёсткий диск? Одна из удобных особенностей накопителей WD заключается в том, что у большинства из них есть соединение JTAG в виде неподключенного 38-контактного разъёма MICTOR на плате. Это значит, что я запросто могу припаять несколько проводов и получить доступ для отладки управляющего жёстким диском микроконтроллера на аппаратном уровне. Да, вы всё правильно поняли: мы будем отлаживать включённый HDD.
 Возможность отладки HDD окажется бесценной, потому что я смогу устанавливать в коде контрольные точки, изучать память и состояние регистров, пошагово исполнять код, отправляя команды диска с PC. Однако в процессе этого мне придётся столкнуться с несколькими неприятными моментами:
- Для отправки команд ATA и наблюдения за срабатыванием контрольных точек HDD должен быть подключён PC. К сожалению, у меня нет USB-адаптера, поддерживающего передачу ATA, поэтому диск придётся подключать к PC напрямую через SATA.
- Если диск не отвечает в течение определённого интервала таймаута, то Windows подумает, что он умер, и все последующие коммуникации не будут выполняться до перезапуска Windows. В некоторых версиях Windows из-за этого возникать «синий экран смерти», вызванный volmgr.
- При отладке HDD проявляет свой норов и иногда нужно переводить его в состояние, требующее отключения-включения питания для правильной работы.
В этом проекте мне впервые довелось работать с отладкой через JTAG, поэтому мне пришлось параллельно обучаться. Путём проб и ошибок я настроил OpenOCD с FT232 и смог подключиться:
 В своей системе я руководствовался тем, что обнаружил в своих экспериментах MalwareTech, но микроконтроллер и HDD, с которыми я работал, слегка отличались. Не знаю, есть ли у него несколько ядер ARM, потому что мне удалось идентифицировать только первое. Как бы то ни было, следующим этапом было написание небольшого инструмента для отправки команд диску. Команды производителя Выше я вкратце говорил о том, что у накопителей Western Digital есть бэкдор-команды, доступ к которым можно получить через команду ATA SMART READ/WRITE LOG. Это так называемые команды производителя (vendor specific command, VSC), способные читать/записывать прошивку, ОЗУ, модули оверлеев и другие элементы, связанные с ремонтом/диагностикой. Пока больше всех остальных нас интересует команда чтения ОЗУ. Мой план атаки заключался в установке контрольной точки памяти на каком-нибудь адресе, допустим, 0x41414141, с последующей отправкой команды чтения ОЗУ по адресу 0x41414141, что должно было вызвать срабатывание контрольной точки. Благодаря этому я смогу увидеть, какая функция обрабатывает эту команду VSC (а значит, команду ATA SMART READ LOG), и вернуться назад по стеку в надежде найти общий диспетчер, который приведёт меня к функции, обрабатывающей запросы чтения. Для отправки VSC нужно просто подготовить запрос передачи ATA, то есть способ отправки низкоуровневой команды непосредственно в жёсткий диск. Для этого мы заполняем структуру значениями, которые хотим запрограммировать в регистры порта ATA, после чего HDD/SSD пытается выполнить эту команду. Также можно отправить дополнительные данные размером в N секторов. Для отправки VSC мы подготавливаем регистры к выполнению команды ATA SMART WRITE LOG, задав значение log page, равное 0xBE (это определённый производителем номер log page, который накопители WD используют для «бэкдора»). Далее мы указываем дополнительные данные размером в один сектор, содержащие ID VSC и принимаемые командой дополнительные параметры. В случае команды чтения ОЗУ мы также указываем адрес памяти и размер, который хотим считать.
 Вот код на C, который я написал для отправки этой VSC:
bool SendVSCAccessKey(HANDLE hDrive, BYTE bVscCmd, bool bWriteAccess, DWORD dwAddress = 0, DWORD dwSize = 0) { DWORD BytesRead = 0; DWORD BufferSize = sizeof(ATA_PASS_THROUGH_EX) + 512; BYTE abPassthroughData[sizeof(ATA_PASS_THROUGH_EX) + 512] = { 0 }; ATA_PASS_THROUGH_EX* pAtaPassthrough = (ATA_PASS_THROUGH_EX*)abPassthroughData; VSC_COMMAND_DATA* pVscCommand = (VSC_COMMAND_DATA*)(pAtaPassthrough + 1); // Подготовка данных передачи: pAtaPassthrough->Length = sizeof(ATA_PASS_THROUGH_EX); pAtaPassthrough->AtaFlags = ATA_FLAGS_DATA_OUT; pAtaPassthrough->TimeOutValue = 5; pAtaPassthrough->DataTransferLength = 512; pAtaPassthrough->DataBufferOffset = sizeof(ATA_PASS_THROUGH_EX); // Подготовка регистров порта для команды SMART READ LOG: IDEREGS* pRegs = (IDEREGS*)pAtaPassthrough->CurrentTaskFile; pRegs->bCommandReg = ATA_OP_SMART; pRegs->bFeaturesReg = SMART_WRITE_LOG; pRegs->bSectorCountReg = 1; pRegs->bSectorNumberReg = 0xBE; // Специальный log address WD pRegs->bCylLowReg = 0x4F; pRegs->bCylHighReg = 0xC2; pRegs->bDriveHeadReg = 0xA0; // Подготовка данных команды VSC: pVscCommand->CommandId = bVscCmd; pVscCommand->Mode = bWriteAccess == true ? VSC_MODE_WRITE : VSC_MODE_READ; pVscCommand->ReadWriteRam.Address = dwAddress; pVscCommand->ReadWriteRam.Length = dwSize; // Отправка команды в накопитель. if (DeviceIoControl(hDrive, IOCTL_ATA_PASS_THROUGH, pAtaPassthrough, BufferSize, pAtaPassthrough, BufferSize, &BytesRead, nullptr) == FALSE) { wprintf(L"SendVSCAccessKey failed 0x%08x\n", GetLastError()); return false; } // Проверка на ошибки. OUT_REGS* pOutRegs = (OUT_REGS*)pAtaPassthrough->CurrentTaskFile; if ((pOutRegs->bStatusReg & 1) != 0) { wprintf(L"SendVSCAccessKey failed drive returned 0x%04x\n", WD_ERROR_CODE(pOutRegs)); return false; } return true; }
Подготовив всё необходимое, я установил контрольную точку памяти для адреса 0x41414141, а затем запустил своё тестовое приложение, отправляющее в диск VSC чтения ОЗУ. Контрольная точка сработала и за дело принялся отладчик:

Вывод GDB из контрольной точки Выполнив дампинг регистров, я смог увидеть, что команда, выполняющая чтение из 0x41414141, находится по адресу 0xFFE1D780, то есть внутри кода прошивки. В чрево зверя Немного поэкспериментировав с функций, вызвавшей срабатывание контрольной точки, я смог чётко увидеть, как она загружает параметры из буфера VSC и выполняет операцию чтения памяти:
 Выполнив трассировку стека, я смог обнаружить таблицу поиска VSC, суммарно содержащую 67 записей; немного больше, чем я ожидал увидеть.
 В процессе отладки диска я сдампил первый 1 Кбит данных стека, чтобы можно было использовать их для подъёма по стеку вызовов, потому что в нём присутствовали косвенные вызовы таблиц функций. Для следующих этапом я дополнил тестовое приложение, добавив новую опцию считывания с диска определённого сектора. Затем я поместил пару контрольных точек в функциях, которые нашёл в стеке вызовов функции _vsc_read_write_memory, и запустил тест чтения сектора. Однако ни одна из контрольных точек не сработала. Путём проб и ошибок мне удалось разобраться, что цепочка функций, ведущих к обработчику VSC, приводит к срабатыванию контрольных точек только для команд SMART READ LOG, SMART WRITE LOG и IDENTIFY. Команда DMA READ EXT из теста чтения сектора не активировала контрольные точки, а значит, обрабатывалась где-то ещё. Поискав функции, ведущие к обработчикам VSC, я смог найти достаточно часто используемые адреса памяти. Это были массивы данных; поискав по образу прошивки, я выяснил, что они используются повсюду. Я решил сдампить эти области памяти, чтобы проверить, что в них находится. У меня была надежда, что удастся найти данные, связанные с обработкой регистров порта/команд ATA, чем получится воспользоваться для нахождения кода обработки запросов чтения. В частности, одна из этих областей представляла собой массив из 40 элементов по 16 байт каждый. Исследовав код и разобравшись в структуре расположения полей, я написал на Python короткий скрипт, выводящий каждый элемент:
 Потратив несколько минут на анализ этих данных (то есть на смотрение в бездну) и изучив память в диске, я смог определить, что первый столбец — это указатель на некие дополнительные данные, в которых не мог разобраться, а второй столбец — это указатель функции. Рассмотрев каждый из этих указателей функций в IDA, я понял, что некоторые из них находились в стеке вызовов, ведущих к обработчику VSC. Похоже было, что этот массив представлял собой список запросов для обработки, а элементы с валидным адресом во втором столбце указывали на функцию, которая, скорее всего, обрабатывала запрос. Следующий этап заключался в проверке того, используются ли поле указателя функции запросы на чтение секторов. Я больше двадцати раз прогнал тест чтения сектора, пытаясь «отравить» эту таблицу запросов в надежде понять, какие элементы относились к запросам чтения; наконец, я нашёл победителя:
 Я поместил на эту функцию контрольную точку и заново запустил тест чтения сектора; разумеется, контрольная точка активировалась! Возникла только одна проблема: этой функции не было ни в одном диапазоне адресов образа прошивки, она находилась где-то ещё… Где же этот код? Выше я упоминал о том, что на пластинах есть особая служебная область, используемая для хранения дополнительных данных (модулей оверлеев), и что в дисках Western Digital она обычно используется для хранения дополнительного кода. Именно там и находится код, содержащий эту функцию. Когда диск загружается, он начинает работу с загрузчика в микроконтроллере, отвечающего за копирование специальной области инициализации прошивки в ОЗУ и её исполнение. Затем код инициализации прошивки распаковывает/копирует все остальные разделы образа прошивки в ОЗУ по соответствующим базовым адресам. На одном из последующих этапов запуска в память загружаются модули оверлеев, содержащие дополнительный исполняемый код.
 Эти модули оверлеев можно сдампить при помощи каких-нибудь VSC, но вместо того, чтобы тратить время на поиск нужной, я решил просто сдампить содержащую код область ОЗУ в файл после полной загрузки диска. Позже я всё-таки выяснил, что модуль оверлея, содержащий код, имеет номер 0x11, но на самом деле это не важно. Теперь остался последний этап: написание патча этого кода, добавляющего небольшую задержку при чтении определённого сектора. Патчим прошивку Немного усложняет процесс патчинга то, что код, который мне нужно пропатчить, находится в модуле оверлея; из-за этого будет сложнее восстановить диск после плохого патча, чем если бы код находился во внешнем флэш-чипе SPI. Однако теперь, когда я могу просто изменять содержимое ОЗУ при помощи VSC, мой план заключался в «горячем» патчинге кода в памяти в процессе работы над модификациями; добившись правильной работы, можно будет уже заняться их записью на пластину. Сдампив код чтения сектора и загрузив его в IDA, я начал анализировать его и планировать, как буду его перехватывать. В конечном итоге, мне нужно было разобраться, какой сектор/сектора пытается считать код, так как задержку нужно добавлять только для одного конкретного сектора, а не для каждой операции чтения. Пока я решил оставить это на потом и просто заняться написанием перехватчика, чтобы убедиться, что proof of concept эксплойта на самом деле работает; в противном случае всё это было бы бессмысленно. Проанализировав код, я нашёл подходящее место для перехвата:
 Здесь мы видим, что функция основного обработчика sub_16A5E выполняет цикл, и каждая итерация вызывает sub_1671C, которая и обрабатывает запрос чтения. SataRequestArray — это уже рассмотренный нами массив из 40 элементов, содержащий указатели функций для каждого запроса. Этот цикл начинается с элемента исходного запроса чтения, а затем продолжает обрабатывать запросы в массиве, пока поле Unk4 не будет равно 0xFF. Думаю, крупные запросы можно разбивать на множество мелких, или что-то подобное, но точно я не уверен… Перехватчик я решил разместить спустя несколько команд sub_1671C, так как это было немного проще, чем вставлять его в тело цикла. Немного помучавшись, я пришёл к такому ассемблерному коду:
.syntax unified # Связанные с таймингами переменные для управления задержкой: .set F_CPU, 10000000 # частота CPU .set MS_DELAY, 200 # задержка в 200 мс # ============================================================================ # Наш код # ============================================================================ .long 0xFFEAB600 .long (9f - 0f) 0: # Вызов нашего перехватчика. push {r0-r7, lr} blx Hook_SataDmaRead pop {r0-r7, lr} # Замена команд, которые мы переписали. movs r7, r0 lsls r0, r0, #4 adds r5, r0, r1 ldrb r1, [r5, #0xD] sub sp, sp, #0x1C str r1, [sp, #0xC] ldrb r0, [r5, #0xE] # Перескакиваем назад. ldr lr, =0x0001672E+1 bx lr Hook_SataDmaRead: # Подготовка счётчика задержки. ldr r3, =(MS_DELAY * F_CPU / 1000) Hook_SataDmaRead_loop: # Продложаем, пока счётчик не будет равен 0. sub r3, r3, #1 bne Hook_SataDmaRead_loop bx lr .pool 9: # ============================================================================ # Перехват обработчика запросов DMA SATA # ============================================================================ .long 0x00016720 .long (9f - 0f) 0: .thumb_func ldr r7, =0xFFEAB600 bx r7 .pool 9: .long 0xFFFFFFFF
Этот ассемблерный код вставляет в sub_1671C небольшой перехватчик, выполняющий переход к моему блоку кода, который я разместил в неиспользуемом разделе ОЗУ, после чего он пытается выдержать паузу примерно в 200 мс. Вычисление задержки основывается на приблизительных подсчётах и не имеет научной точности, но этого вполне достаточно. Скомпилировав этот код, я модифицировал своё тестовое приложение, чтобы оно выполняло следующий тест:
- Десятикратное выполнение цикла и чтение определённого сектора, вычисление среднего времени чтения на основании времени каждой итерации.
- Запись моих патчей в ОЗУ, добавив таким образом задержку примерно в 200 мс для каждой операции чтения.
- Повторение шага 1 и вычисление нового среднего времени.
После выполнения приложения я получил следующие результаты:
 Сразу же видно, что задержка близка не к 200 мс, а, скорее, к 450 мс; как я и сказал, расчёты приблизительные и неточные… Также можно заметить, что во всех случаях, кроме первого, время чтения в чистом тесте равно 0 мс. Вероятно, это связано с тем, что мы попадаем в кэш, а потому накопителю не нужно искать данные с пластины и считывать их. В тесте задержки мы видим, что время первой итерации тоже равно 0 мс; я не совсем понимаю, почему, но, вероятно, это тоже связано с кэшированием. Остальное время чтения имеет задержки, именно это мне и нужно. Настало время протестировать всё это сверху вниз при помощи разрабатываемого мной эксплойта, и посмотреть, позволяют ли задержки времени чтения успешно его активировать. Задача успешно провалена Немного повспоминав, как работает этот эксплойт, и подготовив новые файлы, я мог приступить к тестированию. Структура этого теста состоит из нескольких элементов, но в с целом он достаточно прост:
- У меня есть полностью немодифицированный HDD, на который я подам внешнее питание и выполню предварительный патчинг кода в ОЗУ своим патчем чтения с задержкой. Это упростит тестирование: мне пока не придётся беспокоиться о записи моих патчей в прошивку диска/модули оверлеев.
- HDD будет подключен к Xbox 360 через SATA (только кабелем для передачи данных). При запуске консоли я попробую считать с HDD определённый сектор. Пока это происходит, на фоне будет выполняться «хакинг» (о нём я расскажу в следующем посте).
- Если операция чтения будет выполняться достаточно долго, то должен сработать мой эксплойт, а код оболочки должен зажечь всё кольцо индикаторов консоли оранжевым цветом, чтобы показать, что всё сработало.
Однако прежде чем я сделаю это, мне хотелось сделать то, что сделал бы хороший ненастоящий учёный: провести контрольный тест, то есть запустить консоль без внесения модификаций в HDD, чтобы убедиться, что эксплойт не сработает. Вероятно, придётся выполнить множество итераций запуска без модификаций HDD, убедиться, что эксплойт не работает, затем запуститься с пропатченным кодом HDD и проверить работоспособность эксплойта. Это позволило бы удостовериться в надёжности патчей чтения с задержкой. А теперь представьте моё удивление после работы по двадцать часов в день целую неделю и почти тридцатичасового отсутствия сна на момент тестирования, когда я запустил консоль без модификаций HDD, и эксплойт успешно сработал. Я подумал, что это может быть случайностью, влиянием ретроградного Меркурия или чего-то подобного. Я много раз выключал и включал консоль, но эксплойт срабатывал почти каждый раз, несмотря на отсутствие модификаций HDD, даже несколько раз отключал-включал HDD, чтобы точно всё проверить. Наверно, мой побочный квест завершён, настало время выспаться, а после пробуждения начать разбираться, почему же эксплойт внезапно решил заработать. Заключение В последующие дни я лучше разобрался во всех переменных, связанных с эксплойтом Xbox 360, и в результате мне вообще не понадобилось модифицировать прошивку HDD. Получилось сделать так, чтобы он прекрасно работал со всеми имеющимися у меня HDD, однако SSD-накопители отвечали слишком быстро для стабильного срабатывания эксплойта. Это было забавное путешествие в мир встраиваемых устройств, я многому научился и это помогло мне набраться уверенности в реверс-инжиниринге низкоуровневых встраиваемых устройств. К сожалению, я по-прежнему понятия не имею, как работает жёсткий диск. Мне бы определённо хотелось из чистого любопытства изучить эту тему глубже, но, не имея реальной мотивации, я положил всю эту работу на полку. Однако, поэкспериментировав с ИИ, я решил вернуться к ней и проверить, насколько хорошо ИИ справится с анализом «чёрного ящика» встраиваемых систем, так что ждите вторую часть статьи! Анализ прошивок жёстких дисков — это интересная тема, обсуждения которой я встречал всего один-два раза, несмотря на то, что многие люди уже делали раньше ровно то, чем занимался я. По старым HDD есть довольно много информации, придётся лишь просеивать нечёткие и ошибочные посты на форумах за десятки лет, однако целой картины никто не даёт. В частности, интересные публикации по этой теме выпустили Трэвис Гудспид и Sprite (Йерун Домбург); особенно мне нравится контр-криминалистика Трэвиса. Думаю, мы не видим особо много информации по этому предмету, потому что люди боятся, что помогут злоумышленникам создавать зловредное ПО. В этом есть некоторый смысл, но, как мы увидим из второй части статьи, при использовании ИИ для анализа такая точка зрения становится спорной. Не говоря уже о том, что зловредное ПО для HDD уже существует (спасибо, АНБ!). Чтобы эта тема снова не ушла в забвенье, я решил выложить в опенсорс написанные мной скрипты для IDA и для работы с прошивками; надеюсь, это поможет кому-то начать освоение прошивок HDD. Мне было бы любопытно увидеть, что в этих устройствах смогут найти интересного другие исследователи; также хотелось бы, чтобы кто-нибудь задокументировал бэкдор-команды, разработал инструменты для фингерпринтинга прошивок, а возможно, даже и для декомпиляции прошивок (хоть это и совершенно бессмысленно, и я всё равно бы не стал доверять им). Всю мою работу можно найти на GitHub; я обновлю репозиторий после выпуска второй части.-Источник
|