Пример #1
0
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')
Пример #2
0
 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:])
Пример #3
0
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:] + '}')
Пример #4
0
 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
Пример #6
0
    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)
Пример #7
0
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)
Пример #8
0
                        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
Пример #9
0
    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)