Привет, Хабр! Сегодня предлагаем разобрать успешный взлом коммерческого умного замка с поддержкой Bluetooth, который открывается по отпечатку пальца или через беспроводное соединение. Здесь будет реверс-инжиниринг мобильного приложения, манипуляции с API-запросами и перехват протокола связи Bluetooth Low Energy (BLE).

Для анализа использовались динамические инструменты и инструмент для атак «человек посередине» (MitM) в BLE‑канале. После объединения нескольких уязвимостей в архитектуре системы, удалось зарегистрироваться и управлять замком, не имея физического доступа к нему. В результате была полностью скомпрометирована функция разблокировки.

Коротко для тех, кому лень читать

Фокус внимания был направлен на два компонента:

  1. Общение между Android-приложением и удалённым API-сервером.

  2. 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 перехватывать и повторно использовать команды разблокировки.

Анализ причин уязвимости

Компрометация стала возможной из‑за нескольких ошибок в проектировании:

  1. API привязки устройства не проверял авторизацию и близость пользователя.

  2. Шифрование использовало статический ключ, встроенный в клиент.

  3. BLE-канал не имел защиты целостности сообщений или механизмов против replay-атак.

  4. Код приложения раскрывал все криптографические операции и структуру протокола без обфускации.

Чтобы предотвратить подобные атаки, производителям следует:

  • Добавить проверку близости при привязке (например, через BLE challenge-response).

  • Использовать уникальные ключи для каждого устройства, безопасно прошитые на этапе производства.

  • Добавить коды аутентификации сообщений (MAC) с nonce или счётчиками для защиты от replay-атак.

  • Заменить режим ECB на безопасный (например, AES-GCM).

  • Обфусцировать код приложения и защитить его от динамического анализа.

Вот так вот комбинация небезопасного API, слабой криптографии и плохой реализации BLE приводит к полной компрометации устройства. Так что, создавая умный дом, делайте его ещё и безопасным.

Спасибо за внимание. Ваш Cloud4Y. Читайте нас здесь или в Telegram‑канале!

Комментарии (1)


  1. propell-ant
    03.06.2025 11:42

    Достаточно и одного пункта "уязвимость к replay-атакам".