[Перевод] Хакинг прошивок жёстких дисков

Страницы:  1

Ответить
 

Professor Seleznov


pic
В прошлом году я работал над эксплойтом для консоли 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, потому что они имелись у меня в наличии. Вот наши смелые подопытные, которые переживут (надеюсь) все эксперименты, которые я над ними проведу:
pic
Перечислю здесь производителей и модели:
  • 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 дополнительную информацию о формате образа прошивки, а после нескольких минут изучения его в шестнадцатеричном редакторе смог разобраться в следующем:
pic
Формат очень простой: по сути, это всего лишь список статических исполняемых разделов/разделов данных в файле без форматирования, начинающемся с заголовков разделов. Кроме того, у каждого заголовка раздела и блока данных есть собственная контрольная сумма (8-битная) для верификации валидности данных/корректности копирования. Я написал небольшой плагин загрузчика IDA, чтобы можно было загружать образ прошивки и начать его анализировать. Затем я осознал, что все разделы этого образа, за исключением первого, зашифрованы. Один из фрагментов найденной на форумах информации заключался в том, что первый раздел — это фиктивный модуль загрузчика, используемый загрузчиком микроконтроллера для распаковки и загрузки в память остальных разделов. Однако автор поста отказался сообщать алгоритм сжатия.
Сначала я пропустил один из сжатых блоков через несколько инструментов, чтобы попытаться идентифицировать алгоритм, но никаких вменяемых результатов не получил. Поэтому я загрузил первый раздел в IDA как код ARM и приступил к реверс-инжинирингу его работы. Многие микроконтроллеры HDD/SSD основаны на архитектуре ARM, и многие из них имеют несколько ядер, отвечающих за выполнение различных задач. К счастью, у этого HDD WD имелось лишь одно ядро ARM, что сильно упрощало работу с ним. Спустя несколько минут реверс-инжиниринга мне удалось разметить основную часть цикла загрузки раздела и идентифицировать функцию, отвечающую за распаковку данных:
pic
Поработав ещё какое-то время над реверс-инжинирингом подпрограммы распаковки, я в конечном итоге получил её рабочую реализацию. В ней использовался алгоритм LZHUF, но с парой изменений, из-за чего я не смог определить его, пропустив один из сжатых блоков через утилиту идентификации. Значение константы N изменили с 2048 до 4096, а при вычислении длин последовательностей THRESHOLD вычитается, а не прибавляется:
pic
Обновив свой скрипт загрузчика 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 байт в самом начале файла:
pic
Comparing of the two firmware files
Хоть из этого и нельзя сделать окончательные выводы, это хороший показатель того, что файл прошивки, скорее всего, не подписан каким-то сильным криптоалгоритмом с публичным ключом наподобие RSA или ECDSA. Стоит также отметить, что два файла прошивки имеют одну версию, но для разных форм-факторов Samsung PM871a: один для 2,5-дюймовой модели SATA, другой для модели M.2. То есть, несмотря на небольшие отличия в них, всё равно есть вероятность, что они подписаны. 28 байт — странная длина для хэша или подписи, это может быть SHA-224 или обрезанный SHA-256, но с этим мы разберёмся потом.
Следующим этапом будет определение местоположения заголовков разделов (если они вообще есть). Загрузив файл в IDA в виде двоичного, я сразу увидел, что он разбит на разделы с разными базовыми адресами; значит, чтобы всё загрузилось правильно, нам нужны заголовки разделов. Похоже, первые 8 килобайт файла — это какие-то блоки метаданных, после которых мы видим следующее:
pic
Выделенные красным байты — это почти наверняка адреса памяти сегментов кода/данных. Откуда я это знаю? Потому что смотрел в шестнадцатеричные редакторы бездну уже больше двадцати лет, и невероятно хорошо справляюсь с разбором форматов двоичных файлов. Кроме того, эти адреса достаточно точно соответствуют распределению памяти ядер ARM Cortex-M3:
pic
Вглядываясь в бездну ещё несколько минут, я смог разобраться, что смещение и размер каждого раздела располагаются интервалами блоков по 16 КБ, а после написания ещё одного скрипта загрузчика IDA прошивка полностью загрузилась и была готова к анализу.
Samsung HM020GI
Последним остался Samsung HM020GI. Изучая дамп прошивки этого накопителя в шестнадцатеричном редакторе, я чётко видел незашифрованные строки и то, что походило на машинный код. Однако, несмотря на все мои попытки, мне не удалось найти архитектуры, для которой мог бы дизассемблироваться этот код. Выделялось в этом файле то, что во всём нём порядок слов как будто был изменён:
pic
Это начинало походить на крайне эзотерическую архитектуру набора команд или даже специализированный байт-код, который выполняется через встроенную в микроконтроллер виртуальную машину. Я решил пока отложить этот HDD, но мы вернёмся к нему во второй части!
Запись модифицированной прошивки
Ниже я буду рассказывать о своей работе только с жёстким диском Western Digital, потому что на него я потратил больше всего времени. К тому же читателям будет не очень интересно, если я просто буду повторять одни и те же шаги для каждого диска, с которым работал. Другие накопители вернутся во второй части, где я опишу уникальное исследование, которое провёл для одного из них.
Существует три основных способа записи новой прошивки в накопитель:
  • При помощи команды ATA DOWNLOAD MICROCODE. Это самый распространённый способ, поддерживаемый большинством (или даже всеми?) накопителями.
  • Через бэкдор-команды производителя, обычно используемые для ремонта/диагностики или в накопителях, где в качестве средств патчинга/обновления прошивок в основном применяются оверлеи служебной области.
  • Через последовательный интерфейс на печатной плате накопителя, обычно предназначенный для ремонта/диагностики.
Команда DOWNLOAD MICROCODE
Все HDD и SSD общаются с устройством-хостом при помощи той или иной версии спецификации ATA, описывающей все команды и ответы, используемые при обмене информацией. В них входят команды чтения и записи данных, запросов и записи информации диска и так далее. Одна из таких команд — команда скачивания микрокода, применяемая для загрузки в диск нового кода/прошивки. Обычно эта команда отправляется вместе с дополнительными значениями регистров, указывающими размер прошивки, способ передачи новой прошивки в диск (или блоками, или через DMA), после чего накопитель потенциально может валидировать полученную прошивку и записать её в постоянное хранилище. Если обновление прошивки выполнено успешно, то можно выполнить цикл отключения-включения, после чего процесс завершится; в противном случае ваш диск превратится в дорогое пресс-папье. Обычно после этого диск можно восстановить, но это требует хакинга разного уровня, чем обычный пользователь вряд ли станет заниматься.
Большинство накопителей (если не все) должно поддерживать эту команду, и именно с её помощью выполняется обновление прошивок самых новых накопителей. Все утилиты обновления прошивок, которые можно найти на веб-сайтах OEM-поставщиков, просто отправляют новую прошивку в накопитель при помощи этой команды.
Бэкдор-команды производителя
У многих жёстких дисков Western Digital вообще не было полных обновлений прошивок; вместо этого в них применяются модули оверлеев, расположенные в служебной области пластин. Служебная область — это специальный блок на пластине диска, доступ к которому невозможен обычным образом; он содержит такую информацию, как данные о конфигурации диска (информация о модели/серийном, номере данные о геометрии и так далее), данные статуса SMART и обновлённые части кода прошивки. Обычно их называют модулями или оверлеями; в служебной области их бывают десятки. В частности, производитель дисков Western Digital любит использовать некоторые из них под обновлённые части кода, которые загружаются в память при запуске диска.
pic
Модули служебной области HDD WD в программе PC-3000
Для доступа к этим областям применяются передаваемые диску бэкдор-команды, позволяющие считывать, а иногда и записывать эти модули. Western Digital использует команду SMART READ/WRITE LOG, которая обычно применяется для чтения (и изредка для записи) значений статуса SMART. Эти команды получают параметр, называемый log address — это 8-битное значение, указывающее, какую страницу данных SMART нужно считать/записать. У многих из них есть заранее заданное предназначение согласно спецификации ATA, но существует и «задаваемый производителем» диапазон, который, как мы увидим ниже, можно использовать для доступа ко всевозможным командам ремонта и диагностики:
pic
Значения log address в спецификации ATA
Физический последовательный интерфейс
У многих HDD есть физический последовательный интерфейс в виде четырёх контактов рядом с разъёмом SATA, который вы, вероятно, уже видели. Эти контакты предназначены для последовательного порта RS232, к которому можно подключиться и отправлять в диск ремонтные/диагностические команды. Каждый производитель диска/модель поддерживают разные команды; по некоторым из них можно найти онлайн документацию, составленную реверс-разработчиками, в случае других придётся анализировать прошивки. Пока мы не будем заниматься последовательным портом и вернёмся к нему во второй части.
pic
Western Digital SPI Flash
Перепрошивку накопителя WD я планировал выполнить при помощи бэкдор-команд производителя. У меня уже был готов скрипт на Python для распаковки, патчинга и повторной упаковки образа прошивки; оставалось только написать инструмент для записи образа в диск. Однако я опасался, что даже если смогу успешно записать новую прошивку, высока вероятность того, что первые несколько патчей будут неправильными, и это вполне может привести диск в состояние, не позволяющее использовать бэкдор-команды для перепрошивки и восстановления. Мне требовалось надёжный способ восстановления, иначе бы я мог запросто окирпичить с десяток накопителей, прежде чем добьюсь реального прогресса.
К счастью, в накопителях WD основной образ прошивки хранится в двух местах. Первое — это внутренняя флэш-память микроконтроллера, именно её использовал мой диск WD. Второе — это флэш-чип SPI, который есть на некоторых моделях; он замещает внутреннюю флэш-память микроконтроллера. Хоть в моей модели диска и не был установлен флэш-чип SPI, контактные площадки под него всё равно присутствовали, поэтому к ним можно было припаять чип и пару резисторов, чтобы приказать микроконтроллеру загружаться с него, а не с внутренней флэш-памяти.
pic
Я планировал использовать SPI для тестирования модифицированных прошивок, а если я запишу что-то, мешающее накопителю загружаться или перезаписывать новые образы, то при помощи внешнего программатора смогу перезаписать прошивку в цепи. Я заказал пару подходящих флэш-чипов SPI, а затем, пока их не доставили, приступил к анализу прошивки.
Анализируем прошивку
Следующий этап будет самой сложной частью нашего проекта: нужно будет найти код, отвечающий за обработку запросов чтения. Эта прошивка очень низкоуровневая, в ней нет строк или полезных подсказок, из которых можно извлечь информацию; к тому же она разбросана по множеству разных сегментов памяти, часть из которых находится вне образа прошивки. Чтобы найти этот блок кода, придётся быть очень изобретательным.
Вы когда-нибудь уже отлаживали жёсткий диск?
Одна из удобных особенностей накопителей WD заключается в том, что у большинства из них есть соединение JTAG в виде неподключенного 38-контактного разъёма MICTOR на плате. Это значит, что я запросто могу припаять несколько проводов и получить доступ для отладки управляющего жёстким диском микроконтроллера на аппаратном уровне. Да, вы всё правильно поняли: мы будем отлаживать включённый HDD.
pic
Возможность отладки HDD окажется бесценной, потому что я смогу устанавливать в коде контрольные точки, изучать память и состояние регистров, пошагово исполнять код, отправляя команды диска с PC. Однако в процессе этого мне придётся столкнуться с несколькими неприятными моментами:
  • Для отправки команд ATA и наблюдения за срабатыванием контрольных точек HDD должен быть подключён PC. К сожалению, у меня нет USB-адаптера, поддерживающего передачу ATA, поэтому диск придётся подключать к PC напрямую через SATA.
  • Если диск не отвечает в течение определённого интервала таймаута, то Windows подумает, что он умер, и все последующие коммуникации не будут выполняться до перезапуска Windows. В некоторых версиях Windows из-за этого возникать «синий экран смерти», вызванный volmgr.
  • При отладке HDD проявляет свой норов и иногда нужно переводить его в состояние, требующее отключения-включения питания для правильной работы.
В этом проекте мне впервые довелось работать с отладкой через JTAG, поэтому мне пришлось параллельно обучаться. Путём проб и ошибок я настроил OpenOCD с FT232 и смог подключиться:
pic
В своей системе я руководствовался тем, что обнаружил в своих экспериментах 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 и принимаемые командой дополнительные параметры. В случае команды чтения ОЗУ мы также указываем адрес памяти и размер, который хотим считать.
pic
Вот код на 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 чтения ОЗУ. Контрольная точка сработала и за дело принялся отладчик:
pic
Вывод GDB из контрольной точки
Выполнив дампинг регистров, я смог увидеть, что команда, выполняющая чтение из 0x41414141, находится по адресу 0xFFE1D780, то есть внутри кода прошивки.
В чрево зверя
Немного поэкспериментировав с функций, вызвавшей срабатывание контрольной точки, я смог чётко увидеть, как она загружает параметры из буфера VSC и выполняет операцию чтения памяти:
pic
Выполнив трассировку стека, я смог обнаружить таблицу поиска VSC, суммарно содержащую 67 записей; немного больше, чем я ожидал увидеть.
pic
В процессе отладки диска я сдампил первый 1 Кбит данных стека, чтобы можно было использовать их для подъёма по стеку вызовов, потому что в нём присутствовали косвенные вызовы таблиц функций. Для следующих этапом я дополнил тестовое приложение, добавив новую опцию считывания с диска определённого сектора. Затем я поместил пару контрольных точек в функциях, которые нашёл в стеке вызовов функции _vsc_read_write_memory, и запустил тест чтения сектора. Однако ни одна из контрольных точек не сработала. Путём проб и ошибок мне удалось разобраться, что цепочка функций, ведущих к обработчику VSC, приводит к срабатыванию контрольных точек только для команд SMART READ LOG, SMART WRITE LOG и IDENTIFY. Команда DMA READ EXT из теста чтения сектора не активировала контрольные точки, а значит, обрабатывалась где-то ещё.
Поискав функции, ведущие к обработчикам VSC, я смог найти достаточно часто используемые адреса памяти. Это были массивы данных; поискав по образу прошивки, я выяснил, что они используются повсюду. Я решил сдампить эти области памяти, чтобы проверить, что в них находится. У меня была надежда, что удастся найти данные, связанные с обработкой регистров порта/команд ATA, чем получится воспользоваться для нахождения кода обработки запросов чтения. В частности, одна из этих областей представляла собой массив из 40 элементов по 16 байт каждый. Исследовав код и разобравшись в структуре расположения полей, я написал на Python короткий скрипт, выводящий каждый элемент:
pic
Потратив несколько минут на анализ этих данных (то есть на смотрение в бездну) и изучив память в диске, я смог определить, что первый столбец — это указатель на некие дополнительные данные, в которых не мог разобраться, а второй столбец — это указатель функции. Рассмотрев каждый из этих указателей функций в IDA, я понял, что некоторые из них находились в стеке вызовов, ведущих к обработчику VSC. Похоже было, что этот массив представлял собой список запросов для обработки, а элементы с валидным адресом во втором столбце указывали на функцию, которая, скорее всего, обрабатывала запрос.
Следующий этап заключался в проверке того, используются ли поле указателя функции запросы на чтение секторов. Я больше двадцати раз прогнал тест чтения сектора, пытаясь «отравить» эту таблицу запросов в надежде понять, какие элементы относились к запросам чтения; наконец, я нашёл победителя:
pic
Я поместил на эту функцию контрольную точку и заново запустил тест чтения сектора; разумеется, контрольная точка активировалась! Возникла только одна проблема: этой функции не было ни в одном диапазоне адресов образа прошивки, она находилась где-то ещё…
Где же этот код?
Выше я упоминал о том, что на пластинах есть особая служебная область, используемая для хранения дополнительных данных (модулей оверлеев), и что в дисках Western Digital она обычно используется для хранения дополнительного кода. Именно там и находится код, содержащий эту функцию. Когда диск загружается, он начинает работу с загрузчика в микроконтроллере, отвечающего за копирование специальной области инициализации прошивки в ОЗУ и её исполнение. Затем код инициализации прошивки распаковывает/копирует все остальные разделы образа прошивки в ОЗУ по соответствующим базовым адресам. На одном из последующих этапов запуска в память загружаются модули оверлеев, содержащие дополнительный исполняемый код.
pic
Эти модули оверлеев можно сдампить при помощи каких-нибудь VSC, но вместо того, чтобы тратить время на поиск нужной, я решил просто сдампить содержащую код область ОЗУ в файл после полной загрузки диска. Позже я всё-таки выяснил, что модуль оверлея, содержащий код, имеет номер 0x11, но на самом деле это не важно. Теперь остался последний этап: написание патча этого кода, добавляющего небольшую задержку при чтении определённого сектора.
Патчим прошивку
Немного усложняет процесс патчинга то, что код, который мне нужно пропатчить, находится в модуле оверлея; из-за этого будет сложнее восстановить диск после плохого патча, чем если бы код находился во внешнем флэш-чипе SPI. Однако теперь, когда я могу просто изменять содержимое ОЗУ при помощи VSC, мой план заключался в «горячем» патчинге кода в памяти в процессе работы над модификациями; добившись правильной работы, можно будет уже заняться их записью на пластину.
Сдампив код чтения сектора и загрузив его в IDA, я начал анализировать его и планировать, как буду его перехватывать. В конечном итоге, мне нужно было разобраться, какой сектор/сектора пытается считать код, так как задержку нужно добавлять только для одного конкретного сектора, а не для каждой операции чтения. Пока я решил оставить это на потом и просто заняться написанием перехватчика, чтобы убедиться, что proof of concept эксплойта на самом деле работает; в противном случае всё это было бы бессмысленно. Проанализировав код, я нашёл подходящее место для перехвата:
pic
Здесь мы видим, что функция основного обработчика 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 и вычисление нового среднего времени.
После выполнения приложения я получил следующие результаты:
pic
Сразу же видно, что задержка близка не к 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; я обновлю репозиторий после выпуска второй части.-Источник
 
Loading...
Error