Bluetooth

Note

If you’re new to using Scapy, start with the usage documentation, which describes how to use Scapy with Ethernet and IP.

Warning

Scapy does not support Bluetooth interfaces on Windows.

What is Bluetooth?

Bluetooth is a short range, mostly point-to-point wireless communication protocol that operates on the 2.4GHz ISM band.

Bluetooth standards are publicly available from the Bluetooth Special Interest Group.

Broadly speaking, Bluetooth has three distinct physical-layer protocols:

Bluetooth Basic Rate (BR) and Enhanced Data Rate (EDR)

These are the “classic” Bluetooth physical layers.

BR reaches effective speeds of up to 721kbit/s. This was ratified as IEEE 802.15.1-2002 (v1.1) and -2005 (v1.2).

EDR was introduced as an optional feature of Bluetooth 2.0 (2004). It can reach effective speeds of 2.1Mbit/s, and has lower power consumption than BR.

In Bluetooth 4.0 and later, this is not supported by Low Energy interfaces, unless they are marked as dual-mode.

Bluetooth High Speed (HS)

Introduced as an optional feature of Bluetooth 3.0 (2009), this extends Bluetooth by providing IEEE 802.11 (WiFi) as an alternative, higher-speed data transport. Nodes negotiate switching with AMP.

This is only supported by Bluetooth interfaces marked as +HS. Not all Bluetooth 3.0 and later interfaces support it.

Bluetooth Low Energy (BLE)

Introduced in Bluetooth 4.0 (2010), this is an alternate physical layer designed for low power, embedded systems. It has shorter setup times, lower data rates and smaller MTU sizes. It adds broadcast and mesh network topologies, in addition to point-to-point links.

This is only supported by Bluetooth interface marked as +LE or Low Energy – not all Bluetooth 4.0 and later interfaces support it.

Most Bluetooth interfaces on PCs use USB connectivity (even on laptops), and this is controlled with the Host-Controller Interface (HCI). This typically doesn’t support promiscuous mode (sniffing), however there are many other dedicated, non-HCI devices that support it.

Bluetooth sockets (AF_BLUETOOTH)

There are multiple protocols available for Bluetooth through AF_BLUETOOTH sockets:

Host-controller interface (HCI) BTPROTO_HCI

Scapy class: BluetoothHCISocket

This is the “base” level interface for communicating with a Bluetooth controller. Everything is built on top of this, and this represents about as close to the physical layer as one can get with regular Bluetooth hardware.

Logical Link Control and Adaptation Layer Protocol (L2CAP) BTPROTO_L2CAP

Scapy class: BluetoothL2CAPSocket

Sitting above the HCI, it provides connection and connection-less data transport to higher level protocols. It provides protocol multiplexing, packet segmentation and reassembly operations.

When communicating with a single device, one may use a L2CAP channel.

RFCOMM BluetoothRFCommSocket

Scapy class: BluetoothRFCommSocket

RFCOMM is a serial port emulation protocol which operates over L2CAP.

In addition to regular data transfer, it also supports manipulation of all of RS-232’s non-data control circuitry (RTS, DTR, etc.)

Bluetooth on Linux

Linux’s Bluetooth stack is developed by the BlueZ project. The Linux kernel contains drivers to provide access to Bluetooth interfaces using HCI, which are exposed through sockets with AF_BLUETOOTH.

BlueZ also provides a user-space companion to these kernel interfaces. The key components are:

bluetoothd
A daemon that provides access to Bluetooth devices over D-Bus.
bluetoothctl
An interactive command-line program which interfaces with the bluetoothd over D-Bus.
hcitool
A command-line program which interfaces directly with kernel interfaces.

Support for Classic Bluetooth in bluez is quite mature, however BLE is under active development.

First steps

Note

You must run these examples as root. These have only been tested on Linux, and require Scapy v2.4.3 or later.

Verify Bluetooth device

Before doing anything else, you’ll want to check that your Bluetooth device has actually been detected by the operating system:

$ hcitool dev
Devices:
        hci0 xx:xx:xx:xx:xx:xx

Opening a HCI socket

The first step in Scapy is to open a HCI socket to the underlying Bluetooth device:

>>> # Open a HCI socket to device hci0
>>> bt = BluetoothHCISocket(0)

Send a control packet

This packet contains no operation (ie: it does nothing), but it will test that you can communicate through the HCI device:

>>> ans, unans = bt.sr(HCI_Hdr()/HCI_Command_Hdr())
Received 1 packets, got 1 answers, remaining 0 packets

You can then inspect the response:

>>> # ans[0] = Answered packet #0
>>> # ans[0][1] = The response packet
>>> ans[0][1]
>>> p.show()
###[ HCI header ]###
  type= Event
###[ HCI Event header ]###
     code= 0xf
     len= 4
###[ Command Status ]###
        status= 1
        number= 2
        opcode= 0x0

Receiving all events

To start capturing all events from the HCI device, use sniff:

>>> pkts = bt.sniff()
(press ^C after a few seconds to stop...)
>>> pkts
<Sniffed: TCP:0 UDP:0 ICMP:0 Other:0>

Unless your computer is doing something else with Bluetooth, you’ll probably get 0 packets at this point. This is because sniff doesn’t actually enable any promiscuous mode on the device.

However, this is useful for some other commands that will be explained later on.

Importing and exporting packets

Just like with other protocols, you can save packets for future use in libpcap format with wrpcap:

>>> wrpcap("/tmp/bluetooth.pcap", pkts)

And load them up again with rdpcap:

>>> pkts = rdpcap("/tmp/bluetooth.pcap")

Working with Bluetooth Low Energy

Note

This requires a Bluetooth 4.0 or later interface that supports BLE, either as a dedicated LE chipset or a dual-mode LE + BR/EDR chipset (such as an RTL8723BU).

These instructions only been tested on Linux, and require Scapy v2.4.3 or later. There are bugs in earlier versions which decode packets incorrectly.

These examples presume you have already opened a HCI socket (as bt).

Discovering nearby devices

Enabling discovery mode

Start active discovery mode with:

>>> # type=1: Active scanning mode
>>> bt.sr(
...   HCI_Hdr()/
...   HCI_Command_Hdr()/
...   HCI_Cmd_LE_Set_Scan_Parameters(type=1))
Received 1 packets, got 1 answers, remaining 0 packets

>>> # filter_dups=False: Show duplicate advertising reports, because these
>>> # sometimes contain different data!
>>> bt.sr(
...   HCI_Hdr()/
...   HCI_Command_Hdr()/
...   HCI_Cmd_LE_Set_Scan_Enable(
...     enable=True,
...     filter_dups=False))
Received 1 packets, got 1 answers, remaining 0 packets

In the background, there are already HCI events waiting on the socket. You can grab these events with sniff:

>>> # The lfilter will drop anything that's not an advertising report.
>>> adverts = bt.sniff(lfilter=lambda p: HCI_LE_Meta_Advertising_Reports in p)
(press ^C after a few seconds to stop...)
>>> adverts
<Sniffed: TCP:0 UDP:0 ICMP:0 Other:101>

Once you have the packets, disable discovery mode with:

>>> bt.sr(
...   HCI_Hdr()/
...   HCI_Command_Hdr()/
...   HCI_Cmd_LE_Set_Scan_Enable(
...     enable=False))
Begin emission:
Finished sending 1 packets.
...*
Received 4 packets, got 1 answers, remaining 0 packets
(<Results: TCP:0 UDP:0 ICMP:0 Other:1>, <Unanswered: TCP:0 UDP:0 ICMP:0 Other:0>)

Collecting advertising reports

You can sometimes get multiple HCI_LE_Meta_Advertising_Report in a single HCI_LE_Meta_Advertising_Reports, and these can also be for different devices!

# Rearrange into a generator that returns reports sequentially
from itertools import chain
reports = chain.from_iterable(
  p[HCI_LE_Meta_Advertising_Reports].reports
  for p in adverts)

# Group reports by MAC address (consumes the reports generator)
devices = {}
for report in reports:
  device = devices.setdefault(report.addr, [])
  device.append(report)

# Packet counters
devices_pkts = dict((k, len(v)) for k, v in devices.items())
print(devices_pkts)
# {'xx:xx:xx:xx:xx:xx': 408, 'xx:xx:xx:xx:xx:xx': 2}

Filtering advertising reports

# Get one packet for each device that broadcasted short UUID 0xfe50 (Google).
# Android devices broadcast this pretty much constantly.
google = {}
for mac, reports in devices.items():
  for report in reports:
    if (EIR_CompleteList16BitServiceUUIDs in report and
        0xfe50 in report[EIR_CompleteList16BitServiceUUIDs].svc_uuids):
      google[mac] = report
      break

# List MAC addresses that sent such a broadcast
print(google.keys())
# dict_keys(['xx:xx:xx:xx:xx:xx', 'xx:xx:xx:xx:xx:xx'])

Look at the first broadcast received:

>>> for mac, report in google.items():
...   report.show()
...   break
...
###[ Advertising Report ]###
  type= conn_und
  atype= random
  addr= xx:xx:xx:xx:xx:xx
  len= 13
  \data\
   |###[ EIR Header ]###
   |  len= 2
   |  type= flags
   |###[ Flags ]###
   |     flags= general_disc_mode
   |###[ EIR Header ]###
   |  len= 3
   |  type= complete_list_16_bit_svc_uuids
   |###[ Complete list of 16-bit service UUIDs ]###
   |     svc_uuids= [0xfe50]
   |###[ EIR Header ]###
   |  len= 5
   |  type= svc_data_16_bit_uuid
   |###[ EIR Service Data - 16-bit UUID ]###
   |     svc_uuid= 0xfe50
   |     data= 'AB'
  rssi= -96

Setting up advertising

Note

Changing advertisements may not take effect until advertisements have first been stopped.

Eddystone URL beacon

This example sets up a virtual Eddystone URL beacon:

# Load the contrib module for Eddystone
load_contrib('eddystone')

# Eddystone_URL.from_url() builds an Eddystone_URL frame for a given URL.
#
# build_set_advertising_data() wraps an Eddystone_Frame into a
# HCI_Cmd_LE_Set_Advertising_Data payload, that can be sent to the BLE
# controller.
bt.sr(Eddystone_URL.from_url(
  'https://scapy.net').build_set_advertising_data())

Once advertising has been started, the beacon may then be detected with the Eddystone Validator (Android):

../_images/ble_eddystone_url.png

Starting advertising

bt.sr(HCI_Hdr()/
      HCI_Command_Hdr()/
      HCI_Cmd_LE_Set_Advertise_Enable(enable=True))

Stopping advertising

bt.sr(HCI_Hdr()/
      HCI_Command_Hdr()/
      HCI_Cmd_LE_Set_Advertise_Enable(enable=False))

Resources and references

  • 16-bit UUIDs for members: List of registered UUIDs which appear in EIR_CompleteList16BitServiceUUIDs and EIR_ServiceData16BitUUID.
  • Company Identifiers: List of company IDs, which appear in EIR_Manufacturer_Specific_Data.company_id.
  • Generic Access Profile: List of assigned type IDs and links to specification definitions, which appear in EIR_Header.