Picture a world where you browse the web without that little lock next to the URL to assure that your web communications are safe, well at least for the most part anyway. That’s the current state of security with Bluetooth Low Energy (BLE), and more generally the Internet of Things (IoT). Back to the web, Transport Layer Security (TLS) exists to solve two problems: authentication and encryption. In this blog, I propose a system to solve the BLE authentication puzzle by automating, and hopefully standardizing, IoT security testing.
Setting the Stage
I’ll skip the archetypal IoT introduction because you know it: very probably, IoT is in your home, in your car, and where you work. The fact is, mainstream IoT has become almost synonymous with bad security by consistently leaning towards convenience in the security-convenience-tradeoff, or even outright neglecting security. But things are changing. Governments are getting serious about it and the ioXt Alliance — an association of IT companies, including NowSecure — has published an IoT mobile security standard to ensure security and privacy of IoT-connected mobile apps and VPNs.
For the purpose of this article, let’s narrow down the term “security” a little bit. IoT devices need to communicate with the world around them. The ioXt standard defines a Proximity attack: Authentication security requirement that basically requires mutual authentication between IoT devices and other devices in their proximity, including smartphones. IoT communication can take place through different data-carrying channels such as WiFi, Bluetooth, NFC and others, depending on the application. While this article will focus on BLE-connected mobile apps, the main idea carries over to any kind of BLE communication.
Meet the Man in the Middle
Authentication guards against the classic man-in-the-middle attack. I’ll just quote the BLE 5.2 specification defines it perfectly:
A man-in-the-middle (MITM) attack occurs when a user wants to connect two devices but instead of connecting directly with each other they unknowingly connect to a third (attacking) device that plays the role of the device they are attempting to pair with. The third device then relays information between the two devices giving the illusion that they are directly connected. The attacking device may even eavesdrop on communication between the two devices (known as active eavesdropping) and is able to insert and modify information on the connection. In this type of attack, all of the information exchanged between the two devices are compromised and the attacker may inject commands and information into each of the devices thus potentially damaging the function of the devices. Devices falling victim to the attack are capable of communicating only when the attacker is present.
Core 5.2, Vol. 0, Part A, 5.2.3
This definition implicitly emphasizes the difference between passive eavesdropping, in which the attacker would silently listen in on the conversation, and active eavesdropping, in which the attacker would manipulate the conversation as it’s taking place.
The BLE spec defines a generous radio range of 100 meters, which in practice depends on signal strength and other physical factors. This physical range is the most common playing field of the man-in-the-middle attack. In comparison, Near-field Communication (NFC) only has a range of about 20 centimeters which presents a much slimmer opportunity for attack.
BLE Specification Primer
Developed by Nokia in 2006 and integrated into the open Bluetooth 4.0 standard in 2010, BLE is the low energy variant of Bluetooth, now called Bluetooth Classic.
The BLE protocol is made up of a few layered sub-protocols for handling different parts of communication, and they sometimes interleave in functionality. Of special interest to us are:
- GAP (Generic Access Profile): Defines topology and connection modes
- GATT (Generic Attribute Profile): Defines the structure of data/attributes and how they’re transferred once a connection has been established.
- SMP (Security Management Protocol): Handles security.
BLE protocol stack. Core 5.2, Vol. 3, Part C, 2.1
The device that initiates communication acts as the client, in BLE terms, the Central. In our example, the Central would be a smartphone operated by a mobile app. The other part of the conversation is the server, in BLE terms a Peripheral, and it’s the IoT device that receives commands or requests and returns responses. This is the IoT part of the conversation: smart speakers, IV pumps, smart locks, cars, industrial systems, manufacturing equipment, etc. The atomic abstraction in the BLE spec is a Characteristic — basically a readable and/or writeable property with certain security parameters. A collection of Characteristics is called a Service. These abstractions fall under the GATT protocol, which defines a Profile: a predefined set of Services for IoT device manufacturers to use, in an attempt to standardize the interfaces that certain categories of devices expose. For example, the Heart Rate Profile would include the Heart Rate Service and the Device Information Service, the former would, for example, include commands for reading the current heart rate Characteristic and the writable on/off switch Characteristic.
As mentioned, Characteristics can be readable and/or writable and they can dictate whether these accessors require encryption. Parameters such as the algorithm used in encryption and whether or not encryption requires authentication (thereby protecting against active eavesdropping,) depend on the Security Mode used by the communicating devices. The BLE spec defines two Security Modes each with a number of security levels, which differ in their cryptography parameters and can easily become confusing. For simplicity’s sake, let’s assume the most strict configuration, which is the FIPS-compliant LE Secure Connections. Even though it’s the most strict, this is really not asking for much, since Secure Connections was introduced in BLE 4.2, which was released in 2014 and is available by default on any recent smartphone.
Pairing and Security
Now imagine that an attacker can stealthily tamper with the on/off switch Characteristic in the heart rate monitor above, this is the potential of a MitM attack. Authentication defends against MitM by cryptographically ensuring that each party can verify the identity of the other. In the BLE world, it’s the “pairing” pop-up that you see when you connect your smartphone with the IoT device for the first time. Once paired, devices store long-term parameters and they become “bonded,” so they don’t have to repeat the pairing process every time the devices seek to connect.
Pairing in BLE happens over three phases during which the devices exchange cryptographic parameters, namely a Temporary Key (TK) which is used to generate a Short Term Key (STK) used for encrypting the connection when encryption is required by a Characteristic.
Different devices have different input/output capabilities, and so the spec has defined four pairing methods, called Association Models (Core 5.2, Vol. 1, Part A, 5.2.4). The working mechanism of each of those has been well explained before. And to add more to the confusion, there’s also the concept of “connection models.” BLE versions 4.0 and 4.1 use a connection model now called LE Legacy Pairing, while 4.2 and above use the Secure Connection model. These models differ in their encryption algorithm, the key exchange algorithm used, their authentication mechanism, and therefore their overall security. The BLE version used depends on both the Central (smartphone) and the Peripheral (IoT device.)
Having a secure key exchange algorithm, such as ECDH which is offered by Secure Connections mode, ensures that communication can be encrypted. But keep in mind that key exchanges are just that, a way to exchange keys, they do not protect against active eavesdropping. To protect against active eavesdropping, you need some sort of authentication first, which BLE also offers out of the box, via a MITM flag. The spec says:
The MITM field is a 1-bit flag that is set to one if the device is requesting MITM protection, otherwise it shall be set to 0. A device sets the MITM flag to one to request an Authenticated security property for the STK when using LE legacy pairing and the LTK when using LE Secure Connections.
Core 5.2, Vol. 3, Part H, 3.5.1
With all of this in mind, we can be sure that we have good encryption and authentication given that a) our communication is over LE Secure Connections and b) MITM flag is on. This constraints us to BLE 4.2 and above. But in case we want to be a bit more generous and for backwards compatibility, we can consider the same for LE Legacy Pairing given that our Association Model is either Out of Band or Numerical Comparison. We discard Passkey authentication because it’s been proven weak in previous research, and we discard Just Works because the TK is set to 0 and it becomes very easy for an attacker to brute force the STK and eavesdrop on the connection.
In order to showcase and simulate examples of good and bad BLE security, we use nRF Connect Mobile to simulate a BLE Peripheral with ad hoc Characteristics that require authentication and others that don’t. Conveniently, iOS and Android offer BLE traffic capturing capabilities, and Wireshark supports both of their packet capture formats.
Here, I’ve created a Peripheral with Characteristics that require encryption and authentication. We can easily find the authentication packets by searching for the
Pairing response string:
The screenshot above shows how a
Read Request (for the Current Time Characteristic) is followed by an
Error Response - Insufficient Authentication, after which comes a
Pairing Request and finally, the
If, however, we set up a Characteristic that doesn’t require authentication and the devices aren’t already authenticated, everything goes smoothly:
The Two-Piece Puzzle
Let us zoom out for a second and consider our goal: testing for authentication to ensure no one can tamper with our BLE conversation. We can now tell good from bad in terms of BLE authentication. How can we leverage this info to test BLE security in an automated way? To do that we need two things: Automate packet capturing of the traffic, and perform deep packet inspection to look for the security parameters seen in the Wireshark screenshots above.
Let us zoom out for a second and consider our goal: testing for authentication to ensure no one can tamper with our BLE conversation.
Automating Packet Capturing
On Android it’s easy to automate the process.
- Look for the file named btsnoop_hci.log. On most devices this would be in a path that you can access with adb pull.
- Delete the file and kill the bluetooth daemon to start a new recording session.
- `adb pull` the file to save the recording session.
Due to Android’s sandboxing, you need to be root in order to kill bluetooth, however this could be done the non-root way by using a slightly different approach. We’ll do away with trying to kill bluetooth, and when we want to initiate a recording session, we would open btsnoop_hci.log and seek to the end of the file, effectively reading from it later, to an output file. This output file would only contain raw packets, no file header or metadata, but it shouldn’t be too hard to add those back in order to reconstruct a valid packet capture file. (We’ll cover more about file formats in the next section.)
For the jailed approach, we’ll have to do some reverse engineering to figure out how Apple’s BLE traffic-capturing utility, PacketLogger, does its job.
Reversing enough Apple daemons, you learn not to look for something in just one file; everything is a microservice. Reversing enough Objective-C, you learn that selectors are usually a goldmine. Also, it’s reasonable to think that PacketLogger would have to
connect to the device before initiating a recording session. Let’s first look for all the PacketLogger-related binaries:
[email protected] PacketLogger.app % find /Applications/PacketLogger.app -type f | file -f - | grep Mach-O /Applications/PacketLogger.app/Contents/MacOS/PacketLogger: Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit executable x86_64] [arm64:Mach-O 64-bit executable arm64] /Applications/PacketLogger.app/Contents/MacOS/PacketLogger (for architecture x86_64):Mach-O 64-bit executable x86_64 /Applications/PacketLogger.app/Contents/MacOS/PacketLogger (for architecture arm64):Mach-O 64-bit executable arm64 /Applications/PacketLogger.app/Contents/Resources/packetlogger: Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit executable x86_64] [arm64:Mach-O 64-bit executable arm64] /Applications/PacketLogger.app/Contents/Resources/packetlogger (for architecture x86_64):Mach-O 64-bit executable x86_64 /Applications/PacketLogger.app/Contents/Resources/packetlogger (for architecture arm64):Mach-O 64-bit executable arm64 /Applications/PacketLogger.app/Contents/Library/LaunchServices/com.apple.bluetooth.PacketLoggerHelper: Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit executable x86_64] [arm64:Mach-O 64-bit executable arm64] /Applications/PacketLogger.app/Contents/Library/LaunchServices/com.apple.bluetooth.PacketLoggerHelper (for architecture x86_64):Mach-O 64-bit executable x86_64 /Applications/PacketLogger.app/Contents/Library/LaunchServices/com.apple.bluetooth.PacketLoggerHelper (for architecture arm64):Mach-O 64-bit executable arm64 /Applications/PacketLogger.app/Contents/Frameworks/PacketDecoder.framework/Versions/A/PacketDecoder: Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit dynamically linked shared library x86_64] [arm64] /Applications/PacketLogger.app/Contents/Frameworks/PacketDecoder.framework/Versions/A/PacketDecoder (for architecture x86_64):Mach-O 64-bit dynamically linked shared library x86_64 /Applications/PacketLogger.app/Contents/Frameworks/PacketDecoder.framework/Versions/A/PacketDecoder (for architecture arm64):Mach-O 64-bit dynamically linked shared library arm64
So we’ve got some company here besides the main binary:
com.apple.bluetoothPacketLoggerHelper, and the dylib PacketDecoder. Now if we simply look for the connect string in the selectors in all of those binaries, we’d be taking a nice shortcut to our goal. We eventually that our target is in the PacketDecoder binary.
And it’s in the appropriately named class
Looking at the implementation of
-[PLiOSDataReceiver connectToDevice], we see the interesting function call
AMDeviceSecureStartService(x0, "com.apple.bluetooth.BTPacketLogger", x2, x3):
A quick search on
AMDeviceSecureStartService tells us that it’s related to Apple’s lockdownd. Lockdown is the TCP bridge between the iPhone and the outside world: device info, backups, file access, etc. are implemented as Lockdown services. The go-to open-source solution for interfacing with lockdownd is libimobiledevice, and although the service
com.apple.bluetooth.BTPacketLogger hasn’t been implemented there yet, it shouldn’t be too hard to implement it since we now know the “channel” through which the packets should travel. An even more accessible solution is Frida, which offers a Lockdown interface out of the box. With a connection to the BTPacketLogger service, you become some reverse engineering away from fully implementing the BTPacketLogger lockdown service outside of PacketLogger. That reverse engineering is simply figuring out how PacketLogger decodes packets it receives from the iPhone and writes them to the packet capture file.
Deep Packet Inspection
Android’s packet capture format of choice is BTSnoop. The format came from the late Symbian OS, which in turn was derived from snoop by Sun Microsystems in RFC 1761. iOS uses the Apple proprietary PacketLogger format, which fortunately has already been reverse engineered and has been in Wireshark for a good number of years. Interestingly, there exists an obscure repo on Github written in Python which decodes both formats and can do the heavy lifting when it comes to a task like this. All we’d do is simply loop through all the packets in a capture file and look for a successful pairing response with the Secure Connection and MITM flags on.
is_auth = False records = logparse.parse(capture_fpath) for r in records: hci_pkt_type, hci_pkt_data = hci_uart.parse(r) if hci_pkt_type != hci_uart.ACL_DATA: continue hci_data = hci_acl.parse(hci_pkt_data) l2cap_len, l2cap_cid, l2cap_data = l2cap.parse(hci_data, hci_data) if l2cap_cid != l2cap.L2CAP_CID_SMP: continue smp_opcode, smp_data = smp.parse(l2cap_data) if smp_opcode != OPCODE_PAIRING_RESPONSE: continue auth_req = int(smp_data) if self._is_secure_connection(auth_req) and self._is_mitm_protected(auth_req): is_auth = True break
The _is_secure_connection() and _
is_mitm_protected() are “macros” that just parse the different
Auth Req parameters, as we saw them in the Wireshark screenshot above.
The Other Puzzle: Encryption
Now that we’ve got authentication down, what about encryption? Encryption is a bit tricky to test, because the GATT protocol doesn’t give us info about the security requirements of every Characteristic — which is probably in the interest of staying as lightweight as possible — which means that a Pairing Request is only sent after a Pairing Response returns Insufficient Authentication. Or in other words, once devices have been paired, there’s no telling which Characteristics require encryption and which do not. The solution? Iteratively invoke (read and/or write) every Characteristic, deleting Bonding data before each invocation, so that devices communicate as complete strangers, returning Insufficient Authentication when a line is being crossed. But that’s a topic for another article .
Mobile security analysts who want a faster way to test IoT mobile applications can take a shortcut with NowSecure Workstation. The solution automates interactive standards-based testing for deep exploration of mobile apps of all kinds. Contact us to get a demo and see how Workstation boosts productivity while testing complex apps such as IoT and USB/Bluetooth-connected equipment.