Wireless Keystroke Injection vulnerability

In this post we will show a vulnerability of Microsoft Windows 10 (and 11) that allows an attacker to impersonate a previously-paired Bluetooth Low Energy (BLE) HID device, particularly a BLE keyboard, and to inject unencrypted keystrokes, resulting in code execution. This vulnerability was reported to Microsoft (via MSRC) that catalogued it as RCE (Remote Code Execution) and severity “Important”.

The scenario we will be handling in this post is that of a Windows system acting as a BLE Master that has a previously-paired legitimate BLE keyboard. After a period of inactivity, the legitimate keyboard is disconnected to save energy. We will explain how at this point the vulnerability allows impersonating the legitimate keyboard without breaking the BLE cryptographic protection.

Some concepts about Bluetooth Low Energy

Bluetooth Low Energy (BLE) is a Bluetooth protocol used by devices that need to save energy and have low communication requirements. There are many devices that use this communication technology instead of Bluetooth classic. BLE is quite different from Bluetooth classic: it uses simpler channel hopping and less radio channels; the pairing procedure and communication stack is also different… To understand the explanation of the vulnerability, let’s show some BLE concepts such as pairing and connection flow.

First of all, we must understand the BLE stack structure. The protocol stack is divided in 2 parts: The Host part, comprising high-level protocols such as ATT, GATT and SMP; and the Controller part, comprising Link Manager and LE Radio (PHY). Each part can be implemented in one single physical device (as it is the case for a BLE keyboard) or in two different ones (as in a laptop that implements the Host part at the OS level and the Controller part at the firmware level). These two parts can communicate via the HCI interface (Host-Controller-Interface), using commands (Host to Controller) and events (Controller to Host). The following image shows illustrates these two parts:

BLE Stack

Source: Bluetooth 5.2 Core Specification – Vol.1 – Part A (p.203)

In a BLE communication there are two participants: a Slave device, that advertises its presence, and a Master device, that can start a connection upon the reception of the Slave’s advertising. The Slave part has attributes that are exposed to Master and, using these attributes, both can send and receive information. When the connection is started, the devices can choose to first perform the Pairing procedure (if they want to encrypt the communication) or simply start transferring data without Pairing (resulting in an unencrypted connection). For our purposes, we will assume that the two devices perform the Pairing procedure.

BLE pairing procedure is described in Bluetooth 5.2 Core Specification, determining the steps to be followed by master and slave to establish a common key -usually- associated to this pair of devices, which is called LTK (Long Term Key). This procedure is required to provide encryption and authentication for BLE communication. BLE defines two pairing schemes: LE Legacy pairing and LE Secure Paring. Both pairing procedures can be performed to create a Long Term Key (LTK) that will be used on each connection to derive a Session Key (SK). As the LTK is calculated and stored at the Host level, it needs to be passed to the Link Controller to allow for the derivation of the SK. The Link Layer will then use the SK to encrypt the communications.

The following diagram shows the simplified message exchange for the first connection, before the pairing takes place, and the establishment of the encrypted channel, after the pairing is completed.

First BLE Connection

Many Bluetooth Low Energy devices drop their connections if there is no need for further communication, so that they can save energy (that is certainly the case of most HID devices). When two devices that have been previously paired want to re-connect, they will use the same message flow without performing the Pairing procedure. The  vulnerability we found can be exploited in the re-connection phase, as will be explained in the next parts of the post.

The vulnerability

Before encryption is established, Bluetooth specification allows to exchange some unencrypted messages, for example the LL_FEATURE_REQ or LL_VERSION_IND, for the Controller-level; but also GATT and ATT messages, as part of GATT Discovery protocol, that are Host-level messages.

As mentioned above, each time a paired BLE device initiates a connection, encryption can be re-established using LL_ENC_REQ and subsequent messages. The Bluetooth specification also allows unencrypted messages just after LL_ENC_REQ is received by the slave. At this moment, the Slave Controller can empty its buffers of unencrypted data before sending LL_ENC_RSP. This can be found in the Bluetooth 5.2 Core Specification – Vol.1 – Part B – 5.1.3:

" Otherwise, when the Link Layer of the slave receives an LL_ENC_REQ PDU it shall generate the slave’s part of the initialization vector (IVs) and the slave’s part of the session key diversifier (SKDs). IVs shall be a 32 bit random number generated by the Link Layer of the slave. SKDs shall be a 64 bit random number generated by the Link Layer of the slave. Both IVs and SKDs shall be generated using the requirements for  random number generation defined in [Vol 2] Part H, Section 2.
The Link Layer of the slave shall finalize the sending of the current Data Physical Channel PDU and may finalize the sending of additional Data Physical Channel PDUs queued in the Controller. After these Data Physical Channel PDUs are acknowledged, until this procedure is complete or specifies otherwise, the Link Layer of the slave shall only send Empty PDUs, LL_TERMINATE_IND PDUs, and PDUs required by this procedure.
If any of the Data Physical Channel PDUs sent by the slave is an LL Control PDU, the Link Layers shall resume any outstanding procedure(s) after the Encryption Start procedure has completed.
The Link Layer of the slave shall then send an LL_ENC_RSP PDU. The Link Layer of the slave shall then notify the Host with the Rand and EDIV fields. After having sent the LL_ENC_RSP PDU, the Link Layer of the slave can receive an LL_UNKNOWN_RSP PDU corresponding to an LL Control PDU sent by the slave. The slave should not disconnect the link in this case. "

This opens a window of opportunity where the Slave’s controller can send unencrypted data after the encryption procedure has started, but before the encryption channel is established. The following diagram shows the exact moment when these messages are allowed to be processed by the Master:

Window Of Oportunity

Data sent by the Slave in this moment must be processed by Master’s Controller and sent to Master’s Host. The Master’s Host must decide if these messages should be processed or not. The vulnerability is right there: for HID (Human Interface Devices), as keyboards or mouses, that use BLE and are paired with Windows OS (as Master), the Master’s Host accepts and processes these unencrypted messages that contain the keystrokes, instead of waiting for the  HCI_ENCRYPTION_CHANGE confirmation that indicates that the encryption channel is finally established or a fail during this procedure. An attacker can exploit this vulnerability by emulating the legitimate HID device and send unencrypted HID messages containing the keystrokes during this window of opportunity, without knowing the LTK and thus impersonating the real HID device.

Exploiting the vulnerability

Before trying to exploit this vulnerability, we need to know how a keyboard sends the keystrokes in legitimate communication. BLE HID devices use a protocol called HID over GATT, defined in the document HID OVER GATT PROFILE SPECIFICATION (V10r00). Basically, when the keyboard establishes the connection and encryption with a Master device, it sends the HID Report Map with the format and the handles that will be used to send these reports. A HID report is a group of bytes representing one or more keystrokes, differentiating between modifier keys, such as SHIFT, CONTROL or ALT; and normal keys, such as a, s, d or f. Modifier keys are sent as a byte mask (usually occupying 1 byte together). The regular keys are codded into a number and are sent using one byte for each key.

The image above shows an example of message using the report model of a Microsoft 1898 BLE Keyboard. It uses the first byte to encode modifier keys, and the following 10 to encode normal keys. In this example, this report is representing the keystroke Shift + a.

Now, we will show how we tried to exploit this vulnerability following two different approaches: The first, using a Host-level application only, that in the end did not work. The second one, using a Controller-level modification, that was the final exploit.

First attempt: Implementing the exploit at the Host level

At first, we tried to implement the exploit at the host level, and it didn’t work. We used a regular dongle (with chipset CSR8510, that allows to change the BDADDR of the dongle) for the Controller-level and Mirage framework for the Host-level. We used the module ble_slave to create an scenario that catches the “onMasterConnect” signal. When that signal is received, we start sending Keystrokes using HID reports, as explained above. As this approach happens at Host-level, we cannot manipulate the timing and messages sent at the Controller-level. Host can only tell the Controller to send HID reports using HCI, but it is the Controller who decides when, exactly, they will be sent. To illustrate this behavior, we captured (using Sniffle, a Bluetooth Low Energy sniffer that captures Bluetooth communications in the air) the traffic produced by this implementation of the attack:

In the capture we can see that HID Report messages (Handle Value Notification, Handle: 0x0013) are sent when the controller decides to send them, and there is only one that falls into the window of opportunity (between the LL_ENC_REQ and LL_ENC_RSP messages). Using this approach, we managed to inject only one or two keystrokes within a few attempts, which has no real impact.

We realized, therefore, that we had to go down to the Controller-level to improve the stability and reliability of the attack, controlling the order and the timing of the messages at the Link Layer level.

Final exploit: implementation at the Controller level

For the implementation at the Controller-level, or Link Layer, we use Zephyr RTOS as system to be flashed into a Nordic nRF5840 dongle. We modify the Zephyr implementation of USB_HCI example to manipulate the traffic to be sent after LL_ENC_REQ. Using this approach, we have full control of Link Layer messages and the state machine of the controller. When the message LL_ENC_REQ is received by our modified firmware, we manually craft the high-level HID report messages and enqueue them into the controller’s sending queue. We also freeze the sending of any other messages that could be sent by Controller standard implementation. As we have done before, we show now a capture of the traffic produced by the attack using this method, obtained with Sniffle:

Capture Controller Level

The capture illustrates that we have full control of messages sent by the Slave’s Controller, preventing the LL_ENC_RSP message from being sent by it. At this point, we had to face another problem: if we send too many reports too quickly, the reports might not be processed or might be processed by the master in a different order. To solve this problem, we implement an exponential decrement of waiting time between keystrokes. Decremental Waiting Time

After receiving the LL_ENC_REQ, the dongle waits 4 seconds to send the first keystroke. After that, it decreases the waiting time dividing by 2 each time it sends a keystroke until it reaches 20 milliseconds. This allows us to inject more than 3000 HID Reports before the Master decides to close the connection. In the best case, that means that we can write more than 13000 characters before the communication is terminated.

Performing the Attack

There are some preconditions for the attack to be performed:

  • We need to know the HID Report Map or the Handle and the format used by the Keyboard. This can be obtained by analyzing a replica of the paired keyboard with the victim’s PC.
  • Also, we need to know the BDADDR of the legitimate keyboard. For this purpose, we need to listen to the BLE communications (using, for example Sniffle) on keyboard re-connection. The Advertising messages of the keyboard include its MAC.
  • Finally, we need to be within the victim’s PC Bluetooth Low Energy range.

Once we have this, we can program the dongle and use it for performing the attack.

Our firmware modification includes the capability of programming the dongle using HCI vendor messages. These messages can be sent to the controller through the HCI interface using, for example, hcitool. The following vendor messages are available:

  • 0x3f 0x03f0 to program the Handle (two first bytes), the report size (the third byte), and the operation type (the last byte).
  • 0x3f 0x03e1 to clear Keystrokes list.
  • 0x3f 0x03e2 to add keystrokes to the list that will be sent to the Master.
  • 0x3f 0x03e3 to indicate that the keystroke list should be sent in a loop until the Master device closes the connection.

Using this interface is tedious and complicated, so we have created a script (kb_injection.py) to program the dongle with a more user-friendly interface. The script reads a configuration file that looks like this one:

{
"REPORT_HANDLE":"0x0013",
"REPORT_SIZE":11,
"REPORT_OPTYPE":"0x1b",
"TEXT_SCRIPT":"{L_WIN}+rpowershell.exe{ENTER}{SLEEP 500}calc.exe{ENTER}"
}

The configuration file shown in the example is valid to perform the attack against a Windows PC with Microsoft BLE Keyboard 1898. This example shows how to prepare a configuration file to open the “Run” dialog on Windows PC, write powersell.exe, press “ENTER”, wait for 0.5 seconds, write calc.exe and finally press “ENTER” again. The expected result is to open the calculator. For more details on how to configure the dongle you can visit our Github.

After the configuration file is written, we can execute the kb_injection.py script as follows:

kb_injection.py -i <hci_interface_of_dongle> -m <BDADDR> -c <configuration_file>

When the script is executed the nRF5940 dongle starts to send advertising messages. Then, when the legitimate keyboard disconnects, the Windows PC will connect to our dongle and the attack will start. This behavior implies that the attacker does not need to know when the legitimate keyboard disconnects: the attack can be launched at any time and it will start whenever the legitimate keyboard disconnects.

Both the script to program the dongle and the modified firmware can be found in our Github.

Links

References

Used and related Tools

Related documents or publications