Hacking Unicorns with Web Bluetooth
By Paul Stone, 28 Feb. 2017
At Context, we've been interested in Bluetooth LE for a while now. We've written the RaMBLE Android app to scan for BLE devices and we've tested various Bluetooth LE devices for clients as part of our Product Security Evaluation service. I recently came across an interesting Bluetooth LE device that I decided to reverse-engineer for fun. In this blog I'll outline some of the tools and techniques I used to reverse-engineer the BLE protocol for this particular device, and show how I used Chrome's new Web Bluetooth API to write a webpage that can connect to and control it.
I first came across this device while running RaMBLE on my daily commute. Among the usual fitness trackers and wireless headphones I spotted a device named 'CloudPets B 1.0.19'. Intrigued, I Googled the name and discovered a range of cuddly toys that use Bluetooth LE to communicate with a smartphone app.
The idea of the toy is that friends or relatives can use the CloudPets smartphone app to record an audio message and send it to app on the parents' phone. The app then uploads the audio to the toy using Bluetooth LE. When the child presses the animal's right paw, it will play back the message. They can then record their own message by pressing the toy's other paw. The app retrieves this message via Bluetooth and sends it back to the friend-or-relative.
I thought this mix of functionality (audio playback and recording) along with BLE connectivity in the shape of a cuddly toy was a great opportunity for some reverse-engineering.
This wouldn't be the first time I've purchased a Bluetooth LE device after discovering it with RaMBLE. Before jumping into another impulse purchase, I first decided to do a bit of research and see how much technical information I could find about the toy online.
Being a wireless device available for purchase in the US, the device has already undergone FCC testing. The FCC documents provided lots of useful information, including block diagrams of the internals, datasheets and various photos - including the following delightful view of the FCC 'teardown':
The FCC documents reveal that the toy uses a Texas Instruments CC2541 Bluetooth LE SoC, alongside a Sonix SNC7232 sound processor. The CC2541 uses the Intel 8051 microcontroller architecture and has 256kb of flash program storage. The toy also has 2Mbit of additional flash storage which is presumably used to store the audio recordings.
Due diligence done, I decided to go ahead and order the £12 Unicorn model.
Dissecting the Advertisement
After setting up my unicorn using the official app, I first decided to look at what data the toy was broadcasting.
Most Bluetooth LE devices make their presence known by broadcasting 'advertising packets'. This allows devices such as smartphones to discover and connect to nearby devices. Advertising packets contain the address of the device plus other optional fields. To look at advertising packets, we usually use our own RaMBLE Android app, or the Nordic nRF Connect app which gives more detailed information. The advertising packets being broadcast by the toy had the following fields:
What does this tell us? The address is from a range that belongs to Texas Instruments. This suggests that the Bluetooth radio in the device was manufactured by them. The flags tell us that the devices supports only Bluetooth LE, and not the older 'classic' Bluetooth protocol. The manufacturer data field can be used for any purpose. In this case, it contains textual data - some numbers separated by underscores. We'll come back to this later.
Bluetooth LE devices use the GATT (Generic Attribute) protocol. Each device has a GATT profile which describes the services it supports. When a smartphone first connects to a Bluetooth LE device, it will fetch a list of services and characteristics that it offers - this is called 'service discovery'. The Bluetooth spec describes a number of standard services. Manufacturers can also create their own custom services. Using the nRF Connect app, I connected to the toy and found the following services:
Our Unicorn offers four services - the first two are supported by most BLE devices. The third service is the Texas Instruments firmware update service. This allows the device firmware to be updated over BLE.
The final service UUID gave no results on Google, so I assumed that this was the service used by the CloudPets app to control of the various features of the toy. Each GATT service contains one or more characteristics. These are essentially fields that can be read or written to communicate with the device. Helpfully these characteristics included the 'User Description' metadata (or 'descriptor' in GATT terminology) that names each one:
Looking at the user description, we can guess at the purpose of some of these characteristics. However to fully understand them I needed to do a bit more digging. I took two approaches to figure out how the various characteristics worked - the first was to look at the decompiled source of the CloudPets app; the other was to log the Bluetooth data being sent between the app and the toy as I used it.
I first used ADB to pull the CloudPets APK file from my phone:
$ adb shell pm list packages | grep cloudpets package:com.spiraltoys.cloudpets2 $ adb shell pm path com.spiraltoys.cloudpets2 package:/data/app/com.spiraltoys.cloudpets2-1/base.apk $ adb pull /data/app/com.spiraltoys.cloudpets2-1/base.apk cloudpets.apk [100%] /data/app/com.spiraltoys.cloudpets2-1/base.apk
I then used the excellent jadx tool to decompile the compiled .dex classes into Java source:
$ jadx -d cloudpets-decomp cloudpets.apk
I imported the decompiled source into Android Studio to allow for easy navigation (File > New > Import Project…, select the directory you decompiled to, then choose the 'Create project from existing sources' option). The code had not been obfuscated, which meant that most of the class, method and variable names were left intact.
I first searched for the service and characteristic UUIDs used by the toy, and found the 'ToyPeripheral' class. This included the UUIDs as constants, named similarly to the 'user description' metadata above:
Next I looked at the classes that encoded commands for the various characteristics. I started off with the simpler functionality, looking at the code for the LED characteristic in the ToyTasakSetLedState class.
From this, I could see that the LED characteristic took a 5-byte value, formatted like so:
I was able to verify this behaviour using the nRF Connect app to manually write some bytes to the LED characteristic:
Next I looked at the Config characteristic. It turns out this controls the content of the 'manufacturer data' field in the toy's advertising data. The characteristic is written when the toy is first set up. It contains which animal it is, and the date and time it was set up:
Looking at the string '0411_01_19_09_38' from the advertising packet earlier, the '04' tells us that my CloudPet is 'Starburst the Unicorn' and the remainder tells us he/she was activated on November 1st at 19:09:38 (the toy I first spotted with RaMBLE turned out to be 'Bentley the Bear')
Next I looked at the Command characteristic. From the source code I could see that there are five slots that can store audio. Normally slot 2 is used for recordings received from the app, slot 1 is used for audio recorded from the microphone on the toy. The other slots are normally used by the app for a 'barnyard animal sound' game similar to Simon. The playback command has the following structure:
In the code the first byte, 08 was a constant COMMAND_SEND_SNC7232_COMMAND. The next byte, 01 was named SNC7232_COMMAND_PLAY_SLOT. As I learned from the FCC documents, the SNC7232 is the sound chip used in the toy.
Interestingly, the app only ever sends the 01 command to the SNC chip, and there were no other constants referring to the SN7232. The obvious question is, does it support any other commands? Well, I tried sending 08 02 and discovered that this silently triggered the audio recording functionality. Normally you would have to press the switch in the left paw to trigger recording - this also causes the heart LED to light up. By triggering recording using the Command characteristic, we can record up to 40 seconds of audio, while the LED remains off.
Sending and receiving Audio
Now I had a way to turn my unicorn into a remote surveillance device, I wanted to figure out how to retrieve the audio recording. Looking at the source code, I could see that this involved using several different characteristics.
The Android GATT APIs are asynchronous so it can be tricky to figure out what order things are supposed to happen in just by looking at the source. I decided to use the 'Bluetooth HCI snoop log' setting in the Android Developer options. When enabled, this logs all Bluetooth commands and data to a file in /sdcard/btsnoop_hci.log. The log can be retrieved using adb and viewed in Wireshark.
I recorded various logs while uploading and downloading audio using the app. After looking at these in Wireshark, I could see the sequence of commands and data involved in these functions. The steps for downloading audio from the toy go like this:
- Enable notifications for the 'receive audio' characteristic
- Send 02 to the command characteristic
- Wait for the 'state' characteristic to change to 07 (audio download)
- The toy will then send a large number of 16-byte notifications on the 'receive audio' characteristic
- Wait for the 'state' characteristic to change to 01 (idle)
- Concatenate the received notifications and decode the audio
The process for uploading audio was similar - it used the 'send audio' characteristic to receive chunks of data, and the 'data request' characteristic for flow control.
Bluetooth LE isn't really designed for sending or receiving large quantities of data. Using the CloudPets app, it takes 30 seconds or more to transfer recordings to and from the toy. The app uses native Android libraries to perform audio compression - libMsAdpcm.so for compressing uploaded audio and libAudio32Encode for decompressing audio received from the toy. These use the MSADPCM and G.722.1 codecs, respectively. The G.722.1 algorithm seemed to have been tweaked, and I didn't particularly want to spend time reversing the libraries. So instead I wrote some Python code to call into these libraries using ctypes to handle the encoding and decoding for me. The APK contained only ARM binaries, so without x86 versions the best place to run my Python scripts was on my phone using Termux. Using my scripts I was successfully able to decode the audio data recorded and received from the toy.
Web Bluetooth is a draft specification that allows web browsers to interact with Bluetooth LE devices using the GATT protocol. It is enabled by default in Chrome 56 onwards, but no other browsers currently support it. There has been some understandable wariness about allowing web pages to connect to Bluetooth devices, however the way it is currently implemented prevents web pages from silently connecting to devices. The user must explicitly choose a single device to connect to, like so:
There is also a standard blacklist of Bluetooth services and characteristics that aims to prevent malicious web pages from doing bad things (interestingly the Texas Instruments firmware update service used by the toy is blacklisted).
Now that I had figured out how the various GATT characteristics worked, I decided to put it all together into a single webpage that could demonstrate the various features of the toy. It can do the following things:
- Control the 'heart' LED
- Play back audio stored in any of the 5 slots
- Use Media API to record audio from the phone, and upload it to the toy
- Remotely trigger the recording functionality
- Download the recorded audio and play it back on the phone
The great thing about Web Bluetooth is that you can control devices using only a browser and a simple web page - there's no app to install. The code for this is now available on GitHub.
The purpose of this project was mostly to have fun hacking a Bluetooth unicorn, and look at how BLE is used in a real world product. However, we should also look at the security implications of what we've done here.
Firstly, the toy does not use any built-in Bluetooth security features. It doesn't support bonding/pairing that would enable some level of authentication and encryption between the device and phone. In fact many Bluetooth LE devices intended for use with smartphones don't bother with pairing at all, to simplify the user experience. Some manufacturers instead choose to implement their own security on top of the GATT protocol.
When first setting up the toy using the official CloudPets app, you have to press the paw button to 'confirm' the setup. I initially thought this might be some sort of security mechanism, but it turns out this isn't required at all by the toy itself. Anyone can connect to the toy, as long as it is switched on and not currently connected to anything else. Bluetooth LE typically has a range of about 10 - 30 meters, so someone standing outside your house could easily connect to the toy, upload audio recordings, and receive audio from the microphone.
I am still looking at the firmware update mechanism. The CloudPets app performs a firmware update when you first set up the toy, and the firmware files are included in the APK. The firmware is not signed or encrypted - it's only validated using a CRC16 checksum. Therefore it would be perfectly possible to remotely modify the toy's firmware. However, given that it's already possible to turn the toy into a remote listening device using the built-in functionality, there's not much to be gained by modifying the firmware for nefarious ends.
Context policy is to follow responsible disclosure, so we've been attempting to contact the toy's manufacturer, SpiralToys, since last October. After several emails, and messages to their Facebook and Twitter accounts, we've not yet had any response.
As previously mentioned other security issues with this product were made public yesterday and it appears as though these researchers also experienced similar issues in trying to disclose vulnerabilities to SpiralToys. In this instance, after 5 months of trying to responsibly disclose the issues and with other more serious issues now publically available we have made the decision that it is in public interest for us to release the details of our findings.
Specifically for the issues we have identified our guidance is to switch the toy off when not in use.
We'll update this page if we do hear back from SpiralToys. As mentioned above, the issues that affect the toy can only be exploited by someone within Bluetooth range - around 10m.
This of course isn't the first IoT-enabled toy to have security problems. The 'Cayla' Bluetooth-enabled doll has been making headlines recently, and I expect there are many more connected toys out there that are somewhat lacking in the security department.
The issues described above, alongside the independently discovered CloudPets server breach show that little thought has been put into securing the product. The CloudPets toys are still available to purchase through various online retailers. If you're interested in Bluetooth LE, I'd recommend buying one of these as a cheap way of experimenting with the protocol. If you own one of these toys (or any other IoT/connected toy), I would recommend keeping it turned off whilst it's not in use.