Привет всем!  Как всегда ярко, мощно и динамично отгремел The Standoff, отшумели насыщенные PHDays10, и мы занялись разбором накопившихся завалов из отложенных дел. На волне впечатлений от международного форума по практической безопасности «Positive Hack Days: Начало» (и пока не стартанул осенний The Standoff) решили с коллегой дооформить статью о том, как мы ковыряли бейдж с позапрошлогодней конференции OFFZONE и что из этого вышло. Наш разбор под катом. Ну что, поехали? Всегда кажется, что проблемы легко разрешимы, если идти по пути наименьшего сопротивления. Путь, который кажется таким легким, оказывается наиболее тяжким и жестоким. Уинстон Черчилль Напомним, что одной из фишек мероприятия в 2019 году был бейдж — ниже его можно увидеть в базовой конфигурации. Его идея не нова: в мире конференций предостаточно похожих интерактивных устройств, поэтому всегда приятно — и уму, и сердцу — такие экземпляры поизучать.  Итак, чем примечателен этот бейдж? Как ни странно, тем, что изначально на нем ничего не было. В специально отведенной CRAFT.ZONE на конференции — этакой комнатке с паяльниками — любой участник мог расширить базовую начинку бейджа.        Тюнингованный бейдж давал доступ к заданиям, которые выбирались с помощью DIP-переключателя. Выполненное задание по USB отправляло в консоль флаг в формате offzone{}. Сами задания — а их было всего пять — можно было получить через Telegram-бот. Туда же участники постили полученные флаги.      Конечно, нашлось на конференции несколько человек, которые все эти задания успешно решили. Например, AV1ct0r на своей страничке выложил свои варианты решений:  После конференции авторы бейджа опубликовали статьи, посвященные истории его создания, сложностям, с которыми пришлось столкнуться в ходе производства устройств, и связанным с бейджем заданиями. Вот они:   Текстолит вместо картона. Пара слов об интерактивном бейдже OFFZONE 2019  Вокруг бейджа за 80 дней: по ту сторону OFFZONE    В примечаниях к первой статье организаторы приложили принципиальную схему бейджа. Ядром бейджа был микроконтроллер Stm32F103c8t6, а прошивка загружалась через Arduino IDE с аддонами для STM32 (разработчики бейджа указывали STM32duino в своих статьях).           Бо́льшая часть проблем либо не имеет решения, либо имеет несколько решений. Лишь очень немногие проблемы имеют только одно решение. Эдмунд Беркли Ядро бейджа — микроконтроллер Stm32F103c8t6 — построен на архитектуре ARM Cortex M3, разрядность — 32 бита, объем памяти программ (FLASH) — 64/128 Кб, объем оперативной памяти (RAM) — 20 КБ.  Наш первый шаг — получить прошивку. Сначала пошли классическим путем, то есть подключили программатор и через отладочные порты попытались считать ее содержимое. Однако на процессоре исследуемого устройства была активирована опция Read Out Protection (RDP) — защита от считывания программы, записанной во флеш. Это видно по соответствующему значению считанного бита в Option Bytes. Такой способ защиты прошивки от считывания — самый простой и действенный: при активированном RDP микроконтроллер нельзя ни отладить, ни прочитать с него прошивку. А если попытаться этот бит выключить, прошивка очистится. Решили обратиться к наработкам независимых исследователей в интернете. Оказалось, что механизмы защиты микроконтроллеров, в частности STM32, давно проверяют на прочность, и есть ряд техник, позволяющих обойти подобные защиты и получить содержимое прошивки. Так, например, для семейства микроконтроллеров STM32F1 существуют уязвимости CVE-2020-13466 (STMicroelectronics STM32F103 devices through 2020-05-20 allow physical attackers to execute arbitrary code via a power glitch and a specific flash patch/breakpoint unit configuration) и CVE-2020-8004 (STMicroelectronics STM32F1 devices have Incorrect Access Control). Если кратко, вторая уязвимость позволяет произвести чтение данных из флеш-памяти, обратившись через систему прерываний процессора во время отладки.  Мы использовали уязвимость CVE-2020-8004 для чтения прошивки нашего устройства, поскольку ее эксплуатация не требует дополнительных аппаратных инструментов кроме обычного отладчика, подобного J-Link. В этой статье шаги описаны очень подробно, поэтому мы их просто повторили. А именно: подключили отладчик J-Link,  использовали программную связку OpenOCD (Open On-Chip Debugger — система отладки для микроконтроллеров) и скрипты, с помощью которых скачали байты прошивки из исследуемого бейджа. Первый дамп прошивки получен! Дамп через выбранную нами уязвимость не дает 100-процентное покрытие кода, как и было указано в источнике. На примере загрузчика это выглядит так: Из каждых 256 байт непрочитанными оказались 28 байт — они заполняются значениями 0xFF. Для полноценного реверса такой способ, разумеется, не подходит. Тем не менее дамп позволяет получить представление о коде, проанализировать его глазами, а также изучить доступные отладочные строки.  В результате анализа выяснилось, что прошивка фактически состоит из двух кусков: дампа памяти с адреса 0x08000000 — загрузчик, или STM32duino bootloader, и дампа памяти с адреса 0x08002000 — прошивка бейджа. В свою очередь, анализ загрузчика по адресам 0x08000000–0x08002000 показал кое-что интересное: загрузчик STM32duino bootloader поддерживает протокол USB DFU (Universal Serial Bus Device Firmware Upgrade — обновление микропрограммы устройства по универсальной последовательной шине). Ниже представлены строки из дампа, которые используются в логах USB DFU. Проект STM32duino является продолжением проекта LeafMaple: софт STM32duino поддерживает не только платы LeafMaple и их клоны, но также и целый набор плат на stm32f103 и stm32f4. Таким образом, загрузчик STM32duino bootloader использует наработки загрузчика Maple-Bootloader, позволяющего загружать программы (скетчи) из Maple-IDE по USB. При изучении документации и функций выяснилось, что он может не только загружать, но и считывать прошивки из микроконтроллера: «…Maple Mini Bootloader 2.0 supports the next uploading protocols…». Значит, можно попробовать считать нашу прошивку с помощью протокола USB DFU и получить еще один дамп, но уже со 100-процентным покрытием в отличие от предыдущего раза. Для работы по протоколу USB DFU можно использовать утилиту dfu-util. Так и поступили: подключили бейдж к USB и считали прошивку с ее помощью. И вот наконец мы получили прошивку бейджа. Отметим, что сам загрузчик STM32duino bootloader не входит в сдампленную область, но это неважно, так как его код доступен на официальном сайте проекта STM32duino. Имея полноценную прошивку бейджа и код загрузчика, мы можем ее исследовать. Чтобы в дальнейшем отлаживаться непосредственно на работающем устройстве через отладчик, советуем «перезалить» бейдж сдампленной прошивкой, удалив при этом включенную защиту от считывания Read Out Protection. Забегая вперед, скажем, что эта опция нам очень пригодилась, поскольку по ходу разбора нам пришлось дампить память в различных режимах работы бейджа. Для анализа прошивки в IDA Pro не хватает отладочных символов, но, зная об используемом ПО Arduino STM32, можно отталкиваться от исходных кодов с GitHub. Мы считаем, что необходимо и достаточно восстановить сначала высокоуровневые функции, а затем переходить к низкоуровневым. Ниже показано, как обработчик ResetHandler (вектор прерывания 0) запускает главную программу main. Функция main передает управление rt_main, которая запускает обработку заданий в два этапа:  общая подготовка и инициализация — функция offzone_tasks_prepare @080047B4; контроль и выполнение заданий — функция offzone_tasks_do @08004E98.  В offzone_tasks_do представлено пять заданий (task1 — task5). Задание task0, как выяснилось, служило для отправки информации о бейдже по инфракрасному порту во внешний мир — об этом расскажем чуть позже.  Для дальнейшего анализа каждого из заданий необходимо восстановить код, занимающийся периферией. Общее для всех заданий — работа со светодиодной лентой. Давайте с нее и начнем. Светодиодные огоньки входят в базовую комплектацию устройства: бейдж активно мигает при включении, а также при работе разных заданий. По схеме это лента WS2812B компании WORLDSEMI, состоящая из четырех светодиодов.  В Arduino STM32 за управление лентой отвечает библиотека WS2812B (Neopixel) library for Arduino STM32 (Libmaple core). Удобнее начинать поиск функций в дизассемблере с инициализации (функция WS2812B::begin), а точнее с константы SPI_CLOCK_DIV32 = 32. Хорошо, что в дизассемблерных функциях не так много констант со значением 32. Вот как выглядит ход мыслей при поиске функции WS2812B::begin в IDA: Обычно линкер собирает все функции в том же порядке, в каком они объявлены в исходном тексте. Если в исполняемом коде к функции нет обращений, линкер ее пропускает. Последовательность функций в исходном тексте WS2812B.cpp представлена ниже. Та же последовательность функций в дизассемблере: Примеры использования разных способов зажигания светодиодов (радуга, колесо и др.) приведены в репозитории библиотеки по ссылке.  Именно консольный порт USB используется для взаимодействия с внешним миром: с его помощью вводятся команды управления, выводятся содержимое флагов и другие сообщения. Исходный код для обработчика консольного порта находится в репозитории по ссылке. Класс USBSerial инициализируется самым первым в функции offzone_tasks_prepare, вызывая USBSerial::begin (Unsigned long ignoreBaud, uint8_t ignore). Глобальная переменная g_USB (@200022FC) класса USBSerial доступна во всех заданиях. Почему два параметра в USBSerial::begin игнорируются? На это отвечает сам автор, комментируя функцию USBSerial::begin: «Roger Clark. Two new begin functions has been added so that normal Arduino Sketches that use Serial.begin(xxx) will compile». Отталкиваясь от USBSerial::begin в коде, находим следующие функции: USBSerial::write(uint8 ch) = @0800B298 USBSerial::write(const char *str) = @0800B2AE USBSerial::read(uint8 * buf, uint32 len) = @0800B2C8 USBSerial::flush() = @0800B2E4 и т.д. Далее находим различные функции print, которые используют глобальную переменную g_USB, — пример ниже. Теперь можно побродить по местам вызова различных функций print и узнать, где происходит вывод флагов в консоль.  Довольно скоро мы нашли функцию print_flag (@08003824), которая выводит флаги в консоль. Оказалось, что флаги не хранятся в открытом виде.  Они декодируются через XOR с ключом, который создается в функции offzone_tasks_prepare. Как именно — показано ниже. Магический адрес 0x1FFFF7E8 — это Unique Device ID. Каждому микроконтроллеру STM32 присвоен уникальный серийный номер, который зашивается на заводе (с адреса 0x1FFFF7E8 длиной 12 байт) в область памяти, доступную только для чтения. Это означает, что его нельзя ни стереть, ни переписать, а только читать.   Получается g_key = <12 байт Unique Device ID> + «\x12\x34\x56\x78» (последние четыре байта являются константой). Закодированное значение флага  ксорится с g_key. Закодированное значение флага в прошивке разных бейджиков будет одним и тем же, а значение g_key у разных бейджиков — уникальным. Соответственно, и результат XOR тоже будет уникальным; другими словами, для каждого бейджа будет создаваться свой флаг. Следовательно, у организаторов должна храниться база данных с Unique Device ID всех бейджиков, чтобы отслеживать правильность флагов, отправляемых в Telegram-бот.  Функция print_flag вызывается только для трех задач: DIP Switch, OLED и 433 Radio. Значит, для этих задач можно получить закодированные значения флагов из прошивки: DIP Switch = «B5AF35B64A5FA717F5CC1A4D5C5F76A2» OLED = «442E59FE49270D77FDCEECC66FA8C572» 433 Radio = «4244590E91932549A918A9CD819DBAB9» Для задач IRDA и RFID флаги будут одинаковыми — их надо было выполнять на площадке конференции.  «Есть задача — реши ее».  Бигвелд, мультфильм «Роботы» (2005)  Кстати, о задачах. Далее приводятся цитаты из статьи «Текстолит вместо картона. Пара слов об интерактивном бейдже OFFZONE 2019».  «В первом задании нужно было выставить младшие четыре разряда переключателей в такую комбинацию, чтобы все четыре светодиода загорелись зеленым цветом. Как только это происходило, бейдж в USB через COM-порт отправлял участнику флаг.  Немного объяснений для тех, кто не успел в зону пайки на конференции и хочет повторить трюк дома. Старшие четыре разряда переключателя определяют то, в каком режиме запустится бейдж после рестарта. Положение этих переключателей нужно трактовать как представление двоичного числа: восьмой переключатель в положении ON — это 0001, седьмой ON — это 0010, восьмой и шестой ON — это 0101 и так далее. Таски имели номера от 1 до 5. Состояние 0 — режим кошелька OFFCOIN». (Орф. и пункт. автора здесь и далее сохранены. — Прим. ред.) Четыре разряда переключателя (SW4/5/6/7) отвечали за номер задания. Чтение разряда SW происходило с помощью функции digitalRead (@0800B328) в функции task_1_LED (@08003BE0). Номер задания всегда выводился в консоль. Четыре разряда переключателя (SW0/1/2/3) относились к заданию DIP Switch. Правильная комбинация этих разрядов приводила к выдаче флага — ну да, без знания прошивки только перебор всех возможных комбинаций дал бы решение. При правильном решении все огоньки зажигались зеленым цветом (@08003868). «Для второго задания нужно было смонтировать миниатюрный 0,96-дюймовый OLED-дисплей с интерфейсом I2C и пару перемычек… Запаяли, выбрали режим 2 на DIP Switch, перезапустили бейдж, дождались окончания процесса погрузки программы, прошли тест на эпилептика — и теперь можно играть во Flappy Quote. Цель таска — пройти 1337 ворот кавычкой и не умереть. Руками сделать это не так-то просто. Однако если подключиться по COM-порту к бейджу в режиме Flappy Quote, можно увидеть очень много полезной информации — ее хватит, чтобы написать своего бота! Цель в 1337 ворот уже не выглядит столь устрашающей». Функция task_2_OLED_Flappy (@08003E0C) отвечала за action-игру Flappy Quote. Здесь уже использовалась библиотека Adafruit_SSD1306 для OLED-дисплея. Для управления в игре была отдельная кнопка SW1 (стоит отметить, что эта кнопка применялась и в других заданиях). При нажатии на нее «кавычка» двигалась вверх.  Для примера покажем, как рисуется наш главный герой — «кавычка» — на OLED-экране по заданным координатам (функция drawMyObject @08003060). В отдельной глобальной константе (@0800DFB0 или @200000F8) хранится значение 1337 — это количество препятствий на пути «кавычки». В самом конце функции task_2_OLED_Flappy есть соответствующая проверка: а не все ли препятствия пройдены? Если препятствий не осталось, то отправляем флаг в консоль. Единственное решение — это написать свой бот, отправляя команды через консоль. Самый простой вариант в нашем случае, когда на руках есть прошивка, — заменить значение 1337 на что-нибудь попроще, хотя бы на нулевое значение, и обновить устройство. Тогда при старте игры мы увидим в консоли следующее: «…у нас было радио диапазона 433МГц и два модуля: приемник и передатчик. При помощи радио был реализован чат для всех владельцев этих модулей, а также поиск постоянно курсирующего туда-сюда источника передачи флага. Нужно было просто гулять по OFFZONE, глазеть по сторонам и ловить радиопакетики. Один из них и содержал искомый флаг». Для этого задания использовалась функция task_3_RADIO (@08004BE0). Задача участника — проще некуда: напаять периферию, гулять и ловить радиопакеты. Для анализа прошивки снова понадобится библиотека Adafruit_SSD1306 для OLED-дисплея и VirtualWire library for Arduino, а для поиска функций VirtualWire в дизассемблере лучше начать с vw_setup (@08002BE0). На схеме задействованы два GPIO (433 RX = vw_set_rx_pin / 433 TX =vw_set_tx_pin) для приема и отправки данных по радиоканалу. Библиотека VirtualWire передает каждое сообщение по следующему протоколу: 36 бит преамбулы (последовательность пар бит 0-1); 12 бит «начального символа» 0xB38; байт — длина сообщения N (от 4-х до 30 байт); N байт — содержимое сообщения; 2 байта контрольной суммы. При этом после «начального символа» каждая тетрада (4 бита) кодируется 6 битами. Следовательно, байт кодируется 12 битами. При отправке уходит сначала старшая тетрада, а затем младшая. Сообщения пользователей в чате соответствуют шаблону: «[имя_участника] текст». Сообщение, которое приводит к выдаче флага, выглядит следующим образом: Иначе говоря, при получении 4-байтного сообщения \x55\x66\x55\x66 в консоль отправится наш флаг. «…можно было напаять простой ИК-приемник TSOP38238, найти в зоне пайки установленный организаторами бейдж и принять посылку, которая там передается. Посылкой оказывался 7ZIP-архив, который содержал пожатую файловую систему FAT12. Нужно было покопаться в ней и найти флаг». Для этой задачи использовалась функция task_4_IRDA (@08003B74), а для приема инфракрасных сигналов настраивался встроенный таймер. Контроллер STM32F103х имеет четыре 16-битных таймера — и в этой задаче применялся таймер общего назначения TIM3.  Настройка таймера происходит в функции offzone_tasks_prepare. Для этого предделитель таймера устанавливается на 720 (72 МГц / 720 = 100 КГц, один «тик» равен 10 микросекундам). Источник сигнала для таймера TIM3 — это ИК-приемник, подключаемый к GPIO 6 (PA6 на схеме). Сам таймер TIM3 настраивается на захват периода (канал 1) и длины импульса сигнала (канал 2). В функции task_4_IRDA проверяется захваченная длина импульса, полученного по инфракрасному порту. В зависимости от его длины устанавливается бит либо в 0, либо в 1. Затем биты собираются в одно двойное слово в переменной irda_dword. Если значение в irda_dword равно 0x815B3E9C, то мы на правильном пути. Далее необходимо принять остальные данные пакета в функции parse_irda_data (@08003530). Функция parse_irda_data принимала данные от ИК-передатчика организаторов и выводила их в консоль. Как они выглядели, можно подсмотреть в решении AV1ct0r.  Дальнейшее решение уже никак не связано с бейджем — любопытные могут пройти этот путь самостоятельно. 7z = unpack data.bin = this is FAT12 = search delete file = hint “format: offzone{md5(some_string)}” = long file name = short name «SOME_S~1» = Md5(«SOME_S~1») = 6b74e4f9de525c8a22b84521fcbd66e8 offzone{6b74e4f9de525c8a22b84521fcbd66e8}  «Затем участники могли напаять RFID-приемопередатчик RC-522, в зоне с заданиями по взлому IoT-устройств найти помеченную карту-пропуск, сдампить ее, поковыряться в дампах, заметить некоторые интересные особенности в них, подкорректировать данные и записать на свою карту-болванку. Если все сделано правильно, проверка карты на IoT-зоне проходила успешно, и участник получал флаг». Для решения этой задачи использовалась функция task_5_RFID (@08006EE4), а для работы с RFID-приемопередатчиком (Radio-Frequency IDentification) — библиотека MFRC522, версия 1.4.3. На схеме модуль RC-522 подключался к контроллеру STM32 на ножки PB10-15. Инициализация модуля выполнялась в функции offzone_tasks_prepare (@080047B4).  Взаимодействие с RC-522 происходит через консоль. Доступны следующие команды: Опять же, поиграться с RFID можно было только на площадке конференции. Для этого организаторы мероприятия предоставляли специальную карту-пропуск и файл trace.txt, необходимый для восстановления ключа чтения блоков. Подсмотреть решение снова можно у AV1ct0r. На самом деле, это было не совсем задание: режим task0 использовали организаторы, чтобы проверить, сколько OFFCOIN накопилось для определенного бейджа. В действительности бейдж не хранит у себя никакие «койны». Это и не нужно: все флаги участники должны были отправлять в Telegram-бот, а организаторы, в свою очередь, — вести базу данных уникальных номеров бейджиков. Как оказалось, Unique Device ID — это единственное, что должен был передавать бейдж во внешний мир. Канал отправки сообщений — это инфракрасный передатчик (входит в состав базовой конфигурации бейджа), подключенный к ножке PB0. Для работы с IR-передатчиком используется библиотека irmp-master версии 3.0.7 (точнее функции, связанные с отправкой, — IRSND). В функции offzone_tasks_prepare настраиваются таймеры TIM2 и TIM3, а еще некая криптография — вопреки вашим ожиданиям, Unique Device ID не передается в открытом виде. Помимо этого, все светодиоды включаются изумрудным цветом, тем самым показывая пользователю, что бейдж находится в режиме task0. Таймер TIM3 настраивается для ножки PB0 — для таймера TIM3 это канал 3 — на генерацию сигналов ШИМ.  Таймер TIM2 настраивается на отправку данных по прерыванию, как показано ниже. Как же передается уникальный номер бейджика? Как хеш SHA1 от Unique Device ID.  Помимо криптосистемы SHA, в task0также применяется алгоритм симметричного шифрования AES (режим CBC). Скорее всего, использовались предкомпилированные криптографические библиотеки для чипов STM. Мы ориентировались на AESLib, ведь все AES-библиотеки похожи — алгоритм один и тот же. Функция task0 (@08003C28) формирует содержимое IR-сообщений (функция ir_send_payload @08003A68), которые отправляются в обработчике прерывания TIM2_IRQHandler (@08002EF8). Отправляемые сообщения: хеш уникального номера бейджа SHA1(UID) длиной 20 байт; шифрованный текст «YELLOW SUBMARIN» по алгоритму AES CBC длиной 16 байт;     инициализирующий вектор IV длиной 16 байт. Еще один момент: когда формируются отправляемые сообщения (функция ir_send_payload), выбирается протокол SAMSG48 (id = 41, Samsung48). Почему так сделано? Библиотека irmp в основном используется для обмена данными по ИК-каналу с телевизорами разных марок. Как таковые «сырые» данные не передаются — они должны быть в каком-то формате. Поэтому организаторы выбрали формат протокола SAMSG48, в котором каждое сообщение «оборачивается» заголовком, а данные передаются по 4 байта. Все приведенные выкладки по task0 построены на статическом анализе, а для проверки необходимо отдельное устройство для выуживания ИК-данных, отправляемых бейджем.  Одна из историй Кратоса, рассказанных им своему сыну. Сын: «Может, еще историю? Хуже предыдущей уж точно не будет». Кратос: «Одна из них была про зайца и черепаху». Сын: «И что было?» Кратос: «Заяц и черепаха решили бежать наперегонки. Заяц был глуп и не сомневался в победе, а черепаха была стойкой и дисциплинированной. Черепаха победила». Сын: «Ты... похоже, совсем не умеешь рассказывать». God Of War 4 При решении подобных задач зачастую есть два варианта достижения цели: Заячий путь, или путь пентестера: необходимо знать нужные инструменты и уметь их применять, в том числе в комбинации друг с другом. Быстрое решение — это быстрое закрытие проблемы, без уклона в детали.  Черепаший путь, или путь исследователя: необходимо глубоко разбираться в области и более досконально подходить к изучению проблемы, применяя при этом, возможно, не самые очевидные и стандартные способы решения.  Организаторами не предполагался черепаший путь решения задачи. Несмотря на это, мы выбрали именно этот путь и прошли его до конца. Время не стоит на месте, и даже хардварные защиты микроконтроллеров обходят и взламывают. Уязвимость, позволившая обойти опцию Read Out Protection и считать прошивку, стала основной причиной для написания этой статьи. Гораздо легче понять логику устройства, имея в своем распоряжении прошивку, хотя задачу это вовсе не упрощает. Обстоятельства так сложились, что почти через два года и совсем не за те пару дней, которые отводились на задания по бейджу на конференции, и — что ценно — совершенно не так, как было задумано организаторами, но мы расковыряли. Было интересно. Спасибо всем причастным! Авторы: Сергей Федонин и Владимир Назаров, Positive Technologies