def kbdecrypt(pkt, key=None, verbose=None): """Decrypt Zigbee frames using AES CCM* with 32-bit MIC""" if verbose is None: verbose = conf.verb if key == None: if conf.killerbee_nkey == None: log_killerbee.error( "Cannot find decryption key. (Set conf.killerbee_nkey)") return None key = conf.killerbee_nkey if len(key) != 16: log_killerbee.error( "Invalid decryption key, must be a 16 byte string.") return None elif not pkt.haslayer(ZigbeeSecurityHeader) or not pkt.haslayer(ZigbeeNWK): log_killerbee.error( "Cannot decrypt frame without a ZigbeeSecurityHeader.") return None try: import zigbee_crypt except ImportError: log_killerbee.error( "Could not import zigbee_crypt extension, cryptographic functionality is not available." ) return None #TODO: Investigate and issue a different fix: # https://code.google.com/p/killerbee/issues/detail?id=30 # This function destroys the packet, therefore work on a copy - @cutaway pkt = pkt.copy() #this is hack to fix the below line pkt.nwk_seclevel = 5 #the issue appears to be when this is set #mic = struct.unpack(">I", f['mic']) mic = pkt.mic f = pkt.getlayer(ZigbeeSecurityHeader).fields encrypted = f['data'] sec_ctrl_byte = str(pkt.getlayer(ZigbeeSecurityHeader))[0] # Bug fix thanks to cutaway (https://code.google.com/p/killerbee/issues/detail?id=25): #nonce = struct.pack('L',f['ext_source'])+struct.pack('I',f['fc']) + sec_ctrl_byte nonce = struct.pack('L', f['source']) + struct.pack( 'I', f['fc']) + sec_ctrl_byte #nonce = "" # build the nonce #nonce += struct.pack(">Q", f['ext_source']) #nonce += struct.pack(">I", f['fc']) #fc = (f['reserved1'] << 6) | (f['extended_nonce'] << 5) | (f['key_type'] << 3) | f['reserved2'] #nonce += chr(fc | 0x05) if verbose > 2: print "Decrypt Details:" print "\tKey: " + key.encode('hex') print "\tNonce: " + nonce.encode('hex') print "\tMic: " + mic.encode('hex') print "\tEncrypted Data: " + encrypted.encode('hex') crop_size = 4 + 2 + len( pkt.getlayer(ZigbeeSecurityHeader).fields['data'] ) # the size of all the zigbee crap, minus the length of the encrypted data, mic and FCS # the Security Control Field flags have to be adjusted before this is calculated, so we store their original values so we can reset them later #reserved2 = pkt.getlayer(ZigbeeSecurityHeader).fields['reserved2'] #pkt.getlayer(ZigbeeSecurityHeader).fields['reserved2'] = (pkt.getlayer(ZigbeeSecurityHeader).fields['reserved2'] | 0x05) zigbeeData = pkt.getlayer(ZigbeeNWK).do_build() zigbeeData = zigbeeData[:-crop_size] #pkt.getlayer(ZigbeeSecurityHeader).fields['reserved2'] = reserved2 (payload, micCheck) = zigbee_crypt.decrypt_ccm(key, nonce, mic, encrypted, zigbeeData) if verbose > 2: print "\tDecrypted Data: " + payload.encode('hex') frametype = pkt.getlayer(ZigbeeNWK).fields['frametype'] if frametype == 0: payload = ZigbeeAppDataPayload(payload) elif frametype == 1: payload = ZigbeeNWKCommandPayload(payload) else: payload = Raw(payload) return payload
def kbdecrypt(pkt, key=None, verbose=None): """Decrypt Zigbee frames using AES CCM* with 32-bit MIC""" if verbose is None: verbose = conf.verb if key == None: if conf.killerbee_nkey == None: log_killerbee.error("Cannot find decryption key. (Set conf.killerbee_nkey)") return None key = conf.killerbee_nkey if len(key) != 16: log_killerbee.error("Invalid decryption key, must be a 16 byte string.") return None elif not pkt.haslayer(ZigbeeSecurityHeader) or not pkt.haslayer(ZigbeeNWK): log_killerbee.error("Cannot decrypt frame without a ZigbeeSecurityHeader.") return None try: import zigbee_crypt except ImportError: log_killerbee.error("Could not import zigbee_crypt extension, cryptographic functionality is not available.") return None # TODO: Investigate and issue a different fix: # https://code.google.com/p/killerbee/issues/detail?id=30 # This function destroys the packet, therefore work on a copy - @cutaway pkt = pkt.copy() # this is hack to fix the below line pkt.nwk_seclevel = 5 # the issue appears to be when this is set # mic = struct.unpack(">I", f['mic']) mic = pkt.mic f = pkt.getlayer(ZigbeeSecurityHeader).fields encrypted = f["data"] sec_ctrl_byte = str(pkt.getlayer(ZigbeeSecurityHeader))[0] # Bug fix thanks to cutaway (https://code.google.com/p/killerbee/issues/detail?id=25): # nonce = struct.pack('L',f['ext_source'])+struct.pack('I',f['fc']) + sec_ctrl_byte nonce = struct.pack("L", f["source"]) + struct.pack("I", f["fc"]) + sec_ctrl_byte # nonce = "" # build the nonce # nonce += struct.pack(">Q", f['ext_source']) # nonce += struct.pack(">I", f['fc']) # fc = (f['reserved1'] << 6) | (f['extended_nonce'] << 5) | (f['key_type'] << 3) | f['reserved2'] # nonce += chr(fc | 0x05) if verbose > 2: print "Decrypt Details:" print "\tKey: " + key.encode("hex") print "\tNonce: " + nonce.encode("hex") print "\tMic: " + mic.encode("hex") print "\tEncrypted Data: " + encrypted.encode("hex") crop_size = ( 4 + 2 + len(pkt.getlayer(ZigbeeSecurityHeader).fields["data"]) ) # the size of all the zigbee crap, minus the length of the encrypted data, mic and FCS # the Security Control Field flags have to be adjusted before this is calculated, so we store their original values so we can reset them later # reserved2 = pkt.getlayer(ZigbeeSecurityHeader).fields['reserved2'] # pkt.getlayer(ZigbeeSecurityHeader).fields['reserved2'] = (pkt.getlayer(ZigbeeSecurityHeader).fields['reserved2'] | 0x05) zigbeeData = pkt.getlayer(ZigbeeNWK).do_build() zigbeeData = zigbeeData[:-crop_size] # pkt.getlayer(ZigbeeSecurityHeader).fields['reserved2'] = reserved2 (payload, micCheck) = zigbee_crypt.decrypt_ccm(key, nonce, mic, encrypted, zigbeeData) if verbose > 2: print "\tDecrypted Data: " + payload.encode("hex") frametype = pkt.getlayer(ZigbeeNWK).fields["frametype"] if frametype == 0: payload = ZigbeeAppDataPayload(payload) elif frametype == 1: payload = ZigbeeNWKCommandPayload(payload) else: payload = Raw(payload) return payload
def kbdecrypt(source_pkt, key=None, verbose=None, doMicCheck=False): """Decrypt Zigbee frames using AES CCM* with 32-bit MIC""" if verbose is None: verbose = conf.verb if key == None: if conf.killerbee_nkey == None: log_killerbee.error( "Cannot find decryption key. (Set conf.killerbee_nkey)") return None key = conf.killerbee_nkey if len(key) != 16: log_killerbee.error( "Invalid decryption key, must be a 16 byte string.") return None if not source_pkt.haslayer(ZigbeeSecurityHeader): log_killerbee.error( "Cannot decrypt frame without a ZigbeeSecurityHeader.") return None if not source_pkt.haslayer(ZigbeeNWK): log_killerbee.error("Cannot decrypt frame without a ZigbeeNWK.") return None try: import zigbee_crypt except ImportError: log_killerbee.error( "Could not import zigbee_crypt extension, cryptographic functionality is not available." ) return None # This function destroys the packet, therefore work on a copy - @cutaway pkt = source_pkt.copy() # DOT154_CRYPT_ENC_MIC32 is always used regardless of what is claimed in OTA packet, so we will force it here. # This is done because the value of nwk_seclevel in the ZigbeeSecurityHeader does # not have to be accurate in the transmitted frame: the Zigbee NWK standard states that # the nwk_seclevel should be overwritten in the received frame with the value that is being # used by all nodes in the Zigbee network - this is to ensure that unencrypted frames can't be # maliciously injected. i.e. the receiver shouldn't trust the received nwk_seclevel. # Recreate 'pkt' by rebuilding the raw data and mic to match: pkt.nwk_seclevel = DOT154_CRYPT_ENC_MIC32 pkt.data += pkt.mic pkt.mic = pkt.data[-4:] pkt.data = pkt.data[:-4] encrypted = pkt.data # So calculate an amount to crop, equal to the size of the encrypted data and mic. Note that # if there was an FCS, scapy will have already stripped it, so it will not returned by the # do_build() call below (and hence doesn't need to be taken into account in crop_size). crop_size = len(pkt.mic) + len(pkt.data) # create NONCE (for crypt) and zigbeeData (for MIC) according to packet type sec_ctrl_byte = str(pkt[ZigbeeSecurityHeader])[0] if pkt.haslayer(ZigbeeAppDataPayload): nonce = struct.pack('L', source_pkt[ZigbeeNWK].ext_src) + struct.pack( 'I', source_pkt[ZigbeeSecurityHeader].fc) + sec_ctrl_byte zigbeeData = pkt[ZigbeeAppDataPayload].do_build() else: nonce = struct.pack( 'L', source_pkt[ZigbeeSecurityHeader].source) + struct.pack( 'I', source_pkt[ZigbeeSecurityHeader].fc) + sec_ctrl_byte zigbeeData = pkt[ZigbeeNWK].do_build() # For zigbeeData, we need the entire zigbee packet, minus the encrypted data and mic (4 bytes). zigbeeData = zigbeeData[:-crop_size] (payload, micCheck) = zigbee_crypt.decrypt_ccm(key, nonce, pkt.mic, encrypted, zigbeeData) if verbose > 2: print "Decrypt Details:" print "\tKey: " + key.encode('hex') print "\tNonce: " + nonce.encode('hex') print "\tZigbeeData: " + zigbeeData.encode('hex') print "\tDecrypted Data: " + payload.encode('hex') print "\tEncrypted Data: " + encrypted.encode('hex') print "\tMic: " + pkt.mic.encode('hex') 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)
def kbdecrypt(pkt, key = None, verbose = None): """Decrypt Zigbee frames using AES CCM* with 32-bit MIC""" if verbose is None: verbose = conf.verb if key == None: if conf.killerbee_nkey == None: log_killerbee.error("Cannot find decryption key. (Set conf.killerbee_nkey)") return None key = conf.killerbee_nkey if len(key) != 16: log_killerbee.error("Invalid decryption key, must be a 16 byte string.") return None elif not pkt.haslayer(ZigbeeSecurityHeader) or not pkt.haslayer(ZigbeeNWK): log_killerbee.error("Cannot decrypt frame without a ZigbeeSecurityHeader.") return None try: import zigbee_crypt except ImportError: log_killerbee.error("Could not import zigbee_crypt extension, cryptographic functionality is not available.") return None #TODO: Investigate and issue a different fix: # https://code.google.com/p/killerbee/issues/detail?id=30 # This function destroys the packet, therefore work on a copy - @cutaway pkt = pkt.copy() #this is hack to fix the below line pkt.nwk_seclevel=5 #the issue appears to be when this is set # Parse the payload as ciphertext || MIC, where MIC is 4 bytes pkt.mic = pkt.getlayer(ZigbeeSecurityHeader).data[-4:] pkt.data = pkt.getlayer(ZigbeeSecurityHeader).data[:-4] mic = pkt.mic f = pkt.getlayer(ZigbeeSecurityHeader).fields encrypted = f['data'] sec_ctrl_byte = str(pkt.getlayer(ZigbeeSecurityHeader))[0] nonce = struct.pack('L',f['source'])+struct.pack('I',f['fc']) + sec_ctrl_byte if verbose > 2: print "Decrypt Details:" print "\tKey: " + key.encode('hex') print "\tNonce: " + nonce.encode('hex') print "\tMic: " + mic.encode('hex') print "\tEncrypted Data: " + encrypted.encode('hex') crop_size = 4 + len(pkt.getlayer(ZigbeeSecurityHeader).fields['data']) # the size of all the zigbee crap, minus the length of the encrypted data, mic zigbeeData = pkt.getlayer(ZigbeeNWK).do_build() zigbeeData = zigbeeData[:-crop_size] # zigbeeData = octet string a = NwkHeader || AuxiliaryHeader # encrypted = octet string c = Payload (payload, micCheck) = zigbee_crypt.decrypt_ccm(key, nonce, mic, encrypted, zigbeeData) if verbose > 2: print "\tDecrypted Data: " + payload.encode('hex') print "\tmicCheck: " + str(micCheck) frametype = pkt.getlayer(ZigbeeNWK).fields['frametype'] if frametype == 0: payload = ZigbeeAppDataPayload(payload) elif frametype == 1: payload = ZigbeeNWKCommandPayload(payload) else: payload = Raw(payload) return payload
def kbdecrypt(pkt, key=None, verbose=None, doMicCheck=False): """Decrypt Zigbee frames using AES CCM* with 32-bit MIC""" if verbose is None: verbose = conf.verb if key == None: if conf.killerbee_nkey == None: log_killerbee.error( "Cannot find decryption key. (Set conf.killerbee_nkey)") return None key = conf.killerbee_nkey if len(key) != 16: log_killerbee.error( "Invalid decryption key, must be a 16 byte string.") return None elif not pkt.haslayer(ZigbeeSecurityHeader) or not pkt.haslayer(ZigbeeNWK): log_killerbee.error( "Cannot decrypt frame without a ZigbeeSecurityHeader.") return None try: import zigbee_crypt except ImportError: log_killerbee.error( "Could not import zigbee_crypt extension, cryptographic functionality is not available." ) return None #TODO: Investigate and issue a different fix: # https://code.google.com/p/killerbee/issues/detail?id=30 # This function destroys the packet, therefore work on a copy - @cutaway pkt = pkt.copy() #this is hack to fix the below line pkt.nwk_seclevel = 5 #the issue appears to be when this is set # Now recreate 'pkt' by rebuilding the raw data and creating a new scapy Packet, because # scapy splits the data/mic according to the nwk_seclevel in the ZigbeeSecurityHeader when # the scapy Packet is created. The value of nwk_seclevel in the ZigbeeSecurityHeader does # not have to be accurate in the transmitted frame: the Zigbee NWK standard states that # the nwk_seclevel should be overwritten in the received frame with the value that is being # used by all nodes in the Zigbee network - this is to ensure that unencrypted frames can't be # maliciously injected. i.e. the receiver shouldn't trust the received nwk_seclevel. newpkt = pkt.build() if pkt.haslayer(Dot15d4FCS): pkt = Dot15d4FCS(newpkt) else: pkt = Dot15d4(newpkt) #mic = struct.unpack(">I", f['mic']) mic = pkt.mic f = pkt.getlayer(ZigbeeSecurityHeader).fields encrypted = f['data'] sec_ctrl_byte = str(pkt.getlayer(ZigbeeSecurityHeader))[0] # Bug fix thanks to cutaway (https://code.google.com/p/killerbee/issues/detail?id=25): #nonce = struct.pack('L',f['ext_source'])+struct.pack('I',f['fc']) + sec_ctrl_byte nonce = struct.pack('L', f['source']) + struct.pack( 'I', f['fc']) + sec_ctrl_byte #nonce = "" # build the nonce #nonce += struct.pack(">Q", f['ext_source']) #nonce += struct.pack(">I", f['fc']) #fc = (f['reserved1'] << 6) | (f['extended_nonce'] << 5) | (f['key_type'] << 3) | f['reserved2'] #nonce += chr(fc | 0x05) if verbose > 2: print "Decrypt Details:" print "\tKey: " + key.encode('hex') print "\tNonce: " + nonce.encode('hex') print "\tMic: " + mic.encode('hex') print "\tEncrypted Data: " + encrypted.encode('hex') # For zigbeeData, we need the entire zigbee packet, minus the encrypted data and mic (4 bytes). # So calculate an amount to crop, equal to the size of the encrypted data and mic. Note that # if there was an FCS, scapy will have already stripped it, so it will not returned by the # do_build() call below (and hence doesn't need to be taken into account in crop_size). crop_size = 4 + len(pkt.getlayer(ZigbeeSecurityHeader).fields['data']) # the Security Control Field flags have to be adjusted before this is calculated, so we store their original values so we can reset them later #reserved2 = pkt.getlayer(ZigbeeSecurityHeader).fields['reserved2'] #pkt.getlayer(ZigbeeSecurityHeader).fields['reserved2'] = (pkt.getlayer(ZigbeeSecurityHeader).fields['reserved2'] | 0x05) zigbeeData = pkt.getlayer(ZigbeeNWK).do_build() zigbeeData = zigbeeData[:-crop_size] #pkt.getlayer(ZigbeeSecurityHeader).fields['reserved2'] = reserved2 (payload, micCheck) = zigbee_crypt.decrypt_ccm(key, nonce, mic, encrypted, zigbeeData) if verbose > 2: print "\tDecrypted Data: " + payload.encode('hex') frametype = pkt.getlayer(ZigbeeNWK).fields['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)
def kbdecrypt(pkt, key = None, verbose = None): """Decrypt Zigbee frames using AES CCM* with 32-bit MIC""" if verbose is None: verbose = conf.verb if key == None: if conf.killerbee_nkey == None: log_killerbee.error("Cannot find decryption key. (Set conf.killerbee_nkey)") return None key = conf.killerbee_nkey if len(key) != 16: log_killerbee.error("Invalid decryption key, must be a 16 byte string.") return None elif not pkt.haslayer(ZigbeeSecurityHeader) or not pkt.haslayer(ZigbeeNWK): log_killerbee.error("Cannot decrypt frame without a ZigbeeSecurityHeader.") return None try: import zigbee_crypt except ImportError: log_killerbee.error("Could not import zigbee_crypt extension, cryptographic functionality is not available.") return None from struct import pack f = pkt.getlayer(ZigbeeSecurityHeader).fields mic = pack(">I", f['mic']) encrypted = f['data'] nonce = "" # build the nonce nonce += pack(">Q", f['source']) nonce += pack(">I", f['fc']) fc = (f['reserved1'] << 6) | (f['extended_nonce'] << 5) | (f['key_type'] << 3) | f['reserved2'] nonce += chr(fc | 0x05) if verbose > 2: print "Decrypt Details:" print "\tKey: " + key.encode('hex') print "\tNonce: " + nonce.encode('hex') print "\tMic: " + mic.encode('hex') print "\tEncrypted Data: " + encrypted.encode('hex') crop_size = 4 + 2 + len(pkt.getlayer(ZigbeeSecurityHeader).fields['data']) # the size of all the zigbee crap, minus the length of the encrypted data, mic and FCS # the Security Control Field flags have to be adjusted before this is calculated, so we store their original values so we can reset them later reserved2 = pkt.getlayer(ZigbeeSecurityHeader).fields['reserved2'] pkt.getlayer(ZigbeeSecurityHeader).fields['reserved2'] = (pkt.getlayer(ZigbeeSecurityHeader).fields['reserved2'] | 0x05) zigbeeData = pkt.getlayer(ZigbeeNWK).do_build() zigbeeData = zigbeeData[:-crop_size] pkt.getlayer(ZigbeeSecurityHeader).fields['reserved2'] = reserved2 (payload, micCheck) = zigbee_crypt.decrypt_ccm(key, nonce, mic, encrypted, zigbeeData) if verbose > 2: print "\tDecrypted Data: " + payload.encode('hex') frametype = pkt.getlayer(ZigbeeNWK).fields['frametype'] if frametype == 0: payload = ZigbeeAppDataPayload(payload) elif frametype == 1: payload = ZigbeeNWKCommandPayload(payload) else: payload = Raw(payload) if micCheck == 1: return (payload, True) else: return (payload, False)