Меня зовут Алексей Плаксин, я системный аналитик в компании KODE и сейчас расскажу вам, как делал реверс-инжиниринг бытовой техники.

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

На руках у нас было три устройства — весы, чайник и мультиварка. О том, как они работают, мы знали только из инструкций и теоретической информации на сайте, потому что документация протоколов взаимодействия была утеряна в анналах истории. У нас не было опыта разработки приложений для управления подобными умными устройствами. Также мы не знали, как работает управление по Bluetooth и Wi-Fi.

Проект обещал быть интересным.

Ресерчим и определяем источник трафика

По беглому поиску информации мы нашли такой объем данных об интернете вещей, что стало ясно: нужно сузить поиск. По описанию на коробках мы поняли, что устройства работают через Bluetooth. По ключевым словам вроде «Мультиварка название бренда управление по Bluetooth» нашли YouTube-канал, в котором доблестное Open Source-сообщество рассказывало, как найти закономерность в отправке и получении информации с устройств. Это и была наша отправная точка: теперь нужно было всё проверить и уточнить.

Ещё с университета я помнил про Wireshark — утилиту для анализа сетевого трафика. Оказалось, что он перерабатывает и Bluetooth-трафик.

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

Но устройства были в Калининграде — ими был заставлен целый стол в офисе, а я — удалёнщик из Екатеринбурга. Поэтому я попросил помощи коллег из команды QA, передаю им привет и лучи добра. Им нужно было:

1. Установить и запустить старую версию приложения, 

2. Включить запись экрана на телефоне,

3. Выполнить задачи вроде «Вскипятить чайник, включить подогрев воды и подсветку».

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

Лирическое отступление: лог записывался за сутки, поэтому мне надо было освоить фильтрацию. Самой простой оказалась фильтрация по mac-адресу устройства. Bluetooth.addr == a4:c1:38:d6:a7:b5 — команда поиска всех пакетов, отправленных или полученных от этого устройства.

Немного углубимся в теорию Bluetooth, чтобы выделить ключевое. Представьте, что вы на собеседовании и рекрутер спрашивает про ваши навыки. Подобный процесс происходит между Bluetooth-адаптером телефона и умным устройством. Контроллер по порядку опрашивает адреса сервисов устройства и устройство отвечает, что из этого умеет делать: DeviceName, Appearance, Preferred Connection Parameters. Но меня интересовало не это, а загадочная аббревиатура UART — универсальный асинхронный приемник-передатчик с двумя каналами: Rx и Tx. Это и был канал общения с устройством: в Tx можно писать команды, а из Rx читать данные, которые присылает устройство — например, прошивку. Маленький успех!

Учимся говорить на одном языке

Мало просто отправлять команды, надо комплексно понимать, как приложение общается с устройством.

Для этого я использовал скринкаст приложения и логи. Логи содержали временную метку, по которой я понимал, какое действие на телефоне вызвало конкретный набор байтов. Это было похоже на изучение иностранного языка с разговорником — сначала читаешь фразу, а потом — что она означает. 

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

  1. На запрос: 69|01|01|69 приходит ответ 69|01|01|02|3b|69. Это означает, что версия прошивки в приложении — 2.3b.

  2. На запрос 69|02|f0|69 приходит ответ 69|02|f0|4c|34|fd|d7|fd|fb|69. Это означает, что mac-address устройства fb:fd:d7:fd:34:4c.

  3. На запрос 69|03|ff|19|40|50|d4|80|dc|87|a5|69 приходит ответ 69|03|ff|01|69. А это — передача ключа доступа к устройству, без которого сессия будет разорвана самим устройством.

Собираем требования

Когда я писал требования под такую реализацию, я учитывал, что у команды немного опыта работы с Bluetooth. Поэтому сделал артефакт, которым горжусь — полный сборник логики построения известных команд для всех поддерживаемых устройств. И это только введение)

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

Если говорить про общий формат требований, по каждому устройству неразрывно существуют два артефакта: 

  1. Функциональные требования — базовое описание функциональности приложения для работы с устройством.

  2. Системные требования — описание алгоритма.

По полученной информации удалось составить общее представление о работе автомата состояний внутри техники. Успех? Успех, но праздновать рано. Получив ключ к общему протоколу, я также получил целый парк устройств для расширения списка поддерживаемых устройств. Здесь ждал новый вызов: оказалось, что не всевитамины одинаково полезны устройства работают одинаково. Поэтому кроме описания команд для работы с устройством, я добавил в системные требования диаграммы состояний и текстовое описание логики работы (Боже, храни PlantUML!).

Они помогли наглядно понимать разницу в «поведении» устройств. Например, у чайника и мультиварки похожий набор параметров. Но если чайнику дать команду «Старт», он начнет кипятить воду. А если дать команду «Старт» мультиварке, она ничего не будет делать, пока ты подробно не объяснишь, что именно от нее хочешь.

Проходим главное испытание

Но главным испытанием стал антагонист из заголовка статьи — весы.

Стандартные навыки Bluetooth-устройств описаны в документации Bluetooth Foundation (BTF). Например, у BTF есть специальный профиль для напольных весов — красивый, с возможностью передачи биометрии, все дела. Я логично предполагал, что он и используется в новых весах с биометрией. Однако в обмене меня встречал Unknown Service.

Здесь мне снова поможет аналогия с собеседованием. Рекрутер ожидает, что соискатель умеет работать с базами данных и базовой статистикой, и спрашивает: «Что вам ближе — SQL или NoSQL?». А в ответ получает «Предпочитаю считать через матрицы преобразований многомерных пространств в полночь угольными чернилами на прошлогодней бересте» (подставьте любой непонятный булшит). 

Естественно, такой ответ удивит рекрутера, вот и я удивился. Ведь я хотел, чтобы весы отдавали статус, время, измерения и биометрию. А они отдавали вот это:

Ладно, у меня были инструменты, и я решил их использовать.

Метка времени 

Синхронизируется с телефоном, класс. Но что это внутри? Какой формат? Количество секунд? Ага, просто количество секунд с 01.01.01. Записал.

Данные о весе

Наверное, весы измеряют вес в килограммах. Смотрим.

06

09

00

fe

Весы показывают 23,1 кг. Хм, что-то интересное. Если мы просто переведем это в десятичные — получится белиберда 101 253 374. Но у нас значение с запятой. Поиск приводит нас в стандарт медицинского оборудования (!) и описанный для этого стандарта формат передачи данных с плавающей запятой в двух и четырех байтах. А теперь ловкость рук и никакого мошенничества. Разворачиваем значение.

fe

00

09

06

Первый байт — fe, говорит нам о количестве знаков после запятой (в данном случае -2). Остальная часть — значение. Переводим в калькуляторе и получаем 2310. Ура!

Статус

Тут пришлось вспомнить что шестнадцатеричные значения можно перевести не только в десятичные, но и в двоичные. И получить строку 0 и 1, которые по факту являются набором булевых флагов.

Абстрактный пример: 08 = 1111

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

Есть статичные показания, которые меняются только в зависимости от единиц измерения, а есть динамические показания, которые отправляются при каждом измерении: система измерения сопротивления тела, состояние взвешивания, уровень точности.

Биометрия

Самая главная фича весов — биометрия. В протоколе описана передача готовых параметров:

  • процент жира,

  • процент воды,

  • масса костей,

  • масса мышц.

А у нас в строке с устройства осталось два поля, и одно всегда 00. Значит, дело во втором, в котором значение с запятой. Получается 595,9. Очень интересно, но ничего не понятно.

Опускаемся глубже в документацию и находим, что это показатель сопротивления тела миллиамперному току с частотой в 50 кГц. Теперь знайте, что каждый раз, когда вы встаете на весы, вас немножко бьет током) Рядом находим алгоритм расчета показателей и готово, мы красавчики!

Красавчики ли мы

Реализуем распознавание данных, рисуем красивый дизайн, добавляем хранение данных на бэке. С копирайтингом и дизайн-гайдами. Любо-дорого. Тестируем.

Разработчик приходит и говорит: «Весы не работают, почему — непонятно. Всё реализовано, как ты написал, но они не отдают даже измерения. Сессия разрывается и я ничего не могу с этим сделать». Тогда я пошел к тестировщику и попросил его найти устройство, которое мы еще не подключали к этим весам, установить туда приложение и прислать лог первого подключения с нового устройства. Я сравнил два лога — лог первого и лог повторного подключения, и обнаружил разницу. 

Весы передавали два значения, а в ответ я отдавал одно. Причем команда 00 25 01a существовала при каждом подключении. Получается, весы что-то задавали при первом подключении. В итоге я увидел исключающее «или», булево-логическую операцию, которая отличается от обычного «или» тем, что в случае двух единиц отдает ноль. То есть мы правильно сделали внутреннюю логику, но изначально не посмотрели состояние первого подключения, и из-за этого возникла проблема. 

Ответ прост: авторизация! Весы требовали авторизацию.

Выводы

Алгоритм при работе со встроенными системами:

  1. Изучить все доступные данные про схожим типам устройств.

  2. Найти источник трафика для работы с устройством.

  3. Использовать инструменты анализа — Postman, Charles, WireShark.

  4. Сопоставить данные и состояния устройства.

  5. Повторить логику.

  6. Проверить.

  7. Не расстраиваться, если не получилось.

Что важно помнить:

  • Встроенная система существует в вакууме. Ей неоткуда брать данные, кроме командного устройства — значит, скорее всего, набор состояний и внутренняя логика ограничена.

  • Сначала лучше пробовать стандартные решения, и только потом нестандартные.

  • Если логика сломалась, есть возможность сброса.

  • Главное — не выпустить волшебный белый дым)

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


  1. voldemar_d
    29.03.2024 17:36
    +3

    Абстрактный пример: 08 = 1111

    Какой-то он совсем абстрактный. 1111b = 0x0F, а не 0x08.


  1. kozlov_de
    29.03.2024 17:36
    +2

    Решил сначала что весы это знак зодиака вашего заказчика


  1. RTFM13
    29.03.2024 17:36
    +1

    Повезло, что у скороварок еще каменнный век. Но это не надолго, лампочки уже TLS используют, не хакнешь особо.