26 апреля 2018 года компания ИНФОРИОН провела конференцию для студентов МГТУ им. Баумана SMARTRHINO-2018. Специально для конференции было подготовлено небольшое устройство на базе микроконтроллера STM32F042.

Этот носорожек стал подопытным главным героем мастер-класса по реверсу прошивки. К сожалению, за время, отведенное на мастер-класс, не представлялось возможным провести полное исследование прошивки, поэтому мы решили восполнить это подробным разбором в формате статьи. Надеемся, информация будет полезна не только участникам конференции, но также всем начинающим кодокопателям.

Первая часть статьи составлена по мотивам проведённого мастер-класса и рассчитана на начинающих — уделяется внимание базовым подходам к реверсу прошивок и особенностям работы с дизассемблером IDA. Вторая часть немного сложнее, в ней уделяется внимание особенностям работы устройств на базе операционных систем реального времени.

Осторожно, под катом мигающий носорог и его прошивка!

Легенда


Участникам семинара была предложена следующая легенда.

Вам досталось устройство и небольшая инструкция к нему.

Прибор осветительный «Носорог»


Инструкция по эксплуатации

Прибор осветительный «Носорог» предназначен для освещения помещений небольшой площади. Прибор совмещает в себе стильный компактный дизайн, яркие светодиоды с низким потреблением тока и USB-интерфейс для подключения питания.

Устройство оснащено Bluetooth-модулем для удаленного управления. Предоставляются широкие возможности для контроля освещенности, позволяющие задавать оттенок и насыщенность для каждого светодиода в отдельности.

Управление устройством осуществляется через специальное программное обеспечение «Синезубик».

Приятного использования!

Упомянутого программного обеспечения для управления устройством у вас нет и необходимо написать его с нуля. Помимо этого необходимо удостовериться в безопасности использования данного устройства.

То есть всё, что есть у исследователя — устройство, которое можно включить. Если есть устройство, то можно попытаться получить его прошивку, вычитав её из flash-накопителя микроконтроллера. Этот этап был пропущен для упрощения и ускорения мастер-класса — участники получили готовый образ прошивки в виде бинарного файла rhino_fw42k6.bin (как если бы они получили прошивку, например, из обновлений).

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

Мастер-класс проходил в интерактивном режиме – с возможностью спрашивать, предлагать свои пути решения. Для участников было доступно 4 рабочих «Носорога».

Внешний осмотр


Кратко: на этом этапе производится внешний осмотр устройства с целью поиска маркировок, доступных разъемов.

В начале семинара был сделан акцент на то, чтобы сначала внешне изучить устройство, потом уже приступать к реверсу прошивки.

В первую очередь, интересует микроконтроллер, потом периферийные устройства и разъёмы.

Внешний осмотр устройства позволил установить следующее:

  • Микроконтроллер STM32F042 – тут сразу стоит обратиться к документации на микроконтроллер (если такая есть), откуда можно узнать архитектуру, разрядность микроконтроллера и много чего полезного (для нашего случая – 32 разрядный микроконтроллер на архитектуре ARM);

  • На тыльной стороне имеется разъем без обозначений – те, кто работал с микроконтроллерами, могут сделать верное предположение, что это разъем для прошивки устройства (во-первых, он не промаркирован; во-вторых, он имеет 5 контактов, что соответствует необходимому количеству контактов для перешивки микроконтроллера);

  • Контакты GND, TX;
  • USB-разъем для питания устройства (об этом говорится и в «Инструкции»);
  • Неизвестный разъем XP2 на лицевой стороне устройства;

  • Непонятная желтая блямба на ноге носорога – вероятно, сенсорная кнопка.


Самые шустрые участники сразу же подключили питание устройствам и увидели следующее:


Также было обнаружено, что появились доступные Bluetooth-устройства c именами RHINOCEROS-220x, при подключении к которым в системе создаётся виртуальный COM-порт. Оказалось удобным подключаться к устройству по Bluetooth со смартфона и взаимодействовать через мобильное приложение «Serial Bluetooth Terminal» или аналогичное.

Было установлено, что при отправке в COM-порт произвольного текста устройство возвращает ответ Unknown command.

Начальное исследование прошивки


Кратко: на этом этапе выполняется предварительный анализ прошивки. Просмотр строк. Загрузка прошивки в IDA Pro.

Перед разбором кода прошивки имеет смысл проверить, не упакован ли код. Тут могут быть разные подходы, в простом случае достаточно воспользоваться утилитой strings, чтобы получить строки бинарного файла (приводятся в сокращении):

../Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_cortex.c
../Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_dma.c
…
Hardware init done... Starting FreeRTOS
sendMsg error %s
TSC %d
SET AUTH %d
cmd[%d] %s
UART task
Bluetooth task
AT+AB ShowConnection
…
AT-AB -BypassMode-
state bypass
ERROR: Wrong header length
cmd: %s
led idx %d hue %d sat %d val %d
msg %s
addr=%x, size=%x
User auth pass %s
Congrats amigo!
Wrong won't give up!
ERROR: Unk cmd
I've got a super power and now I'm seeing invisible tactical combatant nano-ants everywhere
…
uartRxTask
watchdogTask
sensorTask
bluetoothTask
ledsTask

Строк нашлось много – можно сделать предположение, что прошивка не сжата и не зашифрована. Уже на этом этапе можно обратить внимание на некоторые примечательные строки, например, форматные строки, строки с описанием ошибок и указанием операционной системы (а вы их увидели?). Наличие осмысленных строк, по большому счёту, можно считать половиной успешного реверса.

Что ж, попробуем загрузить прошивку в самый популярный дизассемблер. Будем использовать IDA версии 6.9 для 32-битного кода (так как микроконтроллер 32-разрядный).

При открытии файла прошивки IDA не может автоматически определить архитектуру и точку входа – необходимо ей помочь.

На этом этапе необходимо снова обратиться к документации на микроконтроллер STM32F042x4 STM32F042x6 и посмотреть раздел 5 «Memory mapping»:



В качестве Processor Type выбираем ARM Little endian, ставим галку Manual load, нажимаем OK:


В окне «Do you want to change the processor type» нажимаем Yes, после этого IDA предлагает нам создать сегменты ОЗУ (RAM) и ПЗУ (ROM), ставим галку ROM.

Теперь мы должны указать адрес начала ROM. На схеме нужно смотреть секцию Flash – это адреса 0x08000000 – 0x08008000. Также укажем, что именно в этот же адрес мы хотим загрузить файл прошивки: Loading address = 0x08000000.


В окне "ARM and Thumb mode switching instructions" нажимаем OK.

Далее IDA говорит, что ничего не знает о произвольных двоичных файлах и точку входа – функцию main — вы должны определить самостоятельно. Нажимаем OK.

Загрузка произведена. Можно изучать прошивку.

Откроем окно строк (Shift + F12). Можно обратить внимание на то, что не все строки совпадают с результатами из утилиты strings – IDA распознала не всё, к сожалению. Немного погодя мы ей поможем…

Примечание для начинающих


  • Любая программа/прошивка представляет собой набор двоичных данных. IDA Pro может по-разному интерпретировать эти данные исходного файла (представлять данные в виде команд или данных в том или ином формате). При этом тут нет кнопки «Назад» (Ctrl+Z), чтобы отменить выбранное отображение — нужно знать, как переключаться между разными режимами отображения. (Шпаргалка по горячим клавишам IDA Pro)
  • Реверс-инженер из кажущегося хаоса двоичных данных восстанавливает логику, структуру и читаемость.
  • Строки – важная информация при реверсе! Так как, по сути, среди всего набора двоичных данных являются наиболее просто и быстро воспринимаются человеком. Строки позволяют делать выводы о назначении функций, переменных и блоков кода.
  • Именуй просмотренные функции! По умолчанию, IDA даёт функциям имена по их стартовым адресам. При анализе держать в голове эти адреса весьма сложно, гораздо проще пользоваться осмысленными именами. Для того, чтобы поименовать функцию достаточно хотя бы её беглого анализа – это уже будет важным подспорьем для дальнейшего анализа.
  • Именуй распознанные переменные! Для того чтобы эффективнее проводить анализ блоков кода и функций, имеет смысл именовать переменные, которые распознала IDA, в соответствии с их назначением (всё, как в лучших практиках программирования).
  • Оставляй комментарии, чтобы не забыть важное. По аналогии с программированием, комментарии при реверсе позволяют дополнительно пояснять логику работы программы или отдельных её участков.
  • По возможности создавай структуры! IDA в своём арсенале имеет средство работы со структурами, имеет смысл освоить это средство и применять его при необходимости. При наличии структур исследуемый код станет еще проще для восприятия.

Анализ строк


Кратко: Анализ строк может помочь составить примерный план исследования двоичного файла.

Итак, строки.

Hardware init done... Starting FreeRTOS
sendMsg error %s
…
cmd[%d] %s
rsp[%d] %s
UART task
Bluetooth task
…
AT+AB SPPDisconnect
AT+AB DefaultLocalName RHINOCEROS-2205
…

Лишь только на основании строк уже можно получить много информации:

  • Операционная система – FreeRTOS;
  • Наличие форматных строк – скорее всего используются printf-подобные функции, можно будет установить назначение регистров/переменных;
  • Названия задач (тасков) – можно предположить назначение этих самых тасков и связанных с ними функций;
  • Использование AT-команд – предположительно так строится взаимодействие микроконтроллера и Bluetooth-модуля.

Далеко не всегда всё так радужно при анализе прошивок – строк и debug-информации может не быть совсем или они малоинформативны, но при создании прошивки мы намеренно не стали усложнять процесс обратной разработки.

Идентификация стандартных функций


Кратко: на данном этапе необходимо удостовериться, что строки действительно распознались, после чего предстоит идентифицировать некоторые стандартные функции языка C.

После загрузки прошивки и автоматического анализа IDA распознала тела функций (не всех, кстати), но среди имён функций нет ни одного «нормального» (только автоматические имена от IDA), что может быть небольшой сложностью в сравнении с реверсом ELF- или PE-файла.


Таким образом, в ходе исследования нужно определить назначение не только специфических функций конкретной прошивки, но и идентифицировать стандартные C-функции. Может возникнуть разумный вопрос — где гарантия, что такие функции есть в прошивке и что они стандартные? Здесь стоит сказать, что обычно при создании программного обеспечения (в том числе прошивок), в 9 случаях из 10 не заморачиваются созданием своей уникальной libc-библиотеки, а пользуются тем, что уже написано и проверено временем. Именно поэтому в 90% случаев можно выдвигать предположение о наличии стандартных С-функций.

Поскольку Hex-Rays Decompiler умеет превращать ARM-ассемблер в С-код, воспользуемся этой приятной возможностью. Стоит обратить внимание, что наличие декомпилированного листинга не отменяет необходимости понимать ассемблер, тем более, декомпил существует далеко не для всех платформ.

Откроем окно строк в IDA (Shift+F12).



Выберем строку sendMsg error %s, откроем ссылки на эту строку (клавиша X – Xrefs — Cross References) — IDA распознала ссылки на строку, это хорошо:



Однако среди строк, выделенных зелёным в дизассемблере, есть просто байты, выделенные красным. При этом некоторые строки явно распознаны не полностью. Так, например, если установить курсор на адрес 0x080074E6 и нажать клавишу A (потом согласиться с предложением «Directly convert to string?»), то получится строка «No device connected». Таким же образом можно пройтись по всем строко-подобным данным и превратить их в строки (или, например, написать Python-скрипт, который пробежится по указанному диапазону адресов и создаст строки).

Следующее препятствие, которое может возникнуть – нераспознанные ссылки на строки (даже если строка распозналась). Попробуйте пройтись по строкам, нажимая клавишу X. Так, например, в моем случае не найдена ссылка на строку «recvMsg error». Ссылка на объект может быть не найдена по двум очевидным причинам:

  • нет кода, который ссылается на текущий объект;
  • IDA не распознала ссылку.

Постараемся исключить первую из них, выполнив двоичный поиск по прошивке. Открываем окно двоичного поиска (Alt + B), вводим адрес строки, не забываем поставить галочку «Find all occurrences»:


Получили одно вхождение:



Перейдём к нему (адрес 0x0800506С):



Превратим DWORD-число в offset, нажатием клавиши O. Появилась ссылка на строку:



Почему созданы двойные ссылки на строки?
Это связано с особенностью архитектуры ARM – длина команды фиксирована и составляет 32 бита, следовательно, нет возможности в команде передать полный адрес объекта (также 32-битный). Поэтому в коде используется короткое смещение на адрес, расположенный рядом с функцией, где уже хранится полный 32-битный адрес объекта.

Установим курсор чуть выше — внутри функции sub_8005070 (диапазон 0x08005070-0x08005092). Переключимся к декомпилированному листингу нажатием Tab:



Обратим внимание на функцию sub_8006690. Если вернуться к строке «sendMsg error %s», то можно увидеть, что она также передается в функцию sub_8006690. Именно строки с символами форматирования могут привести к предположению о том, что функция sub_8006690 – это стандартный printf. Пусть сейчас на уровне предположения это будет printf (даже если наше предположение окажется неверным, то оно всё равно позволит нам продвинуться в исследовании).

Поставим курсор на имя sub_8006690, нажмём клавишу N, введём новое имя x_printf. Префикс «x_» добавим для удобства (от слова «eXecutable») – так можно будет отличить переименованные нами функции от функций, имена которым дала IDA автоматически.

Можно считать выполненной подготовительную часть, теперь перейдём к анализу таска, отвечающего за обработку Bluetooth-соединения. Выйти на него можно опять же через строки. Во многих окнах IDA можно выполнять поиск по Ctrl+F. Так, можно сразу выбрать строки со словом «bluetooth»:



Что такое таск?
Таск (task, задача) – понятие из мира операционных систем реального времени (RTOS). Если по-простому, то таск можно представлять как отдельный процесс. Подробнее можно почитать в цикле статей о FreeRTOS

Bluetooth-таск


Кратко: идентифицировать и проанализировать функцию обработки команд, передающихся по Bluetooth. Необходимо будет создать дополнительный сегмент памяти в IDA.

Строка «Bluetooth task\r\n» не имеет кросс-ссылок — воспользуемся снова двоичным поиском, получим адрес, где она используется – 0x080058A0, перейдём туда и увидим список частично распознанных ссылок:



Создадим из них полноценные ссылки (проклацав клавишу O, или написав Python-скрипт для IDA).

Возможно, не везде создадутся ссылки (адреса, выделенные зелёным):



Перейдя по ссылкам, выделенным зеленым, увидим, что там не созданы строки. Исправляем — помогаем Иде.

Вернёмся к строке «Bluetooth task\r\n». Теперь в коде по адресу 0x08005556 появилась ссылка на эту строку:



Здесь же видим, что эта строка передается аргументом в уже просмотренную нами функцию x_printf. Не забудем также дать говорящее название текущей функции «sub_8005554», например, «x_bluetooth_task».

Переключимся в декомпил и просмотрим функцию полностью. Обратим внимание на строку 132, где в функцию x_printf передаётся некое число. Если изменить отображение числа с десятичной системы счисления на шестнадцатеричную (клавиша H), то увидим число 0x8007651, которое очень похоже на адрес.



Уже знакомая ситуация – IDA не распознала ссылку. Помогаем ей, правда, для этого нужно переключиться из декомпила в дизассемблер (клавиша Tab): делаем offset, переходим по нему, создаём строку. Переходим обратно в декомпил, нажимаем F5 (обновить).

Радуемся улучшению кода:



Снова обратим внимание на строку 132. Явно, помимо форматной строки в x_printf должен передаваться еще список аргументов переменной длины (va_list), IDA этого не распознала… Ну вы поняли, да? Поможем ей.

Установим курсор на имя функции x_printf, нажмём Y – откроется окно изменения прототипа объекта. Впишем правильный прототип функции printf:

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

Эмм, простите, у вас ошибка в прототипе printf...
Согласен, правильно будет
void x_printf( const char *format, ... )
. И чуть позже мы это исправим.

IDA отобразит аргументы для форматной строки:





Пришло время установить назначение (имена) переменных (снова строки нам помогают):

  • x_printf("recv %s state %d\r\n", v0, v25);x_printf("recv %s state %d\r\n", recv_data, state);
  • x_printf("cmd: %s\r\n", v24);x_printf("cmd: %s\r\n", cmd);
  • x_printf("addr=%x, size=%x\r\n", v14, v15);x_printf("addr=%x, size=%x\r\n", addr, size);

Другие имена уже не так очевидны, но и не супер-сложны для понимания.

Например, обратим внимание на участок кода:



Переменная v3 сравнивается с числом 3, потом фигурирует сообщение о неправильной длине заголовка. Логично переименовать:

  • переменную v3 в header_len;
  • функцию sub_80006C8 в x_strlen (можно зайти в эту функцию и проверить наше предположение).

Далее обращаем внимание на следующий блок кода:



Функция sub_80006B4 используется несколько раз. Внутри она выглядит так:



Узнали её?
strcmp. Переименовываем. Создаём из хаоса и разрозненности стройный читаемый код.

Теперь обратим внимание на переменные v20000624, v20000344, v20000348. IDA выделила их красным цветом. Всё потому, что они ссылаются на адреса, которые отсутствуют в текущей базе дизассемблера. Если снова обратиться к документации на микроконтроллер, то можно увидеть, что диапазон адресов 0x20000000-0x20001800 относится к RAM.

Почему 0x20001800?
0x1800 — это 6Kb RAM, а это указано в документации

Если переменная ссылается на несуществующую область памяти, для нее будут недоступны xref’ы – исследование будет вызывать дискомфорт… Для удобства и производительности имеет смысл создать дополнительный сегмент памяти. Открываем окно сегментов (Shift + F7), добавляем RAM-сегмент:



Обновляем декомпил. Обращаем внимание на переменную unk_20000344:



Очень похоже, что это некий auth_flag (флаг авторизации). Так и запишем, то есть назовем эту переменную. В моем случае кросс-ссылок не нашлось – используем двоичный поиск и создаём ссылки.

Проверка на устройстве


Кратко: проверим отдельные предположения на работающем устройстве

Статический анализ – классная штука, но еще лучше, если есть возможность исследовать код в динамике. Тут тоже простор для творчества, но если не усложнять, то самое простое – подключиться к устройству по Bluetooth, отправить какую-то команду и посмотреть на результат.

Так, например, при отправке строки «ZZZ» устройство ответит строкой ERROR: Wrong header length\r\n, при отправке «MEOW» (эта строка есть в исследуемом коде, передаётся в функцию strcmp) увидим mur-mur (>._.<)\r\n, а при отправке «ZZZZ» — ERROR: Unk cmd. Таким образом, функцию sub_8005234 можно переименовать в x_bluetooth_send.

Составлю список команд, которые возможно поддерживаются устройством, и сразу проверю их. Вот что получилось:

  • “ECH1” – возвращает «ОК», включает эхо-режим – команда дублируется отправителю;
  • “ECH0” – выключает эхо-режим;
  • “MEOW” – возвращает «mur-mur (>._.<)\r\n » — то ли пасхалка, то ли отладочная команда;
  • “LED “ — выключает один из ярких светодиодов;
  • “UART” – возвращает «ОК»;
  • “BLE “ — однократно мигает красным светодиодом;
  • “READ” – возвращает «ERROR: Not auth!»
  • “WRIT” – возвращает «ERROR: Not auth!»
  • “AUTP” – возвращает «ERROR: auth error!»
  • “SETP” – возвращает «ERROR: Not auth!»
  • “VIP “ — возвращает «Wrong won’t give up!»

Промежуточные выводы относительно протокола:

  • команда состоит минимум из 4 символов;
  • есть довольно странные команды, как-то связанные с авторизацией (зачем авторизация на осветительном приборе?).

Улучшение кода. Создание структуры


Кратко: при возможности имеет смысл создавать структуры данных — большое подспорье для анализа.

Идём дальше. Задача минимум для нас – научиться управлять светодиодами.

Эксперимент показал, что с большими светодиодами связана команда «LED » — по крайней мере, она позволила выключить один из четырех больших светодиодов. Посмотрим, что находится в этой ветке:



Здесь можно было бы переименовать переменные, смущают только конструкции вроде

*(_WORD *)(v6 + 4) = sub_8005338(v4);

В большинстве случаев переменная v6 является указателем на структуру. Для удобства также создадим эту структуру. Контекстное меню для переменной v6 – выбираем пункт «Create new struct type».

IDA предлагает следующее определение для структуры:



Здесь доверимся автоматике относительно типов полей структуры, но установим читабельные имена, основываясь на данных из форматной строки:

struct struct_LED
{
_DWORD idx;
_WORD hue;
_BYTE sat;
_BYTE val;
};

После создания структуры код стал ещё приятнее:



Переменная v6 по ходу дела была переименована в led. Дополнительные переменные v7 и v8 для удобства также были переименованы. Пусть Вас не смущает появление дополнительных переменных – компилятору виднее.

По сведениям из форматной строки можно сделать вывод, что цвет задаётся в формате HSV (Hue, Saturation, Value). Для перевода цвета из RGB можно воспользоваться таблицей.

Про переменную v4 пока сложно что-то сказать наверняка, кроме того, что она является структурой и создается в функции sub_8005298:





Можно предположить, что переменная v4 представляет собой аргументы команды, пришедшие по Bluetooth. Давайте так и назовём:

  • v4 — bt_args
  • sub_8005298 — x_get_bt_args

В декомпиле возможна потеря ранее распознанной информации
При манипулировании именами и типами данных в декомпиле могут пропадать или появляться аргументы функций. В этом случае нужно для таких функций явно указывать их прототип (клавиша Y на заголовке функции). Из-за того, что в ARM’е первые 4 аргумента передаются через регистры, IDA при декомпиле может эти аргументы «терять», в этом случае… спешим на помощь ИДЕ. Если по декомпилу непонятно, какие аргументы передаются в функцию, идём в дизассемблерный листинг и смотрим на регистры R0-R3 – не заносятся ли в них какие-то значения перед обращением к интересуемой функции. Если заносятся, то в 90% случаев – это аргументы функции, и нужно прописать эти аргументы в прототипе.



Команда “LED ”


Кратко: исследование LED-команды, продолжаем переименовывать функции и переменные.

Сделаем еще несколько переименований для удобства восприятия:

  • sub_8003B6E – x_create_struct
  • sub_800532C – x_get_value_1
  • sub_8005338 – x_get_value_2

Зайдём в функцию x_get_value_1:



Переименуем sub_800530C в x_get_value_3. А теперь сравним функции x_get_value_1 и x_get_value_2:




В них используется одна и та же функция x_get_value_3, но с отличающимся вторым аргументом (2 и 4). При этом x_get_value_1 возвращает 1-байтовое число, а x_get_value_2 – 2-байтовое.

Анализируем работу с x_get_value_3:

  • работа ведётся со строкой bt_args (или структурой, содержащей строку);
  • когда на вход подается число 2, на выходе – число размером 1 байт;
  • когда на вход подается число 4, на выходе – число размером 2 байта.

Сопоставив эти факты, можно выдвинуть предположение, что функция x_get_value_3 формирует число из hex-строки указанного размера.

Выполним переименования:

  • x_get_value_1 — x_get_byte;
  • x_get_value_2 — x_get_word;
  • x_get_value_3 — x_unhexlify.

Проверим, используется ли функция x_unhexlify где-то ещё.

Используется. Функция sub_8005344 выглядит так:



Можно переименовать её в x_get_dword.

Заинтересованный читатель может погрузиться в статический анализ функции x_unhexlify и структуры bt_args — наверняка это будет увлекательно.

На данный момент мы можем сформировать команду для управления светодиодами:



Остаётся вопрос – нужны ли разделители между отдельными полями?

Пользуясь преимуществом наличия устройства, я проверю 2 варианта:

  • пробелы в качестве разделителей;
  • без разделителей.

Для включения нулевого светодиода в красный цвет (следуя таблице преобразования) необходимо задать следующие значения:

  • Индекс светодиода (idx) = 0x00;
  • Оттенок (hue) = 0x00;
  • Насыщенность (saturation) = 0xFF;
  • Значение (value) = 0xFF.

Команда с пробелами: "LED 00 0000 FF FF" — светодиод зажегся ярким светло-голубым цветом.

Команда без пробелов: "LED 000000FFFF" (пробел после символов «LED» нужен по формату команды) – светодиод зажегся красным цветом.

Таким образом, можно сделать вывод, что параметры команды должны передаваться без пробелов. И здесь же можно выстроить предположение (которое могут подтвердить те товарищи, кто провёл полный статический анализ функции x_unhexlify), что функция x_unhexlify служит для потокового вычитывания информации с указанием размера из некого базового буфера.

  • Включить первый светодиод зелёным: "LED 010078FF80"
  • Включить второй светодиод синим: "LED 0200F0FFFF"
  • Включить третий светодиод фиолетовым: "LED 03012СFF80"

В LED-ветке осталась неизученной функция sub_8003B7C. Она принимает на вход некую переменную dword_20000624. Посмотрим, где используется эта переменная – на всякий случай сразу воспользуемся бинарным поиском (Alt + B):



Обратите внимание на адреса 0x08004FF0, 0x08005D40. Хотела спрятаться! Помогаем Иде – создаём ссылки.

Посмотрим теперь, куда ведут ссылки off_8004FF0 и off_8005D40:

  • функция sub_8004D84 – явно стартовая функция прошивки, так как внутри используется строка «\r\nHardware init done… Starting FreeRTOS\r\n» — переименуем эту функцию в x_main;
  • функция sub_8005A08 – в самом начале использует строку «LED task\r\n» — переименуем эту функцию в x_leds_task.

Таким образом, переменная dword_20000624 используется:

  • ближе к концу функции main;
  • после получения данных по Bluetooth в x_bluetooth_task;
  • в начале цикла функции x_leds_task.

Те, кто программировал потоки в обычной ОС или же работал с тасками RTOS, увидят в этой переменной указатель на очередь для обмена данными между тасками – и правильно сделают. Выполним еще несколько переименований:

  • dword_20000624 — leds_queue;
  • sub_8003BD0 — x_queue_recv;
  • sub_8003B7C — x_queue_send.

Дополнительно можно убедиться в правильности названий, если посмотреть места, где используются эти функции:





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

  • sub_800501C — x_sendMsg;
  • sub_8005044 — x_recvMsg.

Теперь, чтобы убедиться, что мы полностью умеем управлять светодиодами, исследуем функцию x_leds_task.

На этом мы немного прервёмся, выпьем чай с шоколадкой и продолжим во второй части статьи.

Результаты первого этапа


  • Проведён внешний осмотр устройства.
  • Прошивка загружена в дизассемблер.
  • Найдены полезные для исследования строки.
  • Было установлено, что «носорог» управляется через Bluetooth по простому текстовому протоколу.
  • Частично исследован таск обработки команд протокола обмена по Bluetooth.

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

Комментарии (23)


  1. Andrey_13
    22.05.2018 21:15

    Очень интересная статья. Спасибо.


  1. vervolk
    22.05.2018 22:41

    «зачем авторизация на осветительном приборе?» Это вы поймёте, когда будете сидеть в полной темноте или в «стробоскопе».


    1. prusanov Автор
      22.05.2018 22:48

      «зачем авторизация на осветительном приборе?» — это было скорее просто как поспешное недоумение. Потом, правда, недоумение стало ещё больше при виде того, что авторизация здесь используется совсем не для управления освещением.


  1. a_freeman
    22.05.2018 22:41

    А IDA бывает триальная?


    1. prusanov Автор
      22.05.2018 22:44
      +1

      Бывает free-версия. Правда, без декомпила


  1. sim-dev
    23.05.2018 09:51
    +1

    Я бы обязательно перед запуском IDA подключился бы к TX и поглядел бы, что там «выходит». Логический анализатор в помощь для определения параметров сигнала (скорости и т.п.), затем терминал. Думаю, это сильно помогло бы.


    1. prusanov Автор
      23.05.2018 10:07

      Всё верно! При полноценном исследовании так и нужно. Но в рамках мастер-класса это было затруднительно (правда, один из участников был «во всеоружии» и подключился к TX). Ну и по задумке акцент был сделан именно на статический анализ.
      Учитываем замечания и ошибки. Не загадывая постараемся развить тему и затронуть также «железячные» моменты.


  1. PKav
    23.05.2018 14:30

    Извиняюсь за оффтоп. А что за Bluetooth-модуль установлен на спине носорога? Судя по виду, он сильно загажен флюсом, не удается прочитать маркировку.


    1. GennPen
      23.05.2018 14:34
      +1

      SPBT2632C2A


    1. prusanov Автор
      23.05.2018 15:03

      Как уже ответили — SPBT2632C2A. Маркировка на модуле — «006-000095», плохо получилось на фото


      1. GennPen
        23.05.2018 20:26

        Почитал спецификации, этот модуль уже имеет свой STM32 контроллер с распаянными GPIO. Но для обучения конечно же лучше показывать на отдельно стоящем контроллере.


  1. nonpar
    23.05.2018 15:20
    -5

    Не уверен, что тырить чужой код — самое полезный навык, которым следует овладеть студенту. Тем более, что в случае МК его зашивают во внутреннюю флэш память, защищенную от считывания; так что бинарники можно получить, только хакнув ресурсы разработчика. Лучше бы учили писать хороший собственный, плюс хорошо понимать целевые задачи и инструментарий.


    1. Ariman
      23.05.2018 15:30
      +2

      Ну давайте начнем с того, что получить навыки реверс-инжениринга — это не «тырить чужой код». Это навык, который позволит, как минимум серьезно углубить свое понимание того, как строятся электронно-вычислительные устройства на всех уровнях, начиная от аппаратного и заканчивая прикладным софтом. Это существенно помогает при необходимости работы с чужим кодом и железом, например — при необходимости написания патчей и поиске ошибок. Обладая навыком реверса, например, можно отыскать ошибки в чужом бинарнике и даже исправить их — здесь, на хабре, были такие статьи. Также возможно даже разработать бинарный «плагин» расширяющий функциональность чужого приложения, такие задачи тоже, бывает, встают.
      Помимо этого есть профессиональная область, которая требует глубоких знаний в реверс-инжениринге: это поиск уязвимостей в программных и программно-аппаратных продуктах. Она востребована и количество уязвимостей в объектах критической инфраструктуры не снижается год от года — достаточно зайти на CVE и посмотреть количество уязвимостей по годам: Siemens: Vulnerability Statistics
      Далее. Получить бинарный код можно многими путями. Защищают от вычитывания, на самом деле единицы. Но помимо этого есть ряд подходов, позволяющих получить бинарный код — и, между прочим, в этой статье один упоминался, вероятно, вы не дочитали — это файлы обновления прошивки, поставляющиеся производителем.

      Сейчас очень мало выпускников вузов, представляющих себе работу устройства на всех уровнях. Еще меньше тех, кто может восстановить логику этой работы по неполным данным. Кроме того, с вместе с увеличением объемов бинарного кода возрастает потребность в автоматизации работы реверс-инженера, поэтому эта область представляет еще и обширное поле для научных исследований.


      1. nonpar
        23.05.2018 17:57
        -3

        По-моему, если вы хотите выучить студента на разработчика, то наилучший способ «углубить понимание» — разбор открытых исходных текстов программ; их сейчас доступно множество, для широчайшего разнообразия аппаратных платформ. И лабы, лабы и ещё раз лабы, на тех же stm32, с управлением реальными объектами в реальном времени и взаимодействием через современные интерфейсы. Занимаясь же реверсом, инженер обречён следовать за чужой технической мыслью, зачастую — устаревшей. Я уже не говорю о том, что реверс коммерческих продуктов может преследоваться по закону. Что касается перехвата кода при обновлении прошивок — вы, наверное, лучше меня знаете, что их нынче принято шифровать; нешифрованные прошивки для серьезных коммерческих продуктов, тем более в критичных областях — это 90-е в лучшем случае. Этой дорожкой уже ходили при позднем СССРе, где почти всё, что нынче называется IT, было реверсом; результат оказался плачевен.


        1. Ariman
          23.05.2018 19:16
          +1

          Да в том-то и дело, что я знаю лучше вас, поэтому ваш комментарий выглядит очень наивным, как мнение человека, составленное на основе фильмов про хакеров. Страшное слово «криптография» вызывает у вас благоговейный трепет и мысли о непроницаемой защите.

          Вы упорно не хотите понять, что сфера реверс-инжениринга — это не только «стащить чужой код и схематику, чтобы сделать похоже», это еще и поиск уязвимостей, защита информации, исследования в области машинного обучения и многое, многое другое. Не даром DARPA выплачивает миллионы за успехи в сфере реверс-инжениринга. Не даром у нас который год проводят огромные международные конференции ZeroNights, PHDays, а за рубежом — BlackHat и прочие. Но то DARPA, конференции… Что нам ровняться на какие-то там исследовательские институты, когда можно посмотреть фильм про хакеров, правда?)


          1. nonpar
            23.05.2018 21:08
            -2

            К сожалению, из-за отсутствия техподдержки (вернее, из-за жадности тех, кто не хотел её оплачивать) пришлось как-то отреверсить проприетарный протокол достаточно известного бренда. Ничего хорошего в этом не вижу, непроизводительная трата времени, ничего мне профессионально не давшая и ничему новому не научившая. Криптография ломается, конечно, но ломать её — опять же малопочтенное и, в общем случае, преследуемое законом занятие.


            1. Ariman
              23.05.2018 22:04

              И опять вы игнорируете в упор мои слова об огромной области для исследований, о интересных и важных задачах, решение которых приближает нас к автоматизации поиска уязвимостей методами машинного обучения, о том, в конце концов, что это все применяется для защиты — и продолжаете талдычить свое «преследуемо законом!!!11» — уже в котором комментарии, причем не только под этим постом. Складывается впечатление, что у вас какая-то нездоровая фиксация на одном аспекте — ладно бы просто по незнанию, но нет, уже после того, как вам разъяснили, вы все равно продолжаете игнорировать факты.

              То что лично вам вставшая перед вами задача не дала ничего в профессиональном плане — это исключительно ваша веселая история из жизни, на глобальное положение дел (которое я уже третий комментарий пытаюсь до вас донести) она не влияет. Также как и ваши фантазии относительно «почтенности» — я уже приводил ссылки на международные конференции, посвященные информационной безопасности — если вы все-таки избавитесь от навязчивой боязни и посетите такое мероприятие, или хотя бы почитаете научные работы, посвященные информационной безопасности, возможно, узнаете что-то новое.


        1. prusanov Автор
          23.05.2018 21:53
          +1

          А если есть желание выучить студента на исследователя безопасности? Чему, на Ваш взгляд, стоит учить такого студента?

          Касаемо обучения инженеров. Инженер, который создает продукт (программу/устройство), но не имеет представления, как этот продукт будут «ломать», в конечном счёте создаёт продукт, который легко взломать. А уж сколько обновлений передаётся в незашифрованном или «условно-самопально-зашифрованном» виде по той же причине… При обучении-то как раз и стоит затрагивать разные аспекты, в том числе и реверс.

          Бытует такое мнение: «ой, они же завтра побегут всё взламывать» и поэтому нельзя их этому учить! Во-первых, не побегут; а во-вторых, стоит переживать о другом — когда на факультетах ИБ не преподают реверс.


          1. Ariman
            23.05.2018 22:09

            Я так полагаю, раз это все «незаконно!!!», то нужно вообще срочно закрывать факультеты ИБ ведущих технических вузов. А то ишь ты, повадились плодить потенциальных преступников! Бумажка с регламентами — вот настоящее оружие доблестного борца с киберугрозами! Прошелся по предприятию, проверил, что шкафчики на замках, каждая флешка с бирочкой и USB-порты опечатаны — все, враг не пройдет.


    1. prusanov Автор
      23.05.2018 15:55
      +1

      Видимо, не акцентировал внимание в статье, что мастер-класс на конференции был в секции «Информационная безопасность».
      Но в целом, реверсить — не значит «тырить».
      На мой взгляд, реверс учит (буквально, вынуждает) понимать внутреннее устройство системы до самых низов. Это нужно и исследователю безопасности, и обычному разработчику (который вынужден взаимодействовать с другими системами), не говоря уже про вирусный анализ.


  1. jadorvski
    23.05.2018 15:41
    +1

    Очень интересная статья! Спасибо!
    А как понять, по коду, что устройство обращается в GPIO?
    То есть как увидеть, например, нажатия по кнопкам?


    1. JekaKey
      23.05.2018 15:49
      +1

      Будут записи в соотвествующие адресные пространства. В статье дана схема, периферия замаплена с адреса 0x4000 0000, а дальше в даташите смотреть что конкретно находится за каким адресом.


      1. JekaKey
        23.05.2018 16:09

        В качестве примера по GPIO
        www.st.com/content/ccc/resource/technical/document/reference_manual/c2/f8/8a/f2/18/e6/43/96/DM00031936.pdf/files/DM00031936.pdf/jcr:content/translations/en.DM00031936.pdf
        Страница 46, видно что GPIOA имеет адреса 0x4800 0000 — 0x4800 03FF, а на странице 163 даны оффсеты для всех регистров работы с этим портом, настройка, пулапы, и изменение состояния итд. По оффсету 0x14 лежит регистр ODR, имеет 16 бит, которые управляют подачей питания на соответствующей ноге, записывая в эти адреса 0 или 1 будут меняться состояния вывода. А читать состояния ноги можно из регистра IDR.