BLE and Bluetooth Low Energy penetration testing guide covering scoped methodology, discovery, Sniffle traffic capture, GATT enumeration, pairing and encryption analysis, 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.
Follow a consistent order so you do not miss pairing traffic or scope:
The sections below follow this flow: Discovery → Traffic capture → Enumeration → Security assessment → Exploitation.
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. Parse advertising payloads for:
Traffic capture supports security analysis; timing matters more than tool choice. For pairing and key distribution, begin recording before the first bond or reconnect that establishes keys. For application-layer testing, capture while the official client drives the peripheral so write opcodes and handles match real use. Enumeration below does not replace capture—it tells you which handles matter once you have a PCAP.
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. Make sure the device is the CC26x2/CC1352 chipset.
Pair With nRF52840
Using this alongside with the nRF52840 makes for a highly capable setup.
Scripts complementary to Pentest Partners (BLE series).
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 replayReplay the capture writes using an nRF52840
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
| 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 revision, and serial number from the Device Information service (0x180a) when present. Handles vary by firmware; enumerate GATT first, then read characteristics under that service.
gatttool -b $BMAC -t random --char-read -a 0x000c
# Or enumerate handles first and read relevant characteristics under 0x180a
Analyze PCAPs from passive or Sniffle capture to assess pairing behavior, encryption, and key distribution, alongside what you observed during live enumeration.
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 DistributionInterpret SMP fields (do not equate AuthReq hex with “pairing method”):
NoInputNoOutput on both sides often yields Just Works). It is not a single “security mode” byte.AuthReq) is a bitmask on the same PDU: bonding intent, MITM (authenticated pairing required), LE Secure Connections (SC) vs legacy short-term key path, and related flags. Decode the expanded fields in Wireshark rather than memorizing one magic byte.Example AuthReq patterns (illustrative; always confirm in Wireshark): 0x01 bonding without MITM/SC; 0x05 bonding and MITM; 0x09 bonding and SC; 0x0d bonding, MITM, and SC.
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:
Recommended Method
Replay
writescaptured by Sonoff/Sniffle dongle
Using an nRF52840 USB dongle flashed with April Brother ble_connectivity firmware supports scripted or host-driven connection tests and replay-style workflows alongside captures.
Download the nRF Connect for Desktop application from Nordic to use the dongle and replay writes.
If nRF Connect Does Not Detect Dongle When Using Linux
Add The Following to
/etc/udev/rules.d/99-nordic.rulesSUBSYSTEM=="usb", ATTR{idVendor}=="1915", MODE="0666" SUBSYSTEM=="usb", ATTR{idVendor}=="1366", MODE="0666"
Add The Following to/etc/udev/rules.d/99-nordic-serial.rulesSUBSYSTEM=="tty", ATTRS{idVendor}=="1915", MODE="0666" SUBSYSTEM=="tty", ATTRS{idVendor}=="1366", MODE="0666"
Add User to Group then Reloadsudo usermod -aG plugdev $USER sudo usermod -aG dialout $USER sudo udevadm control --reload-rules sudo udevadm trigger sudo reboot now
You should have rw access as your user:ls -l /dev/ttyACM0
After capturing a valid write from a legitimate client (e.g., phone app), replay it with gatttool using the attribute handle and hex payload from the PCAP:
Read/Write (non-interactive):
gatttool -b $BMAC -t random --char-read -a $HANDLE
gatttool -b $BMAC -t random --char-write-req -a $HANDLE -n $VALUE
gatttool -b $BMAC -t random --char-write-cmd -a $HANDLE -n $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. SetHANDLEin hex (e.g.,0x0025); setVALUEas a contiguous hex string with no spaces (e.g.,0100for bytes0x01 0x00).
Use -t public instead of -t random if the device uses a public address.
Example:
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):