The YapLink Project The YapLink Project The YapLink Project The YapLink Project The YapLink Project The YapLink Project The YapLink Project The YapLink Project The YapLink Project The YapLink Project The YapLink Project The YapLink Project The YapLink Project The YapLink Project The YapLink Project The YapLink Project The YapLink Project The YapLink Project

Аппаратный взлом жёсткого диска. Часть 1

BlackPope

Местный
Регистрация
27.04.2020
Сообщения
242
Реакции
35
Жёсткие диски: если вы читаете эту статью, то с большой вероятностью у вас есть одно или несколько таких устройств. Они довольно просты и, по сути, представляют собой набор 512-байтных секторов, пронумерованных возрастающими адресами, также называемыми LBA (Logical Block Address). Компьютер, к которому подключен жёсткий диск (hard drive, HD), может считывать и записывать данные в эти сектора. Обычно используется файловая система, абстрагирующая все эти сектора до файлов и папок.

Неспециалисту может показаться, что оборудование HD должно быть довольно простым: достаточно всего лишь устройства, подключаемого к порту SATA, которое может позиционировать свои головки чтения/записи и считывать или записывать данные на пластины. Однако их работа намного сложнее: разве жёсткие диски не занимаются обработкой сбойных блоки и атрибутов SMART, и не имеют кэша, с которым тоже каким-то образом нужно работать?

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

Элементы печатной платы

Чтобы разобраться, возможно ли взломать жёсткие диски, мне сначала нужно было лучше их узнать. К счастью, как и у большинства из вас, у меня накопилась целая куча старых и/или сломанных жёстких дисков для изучения:



Разумеется, все мы знаем, как должны работать механические детали жёстких дисков, но меня интересовали не они. Мне важна была небольшая печатная плата, находящаяся на обратной стороне HD, где расположены разъёмы SATA и питания. Вот как выглядят подобные печатные платы:



Видно, что на плате есть примерно четыре чипа. Вот что мне удалось о них узнать:



Это память DRAM. С ней всё проще всего, спецификации находятся легко. Эти чипы могут иметь объём от 8 МБ до 64 МБ, и этот размер связан с размером кэша, который должен иметь жёсткий диск.



Это контроллер двигателя привода шпинделя. Это нестандартная деталь, поэтому спецификации найти сложно, однако у некоторых контроллеров, похоже, существуют братья и сёстры, информация о которых чуть более доступна. Кажется, наиболее распространёнными являются контроллеры ST Smooth; кроме управления двигателем шпинделя они занимаются регулированием мощности и имеют несколько аналоговых/цифровых каналов.



Это флэш-память с последовательным интерфейсом. С ней всё тоже легко, она может иметь размер от 64 КБ до 256 КБ. Похоже, она используется для хранения программы, с которой запускается контроллер жёсткого диска. У некоторых жёстких дисков этого чипа нет, однако вместо него имеется флэш-память внутри чипа контроллера диска.



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



А в этом элементе происходит всё самое интересное: это контроллер жёсткого диска. Они изготовляются Marvell, ST и некоторыми другими производителями больших интегральных схем. Некоторые компании-производители жёстких дисков изготавливают и собственные контроллеры: я замечал, что этим занимаются Samsung и Western Digital. Почти всё остальное не представляет никакой сложности, поэтому меня интересовало это устройство.

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

Итак, у нас нет спецификаций на самые важные интегральные схемы, а значит, мы зашли в тупик… или нет?

Подключаем JTAG

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

На форумах HDDGuru я обнаружил тему пользователя Dejan. Ему каким-то образом удалось повредить внутреннюю флэш-память его жёсткого диска, и он захотел понять, существует ли способ загрузки контроллера с внешней флэш-памяти или способ перезаписи флэш-памяти. Ему не удавалось найти ответа в течение пяти дней, но парень оказался изобретательным: следующим постом он опубликовал сообщение, что он смог найти схему расположения выводов порта JTAG. Это важная находка: порт JTAG можно использовать для управления контроллером как марионеткой. Его можно останавливать, перезапускать, модифицировать память, устанавливать контрольные точки, и т.д. Затем Dejan разобрался, как создать дамп загрузочной ПЗУ контроллера, понял, что на одном из разъёмов жёсткого диска есть последовательный порт, и смог восстановить флэш-ПЗУ своего диска. Затем он выполнил дамп ещё нескольких битов и указателей о процессе обновления флэш, после чего окончательно пропал во мгле Интернета.

Всё это оказалось довольно полезной информацией: по крайней мере, это дало мне понять, что все контроллеры Western Digital, похоже, имеют ядро ARM, к которому можно получить доступ через порт JTAG. Из этой информации я также понял, что у жёстких дисков обычно есть последовательный порт, который чаще всего не используется, но может быть полезен для отладки моего хака. Теперь у меня должно быть достаточно информации, чтобы приступить к взлому.

Моя система выглядит так:



Красная штука — это FT2232H, дешёвая плата, которую можно купить примерно за 30 долларов, на ней есть JTAG и последовательный порт, а также интерфейс SPI. Она подключена к интерфейсу JTAG жёсткого диска, а также как к разъёму, на котором у жёсткого диска есть последовательный порт. Жёсткий диск напрямую подключен к порту SATA на материнской плате моих компьютеров, а также к внешнему блоку питания ATX. Для управления JTAG я использовал ПО OpenOCD.

Теперь вопрос заключается в следующем: будет ли это работать на самом деле? Dejan проделал это с 2,5-дюймовым жёстким диском на 250 ГБ с контроллером 88i6745 и выяснил, что используется ядро arm9. Я взял 3,5-дюймовый диск на 2 ТБ с контроллером 88i9146, он имеет другой форм-фактор и чуть новее. К счастью, OpenOCD имеет возможность самостоятельно распознавать, что находится в цепочке JTAG. Вот что я обнаружил:



Это немного сбило меня с толку… Я ожидал одного tap, соответствующего одному ядру ARM… однако обнаружил три tap. Значит ли это, что у этого чипа три ядра ARM?

Проведя исследование, я выяснил, что это так, похоже, у чипа три ядра. Два Feroceon — довольно мощных ядра наподобие arm9, и ядро Cortex-M3 — ядро послабже, больше напоминающее микроконтроллер. Немного поэкспериментировав (и проведя дальнейшие исследования) я выяснил, что все контроллеры имеют собственные функции:
  • Feroceon 1 обрабатывает физическое чтение и запись с/на пластины жёсткого диска
  • Feroceon 2 управляет интерфейсом SATA
  • Feroceon 2 также работает с кэшем и занимается преобразованием из LBA в CHS
  • Cortex-M3 занимается… ничем? Мне удалось остановить его с сохранением всех функций жёсткого диска.
С какого же ядра начинать взлом? Моя задача заключалась в компрометации защиты системы с помощью модификаций прошивки жёсткого диска. Простейший способ реализации этого, который при этом, вероятно, сложнее всего обнаружить — изменение данных на лету. При этом данные на диске не нужно будет менять, а прошивка может просто сделать себя невидимой. Для этого мне нужно найти подходящее для такого перехвата ядро: мне требуется ядро, имеющее доступ к данным, находящимся в процессе переноса с диска на кабель SATA, которое при этом можно сфальсифицировать для модификации данных, пока они находятся между этими двумя точками.

Как эти данные попадают с пластин HD в интерфейс SATA? Здесь я воспользовался своей интуицией. Рассуждал я примерно так:

Если процессоры используют стандартное копирование памяти при частоте 150 МГц, то они бы могли достичь скорости в 150*23/2=2,4 Гбит/с, а на практике, скорее всего, гораздо меньше. По спецификации жёсткий диск имеет скорость 6 Гбит/с, поэтому, вероятно, используется какое-то аппаратное ускорение. Наиболее вероятным аппаратным ускорением будет применение DMA. Это означает, что данные копируются напрямую из логики чтения головок в память без активного участия процессора. То же самое относится и к порту SATA: процессору нужно просто указывать, где находятся данные, а логика DMA занимается считыванием данных напрямую из памяти.

Если это было так, то где бы располагалась память, на которую бы указывал движок DMA? Неплохим кандидатом является кэш жёсткого диска: считываемые с диска данные всё равно попадают в кэш, поэтому логично будет копировать их туда сразу после чтения с диска. Ранее я выяснил, что за работу с кэшем отвечает Feroceon 2, поэтому он стал основной целью моей попытки взлома.



Как видите, привязка к памяти немного фрагментирована. Разбросаны небольшие части ОЗУ, есть пространство IO (ввода-вывода) и IRQ (прерываний), а также немного внутреннего загрузочного ПЗУ. Здесь есть также большой сегмент на 64 МБ того, что, по моему мнению, является чипом DRAM с кэшем на нём. Давайте выясним, так ли это. Сначала я смонтировал диск на свою машину и записал в файл на нём «Hello world!». Смогу ли я найти эту строку в области памяти 64 МБ?

Да, вот и она. Похоже, процессоры Feroceon имеют доступ к кэшу, а сам он привязан к области DRAM 64 МБ.



Инъекция кода

Разумеется, если бы я хотел что-то менять в кэше, то не сканировал бы каждый раз целиком 64 МБ ОЗУ: мне нужно было понять, как работает кэш. Для этого мне нужно было создать дамп прошивки жёсткого диска, дизассемблировать его и понять, чтобы разобраться в функциях кэширования.

Дизассемблирование этой прошивки — нетривиальная задача. Во-первых, в коде перемешаны инструкции ARM и инструкции в стиле thumb. Это раздражает, а у меня нет дизассемблера, способного автоматически переключаться между двумя этими наборами. Кроме того, в коде отсутствует то, что очень упрощает дизассемблирование ПО: обычно процедуры закодированы так, чтобы выводить сообщения типа «Couldn't open logfile!», когда что-то идёт не так. Эти сообщения оказывают огромную помощь, когда разбираешься в том, для чего нужна процедура. Однако в этой прошивке не было таких строк: приходилось разбираться в назначении процедуры только по её коду. Однако кодовая база кажется немного устаревшей, и иногда дизассемблированный код выглядит так, как будто часть функций к нему «прикрутили» позже, что чуть усложнило анализ.

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

После длительного изучения кода и запуска отладчика для проверки моих догадок мне удалось добраться до ядра системы кэширования: таблицы в ОЗУ, которую я назвал «таблицей дескрипторов кэша»:



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

Теперь, когда я раскрыл тайну таблицы дескрипторов кэша, смогу ли я перехватывать запись на диск до того, как она отправится из порта SATA к компьютеру? Чтобы сделать это, мне понадобится возможность исполнения собственного кода в контроллере жёсткого диска. Более того, мне нужно обеспечить запуск кода в нужное время: если я изменю кэш слишком рано, то данные в него ещё не попадут; если я изменю его слишком поздно, то данные уже отправятся на компьютер.

Реализовал я это подключением к уже имеющейся процедуре. Мой хак будет выполняться на Feroceon 2, а этот процессор занимается всей передачей по SATA, поэтому в нём должна быть какая-то процедура, отвечающая за настройку оборудования SATA для получения данных из кэша. Если я найду эту процедуру, то, вероятно, смогу выполнять перед ней собственный код.

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

000167BE ; r0 - slot in sata_req
000167BE sub_0_167BE:
000167BE PUSH {R4-R7,LR}
000167C0 MOVS R7, R0
000167C2 LSLS R1, R0, #4
000167C4 LDR R0, =sata_req
000167C6 SUB SP, SP, #0x14
000167C8 ADDS R6, R1, R0
000167CA LDRB R1, [R6,#0xD]
000167CC LDR R2, =stru_0_40028DC
000167CE STR R1, [SP,#0x28+var_1C]
000167D0 LDRB R0, [R6,#(off_0_FFE3F108+2 - 0xFFE3F0FC)]
000167D2 LDRB R5, [R6,#(off_0_FFE3F108 - 0xFFE3F0FC)]
000167D4 LSLS R0, R0, #4


А вот что происходит, когда в код добавляется вызов моего кода:

000167BE ; r0 - slot in sata_req
000167BE sub_0_167BE:
000167BE PUSH {R4-R7,LR}
000167C0 MOVS R7, R0
000167C2 LD R6, =hookedAddr
000167C4 BX R6
000167C6 .dw checksumFix
000167C8 .dd hookedAddr
000167CC LDR R2, =stru_0_40028DC
000167CE STR R1, [SP,#0x28+var_1C]
000167D0 LDRB R0, [R6,#(off_0_FFE3F108+2 - 0xFFE3F0FC)]
000167D2 LDRB R5, [R6,#(off_0_FFE3F108 - 0xFFE3F0FC)]
000167D4 LSLS R0, R0, #4
...
FFE3F000 PUSH {R0-R12, LR}
FFE3F004 BX changeThingsInCache
FFE3F008 POP {R0-R12, LR}
FFE3F00C LSLS R1, R0, #4
FFE3F010 LDR R0, =sata_req
FFE3F014 SUB SP, SP, #0x14
FFE3F018 ADDS R6, R1, R0
FFE3F01C LDRB R1, [R6,#0xD]
FFE3F020 BX 0x167CC


Как видите, некоторые исходные инструкции были заменены на переход к новому коду в ранее неиспользовавшейся области ОЗУ по адресу 0xFFE3F000 и дополнительное слово, чтобы гарантировать правильность контрольной суммы области кода. Если бы я этого не сделал, то HD попытался бы загрузить резервную копию со своих пластин, а нам этого не нужно. Код, к которому выполняется переход, исполняет процедуру под названием changeThingsInCache, а затем делает то, что бы сделал заменённый код. Затем он продолжает исполнение в исходной процедуре, как будто ничего не произошло.

Теперь мне достаточно написать процедуру для изменения кэшированных данных. Для первого теста я решил написать процедуру, которая в псевдокоде выполняла бы нечто подобное:

void hook() {
foreach (cache_struct in cache_struct_table) {
if (is_valid(cache_struct)) {
foreach (sector in cache_struct.sectors) {
sector[0]=0x12345678;
}
}
}
}


Этот небольшой фрагмент кода при каждом его вызове заменяет первые 4 байта каждого сектора на 0x12345678, поэтому если бы я загрузил это всё на жёсткий диск, то должен был бы увидеть это число в начале каждого считываемого сектора. Я загрузил фрагменты кода по JTAG…



И вот результат:



Продолжение следует…
 
Верх