Share

engineering

19min read

Bluetooth Low Energy Sniffing Guide Part 2

Bluetooth Low Energy Sniffing Guide Part 2

In the previous part of the Bluetooth Low Energy Sniffing Guide, we learned how advertisement, connection establishment, and communication work on Link Layer level. Today, we will focus on higher level layers and learn all the basics, which will guide you through your sniffing challenges.

L2CAP, ATT & GATT

Everything which we described up to this point exists in the Low Energy Controller volume. It can be thought of as the lowest level transport protocol upon which we define other transport layers. That’s the case with Bluetooth Low Energy stack and we need to cover more things to be able to understand how does the actual communication with TI SensorTag work:

image1

In this guide, I will briefly cover only green parts. So far Link Layer gave us a tool, which handles device discovery, connection establishment, connection control and Data PDUs with a simple retry mechanism and the CRC validation included.

Next layer called Logical Link Control and Adaptation Protocol (L2CAP) supports a higher level multiplexing, packet segmentation and reassembly, and the conveying of quality of service information. There are a lot of features and modes described in CS Vol 3, Part A which is dedicated to L2CAP, but I’ll go through only essential things today.

The most important are the virtual channels. They are used over LL PDUs to simulate logical links, being entirely independent of each other. Every channel has a unique Channel ID, with a pool of fixed channels and dynamically allocated channels. Bluetooth Low Energy devices, which are utilizing Low Energy Controller must implement and enable the use of following channels after a connection over Link Layer is established (CS Vol 3, Part A, 2.1):

  1. Low Energy L2CAP Signaling Channel (0x0005)—is used to manage current channels, destroy them and create new ones. Not in the scope of this guide.
  2. Attribute Protocol Channel (0x0004)—is allowed to send only ATT packets. Preconfigured with minimum SDU equal to 27, infinite flush time, best effort QoS and operating in Basic Mode (CS Vol 3, Part G, 5.2.2).
  3. Security Manager Protocol Channel (0x0006)— defines the protocol and behavior to manage pairing, authentication and encryption between LE devices. Fully covered in CS Vol 3, Part H (not in the scope of this guide).

Attribute Protocol (ATT), that you send through L2CAP Channel ID 4 is probably the most common packet type, that you’ll see in your sniffer traces. The general idea concentrates around so-called attributes which are defined by the following components:

  1. UUID (16 bits, 32 bits or 128 bits, CS Vol 3, Part F, 3.2.1)—this value defines a type of an attribute. Each type has predefined value schema, which should be properly interpreted by upper layers of the protocol. There are a lot of assigned UUIDs with the following base: XXXXXXXX-0000-1000-8000-00805F9B34FB. For example 00002A00-0000-1000-8000-00805F9B34FB represents Device Name type with UTF8 value. It can be written in the shorter 16 bit UUID form: 2A00.
  2. Handle (2 bytes, CS Vol 3, Part F, 3.2.2)—the unique handle of an attribute. Due to size limitations, there can be a maximum number of 65535 attributes. Handles are specific to the device and in a few cases, they can be cached by a master (CS Vol 3, part G, 2.5.2). Otherwise, they are discovered during the discovery phase on each connection attempt.
  3. Value (max 512 bytes, CS Vol 3, Part F, 3.2.4)–the value of an attribute. It can be a fixed length or varying length; the schema of value should be determined by its UUID.
  4. Permissions (implementation defined, CS Vol 3, Part F, 3.2.5)—permissions can’t be discovered directly using ATT protocol and are defined by the higher level protocol. Attributes can have read/write permissions, be optionally encrypted, require authentication or authorization.

Attribute Protocol defines a handful of operations which allow you to read, write and discover attributes. A table below shows most of them:

Request Input Output Description

Find Information

(CS 5.0, Vol 3, Part F, 3.4.3.1)

Handle range

List<(handle, UUID)>

List of handles with their UUID in specified handle range.

Find By Type Value

(CS 5.0, Vol 3, Part F, 3.4.3.3)

Handle range, 16 bit UUID, Value

List<(handle, handle)>

Return list of handles (with optional grouping handle) which are contained in specific handle range and have UUID/Value input pair.

Read By Type

(CS 5.0, Vol 3, Part F, 3.4.4.1)

Handle range, UUID

List<(handle, value)>

Return list of handles with their value in certain handle range and of a specific type.

Read

(CS 5.0, Vol 3, Part F, 3.4.4.3)

handle

value

Return value of specific attribute based on its handle.

Read Blob

(CS 5.0, Vol 3, Part F, 3.4.4.5)

Handle, offset

value

Read part of a specific attribute. The only option to read long attribute’s value.

Read multiple

(CS 5.0, Vol 3, Part F, 3.4.4.7)

List<handle>

List<value>

Read multiple values from set of handle attributes. Only last attribute is allowed to return variable length payload.

Read by Group Type

(CS 5.0, Vol 3, Part F, 3.4.4.9)

Handle range, UUID

List length, List<value>

Read a values of attributes of specified grouping type.

Write

(CS 5.0, Vol 3, Part F, 3.4.5.1)

Handle, Value

(opcode only)

Write value to an attribute with specific handle.

Write Command

(CS 5.0, Vol 3, Part F, 3.4.5.3)

Handle, Value

-

Write value to an attribute with specific handle without response.

Prepare Write

(CS 5.0, Vol 3, Part F, 3.4.6.1)

Handle, Offset, Value

Handle, Offset, Value

Prepare chunk of data to be queued for write to specific attribute handle. This command is useful for long attributes.

Execute Write

(CS 5.0, Vol 3, Part F, 3.4.6.1)

(Cancel/WriteAll) flag

(opcode only)

Cancel or write all data queued using “Prepare Write” request.

Handle Value Notification

(CS 5.0, Vol 3, Part F, 3.4.7.1)

-

Handle, Value

Value of specific attribute sent by a server.

Handle Value Indication

(CS 5.0, Vol 3, Part F, 3.4.7.2)

-

Handle, Value

Value of specific attribute sent by a server. Client should send ACK.

There are a lot of possible commands, which can be sent by a client. The question is, how can a mobile phone actually know what it can do with a device, to which it connected This process is defined by the Generic Attribute (GATT) protocol and in most cases, ATT packet exchange starts with a discovery phase. Will will start with that as well.

Discovery process

Discovery process is a phase where the central device creates an in-memory representation of the client’s attribute tree consisting of services, characteristics, and descriptors. These three entities are attributes with special meaning defined by GATT protocol. Here’s an example—the following graph shows part of the TI SensorTag’s attributes hierarchy:

image12

image5

Services are grouping attributes which define the specific functionality of a device. As you may notice, the device can advertise service UUIDs, which are supported in its advertisement data during the scanning process. You can find a list of predefined services (like Blood Pressure service) here.

Characteristics are attributes which commonly support the read/write/notify commands. They can be used for example to control the device’s specific behaviors or to get info about the state of the sensor. As always, a list of predefined characteristics is available on the Bluetooth web page.

Finally, descriptors define a characteristic to which they belong. The most common type is the Client Configuration Characteristic (used to control a notification and indication registration) and Characteristic User Description (used to provide a human-readable description of functionality). The full predefined list is presented here.

In the nRF Connect application, after connection establishment, you can explore available services, characteristics and descriptors. In the attached image above you can see the list of Gyroscope characteristics of the TI Sensor Tag device.

image8

Going back to the Wireshark sniffing we can see that a lot of packets are exchanged during the discovery process. In my case, both devices transmitted 556 packets to recreate attribute tree and it took 8340 ms (!) to complete the whole operation. This is why it’s important to keep a low number of characteristics if you want to get fast reconnection time when the attribute tree is not cached.

Everything starts with the “Read By Group Type” request which searches for GATT Primary Service Declaration (0x2800, CS Vol 3, Part G, 4.4) between 0x0001 (minimum attribute handle) and 0xFFFF (maximum attribute handle):

image10

There are 4 PDUs nested in total and in the following figures we won’t include L2CAP PDU and lower level PDUs as nothing interesting will change here in the case of sniffed ATT packets. The response to the above request contains a finite list of attribute values in the form of triplets:

image14

Following fields are:

  • Opcode (0x11) - Read By Group Type Response opcode.
  • Length (0x06) - the size of each attribute data.
  • For each attribute data (3 in total for this packet, values are presented for the first field)
    • Handle (0x000C)- Handle of this GATT Primary Service Declaration.
    • End handle (0x000F) - Group end handle. It’s the last handle, contained inside declared service. All characteristics and descriptors are contained between 0x000C and 0x000F.
    • UUID (0x1800) - UUID of declared service. In this case it’s the Generic Access Profile.

    The number of attribute values contained inside of the packet depends on negotiated ATT_MTU (in my case it is 23 - minimal value). Above payload is 20 bytes long and server cannot add another field as it won’t fit in the packet. The client can detect other primary services by issuing a new request with start handle bumped to the maximum received end group handle plus one (0x0023). The list is complete when the server responds with Attribute Not Found error (CS Vol 3, Part F, 3.4.1.1) or 0xFFFF handle is present as an End Group Handle inside the response. You can add following Wireshark filter “btatt.opcode.method == 0x11” above nRF Sniffer Toolbar to see all Read By Group Type responses. TI Sensor Tag exposes 13 services. In the nRF Connect iOS application 0x1800 and 0x1801 are not visible to the user and are hidden by the OS.

    Once Primary Services and Secondary Services are discovered we can proceed to characteristics discovery. We must use “Read By Type” request with GATT Characteristic Declaration (CS Vol 3, Part G, 4.6.1 and CS Vol 3, Part G, 3.3.1):

    image17

    Fields:

    • Opcode (0x08)—Read By Type Request opcode
    • Start handle (0x000C)—we used the start handle value received from the Generic Access Profile Service Definition
    • End handle (0x000F)—We used end handle value received from Generic Access Profile Service Definition
    • Attribute type (0x2803)—GATT Characteristic Declaration attribute type

    This request will allow us to get information about every characteristic contained in specified service, which is defined implicitly here by handle range (0x000C-0x000F). As a result we will get all the necessary details inside the attribute value for each characteristic declaration we received in the packet:

    image19

    Fields:

    • Opcode (0x09) - Read By Type Response opcode.
    • Length (0x07) - Length of each record in the payload. This message got only one record with the following values:
      • Handle (0x000D)—Handle of the GATT Characteristic Declaration.
      • Characteristic Properties (0x20) (CS Vol 3, Part G, 3.3.1.1)—Bitflag representing available properties of the characteristic. In this case it is Indicatable.
      • Characteristic Value Handle (0x000E)—Handle of the Attribute containing the value of this characteristic.
      • Characteristic UUID (0x2A05)—UUID of the characteristic. It’s a Service Changed characteristic.

    Central device can also search characteristics by UUID when service handle range is known to it (described in detail in CS Vol 3, Part G, 4.6.1).

    The last step of the discovery process, which is in most cases optional and can be lazily evaluated, is the discovery of descriptors (CS Vol 3, Part G, 4.7). Find Information Request should be used with starting handle set to the handle of specified characteristic value + 1, and the ending handle set to the ending handle of the specified characteristic. Let’s look at the example:

    image15

    Fields:

    • Opcode (0x04)—Find Information Request opcode
    • Starting handle (0x0026)—In the case of my SensorTag TI, this handle is one above the Temperature Characteristic’s value
    • End handle (0x0027)—a handle of the next GATT Characteristic Declaration handle minus one

    Based on the handle range, we expect to get two attributes in between:

    image21

    Fields:

    • Opcode (0x05)—Find Information Response opcode
    • Format (0x01)—in the response we are receiving (handle/UUID) pair and this fields define the size of UUID (16 bits for 0x01 and 128 bits for 0x02)
    • Handle #1 (0x0026)—a handle of the first attribute
    • UUID #1 (0x2902)—a UUID of Client Characteristic Configurationdescriptor
    • Handle #2 (0x0027)—a handle of the second attribute
    • UUID #2 (0x2901)—a UUID of Characteristic User Description descriptor

    To sum up, the following three types of requests and responses: Read By Group Type (for services), Read By Type (for characteristics) and Find Information (for descriptors), are the essence of the discovery procedure. Below you can find parts of the attribute tree for the TI Sensor Tag device (see the complete table here), put in order by their handle identifiers. I hope this helps you visualize what is the end result of the discovery process from central’s point of view:

    TI Base UUID: F0000000-0451-4000-B000-00000000xxxx. 128-bit UUIDs are typed 'bold'

    Handle UUID UUID (name) Value

    ...

    ...

    ...

    ...

    0x0C

    0x2800

    GATT Primary Service Declaration

    0x1801 (Gatt Service)

    0x0D

    0x2803

    GATT Characteristic Declaration

    0x200E00052A

    0x0E

    0x2A05

    GATT Service Changed

    -

    0x0F

    0x2902

    GATT Client Characteristic Conf.

    0x0000

    ...

    ...

    ...

    ...

    0x23

    0x2800

    GATT Primary Service Declaration

    0xAA00 (Temp. service, value is 128bits)

    0x24

    0x2803

    GATT Characteristic Declaration

    0x12250001AA

    0x25

    0xAA01

    Temperature Data

    0x00000000

    0x26

    0x2902

    GATT Client Characteristic Conf.

    0x0000

    0x27

    0x2901

    GATT Characteristic User Desc.

    “IR Temp. Data” (14 bytes)

    0x28

    0x2803

    GATT Characteristic Declaration

    0x0A290002AA

    ...

    ...

    ...

    ...

    Characteristic Read

    Once we know everything about services, characteristics, and descriptors, we can actually play with them. The first of the most common operations is a read command. On my SensorTag, I chose to use the gyroscope service and its underlying characteristics as an example.

    Turn on your sniffer, open Wireshark application and make sure you are connected to the SensorTag via nRF Connect application. As we will work with messages, which are rarely transmitted during connection intervals, it’s a good idea to filter out all empty packets. You can do that by applying a simple btle.data_header.length > 0 filter:

    image16

    image22
    image6

    In the nRF application select AA50 service UUID, which is a Gyroscope Service. Then, by clicking on it, you can see all contained characteristics, which are (in order):

    • AA51—Gyroscope Data (handle 0x0060)—stores 6 byte value, where 2 bytes are reserved for each axis
    • AA52—Gyroscope Config (handle 0x0064)—first three bits determine if measurements for particular axes are enabled. Value of 0x07 enable all of them (X, Y & Z)
    • AA53—Gyroscope Period (handle 0x0067)—contains a value determining how often gyroscope measurements are sent via notifications if they are enabled

    On the “Characteristics” screen in the nRF Connect app you can also see available actions on the right side of each characteristic:

    • Read operation (1)—visualized as an arrow directed to the bottom pointing down
    • Enable/Disable notifications (2)—visualized as three arrows pointing down
    • Write operation (3)—visualized as an arrow pointing up

    Numbers of buttons are mentioned in the attached screenshot of the nRF Connect application. The first one to try out is the read operation (1). Triggering it will send ATT Read Request PDU to the TI Sensor Tag:

    image13

    Fields:

    • Opcode (0x0A)—Read Request opcode
    • Handle (0x0060)—Handle of an attribute, which stores a value of Gyroscope Data Characteristic

    Read Response is as simple to interpret as a request:

    image4

    Fields:

    • Opcode (0x0B)—Read Response opcode
    • Value (0x000000000000)—Value of Gyroscope Data Characteristic

    In this specific case, data is very short (only 6 bytes). ATT protocol supports values as long as 512 bytes and in such situations, ATT_MTU is a limitation. There is a different operation to handle called “Read Blob Request” which allows for a specifying offset of value in the request (CS Vol 3, Part F, 3.4.4.5). Unfortunately, both iOS and Android APIs don’t allow us to use this feature directly and the operating system automatically selects a proper version.

    Characteristic Write

    As we can see, the zeroed value may indicate that the Gyroscope sensor is turned off. We need to enable it first to be able to read actual angular velocity. This can be done, by adding a value to the Gyroscope Config Characteristic (button 3 in the nRF Connect app):

    image3

    Fields:

    • Opcode (0x12)—Write Request opcode
    • Handle (0x0064)—Handle of an attribute, which stores a value of Gyroscope Config Characteristic
    • Value (0x07)—the actual value to be added to the attribute. It’s a bit mask (0b111), and each bit indicates if a specific axis is enabled or not.

    TI Sensor Tag acknowledges this transaction, by sending appropriate opcode:

    image7

    Field:

    • Opcode (0x13)—Write Response opcode

    Now we can read the Gyroscope Data Characteristic value once again and we get a meaningful result this time. There are two more things worth mentioning:

    • As you may have noticed, write response (and other operations as well) doesn’t contain any “transaction id” field, which corresponds to the request. Therefore, ATT protocol assumes that the communication is serial and that different request/response packets shouldn’t interleave with each other. The only exception is a notification, which may happen at any time. More info can be found in the specification in CS Vol 3, Part F, 3.3.2.
    • There is also an option to send a write request without confirmation. It’s called “Command” and comes in handy to achieve maximum ATT protocol throughput (CS Vol 3, Part F, 3.4.5.3).

    Characteristic Notification

    Lastly, we can turn on notifications to be able to track Gyroscope data values periodically without a need to send read requests every time. Both notifications and indications are managed by Client Characteristic Configuration Descriptor, which remembers settings per connection and even holds the information for bonded devices (CS Vol 3, Part G, 3.3.3.3). This procedure is executed by clicking the button number 2 in the nRF Connect app. Initially, we are turning notifications on:

    image18

    Fields:

    • Opcode (0x12)—Write Request opcode
    • Handle (0x0061)—Handle to CCC Descriptor of Gyroscope Data Characteristic
    • Value (0x0001)—First bit sets notification to be turned on

    To turn notifications off, we must set the notification bit to 0:

    image11

    As a result, TI SensorTag will start to periodically send us the following payload (with varying values), which is a notification:

    image2

    Fields:

    If the attribute value is longer, then ATT_MTU - 3, which in most cases is 20 bytes, will be truncated. If this happens, You need an additional read operation (CS Vol 3, Part F, 3.4.7.1).

    Summary

    And that’s it! You learned all the basics related to BLE sniffing. I strongly recommend going through the Bluetooth Core Specification too. If you still have problems with outlining relations between GATT and ATT protocols, check out CS Vol 3, Part G, 4.13. Over there you’ll be able to clearly see mappings between GATT procedures and ATT commands. Good luck!

Share

Przemek

Staff Software Engineer

Did you enjoy the read?

If you have any questions, don’t hesitate to ask!

Did you enjoy the read?

If you have any questions, don’t hesitate to ask!