Главная » Хабрахабр » Реверс-инжиниринг прошивки устройства на примере мигающего «носорога». Часть 2

Реверс-инжиниринг прошивки устройства на примере мигающего «носорога». Часть 2

Представляем вашему вниманию вторую часть статьи о реверс-инжиниринге прошивки устройства «Мигающий носорог» по мотивам мастер-класса на конференции SMARTRHINO-2018.

Отдельные команды были проверены на работающем устройстве. В первой части статьи прошивка устройства была загружена в дизассемблер IDA и выполнен первичный анализ команд протокола устройства.

Во второй части будет выполнен анализ оставшихся тасков прошивки.

Напомню, после анализа Bluetooth-таска в части управления светодиодами, было решено переключиться на LED-таск, так как исходная задача – создать приложение для управления светодиодами, а для этого необходимо детальное понимание работы прошивки.

Файл прошивки доступен для самостоятельного изучения.

Вся информация приводится исключительно в образовательных целях.

Под катом много мигающего носорога.

LED-таск

Кратко: полный разбор таска, отвечающего за переключение светодиодов. Анализ типов данных и глобальных переменных.

LED-таск представлен функцией x_leds_task, расположенной по адресу 0x08005A08.

Помимо странных строк «I've got a super power… » в основной функции LED-таска можно обратить внимание на строку «hue > max: change shine\r\n».

В контекстном меню переменной v26 выбираем пункт «Convert to struct *», затем указываем созданную ранее структуру: При этом видим знакомую ситуацию – (WORD *)(v26 + 4).

С учётом, что v5 = v26, повторяем операцию «Convert to struct *» для переменной v5.

Устанавливаем везде hex-представление. Продолжаем структурировать код и данные. Переименовываем:

  • v5 — led;
  • v6 — idx;
  • v8 — hue_1;
  • v9 — hue_2;
  • v26 — _led;

Код улучшается. Но некоторые переменные всё ещё режут глаз, например, переменная v23:

По всей видимости, v23 - это массив из 4 байт.

idx – это индекс светодиода; этот индекс добавляется к базовому адресу; таким образом обращение ведется к элементам по одинаковым смещениям – так ведут себя массивы.

Назначаем тип 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-таске не может не обратить на себя внимание следующая ветка:

Но если вспомнить, что в качестве индекса светодиода берется всего 2 символа, попытка достичь данного кода будет заведомо неудачной. Можно попробовать выполнить команду LED B816D8D90000FFFF. Функцию sub_8004AE8 переименую в x_mad_blinking, а также пришло время исправить сигнатуру функции x_printf (в прошлый раз записал неправильную сигнатуру): Оставим эту ветку «на потом».

void x_printf(const char *format, ...)

Основной цикл LED-таска разобран, но есть еще код в самом начале таска.

Посмотрим на код:

Скорее всего, это assert или аналогичная функция. В строке 49, по всей видимости, проверяется доступность светодиодов и, в случае ошибки, происходит обращение к функции sub_8004BBС, которая выключает прерывания и запускает бесконечный цикл, в котором используется строка «../Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_gpio.c».

Переименуем:

  • sub_8004BBC — x_gpio_assert;
  • sub_800698C — x_check_gpio.

Назначение функции sub_8006968 станет понятно, если внимательно посмотреть на устройство при включении:

Все четыре светодиода вместе включаются сначала красным, потом зелёным, потом синим. После этого устанавливаются по цветам: 0-красный, 1-зеленый, 2-синий, 3-фиолетовый. И только потом начинают переключаться мозаикой.

Переименуем функцию sub_8006968 в x_led_all_set_rgb (RGB – чисто по наитию, по передаваемым аргументам). Поскольку мозаика запускается в основном цикле таска, то логично, что строки 58-61 перед главным циклом отвечают за кратковременное включение разных цветов на светодиодах, а строки 52-56 – за установку красного-зелёного-синего на всех светодиодах сразу.

Странности в LED-таске

Кратко: определение функциональности кода, содержащего странные строки. Формирование пароля для устройства.

Теперь перейдём к самому началу функции x_leds_task:

«eraze», «gen», «flash», «reset» – зачем это всё???

Попробуем разобраться.

Пусть sub_80066BC будет x_leds_task_init.

Посмотрим на sub_8006B38:

Чистой воды memset, согласны?

Что-то не так с типом переменной v24: Вернёмся к x_leds_task.

Между переменными v24 и v25 целых 12 байт (0x44 – 0x38). IDA немного ошиблась с типом, но комментарий с отметкой стека нам помогает. Поэтому 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 и происходит обращение к трём неизвестным функциям:

Документация на микроконтроллер совершенно четко говорит, что это диапазон «FLASH Interface». Бегло просмотрев эти три функции, увидим, что в них происходит обращение к адресам в диапазоне 0x40022000 — 0x400223FF. То есть функция x_erase стирает кусочек флэш-памяти — прекрасно!

По всей видимости, функция x_flash выполняет запись во флэш-память, предварительно проверив длину строки для записи (кстати, аргументы a2 и a3 тут лишние – поможем Иде):

И это всё происходит в «осветительном приборе»???

После беглого взора и переименования переменных она будет иметь такой вид: Хорошо, а что там с функцией x_gen?

При этом функция sub_8006CB4 выглядит вот так:

А sub_8006D10 – вот так:

Если интернет еще не полностью запрещён, наверняка Вы найдёте эти константы в исходниках на random-функции. Не сдерживайте желание выполнить поиск в интернете этих неприлично-красивых констант: 0xABCD, 0x1234, 0xE66D, 0xDEEC, 0x4C957F2D и 0x5851F42D. Не зря родительская функция называется 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.

Попытка авторизации командой AUTP приводит нас в функцию x_check_password для проверки пароля: Как показала проверка на работающем устройстве, для команд READ, WRIT, SETP необходима авторизация.

Получается, что длина пароля должна быть 8 символов и пароль сравнивается (в функции sub_8006B08) с байтами по адресу 0x08007C00 – где хранится сгенерированная строка случайных символов A-Z.

Ну или почти не можем… Получается, не зная пароля, мы не можем авторизоваться на устройстве.

Обратим внимание на то, где используется переменная auth_flag:

А вот в Sensor-таск мы как раз еще не заглядывали. Оказывается, она используется не только в Bluetooth-таске. Идём туда.

Sensor-task

Кратко: что делает сенсорная кнопка?

И это не может не радовать: В соответствии с лучшими практиками программирования вся функция Sensor-таска умещается в один IDA-экран.

Строки-строки…

  • «TSC %d\r\n» — эта строка должна заставить задуматься про Touch sensing controller для микроконтроллеров STM32;
  • «AUTH BTN\r\n» — кнопка авторизации???
  • «SET AUTH %d\r\n» — установка флага авторизации?

Посмотрим, как будет вести себя устройство, если нажимать сенсорную кнопку (все же поняли, что у носорога на ноге сенсорная кнопка?):

При длительном нажатии этот светодиод включается на продолжительное время. При кратковременном нажатии загорается красный небольшой светодиод.

Тогда, если разница между текущим временем и началом касания сенсора больше, чем 1000 (1 секунда), то светодиод включается на 0xEA60 милисекунд (1 минута). Если соотнести это с кодом, то можно сделать предположение, что функция sub_8000708 – это функция получения текущего времени. Но больший интерес представляет переменная 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.

Опытный копатель без труда увидит здесь… Это только треть функции… Мммм… Вкусняшка!

Что?

операцию целочисленного деления.

Как это можно раскопать?

Если приложить усилия, то от функции sub_8000254 можно добраться до x_printf (через несколько других функций). В этом месте стоит сделать важное замечание – обычно все стандартные функции достаточно стандартны. Это значит, что можно попробовать найти в открытом доступе хоть какой-нибудь исходный код исследуемой функции, чтобы исследование было более продуктивным.

По исходному коду выходим на функцию itoa и делаем вывод, что функция sub_8000254 – это оператор оператор % (взятие остатка от деления), а эта страшная длинная функция ни что иное, как взятие целой части от деления (операция div).
Итак, берём исходник printf, далее смотрим vfprintf, сопоставляя её с кодом исследуемой прошивки.

Может возникнуть вполне законный вопрос – почему так? Дело в том, что операций DIV, MOD может не быть в конкретном микроконтроллере, поэтому компилятор вместо этих операторов подставляет вызов отдельных функций. Кстати, вот ещё какие бывают математические функции.

Не забываем выполнять переименования по ходу копания.

Таким образом, функция x_vip_check, вычисляет… А это и будет вашим домашним заданием.

Кстати, если выполнить правильную команду VIP , получим «носорога на дискотеке»:

Краткий отчет по прошивке

Прошивка устройства построена на базе операционной системы реального времени FreeRTOS. В системе имеются следующие таски:

  1. Bluetooth-таск. Обрабатывает команды, приходящие в текстовом виде по Bluetooth.
  2. LED-таск. Управляет цветными светодиодами в соответствии с Bluetooth-командами.
  3. Sensor-таск. Включает красный светодиод, позволяет выполнить кратковременную авторизацию без пароля на устройстве.
  4. UART-таск. Позволяет взаимодействовать с Bluetooth-модулем по внутреннему UART-порту (используется для инициализации Bluetooth).
  5. Watchdog-таск. Отслеживает зависание тасков.

При исследовании не учитывалась возможность читать данные из UART-порта (контакты Tx/GND).

Итоги

В ходе мастер-класса на конференции была разобрана только основная функциональность управления светодиодами. Самым активным участникам были подарены их подопытные «носороги».

Особенностью макета может быть возможность менять прошивку сколько угодно раз, для каждого курса – своя прошивка. На мой взгляд, из «носорога» получился достойный макет для учебного курса по обратной разработке и поиску уязвимостей. В отличие от разбора исполняемого файла, реверс прошивки позволяет лучше понять:

  • как работать с IDA;
  • принципы взаимодействия прошивки с устройством;
  • принципы работы RTOS.

Большое спасибо всем дочитавшим до конца!


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

Частичка программы HolyJS 2018 Moscow

Конференция состоится 24–25 ноября. HolyJS 2018 Moscow уже совсем скоро. В этот раз программа получилась весьма разнообразной, однако несложно выделить главные тенденции: Доклады из первых рук (#firsthand) — доклады о инструментах/решениях от их авторов. Мы особенно тщательно подошли к выбору ...

[Перевод] GPU консоли Nintendo DS и его интересные особенности

Я хотел бы рассказать вам о работе GPU консоли Nintendo DS, об его отличиях от современных GPU, а также выразить своё мнение о том, почему использование Vulkan вместо OpenGL в эмуляторах не принесёт никаких преимуществ. Это может пригодиться для эмуляции ...