# This class can be used to create a bluetooth connection # to a remote device. currently it only supports unauthenticated # connections. in general, it is very basic and offers the bare minimum # to semi-reliably hold an active l2cap channel. import binascii import struct import threading import time from pwnlib import log import internalblue.hci as hci from internalblue.utils.packing import p8, p16 CONNECTION_TYPE_CLASSIC = 0 CONNECTION_TYPE_BLE = 1 class BluetoothConnection: def __init__(self, core, bd_addr, reconnect=1, keepalive=True, timeout=5): self.core = core self.remote_addr = bd_addr self.reconnect = reconnect self.keepalive = keepalive self.timeout = timeout # the handle also determines whether there is currently an active connection self.handle = None self.aclHandlers = [] self.reconnect_counter = 0 self.keepalive_active = False self.link_keys = {} self.encrypted = False self.started_connection = False # connection type can be either 0 (classic) or 1 (ble), default is classic self.connection_type = CONNECTION_TYPE_CLASSIC self.connection_callback = None self.encryption_callback = None self.core.registerHciCallback(self._callback) def _keepaliveTimer(self): if self.keepalive and self.handle: self._sendKeepalive() if self.keepalive_active: threading.Timer(1, self._keepaliveTimer).start() def _sendKeepalive(self): pass def _callback(self, record): h4_record = record[0] if issubclass(h4_record.__class__, hci.HCI_Event): self._hciEventHandler(h4_record) elif issubclass(h4_record.__class__, hci.HCI_Acl): self._aclEventHandler(h4_record.getRaw()) def _hciEventHandler(self, h4_record): event = h4_record.event_code hci_data = h4_record.data status = hci_data[0] # connection complete event if event == 3: # connection complete - sucess if status == 0: handle = struct.unpack_from("h", hci_data[1:])[0] self.handle = handle log.info("Connection to %s complete", binascii.hexlify(self.remote_addr).decode("utf-8")) self.keepalive_active = True self._keepaliveTimer() # connection complete - page timeout elif status == 4: log.info("Page timeout while connecting to %s", binascii.hexlify(self.remote_addr).decode("utf-8")) # disconnection complete event elif event == 5: self.handle = None log.info("Disconnected from " + binascii.hexlify(self.remote_addr).decode("utf-8")) if self.reconnect_counter < self.reconnect: log.info("Trying to reconnect (attempt %d of %d)", self.reconnect_counter, self.reconnect) # wait a second, otherwise we sometimes don't get the connection complete event... time.sleep(1) self.connect() self.reconnect_counter += 1 # authentication complete elif event == 6: # workaround as there is apparently a bug in pythons struct (status,) = struct.unpack_from("b", hci_data) (handle,) = struct.unpack_from("h", hci_data[1:]) log.info("got Authentication Complete from handle %s, status: %d", hex(handle), status) if status == 0: # authentication was successful, now set connection encryption self.core.sendHciCommand(0x0413, p16(handle) + "\x01") self.encrypted = True if self.encryption_callback: self.encryption_callback() pass else: handle = 0 # encryption change complete elif event == 8: (handle, encrypt) = struct.unpack_from("hb", hci_data) log.info("Got Encryption Change Complete from handle %s, encrypt: %d", hex(handle), encrypt) # pin code request elif event == 0x16: (bd_addr,) = struct.unpack_from("6s", hci_data) log.info("Got Pin Code Request for %s", binascii.hexlify(bd_addr).decode("utf-8")) self.core.sendHciCommand(0x040d, bd_addr + "\x00" + "\x41" * 0x10) # link key request elif event == 0x17: (bd_addr,) = struct.unpack_from("6s", hci_data) log.info("Got Link Key request from %s", binascii.hexlify(bd_addr).decode("utf-8")) # link keys are not really implemented yet, just return a random link key self.core.sendHciCommand(0x040b, bd_addr + bytes.fromhex("0d2017c7f90a78cefeeed32210e6519a")) return if bd_addr in self.link_keys: # we have a link key for this device, set it lkey_buf = self.link_keys[bd_addr][::-1] self.core.sendHciCommand(0x040b, bd_addr + lkey_buf) else: # send negative link key reply, we don't have a key self.core.sendHciCommand(0x040c, bd_addr) # link key notification elif event == 0x18: (bd_addr, link_key) = struct.unpack_from("6s16s", hci_data) log.info("Got Link Key notification from %s, key: %s", bd_addr, binascii.hexlify(link_key).decode("utf-8")) self.link_keys[bd_addr] = link_key # io capability request elif event == 0x31: (bd_addr,) = struct.unpack_from("6s", hci_data) log.info("Got IO capability request from %s", binascii.hexlify(bd_addr).decode("utf-8")) # pretend to not have a display or oob data present # no display: 0x03, no oob: 0x00, auth requirements: 0x02 self.core.sendHciCommand(0x042b, bd_addr + "\x03\x00\x02") # user confirmation request elif event == 0x33: (bd_addr,) = struct.unpack_from("6s", hci_data) log.info("Got user confirmation request from %s", binascii.hexlify(bd_addr).decode("utf-8")) # we just accept any confirmation requests self.core.sendHciCommand(0x42c, bd_addr) # simple pairing complete elif event == 0x36: (bd_addr,) = struct.unpack_from("6s", hci_data) log.info("Got simple pairing complete from %s", binascii.hexlify(bd_addr).decode("utf-8")) # le event # everything from le lands here... elif event == 0x3e: le_event_type = hci_data[0] le_handle = struct.unpack_from("h", hci_data[2:4])[0] # enhanced connection complete if le_event_type == 0x0a: log.info("Got le enhanced connection complete, removing device from whitelist") self.core.sendHciCommand(0x2012, bytes.fromhex("00") + self.remote_addr[::-1]) elif le_event_type == 0x01: # sometimes we get connection complete events from previous sessions log.info("got le connection complete with handle %d", le_handle) if self.started_connection: self.handle = le_handle else: log.info("but ignoring it as we did not initiate this connection") def _aclEventHandler(self, data): log.debug("Received ACL data: %s", binascii.hexlify(data).decode("utf-8")) for handler in self.aclHandlers: handler(data) def encryptConnection(self): log.info("+ + + + + + + + Encrypt + + + + + + + +") if not self.handle: log.info("Cannot encrypt, no active connection") return # authentication requested hci cmd log.info("Send authentication requested hci cmd") self.core.sendHciCommand(0x0411, p8(self.handle) + "\x00") timeout = 3 ctr = 0 # wait 3 seconds for an encryted connection while ctr < timeout: time.sleep(0.1) if self.encrypted: return True return False def registerACLHandler(self, handler): self.aclHandlers.append(handler) log.debug("Registered new acl handler") def sendACL(self, data): data_len = p16(len(data)) handle = p16(self.handle | 0x2000) log.debug("Sent acl data: %s", binascii.hexlify(data).decode("utf-8")) self.core.sendH4(0x02, handle + data_len + data) def connect(self): if self.connection_type == CONNECTION_TYPE_CLASSIC: self.core.connectToRemoteDevice(self.remote_addr) elif self.connection_type == CONNECTION_TYPE_BLE: # connection cancel self.core.sendHciCommand(0x200e, b"") # currently only supports random ble addresses, which are the ones # we're targeting here anyways self.core.connectToRemoteLEDevice(self.remote_addr, addr_type=0x01) self.started_connection = True else: log.error("invalid connection type: %d", self.connection_type) timeout_counter = 0 while timeout_counter < self.timeout: if self.handle: break time.sleep(0.1) timeout_counter += 0.1 if self.handle is None: status = False log.info("Connection timeout") if self.reconnect_counter < self.reconnect: log.info("Trying to reconnect (attempt %d of %d)", self.reconnect_counter, self.reconnect) self.reconnect_counter += 1 status = self.connect() else: log.error("Reconnection attempts exhausted") status = False else: log.info("Connection successful") if self.connection_callback: self.connection_callback() status = True return status