riverloopsec / killerbee

IEEE 802.15.4/ZigBee Security Research Toolkit
http://www.riverloopsecurity.com
Other
742 stars 215 forks source link

i suspect there is something wrong about the mac footer calculated #256

Closed jbgod closed 2 years ago

jbgod commented 2 years ago

i had use killerbee to write a tool for zigbee network connect. i just use the kb.inject to send zigbee packet,with a threading capture process to sniffer the pack by another project from github. i can't capture the beacon back by kb.pnext ,because the gateway response the beacon too fast.

until i solve so many troubles about this tool, i found something that make me feel so weird. i imitate as a normal zigbee device to send association request, but the zigbee gateway response with a Pan access denied i capture packets on my windows at the sametime as fllows.

企业微信截图_16529350014656 企业微信截图_16529435713717

i found the association request packet send by killerbee, and call the crc check function makeFCS, indeed this packet will also be invalidcrc image

i can just flow the inject() to RF_txpacket(), i can't find the send packet crc where to calculate. i don't understand the logic of writecmd()

def RF_txpacket(self,packet):
    """Send a packet through the radio."""
    self.writecmd(self.CCSPIAPP,0x81,len(packet),packet);
    #time.sleep(1);
    #self.strobe(0x09);
    return;
taylorcenters commented 2 years ago

What hardware are you using?

jbgod commented 2 years ago

i had try apimote and cc2531 on raspbain with python3.7 and python 3.6 i control the time interval of each packet send(beacon request, association requst, data request) as the normal endpoint device and tried noral mac_adress and random mac address. but the zigbee pan denied my request. i had try many way to debug my tool,but i can't solve this problem. today i use apimote to send packet with a threading process control cc2531 to capture the packet.

it's 0:33 local time now,i will back to my lab after 9 hours i'm waiting your help.

taylorcenters commented 2 years ago

the apimote should be letting the radio auto generate the crc if you use the inject() function

https://github.com/riverloopsec/killerbee/blob/c4137618263ffdb324ff86ed8a7875c3282fc1f1/killerbee/dev_apimote.py#L182 self.handle.RF_autocrc(1) #let radio add the CRC

the RF_txpacket is sending a command to the apimote firmware - you can see the code to handle it here: https://github.com/travisgoodspeed/goodfet/blob/master/firmware/apps/radios/ccspi.c https://github.com/travisgoodspeed/goodfet/blob/master/firmware/include/ccspi.h

the CCSPIAPP tells it to use this app - 0x81 is the "verb" to use the ccspi_tx function - then has the packet bytes+length to send

jbgod commented 2 years ago
def test_decrypt_cm(self):
    key = b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f'
    nonce = b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c'
    enc_data = b'\x17\x36\xb7\x8c'
    mic = b'\xfc\xe0\xce\x86'
    zigbee_data = b'\x00\x00\x00\x00'

    (pt_data, mic_check) = decrypt_ccm(key, nonce, mic, enc_data, zigbee_data)

i guess enc_data is aps_payload in packet with pkcs7padding, what is the args zigbee_data. i capture the transport key packet, i want to decrypt it, i use zigbee_data = b'\x00\x00\x00\x00' to decrypt my capture packet's aps_payload, the result is not right

taylorcenters commented 2 years ago

can find implementation for decrypt_ccm here: https://github.com/riverloopsec/killerbee/blob/develop/zigbee_crypt/zigbee_crypt.c

The parameters for this function are: key: 16-byte decryption key nonce: 13-byte nonce mic: 4-16 byte message integrity check (MIC) encrypted_payload: The encrypted data to decrypt zigbee_data: The zigbee data within the frame, without the encrypted payload, MIC, or FCS

the zigbee_data is used as auth-data, so it will need to match the same value used during encryption. The decrypted data should be unaffected, the MIC validation will be false if this value doesn't match.

jbgod commented 2 years ago

python3 test.py 32 b'\x94J\x84\xfe\xff\xbd\x1b\xec\t\xa0\x05\x000' Traceback (most recent call last): File "test.py", line 103, in zigbee_aps_decrypt(data) File "test.py", line 96, in zigbee_aps_decrypt result = kbdecrypt(scapy_packet, key) File "/usr/local/lib/python3.7/site-packages/killerbee/scapy_extensions.py", line 474, in kbdecrypt nonce: bytes = struct.pack('L',source_pkt[ZigbeeNWK].ext_src)+struct.pack('I',source_pkt[ZigbeeSecurityHeader].fc) + sec_ctrl_byte struct.error: required argument is not an integer

my code

`

data = b'a\x88\xec!8\xb8o\x00\x00\x08\x00\xb8o\x00\x00\x1e~!\xc40\t\xa0\x05\x00\x94J\x84\xfe\xff\xbd\x1b\xec\xdd\xae/>\xde\x19Ff\xb5\xdeHuH\xec\x98B] \x12\xfa\xb4]8>Y\xc87\xa9y0\xc6p\xce\x16\xefM\xdf.\xb97\xeb' key = bytes([0x5A, 0x69, 0x67, 0x42, 0x65, 0x65, 0x41, 0x6C, 0x6C,0x69,0x61,0x6E,0x63, 0x65, 0x30, 0x39]) scapy_packet = Dot15d4(data) result = kbdecrypt(scapy_packet, key)

`

i try the function in kbdecrypt,some error happen

taylorcenters commented 2 years ago

hmm failing here: https://github.com/riverloopsec/killerbee/blob/3bbf7eb147ce9b94734ef17a1659e36690251d25/killerbee/scapy_extensions.py#L474

from your code I see

>>> scapy_packet = Dot15d4(data)
>>> scapy_packet[ZigbeeNWK].ext_src
>>> scapy_packet[ZigbeeSecurityHeader].fc
368649

so it's failing because your packet isn't using an ext_src. I'll keep looking to see how to fix.

edit: I will drink coffee to think more clearly - the above conclusion may be incorrect

jbgod commented 2 years ago

i had try decrypt_ccm. the aes-ccm* result is wrong the packet is transport key packet to send encrypted network key

the packet is(copy from ubiqua)

(encrypted packet data) 61 88 EC 21 38 B8 6F 00 00 08 00 B8 6F 00 00 1E 7E 21 C4 30 09 A0 05 00 94 4A 84 FE FF BD 1B EC (encrypted aps payload) DD AE 2F 3E DE 19 46 66 B5 DE 48 75 48 EC 98 42 5D 20 12 FA B4 5D 38 3E 59 C8 37 A9 79 30 C6 70 CE 16 EF (APS MIC) 4D DF 2E B9 FF FF

(decrypted packet data) (APS payload) 01 47 F3 20 01 83 1C 1C B6 43 A1 45 7F 3F 80 D9 9D 00 88 31 CA FE FF F9 E3 B4 94 4A 84 FE FF BD 1B EC (aps MIC) 49 18 A0 1E

from Crypto.Cipher import AES
from Crypto.Util import Counter
import binascii
from zigbee_crypt import *
from killerbee.scapy_extensions import *

#capture on the air
data = b'a\x88\xec!8\xb8o\x00\x00\x08\x00\xb8o\x00\x00\x1e~!\xc40\t\xa0\x05\x00\x94J\x84\xfe\xff\xbd\x1b\xec\xdd\xae/>\xde\x19Ff\xb5\xdeHuH\xec\x98B] \x12\xfa\xb4]8>Y\xc87\xa9y0\xc6p\xce\x16\xefM\xdf.\xb97\xeb'
def zigbee_aps_decrypt(data): 
    #select mac payload         
    #nwk header  8 bytes | nwk payload
    mac_payload = data[9 : -2]
    nwk_header = mac_payload[:8]
    #select nwk payload        
    #aps header (2 bytes) | aps aux header (13 bytes) | aps payload 
    #nwk_payload = data[17: -2]
    nwk_payload = mac_payload[8:]
    aps_header = nwk_payload[:2]
    aps_aux_header = nwk_payload[2:15]
    # content in APS AUX Header
    # APS Security Control (1 bytes) | APS Frame Counter (4 bytes) | Source Address (8 bytes)
    #print(aps_aux_header)
    aps_security_control = aps_aux_header[0]
    aps_frame_counter = aps_aux_header[1:5]
    #print(aps_frame_counter)
    source_address = aps_aux_header[5:]
    #print(source_address)
    aps_payload = nwk_payload[15:-4]
    print(len(aps_payload))
    aps_mic = nwk_payload[-4:]
    #print(aps_payload)
    #add_auth_data
    auth_data = nwk_header + aps_aux_header
    add_auth_data = len(auth_data).to_bytes(2, byteorder = 'big') + auth_data
    add_auth_data = pkcs7padding(add_auth_data)
    #nonce
    nonce = source_address + aps_frame_counter + bytes([aps_security_control])
    #print(nonce)
    enc_data = aps_payload
    enc_data1 = pkcs7padding(aps_payload)
    zigbee_data = b'a\x88\xec!8\xb8o\x00\x00\x08\x00\xb8o\x00\x00\x1e~!\xc40\t\xa0\x05\x00\x94J\x84\xfe\xff\xbd\x1b\xec'
    key = bytes([0x5A, 0x69, 0x67, 0x42, 0x65, 0x65, 0x41, 0x6C, 0x6C,0x69,0x61,0x6E,0x63, 0x65, 0x30, 0x39])
    (pt_data, mic_check) = decrypt_ccm(key, nonce, aps_mic, enc_data, zigbee_data)
    (pt_data1, mic_check1) = decrypt_ccm(key, nonce, aps_mic, enc_data1, zigbee_data)
    print(pt_data)
    print(pt_data1)
    print(mic_check)
    # scapy_packet = Dot15d4(data)
    # result = kbdecrypt(scapy_packet, key)
    # print(result)

zigbee_aps_decrypt(data)
#result
python test.py
35
b"\xd0\xa5`\x14l\x9f\xaa\xf6\xd4\xc6\x86\xf1)\xd1\xc3\x85 W\xf4j\xc1i\xe7\xee\xe8+\xfbV'$ZO\xf4\xf1N"
b"\xd0\xa5`\x14l\x9f\xaa\xf6\xd4\xc6\x86\xf1)\xd1\xc3\x85 W\xf4j\xc1i\xe7\xee\xe8+\xfbV'$ZO\xf4\xf1N\x7f\xf3,\xc1\xfb\xe0\xaa\xd3\xbe\xb9J\x13K"
0

i think i should read the aes-ccm* document andthend read the source code i had read this paper https://lucidar.me/en/zigbee/zigbee-frame-encryption-with-aes-128-ccm/ but i'm not sure the value of flags and two last bytes of the counter

counter = Flags + nonce + bytes([a]) + bytes([b])

if i got the true counter, i can use aes_ctr to decrypt it

taylorcenters commented 2 years ago
source_mac_warning

I saved the packet you provided as a pcap to view in wireshark. It also noted an issue with the source address and did not seem to be able to parse or decrypt the packet

>>> bytes(packet)
b'a\x88\xec!8\xb8o\x00\x00\x08\x00\xb8o\x00\x00\x1e~!\xc40\t\xa0\x05\x00\x94J\x84\xfe\xff\xbd\x1b\xec\xdd\xae/>\xde\x19Ff\xb5\xdeHuH\xec\x98B] \x12\xfa\xb4]8>Y\xc87\xa9y0\xc6p\xce\x16\xefM\xdf.\xb97\xeb'

you can save to pcap by

>>> kbwrpcap("/path/to/output/mypacket.pcap", [bytes(packet)])
jbgod commented 2 years ago

i captue it from ubiqua,the transport key packet with seqnum 0xec,the data in python code is capture from pyCCsniffer. they are same data except the mfc. i had save it as pcap file. result.zip i add the trust center link in ubiqua,so the software decrypt it image the packet is the no.158 packet in wireshark image

taylorcenters commented 2 years ago

Thanks I'll take a look!

jbgod commented 2 years ago

ok,thank you,i work for many days for this thing,i will keep on this weekend. i am waiting for your response

jbgod commented 2 years ago

i read the zigbee's document about the encryption and decryption i tried the sec_keyhash to check the first block of AES(Key, A1) but the result is not the aes_key_a1(m[0:16] ^ c[0:16]) image

In [69]: key
Out[69]: b'ZigBeeAlliance09'

In [70]: hash_key = sec_key_hash(key, b'\x00')

In [76]: aes = AES.new(hash_key)

In [77]: aes.encrypt(A1)
Out[77]: b'\xf90:H\xfai\xd4\x8e\xc5\x8c(j\x99S~\x06'

In [79]: sec_key_hash(key, b'\x00')
Out[79]: b'K\xab\x0f\x17>\x144\xa2\xd5r\xe1\xc1\xefG\x87\x82'

In [80]: A1
Out[80]: b'\x01\x94J\x84\xfe\xff\xbd\x1b\xec\t\xa0\x05\x000\x00\x01'

In [81]: A1 = flags + nonce + bytes([0x00]) + bytes([0x01])

In [82]: aes.encrypt(A1)
Out[82]: b'\xf90:H\xfai\xd4\x8e\xc5\x8c(j\x99S~\x06'

In [83]: aes_key_a1
Out[83]: b'\xd8\xafh\xcd\xfe\x18\xc5z\xa9h\x0b\xd4\r\x93\xa7\xc2'

image https://reverseengineering.stackexchange.com/questions/9161/what-key-is-being-using-to-encrypt-the-key-transport-in-this-zigbee-capture

edit: i read some document about the zigbee it's tell me the key to encrypt aps payload is hash of the the trust center key, but don't tell me more about that,do you know how to realize this hash function

taylorcenters commented 2 years ago

Were you able to figure this out? I've tried using

from zigbee_crypt import *
pkt = b'a\x88\xe9!8\xb8o\x00\x00\x08\x00\xb8o\x00\x00\x1e|!\xc30\x08\xa0\x05\x00\x94J\x84\xfe\xff\xbd\x1b\xec\x19\xee\xc7A\xa3Y*\xe81\xcf\xa0\xe6\xf2\x14\xea\xb9\xae\x81\x07\x100\xc7\x8e\x82\xc4g\xbax\x9fD\xae4\x07w\xb6\x9b?xo\xff\xff'
key = sec_key_hash(b'ZigBeeAlliance09', b'\x00')
kbdecrypt(pkt, key)

but didn't have success - I am able to see that wireshark decrypts the packet from your pcap with the key ZigBeeAlliance09

taylorcenters commented 2 years ago

actually, I think I got this working with the hashed key.

>>> from zigbee_crypt import *
>>> from killerbee.scapy_extensions import *
>>> from scapy.all import *
>>>
>>> key = b'ZigBeeAlliance09'
>>> hkey = sec_key_hash(key, b'\x00')
>>>
>>> pkt_list = kbrdpcap('result.pcap')
>>> pkt = pkt_list[131].copy()
>>> bytes(pkt)
b'a\x88\xe9!8\xb8o\x00\x00\x08\x00\xb8o\x00\x00\x1e|!\xc30\x08\xa0\x05\x00\x94J\x84\xfe\xff\xbd\x1b\xec\x19\xee\xc7A\xa3Y*\xe81\xcf\xa0\xe6\xf2\x14\xea\xb9\xae\x81\x07\x100\xc7\x8e\x82\xc4g\xbax\x9fD\xae4\x07w\xb6\x9b?xo\xff\xff'
>>>
>>> pkt.nwk_seclevel = 5
>>>
>>> nonce = struct.pack('L', pkt[ZigbeeSecurityHeader].source)
>>> nonce = nonce + struct.pack('I', pkt[ZigbeeSecurityHeader].fc)
>>> nonce = nonce + bytes(pkt[ZigbeeSecurityHeader])[0:1]
>>>
>>> a_data = bytes(pkt[ZigbeeAppDataPayload])[:-len(pkt.data)]
>>> mic = pkt.data[-4:]
>>> ct_data = pkt.data[:-4]
>>>
>>> (pt_data, check) = decrypt_ccm(hkey, nonce, mic, ct_data, a_data)
>>> pt_data
b'\x05\x01G\xf3 \x01\x83\x1c\x1c\xb6C\xa1E\x7f?\x80\xd9\x9d\x00\x881\xca\xfe\xff\xf9\xe3\xb4\x94J\x84\xfe\xff\xbd\x1b\xec'
>>> check
1
>>> ZigbeeAppCommandPayload(pt_data)
<ZigbeeAppCommandPayload  cmd_identifier=APS_CMD_TRANSPORT_KEY key_type=Standard Network Key key='G\xf3 \x01\x83\x1c\x1c\xb6C\xa1E\x7f?\x80\xd9\x9d' key_seqnum=0 dest_addr=b4:e3:f9:ff:fe:ca:31:88 src_addr=ec:1b:bd:ff:fe:84:4a:94 |>

I need to put in a PR to fix the issue with missing ext_src to pull source addr from the ZigbeeSecurityHeader field but hopefully the above will work for you.

jbgod commented 2 years ago

i just have one question can't solved,the first question of this issue. why the association request i send by killerbee is be reject by the zigbee gateway https://github.com/riverloopsec/killerbee/issues/256#issue-1241311966

the autocrc(1) result and the makeFCS() result is different in my test. but i don't know how to solve this in the firmware

i wirte some function for me to send the zigbee packet function

def kb_inject_packet(payload):
    global kb_kb
    try:
        kb_kb.inject(payload)    
    except Exception as e:
        sys.stderr.write("ERROR: Unable to inject packet: {0}".format(e))
        sys.exit(-1)
    kb_kb.sniffer_off()

def create_beacon_request_packet(seqnum):
    #beacon request format
    beacon = b"\x03\x08\x00\xff\xff\xff\xff\x07"
    beaconp1 = beacon[0:2]
    beaconp2 = beacon[3:]    
    packet = b''.join([beaconp1, b"%c" % seqnum, beaconp2]) 
    return packet

def create_asso_packet(frame_control, seqnum, destination_pan_id, destination_address, source_address, mac_payload):
    packet = b''.join([frame_control, b"%c" % seqnum, destination_pan_id, destination_address, 
    #soure pan id
    b'\xff\xff', source_address, mac_payload ])
    return packet

def create_data_req_packet(frame_control, seqnum, destination_pan_id, destination_address, source_address, mac_payload):
    packet = b''.join([frame_control, b"%c" % seqnum, destination_pan_id, destination_address, source_address, mac_payload ])
    return packet

def create_acknowledge_packet(seqnum):
    packet = b''.join([b"\x12\x00", b"%c" % seqnum])
    return packet

kb_kb = KillerBee("/dev/ttyUSB0")

i send packet with these function,and capture packets show in my first question the seqnum of beacon request,association request and data requst are from 100 to 102. the destination__pan_id destination_address in association request are capture in the beacon back from gateway,source address is randmac maybe it's FCS error cause the association response with the Pan Access denied

taylorcenters commented 2 years ago

What is mac_payload? can you show an example of what payload is that you send to kb_kb.inject(payload) ?

jbgod commented 2 years ago

all zigbee packet contain the struct of IEEE 802.15.4: MAC Header, Mac Payload, Mac Footer(FCS) the nwk layer data will be contained in Mac Payload,APS data will be contained inNWK payload show in the packets by ubiqua

image after reveive the beacon back from gateway. end device send a association request to the gateway. In the association request packet,the Mac Payload is b'\x01\x8e'

# association request inject data, you can replace the '\xa2' in the third bytes(seqnum)
#i cut off the MFC
#MAC Header bytes.fromhex('23C8A221380000FFFF8831CAFEFFF9E3B4')
#     frame_control '\x23\xc8'
#     seqnum \xa2
#     destination_pan_id \x21\x38
#     destination_address \x00\x00
#     source_pan_id '\xff\xff'
#     source_address '\x88\x31\xca\xfe\xff\xf9\xe3\xb4'
#Mac Payload  b'\x01\x8e'
#      command Frame ID '\x01'
#      compatibility Information '\x8e'
bytes.fromhex('23C8A221380000FFFF8831CAFEFFF9E3B4018E') 
# all association request packet's mac_payload are the same,so i just use mac_payload in my create_asso_packet() to create association request packet
#other data can picked up from beacon packet,just like what zbstrumbler do
# i learn the core ideas of zbstrumbler and write my code

image after send a association request, end device will send a data request, the Mac Payload is b'\x04' attention, this type of packets will cause error in Dot15d4,packet's data don't have source pan id

# data request packet inject data, you can replace the '\xa3' in the third bytes(seqnum)
#MAC Header
#     frame_control '\x63\xc8'
#     seqnum \xa3
#     destination_pan_id \x21\x38
#     destination_address \x00\x00
#     source_address '\x88\x31\xca\xfe\xff\xf9\xe3\xb4'
#Mac Payload
#      command Frame ID '\x04'
bytes.fromhex('63C8A3213800008831CAFEFFF9E3B404')

after send association request and data request to the zigbee coordinator the coordinator will response a association response,it show the status of joining a pan. image this is association response packlet with success

#association success's mac payload
bytes.fromhex("02B86F00")

image this is assocaiton response packet with deny

#association denied's mac payload
the last byte in mac payload wiil be '\x02' mean associate denied

the steps to sniffer the network key as fllow(my conclusion from end point device behavior's analysis) 1.send beacon request,get the assocaite permit beacon packet, read the information of the coordinator(Pan id, source address, extened pan id)(zbstrumber do like this to sniffer the devices) 2.send a association request with a mac address to alloca a short address in the coordinator's Pan network 3.send a data request packet to coordinator 4.receive a association response packet with associate success from coordinator. and send a acknowlege packet with the seqnum in the association response in a very short time(maybe 0.05 s, i can response in 0.005s now) 5.after step1->4 ,the coordinator will send a transport key packet to tell the endpoint device,the network key of this pan.(transport key packet's decrypt we solved lasted weekend)

now,the core issue for killerbee to pretend as a normal zigbee endpoint device and cheat coordinator's network key is why the association packet sended by killerbee will be denied by coordinator. the autocrc(1) 's result and the makeFCS()'s result is not the same. MAC layer is based on IEEE 802.15.4, it's sucurity is base on FCS's crc check