On iOS the Bluetooth stack is split into three layers as shown in Figure 1. At the top is the CoreBluetooth framework used by iOS app developers and at the bottom is the Bluetooth hardware itself. In between these two layers is a collection of daemons that implement various aspects of the Bluetooth stack. For example, most Bluetooth Low Energy (BLE) specific functionality is contained in the BTLEServer daemon. BlueTool is one of the daemons running and is primarily responsible for acting as a bridge between the rest of the Bluetooth stack and the hardware. It also likely acts as an internal test tool at Apple during development.
All analysis in this article was carried out on a 5th generation iPod Touch running iOS 8.4 (12H143). My primary focus during analysis was on understanding what functionality BlueTool provides. I was not actively looking for bugs despite finding a couple.
|[[/assets/images/2015/12/iOS-Bluetooth-Overview.png||alt=”Overview of the iOS Bluetooth stack.”]]|
BlueTool is started automatically at boot by launchd and may also be started manually. When it is started automatically by launchd, BlueTool is executed with an undocumented
-R flag that can be seen by either inspecting the running processes or looking at the launchd plist file for the executable. If BlueTool is started manually it will drop the user into an interactive shell as shown in the listing below.
When BlueTool is started by launchd it creates an XPC service called
com.apple.BlueTool and ignores all input from stdin. Most output is logged to stdout or stderr.
In addition to the interactive shell and XPC service, data is also sent to and received from the Bluetooth hardware. This communication is carried out over a UART connection and uses the Bluetooth Host-Controller Interface (HCI) command set.
When BlueTool is started by launchd the undocumented
-R flag causes two things to happen:
The XPC service handler first asserts that the message dictionary contains the
kBluetoolMessageId key and then selects a command-specific handler based on its value. Possible values are 2 through 5 inclusive. Note that the values 0 and 1 are not bound to any handler, it is unclear to me why.
|2||Process given file path as script.|
|3||Process named script.|
|5||Return version information.|
The following are summaries of each command.
Setting kBluetoolMessageId equal to two causes the program to then look for the
script key. The value of this key is a string that contains a path to a script to be run by BlueTool. The script file is simply a single command per line.
During testing I noticed that the file is opened using
fopen() with no checks beforehand to ensure the path is valid. As a result it is possible to read files at other locations. Although this is a directory traversal vulnerability there are two caveats:
Since my testing was on a jailbroken device it is unclear to me if the sandbox had been disabled. I did find it possible to read
/etc/passwd however this should be tested on a non-jailbroken device. Regardless, due to the BlueTool process being started by launchd we typically don’t have control or access to stderr. As a result, this bug is likely not very interesting.
This command allows a client of the XPC service to execute a named script. The three script names I’ve found so far are: boot, init, and deepsleep.
When kBluetoolMessageId is set to 4 BlueTool will then search for a dictionary key of
command. The string associated with this key is then split into substrings using
strtok and passed to the central command processor used by both the XPC service and the interactive shell.
The final command simply replies with the chip model, firmware version, and manufacturer as strings. This command takes no inputs.
As was shown previously, BlueTool supports a number of commands. Most commands accept the
-h flag to show additional information about the command. Two commands that I looked at in particular are
bcm. Before executing most commands it is expected that you connect to a device via the
Shortly after I began experimenting with the interactive shell I noticed that executing the
spam command lead to a segfault. Being hopeful that I may be able to turn this bug into something useful I decided to investigate it.
Connecting to BlueTool with LLDB and passing the
spam command I found that BlueTool was crashing due to trying to branch to the address
0x00000000. This happened because I was not opening a device beforehand. Opening a device is required because BlueTool tries to abstract away the hardware to some degree by placing a set of function pointers in memory when opening the device. These function pointers provide hardware specific functionality. Since the
spam command does not first ensure a device is open it will blindly try to branch to the address stored in the function pointer. For more on the function pointers and other data stored when opening a device see the section titled The 0x400 Structure.
bcm command offers a subcommand that allows you to specify the path of a
.hcd file (Broadcom firmware) to download. I am still in the process of dumping and reversing the firmware, however the presence of this command means it is possible to update the Bluetooth module.
Through out the binary there is heavy use of what I’ve dubbed “the 0x400 structure”. This name came to be because this structure is always referenced by adding 0x400 to a base register. The structure contains the following.
|0x400||socket||UART socket file descriptor|
|0x404||open||boolean value indicating if device is open or closed|
|0x408||0x19eb1||write to socket (1 byte max)|
|0x40c||0x19eb9||write to socket (R2 bytes max)|
|0x410||0x19ec1||call select() on socket.|
|0x414||0x19f41||a more different write|
|0x418||0x19fa1||read H4 HCI event/cmd/data body/header|
|0x41c||0x19e25||set baud rate|
|0x420||0x1a175||close UART socket|
|0x424||0x1a191||flush the socket, input and output|
|0x428||0x1a199||spam 0xAA to UART|
|0x42c||0x0||set via STR at 0x19d84|
The values stored in this structure were obtained by inspecting the memory of a running BlueTool process and mapped to there offset through analysis of the open-device function.
Communication between BlueTool and the Bluetooth module occurs over a UART connection. The protocol used over this connection is the standard HCI protocol specified by the Bluetooth specification. BlueTool makes use of various vendor-specific HCI commands. Since Broadcom does not seem to release datasheets and documentation describing the various commands the following list was compiled based off strings in the binary as well as the Broadcom patchram tool at https://code.google.com/p/broadcom-bluetooth/.
|0xFC01||Write Bluetooth device address|
|0xFC18||Update baud rate|
|0xFC1C||Write SCO PCM int|
|0xFC1E||Write PCM data format|
|0xFC2E||Download mini driver|
|0xFC27||Write sleep mode|
|0xFC45||Write UART clock setting 48 MHz|
|0xFC4C||Copy bytes to destination address|
|0xFC6D||Write i2spcm interface parameter|
The default UART device is opened by calling
connect() with the device string
com.apple.uart.Bluetooth. Other device nodes found are:
Moving forward with my analysis of the iOS Bluetooth stack I see the following as interesting next steps:
As I have previously mentioned, my goal for this analysis is to document and understand the iOS Bluetooth stack. Both of these next steps will help further my understanding and I hope will provide a basis for others to work off of.