1 Commits

Author SHA1 Message Date
Jiska Classen 295043bed9 integrated receive statistics into new version of internalblue, added samsung galaxy s8 2019-09-04 14:09:11 +02:00
6 changed files with 475 additions and 2 deletions
+14 -2
View File
@@ -1,10 +1,22 @@
InternalBlue PoCs and Examples
==============================
All examples were tested on a *Nexus 5* (*BCM4339* chip with firmware *BCM4335C0*) on *Android* and *LineageOS*.
The following examples were tested on a *Nexus 5* (*BCM4339* chip with firmware *BCM4335C0*) on *Android* and *LineageOS*.
* [CVE_2018_5383_Invalid_Curve_Attack_PoC](CVE_2018_5383_Invalid_Curve_Attack_PoC.py)
provides tries to set the y-coordinate during ECDH key exchange to zero. If the device under test accepts the pairing (50% probability), it is vulnerable.
* [LMP_MAC_Address_Filter](LMP_MAC_Address_Filter.py)
replies to all LMP packets with `LMP_not_accepted` if their source is not from a MAC address in the whitelist.
* [NiNo_PoC](NiNo_PoC.py) sets the IO capabilities of the *Nexus 5* to no input, no output.
* [NiNo_PoC](NiNo_PoC.py) sets the IO capabilities of the *Nexus 5* to no input, no output.
Examples for the Raspberry Pi 3:
* [raspi3_rxdn](raspi3_rxdn.py) prints the first bytes of the LE connection struct within the `_connTaskRxDone` callback.
For debugging purposes, warnings are shown for packet failures. The full logging can be enabled via `log_level debug` on the
*InternalBlue* command line. It contains the current channel, RSSI, and event number for each packet. To blacklist channels
from hopping, use `sendhcicmd 0x2014 ff00000000` or similar. Use `wireshark` or `btmon`
to see the channel blacklisting live within the connection struct.
Examples for the Samsung Galaxy S8:
* [s8_rxdn](s8_rxdn.py) same as for Raspberry Pi 3. Call *InternalBlue* with `internalblue -s` for serial setup.
+47
View File
@@ -0,0 +1,47 @@
#!/usr/bin/python2
from pwn import *
from internalblue.adbcore import ADBCore
from internalblue.bluezcore import BluezCore
"""
Script that shows receive statistics from LE connections over HCI on the CYW20735B1 evaluation board.
Generated with Nexmon.
"""
internalblue = ADBCore()
try:
internalblue.interface = internalblue.device_list()[0][1] # just use the first Android device
except IndexError:
internalblue = BluezCore()
try:
internalblue.interface = internalblue.device_list()[0][1] # ...or the first local HCI interface
except IndexError:
log.critical("Adapt the Python script to use an available Broadcom Bluetooth interface.")
exit(-1)
# setup sockets
if not internalblue.connect():
log.critical("No connection to target device.")
exit(-1)
progress_log = log.info("Connected to first target, installing patches...")
# GENERATED PATCHES
internalblue.patchRom(0x0008ea46, '\x89\xf1\x5b\xbc')
internalblue.patchRom(0x0008edc2, '\x89\xf1\x1d\xbc')
internalblue.patchRom(0x0008eec0, '\x89\xf1\x1e\xbb')
internalblue.writeMem(0x00218200, '\x10\xb5\xcc\x22\xff\x21\xce\x20\x0c\xf6\x43\xfe\x04\x46\x04\x22\x07\x49\x0a\x30\x50\xf6\x53\xfb\x06\x4b\x04\xf1\x0e\x00\x19\x68\xc8\x22\x50\xf6\x4c\xfb\x20\x46\xbd\xe8\x10\x40\x0c\xf6\x03\xbd\x18\x80\x21\x00\x80\x28\x28\x00')
internalblue.writeMem(0x00218300, '\x95\xf6\x70\xfc\xff\xf7\x7c\xff\x76\xf6\x9f\xbb\x00\xbf\x00\xbf')
internalblue.writeMem(0x00218500, '\x2d\xe9\xf0\x5f\xfe\xb5\x07\x46\xf3\x22\xff\x21\xf5\x20\x0c\xf6\xc0\xfc\x04\x46\x04\xf1\x0a\x03\x04\x22\x0f\x49\x18\x46\x50\xf6\xce\xf9\x04\xf1\x0e\x03\x4f\xf0\xef\x02\x39\x46\x18\x46\x50\xf6\xc6\xf9\x04\xf1\x0e\x03\x4f\xf0\x01\x02\x07\xf5\xe9\x71\x18\x46\x50\xf6\xbd\xf9\x20\x46\x0c\xf6\x76\xfb\x38\x46\xbd\xe8\xfe\x40\x76\xf6\xb8\xbc\x00\xbf\x00\xbf\x00\x80\x21\x00')
internalblue.writeMem(0x00218600, '\x70\xb5\x05\x46\xfe\xb5\x05\x46\xf4\x22\xff\x21\xf6\x20\x0c\xf6\x40\xfc\x04\x46\x04\xf1\x0a\x03\x04\x22\x0b\x49\x18\x46\x50\xf6\x4e\xf9\x04\xf1\x0e\x03\x4f\xf0\xf0\x02\x29\x46\x18\x46\x50\xf6\x46\xf9\x20\x46\x0c\xf6\xff\xfa\x00\xf0\xe2\xf8\xbd\xe8\xfe\x40\x76\xf6\xc1\xbb\x00\xbf\x00\xbf\x08\x80\x21\x00')
internalblue.writeMem(0x00218800, '\x10\xb5\x08\x22\x82\xb0\xff\x21\x0a\x20\x0c\xf6\x42\xfb\x04\x22\x04\x46\x0b\x49\x0a\x30\x50\xf6\x52\xf8\x00\x20\x9f\xf6\xec\xff\x95\xf6\x3f\xff\x02\xa9\x41\xf8\x04\x0d\x04\x22\x04\xf1\x0e\x00\x50\xf6\x45\xf8\x20\x46\x0c\xf6\xfe\xf9\x02\xb0\x10\xbd\x00\xbf\x10\x80\x21\x00')
internalblue.writeMem(0x00218000, '\x52\x58\x44\x4e\x00\x00\x00\x00\x4c\x45\x50\x52\x00\x00\x00\x00\x52\x53\x53\x49\x00\x00\x00\x00\x52\x42\x55\x46\x00')
# shutdown connection
internalblue.shutdown()
log.info("--------------------")
log.info("To see statistics, execute 'internalblue' and run 'log_level debug'.")
+90
View File
@@ -0,0 +1,90 @@
#!/usr/bin/env python2
# Jiska Classen
# Get receive statistics on a Raspberry Pi 3 for BLE connection events
from pwn import *
from internalblue.hcicore import HCICore
internalblue = HCICore()
device_list = internalblue.device_list()
if len(device_list) == 0:
log.warn("No HCI devices connected!")
exit(-1)
internalblue.interface = device_list[0][1] # just use the first device
RX_DONE_HOOK_ADDRESS = 0x35fbc # _connTaskRxDone
HOOKS_LOCATION = 0x210500
ASM_HOOKS = """
// restore first 4 bytes of _connTaskRxDone
push {r4-r6,lr}
mov r4, r0
// fix registers for our own routine
push {r1-r7, lr}
mov r7, r0
// allocate vendor specific hci event
mov r2, 243
mov r1, 0xff
mov r0, 245
bl 0x3670 // bthci_event_AllocateEventAndFillHeader(4+239+2, 0xff, 4+239);
mov r4, r0 // save pointer to the buffer in r4
// append buffer with "RXDN"
add r0, 10 // buffer starts at 10 with data
ldr r1, =0x4e445852 // RXDN
str r1, [r0]
add r0, 4 // advance buffer by 4
// copy 239 bytes of le_conn to buffer
mov r2, #238
mov r1, r7 // le_conn[0]
bl 0x45824 // __rt_memcpy
// for debugging purposes, we overwrite the first byte
// (which is the connTaskCallback anyway) with RSSI info
mov r2, #1 // 1 rssi byte
add.w r1, r7, #0x10a // le_conn[0x10a] is position of rssi
mov r0, r4
add r0, 14
bl 0x45824 // __rt_memcpy
// send hci event
mov r0, r4 // back to buffer at offset 0
bl 0x358e // send_hci_event
// undo registers for our own routine
mov r0, r7
pop {r1-r7, lr}
// branch back to _connTaskRxDone + 4
b 0x35fc0
"""
# setup sockets
if not internalblue.connect():
log.critical("No connection to target device.")
exit(-1)
# Install hooks
code = asm(ASM_HOOKS, vma=HOOKS_LOCATION)
log.info("Writing hooks to 0x%x..." % HOOKS_LOCATION)
if not internalblue.writeMem(HOOKS_LOCATION, code):
log.critical("Cannot write hooks at 0x%x" % HOOKS_LOCATION)
exit(-1)
log.info("Installing hook patch...")
patch = asm("b 0x%x" % HOOKS_LOCATION, vma=RX_DONE_HOOK_ADDRESS)
if not internalblue.patchRom(RX_DONE_HOOK_ADDRESS, patch):
log.critical("Installing patch for _connTaskRxDone failed!")
exit(-1)
log.info("--------------------")
log.info("To see statistics, execute 'internalblue' and run 'log_level debug'.")
+90
View File
@@ -0,0 +1,90 @@
#!/usr/bin/env python2
# Jiska Classen
# Get receive statistics on a Raspberry Pi 3 for BLE connection events
from pwn import *
from internalblue.hcicore import HCICore
internalblue = HCICore()
device_list = internalblue.device_list()
if len(device_list) == 0:
log.warn("No HCI devices connected!")
exit(-1)
internalblue.interface = device_list[0][1] # just use the first device
RX_DONE_HOOK_ADDRESS = 0x56622 # _connTaskRxDone
HOOKS_LOCATION = 0x210500
ASM_HOOKS = """
// restore first 4 bytes of _connTaskRxDone
push {r4-r6,lr}
mov r4, r0
// fix registers for our own routine
push {r1-r7, lr}
mov r7, r0
// allocate vendor specific hci event
mov r2, 243
mov r1, 0xff
mov r0, 245
bl 0x2770 // bthci_event_AllocateEventAndFillHeader(4+239+2, 0xff, 4+239);
mov r4, r0 // save pointer to the buffer in r4
// append buffer with "RXDN"
add r0, 10 // buffer starts at 10 with data
ldr r1, =0x4e445852 // RXDN
str r1, [r0]
add r0, 4 // advance buffer by 4
// copy 239 bytes of le_conn to buffer
mov r2, #238
mov r1, r7 // le_conn[0]
bl 0x775C8 // __rt_memcpy
// for debugging purposes, we overwrite the first byte
// (which is the connTaskCallback anyway) with RSSI info
mov r2, #1 // 1 rssi byte
add.w r1, r7, #0x10a // le_conn[0x10a] is position of rssi
mov r0, r4
add r0, 14
bl 0x775C8 // __rt_memcpy
// send hci event
mov r0, r4 // back to buffer at offset 0
bl 0x268E // send_hci_event
// undo registers for our own routine
mov r0, r7
pop {r1-r7, lr}
// branch back to _connTaskRxDone + 4
b 0x56626
"""
# setup sockets
if not internalblue.connect():
log.critical("No connection to target device.")
exit(-1)
# Install hooks
code = asm(ASM_HOOKS, vma=HOOKS_LOCATION)
log.info("Writing hooks to 0x%x..." % HOOKS_LOCATION)
if not internalblue.writeMem(HOOKS_LOCATION, code):
log.critical("Cannot write hooks at 0x%x" % HOOKS_LOCATION)
exit(-1)
log.info("Installing hook patch...")
patch = asm("b 0x%x" % HOOKS_LOCATION, vma=RX_DONE_HOOK_ADDRESS)
if not internalblue.patchRom(RX_DONE_HOOK_ADDRESS, patch):
log.critical("Installing patch for _connTaskRxDone failed!")
exit(-1)
log.info("--------------------")
log.info("To see statistics, execute 'internalblue' and run 'log_level debug'.")
+90
View File
@@ -0,0 +1,90 @@
#!/usr/bin/env python2
# Jiska Classen
# Get receive statistics on a Raspberry Pi 3 for BLE connection events
from pwn import *
from internalblue.adbcore import ADBCore
internalblue = ADBCore(serial=True)
device_list = internalblue.device_list()
if len(device_list) == 0:
log.warn("No HCI devices connected!")
exit(-1)
internalblue.interface = device_list[0][1] # just use the first device
RX_DONE_HOOK_ADDRESS = 0x1344D0 # _connTaskRxDone !!! has a Patchram position
HOOKS_LOCATION = 0x210500
ASM_HOOKS = """
// restore first 4 bytes of _connTaskRxDone
push {r4-r12,lr}
mov r4, r0
// fix registers for our own routine
push {r1-r7, lr}
mov r7, r0
// allocate vendor specific hci event
mov r2, 243
mov r1, 0xff
mov r0, 245
bl 0xE628 // bthci_event_AllocateEventAndFillHeader(4+239+2, 0xff, 4+239);
mov r4, r0 // save pointer to the buffer in r4
// append buffer with "RXDN"
add r0, 10 // buffer starts at 10 with data
ldr r1, =0x4e445852 // RXDN
str r1, [r0]
add r0, 4 // advance buffer by 4
// copy 239 bytes of le_conn to buffer
mov r2, #238
mov r1, r7 // le_conn[0]
bl 0x857B4 // __rt_memcpy
// for debugging purposes, we overwrite the first byte
// (which is the connTaskCallback anyway) with RSSI info
mov r2, #1 // 1 rssi byte
add.w r1, r7, #0x1ca // le_conn[0x1ca] is position of rssi
mov r0, r4
add r0, 14
bl 0x857B4 // __rt_memcpy
// send hci event
mov r0, r4 // back to buffer at offset 0
bl 0xE418 // bthci_event_AttemptToEnqueueEventToTransport
// undo registers for our own routine
mov r0, r7
pop {r1-r7, lr}
// branch back to _connTaskRxDone + 4
b 0x1344D4
"""
# setup sockets
if not internalblue.connect():
log.critical("No connection to target device.")
exit(-1)
# Install hooks
code = asm(ASM_HOOKS, vma=HOOKS_LOCATION)
log.info("Writing hooks to 0x%x..." % HOOKS_LOCATION)
if not internalblue.writeMem(HOOKS_LOCATION, code):
log.critical("Cannot write hooks at 0x%x" % HOOKS_LOCATION)
exit(-1)
log.info("Installing hook patch...")
patch = asm("b 0x%x" % HOOKS_LOCATION, vma=RX_DONE_HOOK_ADDRESS)
if not internalblue.writeMem(RX_DONE_HOOK_ADDRESS, patch):
log.critical("Installing patch for _connTaskRxDone failed!")
exit(-1)
log.info("--------------------")
log.info("To see statistics, execute 'internalblue' and run 'log_level debug'.")
+144
View File
@@ -46,6 +46,23 @@ class InternalBlue:
self.interface = None # holds the context.device / hci interaface which is used to connect, is set in cli
self.fw = None # holds the firmware file
# RXDN statistics callback variables
self.last_nesn_sn = None
self.last_success_event = None
self.last_event_ctr = 0
self.last_channel = 0
self.last_channel_map = 0
self.last_event_timestamp = 0
# variables for measuring PDR
self.pdr_values = {}
for i in range(0, 37):
self.pdr_values[i] = collections.deque([1, 1, 1, 1, 1, 1, 1, 1, 1], maxlen=10)
self.PDR_THRESHOLD = 0.9
self.CHAN_COUNT_THRESHOLD = 10
self.pdr_logfile = open("/tmp/pdr.log", "w")
self.data_directory = data_directory
self.s_inject = None # This is the TCP socket to the HCI inject port
@@ -119,6 +136,7 @@ class InternalBlue:
# Register callbacks which handle specific HCI Events:
self.registerHciCallback(self.connectionStatusCallback)
self.registerHciCallback(self.coexStatusCallback)
self.registerHciCallback(self.lereceiveStatusCallback)
def check_binutils(self, fix=True):
"""
@@ -1495,6 +1513,132 @@ class InternalBlue:
log.info("[Coexistence Statistics: Grant=%d Reject=%d -> Reject Ratio %.4f]" % (coex_grant, coex_reject, ratio))
return
def lereceiveStatusCallback(self, record):
"""
RXDN Callback Function
Depends on the raspi3_rxdn.py or eval_rxdn.py script,
which patches the _connTaskRxDone() function and copies
info from the LE connection struct to HCI.
"""
hcipkt = record[0] # get HCI Event packet
if not issubclass(hcipkt.__class__, hci.HCI_Event):
return
# Only do something when we have the correct firmware
if not self.fw:
return
raspi = False
eval = False
s8 = False
if self.fw.FW_NAME == "BCM43430A1":
raspi = True
elif self.fw.FW_NAME == "CYW27035B1":
eval = True
elif self.fw.FW_NAME == "BCM4347B0":
s8 = True
else:
return
if self.fw and hcipkt.data[0:4] == "RXDN":
data = hcipkt.data[4:]
# log.info("data: " + data.encode('hex'))
# Raspi 3 gets errors
if len(data) < 239:
return
if raspi or s8:
packet_curr_nesn_sn = u8(data[0xa0])
elif eval:
packet_curr_nesn_sn = u8(data[0xa4])
packet_channel_map = data[0x54:0x7b]
packet_channel = u8(data[0x83])
packet_event_ctr = u16(data[0x8e:0x90])
packet_rssi = u8(data[0]) # currently not supported by s8!
# When looking at the NESN and the SN bit, we check for TX and RX errors
# if self.last_nesn_sn and ((self.last_nesn_sn ^ packet_curr_nesn_sn) & 0b1100) !=0b1100:
# log.info(" ^----------------------------- ERROR --------------------------------")
# When looking only at the NESN bit, we check for TX errors -> this is the thing we want in our EWSN paper
if self.last_nesn_sn and ((self.last_nesn_sn ^ packet_curr_nesn_sn) & 0b0100) != 0b0100:
pdr_current = 0
else:
pdr_current = 1
self.pdr_values[packet_channel].appendleft(pdr_current)
pdr_avg = sum(self.pdr_values[packet_channel]) / float(len(self.pdr_values[packet_channel]))
# log.info("%s event: %5d, channel: %2d, channel_map: 0x%010x, PDR: %d, PDR_avg: %f" % (self.last_event_timestamp, self.last_event_ctr, self.last_channel, self.last_channel_map, pdr_current, pdr_avg))
self.pdr_logfile.write("%s event: %5d, channel: %2d, channel_map: 0x%010x, PDR: 1\r\n" % (
self.last_event_timestamp, self.last_event_ctr, self.last_channel, self.last_channel_map))
self.pdr_logfile.flush()
if pdr_avg < self.PDR_THRESHOLD:
active_channels = bin(self.last_channel_map).count('1')
if active_channels <= self.CHAN_COUNT_THRESHOLD:
# log.info('to few channels active -> whiteliste all')
self.pdr_logfile.write('to few channels active -> whiteliste all')
self.pdr_logfile.flush()
self.sendHciCommand(0x2014, '\xff\xff\xff\xff\xf1', timeout=0)
else:
self.pdr_logfile.write(
"going to BLACKLIST channel %2d, %2d channels active" % (self.last_channel, active_channels))
new_channel_map = self.last_channel_map & (0x1fffffffff ^ (0b1 << self.last_channel))
self.pdr_logfile.write(
"current_channel_map: 0x%010x, new map: 0x%010x" % (self.last_channel_map, new_channel_map))
self.pdr_logfile.flush()
new_channel_map_bytes = []
for i in range(0, 5):
new_channel_map_bytes.append(chr(new_channel_map >> (i * 8) & 0xff))
self.sendHciCommand(0x2014, ''.join(new_channel_map_bytes), timeout=0)
# currently only supported by eval board: check if we also went into the process payload routine,
# which probably corresponds to a correct CRC
# if self.last_success_event and (self.last_success_event + 1) != packet_event_ctr:
# log.info(" ^----------------------------- MISSED -------------------------------")
# TODO example for setting the channel map
# timeout needs to be zero, because we are already in an event reception routine!
# self.sendHciCommand(0x2014, '\x00\x00\xff\x00\x00', timeout=0)
self.last_nesn_sn = packet_curr_nesn_sn
# draw channel with rssi color
color = '\033[92m' # green
if 0xc8 > packet_rssi >= 0xc0:
color = '\033[93m' # yellow
elif packet_rssi < 0xc0:
color = '\033[91m' # red
channels_total = u8(packet_channel_map[37])
channel_map = 0x0000000000
if channels_total <= 37: # raspi 3 messes up with this during blacklisting
for channel in range(0, channels_total):
# channel_map |= (0b1 << 39) >> u8(packet_channel_map[channel]) # Use the flipped representation of the channel map to be consistent in my code
channel_map |= (0b1 << u8(packet_channel_map[channel]))
# log.debug("LE event %5d, map %10x, RSSI %d: %s%s*\033[0m " % (packet_event_ctr, channel_map,
# (packet_rssi & 0x7f) - (128*(packet_rssi >>7)), color, ' '*packet_channel))
# Store the following information for the next connection event, because we will know only by then, if the event was successful
self.last_event_ctr = packet_event_ctr
self.last_channel = packet_channel
self.last_channel_map = channel_map
self.last_event_timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
if self.fw and hcipkt.data[0:4] == "LEPR":
data = hcipkt.data[4:]
self.last_success_event = u16(data[0x8e:0x90])
def readHeapInformation(self):
"""
Traverses the double-linked list of BLOC structs and returns them as a