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
To demonstrate the vulnerability, Armis team developed a proof of concept and exploited the CVE-2017-0781 vulnerability on Android 7.1. https://github.com/ArmisSecurity/blueborne
Last release version of Nexus 4 runs on Android 5.1.1 and bluetooth implementation contains a lot a changes regarding Android 7.x. The main difference is the heap allocator which is totally different.
To exploit CVE-2017-0781 on Nexus 4 device (latest software update) , a different exploit has been developed and is detailed in this blog post.
ALSR bypass was done using CVE-2017-0785 but it is not explained here because there is no difference with Android 7.0
The android image used for this exploit is available here :
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
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
According to the Armis technical paper, this overflow is triggered for any chosen size.
“Notably, since it’s possible to send an arbitrarily sized packet, the
osi_malloc allocation size can be controlled, since
rem_len represents the size of the payload in the packet. This allows an overflow of 8 bytes on the heap following a buffer of any chosen size, which makes exploitation much easier.”
Then the overflow is used to override data contained in another object.
The function pointer
((post_to_task_hack_t *) (&p_mg->data))->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.
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
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|
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
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_Theader. When the
FREE_QUEUE_Tstructure of the corresponding pool is updated, the new value of
p_firstwill 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_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.c file) which processes incoming packets we can understand the following workflow :
- Wait for a new event by calling the blocking function
- Get new mbox message of
- 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.
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.
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
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.
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 .
Data can be uploaded using for instance adb push /sdcard/
Python exploitation script :
Modified adbd daemon + patch :
mkdir lib adb pull /system/lib/libc.so ./lib adb pull /system/lib/hw/bluetooth.default.so ./lib sudo python2 exploit.py hci0 AA:BB:CC:DD:EE:FF ./lib/
Thanks to David, Guillaume and Max for their feedback 😉