Sniffing the Bluetooth Low Energy (BLE) traffic between devices has recently become extremely affordable, with Adafruit's Bluefruit LE Sniffer being the least expensive option at only $29.95 USD. This is a small price to pay if you're at all interested in how various BTLE devices communicate. I was, and decided it would be fun to observe the field upgrade process of the goTenna and try to extract the firmware.
Certainly there are easier ways to find the firmware. Directly examining the application they publish in the Apple App Store or Google Play would likely bear fruit. However, pulling the firmware out of thin air, as it were, seemed more interesting.
I plugged the Bluefruit LE Sniffer and the goTenna into my desktop to capture both the BLE packets and the serial output of the goTenna. During the upgrade process the serial output provides some context to the BLE packets I was capturing.
The Bluefruit LE Sniffer uses Wireshark, which is a very familiar interface for examining traffic. However, I quickly gave up trying to do any sort of protocol analysis directly in Wireshark as each Bluetooth frame was small and there were thousands. I wrote a small Python program to parse the pcap file and print the individual packets. I did observe, in Wireshark, that the goTenna uses the Bluetooth Attribute Protocol for the majority of communication with the host device. I ended up with packets that looked like this:
After some scrutiny I noticed a pattern: every message has a start delimiter of 0x1002, a single byte identifier, a single byte rolling sequence number, a payload, and an ending delimiter of 0x1003. Inside a message there is never a single 0x10 without and accomompanying 0x10, which likely means that any 0x10 byte is escaped as 0x1010. This is likely due to 0x10 being a special control character in the protocol. I've broken the messages apart to illustrate this:
The bulk of the messages I captured have large payloads spanning multiple frames and the identifier of 0x09. These contain the firmware, in 250 byte blocks, that is sent to the goTenna during a field upgrade. I created a Python program to dump the contents of these messages into a new file.
However, the file was larger than expected. I knew from the serial log I captured that the firmware should be 163,652 bytes in total. I had to figure out why I had 1,982 extra bytes. After further analysis I determined every message has a two byte CRC-CCITT xmodem style hash at the end, just before the postamble. This CRC is in addition to the CRC that the Bluetooth frame already has.
I made a change to ignore these two bytes and extracted the firmware again.
Closer, but still 678 extra bytes. After making changes to further decode the Bluetooth header and skip frames that did not pass the Bluetooth frame's own CRC check, as well as remove duplicate frames, I was left with a file 1,711 bytes smaller than expected. This is likely as close as we'll get given the Bluetooth sniffer is entirely passive and not able to request retransmission of frames it didn't properly receive. Fully extracting the firmware in this manner would likely require upgrading several devices in order to collect all transmitted firmware blocks with no errors.
Next, I examined the binary file containing the firmware (most of it, at least). A quick check with strings reveals many previously undiscovered CLI commands that can be issued to the goTenna over its serial port. Previously, I had been manually fuzzing the CLI commands and documenting my progress. Having access to a mostly completed firmware allowed me to discover new commands I would not have otherwise uncovered.
The read_eflash command sounded promising. I tried running this and saw what appeared to be the flash contents in hexidecimal.