Внешний вид закатной лампы
Внутри коробки имеется сама лампа, подставка для неё, пульт дистанционного управления и бумажка с 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 = {84, 82, 0, 87, (byte) 2, (byte) dataManager.getGroupId(), (byte) i, (byte) Color.red(curColor), (byte) Color.green(curColor), (byte) Color.blue(curColor), (byte) dataManager.getLight(), (byte) dataManager.getSpeed(), 0, 0, 0, 0}; LogUtil.d("send data command:" + ByteUtils.BinaryToHexString(bArr)); boolean writeAll = BleManager.getInstance().writeAll(Agreement.getEncryptData(bArr)); LogUtil.d("send data result :" + writeAll); }
Вуаля — у нас есть массив, который шифруется при помощи AES и отправляется на лампу. Давайте подробно рассмотрим структуру данных:
|
Порядковый номер байта |
Значение по умолчанию |
Описание |
|
1 |
84 |
Значение по умолчанию. Шапка запроса |
|
2 |
82 |
Значение по умолчанию. Шапка запроса |
|
3 |
0 |
Значение по умолчанию. Шапка запроса |
|
4 |
87 |
Значение по умолчанию. Шапка запроса |
|
5 |
2 |
Тип команды от 1 до 7. |
|
6 |
1 |
ID группы (всегда должно быть больше 1, иначе лампа не примет такой запрос) |
|
7 |
0 |
Неизвестно. В коде именуется как |
|
8 |
|
Зелёный спектр цвета — от 0 до 255 |
|
9 |
|
Красный спектр цвета — от 0 до 255 |
|
10 |
|
Синий спектр цвета — от 0 до 255 |
|
11 |
100 |
Яркость лампы — от 0 до 100 |
|
12 |
100 |
Скорость работы эффекта — от 0 до 100 |
|
13 |
0 |
Используется для команды с типом |
|
14 |
0 |
Используется для команды с типом |
|
15 |
0 |
Используется для команды с типом |
|
16 |
0 |
Используется для команды с типом |
Внимание! Для моего устройства (а может так на всех других) перепутаны местами байты красного и зелёного спектров — поэтому в структуре сначала идёт зелёный, а потом красный, хоть в приложении и наоборот.
Теперь осталось поглядетьgetEncryptDataи дело сделано! Но тут появляется неожиданное обстоятельство:
public static byte[] getEncryptData(byte[] bArr) { aes.cipher(bArr, bArr); return bArr; }
public class aes { public static native void cipher(byte[] bArr, byte[] bArr2); public static native void invCipher(byte[] bArr, byte[] bArr2); public static native void keyExpansion(byte[] bArr); public static native void keyExpansionDefault(); static { System.loadLibrary("AES"); } }
Получается, что приложение использует библиотеку, написанную на 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 { /// <summary> /// Ключ шифрования данных /// </summary> private static readonly byte[] Key = { 0x34, 0x52, 0x2A, 0x5B, 0x7A, 0x6E, 0x49, 0x2C, 0x08, 0x09, 0x0A, 0x9D, 0x8D, 0x2A, 0x23, 0xF8 }; /// <summary> /// Шапка для запроса - всегда статичная /// </summary> 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 сообщения:
/// <summary> /// Получить payload для установки конкретного цвета лампы /// </summary> /// <param name="red">Красный спектр</param> /// <param name="green">Зелёный спектр</param> /// <param name="blue">Синий спектр</param> /// <param name="brightness">Яркость лампы (от 0 до 100)</param> /// <param name="speed">Скорость смены эффектов (от 0 до 100)</param> /// <returns>payload для установки конкретного цвета лампы</returns> 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<byte> payload) { return string.Join("", payload.Select(x => x.ToString("X2").ToLower())); }
И также создадим перечисление доступных команд из приложения:
public enum CommandType : byte { /// <summary> /// Запрос на вступление в группу /// </summary> JoinGroupRequest = 1, /// <summary> /// Установить конкретный цвет лампы /// </summary> Rgb = 2, /// <summary> /// Установить режим свечения в такт музыки /// </summary> Rhythm = 3, /// <summary> /// Установить таймер работы лампы /// </summary> Timer = 4, /// <summary> /// /// </summary> RgbLineSequence = 5, /// <summary> /// Установить скорость работы эффекта /// </summary> Speed = 6, /// <summary> /// Установить яркость лампы /// </summary> 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 интерфейс или при помощи автоматизаций.
ссылка на оригинал статьи https://habr.com/ru/post/722412/
Добавить комментарий