Hacking Bluetooth BLE devices: reversing Suunto heart rate monitor

A couple of days ago I checked out EvilSocket's new tool for enumerating Bluetooth BLE devices called bleah. I decided to start my own research to learn more about them.

My goal is to learn how to use bleah tool to discover the possibilities of a Bluetooth BLE device and then implement my own POC tool in Python. As I currently don't have many such devices at my disposal, I chose the Suunto Heartrate Sensor. The required theoretical knowledge about how the Bluetooth operates can be found on the Bluetooth specifications page.

Enumerating the device - preparation

My first goal was to install and successfully run the bleah tool. I initially used pip to install the bluepy library, which turned out to be a wrong move. The tool was throwing an exception and only after I installed the bluepy from the source it started to work.

One other thing to note was that I didn't get any errors with bleah when my Bluetooth interface was down. If you don't observe the expected behavior, make sure to check if your interface is working. Here is how to check it and how to bring it up if it is down.

Figure 1: Enumerating with bleah

> sudo hciconfig -a
hci0:    Type: Primary  Bus: USB
    BD Address: XX:XX:XX:XX:XX:XX  ACL MTU: 1021:5  SCO MTU: 96:5
    RX bytes:17681 acl:677 sco:0 events:546 errors:0
    TX bytes:28534 acl:61 sco:0 commands:387 errors:0
    Features: 0xff 0xfe 0x0f 0xfe 0xdb 0xff 0x7b 0x87
    Packet type: DM1 DM3 DM5 DH1 DH3 DH5 HV1 HV2 HV3 
    Link policy: RSWITCH HOLD SNIFF 
    Link mode: SLAVE ACCEPT 

> sudo hciconfig hci0 up

After the Bluetooth interface was up and running, everything started working as expected and the enumeration values started showing up.

Enumerating the device - bleah

As my goal was to build very specific POC application, I started off with discovering its Bluetooth address. The Bluetooth address is a unique 48-bit identifier assigned to each Bluetooth device. It was easy to find the correct address as the bleah tool also prints out the device names and additional information.

Once I had the address, I enumerated it to get all the device profile's services. To bring some context, the profile is a simple collection of services provided by the physical device. Each service has its characteristics. The characteristics offer data and functionalities, depending on their attributes. Amongst other things, the attributes define if the characteristic can be read, written to, or perhaps be awaited to receive the notifications. Both services and characteristics are identified with unique UUID-s.

Implementing the notification reader

Figure 2: Suunto sensorOnce I had all the address data I was able to start implementing the POC application. I first wanted to implement a scanner which would return only those devices which are actually Suunto heart rate monitors. This way I could receive all available running devices' data instead of having to select which device I wanted.

Each new device would then subscribe to my callback and start writing out the heart rate. According to the bleah enumeration results, there was a NOTIFY characteristic which can give out the notifications about the heart rate. This meant I could assign a callback to this characteristic and initiate notification sending.

Assigning a callback was easy as it is directly supported by the bluepy library. Initiating the notification process required a bit more research. The idea is to get the handle of the desired NOTIFY characteristic, increase it by one, and write 0x0001 to it. This turns sending notifications for this characteristic on. Additionally, writing the two-byte zero 0x0000 turns it off. Also, my tests have shown the notifications stop being sent if the connection breaks in any way (whether the app is shut down or loses connectivity), so turning them off is not really necessary.

Once I started getting the data back, I had to do some reverse engineering. I didn't go into details and figured out only how to get the heart rate. I was receiving data with different lengths, but the most prominent one was four bytes long. I cast each byte into int and observed the values:

  • The first one was always 0x16

  • The second one was the heart rate

  • The third one is still unknown

  • The fourth one seems to be the signal strength from 0x01 to 0x04

Project on GitHub: bluetooth-ble-heartrate-sensor-poc


This was a great exercise with using and reversing BLE Bluetooth devices. I intend to continue researching the Bluetooth in more security-related direction.

Bluetooth, BLE, bluepy