def receive_encrypted(pkt): global conn_rx_packet_counter raw_pkt = bytearray(raw(pkt)) aa = raw_pkt[:4] header = raw_pkt[4] # Get ble header length = raw_pkt[5] # add 4 bytes for the mic if length is 0 or length < 5: # ignore empty PDUs return pkt # Subtract packet length 4 bytes of MIC length -= 4 # Update nonce before decrypting pkt_count = bytearray(struct.pack( "<Q", conn_rx_packet_counter)[:5]) # convert only 5 bytes pkt_count[4] &= 0x7F # Clear bit 7 for slave -> master nonce = pkt_count + conn_iv aes = AES.new(conn_session_key, AES.MODE_CCM, nonce=nonce, mac_len=4) # mac = mic aes.update( chr(header & 0xE3)) # Calculate mic over header cleared of NES, SN and MD dec_pkt = aes.decrypt(raw_pkt[6:-4 - 3]) # get payload and exclude 3 bytes of crc conn_rx_packet_counter += 1 try: mic = raw_pkt[6 + length:-3] # Get mic from payload and exclude crc aes.verify(mic) return BTLE(aa + chr(header) + chr(length) + dec_pkt + b'\x00\x00\x00') except Exception as e: print(Fore.RED + "MIC Wrong: " + str(e)) return BTLE(aa + chr(header) + chr(length) + dec_pkt + b'\x00\x00\x00')
def send(self, scapy_pkt, print_tx=True): self.raw_send(raw(scapy_pkt)) if self.logs_pcap: self.packets_buffer.append( NORDIC_BLE(board=75, protocol=2, flags=0x3) / scapy_pkt) if print_tx: print(Fore.CYAN + "TX ---> " + scapy_pkt.summary()[7:])
def send_encrypted(pkt): global conn_tx_packet_counter raw_pkt = bytearray(raw(pkt)) aa = raw_pkt[:4] header = raw_pkt[4] # Get ble header length = raw_pkt[5] + 4 # add 4 bytes for the mic crc = '\x00\x00\x00' # Dummy CRC (Dongle automatically calculates it) pkt_count = bytearray(struct.pack( "<Q", conn_tx_packet_counter)[:5]) # convert only 5 bytes pkt_count[4] |= 0x80 # Set for master -> slave nonce = pkt_count + conn_iv aes = AES.new(conn_session_key, AES.MODE_CCM, nonce=nonce, mac_len=4) # mac = mic aes.update( chr(header & 0xE3)) # Calculate mic over header cleared of NES, SN and MD enc_pkt, mic = aes.encrypt_and_digest( raw_pkt[6:-3]) # get payload and exclude 3 bytes of crc conn_tx_packet_counter += 1 # Increment packet counter driver.raw_send(aa + chr(header) + chr(length) + enc_pkt + mic + crc) print(Fore.CYAN + "TX ---> [Encrypted]{" + pkt.summary()[7:] + '}')
def send(self, scapy_pkt, print_tx=True, force_pcap_save=False): self.raw_send(raw(scapy_pkt)) if self.logs_pcap and (self.pcap_tx_handover is 0 or force_pcap_save): self.packets_buffer.append( NORDIC_BLE(board=75, protocol=2, flags=0x3) / scapy_pkt) if print_tx: print(Fore.CYAN + "TX ---> " + scapy_pkt.summary()[7:])
def defragment_l2cap(pkt): global fragment, fragment_start, fragment_left # Handle L2CAP fragment if L2CAP_Hdr in pkt and pkt[L2CAP_Hdr].len + 4 > pkt[BTLE_DATA].len: fragment_start = True fragment_left = pkt[L2CAP_Hdr].len fragment = raw(pkt)[:-3] return None elif fragment_start and BTLE_DATA in pkt and pkt[BTLE_DATA].LLID == 0x01: fragment_left -= pkt[BTLE_DATA].len + 4 fragment += raw(pkt[BTLE_DATA].payload) if pkt[BTLE_DATA].len >= fragment_left: fragment_start = False pkt = BTLE(fragment + '\x00\x00\x00') pkt.len = len(pkt[BTLE_DATA].payload) # update ble header length return pkt else: return None else: fragment_start = False return pkt
def __init__(self, pkt=None, alert_description="", alert_type: AlertType = AlertType.UNKNOWN, alert_severity: Severity = Severity.INFO, is_destination=False): """ Parses the given packet and extra information into a alert object :param pkt: Scapy's packet object, the collected info from the alert :param alert_description: A description providing context/information as to why this particular packet was flagged :param alert_type: IDS or PRIVACY :param is_destination: Boolean telling alert system if the IoT device is the dst or src """ # Initialize with default values self.timestamp = str(datetime.now()) self.device_name = "" self.device_ip = "" self.device_mac = "" self.type = alert_type self.severity = alert_severity try: if pkt: # Default values self.device_ip = "[Layer 2]" if is_destination: if "IP" in pkt: self.device_ip = pkt["IP"].dst self.device_mac = pkt["Ethernet"].dst else: if "IP" in pkt: self.device_ip = pkt["IP"].src self.device_mac = pkt["Ethernet"].src self.payload_info = raw(pkt) else: self.payload_info = "None" except KeyError as e: print("Error attempting to read from packet: " + str(e)) # TODO: Use some magic config trickery to get the name for this device, otherwise unknown self.device_name = "Unknown" self.description = alert_description hasher = hashlib.sha1() hasher.update(str(self.device_mac + self.timestamp).encode('utf-8')) self.id = int(hasher.hexdigest()[:4], 16)
def zigbee_decrypt(pktorig, key_net): """ Decrypts Zigbee packets. Or at least, it should .... """ doMicCheck = False #print("\n############################################################\n\n> Starting 4 parameters extraction") pkt = pktorig.copy() # Set key in byte format key = unhexlify(key_net) # 1. Get MIC using scapy black magic to rebuild the packet correctly pkt.nwk_seclevel = DOT154_CRYPT_ENC_MIC32 pkt.data += pkt.mic pkt.mic = pkt.data[-4:] mic = pkt.mic pkt.data = pkt.data[:-4] # Reset pkt after MIC extracted # 2. Get ciphertext ciphertext = pkt[ZigbeeSecurityHeader].data # 3. Get nonce using two ways. The zbdecrypt way seems to be the right one. The second is another found on another project using_zb_nonce = True if using_zb_nonce: extended_src = pkt[ZigbeeSecurityHeader].source if extended_src is None: return None nonce = struct.pack( 'Q', *struct.unpack('>Q', extended_src.to_bytes( 8, byteorder="big"))) + struct.pack( 'I', pkt[ZigbeeSecurityHeader].fc) + struct.pack( 'B', bytes(pkt[ZigbeeSecurityHeader])[0]) # sys.byteorder MUST BE byteorder="big" else: # Get NONCE : create NONCE (for crypt) and zigbeeData (for MIC) according to packet type ----------> Whatever that means sec_ctrl_byte = str(pkt[ZigbeeSecurityHeader])[0] if ZigbeeAppDataPayload in pkt: nonce = str(struct.pack('L', pkt[ZigbeeNWK].ext_src)) + str( struct.pack('I', pkt[ZigbeeSecurityHeader].fc)) + sec_ctrl_byte else: nonce = str(struct.pack( 'L', pkt[ZigbeeSecurityHeader].source)) + str( struct.pack('I', pkt[ZigbeeSecurityHeader].fc)) + sec_ctrl_byte # 4. Get ZigbeeData, aka the content of the ZigbeeNWK header. data_len = len(ciphertext) + len(mic) if ZigbeeAppDataPayload in pkt: if data_len > 0: header = bytes(pkt[ZigbeeAppDataPayload])[:-data_len] else: header = bytes(pkt[ZigbeeAppDataPayload]) else: if data_len > 0: header = bytes(pkt[ZigbeeNWK])[:-data_len] else: header = bytes(pkt[ZigbeeNWK]) # # Print the 4 extracted parameters # For debug purpose # print(key) # hexdump(key) # print("\n--------------------\n\n" + bcolors.FAIL + "1. NONCE : ?????????? Most likely the cause of the issue. Length is good (13, function says 7 to 13) but probably bad content. Changing little/big endian doesn't correct the decryption.\n" + bcolors.ENDC) # print(f"Nonce : {nonce} ; Length : {len(nonce)}") # print("\n--------------------\n\n" + bcolors.OKGREEN + "2. MIC : validated\n" + bcolors.ENDC) # print(f"MIC : {pkt.mic} ; Length : {len(pkt.mic)}") # print("\n--------------------\n\n" + bcolors.WARNING + "3. Ciphertext : More or less validated. MIC isn't part of what's considered 'Ciphertext' which makes sense.\n" + bcolors.ENDC) # print(f"Ciphertext : {ciphertext} ; Length : {len(ciphertext)}") # hexdump(ciphertext) # print("\n--------------------\n\n" + bcolors.OKGREEN + "4. ZigbeeData : Validated (ends right before the part that is encrypted)\n" + bcolors.ENDC) # print(f"ZigbeeData : {header} ; Length : {len(header)}") # hexdump(header) ########################### # Decryption cipher = AES.new(key, AES.MODE_CCM, nonce=nonce, mac_len=4) # Create cipher cipher.update( header ) # ???? It doesn't change anything, but it was in the original code. payload = cipher.decrypt(ciphertext) # Decrypt the ciphertext # Verify MIC try: cipher.verify(mic) micCheck = True except ValueError: micCheck = False # print("\n\n############################################################\n" + bcolors.HEADER + "Decrypted packet :\n" + bcolors.ENDC) # hexdump(text) frametype = pkt[ZigbeeNWK].frametype if frametype == 0 and micCheck == 1: payload = ZigbeeAppDataPayload(payload) elif frametype == 1 and micCheck == 1: payload = ZigbeeNWKCommandPayload(payload) else: payload = raw(payload) if doMicCheck == False: return payload else: if micCheck == 1: return (payload, True) else: return (payload, False)
ediv='\x00', rand='\x00', skdm=conn_iv, ivm=conn_skd) driver.send(enc_request) last_smp_summary = pkt.summary() if enable_secure_connections is False and SM_Confirm in pkt: switch_pairing = True elif enable_secure_connections and SM_Random in pkt: final_test = True if not disable_smp: enc_start_index += 1 # Handle pairing response and so on smp_answer = BLESMPServer.send_hci( raw(HCI_Hdr() / HCI_ACL_Hdr() / L2CAP_Hdr() / pkt[SM_Hdr])) if smp_answer is not None and isinstance(smp_answer, list): for res in smp_answer: res = HCI_Hdr(res) # type: HCI_Hdr if SM_Hdr in res: pkt = BTLE(access_addr=access_address) / BTLE_DATA( ) / L2CAP_Hdr() / res[SM_Hdr] last_smp_pkt = pkt if encryption_enabled: send_encrypted(pkt) else: driver.send(pkt) elif HCI_Cmd_LE_Start_Encryption_Request in res: conn_ltk = res.ltk
def ZigBeeConversion(self, pkt): """Return a row with the unified format if the packet (input) meets all the requirements Extracts information from ZigBee packet, analyzes it and converts it to the unified format if it corresponds to a data packet with specific information. """ # This is the little dirty trick # We access to the global variable to get the packet #packet = self.pkts[pkt] packet = gpkts[pkt] # Print debug logging.debug(f"Packet[{pkt}] processed") # We check if the packet is well formed # And the fcs is correct # So we compute the fcs and compare it to the one store in the packet try: fcs = int.from_bytes(packet.compute_fcs(raw(packet)[:-2]), 'little') except: return None if fcs != packet.fcs: return None #e = extractor.extract_pkt_info(packet) e = self.extractor.extract_pkt_info(packet) logging.debug(f"Packet[{pkt}] extracted: {e}") # We are only interested in ZCL Packets # So if the packet is an 802.15.4 ACK or DATA # then we ignore this packet and only focus on data packets if e is None: return None if "transmission" in e and "transmission4" in e["transmission"]: transmission4 = e["transmission"]["transmission4"] # The transmission4 is empty if transmission4 == {}: return None # We only check for data aps_frametype and HA profile # As for 802.15.4 ACK, aps one are not interesting if self.verbose: if transmission4['profile'] != 'HA_Home_Automation' or \ transmission4['aps_frametype'] == 'ack': return None else: if transmission4['profile'] != 0x0104 or \ transmission4['aps_frametype'] == 2: return None # tmp only focus on cluster "temperature_measurement" and "on_off" good_cluster = [0x0006, 0x0402] if self.verbose: good_cluster = ["temperature_measurement", "on_off"] if transmission4['cluster'] not in good_cluster: return None row = [] row.append('zigbee') row.append(e["transmission"]["time"]) row.append(e["transmission"]["transmission2"]['src']) row.append(e["transmission"]["transmission2"]['dst']) row.append(e["transmission"]["transmission3"]['srcshort']) row.append(e["transmission"]["transmission3"]['dstshort']) zcl_frametype = transmission4['zcl_frametype'] #### Apptype #### # ZCl_frametype # 1 -> Cluster-wide : In our case it means 'send command' # So apptype is actuator and the command is stored in e["transmission"]["transmission4"]['command'] if zcl_frametype == 1 or zcl_frametype == 'cluster-specific': apptype = 3 row.append(apptype) row.append(transmission4['command']) # In the case of profile-wide zcl_apptype # It correspond to sensor apptype # but the data can be different, it is either get_data or the data itself elif zcl_frametype == 0 or zcl_frametype == 'profile-wide': apptype = 2 row.append(apptype) command_identifier = transmission4['command_identifier'] if command_identifier == 'read attributes response' or command_identifier == 0x01: # TODO : make a conversion structure according to the type of data # Here we are only 2 data types : Boolean (16) or signed int (41) datatype = transmission4['read_attributes_status_records'][ 0]['attribute_data_type'] # boolean if datatype == 16: data = int.from_bytes( transmission4['read_attributes_status_records'][0] ['attribute_value'], 'little') row.append('On' if data else 'Off') #Unsigned Int elif datatype == 41: data = int.from_bytes( transmission4['read_attributes_status_records'][0] ['attribute_value'], 'little') row.append(data) # We don't consider default response as interesting message elif command_identifier == 'report attributes' or command_identifier == 0x0a: return None # We don't consider default response as interesting message elif command_identifier == 'default response' or command_identifier == 0x0b: return None else: row.append('get_data') logging.debug(f"Packet[{pkt}] : {row}") return row
def send(self, scapy_pkt, print_tx=True): self.raw_send(raw(scapy_pkt)) if print_tx: print(Fore.CYAN + "TX ---> " + scapy_pkt.summary()[7:])
driver.send(pkt) elif ATT_Exchange_MTU_Response in pkt: # Send version indication request if version_received == False: pkt = BTLE(access_addr=access_address) / BTLE_DATA() / CtrlPDU() / LL_VERSION_IND(version='4.2') driver.send(pkt) else: send_pairing_request() elif LL_VERSION_IND in pkt: send_pairing_request() elif pairing_procedure and SM_Hdr in pkt: # Handle pairing response and so on smp_answer = BLESMPServer.send_hci(raw(HCI_Hdr() / HCI_ACL_Hdr() / L2CAP_Hdr() / pkt[SM_Hdr])) if smp_answer is not None and isinstance(smp_answer, list): for res in smp_answer: res = HCI_Hdr(res) # type: HCI_Hdr if SM_Hdr in res: pkt = BTLE(access_addr=access_address) / BTLE_DATA() / L2CAP_Hdr() / res[SM_Hdr] if encryption_enabled: send_encrypted(pkt) else: driver.send(pkt) elif HCI_Cmd_LE_Start_Encryption_Request in res: conn_ltk = res.ltk print(Fore.GREEN + "[!] STK/LTK received from SMP server: " + hexlify(res.ltk).upper()) conn_iv = b'\x00' * 4 # set IVm (IV of master) conn_skd = b'\x00' * 8 # set SKDm (session key diversifier part of master)