Глитчи глитчами, дамп дампом, но It's Time to End Information Anarchy

В этом году мы провели уже седьмую конференцию OFFZONE. Кроме того что на площадке можно послушать крутые технические доклады, каждый год здесь проводится множество активностей, в которых посетители могут поучаствовать и выиграть внутреннюю валюту конференции — офкоины. Несколько задач лежат прямо в бейдже каждого участника и доступны в любой момент, достаточно подключить бейдж к ноутбуку по USB. В этом году количество сданных флагов на самый сложный таск превысило наши самые смелые оценки. На это есть две причины: резкий скачок в развитии LLM и уязвимость в сторонней зависимости внутри нашей прошивки — она позволяла прочитать флаги ко всем заданиям без их решения. В этой статье мы раскроем технические детали уязвимости и расскажем, как работал эксплоит участников, который позволил им в сумме получить 91 тысячу офкоинов.
Этот материал мы подготовили еще в конце августа, но опубликовать решили только сейчас. Исследователи Иван Зорин и Евгений Пусан уже выступили с близким к статье докладом «Как отреверсить конференцию: от 11 флагов на бейдже до RCE по USB» на OFFZONE и готовили продолжение к ZeroNights — «Глитчи глитчами, дамп дампом, а все уровни RDP заканчиваются, когда бага на USB-стеке в прошивке попадается». Авторы проделали большую работу, и мы не хотим публиковать свой разбор раньше их доклада, поэтому технический анализ уязвимости в USB-стеке выходит сразу после ZeroNights.
CVE-2021-38541
В 2025 году мы уже рассказывали, как работает наша прошивка, но повторим здесь основные моменты. Наш бейдж работает на микроконтроллере STM32F103RET6, а для работы с USB мы используем HAL-библиотеку USB CDC, поставляемую вместе с STM32CubeIDE. В процессе анализа эксплуатации уязвимости, которая тогда была нам неизвестна, мы наткнулись на интересный issue в GitHub — репозитории библиотеки от 12 июня 2022 года.

Исследователь szymonh пишет, что в 2021 году обнаружил уязвимость переполнения буфера в USB CDC, для которой зарезервирован номер CVE-2021-38541. Он отрепортил ее в STMicroelectronics, и она была исправлена в версии 2.8.0. Но патч не портировали в библиотеки для микроконтроллеров, поставляемые вместе с STM32CubeIDE. В результате разработчики до сих пор выпускают уязвимые версии прошивок. С некоторой регулярностью szymonh возвращается в issue и спрашивает, когда же патч наконец портируют. По его словам, на письма с официальной почты служба безопасности продуктов STMicroelectronics не отвечает.
Так продолжалось до 11 мая 2023 года, когда сотрудник службы поддержки клиентов STMicroelectronics Ali Labbene все-таки ответил исследователю. Вендор не планирует переносить патч в библиотеки для STM32Cube, а разработчики, которые хотят выпускать безопасные прошивки, должны сами установить себе последнюю версию из главной ветки репозитория.

Да, вы все верно поняли. В мире сейчас множество микроконтроллеров STM32, использующих уязвимую библиотеку для работы с USB, так как уязвимость обладает свойствами уязвимостей сразу двух типов: нулевого и первого дня. Она одновременно всем известна и при этом почти нигде не запатчена. Кстати, STMicroelectronics все еще не опубликовала CVE-2021-38541: на сайте cve.org она отмечена как зарезервированная. Рассмотрим технические детали этой уязвимости.
Технические детали
При отправке с хоста SETUP-пакета входящий запрос обрабатывает функция USBD_CDC_Setup:
586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 |
|
Здесь видно, что функциям Control и USBD_CtlPrepareRx передается нигде перед этим не проверенный req->wLength типа uint16_t и указатель на массив hcdc->data. Рассмотрим путь на чтение данных с хоста, функцию USBD_CtlPrepareRx.
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 |
|
Эта функция также не проводит никаких проверок и сразу начинает чтение данных по переданному указателю pbuf. В нашем случае он указывает в поле data структуры USBD_CDC_HandleTypeDef:
#define CDC_DATA_HS_MAX_PACKET_SIZE 512U /* Endpoint IN & OUT Packet size */
typedef struct
{
uint32_t data[CDC_DATA_HS_MAX_PACKET_SIZE / 4U]; /* Force 32bits alignment */
uint8_t CmdOpCode;
uint8_t CmdLength;
uint8_t *RxBuffer;
uint8_t *TxBuffer;
uint32_t RxLength;
uint32_t TxLength;
__IO uint32_t TxState;
__IO uint32_t RxState;
} USBD_CDC_HandleTypeDef;
Таким образом, размер data составляет 512 байт. Однако вспомним, что переданная с хоста длина хранится в переменной типа uint16_t, а это означает, что она может достигать 0xffff (65535). Мы можем передать количество данных, превышающее размер массива, — это уязвимость переполнения буфера.
Эксплуатация уязвимости
Еще перед тем, как мы получили от сообщества несколько образцов использованных эксплоитов, мы разработали свой. Они отличаются в деталях, но используемая уязвимость и метод эксплуатации полностью совпадают.
В структуре USBD_CDC_HandleTypeDef, как указано выше, после поля data лежат указатели на RxBuffer и TxBuffer. В RxBuffer будут записываться данные, полученные после успешно установленного соединения. Мы можем перенаправить этот указатель и таким образом получить примитив arbitrary write. Но куда нам лучше записать данные, чтобы получить исполнение произвольного кода? Здесь мы снова можем обратиться к статье о внутренностях нашего бейджа, которую мы публиковали в 2025 году.
Все CLI-команды в бейдже реализованы в структурах следующего вида:

Мы можем переписать указатель pfnExec для любой из команд на контролируемый нами шелл-код и получить исполнение кода при выполнении этой команды. Здесь очень удобно, что переполняемый нами массив лежит в регионе SRAM, на который установлены права RWX. То есть мы можем прыгнуть ровно на hcdc->data, место которого мы точно знаем, ведь ASLR отсутствует. Осталось только найти функцию, которая расшифрует и выведет нам все флаги, и вызвать ее. По строкам искать ее очень просто:
int __fastcall sub_800E218(int a1)
{
int v2; // r0
const char *v3; // r4
v2 = sub_801D688(128, 1);
if ( !v2 )
return sub_8023B90("\r\nError getting flag\r");
v3 = (const char *)v2;
sub_8023B90("\r\nPlease wait...\r");
if ( sub_800E1A8(a1, v3) == 1 )
sub_8023A48("\r\nHere is your flag: %s\r\n", v3);
else
sub_8023B90("\r\nSomething is wrong with flag\r");
return sub_801D6BC(v3);
}
Эта функция принимает индекс флага. Для эксплуатации нам нужно вызвать ее из шелл-кода в цикле 8 раз — именно столько флагов хранилось в бейдже.

Эксплуатация уязвимости участниками OFFZONE 2026
В первый день несколько участников сдампили прошивку при помощи уязвимости семейства микроконтроллеров STM32F1. Затем они отдали полученную прошивку LLM-агентам Codex и Claude, те обнаружили CVE-2021-38541 и собрали эксплоит для получения всех флагов.
Отметим, что мы специально выбрали именно этот MCU, зная про его уязвимость к дампу прошивки через глитчинг. Мы поощряем усилия технических специалистов, которые умеют использовать глитчинг и проводить реверс-инжиниринг прошивки. То, что это сделали участники в этом году, очень круто.
При этом дамп лишь способ добраться до содержимого, а ценность представляет найденная внутри уязвимость. Именно она требует ответственного разглашения. Мы выступаем за эту политику и призываем к тому же сообщество исследователей и посетителей OFFZONE. Если бы исследователи обратились к нам с обнаруженной уязвимостью, мы бы, без сомнения, наградили их.
К сожалению, эксплоит разошелся среди участников до окончания дня: достаточно было подключить бейдж к ноутбуку по USB, чтобы получить все флаги уже без глитчинга. Это обрушило экономику первого дня и вынудило нас закрыть прием флагов по бейджу в 15:31:20. Всего самый сложный флаг, недоступный без дампа прошивки, сдал 51 посетитель.
Мы разобрали эту атаку по шагам и учтем выводы в прошивке следующего года. Тем, кто взломает бейдж и ответственно сообщит нам о находке, дадим приятный бонус. До встречи на OFFZONE 2027!
Завершим эту статью так, как в 2001 году свое эссе It's Time to End Information Anarchy об ответственном раскрытии уязвимостей завершил Скотт Калп, сотрудник Microsoft Security Response Center: «It's time for the security community to get on the right side of this issue».
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.