BlueBorne exploitation on Nexus 4

Introduction

In September 2017, Armis security researchers have published a whitepaper named “BlueBorne” which reveals several vulnerabilities in different bluetooth stack implementations. All major stacks are impacted and for Android system (Bluedroid), three vulnerabilities have been discovered :

CVE-2017-0785: Memory leak
CVE-2017-0781 and CVE-2017-0782 : Buffer overflow which can lead to remote execution

DISCLAIMER : All the information provided here are for educational purposes only. Performing any hack attempts/tests without written permission from the owner of the targeted device is illegal.

Bluedroid differences between Android 5.x and 7.x

Android 7.x

In Android 7.x system, the vulnerability is located in file bnep_main.c and function bnep_data_ind

Line #578, a malloc is performed with the function osi_malloc(rem_len), pointer returned is saved in p_bcp->pending_data. Then, line #579 a memcpy in executed with ((UINT8 *)(p_bcb->p_pending_data +1) as source address.
It can be noticed that 1 is added to p_bcb->p_pending_data address. However p_bcb->p_pending_data is a BT_HDR* pointer so “+1” means + sizeof(BT_HDR). Finally the destination address is (UINT8*)(p_bcb->p_pending_data) + 8 . Because the length of the copy is not adapted with this offset, there is a buffer overflow of 8 bytes after the allocated p_bcb>p_pending_databuffer.

According to the Armis technical paper, this overflow is triggered for any chosen size.

Then the overflow is used to override data contained in another object.

The function pointer ((post_to_task_hack_t *) (&p_mg->data[0]))->callback and its parameter p_mgs are user controlled from bluetooth packets. In their exploit, this was used to call system function of libc.so to finish the exploitation.

Android 5.x

In this Android 5.x; the vulnerability is located in the same file and function.

In this version, it can be noticed that allocation function is different.
In Android 5.x, GKI_getbuf is called whereas it was osi_malloc in Android 7.x

GKI allocation system is based on memory pools of different size (similar to SLAB allocator).
When a buffer is requested, a memory ‘chunk’ is returned but internally its size can be greater than the requested length. The buffer overflow does not impact the same things than in version of Android 7.x which make Armis PoC not working in this case.

In addition, the BTU_POST_TO_TASK_NO_GOOD_HORRIBLE_HACK switch case is not present in the code and could not be used to gain control of the execution flow.

Modifications between both versions were noticed. A new way had to be found to exploit this vulnerability.

Arbitrary write / GKI Heap allocator

At Bluedroid start up, the GKI dynamic allocator is initialized. Memory pools are created and registered in a global variable named gki_cb.

A pool is a continuous memory with fixed chunk data size.

When an allocation is requested, pools are looped from 0 to 9. When the chunk data size of the pool is large enough for the requested size, the allocator returns the first free chunk.

Sizes of all pools can be found in/include/gki_target.h file and can be summarized as following.

Pool ID

Pool Size

0

0x40

1

0x120

2

0x294

3

0x1010

4

0x1faa

5

0x2ec

6

0x10c

7

0x2818

8

0x80

9

0x2000

Before each data chunk, a 8 bytes header contains information about the data. The first element (p_next), is a pointer to the next buffer in the queue. It is used to create a chained list of buffer.

In order to override data of the next chunk, size requested must be equal to the maximum size in the pool targeted. Up to 8 bytes can be overwritten which correspond to the header part.

In the diagrams below, the memory written by the memcpy call is represented in red :

States of each memory pools are managed by a struct FREE_QUEUE_T which contains usage information about a pool. This structure counts the number of chunk used and has a pointer (p_first) to the next free element.

When an allocation is requested, GKI_getbuf function returns the p_first element and changes the value by the p_next pointer contained in the BUFFER_HDR_T.

In order to write data at a controlled address, two allocations in a same pool are needed.

A first one with a size equals to the maximum buffer size of the pool in order to override the p_next field of following BUFFER_HDR_T header. When the FREE_QUEUE_T structure of the corresponding pool is updated, the new value of p_first will be set with a controlled address.

A second allocation to write data at the address previously set.

To control data written, the second allocation must be executed just after the first one. However the GKI allocator is used internally; when a bluetooth packet is received several GKI_getbuf/GKI_free calls are executed.

The pool ID 0 with a chunk size of 0x40 contains smaller objects and cannot be used because several objects are allocated in this pool when a packet is received.

The pool ID 1 ￼(size 0x120) is the best candidate. By tracing the execution of GKI allocator, it has been noticed that for a packet size of 0x120, only one allocation is done.

This allocation is done by the code previously seen with GKI_getbuf. A good point is that this buffer is then filled with incoming packet data.

At this point we are able to perform an arbitrary write with controlled data using the pool ID 1.

Let’s sum up the arbitrary write :

Step 1 : Send a packet with a size of 0x120 bytes. At the end of payload data, write the BUFFER_HDR structure to set the following p_next with an arbitrary address.
Step 2 : Send a packet with a payload size between 0x40 and 0x120.

Control execution / GKI mail box system

By analysing the main loop (btu_task function of btu_task.c file) which processes incoming packets we can understand the following workflow :

Wait for a new event by calling the blocking function GKI_wait

Get new mbox message of BTU_HCI_RCV_MBOX queue with GKI_read_mbox function

Depending the p_msg->eventvalue, the message is processed. If no case corresponds to the event, a global callbacks table is checked. If a callback for this event was found it is expected.

Loop

The exploitation needs two writes :

A first one to register a new callback. This write is also used to register the corresponding message in the mailbox.

A second one to modify the mbox address and make it point to the previous registered message during the first write.

First write : Register a new callback and prepare a new mailbox message

Callback list is stored in the global btu_cb structure which is described below.
The field event_reg contains callbacks associated to an event value.

A good point is that event_reg is located at the begin of the structure. Previous data will be used to register the next new message in the mailbox.

A first arbitrary write is performed to register the system callback associated to event 0xBE, this new event ID will be used to call the final stage of the exploit (calling libc’s system function).

Second write : Modify the mailbox pointer

Mailing boxes are stored in the gki_cb global variable in the OSTaskQFrist[]field.
A mailbox is simply a chained list of BUFFER_HDR_T elements already described in the previous part.

By changing the pointer of BTU_HCI_RCV_MBOX mailbox queue with the address of the last arbitrary write, the new message will be processed and controlled callback will be executed.

Following diagram sums up the two writes :

Note that arguments in green cannot contain null characters and unused fields are set to 0x1111.

Post Exploitation

Using the exploitation explained above it is now possible to execute shell commands with the privileges of com.android.bluetooth.

Unlike the demo done by Armis, we cannot use netcat tool to upload content of the phone. However old android versions contain adb tool.

A linux version of adbd has been modified and was started as a server on computer side.

Then it is possible from the nexus 4 to connect to this server adb connect .

Working with a LG G3, stock ROM android 5.0.1… Same Bluetooth sources.

Script runs without errors and verified that the payloads land in memory at the correct positions relative to their intended structures (remote debug w/ IDA Pro), but for some reason it does nothing more than jam up bluetooth and requires manual reset during which the slider button flips on/off by itself a few times over 30 secs or so. This only happens on the second iteration of the OSTaskQFirst overwrite, which if I’m sane (?) should be the trigger packet. Seems as though beyond having the data written, something does indeed happen where program flow is altered and then goes back to idle loop, but no longer responds to connections until reset. I have made various adjustments to payload offsets in testing, but none have any effect other than outright crashing bluetooth. IDA is a pain and does not like breakpoints for some reason on this one, even after blanketing the related asm functions, they’re ignored and I never get to trace execution. I am not very familiar with gdb… but can follow and learn if anyone has some tips there.

Also, any idea where the magic header “…00 00010000 00410000 ….” is generated along the way in the sources and why debugging live memory shows one less 0 between 0x41 byte and start of payload than as written in script? Trying to understand its relevance.