In my second semester for my post graduate study, I took up a project work as my "main module" for the semester. Since it is equivalent to 2 modules, my workload was more or less set to that project.
The project was to build a SDP fuzzer on top of a L2CAP fuzzer. For those who do not know, L2CAP is one of the core protocols in Bluetooth that manages the connection between master and slave devices. SDP (short for Service Discovery Protocol) is a protocol that sits on top of L2CAP.
I will use this post to do a simple introduction of SDP, with an example on how to construct a request packet.
Service Discovery Protocol
The Service Discovery Protocol (SDP) offers a framework for applications to identify available services and understand their features. In the Bluetooth context, where services can dynamically change as devices move and come into RF proximity, the process of service discovery differs significantly from traditional network environments.
Specifically designed for Bluetooth, the Bluetooth Service Discovery Protocol (SDP) caters to the unique, fast-changing conditions of Bluetooth communications. Its primary role is to locate services that can be accessed via Bluetooth devices. However, SDP itself does not specify how to access these services once found. Instead, after SDP identifies available services, other protocols and methods can be used for service access. Although SDP can function alongside other discovery protocols, it operates independently, ensuring that services in Bluetooth settings are located with SDP and then accessed using the relevant Bluetooth-defined protocols.
The interaction in SDP involves an SDP Server and an SDP Client. The server holds a database of service records, each describing a single service's attributes. A client can obtain these details by sending an SDP request to the server. When a client or its associated application opts to use a service, it establishes a separate connection to the service provider. Thus, while SDP effectively discovers services and outlines their attributes (including how to access them), it does not handle the actual utilization or delivery of these services.
In scenarios where a device hosts multiple applications offering services, an SDP Server can represent all these service providers by managing requests for their service information. Likewise, an SDP Client can serve multiple client applications by querying various servers. It is important to note that the available SDP Servers change dynamically depending on their RF proximity to the client. When a new server comes into range, the client is informed through mechanisms outside of SDP, allowing it to then use SDP to request further service details. Conversely, if a server moves out of range or becomes unavailable, SDP does not send an explicit notification; the client must infer the server’s absence through unsuccessful responses to its queries.
While L2CAP is a stateful protocol, SDP is mostly stateless. It does have a continuation state for the search responses, for the purpose of retrieving segmented data. (Sometimes the data is too huge to be sent in one single packet.)
SDP Transactions
There are 7 protocol data unit (PDU) IDs associated with SDP transactions.
0x01 Error response
0x02 Service Search Request
0x03 Service Search Response
0x04 Service Attribute Request
0x05 Service Attribute Response
0x06 Service Search Attribute Request
0x07 Service Search Attribute Response
Other values are reserved for future use. From the list of PDU IDs, we can see that there are 3 main types of transactions:
- Service Search – Used to locate service records that match the service search pattern in the request. The SDP server will return a list of service record handles that match the given search pattern.
Service Attribute – Used to retrieve specific attribute values from a service record. The SDP server will return a list of attributes (attribute ID and values).
Service Search Attribute – Used to combine the capabilities of Service Search and Service Attribute requests into a single request. It can be used to reduce SDP transactions especially if there are multiple service records to be retrieved. The request is more complex and the SDP server may return partial list with continuation state. In this case, the client will need to resend the request with the continuation state to obtain the next part of the list.
If the PDU is not correctly formatted or when the SDP server cannot generate an appropriate response, the SDP server will generate an Error response (0x01). There are six errors assigned to error codes:
- Invalid SDP version
- Invalid Service record handle
- Invalid request syntax
- Invalid PDU size
- Invalid continuation state
- Insufficient resource to satisfy Request
SDP Protocol Format
Every SDP packet consists of a PDU header and its parameters. A PDU header contains 3 fields: PDU ID, Transaction ID and the length of the parameters. The parameters may be represented by a data element.
A data element is a typed data representation, which contains a type descriptor, size descriptor, an optional size value for data sequence and the data. The type descriptor and size descriptor makes up the header field of the data element.
A full primer of the SDP data format is available here.
Example:
Supposing I want to make a Service Attribute Request (PID: 0x04). The service record handle that I am querying is 0x00010000. I want to enquire service attributes 0x0001 and 0x0002. There is no continuation state.
Let's work on the data element for the service attributes. For service attributes, the data type is unsigned integer (type code: 0x01) and its size will be 2 bytes (size code: 0x01). Now, based on the data element specification, the type descriptor will take up 5 bits and size descriptor will take up 3 bits.
Type code: 0x01 = 00001
Size code: 0x01 = 001
1st byte for the data element: 0x09 = 00001001
So the data element for the service attributes will be:
0x0001: 09 00 01 (3 bytes)
0x0002: 09 00 02 (3 bytes)
Joining them together, we will have:
09 00 01 09 00 02
Note that the first byte of each data element is 0x09, as we have worked out previously. Also note that the attribute list is 6 bytes.
As we are querying for a list of service attributes, the 2 attribute handles will be encapsulated in a data sequence element.
Type code: 0x06 = 00110 (Data sequence)
Size code: 0x05 = 101 (The data size is contained in the additional 8 bits, which are interpreted as an unsigned integer.)
1st byte for the data element: 0x35 = 00110101
2nd byte for the data element will be the parameter size, which is 6 bytes: 0x06
The full attribute list will be:
35 06 09 00 01 09 00 02
Note that the size of this full attribute list is 8 bytes.
A Service Attribute Request comprises of a few elements:
- PID (1 byte)
- Transaction ID or TID (2 bytes)
- Payload length (2 byte)
- Service record handle (4 bytes)
- Max attribute byte count (2 bytes)
- Service attribute list (variable)
- Continuation State (variable)
Note that payload length is the parameter length, which excludes PID and TID.
Assuming that there is no continuation state for our example, then our datagram will be:
PID: 0x04
TID: 0x0001 (arbitrary value)
Payload length: 4 + 2 + 8 + 1 = 15 = 0x000F
Service record handle: 0x00010000 (4 bytes)
Max Attribute byte count: 0x0007 (Minimum) (2 bytes)
Service attribute list: 0x3506090001090002 (derived earlier, 8 bytes)
Continuation State: 0x00 (1 byte)
Complete datagram:
04 00 01 00 0F 00 07 35 06 09 00 01 09 00 02 00
There we go, we have our SDP request. This datagram will be appended to the L2CAP packet when sent via Bluetooth.
We will discuss on my approach in fuzzing SDP in the next post.



Top comments (0)