• Добро пожаловать на сайт - Forumteam.bet !

    Что бы просматривать темы форума необходимо зарегестрироваться или войти в свой аккаунт.

    Группа в телеграме (подпишитесь, что бы не потерять нас) - ForumTeam Chat [Подписатся]
    Связь с администратором - @ftmadmin

Хардкорный взлом самошифрующегося HDD. Реверсим и хакаем внешний накопитель Aigo

karakas

Проявляет активность
Местный
Регистрация
28.01.19
Хардкорный взлом самошифрующегося HDD. Реверсим и хакаем внешний накопитель Aigo
Hello WorldFebruary 12, 2019




8aefce778655baec5642c.png


Реверсинг и взлом внешних самошифрующихся накопителей — мое давнее хобби. В прошлом мне доводилось упражняться с такими моделями, как Zalman VE-400, Zalman ZM-SHE500, Zalman ZM-VE500. Совсем недавно коллега занес мне еще один экспонат: Patriot (Aigo) SK8671, который построен по типичному дизайну — ЖК-индикатор и клавиатура для ввода ПИН-кода.

01-1.jpg

Корпус
02-1.jpg

Упаковка
Доступ к сохраненным на диске данным, которые якобы зашифрованы, дается после ввода ПИН-кода. Несколько вводных замечаний по этому девайсу:



  • для изменения ПИН-кода необходимо нажать F1 перед разблокировкой;
  • в ПИН-коде должно быть от шести до девяти цифр;
  • после пятнадцати неверных попыток диск очищается.


Аппаратная архитектура
Сначала препарируем девайс на части, чтобы понять, из каких компонентов он состоит. Самое нудное занятие — вскрывать корпус: много микроскопических винтиков и пластика. Вскрыв корпус, видим следующее (обрати внимание на припаянный мной пятиконтактный разъем):

03.jpg



Основная плата
Основная плата довольно проста.

04.jpg

Наиболее примечательные ее части (сверху вниз):

  • разъем для ЖК-индикатора (CN1);
  • пищалка (SP1);
  • Pm25LD010 (спецификация) SPI-флешка (U2);
  • контроллер Jmicron JMS539 (спецификация) для USB-SATA (U1);
  • разъем USB 3 (J1).
SPI-флешка хранит прошивку для JMS539 и некоторые настройки.



Плата ЖК-индикатора
На плате ЖК нет ничего примечательного.

05.jpg

06.jpg

Здесь мы видим:

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


Клавиатурная плата
А вот это уже интереснее!

07.jpg

Вот здесь, на задней стороне, мы видим ленточный соединитель, а также Cypress CY8C21434 — микроконтроллер PSoC 1 (далее по тексту будем звать его просто PSoC).

08.jpg

CY8C21434 использует набор инструкций M8C (см. документацию). На странице продуктауказано, что он поддерживает технологию CapSense (решение от Cypress для емкостных клавиатур). Здесь виден припаянный мной пятиконтактный разъем — это стандартный подход для подключения внешнего программатора через ISSP-интерфейс.



Смотрим на провода
Разберемся, что с чем здесь связано. Для этого достаточно прозвонить провода мультиметром.

09.png

Пояснения к этой на коленке нарисованной схеме:

  • PSoC описан в технической спецификации;
  • следующий разъем, тот, что правее, — ISSP-интерфейс, который волею судеб соответствует тому, что о нем написано в интернете;
  • самый правый разъем — это клемма для ленточного соединителя с клавиатурной платой;
  • черный прямоугольник — чертеж разъема CN1, предназначенного для соединения основной платы с ЖК-платой. P11, P13 и P4 присоединены к ножкам PSoC 11, 13 и 4, на ЖК-плате.
Как называется принцип имитации стойкой защиты?

  • Security by default
  • Security by obscurity
  • Secure design


Последовательность шагов атаки
Теперь когда мы знаем, из каких компонентов состоит этот накопитель, нам необходимо:

  1. Убедиться, что базовая функциональность шифрования действительно присутствует.
  2. Узнать, как генерируются/сохраняются ключи шифрования.
  3. Найти, где именно проверяется ПИН-код.
Для этого я проделал следующие шаги:

  • снял дамп данных SPI-флешки;
  • попытался снять дамп данных PSoC-флешки;
  • удостоверился, что обмен данными между Cypress PSoC и JMS539 фактически содержит нажатые клавиши;
  • убедился, что при изменении пароля в SPI-флешке ничего не переписывается;
  • поленился реверсить 8051-прошивку от JMS539.


Снимаем дамп данных SPI-флешки
Эта процедура очень проста:

  • подключить зонды к ножкам флешки: CLK, MOSI, MISO и (опционально) EN;
  • «обнюхать» коммуникации сниффером, используя логический анализатор (я взял Saleae Logic Pro 16);
  • декодировать SPI-протокол и экспортировать результаты в CSV;
  • воспользоваться decode_spi.rb, чтобы распарсить результаты и получить дамп.
Обрати внимание, что такой подход в случае с JMS539-контроллером работает в особенности хорошо, поскольку этот контроллер на этапе инициализации загружает с флешки всю прошивку.

$ decode_spi.rb boot_spi1.csv dump
0.039776 : WRITE DISABLE
0.039777 : JEDEC READ ID
0.039784 : ID 0x7f 0x9d 0x21
---------------------
0.039788 : READ @ 0x0
0x12,0x42,0x00,0xd3,0x22,0x00,
[...]
$ ls --size --block-size=1 dump
49152 dump
$ sha1sum dump
3d9db0dde7b4aadd2b7705a46b5d04e1a1f3b125 dump

Сняв дамп с SPI-флешки, я пришел к выводу, что ее единственная задача — хранить прошивку для устройства управления JMicron, которая встраивается в 8051-микроконтроллер. К сожалению, снятие дампа SPI-флешки оказалось бесполезным:

  • при изменении ПИН-кода дамп флешки остается тем же самым;
  • после этапа инициализации девайс к SPI-флешке не обращается.
  • самый правый разъем — это клемма для ленточного соединителя с клавиатурной платой;
  • черный прямоугольник — чертеж разъема CN1, предназначенного для соединения основной платы с ЖК-платой. P11, P13 и P4 присоединены к ножкам PSoC 11, 13 и 4, на ЖК-плате.
Последовательность шагов атаки
Теперь когда мы знаем, из каких компонентов состоит этот накопитель, нам необходимо:

  1. Убедиться, что базовая функциональность шифрования действительно присутствует.
  2. Узнать, как генерируются/сохраняются ключи шифрования.
  3. Найти, где именно проверяется ПИН-код.
Для этого я проделал следующие шаги:

  • снял дамп данных SPI-флешки;
  • попытался снять дамп данных PSoC-флешки;
  • удостоверился, что обмен данными между Cypress PSoC и JMS539 фактически содержит нажатые клавиши;
  • убедился, что при изменении пароля в SPI-флешке ничего не переписывается;
  • поленился реверсить 8051-прошивку от JMS539.




Снимаем дамп данных SPI-флешки
Эта процедура очень проста:

  • подключить зонды к ножкам флешки: CLK, MOSI, MISO и (опционально) EN;
  • «обнюхать» коммуникации сниффером, используя логический анализатор (я взял Saleae Logic Pro 16);
  • декодировать SPI-протокол и экспортировать результаты в CSV;
  • воспользоваться decode_spi.rb, чтобы распарсить результаты и получить дамп.
Обрати внимание, что такой подход в случае с JMS539-контроллером работает в особенности хорошо, поскольку этот контроллер на этапе инициализации загружает с флешки всю прошивку.

$ decode_spi.rb boot_spi1.csv dump
0.039776 : WRITE DISABLE
0.039777 : JEDEC READ ID
0.039784 : ID 0x7f 0x9d 0x21
---------------------
0.039788 : READ @ 0x0
0x12,0x42,0x00,0xd3,0x22,0x00,
[...]
$ ls --size --block-size=1 dump
49152 dump
$ sha1sum dump
3d9db0dde7b4aadd2b7705a46b5d04e1a1f3b125 dump

Сняв дамп с SPI-флешки, я пришел к выводу, что ее единственная задача — хранить прошивку для устройства управления JMicron, которая встраивается в 8051-микроконтроллер. К сожалению, снятие дампа SPI-флешки оказалось бесполезным:

  • при изменении ПИН-кода дамп флешки остается тем же самым;
  • после этапа инициализации девайс к SPI-флешке не обращается.
Обнюхиваем коммуникации
Попробуем найти, какой чип отвечает за проверку коммуникаций для интересующих нас времени/контента. Как мы уже знаем, контроллер USB-SATA подключен к ЖК Cypress PSoC, через разъем CN1 и две ленты. Поэтому подключаем зонды к трем соответствующим ножкам:

  • P4, общий ввод/вывод;
  • P11, I2C SCL;
  • P13, I2C SDA.
3b45b973b47865983b310.jpg

Затем запускаем логический анализатор Saleae и вводим на клавиатуре 123456✓. В результате видим следующую диаграмму.

3877c062e80ba3ddc8ab4.png

На ней можем заметить три канала обмена данными:

  • на канале P4 несколько коротких всплесков;
  • на P11 и P13 — почти непрерывный обмен данными.
Увеличивая первый всплеск на канале P4 (синий прямоугольник предыдущего рисунка), получаем следующее:

4954e681129d79ca95f2a.png

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

Но последний аудиопоток канала P4 немного отличается от других: это звук для «неверного ПИН-кода»!

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

13e9b5df862061f39c37c.png

Здесь мы видим однообразные сигналы на P11. Так что, похоже, это и есть синхросигнал. А P13 — данные. Обрати внимание, как шаблон изменяется после окончания звукового сигнала. Было бы интересно посмотреть, что здесь происходит.

Протоколы, работающие с двумя проводами, — это обычно SPI или I2C, и в технической спецификации на Cypress говорится, что эти контакты соответствуют I2C. Как видим, это справедливо и для нашего случая:

737e4c3607007de322b7a.png

Чипсет USB-SATA постоянно опрашивает PSoC — чтобы считывать состояние клавиши, которое по умолчанию равно 0. Затем, при нажатии клавиши 1, оно меняется на 1. Окончательная передача, сразу после нажатия ✓, отличается, если введен неверный ПИН-код. Однако на данный момент я не проверял, что там фактически передается. Но подозреваю, что вряд ли это ключ шифрования. Так или иначе, настало время снять дамп внутренней прошивки PSoC.





Начинаем снимать дамп с внутренней флешки PSoC
Итак, все указывает на то, что ПИН-код хранится во флеш-недрах PSoC. Поэтому нам нужно прочитать эти флеш-недра. Фронт необходимых работ:

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

  • внутренняя флеш-память;
  • SRAM, где ПИН-код может храниться для сравнения его с тем ПИН-кодом, который вводится пользователем.
Забегая вперед, отмечу, что мне все-таки удалось снять дамп внутренней флешки PSoC, обойдя ее систему защиты посредством аппаратной атаки «трассировка с холодной перезагрузкой» — после реверсинга недокументированных возможностей ISSP-протокола. Это позволило мне напрямую снимать дамп действующего ПИН-кода.

$ ./psoc.py
syncing: KO OK
[...]
PIN: 1 2 3 4 5 6 7 8 9

Итоговый программный код:

Что такое ISSP
«Общение» с микроконтроллером может означать разные вещи: от endor to vendor до взаимодействия с применением последовательного протокола (например, ICSP для Microchip’овского PIC).

У Cypress для этого собственный проприетарный протокол, называемый ISSP (in-system serial programming protocol — внутрисистемный протокол последовательного программирования), который частично описан в технической спецификации. Патент US7185162 также дает некоторую информацию. Есть и open source аналог, называемый HSSP (мы воспользуемся им чуть позже). ISSP работает следующим образом:

  • перезагрузить PSoC;
  • вывести магическое число на ножку последовательных данных этой PSoC (для входа в режим внешнего программирования);
  • отправить команды, которые представляют собой длинные битовые строки, называемые векторами.
В документации на ISSP эти векторы определены лишь для небольшой горстки команд:

  • Initialize-1;
  • Initialize-2;
  • Initialize-3 (варианты 3V и 5V);
  • ID-SETUP;
  • READ-ID-WORD;
  • SET-BLOCK-NUM: 10011111010dddddddd111, где dddddddd=block #;
  • BULK ERASE;
  • PROGRAM-BLOCK;
  • VERIFY-SETUP;
  • READ-BYTE: 10110aaaaaaZDDDDDDDDZ1, где DDDDDDDD = data out, aaaaaa = адрес (6 бит);
  • WRITE-BYTE: 10010aaaaaadddddddd111, где dddddddd = data in, aaaaaa = адрес (6 бит);
  • SECURE;
  • CHECKSUM-SETUP;
  • READ-CHECKSUM: 10111111001ZDDDDDDDDZ110111111000ZDDDDDDDDZ1, где DDDDDDDDDDDDDDDD = data out: контрольная сумма девайса;
  • ERASE BLOCK.
Например, вектор для Initialize-2:

1101111011100000000111 1101111011000000000111
1001111100000111010111 1001111100100000011111
1101111010100000000111 1101111010000000011111
1001111101110000000111 1101111100100110000111
1101111101001000000111 1001111101000000001111
1101111000000000110111 1101111100000000000111
1101111111100010010111

У всех векторов одинаковая длина — 22 бита. В документации на HSSP есть некоторые дополнительные сведения по ISSP: «ISSP-вектор — это не что иное, как битовая последовательность, представляющая собой набор инструкций».



Демистификация векторов
Разберемся, что здесь происходит. Первоначально я предполагал, что эти самые векторы представляют собой raw-варианты M8C-инструкций, однако, проверив эту гипотезу, я обнаружил, что опкоды операций не совпадают.

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

Затем мне удалось почерпнуть очень полезную информацию из раздела «Supervisory ROM (SROM)» технического руководства. SROM — это жестко закодированная ROM в PSoC, которая предоставляет сервисные функции (по тому же принципу, что и Syscall), — для программного кода, запущенного в пользовательском пространстве:

  • 00h : SWBootReset
  • 01h : ReadBlock
  • 02h : WriteBlock
  • 03h : EraseBlock
  • 06h : TableRead
  • 07h : CheckSum
  • 08h : Calibrate0
  • 09h : Calibrate1
Сравнивая имена векторов с функциями SROM, мы можем сопоставить операции, поддерживаемые этим протоколом, с ожидаемыми SROM-параметрами. Благодаря этому можем декодировать первые три бита ISSP-векторов:

  • 100 => wrmem
  • 101 => rdmem
  • 110 => wrreg
  • 111 => rdreg
Однако полное понимание внутричиповых процессов можно получить только при непосредственном общении с PSoC.



Общение с PSoC
Поскольку Дирк Петраутский уже портировал Cypress’овский HSSP-код на Arduino, я воспользовался Arduino Uno для подключения к ISSP-разъему клавиатурной платы.

Обрати внимание, что в ходе своих исследований я довольно сильно изменил код Дирка. Мою модификацию можно найти на GitHub: здесь, соответствующий Python-скрипт для общения с Arduino — в моем репозитории cypress_psoc_tools.

Итак, применяя Arduino, я сначала использовал для «общения» только «официальные» векторы. Я попытался прочитать внутреннюю ROM, используя команду VERIFY. Как и ожидалось, этого мне сделать не удалось. Вероятно, из-за того, что внутри флешки активированы биты защиты от считывания.

Затем я создал несколько своих простеньких векторов для записи и чтения памяти/регистров. Обрати внимание, что мы можем читать всю SROM, даже несмотря на то, что флешка защищена!



Идентификация внутричиповых регистров
Посмотрев на «дизассемблированные» векторы, я обнаружил, что девайс использует недокументированные регистры (0xF8–0xFA) для указания M8C-опкодов, которые выполняются напрямую, в обход защиты. Это позволило мне запускать различные опкоды, такие как «ADD», «MOV A, X», «PUSH» или «JMP». Благодаря им (глядя на побочные эффекты, оказываемые ими на регистры) я смог определить, какие из недокументированных регистров фактически являются обычными регистрами (A, X, SP и PC).

В итоге «дизассемблированный» код, сгенерированный инструментом HSSP_disas.rb, выглядит так (для ясности я добавил комментарии):

--== init2 ==--
[DE E0 1C] wrreg CPU_F (f7), 0x00 # Сброс флагов
[DE C0 1C] wrreg SP (f6), 0x00 # Сброс SP
[9F 07 5C] wrmem KEY1, 0x3A # Обязательный аргумент для SSC
[9F 20 7C] wrmem KEY2, 0x03 # Аналогично
[DE A0 1C] wrreg PCh (f5), 0x00 # Сброс PC (MSB) ...
[DE 80 7C] wrreg PCl (f4), 0x03 # (LSB) ... до 3 ??
[9F 70 1C] wrmem POINTER, 0x80 # RAM-указатель для выходных данных
[DF 26 1C] wrreg opc1 (f9), 0x30 # Опкод 1 => "HALT"
[DF 48 1C] wrreg opc2 (fa), 0x40 # Опкод 2 => "NOP"
[9F 40 3C] wrmem BLOCKID, 0x01 # BLOCK ID для вызова SSC
[DE 00 DC] wrreg A (f0), 0x06 # Номер "Syscall" : TableRead
[DF 00 1C] wrreg opc0 (f8), 0x00 # Опкод для SSC, "Supervisory SROM Call"
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12 # Недокументированная операция: выполнить внешний опкод



Защитные биты
На данном этапе я уже могу общаться с PSoC, но у меня все еще нет достоверной информации о защитных битах флешки. Я был очень удивлен, что Cypress не дает пользователю девайса никаких средств для того, чтобы проверить, активирована ли защита. Я углубился в Google, чтобы окончательно понять, что HSSP-код, предоставленный Cypress’ом, был обновлен уже после того, как Дирк выпустил свою модификацию. В результате появился вот такой новый вектор:

[DE E0 1C] wrreg CPU_F (f7), 0x00
[DE C0 1C] wrreg SP (f6), 0x00
[9F 07 5C] wrmem KEY1, 0x3A
[9F 20 7C] wrmem KEY2, 0x03
[9F A0 1C] wrmem 0xFD, 0x00 # Неизвестные аргументы
[9F E0 1C] wrmem 0xFF, 0x00 # Аналогично
[DE A0 1C] wrreg PCh (f5), 0x00
[DE 80 7C] wrreg PCl (f4), 0x03
[9F 70 1C] wrmem POINTER, 0x80
[DF 26 1C] wrreg opc1 (f9), 0x30
[DF 48 1C] wrreg opc2 (fa), 0x40
[DE 02 1C] wrreg A (f0), 0x10 # Недокументированный syscall!
[DF 00 1C] wrreg opc0 (f8), 0x00
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12

Используя этот вектор (см. read_security_data в psoc.py), мы получаем все защитные биты в SRAM в 0x80, где на каждый защищаемый блок приходится по два бита.

Результат удручает: все защищено в режиме «отключить внешние чтение и запись». Поэтому мы не только считывать с флешки ничего не можем, но и записывать тоже (чтобы, например, внедрить туда ROM-дампер). А единственный способ отключить защиту — полностью стереть весь чип.



Первая (неудавшаяся) атака: ROMX
Однако мы можем попробовать сделать следующий трюк: поскольку у нас есть возможность выполнять произвольные опкоды, почему бы не выполнить ROMX, который применяется для чтения флеш-памяти? У такого подхода есть неплохие шансы на успех. Потому что функция ReadBlock, считывающая данные из SROM (которая используется векторами), проверяет, вызывается ли она из ISSP. Однако опкод ROMX, предположительно, может не иметь такой проверки. Итак, вот Python-код (после добавления нескольких вспомогательных классов в сишный Arduino-код):

for i in range(0, 8192):
write_reg(0xF0, i>>8) # A = 0
write_reg(0xF3, i&0xFF) # X = 0
exec_opcodes("\x28\x30\x40") # ROMX, HALT, NOP
byte = read_reg(0xF0) # ROMX reads ROM[A|X] into A
print "%02x" % ord(byte[0]) # print ROM byte

К сожалению, этот код не работает. Вернее, работает, но мы на выходе получаем свои собственные опкоды (0x28 0x30 0x40)! Не думаю, что соответствующая функциональность девайса служит элементом защиты от чтения. Это больше похоже на инженерный трюк: при выполнении внешних опкодов ROM’овская шина перенаправляется на временный буфер.



Вторая атака: трассировка с холодной перезагрузкой
Поскольку трюк с ROMX не сработал, я стал обдумывать другую вариацию этого трюка — описанную в публикации Shedding too much Light on a Microcontroller’s Firmware Protection.



Реализация
В документации на ISSP приведен следующий вектор для CHECKSUM-SETUP:

[DE E0 1C] wrreg CPU_F (f7), 0x00
[DE C0 1C] wrreg SP (f6), 0x00
[9F 07 5C] wrmem KEY1, 0x3A
[9F 20 7C] wrmem KEY2, 0x03
[DE A0 1C] wrreg PCh (f5), 0x00
[DE 80 7C] wrreg PCl (f4), 0x03
[9F 70 1C] wrmem POINTER, 0x80
[DF 26 1C] wrreg opc1 (f9), 0x30
[DF 48 1C] wrreg opc2 (fa), 0x40
[9F 40 1C] wrmem BLOCKID, 0x00
[DE 00 FC] wrreg A (f0), 0x07
[DF 00 1C] wrreg opc0 (f8), 0x00
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12

Здесь производится вызов SROM-функции 0x07, как представлено в документации (ширный шрифт мой):

Это функция проверки контрольной суммы. Она вычисляет 16-битовую контрольную сумму количества блоков, заданных пользователем, в одном флеш-банке, отсчитывая с нуля. Параметр BLOCKID используется для передачи количества блоков, которое будет использоваться при расчете контрольной суммы. Значение 1 будет вычислять контрольную сумму только для нулевого блока, тогда как 0 приведет к тому, что будет вычислена общая контрольная сумма всех 256 блоков флеш-банка. 16-битовая контрольная сумма возвращается через KEY1 и KEY2. В параметре KEY1 фиксируются младшие 8 бит контрольной суммы, а в KEY2 — старшие 8 бит. Для девайсов с несколькими флеш-банками функция контрольной суммы вызывается для каждого по отдельности. Номер банка, с которым она будет работать, задается регистром FLS_PR1 (путем установки в нем бита, соответствующего целевому флеш-банку).
Обрати внимание, что это простейшая контрольная сумма: байты просто суммируются один за другим; никаких изощренных CRC-причуд. Кроме того, зная, что в ядре M8C набор регистров очень невелик, я предположил, что при вычислении контрольной суммы промежуточные значения будут фиксироваться в тех же самых переменных, которые в итоге пойдут на выход: KEY1 (0xF8) / KEY2 (0xF9).

Итак, в теории моя атака выглядит так:

  1. Соединяемся через ISSP.
  2. Запускаем вычисление контрольной суммы с использованием вектора CHECKSUM-SETUP.
  3. Перезагружаем процессор через заданное время T.
  4. Считываем RAM, чтобы получить текущую контрольную сумму C.
  5. Повторяем шаги 3 и 4, каждый раз немного увеличивая T.
  6. Восстанавливаем данные из флешки, вычитая предыдущую контрольную сумму C из текущей.
Однако возникла проблема: вектор Initialize-1, который мы должны отправить после перезагрузки, перезаписывает KEY1 и KEY2:

1100101000000000000000 # Магия, переводящая PSoC в режим программирования
nop
nop
nop
nop
nop
[DE E0 1C] wrreg CPU_F (f7), 0x00
[DE C0 1C] wrreg SP (f6), 0x00
[9F 07 5C] wrmem KEY1, 0x3A # Контрольная сумма перезаписывается здесь
[9F 20 7C] wrmem KEY2, 0x03 # и здесь
[DE A0 1C] wrreg PCh (f5), 0x00
[DE 80 7C] wrreg PCl (f4), 0x03
[9F 70 1C] wrmem POINTER, 0x80
[DF 26 1C] wrreg opc1 (f9), 0x30
[DF 48 1C] wrreg opc2 (fa), 0x40
[DE 01 3C] wrreg A (f0), 0x09 # SROM-функция 9
[DF 00 1C] wrreg opc0 (f8), 0x00 # SSC
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12

Этот код затирает нашу драгоценную контрольную сумму, вызывая Calibrate1 (SROM-функция 9)… Может быть, нам удастся, просто отправив магическое число (из начала приведенного выше кода), войти в режим программирования и затем считать SRAM? И да, это работает! Arduino-код, реализующий эту атаку, довольно прост:

case Cmnd_STK_START_CSUM:
checksum_delay = ((uint32_t)getch())<<24;
checksum_delay |= ((uint32_t)getch())<<16;
checksum_delay |= ((uint32_t)getch())<<8;
checksum_delay |= getch();
if(checksum_delay > 10000) {
ms_delay = checksum_delay/1000;
checksum_delay = checksum_delay%1000;
}
else {
ms_delay = 0;
}
send_checksum_v();
if(checksum_delay)
delayMicroseconds(checksum_delay);
delay(ms_delay);
start_pmode();

  1. Считать checkum_delay.
  2. Запустить вычисление контрольной суммы (send_checksum_v).
  3. Подождать заданный промежуток времени, учитывая следующие подводные камни:я убил уйму времени, пока не узнал, что, оказывается, delayMicroseconds работает корректно только с задержками, не превышающими 16 383 мкс;
  4. и затем снова убил столько же времени, пока не обнаружил, что delayMicroseconds, если ей на вход передать 0, работает совершенно неправильно!
  5. Перезагрузить PSoC в режим программирования (просто отправляем магическое число, без отправки инициализирующих векторов).
Итоговый код на Python:

for delay in range(0, 150000): # Задержка в микросекундах
for i in range(0, 10): # Количество считывания для каждой из задержек
try:
reset_psoc(quiet=True) # Перезагрузка и вход в режим программирования
send_vectors() # Отправка инициализирующих векторов
ser.write("\x85"+struct.pack(">I", delay)) # Вычислить контрольную сумму + перезагрузиться после задержки
res = ser.read(1) # Считать Arduino ACK
except Exception as e:
print e
ser.close()
os.system("timeout -s KILL 1s picocom -b 115200 /dev/ttyACM0 2>&1 > /dev/null")
ser = serial.Serial('/dev/ttyACM0', 115200, timeout=0.5) # Открыть последовательный порт
continue
print "%05d %02X %02X %02X" % (delay, # Считать RAM-байты
read_regb(0xf1),
read_ramb(0xf8),
read_ramb(0xf9))

В двух словах, что делает этот код:

  1. Перезагружает PSoC (и отправляет ему магическое число).
  2. Отправляет полноценные векторы инициализации.
  3. Вызывает Arduino-функцию Cmnd_STK_START_CSUM (0x85), куда в качестве параметра передается задержка в микросекундах.
  4. Считывает контрольную сумму (0xF8 и 0xF9) и недокументированный регистр 0xF1.
Этот код выполняется по десять раз за одну микросекунду. 0xF1 сюда включен, поскольку был единственным регистром, который менялся при вычислении контрольной суммы. Возможно, это какая-то временная переменная, используемая арифметико-логическим устройством. Обрати внимание на уродливый хак, которым я перезагружаю Arduino, используя picocom, когда Arduino перестает подавать признаки жизни (понятия не имею почему).





Снимаем дамп блока 126
Блок 126 должен располагаться где-то в районе 125 x 64 x 18 = 144 000 мкс от начала расчета контрольной суммы в моем полном дампе, и он выглядит вполне правдоподобно. Затем, после ручного отсеивания многочисленных неверных дампов (из-за накопления «незначительных отклонений по времени»), я в итоге получил вот такие байты (на задержке 145 527 мкс):

b21b867ebef00fb3f6563.png

Совершенно очевидно, что ПИН-код хранится в незашифрованном виде! Эти значения, конечно, записаны не в ASCII-кодах, но, как оказалось, отражают показания, снятые с емкостной клавиатуры.

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

4dd6afc94789c51b2195a.png

0xFF означает «15 попыток», и он уменьшается при каждой неверной попытке.





Восстановление ПИН-кода
Вот мой уродливый код, который собирает вместе все сказанное выше:

def dump_pin():
pin_map = {0x24: "0", 0x25: "1", 0x26: "2", 0x27:"3", 0x20: "4", 0x21: "5",
0x22: "6", 0x23: "7", 0x2c: "8", 0x2d: "9"}
last_csum = 0
pin_bytes = []
for delay in range(145495, 145719, 16):
csum = csum_at(delay, 1)
byte = (csum-last_csum)&0xFF
print "%05d %04x (%04x) => %02x" % (delay, csum, last_csum, byte)
pin_bytes.append(byte)
last_csum = csum
print "PIN: ",
for i in range(0, len(pin_bytes)):
if pin_bytes in pin_map:
print pin_map[pin_bytes],
print

Вот результат его выполнения:

$ ./psoc.py
syncing: KO OK
Resetting PSoC: KO Resetting PSoC: KO Resetting PSoC: OK
145495 53e2 (0000) => e2
145511 5407 (53e2) => 25
145527 542d (5407) => 26
145543 5454 (542d) => 27
145559 5474 (5454) => 20
145575 5495 (5474) => 21
145591 54b7 (5495) => 22
145607 54da (54b7) => 23
145623 5506 (54da) => 2c
145639 5506 (5506) => 00
145655 5533 (5506) => 2d
145671 554c (5533) => 19
145687 554e (554c) => 02
145703 554e (554e) => 00
PIN: 1 2 3 4 5 6 7 8 9

Ура! Работает!

Важный момент: значения задержки, использованные мной, скорее всего, актуальны для одного конкретного PSoC — того, которым пользовался я.



Что дальше?
Итак, подведем итоги на стороне PSoC, в контексте нашего накопителя Aigo:

  • мы можем считывать SRAM, даже если она защищена от считывания;
  • мы можем обойти защиту от считывания посредством атаки «трассировка с холодной перезагрузкой» и непосредственного считывания ПИН-кода.
Тем не менее у нашей атаки есть некоторые недоработки — из-за проблем с синхронизацией. Ее можно было бы улучшить следующим образом:

  • написать утилиту для правильного декодирования выходных данных, которые получены в результате атаки «трассировка с холодной перезагрузкой»;
  • использовать FPGA-примочку для создания более точных временных задержек (или использовать аппаратные таймеры Arduino);
  • попробовать еще одну атаку: ввести заведомо неверный ПИН-код, перезагрузить и сдампить RAM, надеясь на то, что правильный ПИН-код окажется сохраненным в RAM, для сравнения. Однако на Arduino это сделать не так-то просто, поскольку уровень сигнала Arduino составляет 5 В, в то время как исследуемая нами плата работает с сигналами в 3,3 В.
Одна интересная вещь, которую можно было бы попробовать, — поиграть уровнем напряжения для обхода защиты от чтения. Если бы такой подход сработал, мы бы смогли получать абсолютно точные данные с флешки — вместо того, чтобы полагаться на чтение контрольной суммы с неточными временными задержками.

Поскольку SROM, вероятно, считывает защитные биты посредством системного вызова ReadBlock, мы могли бы сделать то же самое, что описано в блоге Дмитрия Недоспасова, — повторную реализацию атаки Криса Герлински, анонсированной на конференции REcon Brussels 2017.

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



Заключение
Итак, защита этого накопителя оставляет желать лучшего, потому что для хранения ПИН-кода он использует обычный (не «закаленный») микроконтроллер… Плюс я еще не смотрел (пока), как на этом девайсе обстоят дела с шифрованием данных!

Что можно посоветовать для Aigo? Проанализировав пару-тройку моделей зашифрованных HDD-накопителей, я в 2015 году сделал презентацию на SyScan, в которой рассмотрел проблемы безопасности нескольких внешних HDD-накопителей и дал рекомендации, что в них можно было бы улучшить.

На это исследование я потратил два выходных и несколько вечеров. В общей сложности порядка сорока часов. Считая с самого начала (когда я вскрыл диск) и до конца (дамп ПИН-кода). В эти же сорок часов включено время, которое я потратил, чтобы написать эту статью. Очень увлекательное было путешествие.

Всем спасибо за внимание!
 
Сверху Снизу