Skip to content

Capturing BLE Traffic

This guide shows how to capture BLE traffic for debugging and protocol analysis.

MethodPlatformCaptures
btmon/btsnoopLinuxAll HCI traffic
Android HCI logAndroidPhone ↔ Device
Wireshark + nRF SnifferAnyOver-the-air

The easiest method on Linux is using btmon to capture HCI traffic.

  1. Open a terminal for btmon

    Terminal window
    sudo btmon -w capture.btsnoop
  2. In another terminal, connect to the device

    Terminal window
    bluetoothctl connect 5B:A6:86:38:FA:CA
  3. Perform operations (measurements, commands, etc.)

  4. Stop capture with Ctrl+C

Terminal window
# View packet summary
btmon -r capture.btsnoop | head -100
# Filter to ATT packets only
btmon -r capture.btsnoop | grep -E "(ATT|Write|Notify)"
# Export to pcap for Wireshark
btmon -r capture.btsnoop -w capture.pcap
> HCI Event: LE Meta Event (0x3e) plen 10
LE Connection Complete (0x01)
Status: Success (0x00)
Handle: 64
Address: 5B:A6:86:38:FA:CA (Random)
> ACL Data TX: Handle 64 flags 0x00 dlen 9
ATT: Write Command (0x52) len 4
Handle: 0x000a
Data: f1030205 ← GET_DEVICE_MAC command
< ACL Data RX: Handle 64 flags 0x02 dlen 22
ATT: Handle Value Notification (0x1b) len 17
Handle: 0x000c
Data: f103000c356261363836333866616361 ← Response

Enable Bluetooth HCI logging on your Android device.

  1. Enable Developer Options

    • Settings → About Phone → Tap “Build Number” 7 times
  2. Enable HCI Snoop Log

    • Settings → Developer Options → Enable Bluetooth HCI Snoop Log
  3. Reproduce the behavior in the Huepar app

  4. Disable logging when done

  5. Pull the log file

    Terminal window
    adb pull /data/misc/bluetooth/logs/btsnoop_hci.log

Open .btsnoop or .pcap files in Wireshark for detailed analysis.

# All ATT protocol packets
btatt
# Write operations only
btatt.opcode == 0x52 || btatt.opcode == 0x12
# Notifications only
btatt.opcode == 0x1b
# Specific handle (0xAE01 characteristic)
btatt.handle == 0x000a
# Packets containing F1 (our protocol header)
btatt.value contains f1

Wireshark shows ATT layer details:

Bluetooth Attribute Protocol
Opcode: Handle Value Notification (0x1b)
Handle: 0x000c (Characteristic Value)
Value: f103000c356261363836333866616361
│ │ └─ "5ba68638faca" ASCII
│ └──── Length: 12
└────────── Response type: MAC

If you have the mcbluetooth MCP server configured, you can capture directly:

Terminal window
# Start capture
bt_capture_start --output capture.btsnoop --adapter hci0
# ... perform operations ...
# Stop and get results
bt_capture_stop --capture-id <id>
# Parse the capture
bt_capture_parse --filepath capture.btsnoop --max-packets 100
ATT Write Command to handle 0x000A:
┌────────────────────────────────────────┐
│ F1 │ Type │ Sub │ Param │ Checksum │
└────────────────────────────────────────┘
ATT Handle Value Notification from 0x000C:
┌─────────────────────────────────────────────┐
│ F1 │ Response │ Status │ Data... │ Checksum │
└─────────────────────────────────────────────┘
  1. Correlate commands with responses - Match TX packets with subsequent RX

  2. Check checksums - Verify sum(bytes[1:]) % 256

  3. Note timing - Device may timeout if commands are slow

  4. Compare with app - Use Android HCI log alongside btmon for reference

  5. Focus on data bytes - Ignore ATT headers, focus on payload

Terminal window
# Terminal 1: Start capture
sudo btmon -w session.btsnoop
# Terminal 2: Connect and interact
bluetoothctl connect 5B:A6:86:38:FA:CA
# Use Python to send commands
python3 -c "
import asyncio
from bleak import BleakClient
async def main():
async with BleakClient('5B:A6:86:38:FA:CA') as c:
await c.write_gatt_char('0000ae01-...', bytes([0xF1,0x03,0x02,0x05]))
await asyncio.sleep(1)
asyncio.run(main())
"
# Terminal 1: Ctrl+C to stop, then analyze
btmon -r session.btsnoop | grep -A2 "Write\|Notify"