BiZone_team56 минут назад
Глитчи глитчами, дамп дампом, но It's Time to End Information Anarchy
Уровень сложностиПростойВремя на прочтение8 минОхват и читатели1.7KБлог компании BI.ZONEОбзорВ этом году мы провели уже седьмую конференцию 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
622static uint8_t USBD_CDC_Setup(USBD_HandleTypeDef *pdev,
USBD_SetupReqTypedef *req)
{
USBD_CDC_HandleTypeDef hcdc = (USBD_CDC_HandleTypeDef)pdev->pClassData;
uint16_t len;
uint8_t ifalt = 0U;
uint16_t status_info = 0U;
USBD_StatusTypeDef ret = USBD_OK;
if (hcdc == NULL)
{
return (uint8_t)USBD_FAIL;
}
switch (req->bmRequest & USB_REQ_TYPE_MASK)
{
case USB_REQ_TYPE_CLASS:
if (req->wLength != 0U)
{
if ((req->bmRequest & 0x80U) != 0U)
{
((USBD_CDC_ItfTypeDef *)pdev->pUserData)->Control(req->bRequest,
(uint8_t *)hcdc->data,
req->wLength);
len = MIN(CDC_REQ_MAX_DATA_SIZE, req->wLength);
(void)USBD_CtlSendData(pdev, (uint8_t *)hcdc->data, len);
}
else
{
hcdc->CmdOpCode = req->bRequest;
hcdc->CmdLength = (uint8_t)req->wLength;
(void)USBD_CtlPrepareRx(pdev, (uint8_t *)hcdc->data, req->wLength);
}
}
...Здесь видно, что функциям 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/**
* @brief USBD_CtlPrepareRx
* receive data on the ctl pipe
* @param pdev: device instance
* @param buff: pointer to data buffer
* @param len: length of data to be received
* @retval status
*/
USBD_StatusTypeDef USBD_CtlPrepareRx(USBD_HandleTypeDef *pdev,
uint8_t *pbuf, uint32_t len)
{
/* Set EP0 State */
pdev->ep0_state = USBD_EP0_DATA_OUT;
pdev->ep_out[0].total_length = len;
#ifdef USBD_AVOID_PACKET_SPLIT_MPS
pdev->ep_out[0].rem_length = 0U;
#else
pdev->ep_out[0].rem_length = len;
#endif
/* Start the transfer */
(void)USBD_LL_PrepareReceive(pdev, 0U, pbuf, len);
return USBD_OK;
}Эта функция также не проводит никаких проверок и сразу начинает чтение данных по переданному указателю 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».Теги:• cve
• offzoneХабы:• Блог компании BI.ZONE
Получайте больше инсайтов о систематизации бизнеса
Подписывайтесь на Telegram-канал Business Operations — ежедневные материалы о бизнес-процессах, операционном управлении и повышении эффективности
💬 Подписаться на канал→ Оригинальная статья