Привет, Хабр! Сегодня предлагаем разобрать успешный взлом коммерческого умного замка с поддержкой Bluetooth, который открывается по отпечатку пальца или через беспроводное соединение. Здесь будет реверс-инжиниринг мобильного приложения, манипуляции с API-запросами и перехват протокола связи Bluetooth Low Energy (BLE).
Для анализа использовались динамические инструменты и инструмент для атак «человек посередине» (MitM) в BLE‑канале. После объединения нескольких уязвимостей в архитектуре системы, удалось зарегистрироваться и управлять замком, не имея физического доступа к нему. В результате была полностью скомпрометирована функция разблокировки.
Коротко для тех, кому лень читать
Фокус внимания был направлен на два компонента:
Общение между Android-приложением и удалённым API-сервером.
BLE-связь между приложением и самим замком.
Доступ получен за счёт комбинации уязвимостей:
Ненадёжный API для привязки устройств.
Статическое шифрование (одинаковый ключ для всех замков).
Кастомный BLE-протокол без защиты от перехвата и повторного использования команд.
Хотя BLE-трафик был зашифрован, в нём отсутствовали механизмы проверки целостности сообщений или защиты от атак повторного воспроизведения.
Инструменты
Весь BLE-трафик перехватывался и анализировался с помощью BLE:Bit — мощного инструмента для исследования BLE, позволяющего мониторить трафик в реальном времени и внедрять команды. Изначально этот инструмент разрабатывался для внутренних нужд, но позже стал open-source проектом и помогает сообществу ИБ-специалистов.
Регистрация замка без физического доступа
Приложение привязывает замок к аккаунту пользователя через простой HTTP POST-запрос. По логике, это должно быть возможно только при нахождении рядом с замком (через BLE). Однако выяснилось, что сервер разрешает привязку, зная только MAC-адрес замка.
Пример запроса:
POST /lock/bind
Headers:
token: <redacted>
uid: <redacted>
Body:
{
"name": "lock1",
"userId": <redacted>,
"mac": "<redacted>"
}
Сервер успешно обрабатывал запрос, даже если он отправлялся с устройства, не находящегося рядом с замком и не связанного с ним ранее. Никаких криптографических проверок, подтверждения близости или доказательства владения не требовалось.
Извлечение учётных данных замка через API
После регистрации замка приложение получает его данные через ещё один POST-запрос:
POST /lock/getLockList
Body:
{
"userId": <redacted>
}
В ответе сервера присутствует поле encryptedData
, содержащее зашифрованные данные замка: ключ для BLE‑шифрования, пароль и MAC‑адрес.
Пример ответа:
{
"name": "lock1",
"mac": "<redacted>",
"encryptedData": "RUQ2RDJCODZFQ...=="
}
Расшифровка данных с помощью статического ключа
После декомпиляции приложения был обнаружен следующий код:
public void setData() {
String[] split = new String(
b.b(
c.a(new String(Base64.decodeBase64(this.encryptedData.getBytes()))),
c.a("58966742920123314112157843194045")
)
).split("&");
this.lockKey = split[0];
this.lockPwd = split[1];
this.mac = split[2].trim();
}
Видите проблемы, да? Жёстко зашитый AES‑ключ (58966742920123314112157843194045
) использовался для расшифровки учётных данных блокировки. А ещё приложение использовало AES в режиме ECB без заполнения (AES/ECB/NoPadding) — это небезопасно, так как позволяет расшифровать данные любого замка, имея доступ к бинарнику приложения.
После расшифровки получаем:
lockKey: <redacted>
lockPwd: <redacted>
mac: <redacted>
Этих данных достаточно для формирования валидных BLE-команд и разблокировки замка.
Структура BLE-протокола
Связь между мобильным приложением и замком осуществляется по протоколу Bluetooth Low Energy через две GATT‑характеристики:
0×36f5 — для отправки зашифрованных команд от приложения к замку.
0×36f6 — для ответов от замка, которые приходят в приложение.
Все сообщения шифруются AES в режиме ECB (без защиты от replay‑атак). Ключ (lockKey) уникален для каждого замка, но извлекается из encryptedData (который можно расшифровать из‑за статического ключа).
Каждая команда представляет собой 16-байтовое сообщение, состоящее из:
Кода операции (2 байта) — действие (например, «открыть», «проверить заряд батареи»).
Параметра (переменная длина) — полезная нагрузка в зависимости от типа команды.
Случайных данных (до 16 байт) — заполняет блок до полных 16 байт.
После формирования сообщение шифруется с помощью lockKey и отправляется.
Ответы от замка имеют ту же структуру, но могут содержать байты состояния, данные или подтверждения.
Мобильное приложение и замок общаются через симметричный зашифрованный канал без какой-либо формы MAC (кода аутентификации сообщений), nonce (одноразового числа) или последовательного номера. Это делает протокол уязвимым к replay- и injection-атакам, что было подтверждено на практике.
Каждое сообщение обрабатывается как независимый атомарный блок. Отсутствует контекст сессии, handshake-аутентификация или stateful-сопряжение после первоначального подключения. Это нетипично для безопасных BLE-реализаций, где используются сессионные ключи, методы сопряжения (например, Just Works, Passkey), аутентифицированные характеристики для защиты от подмены данных.
С точки зрения ИБ, такая структура делает протокол легко обратимым и эмулируемым, особенно при наличии постоянного формата сообщений и известного ключа шифрования.
Перехваченный трафик
С помощью BLE:Bit и его возможностей MitM мы записали BLE-трафик между мобильным приложением и замком в реальном времени.
Кусочек перехваченного трафика:
WRITE Android → SmartLock @ 0x36f5: 3E 62 BB 1D F5 6C 60 94 84 E0 E3 87 26 0A 8B BE
NOTIFY SmartLock → Android @ 0x36f6: 93 C6 50 7E 00 79 94 24 6E 6E 40 EA D7 F8 86 7E
WRITE Android → SmartLock @ 0x36f5: FC B1 5D A6 3E 3C DD E4 56 72 60 2D 03 34 84 98
NOTIFY SmartLock → Android @ 0x36f6: DE 4F 0D BB F5 1B 34 08 5F DD F9 FC 59 A9 C5 EF
WRITE Android → SmartLock @ 0x36f5: C6 9D BC DA 2F 39 C7 AD 32 8D DA 21 B2 14 61 FE
NOTIFY SmartLock → Android @ 0x36f6: DE 4F 0D BB F5 1B 34 08 5F DD F9 FC 59 A9 C5 EF
Из этих данных видно, что каждая команда WRITE от приложения сопровождалась ответом NOTIFY от замка. Содержимое сообщений варьировалось в зависимости от типа операции, но всегда имело фиксированный размер 16 байт и шифровалось AES.
Каждая операция (например, открытие замка, проверка статуса, сброс пароля) имела уникальный код операции. Например, OPEN_LOCK
в приложении был определён как 0501
в перечислении BLE-сообщений.
Создание и отправка команд разблокировки
Команда разблокировки в приложении создавалась классом вроде такого:
public class i extends z {
public i(String password) {
super(TYPE.OPEN_LOCK);
char[] chars = password.toCharArray();
a((byte) 6, (byte) chars[0], ..., (byte) chars[5]);
}
}
После создания полезная нагрузка шифруется и передаётся с помощью:
BLEService.a(context, b.a(new i("000000")));
Извлекая пароль из расшифрованного файла encryptedData
, мы смогли воспроизвести эту последовательность и самостоятельно выдать команду разблокировки с помощью специального скрипта.
Уязвимость к replay-атакам
BLE‑сообщения были статичными и повторно используемыми. Поскольку не было механизма проверки актуальности (nonce, счётчик, сессионный ключ), мы смогли перехватить валидную команду разблокировки и отправить её позже — замок выполнял её без каких‑либо дополнительных проверок.
Это полноценная уязвимость replay‑атаки, позволяющая любому злоумышленнику в радиусе действия BLE перехватывать и повторно использовать команды разблокировки.
Анализ причин уязвимости
Компрометация стала возможной из‑за нескольких ошибок в проектировании:
API привязки устройства не проверял авторизацию и близость пользователя.
Шифрование использовало статический ключ, встроенный в клиент.
BLE-канал не имел защиты целостности сообщений или механизмов против replay-атак.
Код приложения раскрывал все криптографические операции и структуру протокола без обфускации.
Чтобы предотвратить подобные атаки, производителям следует:
Добавить проверку близости при привязке (например, через BLE challenge-response).
Использовать уникальные ключи для каждого устройства, безопасно прошитые на этапе производства.
Добавить коды аутентификации сообщений (MAC) с nonce или счётчиками для защиты от replay-атак.
Заменить режим ECB на безопасный (например, AES-GCM).
Обфусцировать код приложения и защитить его от динамического анализа.
Вот так вот комбинация небезопасного API, слабой криптографии и плохой реализации BLE приводит к полной компрометации устройства. Так что, создавая умный дом, делайте его ещё и безопасным.
Спасибо за внимание. Ваш Cloud4Y. Читайте нас здесь или в Telegram‑канале!
propell-ant
Достаточно и одного пункта "уязвимость к replay-атакам".