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

