- Разбираем bluetooth протокол RGB лампы
- Внешний вид официального приложения
- Разбираемся с исходным кодом приложения
- Подготавливаем устройство для сниффинга трафика
- Анализируем протокол общения через bluetooth
- Разбираемся с исходным кодом приложения. Опять
- Реверсим нативную библиотеку шифрования
- Пишем сервис для генерации сообщений протокола
- Используем gatttool для отправки сообщений лампе
- Вывод
Разбираем bluetooth протокол RGB лампы
Внутри коробки имеется сама лампа, подставка для неё, пульт дистанционного управления и бумажка с QR-кодом для скачивания приложения.
Под линзой находится три цветовых круга с световыми элементами:
- Внешний — синий цвет
- Средний — зелёный цвет
- Внутренний — красный цвет
Внешний вид официального приложения
Устанавливаем скачанное приложение на телефон — в качестве подопытного используется Samsung A8 2018 года выпуска (SM-A530F). После установки и открытия приложения нас встречает следующий интерфейс:
- включить/выключить лампу
- группировать несколько ламп в группы для одновременного управления
- Поставить цвет из RGB палитры, отрегулировать яркость
- Установить один из нескольких предустановленных вариантов свечения («дыхание», мигание и плавное переливание цветов) и скорость работы эффекта
- Установить таймер работы лампы
- Функционал свечения в такт музыки — нужно либо выбрать файл с телефона, либо предоставить доступ к микрофону
После подключения лампы к USB разъёму, она становится доступной для соединения с приложением:
Пробуем изменить цвета и установить эффекты — всё работает, значит можно приступать к декомпиляции приложения.
Разбираемся с исходным кодом приложения
Внутри коробки с лампой лежит листок с QR-кодом, который ведёт на страницу скачивания приложения из Google Play или App Store. Чтобы избежать выкачивания приложения из памяти телефона, возьмём APK, который предлагает производитель.
Для декомпиляции приложения воспользуемся JADX — декомпилятор DEX файлов в Java. Скачиваем последний актуальный релиз (1.4.6 на момент написания статьи). Из предложенных в релизе вариантов я выбрал версию со встроенным JRE, дабы не устанавливать лишние зависимости в систему. После запуска открываем ранее скачанный .apk файл и. видим, что исходников практически нет, а те, что есть, не несут какой-либо практической пользы:
Предполагаю, что код приложения обфусцирован и провести обратную операцию либо не получится, либо займёт достаточно много времени. Попробуем пойти более простым путём.
Подготавливаем устройство для сниффинга трафика
Для начала необходимо включить режим разработчика на устройстве — обычно это делается путём 9 нажатий на номер сборки в сведениях об ОС. Далее переходим в настройки режима разработчика, активируем пункты «включить журнал HCI Bluetooth» и «Отладка по USB» и перезапускаем bluetooth.
Заходим в приложение, выбираем из палитры красный, зелёный и синий цвета (чтобы легче было анализировать пакеты), подключаем смартфон через USB к компьютеру и через ADB вытаскиваем дамп:
adb pull /sdcard/btsnoop_hci.log # если не получится с вышеуказанной командой, # то скачиваем полный дамп системы и оттуда вытаскиваем файл по пути # /FS/data/log/bt/btsnoop_hci.log adb bugreport dump
Анализируем протокол общения через bluetooth
Для анализа протокола передачи данных между устройством и лампой воспользуемся Wireshark — программой-анализатором трафика множества различных протоколов. Скачиваем с официального сайта актуальную версию — я выбрал портабельную. Запускаем приложение, открываем bluetoooth dump с устройства, в проставляем фильтр btatt и фильтруем по колонке Info для быстрого поиска отправленных комманд:
Соотносим отправленные цвета по времени и получаем следующую картину:
Никакой закономерности между изменением трёх байт цвета и отправленным значением нет — значит, применяется шифрование на клиенте и в таком виде отправляется на лампу, где происходит обратный процесс и применяются отправленные настройки.
Разбираемся с исходным кодом приложения. Опять
Раз с прошлым приложением у нас ничего не получилось, то скачаем с официального источника. Переходим по ссылке скачивания из Google Play и устанавливаем приложение на телефон. Приложение (на удивление) имеет 100к+ скачиваний и обновлено 27 февраля 2023 года:
Далее необходимо вытащить apk файл приложения при помощи следующих команд:
# Получаем название пакета adb shell "pm list packages | grep strip" # получаем путь до apk файла (из вывода надо выбрать тот путь, что содержит base.apk): adb shell "pm path com.ben.istrips" # забираем приложение на пк adb pull /data/app/com.ben.istrips-JJlXI2S0nofBY-AqpNwOKA==/base.apk ./iStrip.apk
Открываем полученный apk файл через JADX и видим совсем другую картину:
Итак, это успех — у нас теперь есть исходный код приложения, при помощи которого можно узнать, как шифруются данные. Бегло осматриваем исходный код и видим папку ble , в которой содержится файл BleProtocol . Открываем его и видим метод sendColor (комментарии переведены с китайского):
public static void sendColor(DataManager dataManager, int i) < int curColor = dataManager.getCurColor(); byte[] bArr = ; LogUtil.d("send data command:" + ByteUtils.BinaryToHexString(bArr)); boolean writeAll = BleManager.getInstance().writeAll(Agreement.getEncryptData(bArr)); LogUtil.d("send data result :" + writeAll); >
Вуаля — у нас есть массив, который шифруется при помощи AES и отправляется на лампу. Давайте подробно рассмотрим структуру данных:
Значение по умолчанию. Шапка запроса
Значение по умолчанию. Шапка запроса
Значение по умолчанию. Шапка запроса
Значение по умолчанию. Шапка запроса
ID группы (всегда должно быть больше 1, иначе лампа не примет такой запрос)
Неизвестно. В коде именуется как mode
Зелёный спектр цвета — от 0 до 255
Красный спектр цвета — от 0 до 255
Синий спектр цвета — от 0 до 255
Яркость лампы — от 0 до 100
Скорость работы эффекта — от 0 до 100
Используется для команды с типом 4 (настройка таймер) — минута для включения лампы
Используется для команды с типом 4 (настройка таймер) — день недели для выключения лампы
Используется для команды с типом 4 (настройка таймер) — час для выключения лампы
Используется для команды с типом 4 (настройка таймер) — минута для выключения лампы
Внимание! Для моего устройства (а может так на всех других) перепутаны местами байты красного и зелёного спектров — поэтому в структуре сначала идёт зелёный, а потом красный, хоть в приложении и наоборот.
Теперь осталось поглядеть getEncryptData и дело сделано! Но тут появляется неожиданное обстоятельство:
public static byte[] getEncryptData(byte[] bArr)
Получается, что приложение использует библиотеку, написанную на C/C++ и ключа шифрования внутри кода нет — метод cipher принимает массив данных и массив, куда необходимо сохранить зашифрованные данные.
Предположим, что ключ шифрования задаётся функцией keyExpansion либо же устанавливается дефолтный ключ функцией keyExpansionDefault — проверим, используются ли эти методы в коде. После поиска по коду было найдено лишь одно использование метода keyExpansionDefault при создании приложения:
public class App extends Application < // . @Override // android.app.Application public void onCreate() < // . aes.keyExpansionDefault(); // . >>
Делаем вывод о том, что ключ всё-таки хранится внутри библиотеки и его необходимо достать оттуда. Для этого в JADX сохраняем проект через меню File -> Save all (или просто жмём CTRL+S ) и выбираем папку для сохранения.
Реверсим нативную библиотеку шифрования
Для этого потребуется бесплатная версия IDA — интерактивный дизассемблер, который отличается исключительной гибкостью, наличием встроенного командного языка, поддерживает множество форматов исполняемых файлов для большого числа процессоров и операционных систем.
Устанавливаем приложение с официального сайта, открываем при помощи него файл libAES.so , расположенный по пути папка проекта из JADX\app\src\main\lib\x86 , оставляем настройки декомпиляции по умолчанию и перед нами появляется список функций, которые есть в библиотеке:
Здесь видим 4 функции, которые начинаются с Java_ — это и есть те самые нативные функции, описанные внутри aes класса приложения. Переходим в keyExpansionDefault путём двойного нажатия на название в списке и видим первый блок функции, внутри которого есть упоминание key_ptr :
Название переменной говорит само за себя — это указатель на ключ. Поэтому дважды кликаем на key_ptr и переходим в следующий блок:
Переходим в key и. Бинго! Внутри переменной находится массив из 16 байт, который и является ключом шифрования.
Итак, ключ наконец-то найден, теперь можно приступить к генерации собственных шифрованных сообщений для отправки
Пишем сервис для генерации сообщений протокола
Далее будет использоваться .Net Core 6 и язык программирования C#. Весь исходный код опубликован на гитхабе — ссылка на репозиторий.
Проект не представляет из себя чего-то сложного — шифрование AES’ом массива данных при помощи заранее известного ключа.
Создаём класс PayloadGenerator , внутри которого объявляем ранее полученный ключ, шапку запроса, ID группы по умолчанию и создаём экземпляр криптографического объекта для шифрования данных:
public class PayloadGenerator < /// /// Ключ шифрования данных /// private static readonly byte[] Key = < 0x34, 0x52, 0x2A, 0x5B, 0x7A, 0x6E, 0x49, 0x2C, 0x08, 0x09, 0x0A, 0x9D, 0x8D, 0x2A, 0x23, 0xF8 >; /// /// Шапка для запроса - всегда статичная /// private static readonly byte[] Header = < 0x54, 0x52, 0x0, 0x57 >; private readonly ICryptoTransform _crypt; private const int GroupId = 1; public PayloadGenerator() < var aes = Aes.Create(); aes.Mode = CipherMode.ECB; _crypt = aes.CreateEncryptor(Key, null); >>
Далее опишем метод для генерации payload’a сообщения:
/// /// Получить payload для установки конкретного цвета лампы /// /// Красный спектр /// Зелёный спектр /// Синий спектр /// Яркость лампы (от 0 до 100) /// Скорость смены эффектов (от 0 до 100) /// payload для установки конкретного цвета лампы public string GetRgbPayload(byte red, byte green, byte blue, byte brightness = 100, byte speed = 100) < var payload = new byte[16] < Header[0], Header[1], Header[2], Header[3], (byte)CommandType.Rgb, GroupId, 0, green, red, blue, brightness, speed, 0x0, 0x0, 0x0, 0x0 >; var result = new byte[16]; _crypt.TransformBlock(payload, 0, payload.Length, result, 0); return ConvertToHexString(payload); > private static string ConvertToHexString(IEnumerable payload) < return string.Join("", payload.Select(x =>x.ToString("X2").ToLower())); >
И также создадим перечисление доступных команд из приложения:
public enum CommandType : byte < /// /// Запрос на вступление в группу /// JoinGroupRequest = 1, /// /// Установить конкретный цвет лампы /// Rgb = 2, /// /// Установить режим свечения в такт музыки /// Rhythm = 3, /// /// Установить таймер работы лампы /// Timer = 4, /// /// /// RgbLineSequence = 5, /// /// Установить скорость работы эффекта /// Speed = 6, /// /// Установить яркость лампы /// Light = 7 >
В Program.cs создаем экземпляр класса нашего генератора и выводим в консоль сгенерированное сообщение:
using IStripLight; var lightController = new PayloadGenerator(); var result = lightController.GetRgbPayload(0, 0, 255, 50); Console.WriteLine(result);
Итак, генератор сообщений у нас теперь есть, проверим созданные сообщения на работоспособность.
Используем gatttool для отправки сообщений лампе
Для отправки сообщений лампе воспользуемся утилитой gatttool — она позволяет считывать и записывать характеристики GATT (Generic Attribute Protocol) для устройств, использующих Bluetooth low energy.
user@pi:~ $ sudo gatttool -I [ ][LE]> connect 43:d0:0c:e6:2b:20 Attempting to connect to 43:d0:0c:e6:2b:20 Connection successful [43:d0:0c:e6:2b:20][LE]> char-write-cmd 0x0009 ae066f229702720ca898a934839235f1
Яркость на лампе убавилась, а цвет поменялся на зелёный!
Вывод
В статье был проанализирован протокола общения приложения и лампы через реверс-инжиниринг android приложения и нативной библиотеки шифрования AES.
В результате было написано приложение для генерации сообщений для изменения цвета/яркости лампы.
В дальнейшем планируется написать кастомную интеграцию Home Assistant для управления лампой через UI интерфейс или при помощи автоматизаций.