In August Silicon Labs released the BGM111, a Bluetooth Smart (BLE) module that might be powering the next generation of IoT devices in customers' hands and on hackers' workbenches.
I was curious about the module so I grabbed the datasheet. It's behind a registration wall on the Silicon Labs site, but strangely a direct link turns up on the first page of a Google search.
The datasheet is fairly pedestrian. It appears to be running a custom Bluetooth stack on a Cortex-M4, the most powerful CPU core I've seen in a Bluetooth SoC to date. Developers have a few different options for running code. It is possible to use a custom API over a UART and run application code on an external MCU (similar to the BLE112 and Nordic nRF8001). It is also possible to upload code written in BGScript, a custom scripting language, and run it directly on the device (again like the BLE112).
Not much else really caught my eye in the datasheet. JTAG is exposed on the header, so it may be possible to dump the code running onboard and reverse engineer that. However once I reached regulatory section at the end of the datasheet, I noticed this odd statement:
This piqued my interest. What strange chip could be inside? I know of no existing Bluetooth chips on the market with an M4 core. The product web site has a rendered image of the module without the RF cage blocking the chip, but I hadn't heard of Blue Gecko nor seen the logo.
A quick bit of googling indicated that this is a new line of chips produced by Silicon Labs loaded with the EFM32 core. There is plenty of documentation about the CPU core, but no documentation about any wireless SoCs.
Curiosity got the better of me and I picked one up from DigiKey (not cheap at $10/unit). The RF cage came up easily enough after cooking it in an IR oven for a few minutes, though a hot air gun would have also done the trick.
Curiously the label EFR32 turned up very few results in a web search. A little snooping around the Silicon Labs site exposes the story.
In 2013 Silicon Labs acquired Energy Micro, a fabless semiconductor company working on low power wireless chips. At the time Energy Micro was hard at work on their EFR4 Draco line, aimed at the super low power market.
After the acquisition, it appears Silicon Labs renamed the EFR4 to EFR32. They have focused their entire marketing effort on the BGM111 module and have not even released the EFR32 to distributors. The sole reference I have found to the chip in official documentation is in the Blue Gecko Bluetooth Smart Module Wireless Starter Kit User's Guide.
This is an interesting development, the first new Bluetooth silicon to enter the market in quite a while. I'm interested to see where this module ends up, and I'm even more interested in the stack running on the device. If you come across a BGM111 in any end-user products, let me know!
Recently at DEFCON 23 Richo Healey and I gave a talk about hacking electric skateboards. One of the skateboards, the Yuneec E-GO, uses a custom wireless protocol between its handheld remote and the board. We touched on how we reverse engineered the protocol during the talk, but I wanted to go into more depth on our methodology.
In short, this is the story of how we went from HackRF to skateboard jammer on Ubertooth. Read on for the gory details!
Finding the Signal
We theorized that the skateboard and remote used the 2.4 GHz band, which is well supported by HackRF One. Ordinarily one would use GNU Radio and a basic waterfall sink to look at spectrum, but GNU Radio can be a bit cumbersome.
Luckily we had a PortaPack to play with! The PortaPack sits on top of the HackRF and acts as a wideband spectrum analyzer (among other things). We tuned up to 2400 MHz and swept the spectrum in 20 MHz increments looking for our signal.
By holding the remote near the antenna, we could easily see short bursts occuring regularly at four frequencies: 2408 MHz, 2418 MHz, 2423 MHz, and 2463 MHz. Conveniently enough, by tuning the HackRF to 2416 MHz we could see from 2406 MHz to 2426 MHz, allowing us to capture the lower three frequencies at the same time.
Recovering Modulation Parameters
We next turned to GNU Radio and Baudline for doing further signal analysis. Using a very simple flowgraph, we could capture samples of the remote transmitting, save them to disk, and load them up into Baudline. Once we had loaded the capture into baudline, we could zoom in on individual transmissions and inspect them visually.
By looking at an individual transmission we could tell that the device uses a lower frequency to represent a 0 and a higher frequency a 1. This modulation mode is called frequency shift keying, or 2FSK. By measuring with Baudline, we could see that the lower frequency and upper frequency were around 300 kHz apart, giving a frequency offset of 150 kHz. In addition to discovering the modulation mode and frequency offset, we could also see that the transmissions included long sequences of 0's, meaning it was highly unlikely the device used encryption or data whitening.
Turning back to GNU Radio, we set out to create a flowgraph to demodulate 2FSK. This is somewhat complicated business, but thanks to Mike Ossmann's latest SDR tutorial video and an excellent blog post by Don C. Weber we were able to puzzle out some of the finer points of 2FSK demodulation. The workhorse is the Quadrature Demod block, which tracks changes in angle of a complex-valued signal. This somehow translates into changes in frequency, but this is the part where we admit that neither of us had any idea what we were doing with DSP (as evidenced by Richo's BSidesLV talk).
At this point we had turned our complex RF signal into a real-valued signal. I doubt it is the best tool for the job, but Audacity did a fine job dealing with this data. After importing the raw data into Audacity, we finally had something that resembled actual data.
At the beginning of the packet is the preamble. The signal alternates between 0 and 1 so that the receiving radio can lock on and recover the symbol rate. Following that is the first meaningful data, the access code (also called a sync word). This is a fixed-length value, usually 16, 24, or 32 bits long, which the receiving radio can synchronize on to differentiate the signal from noise. Finally the rest of the packet is the payload.
Zooming in on the signal, the first order of business was recovering the data rate. The preamble makes this convenient as it always alternates between 0 and 1. This allowed us to count the number of samples from a peak to a valley, which gave us samples/symbol (symbols being bits, since this was 2FSK). Dividing the samples/second by the samples/symbol gives symbols/second, the actual data rate.
In our case, we measured around 80 samples/symbol. Dividing our sample rate of 20 million samples/second by 80 gave a data rate of 250,000. This suggests a data rate of 250 kbit which is very commonly supported by off-the-shelf 2.4 GHz radios.
The final piece of the puzzle was the access code. To recover this value, we began the tedious process of counting bits. We measured the length (in samples) of each stretch of 1's and 0's using Audacity. By dividing these lengths by our samples/symbol, we could recover the number of bits in each stretch. After recovering around 12 bits of the access code we turned back to GNU Radio.
The Correlate Access Code block takes as input an access code and tags the data stream with a value of 0x02 or 0x03 (the actual data is one byte of 0x00 or 0x01 for each bit). We took the output of this block and dumped it to a file. To analyze this file, once again Don C. Weber has us covered with grc_bit_converter. This tool reads the tagged output, packs the bits into bytes, and dumps the data in ASCII hex. We we treated the first 32 bits of this output as a candidate access code.
Sniffing with Ubertooth
Armed with the frequencies the device transmitted on, modulation mode of 2FSK, frequency offset of 150 Hz, data rate of 250 kbit, and access code, we finally had enough information to use a narrowband transceiver to attempt to receive packets. Ubertooth makes an ideal platform to implement a sniffer, as its CC2400 is more than up to the task.
The details of tuning up the CC2400 are best left to code, but it was fairly straightforward to rig it up to tune to one of the known channels and dump packets over USB. This gave us our first samples of packet data.
time=1084328910 delta_t=1012.321200 ms freq=2418 ff e1 92 13 01 d1 00 00 00 00 80 00 80 00 00 00 ff e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ff bf
Of note is the
80 00 80 00 starting at the 11th byte of the data. By
adjusting the speed control on the remote, these values varied linearly
according to position of the speed control. It was thus clear at this
point that the remote communicated in plaintext to the controller.
The final order of business was figuring out the hop pattern and hop interval. As indicated in the above capture from the Ubertooth data, each packet was timestamped using Ubertooth's internal 100 ns clock. This gave us a high-resolution time source for measurements. Sitting on a single channel, we could calculate the delta between consecutive packets and plot these. The minimum value observed was 44 ms between packets, with all other values being multiples of 44 ms (accounting for false positives). The fact that we were seeing multiples of 44 ms indicates we were probably missing some packets, which is to be expected.
Last up was the hop pattern. Looking at the three lower channels in Baudline, it is fairly evident that the device hops between at least the visible three channels in order before returning to the initial channel. We measured the delta between the start of one packet and the start of the next packet to be 11 ms. There was a gap between the third visible channel and the initial channel of 22 ms, so it wasn't much of a stretch to deduce the device hopped to the fourth channel and then back to the first channel in that window. Thus the simple hopping pattern was: channel 1 -> channel 2 -> channel 3 -> channel 4 -> back to channel 1, with a delay of 11 ms between each channel.
Finally having figured out the hop interval and hop pattern, we had all the variables needed to completely follow the E-GO remote as it transmitted to the board. Implementing this in Ubertooth was again relatively straightforward, though the code was now somewhat complicated by a state machine.
Since the goal of the research was to attack the skateboards, we set out to create a jammer. In order to jam a narrowband communication such as this, it is sufficient to generate random noise during transmission that confuses the receiving device. The CC2400 has a special mode that produces pseudorandom data, so we made use of this.
The jamming code co-opts the connection following code we introduced to sniff connections. When the CC2400 detects the access code, instead of receiving the packet data we turn the radio into transmit mode and configure it to send pseudorandom data for around 2 ms. Although there is a delay of around 200 us to switch the radio into TX mode, enough of the packet data is obscured by noise that the board stops responding to the remote after around half a second.
For robustness, the jammer also includes a timeout. If no access code is detected after 11 ms plus a bit of slop, the Ubertooth begins transmitting anyway before hopping to the next channel and waiting for the access code again. Without this trick, the code would not reliably jam the connection.
Once the connection was jammed, the board immediately stopped driving the wheels and turned back into a regular skateboard. This is normally not an issue, as the rider would likely coast to a stop before anything bad happened. However, if the rider had been going downhill this attack would also disable the brakes, which could have obvious consequences.
One oddity that we noticed is that occasionally the board responds to packets with packets of its own. We haven't decoded the contents of these packets, but it is likely that they contain basic telemetry such as battery level. In the interest of open research, here is a link to a sample capture of the board and remote both transmitting on the same channel back-to-back.
If you have an E-GO skateboard and an Ubertooth, give it a try and let me know how it goes!
As always, this was not possible without the help of many people. We would like to thank Jared Boone from ShareBrained Technology for his incredible patience assisting us with GNU Radio while he was in the midst of pushing out a product. We would also like to thank Don C. Weber for publishing an excellent walkthrough on decoding FSK with GNU Radio as well as Michael Ossmann for his incredibly awesome SDR tutorial video series. Finally, thanks to @safehex for winning the E-GO we used in this research at the BSidesLV charity auction.
At CanSecWest last week I demonstrated a remote Bluetooth stack crash in Bluedroid, Android's Bluetooth stack since Android 4.3. This post briefly describes the bug.
For the impatient, you can skip directly to the video of the crash.
The vulnerability is in Bluedroid's BLE (Bluetooth Smart) packet parsing code. In order to exercise this vulnerability, an attacker must force a user to connect to a malicious BLE device. Upon connection, the malicious device will issue a malformed GATT notification packet that causes the stack to crash.
It may sound a bit far-fetched that an attacker could force a user to connect to a device, but consider the fact that many BLE apps for Android opportunistically connect to any advertising device in order to determine if it is the device associated with that app. The app need only connect for this attack to succeed.
This vulnerability is not exploitable: the crash is caused by a
FORTIFY_SOURCE check failure. Additionally, the vulnerability has been fixed
since Android 4.4.
Show me the code
The code in question can be found in
gatt_process_notification (line 614). This is code for parsing notification
packets, which are messages that a BLE device can periodically send to a BLE
master. On line 626 you see the following code:
STREAM_TO_UINT16 (value.handle, p); value.len = len - 2; memcpy (value.value, p, value.len);
len are controlled by the attacker,
though in this case we're only interested in
p is the content of the
packet sent by the attacker and
len is the number of bytes in the packet.
The code expects a packet with a length of at least two bytes. If an attacker
sends a malformed single byte packet, the calculation
value.len = len - 2 will
underflow to 65534. The
memcpy will attempt to copy nearly 64k of data from
I've made a demonstration video of the remote Android Bluetooth stack crash.
I built an attack platform using a modified version of BlueZ, the Linux Bluetooth stack. BlueZ is configured to act as a BLE device running a GATT server. Whenever a BLE master connects to it, it automatically sends a malformed notification packet that is one byte long.
In the video, I demonstrate the vulnerability using a BLE heart rate monitor app. For the purpose of demonstration, I manually connect the app to the evil BlueZ. The stack crashes when the music stops playing.
The output of
adb logcat contains lines similar to the following:
F/libc (19174): FORTIFY_SOURCE: memcpy buffer overflow. Calling abort(). F/libc (19174): Fatal signal 11 (SIGSEGV) at 0xdeadbaad (code=1), thread 19956 (BTU)
Again I note that this attack is not exploitable due to
runtime checks. The code is instrumented at compile time where the length of the
target buffer is known. At runtime, the code checks to see if the
length is larger than the target buffer length and if so calls
This is the timeline following discovery of the bug:
- 2013-09-30: Vulnerability disclosed to Google
- 2013-10-07: Fix committed
- 2013-10-30: Android 4.4 r0.9 tagged
- 2013-10-31: Android 4.4 released with fix
Google did not issue a fix for this on Android 4.3, the rationale being that all users should upgrade to 4.4.
If you're interested in learning more about BLE active attacks and BLE fuzzing, check out the video of my CanSecWest talk, Outsmarting Bluetooth Smart.
Bluetooth devices are all around us and a surprising number of them are left discoverable. In this post I describe techniques for finding discoverable Bluetooth devices and listing the services running on them. I will also cover basic BLE (Bluetooth Smart) reconnaissance.
This tutorial assumes you have a modern Linux system or VM running BlueZ. The commands below should work in BlueZ 4.101 and BlueZ 5.x.
Finding Bluetooth Devices
The most basic way to find local discoverable Bluetooth devices is to
hcitool scan. In the following example, there are two discoverable
devices near my laptop: a Nexus 4 and a wireless speaker:
$ hcitool scan Scanning ... 98:D6:XX:XX:XX:XX Nexus 4 00:0D:XX:XX:XX:XX Bluetooth Speaker
The left column is the device's BD ADDR (Bluetooth Device ADDRess). This address is unique to the device and is very similar to a WiFi or ethernet MAC address. The right column is the device's human-readable name, which the device sent us in response to our scan requests.
To get a little more information about a device, we can use
inq, another subcommand of
$ hcitool inq Inquiring ... 98:D6:XX:XX:XX:XX clock offset: 0x0000 class: 0x5a020c 00:0D:XX:XX:XX:XX clock offset: 0x5a75 class: 0x240404
This output again includes the BD ADDR, but this time it also includes the clock offset and class of device. Clock offset is a low-level value that can be ignored. Class of Device (CoD) tells us what type of device we're talking to.
You can decode CoD using a tool I wrote called btclassify. Running it with the above classes tells us these devices identify as a phone and a wearable headset:
$ ./btclassify.py 0x5a020c 0x240404 0x5a020c: Phone (Smartphone): Telephony, Object Transfer, Capturing, Networking 0x240404: Audio/Video (Wearable Headset Device): Audio, Rendering
Diving Deeper: Services
Now that we know we have a phone and a headset (which is actually a speaker), we'd like to find out what services they run.
In Bluetooth service information is available via the Service Discovery
Protocol, or SDP. BlueZ ships with
sdptool for querying SDP. The
browse subcommand is typically the best tool for listing services. It
has fairly verbose output, so it's useful to filter it using grep:
$ sdptool browse 98:D6:XX:XX:XX:XX | grep Service\ Name Service Name: Headset Gateway Service Name: Handsfree Gateway Service Name: AV Remote Control Target Service Name: Advanced Audio Service Name: Android Network Access Point Service Name: Android Network User Service Name: OBEX Phonebook Access Server Service Name: OBEX Object Push
Given the presence of "Android Network" services, we can conclude that this device is likely an Android smart phone. The other services are relatively typical for a phone. For instance, Headset and Handsfree are used with headsets and car audio systems, and the OBEX services are used for transferring contacts and sharing files.
Unfortunately when we try to run
sdptool browse against the speaker,
we do not receive any records. Instead we can use the
subcommand to receive these records. This command runs for 20 seconds
before terminating, so don't worry if it appears to have hung:
$ sdptool records 00:0D:XX:XX:XX:XX Service RecHandle: 0x10001 Service Class ID List: "Audio Sink" (0x110b) Protocol Descriptor List: "L2CAP" (0x0100) PSM: 25 "AVDTP" (0x0019) uint16: 0x102 Profile Descriptor List: "Advanced Audio" (0x110d) Version: 0x0102 Service RecHandle: 0x10002 Service Class ID List: "AV Remote Target" (0x110c) Protocol Descriptor List: "L2CAP" (0x0100) PSM: 23 "AVCTP" (0x0017) uint16: 0x103 Profile Descriptor List: "AV Remote" (0x110e) Version: 0x0103 Service Name: Hands-Free unit Service RecHandle: 0x10004 Service Class ID List: "Handsfree" (0x111e) "Generic Audio" (0x1203) Protocol Descriptor List: "L2CAP" (0x0100) "RFCOMM" (0x0003) Channel: 1 Language Base Attr List: code_ISO639: 0x656e encoding: 0x6a base_offset: 0x100 Profile Descriptor List: "Handsfree" (0x111e) Version: 0x0105
Pay attention to the values after "Service Class ID List". This tells us the device has "Audio Sink", "AV Remote Target", "Handsfree", and "Generic Audio" services. These services make sense given that we know this is a speaker.
Both of these commands give a complete breakdown of all lower-level protocols used to implement the services. This information is not typically useful during general reconnaissance, but it can be useful when developing fuzzers.
One final thing to note is that
sdptool works even if a device is not
discoverable. If you know a device's BD ADDR you can try to connect to
sdptool browse or
records to find out if it's nearby but
Information Hidden by BlueZ
Certain devices return a bit more information that BlueZ does not
hcitool output. You can get at this information using
hcidump or BlueZ 5's
hcidump works on all recent
versions of BlueZ, I will demonstrate its use.
In one terminal run
hcidump and tell it to log to a file:
$ sudo hcidump -w inquriy.cap
In another terminal run
$ hcitool inq Inquiring ... 00:26:XX:XX:XX:XX Richard Gill's MacBook Pro
Yowza, we can already see the name of the owner of a nearby laptop. Could be useful for social engineering. This naming convention is fairly common among Mac laptops. Worse, on at least some versions of Mac OS X when you put Bluetooth into discoverable mode it will remain in that mode indefinitely.
hcidump and load the file it created in Wireshark. Filter the
output to just inquiry responses using
bthci.evt_code == 0x2f. If the
device is friendly, you will see information similar to the following
Under "Extended Inquiry Response Data" you can see a list of service
UUIDs, which will be fairly similar to the output of
sdptool. On the
right I've highlighted a "manufacturer specific" field. This field
helpfully tells us the specific model number of the laptop! This
information is also highly useful for social engineering.
All the of the above applies to classic Bluetooth devices, such as phones, headsets, speakers, and so forth. BLE devices are becoming increasingly common, and they require their own set of tools. Some examples of BLE devices are fitness wristbands, heart rate monitors, smart watches, proximity sensors, and so on.
Listing BLE Devices
The most basic tool to list local BLE devices is
$ hcitool lescan LE Scan ... DC:A0:F2:B9:4F:9E (unknown) DC:A0:F2:B9:4F:9E Flex 00:22:XX:00:XX:XX (unknown) 00:22:XX:00:XX:XX Polar H7 00XXXX
I haven't censored the first device because it is using a random address that can vary over time, a BLE security feature that makes tracking devices more difficult.
Given a bit of a-priori knowledge, we can assume the first device is a FitBit Flex. The second device is a Polar H7 heart rate monitor.
Listing BLE Services
Recent versions of BlueZ ship with
gatttool for querying BLE services
over GATT. The
--primary command line switch connects to the device
and asks it for a list of services:
$ gatttool --primary -b 00:22:XX:00:XX:XX attr handle = 0x0001, end grp handle = 0x000b uuid: 00001800-0000-1000-8000-00805f9b34fb attr handle = 0x000c, end grp handle = 0x000e uuid: 00001801-0000-1000-8000-00805f9b34fb attr handle = 0x000f, end grp handle = 0x0014 uuid: 0000180d-0000-1000-8000-00805f9b34fb attr handle = 0x0015, end grp handle = 0x0023 uuid: 0000180a-0000-1000-8000-00805f9b34fb attr handle = 0x0024, end grp handle = 0x0026 uuid: 0000180f-0000-1000-8000-00805f9b34fb attr handle = 0x0027, end grp handle = 0xffff uuid: 6217ff49-ac7b-547e-eecf-016a06970ba9
The service UUID defines the service. UUIDs of the form
000018xx-0000-1000-8000-00805f9b34fb are generally 16-bit services that
are assigned by the Bluetooth SIG. Information about these can be found
GATT assigned numbers
Based on the UUIDs, we can see this device has "Generic Access", "Generic Attribute", "Heart Rate", "Device Information", and "Battery" services. The last UUID is a manufacturer-specific UUID. If you see another device with this service, it's likely a Polar H7.
Given the presence of the "Heart Rate" service we can assume this device is probably a heart rate monitor. The "Device Information Service" is more interesting, and I'll describe some fun tricks with that in the next section.
One thing to note: if you're trying to connect to a device using a
random address, such as the FitBit Flex, you need to add the
command line flag. Let's list the services for that device:
$ gatttool -t random --primary -b DC:A0:F2:B9:4F:9E attr handle = 0x0001, end grp handle = 0x0007 uuid: 00001800-0000-1000-8000-00805f9b34fb attr handle = 0x0008, end grp handle = 0x0008 uuid: 00001801-0000-1000-8000-00805f9b34fb attr handle = 0x0009, end grp handle = 0x000e uuid: adabfb00-6e7d-4601-bda2-bffaa68956ba attr handle = 0x000f, end grp handle = 0x0012 uuid: 558dfa00-4fa8-4105-9f02-4eaa93e62980 attr handle = 0x0013, end grp handle = 0x0018 uuid: 0000180a-0000-1000-8000-00805f9b34fb attr handle = 0x0019, end grp handle = 0xffff uuid: 0000180f-0000-1000-8000-00805f9b34fb
This device has many services in common with the heart rate monitor, but there are two additional manufacturer-specific services. Seeing these services on another device indicates you're probably looking at a FitBit Flex.
Finally, to see a list of all characteristics you can use the
--characteristics flag or
characteristics command in interactive
mode. I will omit its output, since it's generally very long. Once
you've found a characteristic you're interested in, you can try reading
Sensitive Information in Device Information Service
The "Device Information Service" is very interesting. From the
about it, we can see there is an optional field called "serial number".
Clicking on the link brings us to the
serial number string characteristic,
which is assigned number 0x2a25. Let's use
gatttool's interactive mode
to try to read it:
$ gatttool -I -b 00:22:XX:00:XX:XX [ ][00:22:XX:00:XX:XX][LE]> connect [CON][00:22:XX:00:XX:XX][LE]> char-read-uuid 2a25 [CON][00:22:XX:00:XX:XX][LE]> handle: 0x001b value: 30 30 30 33 3X 3X 3X 3X 3X 3X 00
This value is an ASCII representation of the serial number, which in this case is 0003XXXXXX. This value is likely unique and trackable.
Let's try that on the FitBit:
$ gatttool -t random -I -b DC:A0:F2:B9:4F:9E [ ][DC:A0:F2:B9:4F:9E][LE]> connect [CON][DC:A0:F2:B9:4F:9E][LE]> char-read-uuid 2a25 [CON][DC:A0:F2:B9:4F:9E][LE]> Read characteristics by UUID failed: No attribute found within the given range
Too bad, it doesn't have it. One other interesting UUID to query is the manufacturer name:
[CON][DC:A0:F2:B9:4F:9E][LE]> char-read-uuid 2a29 handle: 0x0015 value: 46 69 74 62 69 74 00 00 00 00 00 00 00 00 00 00 00 00 00
Converting this to ASCII gives us "FitBit", as expected.
One Last Thing
gatttool is nowhere near as reliable as the above makes it out
to be. Connections will fail and drop all the time, even under
controlled conditions. Just be patient and things generally work
Have fun doing Bluetooth recon!
Recently Omri Iluz wrote about his experiences capturing BLE very cheaply using an RTL-SDR and an MMDS downconverter. His work is very interesting and is a good way to get starting playing with BLE on the cheap.
A software defined radio approach is very powerful, and if you're interested in sniffing Bluetooth with SDR you should definitely check out gr-bluetooth. Another interesting approach is to use a narrowband radio as a sniffer, like the one on the Ubertooth.
I've been researching BLE (also known as Bluetooth Low Energy and Bluetooth Smart) since 2012, and I wanted to share the BLE sniffer I built on the Ubertooth platform. My sniffer is highly robust and can capture data from connections on data channels. I also discovered weaknesses in BLE's security and wrote a tool to decrypt packets under some circumstances.
The sniffer is turnkey and painless: if you have an Ubertooth you can begin sniffing packets right now by running a single command. Our tools capture to PCAP files that can be loaded into Wireshark for analysis using the BLE plugin that ships with recent development builds of Wireshark.
My BLE sniffer and Ubertooth itself are 100% open source. The source for the Ubertooth firmware, host tools, and board design can be found on the Ubertooth Github.
Ubertooth is an open source platform for Bluetooth research. It has a powerful ARM microcontroller connected to a reconfigurable radio chip, the TI CC2400. Although it was originally built to monitor classic Basic Rate (BR) Bluetooth, it serves as an excellent platform for building a BLE sniffer.
At the physical layer, our BLE sniffer works by configuring the CC2400's modulation parameters to match those of BLE. We also program the radio to search for a 32 bit value known as the Access Address that is at the beginning of every packet. When a nearby BLE device transmits, our radio sees the 32 bit Access Address and begins sending data to the ARM microcontroller.
Before we can read the data, we have to dewhiten it. This is done by XOR'ing the data with the output of a 7 bit LFSR. Our implementation dewhitens 32 bits at a time thanks to an impressive algorithm by Dominic Spill and Michael Ossmann. After this we parse the header and validate the CRC. Valid packets are passed up to the PC via USB where they are displayed and logged to PCAP files.
Timing Is Everything
Almost everything above happens on the Ubertooth dongle itself, and the PC just acts as a logging platform. This was a deliberate design choice made to satisfy one key requirement: timing.
In BLE, timing is everything. During connections devices hop to different channels relatively frequently, on the order of milliseconds. In this time we must receive the data, dewhiten it, parse the header, and make a decision about hopping very quickly. Sending the data to the PC and waiting for it to send a decision back would take too long for all but the slowest connections. USB latency alone is measured in milliseconds.
Additionally, on advertising channels and during connections two devices will transmit very quickly in sequence. First one device will transmit, and then 150 microseconds later the other will transmit. If we're busy analyzing the data from the first transmission, we may miss the second one altogether! This type of latency would be impossible to achieve over USB.
Following connections is where everything comes together. Advertising packets are sent on three channels in no particular order and can be captured easily. Connections hop along a sequence of 37 data channels very quickly, spending between 7.5 ms and 4 seconds on a given channel. If we wish to capture data from a BLE connection, we must hop along with the master and slave and listen for their packets on each data channel.
First we must sniff a CONNECT_REQ packet, which is transmitted by a BLE master device on an advertising channel. This packet initiates a connection between two devices and contains all the connection-specific details, such as Access Address, how frequently to hop, and in what order to visit data channels.
Once we have the details from the CONNECT_REQ packet, we have everything we need to follow along with the master and slave as they hop among the data channels. We hop to data channel 0 and wait for the first transmission. First the master transmits and then 150 microseconds later the slave transmits. We minimally process these packets and send them along to the PC. Then we hop to the next channel in the sequence and wait for the next packets. This continues until the master or slave closes the connection.
BLE conections are actually quite simple, significantly moreso than the hop pattern of BR Bluetooth. The only difficulty is meeting timing requirements, which we can do easily since all our processing occurs on the ARM microcontroller.
Our Ubertooth BLE sniffer also includes support for capturing data from connections that are already active at the time of sniffing. This feature, called promiscuous mode, is not supported by any other inexpensive commercial or open source sniffer. The only other tool I know of with support for this costs over US$ 20,000.
Due to the nature of BLE, without observing a CONNECT_REQ packet it is extremely difficult to recover all the parameters needed to successfully follow connections as they hop among the data channels. Hop timing, channel ordering, and even CRC calculation elude us.
I developed a few clever tricks to recover these key parameters using Ubertooth. Once we've recovered them, we feed them back into the normal connection following code and can actually begin following these active connections. For more details on how we recover the parameters, refer to my USENIX WOOT whitepaper.
Disclaimer: This mode is a little touchy: recovering the parameters can be tricky and we don't filter false positives well. However, once the parameters have been recovered, connection following is just as robust as if the CONNECT_REQ packet had been observed.
In early 2013 I discovered that BLE's encryption has a fatal flaw. I wrote a tool called crackle to automatically exploit this flaw and decrypt encrypted BLE data.
An attacker present during pairing can recover the encryption keys used to protect data during connections. Furthermore, since these encryption keys are reused, this attacker can decrypt every future conversation between the master and slave devices.
This attack is completely passive. The attacker simply has to capture the packets sent by the pairing devices using a tool such as Ubertooth. No packets are ever transmitted by the attacker, and the victims will have no knowledge that they are being eavesdropped on.
If you give crackle a PCAP file that contains the pairing data, it will automatically crack the encryption key and decrypt any further data sent during the connection. If you give it a key and a PCAP file filled with encrypted data, it will decrypt the data.
Obviously this is a huge weakness in BLE and severely weakens the security of the system. I was surprised that, although this weakness has been public for a while, the latest version of the Bluetooth Core Specification (version 4.1, published in December 2013) does not address it.
I've intentionally left out a lot of details since this blog post is already long-winded enough as-is. If you're interested in more depth, you can do any of the following:
- Check out my USENIX talk and white paper
- Watch my earlier, related talks
- Send me an email: mikeryan at lacklustre dot net
- Tweet me @mpeg4codec
- Drop into #ubertooth on freenode and say hello!
Ubertooth can be purchased from several places, the full list is available on the Great Scott Gadgets web site.
Finally I would like to express thanks to the many people who helped make this work possible, but in particular: