def scan_ll_feature(self, paddr, patype, timeout: int=10): """LL feature scanning paddr - Peer addresss for scanning LL features. patype - Peer address type, public or random. timeout - sec """ spinner = Halo(text="Scanning", spinner={'interval': 200, 'frames': ['', '.', '.'*2, '.'*3]}, placement='right') hci = HCI(self.hci) logger.info('Scanning LE LL Features of %s, using %s\n'%(blue(paddr), blue(self.hci))) spinner.start() try: event_params = hci.le_create_connection(paddr, patype, timeout=timeout) logger.debug(event_params) except RuntimeError as e: logger.error(str(e)) return except TimeoutError as e: logger.info("Timeout") # logger.error("TimeoutError {}".format(e)) return event_params = hci.le_read_remote_features(event_params['Connection_Handle']) spinner.stop() logger.debug(event_params) print(blue('LE LL Features:')) pp_le_features(event_params['LE_Features']) event_params = hci.disconnect(event_params['Connection_Handle'], ControllerErrorCodes.REMOTE_USER_TERM_CONN) logger.debug(event_params) return
def scan(self, remote_bd_addr:str): hci = HCI(self.iface) event_params = hci.create_connection({ 'BD_ADDR': remote_bd_addr, 'Packet_Type': 0xcc18, 'Page_Scan_Repetition_Mode': 0x02, 'Reserved': 0x00, 'Clock_Offset': 0x0000, 'Allow_Role_Switch': 0x01 }) if event_params['Status'] != 0: print(ERROR, 'Failed to create ACL connection') sys.exit(1) event_params = hci.read_remote_version_information(cmd_params={ 'Connection_Handle': event_params['Connection_Handle'] }) if event_params['Status'] != 0: print(ERROR, 'Failed to read remote version') sys.exit(1) print(blue('Version')) print(' Version:') print(' '*8+lm_vers[event_params['Version']], '(LMP)') print(' '*8+ll_vers[event_params['Version']], '(LL)') print(' Manufacturer name:', event_params['Manufacturer_Name']) print(' Subversion:', event_params['Subversion'], '\n') event_params = hci.read_remote_supported_features({ 'Connection_Handle': event_params['Connection_Handle'] }) if event_params['Status'] != 0: print(ERROR, 'Failed to read remote supported features') else: print(blue('LMP features')) pp_lmp_features(event_params['LMP_Features']) print() if not True if (event_params['LMP_Features'][7] >> 7) & 0x01 else False: sys.exit(1) print(blue('Extended LMP features')) event_params = hci.read_remote_extended_features({ 'Connection_Handle': event_params['Connection_Handle'], 'Page_Number': 0x00 }) if event_params['Status'] != 0: print(ERROR, 'Failed to read remote extented features') else: pp_ext_lmp_features(event_params['Extended_LMP_Features'], 0) for i in range(1, event_params['Maximum_Page_Number']+1): event_params = hci.read_remote_extended_features({ 'Connection_Handle': event_params['Connection_Handle'], 'Page_Number': i}) if event_params['Status'] != 0: print(ERROR, 'Failed to read remote extented features, page', i) else: pp_ext_lmp_features(event_params['Extended_LMP_Features'], i)
def scan_ll_feature(self, paddr, patype): """LL feature scanning paddr - Peer addresss for scanning LL features. patype - Peer address type, public or random. """ hci = HCI(self.hci) logger.info('Scanning LE LL Features of %s, using %s\n' % (blue(paddr), blue(self.hci))) try: event_params = hci.le_create_connection( HCI_Cmd_LE_Create_Connection(paddr=bytes.fromhex( paddr.replace(':', ''))[::-1], patype=patype)) logger.debug(event_params) except RuntimeError as e: logger.error(e) return event_params = hci.le_read_remote_features( HCI_Cmd_LE_Read_Remote_Features( handle=event_params['Connection_Handle'])) logger.debug(event_params) print(blue('LE LL Features:')) pp_le_features(event_params['LE_Features']) event_params = hci.disconnect({ 'Connection_Handle': event_params['Connection_Handle'], 'Reason': ERR_REMOTE_USER_TERMINATED_CONNECTION }) logger.debug(event_params) return
def detect_pairing_feature(self, paddr, patype, timeout:int=10): """ """ # TODO Mac OS 会弹窗,需要解决。 hci = HCI(self.hci) logger.info("Detecting SMP pairing feature of %s, using %s\n"%(blue(paddr), blue(self.hci))) pairing_req = SM_Hdr(sm_command=btsmp.CmdCode.PAIRING_REQUEST) / \ SM_Pairing_Request(iocap="NoInputNoOutput", oob='Not Present', authentication=(0b00 << AUTHREQ_RFU_POS) | (0 << CT2_POS) | \ (0 << KEYPRESS_POS) | (1 << SC_POS) | (0 << MITM_POS) | \ (BONDING << BONDING_FLAGS_POS), max_key_size=16, initiator_key_distribution=(0b0000 << INIT_RESP_KEY_DIST_RFU_POS) \ | (1 << LINKKEY_POS) | (1 << SIGNKEY_POS) | (1 << IDKEY_POS) \ | (1 << ENCKEY_POS), responder_key_distribution=(0b0000 << INIT_RESP_KEY_DIST_RFU_POS) \ | (1 << LINKKEY_POS) | (1 << SIGNKEY_POS) | (1 << IDKEY_POS) \ | (1 << ENCKEY_POS)) event_params = None spinner = Halo(text="Scanning", spinner={'interval': 200, 'frames': ['', '.', '.'*2, '.'*3]}, placement='right') hci = HCI(self.hci) logger.info('Scanning LE LL Features of %s, using %s\n'%(blue(paddr), blue(self.hci))) spinner.start() try: event_params = hci.le_create_connection(paddr, patype, timeout=timeout) logger.debug(event_params) result = btsmp.send_pairing_request(event_params['Connection_Handle'], pairing_req, self.hci) logger.debug("detect_pairing_feature(), result: {}".format(result)) rsp = btsmp.recv_pairing_response(timeout, self.hci) logger.debug("detect_pairing_feature(), rsp: {}".format(rsp)) spinner.stop() pp_smp_pkt(rsp) except RuntimeError as e: logger.error(str(e)) except TimeoutError as e: output = subprocess.check_output(' '.join(['hciconfig', self.hci, 'reset']), stderr=STDOUT, timeout=60, shell=True) event_params = None logger.info("Timeout") # logger.error("detect_pairing_feature(), TimeoutError {}".format(e)) if event_params != None: hci.disconnect(event_params['Connection_Handle'], ControllerErrorCodes.UNSUPPORTED_REMOTE_FEATURE) return
def detect_pairing_feature(self, paddr, patype, timeout: int = 10): """ """ hci = HCI(self.hci) logger.info("Detecting SMP pairing feature of %s, using %s\n" % (blue(paddr), blue(self.hci))) pairing_req = SM_Hdr(sm_command=btsmp.CmdCode.PAIRING_REQUEST) / \ SM_Pairing_Request(iocap="NoInputNoOutput", oob='Not Present', authentication=(0b00 << AUTHREQ_RFU_POS) | (0 << CT2_POS) | \ (0 << KEYPRESS_POS) | (1 << SC_POS) | (0 << MITM_POS) | \ (BONDING << BONDING_FLAGS_POS), max_key_size=16, initiator_key_distribution=(0b0000 << INIT_RESP_KEY_DIST_RFU_POS) \ | (1 << LINKKEY_POS) | (1 << SIGNKEY_POS) | (1 << IDKEY_POS) \ | (1 << ENCKEY_POS), responder_key_distribution=(0b0000 << INIT_RESP_KEY_DIST_RFU_POS) \ | (1 << LINKKEY_POS) | (1 << SIGNKEY_POS) | (1 << IDKEY_POS) \ | (1 << ENCKEY_POS)) event_params = None try: event_params = hci.le_create_connection( HCI_Cmd_LE_Create_Connection(paddr=bytes.fromhex( paddr.replace(':', ''))[::-1], patype=patype), timeout) logger.debug(event_params) result = btsmp.send_pairing_request( event_params['Connection_Handle'], pairing_req, self.hci) logger.debug("detect_pairing_feature(), result: {}".format(result)) rsp = btsmp.recv_pairing_response(timeout, self.hci) logger.debug("detect_pairing_feature(), rsp: {}".format(rsp)) pp_smp_pkt(rsp) except RuntimeError as e: logger.error(e) except TimeoutError as e: output = subprocess.check_output(' '.join( ['hciconfig', self.hci, 'reset']), stderr=STDOUT, timeout=60, shell=True) event_params = None logger.info("Timeout") # logger.error("detect_pairing_feature(), TimeoutError {}".format(e)) if event_params != None: hci.disconnect({ 'Connection_Handle': event_params['Connection_Handle'], 'Reason': 0x1A }) return
def inquiry(self, inquiry_len=0x08): logger.info('BR scanning on ' + blue("hci%d"%self.devid) + \ ' with timeout ' + blue("%.2f sec\n"%(inquiry_len*1.28))+'\n') self.scanned_dev = [] self.remote_name_req_flag = True hci = HCI(self.iface) def inquiry_result_handler(result: bytes): event_code = result[0] logger.debug("Entered inquiry(), inquiry_result_handler()\n" "{}".format(HciEventCodes[event_code].name)) if event_code == HCI_Inquiry_Result.evt_code: self.pp_inquiry_result(result[2:]) elif event_code == HCI_Inquiry_Result_with_RSSI.evt_code: self.pp_inquiry_result_with_rssi(result[2:]) elif event_code == HCI_Extended_Inquiry_Result.evt_code: self.pp_extended_inquiry_result(result[2:]) else: logger.warning('Unknow inquiry result: {}'.format(result)) try: hci.inquiry(inquiry_len=inquiry_len, inquiry_result_handler=inquiry_result_handler) logger.info('Inquiry completed\n') if self.remote_name_req_flag and len(self.scanned_dev) != 0: logger.info('Requesting the name of the scanned devices...') for bd_addr in self.scanned_dev: try: name = hci.remote_name_request({ 'BD_ADDR': bytes.fromhex(bd_addr.replace(':', '')), 'Page_Scan_Repetition_Mode': 0x01, 'Reserved': 0x00, 'Clock_Offset': 0x0000 })['Remote_Name'].decode().strip() except Exception as e: print(e) name = '' print(bd_addr + ':', blue(name)) except HciRuntimeError as e: logger.error("{}".format(e)) except KeyboardInterrupt as e: logger.info('BR/EDR devices scan canceled\n') hci.inquiry_cancel() hci.close()
def scan_lmp_feature(self, paddr): hci = HCI(self.iface) conn_complete_evt = hci.create_connection( paddr, page_scan_repetition_mode=0x02) if conn_complete_evt.status != 0: logger.error('Failed to create ACL connection') sys.exit(1) event_params = hci.read_remote_version_information( cmd_params={'Connection_Handle': conn_complete_evt.conn_handle}) if event_params['Status'] != 0: logger.error('Failed to read remote version') sys.exit(1) print(blue('Version')) print(' Version:') print(' ' * 8 + lmp_vers[event_params['Version']], '(LMP)') print(' ' * 8 + ll_vers[event_params['Version']], '(LL)') print(' Manufacturer name:', green(company_identfiers[event_params['Manufacturer_Name']])) print(' Subversion:', event_params['Subversion'], '\n') complete_evt = hci.read_remote_supported_features( event_params['Connection_Handle']) if complete_evt.status != ControllerErrorCodes.SUCCESS: logger.error('Failed to read remote supported features') else: print(blue('LMP features')) pp_lmp_features(complete_evt.lmp_features) print() if not True if (complete_evt.lmp_features[7] >> 7) & 0x01 else False: sys.exit(1) print(blue('Extended LMP features')) complete_evt = hci.read_remote_extended_features( event_params['Connection_Handle'], 0x00) if complete_evt.status != ControllerErrorCodes.SUCCESS: logger.error('Failed to read remote extented features') else: pp_ext_lmp_features(complete_evt.ext_lmp_features, 0) for i in range(1, complete_evt.max_page_num + 1): complete_evt = hci.read_remote_extended_features( event_params['Connection_Handle'], i) if complete_evt.status != ControllerErrorCodes.SUCCESS: logger.error( 'Failed to read remote extented features, page {}'. format(i)) else: pp_ext_lmp_features(complete_evt.ext_lmp_features, i)
def pp_extended_inquiry_result(self, params): '''Parse and print HCI_Extended_Inquiry_Result''' num_rsp = params[0] if num_rsp != 1: logger.info('Num_Responses in HCI_Extended_Inquiry_Result is %d.' % num_rsp) logger.debug('HCI_Extended_Inquiry_Result: {}'.format(params)) return bd_addr, page_scan_repetition_mode, reserved, cod, \ clk_offset, rssi, ext_inq_rsp = struct.unpack( '<6sBB3sHb240s', params[1:]) bd_addr = ':'.join(['%02X' % b for b in bd_addr[::-1]]) if bd_addr in self.scanned_dev: return print('Addr:', blue(bd_addr)) # print('name:', blue(name.decode())) print('Page scan repetition mode: ', end='') pp_page_scan_repetition_mode(page_scan_repetition_mode) print('Reserved: 0x%02x' % reserved) cod = int.from_bytes(cod, byteorder='little') print('CoD: 0x%06X' % cod) pp_cod(cod) print('Clock offset: 0x%04X' % clk_offset) print('RSSI: %d' % rssi) pp_ext_inquiry_rsp(ext_inq_rsp) print('\n') self.scanned_dev.append(bd_addr)
def pp_inquiry_result_with_rssi(self, params): '''Parse and print HCI_Inquiry_Result_with_RSSI.''' num_rsp = params[0] if num_rsp != 1: print( INFO, 'Num_Responses in HCI_Inquiry_Result_with_RSSI is %d.' % num_rsp) print(DEBUG, 'HCI_Inquiry_Result_with_RSSI:', params) return bd_addr, page_scan_repetition_mode, reserved, cod, clk_offset, rssi = \ struct.unpack('<6sBB3sHb', params[1:]) bd_addr = ':'.join(['%02X' % b for b in bd_addr[::-1]]) if bd_addr in self.scanned_dev: return print('Addr:', blue(bd_addr)) # print('name:', blue(name.decode())) print('Page scan repetition mode: ', end='') pp_page_scan_repetition_mode(page_scan_repetition_mode) print('Reserved: 0x%02x' % reserved) cod = int.from_bytes(cod, byteorder='little') print('CoD: 0x%06X' % cod) pp_cod(cod) print('Clock offset: 0x%04X' % clk_offset) print('RSSI: %d' % rssi) print('\n') self.scanned_dev.append(bd_addr)
def pp_inquiry_result(self, params): '''Parse and print HCI_Inquiry_Result.''' num_rsp = params[0] if num_rsp != 1: logger.info('Num_Responses in HCI_Inquiry_Result is %d.' % num_rsp) logger.debug('HCI_Inquiry_Result: {}'.format(params)) return bd_addr, page_scan_repetition_mode, reserved, cod, clk_offset = \ struct.unpack('<6sBB3sH', params[1:]) bd_addr = ':'.join(['%02X' % b for b in bd_addr[::-1]]) if bd_addr in self.scanned_dev: return print('Addr:', blue(bd_addr)) print('Page scan repetition mode: ', end='') pp_page_scan_repetition_mode(page_scan_repetition_mode) print('Reserved: 0x%02x' % reserved) cod = int.from_bytes(cod, byteorder='little') print('CoD: 0x%06X' % cod) pp_cod(cod) print('Clock offset: 0x%04X' % clk_offset) # HCI(self.iface).read_remote_name_req() print('\n') self.scanned_dev.append(bd_addr)
def inquiry(self, lap=0x9e8b33, inquiry_len=0x08, num_rsp=0x00): print(INFO, "BR scanning on " + blue("hci%d"%self.devid) + \ " with timeout " + blue("%.2f sec\n"%(inquiry_len*1.28))+'\n') self.scanned_dev = [] cmd_params = lap.to_bytes(3, 'little') + \ inquiry_len.to_bytes(1, 'little') + num_rsp.to_bytes(1, 'little') # If no filter is set, we can't receive any inquiry result. flt = hci_filter_new() hci_filter_set_ptype(flt, HCI_EVENT_PKT) hci_filter_all_events(flt) dd = hci_open_dev(self.devid) dd.setsockopt(SOL_HCI, HCI_FILTER, flt) hci_send_cmd(dd, OGF_LINK_CTL, OCF_INQUIRY, cmd_params) try: while True: data = dd.recv(300) if len(data) >= 4: event_code = data[1] if event_code == EVT_CMD_STATUS: # print(DEBUG, 'HCI_Command_Status') pass elif event_code == EVT_INQUIRY_RESULT: print(DEBUG, 'HCI_Inquiry_Result') self.pp_inquiry_result(data[3:]) elif event_code == EVT_INQUIRY_RESULT_WITH_RSSI: # print(DEBUG, 'HCI_Inquiry_Result_with_RSSI') self.pp_inquiry_result_with_rssi(data[3:]) elif event_code == EVT_EXTENDED_INQUIRY_RESULT: # print(DEBUG, 'HCI_Extended_Inquiry_Result') self.pp_extended_inquiry_result(data[3:]) elif event_code == EVT_INQUIRY_COMPLETE: # print(DEBUG, 'HCI_Inquiry_Complete') print(INFO, 'Inquiry completed') break else: print(DEBUG, "Unknow:", data) except KeyboardInterrupt as e: print(INFO, "BR/EDR devices scan canceled\n") HCI(self.iface).inquiry_cancel() hci_close_dev(dd.fileno())
def pp_cod(cod: int): '''Print and parse Class of Device.''' #print(DEBUG, 'br_scan.py pp_cod()') if cod > 0xFFFFFF: print(WARNING, "CoD's Format Type is not format #1") return elif cod & 0x000003 != 0: print(WARNING, "CoD's Format Type is not format #1") return print(' Service Class: %s' % bin(cod >> 13)) information = lambda b: (b >> 23) & 1 telephony = lambda b: (b >> 22) & 1 audio = lambda b: (b >> 21) & 1 object_transfer = lambda b: (b >> 20) & 1 capturing = lambda b: (b >> 19) & 1 rendering = lambda b: (b >> 18) & 1 networking = lambda b: (b >> 17) & 1 positioning = lambda b: (b >> 16) & 1 limited_discoverable_mode = lambda b: (b >> 13) & 1 # Parse Service Class Field if information(cod): print(' ' * 8 + 'Information (WEB-server, WAP-server, ...)') if telephony(cod): print(' ' * 8 + 'Telephony (Cordless telephony, Modem, Headset service, ...)') if audio(cod): print(' ' * 8 + 'Audio (Cordless telephony, Modem, Headset service, ...)') if object_transfer(cod): print(' ' * 8 + 'Object Transfer (v-Inbox, v-Folder, ...)') if capturing(cod): print(' ' * 8 + 'Capturing (Scanner, Microphone, ...)') if rendering(cod): print(' ' * 8 + 'Rendering (Printing, Speaker, ...)') if networking(cod): print(' ' * 8 + 'Networking (LAN, Ad hoc, ...)') if positioning(cod): print(' ' * 8 + 'Positioning (Location identification)') if limited_discoverable_mode(cod): print(' ' * 8 + 'Limited Discoverable Mode') # Parse Major Device Class major_dev_cls = (cod >> 8) & 0x001F print(' Major Device Class: %s,' % bin(major_dev_cls), blue(major_dev_clses[major_dev_cls])) # Parse Minor Device class pp_minor_dev_cls((cod >> 8) & 0x0000, major_dev_cls)
def bdaddr_to_company_name(addr: str): company_id = addr.replace(':', '-').upper()[0:8] logger.debug("bdaddr_to_company_name(), addr: " + addr) logger.debug(company_id) try: return blue(oui_company_names[company_id]) except KeyError: return red('Unknown')
def pp_cod(cod: int): '''Print and parse the Class of Device.''' logger.debug('Entered br_scan.py, pp_cod()') if cod > 0xFFFFFF or cod & 0x000003 != 0: logger.warning('CoD\'s Format Type is not format #1') return print(INDENT + 'Service Class: %s' % bin(cod >> 13)) information = lambda b: (b >> 23) & 1 telephony = lambda b: (b >> 22) & 1 audio = lambda b: (b >> 21) & 1 object_transfer = lambda b: (b >> 20) & 1 capturing = lambda b: (b >> 19) & 1 rendering = lambda b: (b >> 18) & 1 networking = lambda b: (b >> 17) & 1 positioning = lambda b: (b >> 16) & 1 limited_discoverable_mode = lambda b: (b >> 13) & 1 # Parse Service Class field if information(cod): print(INDENT * 2 + 'Information') # (WEB-server, WAP-server, ...) if telephony(cod): print(INDENT * 2 + 'Telephony') # (Cordless telephony, Modem, Headset service, ...) if audio(cod): print(INDENT * 2 + 'Audio') # (Cordless telephony, Modem, Headset service, ...) if object_transfer(cod): print(INDENT * 2 + 'Object Transfer') # (v-Inbox, v-Folder, ...) if capturing(cod): print(INDENT * 2 + 'Capturing') # (Scanner, Microphone, ...) if rendering(cod): print(INDENT * 2 + 'Rendering') # (Printing, Speaker, ...) if networking(cod): print(INDENT * 2 + 'Networking') # (LAN, Ad hoc, ...) if positioning(cod): print(INDENT * 2 + 'Positioning') # (Location identification) if limited_discoverable_mode(cod): print(INDENT * 2 + 'Limited Discoverable Mode') # Parse Major Device Class major_dev_cls = (cod >> 8) & 0x001F print(INDENT + 'Major Device Class: %s,' % bin(major_dev_cls), blue(major_dev_clses[major_dev_cls])) # Parse Minor Device class pp_minor_dev_cls((cod >> 8) & 0x0000, major_dev_cls)
def find_rfkill_devid(dev='hci0') -> int: exitcode, output = subprocess.getstatusoutput('rfkill -rno ID,DEVICE') for line in output.splitlines(): id_dev = line.split() if len(id_dev) == 2 and id_dev[1] == dev: return int(id_dev[0]) else: continue raise RuntimeError("Can't find the ID of %s in rfkill" % blue(dev))
def pp_smp_pkt(pkt:bytes): logger.debug("pp_smp_pkt(), pkt: {}".format(pkt)) code = pkt[0] if code == btsmp.CmdCode.PAIRING_RESPONSE: print(blue("Pairing Response")) iocap, oob, auth_req, max_enc_key_size, init_key_distr, rsp_key_distr = struct.unpack('BBBBBB', pkt[1:]) print(" IO Capability: 0x%02x - %s" % (iocap, green(IOCapability[iocap].hname))) print(" OOB data flag: 0x%02x - %s" % (oob, OOBDataFlag[oob].hname)) print(" AuthReq: 0x%02x" % auth_req) bonding_flag = (auth_req & BONDING_FLAGS_MSK) >> BONDING_FLAGS_POS mitm = (auth_req & MITM_MSK) >> MITM_POS sc = (auth_req & SC_MSK) >> SC_POS keypress = (auth_req & KEYPRESS_MSK) >> KEYPRESS_POS ct2 = (auth_req & CT2_MSK) >> CT2_POS rfu = (auth_req & AUTHREQ_RFU_MSK) >> AUTHREQ_RFU_POS print(" Bonding Flag: 0b{:02b} - {}".format(bonding_flag, BondingFlag[bonding_flag].hname)) print(" MitM: {}".format(green("True") if mitm else red("False"))) print(" Secure Connection: {}".format(green("True") if sc else red("False"))) print(" Keypress: {}".format(green("True") if keypress else red("False"))) print(" CT2: {}".format(green("True") if ct2 else red("False"))) print(" RFU: 0b{:02b}".format(rfu)) print(" Maximum Encryption Key Size: %d" % max_enc_key_size) print(" Initiator Key Distribution: 0x%02x" % init_key_distr) enckey = (init_key_distr & ENCKEY_MSK) >> ENCKEY_POS idkey = (init_key_distr & IDKEY_MSK) >> IDKEY_POS signkey = (init_key_distr & SIGNKEY_MSK) >> SIGNKEY_POS linkkey = (init_key_distr & LINKKEY_MSK) >> LINKKEY_POS print(" EncKey: {}".format(green("True") if enckey else red("False"))) print(" IdKey: {}".format(green("True") if idkey else red("False"))) print(" SignKey: {}".format(green("True") if signkey else red("False"))) print(" LinkKey: {}".format(green("True") if linkkey else red("False"))) print(" RFU: 0b{:04b}".format((init_key_distr & INIT_RESP_KEY_DIST_RFU_MSK) >> INIT_RESP_KEY_DIST_RFU_POS)) print(" Responder Key Distribution: 0x%02x" % rsp_key_distr) enckey = (rsp_key_distr & ENCKEY_MSK) >> ENCKEY_POS idkey = (rsp_key_distr & IDKEY_MSK) >> IDKEY_POS signkey = (rsp_key_distr & SIGNKEY_MSK) >> SIGNKEY_POS linkkey = (rsp_key_distr & LINKKEY_MSK) >> LINKKEY_POS print(" EncKey: {}".format(green("True") if enckey else red("False"))) print(" IdKey: {}".format(green("True") if idkey else red("False"))) print(" SignKey: {}".format(green("True") if signkey else red("False"))) print(" LinkKey: {}".format(green("True") if linkkey else red("False"))) print(" RFU: 0b{:04b}".format((rsp_key_distr & INIT_RESP_KEY_DIST_RFU_MSK) >> INIT_RESP_KEY_DIST_RFU_POS)) elif code == btsmp.CmdCode.PAIRING_FAILED: reason = pkt[1] print(red("Pairing Failed")) print(" Reason: 0x%02x (%s)"%(reason, PairingFailedReason[reason].hname)) print(" %s"%PairingFailedReason[reason].desc) else: logger.warning("unknown SMP packet: {}".format(pkt))
def pp_sdptool_output(cls, output:str): '''Split the string output by sdptool into individual servcie records and processes them separately.''' # print(DEBUG, 'parse_sdptool_output') pattern = r'Failed to connect to SDP server on[\da-zA-Z :]*' pattern = re.compile(pattern) result = pattern.findall(output) for i in result: output = output.replace(i, '') record_xmls = output.split('<?xml version="1.0" encoding="UTF-8" ?>\n\n')[1:] print('Number of service records:', len(record_xmls), '\n\n') for record_xml in record_xmls: print(blue('Service Record')) try: sr = ServiceRecord(record_xml) sr.pp() except ElementTree.ParseError as e: print(record_xml) print('\n')
def scan(self, bdaddr, addr_type, include_descriptor: bool): """ bdaddr - Remote BD_ADDR """ def run_mainloop(): mainloop.run() mainloop_thread = threading.Thread(target=run_mainloop, args=[]) mainloop_thread.start() try: try: target = Peripheral(bdaddr, iface=self.devid, addrType=addr_type) except BTLEDisconnectError as e: logger.error("BTLEDisconnectError") print(ERROR_INDENT, e, sep='') return services = target.getServices() print("Number of services: %s\n\n" % len(services)) # Show service for service in services: logger.debug('Start handle: {}'.format(service.hndStart)) logger.debug('End handle: {}'.format(service.hndEnd)) try: characteristics = [] characteristics = service.getCharacteristics() except BTLEException as e: logger.warning("BTLEException") print(WARNING_INDENT, e, sep='') # continue print( blue('Service'), '(0x%04x - 0x%04x, %s characteristics)' % (service.hndStart, service.hndEnd, len(characteristics))) print( indent + 'Handle: 0x%04x' % service.hndStart ) # ", "\"attr handle\" by using gatttool -b <BD_ADDR> --primary print(indent + 'Type: (May be primary service 0x2800)') print(indent + 'Value (Service UUID): ', blue(str(service.uuid).replace(sig_uuid_suffix, '')), end=' ') try: print( '(' + services_spec['0x' + ("%s" % service.uuid)[4:8].upper()]['Name'] + ')', '\x1B[0m') except KeyError: print('(' + red('unknown') + ')', '\x1B[0m') print( indent + 'Permission: Read Only, No Authentication, No Authorization\n' ) # Show characteristic for characteristic in characteristics: descriptors = [] # 对每个 characteristic 都获取 descriptor 会很耗时 # 有些设备会因此断开连接。于是这里提供了一个是否获取 descriptor 的选项 if include_descriptor: descriptors = characteristic.getDescriptors() try: print(indent + yellow('Characteristic'), '(%s descriptors)' % len(descriptors)) #print('-'*8) print(indent * 2 + 'Handle: %#06x' % (characteristic.getHandle() - 1)) print(indent * 2 + 'Type: 0x2803 (Characteristic)') print(indent * 2 + 'Value:') print(indent * 3 + 'Characteristic properties:', green(characteristic.propertiesToString())) print(indent * 3 + 'Characteristic value handle: %#06x' % characteristic.getHandle()) print( indent * 3 + 'Characteristic UUID: ', green( str(characteristic.uuid).replace( sig_uuid_suffix, '')), end=' ' ) # This UUID is also the type field of characteristic value declaration attribute. try: print('(' + characteristics_spec['0x' + ( "%s" % characteristic.uuid)[4:8].upper()]['Name'] + ')') except KeyError: print('(' + red('unknown') + ')') print( indent * 3 + 'Permission: Read Only, No Authentication, No Authorization' ) if characteristic.supportsRead(): print(indent + yellow('Characteristic value')) print(indent * 2 + 'Handle:', green('%#06x' % characteristic.getHandle())) print( indent * 2 + 'Type:', str(characteristic.uuid).replace( sig_uuid_suffix, '')) print(indent * 2 + 'Value:', green(str(characteristic.read()))) print( indent * 2 + 'Permission: Higher layer profile or implementation-specific' ) except BTLEException as e: print(' ' + str(e)) # Show descriptor for descriptor in descriptors: try: print(indent + yellow('Descriptor')) print(indent * 2 + 'Handle:', green('%#06x' % descriptor.handle)) print(indent * 2 + 'Type:', str(descriptor.uuid).replace( sig_uuid_suffix, ''), end=' ') try: print('(' + descriptors_spec['0x' + ( "%s" % descriptor.uuid)[4:8].upper()]['Name'] + ')') except KeyError: print('(Unknown descriptor)') print(indent * 2 + 'Value:', green(str(descriptor.read()))) print(indent * 2 + 'Permissions:') except BTLEException as e: print(indent * 2 + str(e)) print() print() # Set remote device untursted output = subprocess.check_output(' '.join( ['bluetoothctl', 'untrust', bdaddr]), stderr=STDOUT, timeout=60, shell=True) logger.info(output.decode()) # output = subprocess.check_output( # ' '.join(['sudo', 'systemctl', 'stop', 'bluetooth.service']), # stderr=STDOUT, timeout=60, shell=True) # output = subprocess.check_output( # ' '.join(['sudo', 'rm', '-rf', '/var/lib/bluetooth/' + \ # self.hci_bdaddr + '/' + bdaddr.upper()]), # stderr=STDOUT, timeout=60, shell=True) # output = subprocess.check_output( # ' '.join(['sudo', 'systemctl', 'start', 'bluetooth.service']), # stderr=STDOUT, timeout=60, shell=True) finally: if self.agent_registered: self.agent_mgr_1_iface.UnregisterAgent( ObjectPath(self.bluescan_agent.path)) logger.info('Unregistered Agent object') mainloop.quit()
def print(self): if self.addr is None or self.addr_type is None: return print("Number of services: {}".format(len(self.services))) print() print() # Two empty lines before Service Group # Prints each service group for service in self.services: uuid_str_for_show = self.uuid2str_for_show(service.declar.value) try: service_name = green(ServiceUuids[service.declar.value].name) except KeyError: service_name = red("Unknown") print( blue("Service"), "(0x{:04x} - 0x{:04x}, {} characteristics)".format( service.start_handle, service.end_handle, len(service.get_characts()))) print(INDENT + blue("Declaration")) print(INDENT + "Handle: 0x{:04x}".format(service.start_handle)) print(INDENT + "Type: {:04X} ({})".format( service.declar.type.int16, service.declar.type.name)) print(INDENT + "Value: {} ({})".format(green(uuid_str_for_show), service_name)) print(INDENT + "Permissions:", service.declar.permissions_desc) print() # An empty line before Characteristic Group # Prints each Gharacteristic group for charact in service.characts: uuid_str_for_show = self.uuid2str_for_show( charact.declar.value.uuid) # if type(uuid) is int: # uuid = "0x{:04X}".format(uuid) # value_declar_uuid = charact.value_declar.type # if type(value_declar_uuid) is int: # value_declar_uuid = "0x{:04X}".format(value_declar_uuid) # if charact.declar.value.uuid != charact.value_declar.type: # pass # logger.warning("charact.declar.value['UUID'] != charact.value_declar.type") try: charact_name = green( GattAttrTypes[charact.declar.value.uuid].name) # charact_name = green(charact_names[charact.declar.value.uuid]) except KeyError: charact_name = red("Unknown") print(INDENT + yellow("Characteristic"), '({} descriptors)'.format(len(charact.descriptors))) print(INDENT * 2 + yellow("Declaration")) print(INDENT * 2 + "Handle: 0x{:04x}".format(charact.declar.handle)) print(INDENT * 2 + "Type: {:04X} ({})".format( charact.declar.type.int16, charact.declar.type.name)) print(INDENT * 2 + "Value:") print(INDENT * 3 + "Properties: {}".format( green(', '.join(charact.declar.get_property_names())))) print(INDENT * 3 + "Handle: ", green("0x{:04x}".format(charact.declar.value.handle))) print(INDENT * 3 + "UUID: {} ({})".format( green(uuid_str_for_show), charact_name)) print(INDENT * 2 + "Permissions: {}\n".format( charact.declar.permissions_desc)) if charact.value_declar is not None: try: value_declar_name = GattAttrTypes[ charact.value_declar.type].name except KeyError: value_declar_name = "Unknown" type_str_for_show = self.uuid2str_for_show( charact.value_declar.type) error = charact.value_declar.get_read_error() if error != None: value_print = red(error.desc) elif charact.value_declar.value is None: value_print = red('Unknown') else: value_print = green(str(charact.value_declar.value)) print(INDENT * 2 + yellow("Value declaration")) print( INDENT * 2 + "Handle: 0x{:04x}".format(charact.value_declar.handle)) print(INDENT * 2 + "Type: {} ({})".format( type_str_for_show, value_declar_name)) print(INDENT * 2 + "Value: {}".format(value_print)) print(INDENT * 2 + "Permissions: {}\n".format( charact.value_declar.permissions_desc)) # Prints each Characteristic Descriptor for descriptor in charact.get_descriptors(): uuid = descriptor.type if type(uuid) is int: uuid = "0x{:04X}".format(uuid) error = descriptor.get_read_error() if error != None: value_print = red(error.desc) else: if descriptor.value is None: value_print = red('Unknown') else: value_print = green(str(descriptor.value)) print(INDENT * 2 + yellow("Descriptor")) print(INDENT * 2 + "Handle: {}".format( green('0x{:04x}'.format(descriptor.handle)))) print(INDENT * 2 + "Type: {} ({})".format( green("{:04X}".format(descriptor.type.int16)), yellow(descriptor.type.name))) print(INDENT * 2 + "Value: ", value_print) print(INDENT * 2 + "Permissions: {}\n".format( descriptor.permissions_desc))
def scan_devs(self, timeout=8, scan_type='active', sort='rssi'): """LE devices scanning scan_type - Indicate the type of LE scan:active, passive, adv or features. """ if scan_type == 'adv': return scanner = Scanner(self.devid).withDelegate(LEDelegate()) #print("[Debug] timeout =", timeout) # scan() 返回的 devs 是 dictionary view。 if scan_type == 'active': # Active scan 会在 LL 发送 SCAN_REQ PDU logger.warning( 'Before doing an active scan, make sure you spoof your BD_ADDR.' ) logger.info('LE active scanning on %s with timeout %d sec\n' % \ (blue('hci%d'%self.devid), timeout)) devs = scanner.scan(timeout) elif scan_type == 'passive': logger.info('LE passive scanning on %s with timeout %d sec\n' % \ (blue('hci%d'%self.devid), timeout)) devs = scanner.scan(timeout, passive=True) else: logger.error('Unknown LE scan type') return if sort == 'rssi': devs = list(devs) # 将 dictionary view 转换为 list devs.sort(key=lambda d: d.rssi) for dev in devs: print('Addr: ', blue(dev.addr.upper())) print('Addr type: ', blue(dev.addrType)) print('Connectable:', green('True') if dev.connectable else red('False')) print("RSSI: %d dB" % dev.rssi) print("General Access Profile:") for (adtype, desc, val) in dev.getScanData(): # 打印当前 remote LE dev 透露的所有 GAP 数据(AD structure)。 # # 如果 bluepy.scan() 执行的是 active scan,那么这些 GAP 数据 # 可能同时包含 AdvData 与 ScanRspData。其中 AdvData 由 remote LE # dev 主动返回,ScanRspData 由 remote BLE dev 响应 SCAN_REQ 返回。 # # 虽然 LL 分开定义了 Advertising PDUs (ADV_IND, ADV_DIRECT_IND...) # 和 Scanning PDUs (SCAN_REQ, SCAN_RSP)。但它们分别包含的 AdvData # 与 ScanRspData 到了 HCI 层都被放在了 HCI_LE_Advertising_Report # event 中。HCI_LE_Advertising_Report 的 Event_Type 标识了这些数 # 据具体来源于哪个 LL 层的 PDU。另外 ScanRspData 与 AdvData 的格式 # 完全相同,都是 GAP 协议标准定义的 AD structure。 # # 在 LL 定义的 Advertising PDUs 中 ADV_DIRECT_IND 一定不会包含 # AdvData。其余的 ADV_IND,ADV_NONCONN_IND 以及 ADV_SCAN_IND 都 # 可能包含 AdvData。 # # 另外 getScanData() 返回的 desc 还可以通过 ScanEntry.getDescription() # 单独获取;val 还可以通过 ScanEntry.getValueText() 单独获取; # adtype 表示当前一条 GAP 数据(AD structure)的类型。 print('\t' + desc + ': ', end='') if adtype == COMPLETE_16_BIT_SERVICE_CLS_UUID_LIST: print() for uuid in val.split(','): if len(uuid) == 36: # 这里拿到的是完整的 128-bit uuid,但我们需要 16-bit uuid。 print('\t\t' + blue(uuid[4:8])) else: print('\t\t' + blue(uuid)) continue elif adtype == COMPLETE_32_BIT_SERVICE_CLS_UUID_LIST: print() for uuid in val.split(','): if len(uuid) == 36: # 这里拿到的是完整的 128-bit uuid,但我们需要 32-bit uuid。 print('\t\t' + blue(uuid[0:8])) else: print('\t\t' + blue(uuid)) continue elif adtype == MANUFACTURER_SPECIFIC_DATA: val = bytes.fromhex(val) if len(val) > 2: print() print( '\t\tCompany ID:', '0x%04x' % int.from_bytes(val[0:2], 'little', signed=False)) print('\t\tData:', val[2:]) else: print(val) continue print(val) print("\n")
def main(): try: args = parse_cmdline() logger.debug("main(), args: {}".format(args)) if not args['--adv']: # 在不使用 microbit 的情况下,我们需要将选中的 hci 设备配置到一个干净的状态。 if args['-i'] == 'The default HCI device': # 当 user 没有显示指明 hci 设备情况下,我们需要自动获取一个可用的 hci # 设备。注意这个设备不一定是 hci0。因为系统中可能只有 hci1,而没有 hci0。 try: args['-i'] = HCI.get_default_hcistr() except IndexError: logger.error('No available HCI device') exit(-1) init_hci(args['-i']) scan_result = None if args['-m'] == 'br': br_scanner = BRScanner(args['-i']) if args['--lmp-feature']: br_scanner.scan_lmp_feature(args['BD_ADDR']) else: br_scanner = BRScanner(args['-i']) br_scanner.inquiry(inquiry_len=args['--inquiry-len']) elif args['-m'] == 'le': if args['--adv']: dev_paths = get_microbit_devpaths() LeScanner(microbit_devpaths=dev_paths).sniff_adv( args['--channel']) elif args['--ll-feature']: LeScanner(args['-i']).scan_ll_feature(args['BD_ADDR'], args['--addr-type'], args['--timeout']) elif args['--smp-feature']: LeScanner(args['-i']).detect_pairing_feature( args['BD_ADDR'], args['--addr-type'], args['--timeout']) else: scan_result = LeScanner(args['-i']).scan_devs( args['--timeout'], args['--scan-type'], args['--sort']) elif args['-m'] == 'sdp': SDPScanner(args['-i']).scan(args['BD_ADDR']) elif args['-m'] == 'gatt': scan_result = GattScanner(args['-i'], args['--io-capability']).scan( args['BD_ADDR'], args['--addr-type']) # elif args['-m'] == 'stack': # StackScanner(args['-i']).scan(args['BD_ADDR']) elif args['--clean']: BlueScanner(args['-i']) clean(BlueScanner(args['-i']).hci_bdaddr, args['BD_ADDR']) else: logger.error('Invalid scan mode') # Prints scan result if scan_result is not None: print() print() print( blue("----------------" + scan_result.type + " Scan Result" + "----------------")) scan_result.print() scan_result.store() # except (RuntimeError, ValueError, BluetoothError) as e: except (RuntimeError, ValueError) as e: logger.error("{}: {}".format(e.__class__.__name__, e)) traceback.print_exc() exit(1) except (BTLEException) as e: logger.error( str(e) + ("\nNo BLE adapter or missing sudo?" if 'le on' in str(e) else "")) except KeyboardInterrupt: if args != None and args['-i'] != None: output = subprocess.check_output(' '.join( ['hciconfig', args['-i'], 'reset']), stderr=STDOUT, timeout=60, shell=True) print() logger.info("Canceled\n")
def scan_devs(self, timeout=8, scan_type='active', sort='rssi') -> LeDevicesScanResult: """Perform LE Devices scanning and return scan reuslt as LeDevicesScanResult scan_type - 'active' or 'passive' """ if scan_type == 'adv': return scanner = Scanner(self.devid).withDelegate(LEDelegate()) #print("[Debug] timeout =", timeout) spinner = Halo(text="Scanning", spinner={'interval': 200, 'frames': ['', '.', '.'*2, '.'*3]}, placement='right') # scan() 返回的 devs 是 dictionary view。 if scan_type == 'active': # Active scan 会在 LL 发送 SCAN_REQ PDU logger.warning('Before doing an active scan, make sure you spoof your BD_ADDR.') logger.info('LE active scanning on %s with timeout %d sec\n' % \ (blue('hci%d'%self.devid), timeout)) spinner.start() devs = scanner.scan(timeout) spinner.stop() elif scan_type == 'passive': logger.info('LE passive scanning on %s with timeout %d sec\n' % \ (blue('hci%d'%self.devid), timeout)) spinner.start() devs = scanner.scan(timeout, passive=True) spinner.stop() else: logger.error('Unknown LE scan type') return if sort == 'rssi': devs = list(devs) # 将 dictionary view 转换为 list devs.sort(key=lambda d:d.rssi) for dev in devs: dev_info = LeDeviceInfo(dev.addr.upper(), dev.addrType, dev.connectable, dev.rssi) self.devs_scan_result.add_device_info(dev_info) # print('Addr: ', blue(dev.addr.upper())) # print('Addr type: ', blue(dev.addrType)) # print('Connectable:', # green('True') if dev.connectable else red('False')) # print("RSSI: %d dB" % dev.rssi) # print("General Access Profile:") for (adtype, desc, val) in dev.getScanData(): ad_struct = AdStruct(adtype, val) dev_info.add_ad_structs(ad_struct) # 打印当前 remote LE dev 透露的所有 GAP 数据(AD structure)。 # # 如果 bluepy.scan() 执行的是 active scan,那么这些 GAP 数据 # 可能同时包含 AdvData 与 ScanRspData。其中 AdvData 由 remote LE # dev 主动返回,ScanRspData 由 remote BLE dev 响应 SCAN_REQ 返回。 # # 虽然 LL 分开定义了 Advertising PDUs (ADV_IND, ADV_DIRECT_IND...) # 和 Scanning PDUs (SCAN_REQ, SCAN_RSP)。但它们分别包含的 AdvData # 与 ScanRspData 到了 HCI 层都被放在了 HCI_LE_Advertising_Report # event 中。HCI_LE_Advertising_Report 的 Event_Type 标识了这些数 # 据具体来源于哪个 LL 层的 PDU。另外 ScanRspData 与 AdvData 的格式 # 完全相同,都是 GAP 协议标准定义的 AD structure。 # # 在 LL 定义的 Advertising PDUs 中 ADV_DIRECT_IND 一定不会包含 # AdvData。其余的 ADV_IND,ADV_NONCONN_IND 以及 ADV_SCAN_IND 都 # 可能包含 AdvData。 # # 另外 getScanData() 返回的 desc 还可以通过 ScanEntry.getDescription() # 单独获取;val 还可以通过 ScanEntry.getValueText() 单独获取; # adtype 表示当前一条 GAP 数据(AD structure)的类型。 return self.devs_scan_result
def inquiry(self, lap=0x9e8b33, inquiry_len=0x08, num_rsp=0x00): logger.info('BR scanning on ' + blue("hci%d"%self.devid) + \ ' with timeout ' + blue("%.2f sec\n"%(inquiry_len*1.28))+'\n') self.scanned_dev = [] self.remote_name_req_flag = True cmd_params = lap.to_bytes(3, 'little') + \ inquiry_len.to_bytes(1, 'little') + num_rsp.to_bytes(1, 'little') # If no filter is set, we can't receive any inquiry result. flt = hci_filter_new() hci_filter_set_ptype(flt, HCI_EVENT_PKT) hci_filter_all_events(flt) dd = hci_open_dev(self.devid) dd.setsockopt(SOL_HCI, HCI_FILTER, flt) hci_send_cmd(dd, OGF_LINK_CTL, OCF_INQUIRY, cmd_params) try: while True: data = dd.recv(300) if len(data) >= 4: event_code = data[1] if event_code == EVT_CMD_STATUS: logger.debug('HCI_Command_Status') pass elif event_code == EVT_INQUIRY_RESULT: logger.debug('HCI_Inquiry_Result') self.pp_inquiry_result(data[3:]) elif event_code == EVT_INQUIRY_RESULT_WITH_RSSI: logger.debug('HCI_Inquiry_Result_with_RSSI') self.pp_inquiry_result_with_rssi(data[3:]) elif event_code == EVT_EXTENDED_INQUIRY_RESULT: logger.debug('HCI_Extended_Inquiry_Result') self.pp_extended_inquiry_result(data[3:]) elif event_code == EVT_INQUIRY_COMPLETE: logger.debug('HCI_Inquiry_Complete') logger.info('Inquiry completed\n') if self.remote_name_req_flag and len( self.scanned_dev) != 0: logger.info( 'Requesting the name of the scanned devices...' ) for bd_addr in self.scanned_dev: try: name = HCI(self.iface).remote_name_request( { 'BD_ADDR': bytes.fromhex( bd_addr.replace(':', '')), 'Page_Scan_Repetition_Mode': 0x01, 'Reserved': 0x00, 'Clock_Offset': 0x0000 })['Remote_Name'].decode().strip() except Exception as e: print(e) name = '' print(bd_addr + ':', blue(name)) break else: logger.debug('Unknow: {}'.format(data)) except KeyboardInterrupt as e: logger.info('BR/EDR devices scan canceled\n') HCI(self.iface).inquiry_cancel() hci_close_dev(dd.fileno())
def print(self): for dev_info in self.devices_info: print('Addr: ', blue(dev_info.addr), "("+bdaddr_to_company_name(dev_info.addr)+")" if dev_info.addr_type == 'public' else "") print('Addr type: ', blue(dev_info.addr_type)) print('Connectable:', green('True') if dev_info.connectable else red('False')) print("RSSI: {} dBm".format(dev_info.rssi)) print("General Access Profile:") # TODO: Unify the gap type name parsings of BR and LE for ad in dev_info.ad_structs: try: type_names = gap_type_names[ad.type] except KeyError: type_names = "0x{:02X} ".format(ad.type)+"("+ red("Unknown")+")" print(INDENT+"{}: ".format(type_names), end='') # print(INDENT+"0x{:02X} ({}): ".format(ad.type, type_names), end='') # Parses AD structure based on https://www.bluetooth.com/specifications/specs/ # -> Core Specification Supplement if ad.type == COMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS or \ ad.type == INCOMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS: print() for uuid in ad.value.split(','): if len(uuid) == 36: # 这里拿到的是完整的 128-bit uuid,但我们需要 16-bit uuid。 print(INDENT*2 + blue("0x"+uuid[4:8].upper())) else: print(INDENT*2 + blue(uuid)) elif ad.type == COMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS or \ ad.type == INCOMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS: print() for uuid in ad.value.split(','): if len(uuid) == 36: # 这里拿到的是完整的 128-bit uuid,但我们需要 32-bit uuid。 print(INDENT*2 + blue("0x"+uuid[0:8].upper())) else: print(INDENT*2 + blue(uuid)) elif ad.type == COMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS or \ ad.type == INCOMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS: print() for uuid in ad.value.split(','): print(INDENT*2 + blue(uuid).upper()) elif ad.type == SERVICE_DATA_16_BIT_UUID: print() print(INDENT*2 + "UUID: 0x{}".format(ad.value[0:2*2].upper())) print(INDENT*2 + "Data:", ad.value[2*2:]) elif ad.type == SERVICE_DATA_32_BIT_UUID: print() print(INDENT*2 + "UUID: {}".format(ad.value[0:4*2].upper())) print(INDENT*2 + "Data:", ad.value[4*2:]) elif ad.type == SERVICE_DATA_128_BIT_UUID: print() print(INDENT*2 + "UUID: {}".format(ad.value[0:16*2].upper())) print(INDENT*2 + "Data: ", ad.value[16*2:]) elif ad.type == FLAGS: print() try: value = bytes.fromhex(ad.value) print(INDENT*2 + "LE Limited Discoverable Mode\n" if value[0] & 0x01 else "", end="") print(INDENT*2 + "LE General Discoverable Mode\n" if value[0] & 0x02 else "", end="") print(INDENT*2 + "BR/EDR Not Supported\n" if value[0] & 0x04 else "", end="") # Bit 37 of LMP Feature Mask Definitions (Page 0) print(INDENT*2 + "Simultaneous LE + BR/EDR to Same Device Capable (Controller)\n" if value[0] & 0x08 else "", end="") # Bit 49 of LMP Feature Mask Definitions (Page 0) print(INDENT*2 + "Simultaneous LE + BR/EDR to Same Device Capable (Host)\n" if value[0] & 0x10 else "", end="") # Bit 66 of LMP Feature Mask Definitions (Page 1) except (ValueError, IndexError) as e: logger.debug("LeDevicesScanResult.print(), parse ad.type == FLAGS") print(ad.value, "("+red("Raw")+")") elif ad.type == MANUFACTURER_SPECIFIC_DATA: value = bytes.fromhex(ad.value) company_id = int.from_bytes(value[0:2], 'little', signed=False) try: company_name = blue(company_names[company_id]) except KeyError: company_name = red("Unknown") if len(value) >= 2: print() print(INDENT*2+"Company ID:", '0x{:04X} ({})'.format(company_id,company_name)) try: print(INDENT*2+'Data: ', ''.join(["{:02X}".format(b) for b in value[2:]])) except IndexError: print(INDENT*2+'Data:', None) else: print(value) elif ad.type == TX_POWER_LEVEL: value = int.from_bytes(bytes.fromhex(ad.value), 'little', signed=True) print(value, "dBm", "(pathloss {} dBm)".format(value - dev_info.rssi)) else: print(ad.value) print() print() # Two empty lines before next LE device information
def pp_ext_inquiry_rsp(ext_inq_rsp): '''Parse and print Extended Inquiry Response (240 octets) https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile/ ''' print('Extended inquiry response: ', end='') if ext_inq_rsp[0] == 0: print(red('None')) return print() while ext_inq_rsp[0] != 0: length = ext_inq_rsp[0] data = ext_inq_rsp[1:1 + length] data_type = data[0] ext_inq_rsp = ext_inq_rsp[1 + length:] print('\t', end='') if data_type == COMPLETE_16_BIT_SERVICE_CLS_UUID_LIST: print(gap_type_name_pairs[data_type]) if length - 1 >= 2: eir_data = data[1:] if len(eir_data) % 2 != 0: print('\t\t' + blue('Invalid EIR data length: %d' % len(eir_data))) continue for i in range(0, len(eir_data), 2): uuid = int.from_bytes(eir_data[i:i + 2], byteorder='little') print('\t\t0x%04x ' % uuid, end='') try: print(blue(service_cls_profile_ids[uuid]['Name'])) except KeyError as e: print(red('unknown')) else: print('\t\t' + red('None')) elif data_type == COMPLETE_32_BIT_SERVICE_CLS_UUID_LIST: print(gap_type_name_pairs[data_type]) if length - 1 >= 4: eir_data = data[1:] if len(eir_data) % 4 != 0: logger.info('\t\tInvalid EIR data length: {} {}'.format( len(eir_data), eir_data)) continue for i in range(0, len(eir_data), 4): uuid = int.from_bytes(eir_data[i:i + 4], byteorder='little') print('\t\t0x%08x ' % uuid) else: print('\t\t' + red('None')) elif data_type == COMPLETE_128_BIT_SERVICE_CLS_UUID_LIST: print(gap_type_name_pairs[data_type]) if length - 1 >= 16: eir_data = data[1:] if len(eir_data) % 16 != 0: logger.info('\t\tInvalid EIR data length: {} {}'.format( len(eir_data), eir_data)) continue for i in range(0, len(eir_data), 16): uuid = int.from_bytes(eir_data[i:i + 16], byteorder='little') uuid_str = '%032X' % uuid print('\t\t', end='') print( blue('-'.join([ uuid_str[:8], uuid_str[8:12], uuid_str[12:16], uuid_str[16:20], uuid_str[20:32] ]))) else: print('\t\t' + red('None')) elif data_type == SHORTENED_LOCAL_NAME or \ data_type == COMPLETE_LOCAL_NAME: print(gap_type_name_pairs[data_type] + ':', blue(data[1:].decode())) elif data_type == TX_POWER_LEVEL: print( gap_type_name_pairs[data_type] + ':', blue( str(int.from_bytes(data[1:], byteorder='little')) + ' dBm')) else: try: print(gap_type_name_pairs[data_type]) except KeyError as e: print(red('Unknown, 0x%02x' % data_type)) print('\t\t', data[1:], sep='')
def pp_adv_phych_pdu(pdu: bytes, ch: int) -> list: '''Parse and print advertising physical channel PDU ref BLUETOOTH CORE SPECIFICATION Version 5.2 | Vol 6, Part B page 2871, 2.3 ADVERTISING PHYSICAL CHANNEL PDU Advertising physical channel PDU +------------------+ | Header | Payload | |--------|---------| | 16 b | 1-255 B | +------------------+ Header +-------------------------------------------------+ | PDU Type | RFU | ChSel | TxAdd | RxAdd | Length | |----------|-----|-------|-------|-------|--------| | 4 b | 1 b | 1 b | 1 b | 1 b | 8 b | +-------------------------------------------------+ ''' header = pdu[:2] payload = pdu[2:] pdu_type = (header[0] & PDU_TYPE_MSK) >> PDU_TYPE_POS rfu = (header[0] & RFU_MSK) >> RFU_POS ch_sel = (header[0] & CH_SEL_MSK) >> CH_SEL_POS tx_add = (header[0] & TX_ADD_MSK) >> TX_ADD_POS rx_add = (header[0] & RX_ADD_MSK) >> RX_ADD_POS addrs = [] print("[{}] ".format(ch), end='') if pdu_type == ADV_IND: adv_a = payload[:6][::-1] addrs = [{ 'BD_ADDR': adv_a, 'type': 'public' if tx_add == 0b0 else 'random' }] print("[{}]".format(blue('ADV_IND'))) print("{} AdvA: {}".format('public' if tx_add == 0b0 else 'random', ':'.join('%02X' % b for b in adv_a))) # print("AdvData:", payload[6:]) elif pdu_type == ADV_DIRECT_IND: adv_a = payload[:6][::-1] target_a = payload[6:][::-1] addrs = [ { 'BD_ADDR': adv_a, 'type': 'public' if tx_add == 0b0 else 'random' }, { 'BD_ADDR': target_a, 'type': 'public' if rx_add == 0b0 else 'random' }, ] print("[{}]".format(blue('ADV_DIRECT_IND'))) print("{} AdvA: {}".format('public' if tx_add == 0b0 else 'random', ':'.join('%02X' % b for b in adv_a))) print("{} TargetA: {}".format('public' if rx_add == 0b0 else 'random', ':'.join('%02X' % b for b in target_a))) elif pdu_type == ADV_NONCONN_IND: adv_a = payload[:6][::-1] addrs = [{ 'BD_ADDR': adv_a, 'type': 'public' if tx_add == 0b0 else 'random' }] print("[{}]".format(red('ADV_NONCONN_IND'))) print("{} AdvA: {}".format('public' if tx_add == 0b0 else 'random', ':'.join('%02X' % b for b in adv_a))) # print("AdvData:", payload[6:]) elif pdu_type == ADV_SCAN_IND: adv_a = payload[:6][::-1] addrs = [{ 'BD_ADDR': adv_a, 'type': 'public' if tx_add == 0b0 else 'random' }] print("[{}]".format(blue('ADV_SCAN_IND'))) print("{} AdvA: {}".format('public' if tx_add == 0b0 else 'random', ':'.join('%02X' % b for b in adv_a))) # print("AdvData:", payload[6:]) elif pdu_type == ADV_EXT_IND: print("[{}]".format(yellow('ADV_EXT_IND'))) print("raw: {}".format(payload)) elif pdu_type == SCAN_REQ: scan_a = payload[:6][::-1] adv_a = payload[6:][::-1] addrs = [ { 'BD_ADDR': scan_a, 'type': 'public' if tx_add == 0b0 else 'random' }, { 'BD_ADDR': adv_a, 'type': 'public' if rx_add == 0b0 else 'random' }, ] print("[{}]".format(blue('SCAN_REQ'))) print("{} ScanA: {}".format('public' if tx_add == 0b0 else 'random', ':'.join('%02X' % b for b in scan_a))) print("{} AdvA: {}".format('public' if rx_add == 0b0 else 'random', ':'.join('%02X' % b for b in adv_a))) elif pdu_type == SCAN_RSP: adv_a = payload[:6][::-1] addrs = [{ 'BD_ADDR': adv_a, 'type': 'public' if tx_add == 0b0 else 'random' }] print("[{}]".format(blue('SCAN_RSP'))) print("{} AdvA: {}".format('public' if tx_add == 0b0 else 'random', ':'.join('%02X' % b for b in adv_a))) # print("ScanRspData:", payload[6:]) elif pdu_type == CONNECT_IND: init_a = payload[:6] adv_a = payload[6:12] print('init_a:', ':'.join('%02x' % b for b in init_a)) print('adv_a:', ':'.join('%02x' % b for b in adv_a)) addrs = [ { 'BD_ADDR': init_a, 'type': 'public' if tx_add == 0b0 else 'random' }, { 'BD_ADDR': adv_a, 'type': 'public' if rx_add == 0b0 else 'random' }, ] print("[{}]".format(green('CONNECT_IND'))) print("{} InitA: {}".format('public' if tx_add == 0b0 else 'random', ':'.join('%02X' % b for b in init_a))) print("{} AdvA: {}".format('public' if rx_add == 0b0 else 'random', ':'.join('%02X' % b for b in adv_a))) # print("LLData:", payload[12:]) else: logger.warning("Unknown PDU type 0x%02x'%pdu_type") return addrs
def scan(self, timeout=8, scan_type='active', sort='rssi'): ''' scan_type 指定执行的 LE scan,是 active scan 还是 passive scan。 ''' scanner = Scanner(self.devid).withDelegate(LEDelegate()) #print("[Debug] timeout =", timeout) # scan() 返回的 devs 是 dictionary view。 if scan_type == 'active': # Active scan 会在 LL 发送 SCAN_REQ PDU print( WARNING, 'Before doing an active scan, make sure you spoof your BD_ADDR.' ) print( INFO, "LE active scanning on \x1B[1;34mhci%d\x1B[0m with timeout %d sec\n" % (self.devid, timeout)) devs = scanner.scan(timeout) elif scan_type == 'passive': print( "LE passive scanning on \x1B[1;34mhci%d\x1B[0m with timeout %d sec\n" % (self.deivd, timeout)) devs = scanner.scan(timeout, passive=True) else: print(ERROR, "Unknown LE scan type.") return if sort == 'rssi': devs = list(devs) # 将 dictionary view 转换为 list devs.sort(key=lambda d: d.rssi) for dev in devs: print('Addr: ', blue(dev.addr.upper())) print('Addr type: ', blue(dev.addrType)) print('Connectable:', green('True') if dev.connectable else red('False')) print("RSSI: %d dB" % dev.rssi) print("General Access Profile:") for (adtype, desc, val) in dev.getScanData(): # 打印当前 remote LE dev 透露的所有 GAP 数据(AD structure)。 # # 如果 bluepy.scan() 执行的是 active scan,那么这些 GAP 数据 # 可能同时包含 AdvData 与 ScanRspData。其中 AdvData 由 remote LE # dev 主动返回,ScanRspData 由 remote BLE dev 响应 SCAN_REQ 返回。 # # 虽然 LL 分开定义了 Advertising PDUs (ADV_IND, ADV_DIRECT_IND...) # 和 Scanning PDUs (SCAN_REQ, SCAN_RSP)。但它们分别包含的 AdvData # 与 ScanRspData 到了 HCI 层都被放在了 HCI_LE_Advertising_Report # event 中。HCI_LE_Advertising_Report 的 Event_Type 标识了这些数 # 据具体来源于哪个 LL 层的 PDU。另外 ScanRspData 与 AdvData 的格式 # 完全相同,都是 GAP 协议标准定义的 AD structure。 # # 在 LL 定义的 Advertising PDUs 中 ADV_DIRECT_IND 一定不会包含 # AdvData。其余的 ADV_IND,ADV_NONCONN_IND 以及 ADV_SCAN_IND 都 # 可能包含 AdvData。 # # 另外 getScanData() 返回的 desc 还可以通过 ScanEntry.getDescription() # 单独获取;val 还可以通过 ScanEntry.getValueText() 单独获取; # adtype 表示当前一条 GAP 数据(AD structure)的类型。 print('\t' + desc + ': ', end='') if adtype == COMPLETE_16_BIT_SERVICE_CLS_UUID_LIST: print() for uuid in val.split(','): print('\t\t' + blue(uuid)) continue print(val) print("\n")
def scan(self, bdaddr, addr_type, include_descriptor: bool): #print("[Debug] target_addr:", self.target_bdaddr) #print("[Debug] iface:", self.iface) #print("[Debug] addr_type:", self.addr_type) target = Peripheral(bdaddr, iface=self.devid, addrType=addr_type) #print("[Debug] target", target) services = target.getServices() print("Number of services: %s\n\n" % len(services)) # Show service for service in services: characteristics = service.getCharacteristics() print(blue('Service'), '(%s characteristics)' % len(characteristics)) print( '\tHandle:", "\"attr handle\" by using gatttool -b <BD_ADDR> --primary' ) print('\tType: (May be primary service 0x2800)') print('\tValue (Service UUID): ', blue(str(service.uuid)), end=' ') try: print( '(' + services_spec['0x' + ("%s" % service.uuid)[4:8].upper()]['Name'] + ')', '\x1B[0m') except KeyError: print('(' + red('unknown') + ')', '\x1B[0m') print( '\tPermission: Read Only, No Authentication, No Authorization\n' ) # Show characteristic for characteristic in characteristics: descriptors = [] # 对每个 characteristic 都获取 descriptor 会很耗时 # 有些设备会因此断开连接。于是这里提供了一个是否获取 descriptor 的选项 if include_descriptor: descriptors = characteristic.getDescriptors() try: print(yellow('\tCharacteristic'), '(%s descriptors)' % len(descriptors)) #print('-'*8) print('\t\tHandle: %#06x' % (characteristic.getHandle() - 1)) print('\t\tType: 0x2803 (tCharacteristic)') print('\t\tValue:') print('\t\t\tCharacteristic properties:', green(characteristic.propertiesToString())) print('\t\t\tCharacteristic value handle: %#06x' % characteristic.getHandle()) print( '\t\t\tCharacteristic UUID: ', green(str(characteristic.uuid)), end=' ' ) # This UUID is also the type field of characteristic value declaration attribute. try: print('(' + characteristics_spec['0x' + ( "%s" % characteristic.uuid)[4:8].upper()]['Name'] + ')') except KeyError: print('(' + red('unknown') + ')') print( '\t\tPermission: Read Only, No Authentication, No Authorization' ) if characteristic.supportsRead(): print(yellow('\tCharacteristic value')) print('\t\tHandle:', green('%#06x' % characteristic.getHandle())) print('\t\tType:', characteristic.uuid) print('\t\tValue:', green(str(characteristic.read()))) print( '\t\tPermission: Higher layer profile or implementation-specific' ) except BTLEException as e: print(' ' + str(e)) # Show descriptor for descriptor in descriptors: try: print('\tDescriptor') print('\t\tHandle:', green('%#06x' % descriptor.handle)) print('\t\tType:', descriptor.uuid, end=' ') try: print('(' + descriptors_spec['0x' + ( "%s" % descriptor.uuid)[4:8].upper()]['Name'] + ')') except KeyError: print('(Unknown descriptor)') print('\t\tValue:', descriptor.read()) print('\t\tPermissions:') except BTLEException as e: print('\t\t' + str(e)) print() print()