mirror of
https://github.com/vanhoefm/fragattacks.git
synced 2024-11-28 18:28:23 -05:00
170 lines
4.2 KiB
Python
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
|
||
|
|