BLE and Bluetooth Low Energy penetration testing guide covering device discovery, GATT enumeration, security mode assessment, traffic capture with Sniffle, and exploitation techniques for IoT devices, smart locks, wearables, and BLE-enabled systems.
BLE (Bluetooth Low Energy) is used in IoT devices, smart locks, wearables, beacons, and mobile peripherals. BLE pentesting aligns with PTES (Intelligence Gathering → Vulnerability Analysis → Exploitation) and OWASP IoT Security Testing Guide (ISTG-WRLS) categories: authorization, information disclosure, cryptography, and input validation. Common findings include weak pairing (“Just Works”), unencrypted GATT characteristics, replay attacks, and MITM opportunities when encryption is absent or downgraded.
Discover BLE devices advertising in range. Ensure the Bluetooth adapter is powered on and scanning.
Check adapter status:
hciconfig
hciconfig hci0 up
Change
hci0to your adapter if multiple exist (hciconfig -a).
hcitool (Classic + LE):
sudo hcitool lescan
sudo hcitool scan
lescan — BLE devices only (continuous; Ctrl+C to stop)scan — Classic Bluetooth inquiry
bluetoothctl (interactive):
bluetoothctl
power on
scan on
# Wait for devices; note MAC addresses
scan off
devices
bleah (Python, BLE-focused):
sudo bleah -e
Use a dedicated sniffer (e.g., Sniffle, Ubertooth) to capture advertisements without connecting. Advertisements may reveal device names, service UUIDs, manufacturer data, and TX power.
Enumerate GATT services, characteristics, and descriptors to understand device functionality and identify read/write/notify targets.
Connect and discover services:
bluetoothctl
connect $BMAC
# BlueZ auto-discovers GATT; list appears after connection
menu gatt
list-attributes
# Navigate services/characteristics
select-attribute <UUID>
read
# Or: notify on / notify off
Interactive mode:
gatttool -b $BMAC -I
connect
# If connection fails, try:
connect -t random
# Or:
connect -t public
Primary services:
gatttool -b $BMAC -t random --primary
Characteristics:
gatttool -b $BMAC -t random --characteristics
Read/Write (non-interactive):
gatttool -b $BMAC -t random --char-read -a <HANDLE>
gatttool -b $BMAC -t random --char-write-req -a <HANDLE> -n <HEX_VALUE>
gatttool -b $BMAC -t random --char-write-cmd -a <HANDLE> -n <HEX_VALUE>
--char-write-req — write with response (reliable)--char-write-cmd — write without response (unreliable)Use
-t randomfor BLE Random Static Address or-t publicfor Public Address. Many BLE devices use random addresses.
| Layer | Purpose |
|---|---|
| Service | Logical grouping of characteristics (UUID) |
| Characteristic | Data value (read/write/notify); has UUID and handle |
| Descriptor | Metadata (e.g., Client Characteristic Configuration) |
Common service UUIDs (16-bit):
0x1800 — Generic Access (device name, appearance)0x180a — Device Information (manufacturer, model, firmware)0x180f — Battery Service0xffe0 — Common custom/vendor serviceRead Device Name, Manufacturer, Model, Firmware, and Serial from the Device Information service when present:
gatttool -b $BMAC -t random --char-read -a 0x000c
# Or enumerate handles first and read relevant characteristics
Capture and parse advertising packets for:
Traffic capture is required before security assessment. Set up a sniffer and capture the pairing sequence when a legitimate client connects. Sniffle provides reliable BLE sniffing with advertising channel hopping. Hardware: Sonoff Zigbee 3.0 USB Dongle Plus (CC26x2/CC1352) flashed with NCC Group’s Sniffle firmware.
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
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
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
Write Requests/Commands (replay targets):
_ws.col.info contains "Sent Write Request" || _ws.col.info contains "Sent Write Command"
Older Wireshark (opcode filter):
btatt.opcode == 0x12 || btatt.opcode == 0x52
Bluetooth Attribute ProtocolHandle and Value for replayAnalyze captured traffic to assess pairing behavior, encryption, and key distribution. Use the Sniffle capture from the previous section.
Pairing and encryption are visible in captured traffic. Open the capture in Wireshark and locate the pairing sequence (scroll near the moment the client first connected).
Wireshark filter:
btatt || btsmp || btle
Narrow by device:
(btatt || btsmp || btle) &&
(btle.advertising_address == $BMAC || btle.initiator_address == $BMAC)
Locate pairing:
Bluetooth Security Manager Protocol → Pairing Request / Pairing ResponseIO Capability, Authentication Requirements, Initiator Key Distribution, Responder Key DistributionSecurity mode reference:
| Value | Meaning | Security |
|---|---|---|
| 0x00 | Just Works | ❌ weak (MITM possible) |
| 0x01 | Bonding only | ❌ weak |
| 0x05 | MITM protection | ✔ passkey |
| 0x09 | LE Secure Connections | ✔✔ strong |
| 0x0D | Secure Connections + MITM | 🔒 strongest |
Encryption start (confirm encryption is used):
btle.ll_control.opcode == 0x03
# or
btle.ctrl.opcode == 3
# or (newer Wireshark)
btle && frame contains "ENC"
Confirm encryption sequence:
LL_ENC_REQLL_ENC_RSPLL_START_ENC_REQ — encryption starts hereTest which pairing method the device uses. Per OWASP IoT and industry checklists, evaluate:
| Method | MITM Resistance | Typical Use |
|---|---|---|
| Just Works | ❌ None | Headless, no display |
| Numeric Comparison | ✔ Yes | Both devices have display |
| Passkey Entry | ✔ Yes | One device has keyboard |
| Out-of-Band (OOB) | ✔ Yes | NFC, QR, etc. |
Document whether the device supports encryption downgrade (e.g., accepting Legacy Pairing when LE Secure Connections is available).
Check for known BLE stack and SDK vulnerabilities before exploitation:
After capturing a valid write from a legitimate client (e.g., phone app), replay it with gatttool:
gatttool --char-write-req --handle=$HANDLE --value=$VALUE -b $BMAC -t random
Use -t public instead of -t random if the device uses a public address.
Value format:
0100 for 0x01 0x00)-n with hex stringExample:
gatttool -b 8a:5b:aa:ff:f5:55 -t random --char-write-req -a 0x0025 -n 0100
If the device does not enforce pairing for sensitive characteristics:
Devices that accept commands without sequence numbers or nonces may be vulnerable to replay:
When pairing uses “Just Works” (no MITM protection):