Всем привет!
Исправления давно в проде, а конфа OFFZONE 2024, на которой я выступил с этим докладом, закончилась — пришло время и на Хабре рассказать об уязвимости, которую я нашёл в умном девайсе от VK под названием «Капсула Нео» (далее — «Капсула»). Бага оказалась критичной, с возможностью исполнять на колонке собственный код по сети (RCE).
О том, как мне удалось найти уязвимость, с чем пришлось столкнуться за время проекта и почему VK в итоге не виноваты, читайте под катом.
Старт проекта
![Капсулы различной степени "готовности" Капсулы различной степени "готовности"](https://habrastorage.org/getpro/habr/upload_files/c05/608/e60/c05608e607fcfe2228e763ea8905caad.png)
Эта история началась не совсем так, как другие мои домашние проекты, когда я сам выбираю цель для исследования. В этот раз задачка пришла от человека, отвечающего у нас в компании за программу Bug Bounty (далее — BB). Мол, тут у VK новая колонка выходит, идёт программа бета-тестирования, и нужны хорошие реверс-инженеры, умеющие ковырять железки. Когда прозвучали слова о «хороших реверс-инженерах», мы с ребятами из нашего отдела (далее — Лаба) моментально согласились. От нас потребовались никнеймы и почта для регистрации в программе BB и резервирования части железок под конкретное число исследователей.
Чуть позже этот чел принёс в Лабу по колонке на каждого, и мы с азартом принялись их изучать. Как вы понимаете, возиться с тряпочками, пластиком и прочей ерундой вокруг основной платы обычно совсем не хочется, поэтому в ход сразу пошли и дремель, и термофен — лишь бы те самые тряпочки с пластиком снять. Конечно, это всё шутка, и ничего такого не применялось, просто выделять целый абзац на то, чтобы рассказать про откручивание винтиков мне не хотелось.
![Куча БП от Капсул Куча БП от Капсул](https://habrastorage.org/getpro/habr/upload_files/b2c/19e/f57/b2c19ef57d50d886cf20c20077637274.jpg)
Так вот, когда доступ к основной плате «Капсулы» был получен, мы стали осматривать её на предмет того, что мы обычно ищем на рабочих проектах, а именно:
большие микросхемы (SoC, контроллеры),
флэш-память,
группы контактов (отладочные пины, пины с UART).
Основной камень оказался один: какой-то ноунейм BES2300
, с которым до этого никому из нас не приходилось работать. Флэшек на плате не нашлось, зато обнаружились целых три группы подозрительных контактных площадок. Именно с них мы и решили начать.
![Группы контактов Группы контактов](https://habrastorage.org/getpro/habr/upload_files/b07/657/df5/b07657df5106e5370d26a1e1d90cc370.png)
UART есть, а толку ноль
Самый простой способ определить, какие из множества пинов отвечают за отладочные/информационные сообщения, — это подключить максимальное их количество к логическому анализатору, включить целевой девайс, и посмотреть, что происходит. Если вы видите в окне анализатора что-то типа сплошных длинных полосок на одном из каналов, скорее всего вы нашли UART TX
. В нашем случае такой пин тоже нашёлся. И, так как он принадлежал к одной из тех групп по три пина в каждой, было сделано предположение, что соседние с ним — это земля (GND
) и UART RX
.
![Такого вида "полоски" и нужны Такого вида "полоски" и нужны](https://habrastorage.org/getpro/habr/upload_files/906/06e/380/90606e380b62ca62ce9805d70fd968bf.png)
Определившись с пинами UART и подключив их к USB/UART-свистку, мы открыли Putty с бодрейтом 115200 в надежде увидеть там осмысленный текст. Но конечно же, там нас ожидала каша. Лично я бы стал перебирать все возможные варианты, чтобы найти нужный бодрейт. Но умные люди в Лабе научили меня некоторой магии, как это можно сделать красивше.
![Скрин не из Putty, но "каша" там была такая же Скрин не из Putty, но "каша" там была такая же](https://habrastorage.org/getpro/habr/upload_files/aa4/703/2b9/aa47032b956da5e8306bd35c00b4e697.png)
Алгоритм поиска
В том же лог-анализаторе при выбранной максимально возможной частоте дискретизации необходимо точно так же записать всё, что ходит на пине
UART TX
. Далее в этом трафике необходимо найти минимальный по ширине импульс и привести его, например, к микросекундам. Пускай это будет значение8.64µs
. Затем необходимо единицу (1
) поделить на 8.64 и умножить полученное значение на1_000_000
(для микросекунд). В итоге получится значение, приблизительно равное целевому бодрейту (в нашем примере —115740
≈115200
).
![Максимальный зум на "полоску". Минимальный импульс Максимальный зум на "полоску". Минимальный импульс](https://habrastorage.org/getpro/habr/upload_files/a54/8f7/c78/a548f7c786a871c5fd609da2dc98f4ec.png)
В случае «Капсулы» итоговым бодрейтом оказалось значение 1_500_000
. Подключившись на такой скорости к колонке, мы наконец получили читаемый текст.
![Такой вот своеобразный спам Такой вот своеобразный спам](https://habrastorage.org/getpro/habr/upload_files/5f9/874/ef7/5f9874ef7376ce22faa2f027ff4d86ff.png)
Как полагается, что-либо выжать с UART в тот момент не удалось. На нажатия клавиш он не реагировал, а только беспощадно спамил.
В общем-то, на этом мы тогда и остановились. Работа, проекты — заниматься колонкой совсем не было времени. А потом я вообще стал удалёнщиком... и всё своё оборудование, которое я смог насобирать к моменту отлучения от офиса, я перевёз в другой город вместе со своей красавицей-кошкой Матильдой. Обосновался на лоджии, докупил всякого барахла — можно считать, собственная мини-Лаба у меня теперь есть.
![Мадам кошька Мадам кошька](https://habrastorage.org/getpro/habr/upload_files/5bb/4ff/e8c/5bb4ffe8c41758eaf831f268bfceb44b.jpg)
Возвращаясь к «Капсуле»
Так вот, переехав и более-менее разгрёбшись в череде проектов, я снова смог выкроить время, чтобы заняться колонкой. Правда, пока другой — саундбаром от Yamaha, про который я писал на Хабр цикл статей: раз и два. Рекомендую почитать ;)
Про японский саундбар я вспомнил не просто так. Дело в том, что пока я им занимался, я успел облюбовать новый софт для работы с Serial COM-портами — Serial Port Monitor от Electronic Team. В нём оказалась одна интересная настройка, позволяющая изменять либо вовсе убирать окончание строки, добавляемое к каждой отправляемой команде: \n
, \r
, \r\n
. Этого требовал саундбар — я это видел, так как мог изучать код обработчика команд.
![Диалоговое окно с настройкой окончания строк Диалоговое окно с настройкой окончания строк](https://habrastorage.org/getpro/habr/upload_files/946/c3e/9b9/946c3e9b94bde801a60f87b8825efd0a.png)
К сожалению, кода обработчика нажатий в UART для «Капсулы» у меня не было. Но отправить команду с нормальным окончанием строки мне таки удалось — я точно так же воспользовался Serial Port Monitor, в котором уже был настроен правильный перенос строки: \n
(Putty отправляет \r\n
по умолчанию).
Нажимаю я Enter, совершенно ни на что не надеясь, и вижу: реакция в консоли точно есть! А мужики-то не знают... На самом деле, я решил лабовским пока не рассказывать — нужно сначала всё изучить, что тут есть.
UART — всё-таки толк есть
Первой командой, которую я отправил в консоль, была help
. В ответе среди мусора, которым продолжала беспощадно спамить колонка, я получил следующий список команд:
![«Меню» help «Меню» help](https://habrastorage.org/getpro/habr/upload_files/ef7/0da/475/ef70da475924ebc2bf3ae942f5ff1ce8.png)
help
Тут тебе и чтение/запись памяти, и чтение/запись физических портов — красота! Дополнительно по команде AT+HELP
выдавался и второй список:
![«Меню» AT+HELP «Меню» AT+HELP](https://habrastorage.org/getpro/habr/upload_files/370/4b0/376/3704b03762df4b3bba705a8a199250b5.png)
AT+HELP
А что это тут у нас?! Кроме ещё одного варианта чтения/записи памяти, обнаружилась команда включения отладки по JTAG.
К этому моменту я не сделал ничего, чтобы запустить отладку на железке: пины не искал, JTAG не включал, возможности сдампить память устройства для последующего изучения тоже пока не имел. Поэтому, чтобы не переключаться с UART на разборки с отладкой, я решил попробовать сдампить прошивку прямо через консоль.
Потыкавшись командой md
в различные регионы памяти, я выяснил, что если региона нет, то железка ребутается, иначе выплёвывается дамп региона. Для комфортной работы я смастерил скрипт, который дампит нужные регионы и раскладывает по файликам.
К сожалению, из-за постоянного спама в консоль процесс обещал быть очень небыстрым. Оставив скрипт работать на ночь, я пошёл спать.
![Дамплю память 10 часов... Дамплю память 10 часов...](https://habrastorage.org/getpro/habr/upload_files/0c4/0ab/5c0/0c40ab5c05763304d51dbfdf4b11bac4.gif)
Изучение дампа прошивки
Наутро, выспавшись и получив дамп прошивки «Капсулы», я взялся его изучать: открыл «Иду», распихал по регионам, кое-как разобравшись с дублированием некоторых из них по разным адресам.
К счастью, в дампе виднелись строки, адреса и константы, а значит, что-то из этого можно было совать в поиск Github и «гуго́л». И там и там мне удалось обнаружить кусочки SDK от вендора BES для нужной мне BES2300
. К сожалению, на момент подготовки текста доклада выяснилось, что с гитхаба репозиторий был удалён. Тем не менее, был и второй ресурс, на котором данный SDK был почти в свободном доступе — CSDN. Это такой китайский файлообменник, на котором за китайскую же денежку ты можешь скачать много чего интересного.
![Нужный мне SDK Нужный мне SDK](https://habrastorage.org/getpro/habr/upload_files/bbe/b01/df2/bbeb01df26a35fb03b8eb5951cf1510a.png)
Карты «Мир» там не принимали, но, к счастью, в интернетах есть дяди, которые скачают нужный файлик за рубли (стоило это мне 1500). Файлик оказался чуть более полезным, чем с гитхаба, хоть и всё равно неполным. Тем не менее, нужные мне функции и строки там были — бери да изучай.
Но прежде чем уйти в дебри кода, я решил-таки определиться: чего искать-то вообще?
Модель злоумышленника
Что касается возможных векторов атаки, разгуляться я не мог. В условиях программы BB есть список разрешённых сообщений о проблемах, поэтому мне были доступны и интересны только следующие направления:
поиметь колонку, имея физический доступ (локальное исполнение кода);
поиметь колонку, используя сетевой доступ (удалённое выполнение кода).
Начать я решил с первого — с локального выполнения кода по UART.
Физический доступ. В одной из команд, доступных по UART
, мне удалось обнаружить уязвимость, которая будет очевидна всем, кто сталкивался с переполнениями на стеке.
![Вулна в функции AT+WSACONF Вулна в функции AT+WSACONF](https://habrastorage.org/getpro/habr/upload_files/24f/03f/4ba/24f03f4baea5ffae959a0e07aa4582b1.png)
AT+WSACONF
Я быстро накидал PoC и закинул его в консоль — ожидаемо, железка ребутнулась. Недолго думая, я решил сбацать свой первый в жизни репорт по программе BB: подробно описал, где находится уязвимая функция и как её эксплуатировать — и отправил на проверку.
Неожиданно — мой репорт не приняли. На скринах все детали.
![Мой первый репорт Мой первый репорт](https://habrastorage.org/getpro/habr/upload_files/2b2/bc5/13b/2b2bc513bfa09d30bcadcc7f97e2070c.png)
![Ответ на первый репорт Ответ на первый репорт](https://habrastorage.org/getpro/habr/upload_files/22f/e3c/ce8/22fe3cce8a81ab7d88ccece75f76c03d.png)
Расстроившись и не очень-то желая раскручивать такой вектор, я решил переключиться на второй вариант — сетевой.
Сетевой вектор. Для определения доступных сетевых портов я запустил nmap -p- 192.168.0.123
. Я всякое надеялся увидеть, но только не такое:
![Одинокий порт Одинокий порт](https://habrastorage.org/getpro/habr/upload_files/871/78a/abc/87178aabc9f89c93c5b210b5c19bd25e.png)
![А вообще, курить - вредно! А вообще, курить - вредно!](https://habrastorage.org/getpro/habr/upload_files/d96/1b6/d74/d961b6d746c3d36328585f9f219b741d.jpg)
На самом деле, баловаться с доступом к «Капсуле» по сети я начал ещё до того, как смог полноценно работать в консоли по UART
. Я видел открытый HTTP-порт и даже заходил на него через браузер. Там одна лишь страница настройки Wi-Fi на колонке. Уже не помню точную команду для привязки к сети, но это определённо был GET-запрос.
Так вот, однажды в ходе моих экспериментов я случайно воткнул в аргументы второй знак = («равно»). Получилось что-то типа:
http://192.168.0.123/setup?key1=value1&key2==value2
И тут «Капсула» почему-то перезагрузилась. Заглянув в консоль UART, я увидел портянку, которая выглядела как stacktrace после assert
-а.
![stacktrace stacktrace](https://habrastorage.org/getpro/habr/upload_files/672/e16/658/672e1665805dd327ca8847cf15807e6b.png)
Тут были видны и адреса в стеке, и имена работавших потоков. Затем колонка перезагружалась и начинала спамить мусорными сообщениями по новой. Я пробовал вставлять разное количество знаков «равно» в надежде изменить выводимый в UART трейс.
Так у меня сформировался такой вот список:
![Ловись, крэш, большой и маленький Ловись, крэш, большой и маленький](https://habrastorage.org/getpro/habr/upload_files/bfc/448/b20/bfc448b20f5597048e2533a00231f9b2.png)
То есть да, буквально: большой и маленький крэш, либо совсем без крэша. Такое нестабильное поведение натолкнуло меня на мысль: «А вдруг это можно раскрутить до RCE?».
Начальное понимание происходящего
Самое интересное, что среди мусора в логе была видна и реальная причина падения колонки: assert
на нечётное количество пар key=value
в запросе, к тому же с адресом памяти, где оно упало.
Теперь, когда появился дамп памяти и можно было смотреть на виновника падения, у меня стало формироваться некоторое представление о том, почему происходит assert
и почему в некоторых случаях крэшится без assert
(большой крэш).
По сути есть некая структура keys_dict_t
с максимальным количеством элементов key_value_t
в ней и счётчиком этих элементов. В какой-то момент структура переполняется, и происходит падение.
![Приблизительное представление о переполняемой структуре Приблизительное представление о переполняемой структуре](https://habrastorage.org/getpro/habr/upload_files/c0e/906/29a/c0e90629af50df796e5afb4654b75f10.png)
К сожалению, я пока не понимал, как это эксплуатировать. Ну есть крэш, ну и что? В статике мне сложно было понять, как это раскручивать.
![Отсылка на творчество Евы Морозовой Отсылка на творчество Евы Морозовой](https://habrastorage.org/getpro/habr/upload_files/542/b6a/39e/542b6a39eb6c9065297a0c00edcc720a.jpg)
Тогда я принял решение наконец-то разобраться с отладкой.
Чёртова отладка
Да, именно чёртова, если не сказать грубее. Хуже того, что мне пришлось испытать, мне ощущать в реверс-инжиниринге не доводилось. И вот почему...
![Снова картинка с группами пинов Снова картинка с группами пинов](https://habrastorage.org/getpro/habr/upload_files/1fa/5ee/3ce/1fa5ee3ce5bb6b91d1b71c8f9a9a5167.png)
Для начала отладку необходимо было завести, а для этого — понять, на какие пины она выведена. Тут как с UART не выйдет — придётся перебирать. Хорошо, что вариантов для брута немного — всего две группы по 3 пина. Чтобы определиться, что перебирать, нужно понять, с чем мы имеем дело: либо JTAG, либо SWD. Отладка могла быть вообще никуда не выведена, но раз уж есть команда UART на её включение, скорее всего подключиться физически я смогу. Да и название команды AT+JTAG
говорит о том, что нужно искать интерфейс JTAG.
Если отладка на устройстве реализована через JTAG, потребуется минимум 4 пина, а значит будут задействованы две группы. То, что они разнесены по плате, конечно, смущает, но всякое бывает. Отправляю команду на включение, пробую Jtagulator-ом набрутать правильное расположение пинов — безуспешно.
Более вероятный вариант: на колонке SWD. На него всего нужно два пина (Clock
и I/O
) и земля (GND
). Подсоединяю все 6 пинов, перебираю — тишина. Неужели нет отладки? Я отказывался это принимать, поэтому попробовал просто замерить мультиметром напряжение на этих 6 пинах после включения отладки командой из консоли.
И тут мне повезло: хоть и немного, но напряжение на двух пинах менялось. Будучи уверенным, что я нашёл нужные пины, и перепроверив изменение напряжения несколько раз, я стал перебирать их вручную, подключая к JLink то так, то эдак. В одном из положений подключение вроде бы происходило, но с кучей ошибок и предупреждений — так быть не должно.
![Говорит, мол, мало питалова Говорит, мол, мало питалова](https://habrastorage.org/getpro/habr/upload_files/837/828/50d/83782850dc4ebdda144ab31119d1af1a.png)
Я бы не назвал это успешным подключением. Забавно, что при каждой попытке подключиться ошибки были разные. Отлаживаться было просто невозможно!
Я настолько отчаялся, что даже рассказал товарищам из Лабы, как наконец отправил Enter, смог прочитать память, нашёл отладку. Потом мы долго смеялись над тем, что проект тупо не двигался из-за настройки переноса строки в софте. Бывает.
![Почти успешное подключение Почти успешное подключение](https://habrastorage.org/getpro/habr/upload_files/e48/eb4/1f4/e48eb41f4294489e81859d0425722344.png)
Так вот, я надеялся, что кто-то из товарищей попробует на такой же колонке точно так же подключиться по SWD, попробует отладиться, и у него всё получится. Не получилось.
Далее в ход пошли различные схемы из интернетов о том, что нужно подключить конденсатор, резистор определённых номиналов — мол, это сгладит какие-то там недоподтягивания до нормальных логических уровней. Но всё это точно так же не помогло.
![Умные схемы подключения SWD Умные схемы подключения SWD](https://habrastorage.org/getpro/habr/upload_files/066/1ef/e4d/0661efe4d9b8100d9c160a4e13baed1c.png)
SWD
Расследование, что называется, зашло в тупик. Бага не раскручивается — надо вернуться к анализу кода, авось чего другое найдётся. Спустя бесчисленное количество суток в «Иде» я наконец обратил внимание на команду, которая позволяет писать в физические адреса, а именно в управляющие регистры. Одним из регистров, с которым я решил поиграться, стал Watchdog
. Если он включён, на устройстве будут отрабатывать прерывания, переключаться потоки и тому подобное. Моя мысль заключалась в том, что, скорее всего, отладке мешает какой-то код, который переключает назначение SWD-пинов (GPIO) на другое.
Для проверки гипотезы я отправил в регистр управления Watchdog
значение для его остановки, после чего включил SWD. Случайно это получилось или я действительно что-то остановил, но впервые мне удалось нормально подключиться и прочитать память через JLink! Спойлер: получилось случайно :) Некоторое время я даже смог поотлаживаться, а потом снова всё сломалось.
Тем не менее, схема с некоторыми изменениями оказалась наиболее полезной:
Отключить
Watchdog
.Включить SWD.
Перейти к пункту 1 (повторять 100 раз).
![Практически рабочая схема, отвечаю! Практически рабочая схема, отвечаю!](https://habrastorage.org/getpro/habr/upload_files/cfc/506/1b2/cfc5061b2f6a8563954a60f3fd806f11.png)
И не спрашивайте у меня, как это работает, — я не знаю, отладка всё равно иногда отваливалась. Одно могу сказать точно: через пару дней таких упражнений я таки смог раскрутить найденную багу до RCE. Вот какие факторы к ней приводят:
![Собственно, по поводу структуры я был прав Собственно, по поводу структуры я был прав](https://habrastorage.org/getpro/habr/upload_files/14b/057/76b/14b05776b87f598495a84a44ad4b5d52.png)
На стеке хранится структура со словарём полученных пар
key=value
из URL-запроса. У структуры есть максимальный размерmax_count
.При переполнении словаря
key=value
URL-запроса происходит перезапись поля с количеством элементов словаряcount
.Команда логирования принимаемых запросов поднимает со стека столько указателей на пары ключ-значение, сколько указано в поле
count
, хотя на стеке их количество будет меньшим (и ограниченным размером структуры).Происходит перезапись значений регистров, включая адрес возврата
{R4-R11
,PC}
, значениями, которые я передаю.
На самом деле там есть множество других нюансов при формировании RCE-запроса: длина кода с полезной нагрузкой, количество знаков «равно», требуемых для выравнивания, количество пар key=value
. Но это всё детали. Оставалась самая малость — отправить репорт.
Репорт
![Описание уязвимости Описание уязвимости](https://habrastorage.org/getpro/habr/upload_files/6eb/720/7af/6eb7207af78c57c0d883df7951bae953.png)
![Steps to Reproduce Steps to Reproduce](https://habrastorage.org/getpro/habr/upload_files/fdb/bee/fe1/fdbbeefe12a7153f135530f4b5cddd62.png)
Как и ожидалось, отчёт, содержащий бинарную уязвимость, обрабатывали долго. Я был уверен, что мой репорт подтвердят, но не знал, как именно это происходит на платформе BB. Тут много нюансов:
Понимание разрабами того, как работают переполнения на стеке.
Качественный PoC, результат которого можно увидеть сразу.
Время на исправление бага.
Уже на первом пункте возникли трудности: далеко не все разработчики понимают, как переполняется стек. Да, многие знают, что нужно проверять размер памяти перед копированием, а вот что будет, если не проверять — нет. Но это ладно, ожидаемо.
Второй момент — скорее мой недочёт, хоть и не критичный: я сделал PoC, который ребутает колонку. С одной стороны, это наглядно. А с другой, если не смотреть на выхлоп в консоль, поведение девайса можно счесть за обычный крэш и последующий ребут. Поэтому мне написали с просьбой приложить ещё какое-нибудь подтверждение. Учитывая уже описанные сложности при формировании PoC, это потребует энного времени и отладки. Как бы то ни было, мне удалось сформировать такой запрос, который кирпичит колонку. Думаю, самое оно в качестве пруфа.
![Ничего лучше не придумал Ничего лучше не придумал](https://habrastorage.org/getpro/habr/upload_files/377/332/e1f/377332e1ff7480017bf6fdb9eb27523a.png)
Ну а третий момент, время — это скорее про ожидание, нервы: хватило ли последнего пруфа или нужно ещё, и тому подобное. В конце концов я получил ответ, что бага будет исправлена и я получу вознаграждение. И как раз 31 декабря оно пришло!
Выводы
Какие можно сделать выводы? Давайте порассуждаем:
Бага есть? Есть.
Багу внёс не ты? Не ты (не разработчик VK).
Тебе дали SDK сразу с багой? Да.
Проверять скомпилированный код и реверсить/изучать все линкуемые либы?
Да. Нет (смотреть чужой код на тысячи строк, или того хуже — реверсить его, лично я не стал бы просто так).
Итог: не весь код, который компилируется, зависит от тебя, но изучать его как-то нужно.
Спасибо за то, что дочитали до конца.
![Конец. Конец.](https://habrastorage.org/getpro/habr/upload_files/12f/1d2/f91/12f1d2f91ed29b67817f3c7be604d045.png)
Комментарии (3)
MaFrance351
11.09.2024 11:55Тем не менее, был и второй ресурс, на котором данный SDK был почти в свободном доступе — CSDN. Это такой китайский файлообменник, на котором за китайскую же денежку ты можешь скачать много чего интересного.
О, знакомо. Помню ещё какой-то китайский сайт, где скачал SDK для платёжного терминала. Там надо было загрузить несколько своих файлов с кодом, чтобы набрать очки и разблокировать доступ. Ну я и загрузил парочку каких-то скетчей для ардуино...
zhka
Спасибо! Люблю такие истории. Прочитал с удовольствием.