Пошаговое руководство взлома фитнес-трекера xiaomi mi band 2
В этой статье будет приведено пошаговое руководство взлома фитнес-трекера с поддержкой Bluetooth и низким энергопотреблением средствами ОС Linux.
Эта история началась с моего поста в Facebook, посвященного проблеме отсутствия API для фитнес-трекеров, и почему сей факт мешает исследователям данных создавать что-то полезное и крутое для этих девайсов.
Тот пост спровоцировал жаркую дискуссию и привлек внимание моего друга Володимира Шиманского, пытавшегося помочь мне с кодом Лео Соареса для моего фитнес-трекера MiBand 2. Володимир пытался запустить этот код, но были проблемы с соединением. Проблема была решена в течение нескольких часов, и код обновлен.
Эта ситуация подтолкнула меня к активным действия. Используя вышеуказанный код, я смог подключиться к MiBand 2, поработать с уведомлениями и измерить ритм сердца. Однако этот функционал меня не устраивал. Я хотел получать данные с сенсоров в режиме реального времени и поэкспериментировать с полученной информацией. Мне хотелось создать советника по физическим упражнениям.
В итоге было решено хакнуть фитнес-трекер.
Приступаем.
У меня не было никакого опыта работы с BLE-девайсами (Bluetooth Low Energy), и я решил начать с изучения этой технологии. Выяснилось, что никаких особых сложностей нет:
- У каждого BLE-устройства есть несколько служб.
- У каждой службы есть некоторые характеристики.
- У некоторых характеристик есть дескрипторы (если у характеристики больше чем один параметр или тип соответствует чтению или уведомлению).
- У некоторых характеристик есть доступ только на чтение/запись (например, текущее время, статус батареи или информация о последней ревизии).
- Некоторые характеристики более сложны и работают, используя запросы/цикл уведомлений (например, монитор частоты сердцебиения в режиме реального времени или авторизация).
Вышеуказанного списка вполне достаточно для начала работы с фитнес-трекером.
Также вам понадобятся два приложения для отладки BLE-устройства: анализаторпротоколов Wireshark и BLE отладчик. Кроме того, нужно активировать опции разработчика в устройстве на базе Android (для любителей iOS придется поискать эквивалент в этой платформе).
Для начала нужно отключить MiBand2 от приложения телефона.
Посмотрим, какие службы и характеристики есть у нашего устройства. Открываем отладчик для BLE и запускаем сканирование. Должно появиться примерно следующее:
Рисунок 1: Перечень устройств доступных для подключения
MAC адрес устройства нам понадобится позже, поэтому лучше эту информацию где-то сохранить или записать. Подключаемся и смотрим перечень служб и характеристик.
Рисунок 2: Перечень служб устройства
Выполнив две простые операции, мы уже получили полезную информацию о нашем устройстве. Схожая функция доступна через командную строку при помощи утилит hcitool и gatttool.
Запуск сканирования из командной строки:
sudo hcitool lescan
Подключение к BLE устройство через Mac адрес и получение списка служб и дескрипторов:
sudo gatttool -b YOUR_MAC -I -t random
> connect
В некоторых случаях могут возникнуть проблемы. В том случае можно перезагрузить устройство или запустить следующую команду:
sudo hciconfig hci0 reset
Подготовка к сниффингу данных
Для сниффинга данных во время коммуникации телефона и BLE-устройства нужно включить Bluetooth-логи в настройках разработчика. Вначале необходимо включить настройки на Android-устройстве. Следуйте по шагам, описанным далее.
До Android 4.1 опции разработчика доступны по умолчанию. Начиная с версии 4.2, нужно сделать следующее:
1. Откройте раздел Settings на Android-устройстве.
2. Выберите раздел System (только на устройствах с Android 8.0 и выше).
3. Прокрутите вниз и выберите About phone.
2. Выполните сопряжение устройства с приложением Xiaomi Android App.
3. Отключите Bluetooth.
4. Загрузите btsnoop_hci.log на компьютер.
5. Откройте файл в Wireshark.
6. Найдите первый запрос, имеющий отношение к протоколу ATT, с параметром Handle: 0x0055 (который связан с компанией Anhui Huami Information Technology Co, выпускающей переносные устройства и владеющей брендом Xiaomi).
Рисунок 3: Содержимое логов с Android-устройства
Выделенный запрос – первый этап аутентификации.
Как видно из рисунка выше, обрабатываются следующие значения UUID:
- Pairing device
Main service UUID
0000fee1-0000–1000–8000–00805f9b34fb - Auth Characteristic (Char) UUID
00000009–0000–3512–2118–0009af100700 - Notification descriptor (Des) handle
0x2902 (всегда неизменный)
Аутентификация состоит из следующих шагов:
- Настройка auth-уведомлений (для получения ответа) посредством отправки двухбайтового запроса \x01\x00 в Des.
- Отправка 16-байтового ключа шифрования в Char с командой и добавление двух байт \x01\x00 + KEY.
- Запрос случайного ключа с устройства с командой посредством отправки двух байт \x02\x00 в Char.
- Получение случайного ключа от устройства (последние 16 байт).
Процесс аутентификации Auth оказался чуть сложнее, и появились некоторые проблемы. Мониторинг частоты сердцебиений отключался через 15 секунд. Ниже показан перечень полученных значений UUID:
- Hardware service (HRDW) UUID
0000fee0–0000–1000–8000–00805f9b34fb - Heart Monitor Service (HMS) UUID
0000180d-0000–1000–8000–00805f9b34fb - Heart Rate Measure Characteristic (HRM) UUID
00002a37–0000–1000–8000–00805f9b34fb - Heart Monitor Control Characteristic (HMC) UUID 00002a39–0000–1000–8000–00805f9b34fb
- Sensor Characteristic (SENS) UUID 00000001–0000–3512–2118–0009af100700
- Notification descriptor (DES) handle
0x2902 (всегда неизменный)
Выполняются несколько стандартных операций:
Далее нужно было разобраться, как распаковать информацию, приходящую с устройства, для последующего парсинга.
Некоторую часть данных можно распарсить из логов, некоторую - нельзя.
Рисунок 4: Ответ от устройства с текущим временем
Поиск правильных пакетов и изучение системы кодирования может отнять некоторое время. В моем случае я попытался найти похожие байты в пакетах рядом с друг с другом. Некоторые байты повторяются внутри пакета.
Raw heart: 02102d8c348c448c458c3d8c428c488c 16
Raw heart: 0218468c418c3d8c468c3f8c398c418c 16
Realtime heart: 93
Raw heart: 0220408c448c3f8c428c498c3c8c3d8c 16
Raw heart: 02283d8c398c488c3e8c468c488c328c 16
Realtime heart: 99
Raw heart: 0230438c408c378c3a8c318c458c388c 16
Realtime heart: 102
Raw heart: 02404f8c408c458c428c4d8c558c4d8c 16
Raw heart: 02483e8c3b8c3f8c348c398c318c428c 16
Realtime heart: 98
Raw heart: 02504c8c428c5e8c4f8c588c498c558c 16
Raw heart: 0258478c458c3c8c4e8c3f8c468c4d8c 16
Realtime heart: 100
Raw heart: 0260518c4d8c4f8c4b8c4f8c528c458c 16
Raw heart: 0268408c3f8c538c4d8c408c548c598c 16
Realtime heart: 102
Raw heart: 0278418c508c4e8c548c588c468c498c 16
Raw heart: 0280368c328c2e8c3c8c338c308c3f8c 16
Realtime heart: 101
Здесь можно заметить четкий повторяющийся шаблон 368c 328c 2e8c 3c8c 338c 308c 3f8c в пакете размером 16 байт. Если мы распакуем с учетом, что каждое измерение представляет собой беззнаковый короткий тип размером 2 байта, то получим 7 непосредственных измерений от сенсора сердца.
Также видно, что второй байт просто увеличивается и связан, как я думаю, с временной разницей между измерениями (ответами).
Raw gyro: 01de49ffd9ff3c004cffd8ff3b004dffdcff4400
Raw gyro: 01df4cffd6ff44004dffd8ff40004cffd1ff4700
Raw gyro: 02e1103231323d3274328e329632af32c732cf32
Raw gyro: 01e34fffd7ff56004bffc7ff590049ffccff4c00
Raw gyro: 01e443ffccff43004effcdff40005bffd4ff4c00
Raw gyro: 01e558ffc9ff5f005effbfff66005fffb0ff5900
Raw gyro: 01e64cffacff60005cffa7ff410066ffc9ff4600
Raw gyro: 01e760ffdcff4b0051ffe4ff4f0034ffdeff5300
Raw gyro: 02e903365c36813663361036543688374139fe3a
Raw gyro: 01eb4bffc3ff50004fffc1ff430047ffbbff4100
Raw gyro: 01ec3effb2ff3c0050ffbfff560047ffccff7300
Raw gyro: 01ed4fffe0ff78005cffebff8e0056fff6ff8300
Raw gyro: 01ee7efffbffa1008bff0f00bc00b1ff1900b800
Raw gyro: 01ef9bff0c00d10095fff3ffd600b7ff0800df00
Raw gyro: 02f12445314600479e473348aa481c499749244a
Raw gyro: 01f3c3ff1600fe00beff1800f200a6ff0800e700
Raw gyro: 01f4a9fff8ffd300a7fff3ffd700a9fff1ffdf00
Raw gyro: 01f5b1fff8ffe800b4fff1fff700acfffcffef00
Raw gyro: 01f67ffff7ffc0006bfff4ffb00078ffe9ffb600
Raw gyro: 01f786ffecffc0006ffff0ffbc0060fff1ffc000
Raw gyro: 02f9ca4cbb4c784c964ca84c784c854c444c1b4c
Raw gyro: 01fb7cff0f00bb007eff2700ae0083ff30009800
Raw gyro: 01fc79ff1800b00076ff0f00bc0068ff0900d900
Raw gyro: 01fd78ff07000c01f6fffbff19011c000b00f600
Raw gyro: 01fe4b001100d30054000700c3004300efffeb00
Raw gyro: 01ff1f00d0ff1701fbffe8ff1b01e3ffffff1101
Raw gyro: 0201214b014bec4ad04aba4acb4abe4aba4abd4a
Raw gyro: 0103efffecfffc00e3fff3fff300defff3fffc00
Raw gyro: 0104e3fff0fff400e6ffefff0301dbffe9ff0c01
Raw gyro: 0105e3fff0ff0301e6ffe6fffc00dcffecfffc00
Raw gyro: 0106dffff0fff700dbffeefff600d6fff0fff400
Raw gyro: 0107dfffecffff00e1fff0ff0301defff3fffc00
В случае с гироскопом ситуация оказалась чуть сложнее. Но я думаю, что упаковка должна выполняться схожим образом, как и для данных о частоте сердцебиения. Однако здесь у нас 3 измерения знакового типа для каждой оси гироскопа, а сам пакет размером 20 байт. Таким образом, данные от всех сенсоров укладываются не в 12, а в 3 измерения по осям x, y, z. Первые 2 байта играют ту же роль, что и в предыдущем случае. В итоге моя гипотеза оказалась верной.
Код
Код, как обычно, можно найти на Github с примером использования. Ничего сложного нет, и я решил подробно не рассматривать этот момент в статье.
https://www.securitylab.ru/analytics/499344.php?R=1