Представляем вашему вниманию вторую часть статьи о реверс-инжиниринге прошивки устройства «Мигающий носорог» по мотивам мастер-класса на конференции SMARTRHINO-2018.
В первой части статьи прошивка устройства была загружена в дизассемблер IDA и выполнен первичный анализ команд протокола устройства. Отдельные команды были проверены на работающем устройстве.
Во второй части будет выполнен анализ оставшихся тасков прошивки.
Напомню, после анализа Bluetooth-таска в части управления светодиодами, было решено переключиться на LED-таск, так как исходная задача – создать приложение для управления светодиодами, а для этого необходимо детальное понимание работы прошивки.
Файл прошивки доступен для самостоятельного изучения.
Вся информация проводится исключительно в образовательных целях.
Под катом много мигающего носорога.
LED-таск
Кратко: полный разбор таска, отвечающего за переключение светодиодов. Анализ типов данных и глобальных переменных.
LED-таск представлен функцией x_leds_task, расположенной по адресу 0x08005A08
.
Помимо странных строк «I've got a super power… » в основной функции LED-таска можно обратить внимание на строку «hue > max: change shinern».
При этом видим знакомую ситуацию – (WORD *)(v26 + 4). В контекстном меню переменной v26 выбираем пункт «Convert to struct *», затем указываем созданную ранее структуру:
С учётом, что v5 = v26
, повторяем операцию «Convert to struct *» для переменной v5.
Продолжаем структурировать код и данные. Устанавливаем везде hex-представление. Переименовываем:
- v5 — led;
- v6 — idx;
- v8 — hue_1;
- v9 — hue_2;
- v26 — _led;
Код улучшается. Но некоторые переменные всё ещё режут глаз, например, переменная v23:
Назначаем тип char v23[4]
и переименовываем в leds_smth, код становится симпатичнее:
Можно также обратить внимание, что результат работы функции x_queue_recv возвращается в переменную v25:
x_queue_recv(&v25, leds_queue, 1000);
Но может быть непонятно, как нужные данные оказываются в структуре _led. Дело в том, что переменные v25 и _led расположены рядом в стеке — это можно понять по тому, что в декомпиле они написаны на соседних строках. Расположение переменных на стеке можно увидеть в отдельном окне, если дважды кликнуть на переменной:
Вероятно, они представляют собой структуру или же компилятор провёл оптимизацию. Таким образом, можно утверждать, что данные из Bluetooth-таска передаются в LED-таск. Чтобы узнать точнее, я выполню проверку на устройстве – для нулевого светодиода по Bluetooth отправлю значения 0x208, 0x2D0, 0x398, 0x3E9, которые можно было заметить в коде:
Результаты проверки значения hue (оттенок) на устройстве:
- 0x208 – светодиоды перестали плавно переключаться и установились в цвета: красный, зеленый, синий, фиолетовый;
- 0x2D0 – светодиоды стали снова переключаться;
- 0x398 – ничего не изменилось;
- 0x3E9 – ничего не изменилось.
Если снова посмотреть на код, то можно увидеть, что значение 0x398 может быть логически связано со значением меньше, чем 0x167 (устанавливаются разные значения для элемента массива leds_smth). Поэтому выполню такую проверку: сначала установлю первый светодиод в зелёный цвет (hue=0x78, команда LED 010078FF20
), при этом три других светодиода продолжают переключать свои цвета.
Теперь выполню команду Bluetooth-протокола LED 010398FFFF
– после этого первый светодиод перешёл в общий режим переключения цвета.
Таким образом, значение hue 0x398 сбрасывает статическое значение цвета, а это означает, что массив leds_smth содержит флаги (0 или 1) занятости светодиодов:
- 0 – светодиод не занят, участвует в плавном переключении цветов (hue = 0x398);
- 1 – светодиод занят, пользователь установил статический цвет (hue <= 0x167).
Переименуем leds_smth в leds_busy.
Теперь должно стать понятно назначение следующего блока кода:
Цикл в строках 83-101 осуществляет плавную цветную мозаику с шагом переключения цвета, равным 5: v12 += 5
. В случае, если на светодиоде включен статический цвет, то этот светодиод не участвует в мозаике. После цикла идут строки кратковременного включения всех светодиодов.
Переименуем:
- sub_800678A — x_led_set_hsv;
- v12 — hue_step;
- v13, v17, v18, v19 — led0_busy, led1_busy, led2_busy, led3_busy;
- v11, v20, v21, v22 — hue0, hue1, hue2, hue3;
- dword_200004C4 — led_control.
Функция sub_80039FE предположительно выполняет таймаут (иначе светодиоды переключались не плавно, а моментально), назовём её x_sleep, а переменную v16 – led_timeout.
Назначение функции sub_8006934 пока неочевидно, но она используется везде после установки цвета на светодиодах – можно назвать ее x_led_fix_color.
После этих переименований легко понять функцию sub_8006944 (вызывается в ветке «hue <= 0x167»):
Здесь просто выполняется дополнительная проверка для установления цвета светодиода. Переименуем функцию sub_8006944 в x_led_set_hsv_wrap (суффикс _wrap — пояснение, что это «обёртка» над другой функцией) и установим для неё следующий прототип:
signed int __fastcall x_led_set_hsv_wrap(int led_control, signed int idx, int hue, char sat, char val)
Вернёмся на уровень выше к функции x_leds_task. Ещё раз посмотрев на код, можно обнаружить, что ветка «hue > 0x3E8» стала выглядеть так:
То есть значение hue больше 0x3E8 должно менять таймаут цветной мозаики. Проверю, отправив на устройство некоторые значения:
- hue = 0x3E9 – светодиоды стали переключаться быстро:
- hue = 0xFFFF – светодиоды стали переключаться очень медленно:
При выходе из основного цикла LED-таска используется функция sub_8003C44, которая также используется в функции sub_8005070:
Переименуем:
- sub_8005070 — x_freeMsg;
- sub_8003C44 — x_free_queue.
Далее в LED-таске не может не обратить на себя внимание следующая ветка:
Можно попробовать выполнить команду LED B816D8D90000FFFF
. Но если вспомнить, что в качестве индекса светодиода берется всего 2 символа, попытка достичь данного кода будет заведомо неудачной. Оставим эту ветку «на потом». Функцию sub_8004AE8 переименую в x_mad_blinking, а также пришло время исправить сигнатуру функции x_printf (в прошлый раз записал неправильную сигнатуру):
void x_printf(const char *format, ...)
Основной цикл LED-таска разобран, но есть еще код в самом начале таска.
Посмотрим на код:
В строке 49, по всей видимости, проверяется доступность светодиодов и, в случае ошибки, происходит обращение к функции sub_8004BBС, которая выключает прерывания и запускает бесконечный цикл, в котором используется строка «../Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_gpio.c». Скорее всего, это assert или аналогичная функция.
Переименуем:
- sub_8004BBC — x_gpio_assert;
- sub_800698C — x_check_gpio.
Назначение функции sub_8006968 станет понятно, если внимательно посмотреть на устройство при включении:
Все четыре светодиода вместе включаются сначала красным, потом зелёным, потом синим. После этого устанавливаются по цветам: 0-красный, 1-зеленый, 2-синий, 3-фиолетовый. И только потом начинают переключаться мозаикой.
Поскольку мозаика запускается в основном цикле таска, то логично, что строки 58-61 перед главным циклом отвечают за кратковременное включение разных цветов на светодиодах, а строки 52-56 – за установку красного-зелёного-синего на всех светодиодах сразу. Переименуем функцию sub_8006968 в x_led_all_set_rgb (RGB – чисто по наитию, по передаваемым аргументам).
Странности в LED-таске
Кратко: определение функциональности кода, содержащего странные строки. Формирование пароля для устройства.
Теперь перейдём к самому началу функции x_leds_task:
«eraze», «gen», «flash», «reset» – зачем это всё???
Попробуем разобраться.
Пусть sub_80066BC будет x_leds_task_init.
Посмотрим на sub_8006B38:
Чистой воды memset, согласны?
Вернёмся к x_leds_task. Что-то не так с типом переменной v24:
IDA немного ошиблась с типом, но комментарий с отметкой стека нам помогает. Между переменными v24 и v25 целых 12 байт (0x44 – 0x38). Поэтому v24 переименовываем в buf и назначаем тип unsigned __int8 buf[12]
(Ида предупредит, что новый тип данных больше старого – соглашаемся).
Далее. Функция sub_8004CE4:
Переименовываем а1 в buf, v1 в _buf.
Функция sub_8006B26:
Узнали её?
Конечно, memcpy. Переименовываем.
Тогда назначение функции sub_8004CE4 состоит в получении каких-то данных по адресу 0x08007C00. Между прочим, этот адрес лежит в диапазоне адресов флэш-памяти микроконтроллера (и прошивки, в частности). Переименуем sub_8004CE4 в x_read_data_0x08007C00.
Функция x_leds_task, строка 36:
if ( (unsigned int)buf[0] - 65 > 0x19 )
Изменим отображение данных (клавиша R на числе 65, клавиша H на числе 0x19):
if ( (unsigned int)buf[0] - 'A' > 25 )
Немного поразмыслив, можно понять, что это такая проверка диапазона латиницы A-Z.
Далее, пользуясь подсказками в виде форматных строк, переименовываем:
- sub_8004C10 — x_erase;
- sub_80059C8 — x_gen;
- sub_8004C84 — x_flash.
Функция sub_8003C66 не делает ничего примечательного – только увеличивает некоторую глобальную переменную – переименуем sub_8003C66 в x_smth_inc.
Функция x_erase на самом деле не принимает никаких аргументов – в этом можно убедиться в дизассемблере:
Внутри x_erase используется знакомый нам адрес 0x08007C00 и происходит обращение к трём неизвестным функциям:
Бегло просмотрев эти три функции, увидим, что в них происходит обращение к адресам в диапазоне 0x40022000 — 0x400223FF. Документация на микроконтроллер совершенно четко говорит, что это диапазон «FLASH Interface». То есть функция x_erase стирает кусочек флэш-памяти — прекрасно!
По всей видимости, функция x_flash выполняет запись во флэш-память, предварительно проверив длину строки для записи (кстати, аргументы a2 и a3 тут лишние – поможем Иде):
И это всё происходит в «осветительном приборе»???
Хорошо, а что там с функцией x_gen? После беглого взора и переименования переменных она будет иметь такой вид:
При этом функция sub_8006CB4 выглядит вот так:
А sub_8006D10 – вот так:
Не сдерживайте желание выполнить поиск в интернете этих неприлично-красивых констант: 0xABCD, 0x1234, 0xE66D, 0xDEEC, 0x4C957F2D и 0x5851F42D. Если интернет еще не полностью запрещён, наверняка Вы найдёте эти константы в исходниках на random-функции. Не зря родительская функция называется x_gen.
Тут тоже весьма типичная ситуация: перед циклом вызвать srand(), а в цикле вызывать random(), поэтому переименуем:
- sub_8006D10 — x_rand;
- sub_8006CB4 — x_srand.
Пытливый читатель, заглянув в функцию sub_8005098, сможет узнать, откуда берется seed для функции srand.
Таким образом, функция x_gen формирует случайную строку, указанного размера.
После того, как сгенерированная строка записывается во флэш-память, происходит перезагрузка устройства:
Кажется, какая-то странная перезагрузка. Но если мы посмотрим на список тасков данного устройства, то обнаружим среди них «watchdogTask». Очевидно, при наличии «зависшего таска» watchdog выполняет перезагрузку.
LED-таск кроме режима MadBlinking можно считать проанализированным.
Посмотрим через строки, какие еще таски есть в системе:
Восстановив в коде ссылки на строки, можно увидеть вот такую картину:
Сначала идёт ссылка на строку с именем таска, потом ссылка на основную функцию таска. А используются они в функции main, где и происходит запуск этих тасков:
Выполним недостающие переименования:
- sub_80050FC — x_sensor_task;
- sub_8004AAC — x_watchdogTask;
- sub_8005440 — x_uartRxTask.
Watchdog-таск
Таск watchdog’а не делает ничего особенно интересного:
Переименуем:
- dword_200003F8 — wd_variable;
- sub_8001050 — x_update_wd_var.
UART-таск
Кратко: поиск данных и функций, имеющих ссылки из разных функций. Определение их назначения.
Беглый просмотр UART-таска позволяет обнаружить отправку данных в неизвестную пока очередь, определяемую переменной unk_200003EC:
Восстановив ссылки на эту переменную через бинарный поиск, увидим, что помимо x_uartRxTask она используется в main’е (там очередь создаётся, по всей видимости) и в неизвестной пока функции sub_80051EC:
Переименуем:
- sub_80051EC — x_recvMsg_uart_queue;
- unk_200003EC — uart_queue.
Смотрим кросс-ссылки на x_recvMsg_uart_queue:
- sub_8005250;
- x_bluetooth_task.
Посмотрим сначала функцию sub_8005250:
Подумав, переименуем:
- unk_2000034C — cmd_count;
- a1 — cmd;
- v4 — _cmd;
- v6 — rsp;
- sub_8005250 — x_bluetooth_cmd.
Посмотрим теперь, где ещё используется x_bluetooth_cmd. Все дополнительные ссылки только из Bluetooth-таска, самое время к нему вернуться.
Вернёмся к Bluetooth-таску
Кратко: окончательный разбор Bluetooth-таска. Поиск возможности авторизации без пароля.
Если посмотреть места, в которых используется функция sub_8006A84, а еще не полениться и заглянуть в её недра, то не останется сомнений – это calloc. Оно и логично – чтобы получать данные в буфер, надо этот буфер сначала создать.
Теперь sub_8006DBC. Посмотрим на неё (переменные уже переименованы):
Припомнив функции стандартной библиотеки С для работы со строками, увидим здесь strstr (поиск подстроки) и смело переименуем её.
Пройдемся по коду функции x_bluetooth_task – возможно с последнего посещения здесь что-то изменилось. По ходу именуем переменные:
- v2 — _state;
- v3 — data_len.
Тут же рядом есть функция sub_80052E2. По аналогии с функциями, вытаскивающими числа из Bluetooth-команды, она вытаскивает строку указанной длины — назовём ее x_get_str.
Продолжаем:
- v26 — isEcho;
- v6 — meow_str;
- v10 — uart_cmd_byte;
- v11 — uart_cmd_str;
- v12 — str_0;
- v13 — str_1;
- v14 — format_str;
- sub_8000F5C — x_blink_small_led.
Закончим с беглым переименованием:
- v19 — password; (так как рядом строки про авторизацию и пароль)
- sub_8004CC0 — x_check_password;
- sub_8006AF4 — x_free (так как password, cmd и bt_args являются указателями на динамические объекты (проверьте это!), то память должна освобождаться после их использования);
- sub_8006DAC — x_strcpy (проверьте это!).
Теперь исследуем ветки READ, WRIT, AUTP, SETP.
Как показала проверка на работающем устройстве, для команд READ, WRIT, SETP необходима авторизация. Попытка авторизации командой AUTP приводит нас в функцию x_check_password для проверки пароля:
Получается, что длина пароля должна быть 8 символов и пароль сравнивается (в функции sub_8006B08) с байтами по адресу 0x08007C00 – где хранится сгенерированная строка случайных символов A-Z.
Получается, не зная пароля, мы не можем авторизоваться на устройстве. Ну или почти не можем…
Обратим внимание на то, где используется переменная auth_flag:
Оказывается, она используется не только в Bluetooth-таске. А вот в Sensor-таск мы как раз еще не заглядывали. Идём туда.
Sensor-task
Кратко: что делает сенсорная кнопка?
В соответствии с лучшими практиками программирования вся функция Sensor-таска умещается в один IDA-экран. И это не может не радовать:
Строки-строки…
- «TSC %drn» — эта строка должна заставить задуматься про Touch sensing controller для микроконтроллеров STM32;
- «AUTH BTNrn» — кнопка авторизации???
- «SET AUTH %drn» — установка флага авторизации?
Посмотрим, как будет вести себя устройство, если нажимать сенсорную кнопку (все же поняли, что у носорога на ноге сенсорная кнопка?):
При кратковременном нажатии загорается красный небольшой светодиод. При длительном нажатии этот светодиод включается на продолжительное время.
Если соотнести это с кодом, то можно сделать предположение, что функция sub_8000708 – это функция получения текущего времени. Тогда, если разница между текущим временем и началом касания сенсора больше, чем 1000 (1 секунда), то светодиод включается на 0xEA60 милисекунд (1 минута). Но больший интерес представляет переменная auth_flag, которая устанавливается в 1 при длительном нажатии на сенсорную кнопку, открывая доступ злоумышленнику администратору «осветительного прибора» к привилегированным функциям.
Таким образом, проведя авторизацию «по кнопке» можно прочитать пароль, хранящийся в устройстве (команда READ), выполнить запись в ОЗУ (функция WRIT) или установить новый пароль (SETP).
Mad Blinking
Кратко: может ли быть выполнена странная ветка кода «Mad Blinking»?
Вернёмся к Bluetooth-таску и выполним еще несколько переименований.
- v21 — vip_smth (пока непонятно, что там);
- v22 — vip_str (строка неизвестного размера, извлекаемая из аргументов);
- v23 — mad_led — назначаем «Convert to struct *» и указываем struct_LED.
И тут видим число 0xB816D8D9 (оно встречалось в первой части статьи в Bluetooth-таске) в качестве индекса светодиода. Этот код будет выполнен, если выполнится проверка:
if ( sub_8005520(vip_str, vip_smth) == 0x46F70674 )
Переименуем sub_8005520 в x_vip_check и заглянем в неё:
Учитывая, что первый аргумент – это строка (по крайней мере, строка передается в эту функцию), то по данному коду получается, что второй аргумент – длина этой строки (или длина, которая должна быть обработана). Переименуем:
- a1 — str;
- a2 — len.
Посмотрим на функцию sub_8000254:
А теперь заглянем в sub_8000148. Вот её начало:
Это только треть функции… Мммм… Вкусняшка! Опытный копатель без труда увидит здесь...
Итак, берём исходник printf, далее смотрим vfprintf, сопоставляя её с кодом исследуемой прошивки. По исходному коду выходим на функцию itoa и делаем вывод, что функция sub_8000254 – это оператор оператор % (взятие остатка от деления), а эта страшная длинная функция ни что иное, как взятие целой части от деления (операция div).
Может возникнуть вполне законный вопрос – почему так? Дело в том, что операций DIV, MOD может не быть в конкретном микроконтроллере, поэтому компилятор вместо этих операторов подставляет вызов отдельных функций. Кстати, вот ещё какие бывают математические функции.
Не забываем выполнять переименования по ходу копания.
Таким образом, функция x_vip_check, вычисляет… А это и будет вашим домашним заданием.
Кстати, если выполнить правильную команду VIP , получим «носорога на дискотеке»:
Краткий отчет по прошивке
Прошивка устройства построена на базе операционной системы реального времени FreeRTOS. В системе имеются следующие таски:
- Bluetooth-таск. Обрабатывает команды, приходящие в текстовом виде по Bluetooth.
- LED-таск. Управляет цветными светодиодами в соответствии с Bluetooth-командами.
- Sensor-таск. Включает красный светодиод, позволяет выполнить кратковременную авторизацию без пароля на устройстве.
- UART-таск. Позволяет взаимодействовать с Bluetooth-модулем по внутреннему UART-порту (используется для инициализации Bluetooth).
- Watchdog-таск. Отслеживает зависание тасков.
При исследовании не учитывалась возможность читать данные из UART-порта (контакты Tx/GND).
Итоги
В ходе мастер-класса на конференции была разобрана только основная функциональность управления светодиодами. Самым активным участникам были подарены их подопытные «носороги».
На мой взгляд, из «носорога» получился достойный макет для учебного курса по обратной разработке и поиску уязвимостей. Особенностью макета может быть возможность менять прошивку сколько угодно раз, для каждого курса – своя прошивка. В отличие от разбора исполняемого файла, реверс прошивки позволяет лучше понять:
- как работать с IDA;
- принципы взаимодействия прошивки с устройством;
- принципы работы RTOS.
Большое спасибо всем дочитавшим до конца!
Автор: prusanov