BLUUID: Firewallas, Diabetics, And... Bluetooth

Aug 20, 2024

21 mins read

In this blog, the second in the series, you will learn about how to build a database of Bluetooth Low-Energy (BTLE) Generic Attribute (GATT) Universally Unique Identifiers (UUIDs) capable of remotely identifying Bluetooth Low-Energy devices for the purposes of vulnerability research, exploitation, and quantifying impact.

The importance of understanding this subject matter is put into context by highlighting recent physiological harm caused by BTLE enabled insulin pumps. The methodologies are demonstrated via CVE-2024-40892 and CVE-2024-40893; vulnerabilities that were discovered in Firewalla firewall products as part of this research.

Recently, the FDA issued a Class I recall, the most serious type of recall, for software Version 2.7 of the Apple iOS t:connect mobile app used to interface with a t:slim X2 insulin pump. The reason for the recall is stated to be due to the app repeatedly crashing/restarting. This results in increased battery drain due to excessive bluetooth communications, ultimately ending with the insulin pump shutting down. There have been 224 reported injuries as of April 15, 2024 according to the FDA.

The physical insulin pump hardware which resulted in injuries caused by excessive bluetooth communications was NOT recalled and presumably remains unchanged.

The unfortunate truth is the only issue resolved through this recall is that of the vendor removing access to a bluetooth client that resulted in injuries due to a bug in the software they are responsible. Excessive bluetooth communications (A.K.A. radio spam) resulting in battery drain or device crashes on medical devices requires no novel understanding of bluetooth to carry out. In fact, this is the second time I’m saying this publicly about BTLE enabled insuling pumps in particular.

Injuries relating to BTLE enabled devices are not theoretical. A cursory review of many devices will reveal that repeatedly reading a BTLE device’s battery service; a completely normal operation, will quickly drain the battery as a result of increased radio communications and negatively impact the user’s quality of life. I am not a healthcare or policy expert, but it appears someone (FDA? FCC? Vendors?) are failing miserably at due diligence when it comes to the impact of bluetooth on healthcare, particularly relating to vulnerable individuals.

As stated in the first blog in this series:

  1. Measuring the impact of security and privacy implications of Bluetooth is even harder.

The real world potential harm caused by BTLE vulnerabilities proveably extends beyond just privacy and security. It is imperative that as an increasing number of BTLE medical devices are introduced into circulation that we have some way to measure and quantify these systems. But enough about the mediocrity that is the current state of BTLE devices in healthcare.

Let’s learn how to take a measure of the BTLE ecosystem as a whole so that we can enable meaningful and informed conversations. For every section introducing complex and necessary background information you will find a proposed “shortcut” at the end, often allowing the task to be accomplished with nothing more than built in system tools you already have available.

How Bad is the Best-Case Scenario?

The conclusion is: Bluetooth vulnerability assessment is not even a thing. For those of you that work enterprise security, I’m happy to tell you: Don’t worry, there’s nothing you have to do, because there’s nothing you could do even if you wanted to. I hope that makes you feel better. - Xeno Kovah (Open Wounds: The Last 5 Years Have Left Bluetooth To Bleed)

If a vulnerability exists in a BTLE device, one of the overall best-case scenarios for a device to ever receive a patch for a software bug/vulnerability is through a companion smartphone app since a smartphone has both:

  1. An internet connection capable of downloading the patch.
  2. A BTLE radio capable of pushing the patch to the vulnerable BTLE device.

Despite this blog being Part 2, it was started many, many, many months before Part 1. I have now curated a massive collection of Android application PacKages (APK). If you don’t have that kind of patience, don’t fret, you can follow along at home with just a single APK, such as RoboRoach.

What is an APK and how does it help us with BTLE?

An android application package (APK) that interacts with a remote device over BTLE will contain all the the necessary code to do so. If we are able to curate a collection of these APK files, we can get a view into the BTLE ecosystem from a point-of-view that does not necessitate being within physical radio proximity to a device. Additionally, the dataset will encompass the best-case scenario mentioned before the break.

Reverse engineering and vulnerability analysis of Android APKs is a complex topic. This section aims solely to introduce the nature of the file structure and shortcuts that can be used to quickly determine “Does this APK use Bluetooth?" as a filter for curating our collection. Later sections in this blog will describe how to extract uniquely identifying attributes in the form of UUIDs to create a database for remote device identification in practice.

Android APK files are just ZIP files. If you rename their file extension to .zip, you can extract them to a folder to peek inside at the contents and get familiar. Contents will vary, but generally you’ll find the following files:

  • AndroidManifest.xml

Despite its file extension, this is not plaintext XML. This Android binary XML (A.K.A. AXML) defines the application name, permissions, and various metadata regarding the app.

  • classes<NUMBER>.dex

The DEX files are dalvik bytecode , a register-based machine. This is the core code that runs in a platform-independent way across various phone hardware and CPU architectures.

  • /lib/<CPU_ARCHITECTURE>/library.so

The SO files are Shared Object libraries containing CPU-architecture native assembly code. Typically these are shared object libraries that export Java Native Interfaces (JNI) which allow the platform-independent DEX code to load and execute platform-dependent SO code.

So how do we check if unknown.apk uses Bluetooth?

Well, the app can’t use Bluetooth unless it’s registered with the correct permissions in the manifest. We can check this easily using apkanalyzer which is part of the official Android SDK.

$ apkanalyzer manifest permissions unknown.apk | grep "BLUETOOTH"

android.permission.BLUETOOTH_ADMIN
android.permission.BLUETOOTH_SCAN
android.permission.BLUETOOTH
android.permission.BLUETOOTH_CONNECT

Note: If you’re sourcing your APKs from certain Android app marketplaces, they may have an API endpoint that allows for you to check for the permissions requested by the app before you download it.

We now know that unknown.apk registers Bluetooth permissions. However, this doesn’t necessarily mean it uses them to do the things we’re interested in regarding BTLE GATT. Many apps use location APIs for maps, for which Bluetooth is an important part of determining an accurate location of a phone, but isn’t directly relevant to this research.

How do we determine if unknown.apk uses Bluetooth GATT?

While I believe it is technically possible to use Bluetooth purely from native assembly code, I’ve not found any examples of that occurring, and it looks quite painful to attempt. All observed developers will use the readily available android.bluetooth.BluetoothGatt classes. When their code is compiled to DEX the format for the name of the class changes slightly, but a class in the raw dalvik bytecode is prefixed with L.

We can use the following commands to determine if an APK utilizes the relevant GATT classes.

$ unzip -qq -c unknown.apk "*.dex" | grep 'Landroid/bluetooth/BluetoothGatt'

grep: (standard input): binary file matches

It can be inferred that presence of the class string android.bluetooth.BluetoothGatt is enough to reasonably assert that the app also likely has the necessary permissions to use the class, thereby eliminating the need to explicitly check permissions as an independent step.

How extract uniquely identifying attributes from this APK?

As you may remember from Part 1, we built a piece of hardware that efficiently indexes remote BTLE devices within radio proximity. Among the data logged from this process are the UUIDs that describe the tree of interfaces used to interact with the remote devices.

{
//<snip>
"tree": [
  {
    "svc": "00001800-0000-1000-8000-00805f9b34fb",
    "chr": "00002a00-0000-1000-8000-00805f9b34fb",
    "prop": 2
  },
  {
    "svc": "00001800-0000-1000-8000-00805f9b34fb",
    "chr": "00002a01-0000-1000-8000-00805f9b34fb",
    "prop": 2
  },
  {
    "svc": "00001801-0000-1000-8000-00805f9b34fb",
    "chr": "00002a05-0000-1000-8000-00805f9b34fb",
    "prop": 32
  },
  {
    "svc": "0000180a-0000-1000-8000-00805f9b34fb",
    "chr": "00002a29-0000-1000-8000-00805f9b34fb",
    "prop": 2
  }
]
}

UUID’s are the text representation of 128-bit integers. Mathematically speaking, there’s about 5.3 undecillion (5 with 36 zeros behind it) possible different UUIDs. At a scale that big, seeing the same UUID on the scan of a remote device as well as in an APK out of pure chance is insanely small. Within Bluetooth, there’s biases to this math due to base UUIDs, but the chances are still heavily in our favor that any singular match is meaningful in some way. Even moreso if multiple UUIDs are matched.

Conceptually, the idea of using BTLE UUID’s as a uniquely identifying fingerprint is not a new concept:

A keen reader will notice that Xeno Kovah from OpenSecurityTraining2 has obtained permission to mirror the 2019 BLEScope dataset under Analysis/one_time_initialization/BLEScope_extractedUUIDs.json in their Blue2thprinting repository = .

The methodologies as employed in the above research largely depend on using a DEX disassembler such as Androguard to recover control-flow graphs and references to identify UUIDs that are constructed within the call-tree for android.bluetooth.BluetoothGatt. These methods are very accurate, but also slow.

We can again use apkanalyzer with great success to generate a reference tree:

$ time apkanalyzer dex reference-tree --references-to android.bluetooth.BluetoothGatt unknown.apk

android.bluetooth.BluetoothGatt
 android.bluetooth.BluetoothDevice android.bluetooth.BluetoothGatt connectGatt(android.content.Context,boolean,android.bluetooth.BluetoothGattCallback,int)
  com.firewalla.chancellor.utils.bluetooth.BTDevice void connect()
   com.firewalla.chancellor.utils.bluetooth.BleOperationConnect void run()
<contd.>
real    0m7.892s
user    0m17.757s
sys 0m1.263s

About 8 seconds to recover the reference tree for Bluetooth GATT operations isn’t bad, but this is a single APK. We also only have the reference tree, not the disassembled contents of the (in my case 1665) DEX classes/methods which actually contain the UUIDs.

As of Ghidra 10.2 released November 2022, support for the most common modern “Multi-DEX” format is included. This means that you can use existing write-ups like Callgraphs with Ghidra, Pyhidra, and Jpype by @clearbluejar with minimal changes such as fixing up the “External Programs” table prior to analysis to use absolute paths instead of relative (which appears to be a bug in the APK loader). Again, this method yields very high accuracy for manual review, but is slow for automation.

But what if I want to go fast?

You implement a data model that is capable of matching potentially lossy records. Since the other part of our dataset is collected from real world data via radio where a remote device can disconnect at any time, the dataset is already lossy, and an exact match is never guaranteed anyways.

The most useful android.bluetooth.BluetoothGatt methods utilize a modifier/type of static UUID. The UUID type is from java.util.UUID class, which can be constructed most commonly with java.util.UUID.fromString(String name). In compiled DEX Dalvik bytecode, type const-string are prefixed by $ in the generated bytecode.

All of this to say: If you don’t want to build a full DEX bytecode decompilation pipeline from scratch, you can just use regex.

$ time unzip -qq -c unknown.apk "*.dex" \
| strings -36 \
| tr A-Z a-z \
| sed -nr 's/\$((([a-f0-9]|%|x){4,}-){4}([a-f0-9]|%|x){4,})/\1/p'


fbb05afa-9145-41f1-8076-9de8be56f104
0eba60fd-0155-4528-9c32-3b765057433e
ed4cc6a8-3fcf-4b2b-a15a-157fa8a70a0b
ed4cc6a8-3fcf-4b2b-a15a-157fa8a70a0c
ed4cc6a8-3fcf-4b2b-a15a-157fa8a70a2b
ed4cc6a8-3fcf-4b2b-a15a-157fa8a70a2c
ed4cc6a8-3fcf-4b2b-a15a-157fa8a70a5b
ed4cc6a8-3fcf-4b2b-a15a-157fa8a70a5c
ed4cc6a8-3fcf-4b2b-a15a-157fa8a70a6b
ed4cc6a8-3fcf-4b2b-a15a-157fa8a70a6c
ed4cc6a8-3fcf-4b2b-a15a-157fa8a70a7b
ed4cc6a8-3fcf-4b2b-a15a-157fa8a70a7c
ed4cc6a8-3fcf-4b2b-a15a-157fa8a70a8b
ed4cc6a8-3fcf-4b2b-a15a-157fa8a70a8c
ed4cc6a8-3fcf-4b2b-a15a-157fa8a70a9b
ed4cc6a8-3fcf-4b2b-a15a-157fa8a70a9c
ed4cc6a8-3fcf-4b2b-a15a-157fa8a70aab
ed4cc6a8-3fcf-4b2b-a15a-157fa8a70aac
ed4cc6a8-3fcf-4b2b-a15a-157fa8a70abb
ed4cc6a8-3fcf-4b2b-a15a-157fa8a70abc
ed4cc6a8-3fcf-4b2b-a15a-157fa8a70adb
ed4cc6a8-3fcf-4b2b-a15a-157fa8a70adc
ed4cc6a8-3fcf-4b2b-a15a-157fa8a70ae0
ed4cc6a8-3fcf-4b2b-a15a-157fa8a70ae1
ed4cc6a8-3fcf-4b2b-a15a-157fa8a70aec
ed5cc6a8-3fcf-4b2b-a15a-157fa8a70abd
258eafa5-e914-47da-95ca-c5ab0dc85b11

real    0m0.198s
user    0m0.341s
sys 0m0.042s

Quite a noticeable speedup to only ~0.2s!

While not immediately apparent in the above command, the above regex will also match and extract several common developer patterns before the final UUID is created such as:

Using string.format() to construct the UUID string:

ppOVar16[0] = pLVar9;
pSVar10 = String.format("%08x-0000-1000-8000-00805f9b34fb",ppOVar16);
ref_00 = UUID.fromString(pSVar10);

Mistakenly(?) prefixing an additional 0 to an otherwise valid UUID.fromString():

UUID pUVar1;
pUVar1 = UUID.fromString("000002902-0000-1000-8000-00805f9b34fb");

It will not match on things that are replacement/concatenation:

UUID uuidFromString(String p0)

{
  int iVar1;
  UUID pUVar2;
  undefined ref;
  
  iVar1 = p0.length();
  if (iVar1 == 4) {
    ref = "0000ZZZZZ-0000-1000-8000-00805f9b34fb";
    p0 = ref.replace("ZZZZZ",p0);
  }
  pUVar2 = UUID.fromString(p0);
  return pUVar2;
}

Of course, this is the caveat. Using regex will not dynamically emulate arbitrary DEX bytecode to solve for templated values. Using regex will also match UUIDs that are elsewhere in the app (overmatching), but it will absolutely recover the well-formed BTLE UUIDs quickly and simply.

By simply using some common Linux shell commands, we’re able to curate a database of BTLE GATT UUIDs and their corresponding Android APK.

Does this Remote Identification System Work In Practice?

At time of writing there are ~3M Android apps available in the Google Play Store, with more available in other Android App stores worldwide.

For a sampling:

  • 515,765 apps were indexed.
  • 74,590 of those apps had android.permission.BLUETOOTH permissions.
  • 45,735 of those apps were downloaded from the Google Play Store.
  • 14,681 of those apps included classes for android.bluetooth.BluetoothGatt.* .

These were used to create a database of UUIDs. Now, when scanning and indexing the service/characteristic GATT tree from a remote BTLE device I can instantly query the database through an HTTP endpoint to identify the corresponding Android APK that shares the most overlap with those UUID’s.

Sitting at my desk one night while tinkering on this project I had just finished a piece of code that scans remote BTLE devices and attempts to look up their corresponding Android apps in real time using an HTTP API I’d built. The system worked instantly, uniquely identifying a system out of thin air!

UUID’s collected from a local BTLE device (Left) and the corresponding 95% accurate APK identification for com.firewalla.chancellor (Right)

  1. Unbeknownst to me at the time, my internet connectivity had dropped.
  2. Lack of internet connectivity triggered a behavior which exposed an undocumented BTLE interface on my Firewalla firewall.
  3. My real-time API identified the corresponding Android APK to communicate with the device in my local proximity.
  4. A downloadable filepath is provided to me to immediately begin decompiling the APK and extract the relevant APIs and protocols needed to interact with the device.

At this point in time, I opened the APK for manual analysis in Ghidra and immediately spotted the text -----BEGIN RSA PRIVATE KEY----- in code with close proximity to the utilization of several of the BTLE UUIDs, prompting further investigation due to the fact that private keys should ideally be…private.

Firewalla Analysis

My analysis of the Firewalla App, Hardware, and Web services were explicitly scoped to bluetooth functionality. I performed this analysis not as a comprehensive audit, but as a deliberate demonstration of generic point-and-shoot bluetooth device identification for the purposes of vulnerability research and exploitation. The vulnerabilities identified were for the purposes of enhancing the narrative and subject matter of this blog, nothing more.

Firewalla Device Introduction:

The Firewalla Purple/Purple SE/Gold/Gold Pro is a next generation firewall for home, small to medium businesses, and Managed Service Providers. It features network flow insights, control policies, IDS/IPS, cloud-based behavioral analytics, and VPN functionality. All configuration and logs are synced and controlled by https://my.firewalla.com for a unified cloud based management interface.

By default, no network protocol ports are available on the WAN interface. Even inbound ICMP is disallowed.

The firmware as deployed on these devices is open source and a combination of https://github.com/firewalla/firewalla and https://github.com/firewalla/firerouter/. For initial setup where internet may not be immediately available for cloud synchronization, an out-of-band management interface (backdoor) binary named firereset is spawned which advertises a Bluetooth Low-Energy (BTLE) interface. As part of the setup flow, the end-user is prompted to scan the QR code on the bottom of the device which contains the license UUID of form 00000000-0000-0000-0000-000000000000 in order to register the device to the firewalla cloud. The initial setup configuration values are persisted to a redis service running locally on the device.

After initial configuration, the firereset binary remains present and inactive unless WAN connectivity to the internet is disrupted. This is determined by checkWanConnectivity.

By default, a disruption in WAN connectivity is determined using the following values:

checkWanConnectivity(
    defaultPingTestIP = ["1.1.1.1", "8.8.8.8", "9.9.9.9"], 
    defaultPingTestCount = 8, 
    defaultPingSuccessRate = 0.5, 
    defaultDnsTestDomain = "github.com", 
    forceExtraConf = {}, 
    sendEvent = false
){...}

If greater than 4 ICMP ping responses to a defaultPingTestIP are exceeded or if the DNS hostname github.com cannot be resolved to an IP, the BTLE firereset service is started for a number of minutes.

Remotely disrupting the WAN connectivity checks in order to expose the out-of-band management interface is trivial. Methods include but are not limited to: Spoofed erroneous ICMP/DNS responses, DDoS, copper wire wrapped around a ferrite bead attached to a piezoelectric propane grill igniter.

CVE-2024-40892

The license UUID located in the QR code on the bottom of the hardware is the primary authenticating factor for the device, cannot be changed by the end-user, and exists for the lifetime of the device’s operation. In the event a network disruption occurs resulting in the firereset service starting, Android/iOS devices that are paired with the Firewalla hardware will automatically fall back to BTLE as a communication mechanism. The end-user will transmit their license UUID in plain text contained in the token field, as well as any other information relevant to the running state of the Firewalla hardware.

As an example the readConfig action is shown below which is available on the BTLE service ed4cc6a8-3fcf-4b2b-a15a-157fa8a70a8b at characteristic ed4cc6a8-3fcf-4b2b-a15a-157fa8a70a8c. This action is triggered automatically by the app, in part to assist the end user in diagnosing recent changes which may have resulted in the network disruption.

{
  "begin": true,
  "end": true,
  "token": "00000000-0000-0000-0000-000000000000",
  "payload": "{\"action\":\"readConfig\"}"
}

On the BTLE firereset service, all write operations require at minimum the token field containing the license UUID. There is a credential service that is used to provision SSH credentials available via BTLE service ed4cc6a8-3fcf-4b2b-a15a-157fa8a70a7b at characteristic ed4cc6a8-3fcf-4b2b-a15a-157fa8a70a7c that allows unauthenticated read operations. The returned values contain the gid which is the unique device identifier as registered in Firewalla cloud as well as the mac which is used in combination with the license UUID for registration with the Firewalla cloud.

{
  "bc": true,
  "gid": "ae75a104-dabb-4a6d-adab-f349283ebec6",
  "v": 10,
  "fv": 0,
  "fb": false,
  "mac": "20:6d:31:00:00:00",
  "name": "Firewalla",
  "cs": "fgcf2bAj7Y8="
}

The last remaining value cs is a checksum of the registered license UUID. The following python code is equivalent to its operation.

import hashlib
import base64

def checkSum(uuidString):
    str2 = uuidString[:8]
    m = hashlib.sha256()
    m.update(str2.encode())
    arrayofByte = bytearray(m.digest())
    return base64.standard_b64encode(arrayofByte[:8])

It is possible to recover the first 8 characters of the license UUID in a manner of seconds by either using a pre-computed lookup table or SHA-256 intrinsics.

At a minimum, this negatively affects the predictability of the license UUID.

For legacy reasons, in some cases only the first 8 characters of a license are validated. However, this section of logic directly interacts with the Bone registration/authentication API for Firewalla’s cloud management platform and I did not explore it further.


Equipped with a license UUID obtained by plain-text BTLE sniffing, bruteforce of remaining keyspace from unauthenticated checksum, physical access to the device, or a logic bug in the unexplored Bone API: A malicious actor has permanent irrevocable remote access via BTLE to configure the device.

There is a credential service that is used to provision SSH credentials available via BTLE service ed4cc6a8-3fcf-4b2b-a15a-157fa8a70a7b at characteristic ed4cc6a8-3fcf-4b2b-a15a-157fa8a70a7c. As this is a sensitive interface, the JSON-in-JSON protocol for this service also utilizes another layer of JSON with signed JSON Web Tokens.

The public key as used by firereset can be quickly retrieved from the UPX-compressed pre-compiled Golang binary in their open source repository with the following commands:

wget https://raw.githubusercontent.com/firewalla/firerouter/629683aa24fe7c527cf6570815a513bb006dcd75/platform/purple/bin/firereset

upx -d firereset

strings firereset | grep -A 13 "\-\-\-\-\-BEGIN PUBLIC KEY\-\-\-\-\-"

The private key used to sign the JSON Web Tokens is the same for every Firewalla user/device and is readily available in plain-text within the Firewalla Android app:

We demonstrate this below by signing a fake license UUID payload for which the signature is validated using the public key:

Signature Proof

By default SSH access is only available on the LAN side of the Firewall. WiFi configuration, access, and credentials can be provisioned via BTLE network configuration service ed4cc6a8-3fcf-4b2b-a15a-157fa8a70a8b at characteristic ed4cc6a8-3fcf-4b2b-a15a-157fa8a70a8c at which point SSH access over WiFi is trivial.

CVE-2024-40893

If you prefer not to stay within WiFi radio proximity, the BTLE network configuration service ed4cc6a8-3fcf-4b2b-a15a-157fa8a70a8b at characteristic ed4cc6a8-3fcf-4b2b-a15a-157fa8a70a8c exposes multiple trivial command-injection vulnerabilities that can be used to establish a reverse shell over the WAN interface. As a network appliance enjoying the benfits of a memory-safe programming language, everything passes through the system shell eventually, as is shown by the ~340 unsanitized .exec() calls throughout the firerouter/firewalla codebases.

Throwing a ; into the configurations for the WAN interfaces surfaces 3 separate command injection vulnerabilities right away. sudo can also be utilized without requiring a password. Since the PingTestIP and DNSTestDomain values are used for health checks, they will be executed in perpetuity every 10-15 seconds.

networkConfig.Interface.Phy.Eth0.Extra.PingTestIP = []string{";touch /tmp/pwn5"}
networkConfig.Interface.Phy.Eth0.Extra.DNSTestDomain = ";touch /tmp/pwn6"
networkConfig.Interface.Phy.Eth0.Gateway6 = ";touch /tmp/pwn7"

Operationally, these parameters can be prefixed with space characters " " to visually push the command injection payload beyond the bounds that are viewable by the end user through the app/webapp.

Additionally, the firewalla does not persist filesystem changes across reboots, but the vulnerable parameters for network configuration are stored in redis which is the exception to that rule.

Additionally, since these parameters are synced to the Firewalla cloud and device restore/migration is supported, an attacker can trivially persist internet based remote access even if the hardware is reset and/or firmware is re-flashed.

Proof of Concept

Code:

Leveraging CVE-2024-40893 to execute a reverse shell and achieve remote code execution as root on a Firewalla Purple.

What have we learned?

Quantifying the security and privacy impacts of Bluetooth is hard due to the disparate availability of resources, complexity of analysis, and further complicated by the constraints of physically being within radio proximity. These attributes create difficulty in achieving a holistic understanding of the BTLE ecosystem.

However, once the subject matter is understood, simple shortcuts can be applied to instantly identify and profile remote devices that meet a best-case scenario of ever receiving software updates.

From insulin pumps to firewalls, the best-case scenario of BTLE security and privacy is abysmal. It is imperative that we educate ourselves and strive to do better as an industry. Problems that are introduced in BTLE devices are likely to be permanent and impossible to resolve after the fact. Until that changes, we must do better from the start.

Sharing is caring!