fragattacks/research/libwifi/crypto.py
Mathy Vanhoef 7f93c1cec7 fragattacks: directly track libwifi and not as submodule
This will make it easier for users to clone the repository and will
assure that they always use the correct version of libwifi.
2021-05-08 19:35:48 +04:00

170 lines
4.2 KiB
Python

#!/usr/bin/env python3
import struct, binascii
from .wifi import *
#from binascii import a2b_hex
#from struct import unpack,pack
from Crypto.Cipher import AES, ARC4
from scapy.layers.dot11 import Dot11, Dot11CCMP, Dot11QoS
import zlib
def pn2bytes(pn):
pn_bytes = [0] * 6
for i in range(6):
pn_bytes[i] = pn & 0xFF
pn >>= 8
return pn_bytes
def pn2bin(pn):
return struct.pack(">Q", pn)[2:]
def dot11ccmp_get_pn(p):
pn = p.PN5
pn = (pn << 8) | p.PN4
pn = (pn << 8) | p.PN3
pn = (pn << 8) | p.PN2
pn = (pn << 8) | p.PN1
pn = (pn << 8) | p.PN0
return pn
def ccmp_get_nonce(priority, addr, pn):
return struct.pack("B", priority) + addr2bin(addr) + pn2bin(pn)
def ccmp_get_aad(p, amsdu_spp=False):
# FC field with masked values
fc = raw(p)[:2]
fc = struct.pack("<BB", fc[0] & 0x8f, fc[1] & 0xc7)
# Sequence number is masked, but fragment number is included
sc = struct.pack("<H", p.SC & 0xf)
addr1 = addr2bin(p.addr1)
addr2 = addr2bin(p.addr2)
addr3 = addr2bin(p.addr3)
aad = fc + addr1 + addr2 + addr3 + sc
if Dot11QoS in p:
if not amsdu_spp:
# Everything except the TID is masked
aad += struct.pack("<H", p[Dot11QoS].TID)
else:
# TODO: Mask unrelated fields
aad += raw(p[Dot11QoS])[:2]
return aad
def Raw(x):
return x
def encrypt_ccmp(p, tk, pn, keyid=0, amsdu_spp=False):
"""Takes a plaintext Dot11 frame, encrypts it, and adds all the necessairy headers"""
# Update the FC field
p = p.copy()
p.FCfield |= Dot11(FCfield="protected").FCfield
if Dot11QoS in p:
payload = raw(p[Dot11QoS].payload)
p[Dot11QoS].remove_payload()
# Explicitly set TID so we can assume it's an integer
if p[Dot11QoS].TID == None:
p[Dot11QoS].TID = 0
priority = p[Dot11QoS].TID
else:
payload = raw(p.payload)
p.remove_payload()
priority = 0
# Add the CCMP header. res0 and res1 are by default set to zero.
newp = p/Dot11CCMP()
pn_bytes = pn2bytes(pn)
newp.PN0, newp.PN1, newp.PN2, newp.PN3, newp.PN4, newp.PN5 = pn_bytes
newp.key_id = keyid
newp.ext_iv = 1
# Generate the CCMP Header and AAD for encryption.
ccm_nonce = ccmp_get_nonce(priority, newp.addr2, pn)
ccm_aad = ccmp_get_aad(newp, amsdu_spp)
#print("CCM Nonce:", ccm_nonce.hex())
#print("CCM aad :", ccm_aad.hex())
# Encrypt the plaintext using AES in CCM Mode.
#print("Payload:", payload.hex())
cipher = AES.new(tk, AES.MODE_CCM, ccm_nonce, mac_len=8)
cipher.update(ccm_aad)
ciphertext = cipher.encrypt(payload)
digest = cipher.digest()
newp = newp/Raw(ciphertext)
newp = newp/Raw(digest)
#print("Ciphertext:", ciphertext.hex())
#print(repr(newp))
#print(raw(newp).hex())
return newp
def decrypt_ccmp(p, tk, verify=True):
"""Takes a Dot11CCMP frame and decrypts it"""
p = p.copy()
# Get used CCMP parameters
keyid = p.key_id
priority = dot11_get_priority(p)
pn = dot11ccmp_get_pn(p)
# TODO: Mask flags in p.FCfield that are not part of the AAD
fc = p.FCfield
payload = get_ccmp_payload(p)
if Dot11QoS in p:
p[Dot11QoS].remove_payload()
else:
p.remove_payload()
# Prepare for CCMP decryption
ccm_nonce = ccmp_get_nonce(priority, p.addr2, pn)
ccm_aad = ccmp_get_aad(p)
# Decrypt using AES in CCM Mode.
cipher = AES.new(tk, AES.MODE_CCM, ccm_nonce, mac_len=8)
cipher.update(ccm_aad)
plaintext = cipher.decrypt(payload[:-8])
try:
if verify:
cipher.verify(payload[-8:])
except ValueError:
return None
return p/LLC(plaintext)
def encrypt_wep(p, key, pn, keyid=0):
"""Takes a plaintext Dot11 frame, encrypts it, and adds all the necessairy headers"""
# Update the FC field --- XXX share this with encrypt_ccmp
p = p.copy()
p.FCfield |= Dot11(FCfield="protected").FCfield
if Dot11QoS in p:
payload = raw(p[Dot11QoS].payload)
p[Dot11QoS].remove_payload()
# Explicitly set TID so we can assume it's an integer
if p[Dot11QoS].TID == None:
p[Dot11QoS].TID = 0
priority = p[Dot11QoS].TID
else:
payload = raw(p.payload)
p.remove_payload()
priority = 0
# Add the WEP ICV which will be encrypted
payload += struct.pack("<I", zlib.crc32(payload) & 0xffffffff)
iv = struct.pack(">I", pn)[1:]
cipher = ARC4.new(iv + key)
ciphertext = cipher.encrypt(payload)
# Construct packet ourselves to avoid scapy bugs
newp = p/iv/struct.pack("<B", keyid)/ciphertext
return newp