Меня зовут Алексей Плаксин, я системный аналитик в компании KODE и сейчас расскажу вам, как делал реверс-инжиниринг бытовой техники.
Однажды к нам пришел крупный бренд бытовой техники, который в том числе производит и продает «умную» технику для дома. Нам нужно было в короткие сроки разработать новое мобильное приложение для управления умными устройствами.
На руках у нас было три устройства — весы, чайник и мультиварка. О том, как они работают, мы знали только из инструкций и теоретической информации на сайте, потому что документация протоколов взаимодействия была утеряна в анналах истории. У нас не было опыта разработки приложений для управления подобными умными устройствами. Также мы не знали, как работает управление по Bluetooth и Wi-Fi.
Проект обещал быть интересным.
По беглому поиску информации мы нашли такой объем данных об интернете вещей, что стало ясно: нужно сузить поиск. По описанию на коробках мы поняли, что устройства работают через Bluetooth. По ключевым словам вроде «Мультиварка название бренда управление по Bluetooth» нашли YouTube-канал, в котором доблестное Open Source-сообщество рассказывало, как найти закономерность в отправке и получении информации с устройств. Это и была наша отправная точка: теперь нужно было всё проверить и уточнить.
Ещё с университета я помнил про Wireshark — утилиту для анализа сетевого трафика. Оказалось, что он перерабатывает и Bluetooth-трафик.
Инструмент был найден, оставалось понять, что анализировать. Я вспомнил правило «Всё, что попало в интернет, остаётся в интернете». Поскольку меня интересовала не мемо-археология, а APK старой версии приложения, я установил его, зарегистрировал на тестовую почту — и вуаля, получил источник трафика для анализа.
Но устройства были в Калининграде — ими был заставлен целый стол в офисе, а я — удалёнщик из Екатеринбурга. Поэтому я попросил помощи коллег из команды QA, передаю им привет и лучи добра. Им нужно было:
Затем они вытаскивали с телефона логи устройств и отправляли их мне вместе со скринкастом. Я открывал логи в Wireshark, сравнивал с тем, что происходило на скринкасте, и пытался расшифровать трафик.
Лирическое отступление: лог записывался за сутки, поэтому мне надо было освоить фильтрацию. Самой простой оказалась фильтрация по mac-адресу устройства. Bluetooth.addr == a4:c1:38:d6:a7:b5 — команда поиска всех пакетов, отправленных или полученных от этого устройства.
Немного углубимся в теорию Bluetooth, чтобы выделить ключевое. Представьте, что вы на собеседовании и рекрутер спрашивает про ваши навыки. Подобный процесс происходит между Bluetooth-адаптером телефона и умным устройством. Контроллер по порядку опрашивает адреса сервисов устройства и устройство отвечает, что из этого умеет делать: DeviceName, Appearance, Preferred Connection Parameters. Но меня интересовало не это, а загадочная аббревиатура UART — универсальный асинхронный приемник-передатчик с двумя каналами: Rx и Tx. Это и был канал общения с устройством: в Tx можно писать команды, а из Rx читать данные, которые присылает устройство — например, прошивку. Маленький успех!
Мало просто отправлять команды, надо комплексно понимать, как приложение общается с устройством.
Для этого я использовал скринкаст приложения и логи. Логи содержали временную метку, по которой я понимал, какое действие на телефоне вызвало конкретный набор байтов. Это было похоже на изучение иностранного языка с разговорником — сначала читаешь фразу, а потом — что она означает.
Итак, я составил «разговорник» из действий и наборов байт, которые они вызывают. Также нужно было не забыть про команды, которые выполнялись в фоновом режиме. Например:
Когда я писал требования под такую реализацию, я учитывал, что у команды немного опыта работы с Bluetooth. Поэтому сделал артефакт, которым горжусь — полный сборник логики построения известных команд для всех поддерживаемых устройств. И это только введение)
Несмотря на громоздкость, этот артефакт значительно сократил время на проработку системных требований к устройствам: вместо того, чтобы каждый раз заново писать команды для каждого устройства, я мог сослаться на команду, описанную в сборнике.
Если говорить про общий формат требований, по каждому устройству неразрывно существуют два артефакта:
По полученной информации удалось составить общее представление о работе автомата состояний внутри техники. Успех? Успех, но праздновать рано. Получив ключ к общему протоколу, я также получил целый парк устройств для расширения списка поддерживаемых устройств. Здесь ждал новый вызов: оказалось, что не всевитамины одинаково полезны устройства работают одинаково. Поэтому кроме описания команд для работы с устройством, я добавил в системные требования диаграммы состояний и текстовое описание логики работы (Боже, храни PlantUML!).
Они помогли наглядно понимать разницу в «поведении» устройств. Например, у чайника и мультиварки похожий набор параметров. Но если чайнику дать команду «Старт», он начнет кипятить воду. А если дать команду «Старт» мультиварке, она ничего не будет делать, пока ты подробно не объяснишь, что именно от нее хочешь.
Но главным испытанием стал антагонист из заголовка статьи — весы.
Стандартные навыки Bluetooth-устройств описаны в документации Bluetooth Foundation (BTF). Например, у BTF есть специальный профиль для напольных весов — красивый, с возможностью передачи биометрии, все дела. Я логично предполагал, что он и используется в новых весах с биометрией. Однако в обмене меня встречал Unknown Service.
Здесь мне снова поможет аналогия с собеседованием. Рекрутер ожидает, что соискатель умеет работать с базами данных и базовой статистикой, и спрашивает: «Что вам ближе — SQL или NoSQL?». А в ответ получает «Предпочитаю считать через матрицы преобразований многомерных пространств в полночь угольными чернилами на прошлогодней бересте» (подставьте любой непонятный булшит).
Естественно, такой ответ удивит рекрутера, вот и я удивился. Ведь я хотел, чтобы весы отдавали статус, время, измерения и биометрию. А они отдавали вот это:
Ладно, у меня были инструменты, и я решил их использовать.
Синхронизируется с телефоном, класс. Но что это внутри? Какой формат? Количество секунд? Ага, просто количество секунд с 01.01.01. Записал.
Наверное, весы измеряют вес в килограммах. Смотрим.
Весы показывают 23,1 кг. Хм, что-то интересное. Если мы просто переведем это в десятичные — получится белиберда 101 253 374. Но у нас значение с запятой. Поиск приводит нас в стандарт медицинского оборудования (!) и описанный для этого стандарта формат передачи данных с плавающей запятой в двух и четырех байтах. А теперь ловкость рук и никакого мошенничества. Разворачиваем значение.
Первый байт — fe, говорит нам о количестве знаков после запятой (в данном случае -2). Остальная часть — значение. Переводим в калькуляторе и получаем 2310. Ура!
Тут пришлось вспомнить что шестнадцатеричные значения можно перевести не только в десятичные, но и в двоичные. И получить строку 0 и 1, которые по факту являются набором булевых флагов.
Абстрактный пример: 08 = 1111
Каждый байт обозначет здесь какое-то состояние устройства: есть ли метка времени, есть ли данные с датчика, авторизован ли пользователь, есть ли результат измерения, а также единица измерения. По факту это идентификатор устройства.
Есть статичные показания, которые меняются только в зависимости от единиц измерения, а есть динамические показания, которые отправляются при каждом измерении: система измерения сопротивления тела, состояние взвешивания, уровень точности.
Самая главная фича весов — биометрия. В протоколе описана передача готовых параметров:
процент жира,
процент воды,
масса костей,
масса мышц.
А у нас в строке с устройства осталось два поля, и одно всегда 00. Значит, дело во втором, в котором значение с запятой. Получается 595,9. Очень интересно, но ничего не понятно.
Опускаемся глубже в документацию и находим, что это показатель сопротивления тела миллиамперному току с частотой в 50 кГц. Теперь знайте, что каждый раз, когда вы встаете на весы, вас немножко бьет током) Рядом находим алгоритм расчета показателей и готово, мы красавчики!
Красавчики ли мы Реализуем распознавание данных, рисуем красивый дизайн, добавляем хранение данных на бэке. С копирайтингом и дизайн-гайдами. Любо-дорого. Тестируем.
Разработчик приходит и говорит: «Весы не работают, почему — непонятно. Всё реализовано, как ты написал, но они не отдают даже измерения. Сессия разрывается и я ничего не могу с этим сделать». Тогда я пошел к тестировщику и попросил его найти устройство, которое мы еще не подключали к этим весам, установить туда приложение и прислать лог первого подключения с нового устройства. Я сравнил два лога — лог первого и лог повторного подключения, и обнаружил разницу.
Весы передавали два значения, а в ответ я отдавал одно. Причем команда 00 25 01a существовала при каждом подключении. Получается, весы что-то задавали при первом подключении. В итоге я увидел исключающее «или», булево-логическую операцию, которая отличается от обычного «или» тем, что в случае двух единиц отдает ноль. То есть мы правильно сделали внутреннюю логику, но изначально не посмотрели состояние первого подключения, и из-за этого возникла проблема.
Ответ прост: авторизация! Весы требовали авторизацию.
Алгоритм при работе со встроенными системами:
Что важно помнить:
Встроенная система существует в вакууме. Ей неоткуда брать данные, кроме командного устройства — значит, скорее всего, набор состояний и внутренняя логика ограничена.
Сначала лучше пробовать стандартные решения, и только потом нестандартные.
Если логика сломалась, есть возможность сброса.
Главное — не выпустить волшебный белый дым)