mirror of
https://github.com/vanhoefm/fragattacks.git
synced 2024-11-28 10:18:21 -05:00
fragattacks: fix linux_plain, add rekey-early-install and pn-per-qos
This commit is contained in:
parent
a7c1b406e4
commit
b62e788f1a
@ -2362,6 +2362,7 @@ static int hostapd_ctrl_rekey_ptk(struct hostapd_data *hapd, const char *cmd)
|
||||
{
|
||||
struct sta_info *sta;
|
||||
u8 addr[ETH_ALEN];
|
||||
int early_install = os_strstr(cmd, "early-install") != NULL;
|
||||
|
||||
if (hwaddr_aton(cmd, addr))
|
||||
return -1;
|
||||
@ -2370,8 +2371,9 @@ static int hostapd_ctrl_rekey_ptk(struct hostapd_data *hapd, const char *cmd)
|
||||
if (!sta || !sta->wpa_sm)
|
||||
return -1;
|
||||
|
||||
wpa_printf(MSG_INFO, "TESTING: Reking PTK of " MACSTR, MAC2STR(sta->addr));
|
||||
return wpa_auth_rekey_ptk(sta->wpa_sm);
|
||||
wpa_printf(MSG_INFO, "TESTING: Reking PTK of " MACSTR " early_install=%d",
|
||||
MAC2STR(sta->addr), early_install);
|
||||
return wpa_auth_rekey_ptk(sta->wpa_sm, early_install);
|
||||
}
|
||||
|
||||
|
||||
|
@ -321,15 +321,17 @@ class PingTest(Test):
|
||||
self.actions.insert(i, sep_frag)
|
||||
|
||||
class LinuxTest(Test):
|
||||
def __init__(self, ptype):
|
||||
def __init__(self, ptype, decoy_tid=None):
|
||||
super().__init__([
|
||||
Action(Action.Connected, Action.GetIp), # XXX we don't always want to wait on this?
|
||||
# Note: to inject immediately after 4-way provide IPs using --ip and --peerip
|
||||
Action(Action.Connected, Action.GetIp),
|
||||
Action(Action.Connected, enc=True),
|
||||
Action(Action.Connected, enc=True),
|
||||
Action(Action.Connected, enc=False)
|
||||
])
|
||||
self.ptype = ptype
|
||||
self.check_fn = None
|
||||
self.decoy_tid = decoy_tid
|
||||
|
||||
def check(self, p):
|
||||
if self.check_fn == None:
|
||||
@ -345,8 +347,17 @@ class LinuxTest(Test):
|
||||
|
||||
# Fragment 2: make Linux update latest used crypto Packet Number. Use a dummy packet
|
||||
# that can't accidently aggregate with the first fragment in a corrrect packet.
|
||||
p = station.get_header()/LLC()/SNAP()/IP()/Raw(b"linux_plain decoy fragment")
|
||||
p = station.get_header(prior=2)/LLC()/SNAP()/IP()/Raw(b"linux_plain decoy fragment")
|
||||
p.SC = frag2.SC ^ (1 << 4)
|
||||
|
||||
# - In the attack against Linux, the decoy frame must have the same QoS TID.
|
||||
# - On the other hand, some devices seem to only cache fragments for one sequence
|
||||
# number per QoS priority. So to avoid overwriting the first fragment, add this
|
||||
# option to use a different priority for it.
|
||||
p.TID = 2
|
||||
if self.decoy_tid != None:
|
||||
p.TID = 3
|
||||
|
||||
self.actions[1].frame = p
|
||||
|
||||
# Fragment 3: can now inject last fragment as plaintext
|
||||
@ -458,7 +469,7 @@ class Station():
|
||||
|
||||
# Don't reset PN to have consistency over rekeys and reconnects
|
||||
self.reset_keys()
|
||||
self.pn = 0x100
|
||||
self.pn = [0x100] * 16
|
||||
|
||||
# Contains either the "to-DS" or "from-DS" flag.
|
||||
self.FCfield = Dot11(FCfield=ds_status).FCfield
|
||||
@ -572,8 +583,8 @@ class Station():
|
||||
return header
|
||||
|
||||
def encrypt(self, frame, inc_pn=1, force_key=None):
|
||||
# TODO: Option to use per-QoS transmit replay counters?
|
||||
self.pn += inc_pn
|
||||
idx = dot11_get_priority(frame) if self.options.pn_per_qos else 0
|
||||
self.pn[idx] += 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:
|
||||
@ -581,9 +592,9 @@ class Station():
|
||||
key = b"\x00" * len(key)
|
||||
|
||||
if len(key) == 16:
|
||||
encrypted = encrypt_ccmp(frame, key, self.pn, keyid)
|
||||
encrypted = encrypt_ccmp(frame, key, self.pn[idx], keyid)
|
||||
else:
|
||||
encrypted = encrypt_wep(frame, key, self.pn, keyid)
|
||||
encrypted = encrypt_wep(frame, key, self.pn[idx], keyid)
|
||||
|
||||
return encrypted
|
||||
|
||||
@ -906,7 +917,11 @@ class Authenticator(Daemon):
|
||||
|
||||
def rekey(self, station):
|
||||
log(STATUS, f"Starting PTK rekey with client {station.get_peermac()}", color="green")
|
||||
wpaspy_command(self.wpaspy_ctrl, "REKEY_PTK " + station.get_peermac())
|
||||
cmd = f"REKEY_PTK {station.get_peermac()}"
|
||||
if self.options.rekey_early_install:
|
||||
log(STATUS, "Will install PTK during rekey after sending Msg4")
|
||||
cmd += " early-install"
|
||||
wpaspy_command(self.wpaspy_ctrl, cmd)
|
||||
|
||||
def reconnect(self, station):
|
||||
# Confirmed to *instantly* reconnect: Arch Linux, Windows 10 with Intel WiFi chip, iPad Pro 13.3.1
|
||||
@ -1242,10 +1257,12 @@ def prepare_tests(test_name, stractions, delay=0, inc_pn=0, as_msdu=None, ptype=
|
||||
test = PingTest(REQ_ICMP, actions, as_msdu=as_msdu, bcast=bcast)
|
||||
|
||||
elif test_name == "ping_frag_sep":
|
||||
# Check if we can send frames in between fragments. The seperator uses a different QoS TID.
|
||||
# The second fragment must use an incremental PN compared to the first fragment. So this
|
||||
# also tests if the receivers uses a per-QoS receive replay counter.
|
||||
separator = Dot11(type="Data", subtype=8, SC=(33 << 4) | 0)/Dot11QoS(TID=1)/LLC()/SNAP()
|
||||
# Check if we can send frames in between fragments. The seperator by default uses a different
|
||||
# QoS TID. The second fragment must use an incremental PN compared to the first fragment.
|
||||
# So this also tests if the receivers uses a per-QoS receive replay counter. By overriding
|
||||
# the TID you can check whether fragments are cached for multiple sequence numbers in one TID.
|
||||
tid = 1 if stractions == None else int(stractions)
|
||||
separator = Dot11(type="Data", subtype=8, SC=(33 << 4) | 0)/Dot11QoS(TID=tid)/LLC()/SNAP()
|
||||
test = PingTest(REQ_ICMP,
|
||||
[Action(Action.Connected, action=Action.GetIp),
|
||||
Action(Action.Connected, enc=True),
|
||||
@ -1283,7 +1300,8 @@ def prepare_tests(test_name, stractions, delay=0, inc_pn=0, as_msdu=None, ptype=
|
||||
test = EapolMsduTest(REQ_ICMP, actions)
|
||||
|
||||
elif test_name == "linux_plain":
|
||||
test = LinuxTest(REQ_ICMP)
|
||||
decoy_tid = None if stractions == None else int(stractions)
|
||||
test = LinuxTest(REQ_ICMP, decoy_tid)
|
||||
|
||||
elif test_name == "macos":
|
||||
if stractions != None:
|
||||
@ -1328,7 +1346,7 @@ def prepare_tests(test_name, stractions, delay=0, inc_pn=0, as_msdu=None, ptype=
|
||||
# If requested, override the ptype
|
||||
if ptype != None:
|
||||
if not hasattr(test, "ptype"):
|
||||
log(WARNING, "Cannot override request type of this test.")
|
||||
log(WARNING, "Cannot override request type of the selected test.")
|
||||
quit(1)
|
||||
test.ptype = ptype
|
||||
|
||||
@ -1359,7 +1377,7 @@ def args2msdu(args):
|
||||
|
||||
|
||||
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!")
|
||||
|
||||
parser = argparse.ArgumentParser(description="Test for fragmentation vulnerabilities.")
|
||||
parser.add_argument('iface', help="Interface to use for the tests.")
|
||||
@ -1378,8 +1396,10 @@ if __name__ == "__main__":
|
||||
parser.add_argument('--icmp', default=False, action='store_true', help="Override default request with ICMP ping request.")
|
||||
parser.add_argument('--rekey-request', default=False, action='store_true', help="Actively request PTK rekey as client.")
|
||||
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.")
|
||||
parser.add_argument('--bcast', default=False, action='store_true', help="Send pings using broadcast receiver address (addr1).")
|
||||
parser.add_argument('--pn-per-qos', default=False, action='store_true', help="Use separate Tx packet counter for each QoS TID.")
|
||||
args = parser.parse_args()
|
||||
|
||||
ptype = args2ptype(args)
|
||||
@ -1393,7 +1413,9 @@ if __name__ == "__main__":
|
||||
options.peerip = args.peerip
|
||||
options.rekey_request = args.rekey_request
|
||||
options.rekey_plaintext = args.rekey_plaintext
|
||||
options.rekey_early_install = args.rekey_early_install
|
||||
options.full_reconnect = args.full_reconnect
|
||||
options.pn_per_qos = args.pn_per_qos
|
||||
|
||||
# Parse remaining options
|
||||
global_log_level -= args.debug
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 004cd93c830d9c4f12a0ffc70de267a6f77d465c
|
||||
Subproject commit 95dc6b7e32b63c1e2efa50f2ba60fca1b025dca7
|
@ -892,6 +892,9 @@ static int wpa_receive_error_report(struct wpa_authenticator *wpa_auth,
|
||||
* Error report is not a request for a new key handshake, but since
|
||||
* Authenticator may do it, let's change the keys now anyway.
|
||||
*/
|
||||
#ifdef CONFIG_TESTING_OPTIONS
|
||||
sm->early_install = 0;
|
||||
#endif /* CONFIG_TESTING_OPTIONS */
|
||||
wpa_request_new_ptk(sm);
|
||||
return 0;
|
||||
}
|
||||
@ -1339,6 +1342,9 @@ continue_processing:
|
||||
wpa_auth_logger(wpa_auth, sm->addr, LOGGER_INFO,
|
||||
"received EAPOL-Key Request for new "
|
||||
"4-Way Handshake");
|
||||
#ifdef CONFIG_TESTING_OPTIONS
|
||||
sm->early_install = 0;
|
||||
#endif /* CONFIG_TESTING_OPTIONS */
|
||||
wpa_request_new_ptk(sm);
|
||||
} else if (key_data_length > 0 &&
|
||||
wpa_parse_kde_ies(key_data, key_data_length,
|
||||
@ -3400,6 +3406,23 @@ SM_STATE(WPA_PTK, PTKINITNEGOTIATING)
|
||||
WPA_KEY_INFO_ACK | WPA_KEY_INFO_INSTALL |
|
||||
WPA_KEY_INFO_KEY_TYPE,
|
||||
_rsc, sm->ANonce, kde, pos - kde, 0, encr);
|
||||
|
||||
#ifdef CONFIG_TESTING_OPTIONS
|
||||
if (sm->early_install)
|
||||
{
|
||||
sm->early_install = 0;
|
||||
enum wpa_alg alg = wpa_cipher_to_alg(sm->pairwise);
|
||||
int klen = wpa_cipher_key_len(sm->pairwise);
|
||||
if (wpa_auth_set_key(sm->wpa_auth, 0, alg, sm->addr, 0,
|
||||
sm->PTK.tk, klen,
|
||||
KEY_FLAG_PAIRWISE_RX_TX)) {
|
||||
wpa_sta_disconnect(sm->wpa_auth, sm->addr,
|
||||
WLAN_REASON_PREV_AUTH_NOT_VALID);
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif /* CONFIG_TESTING_OPTIONS */
|
||||
|
||||
done:
|
||||
os_free(kde);
|
||||
os_free(wpa_ie_buf);
|
||||
@ -5279,9 +5302,12 @@ int wpa_auth_rekey_gtk(struct wpa_authenticator *wpa_auth)
|
||||
}
|
||||
|
||||
|
||||
int wpa_auth_rekey_ptk(struct wpa_state_machine *sm)
|
||||
int wpa_auth_rekey_ptk(struct wpa_state_machine *sm, int early_install)
|
||||
{
|
||||
eloop_cancel_timeout(wpa_rekey_ptk, sm->wpa_auth, sm);
|
||||
#ifdef CONFIG_TESTING_OPTIONS
|
||||
sm->early_install = early_install;
|
||||
#endif /* CONFIG_TESTING_OPTIONS */
|
||||
return eloop_register_timeout(0, 0, wpa_rekey_ptk, sm->wpa_auth, sm);
|
||||
}
|
||||
|
||||
|
@ -499,7 +499,7 @@ int wpa_auth_resend_group_m1(struct wpa_state_machine *sm,
|
||||
void (*cb)(void *ctx1, void *ctx2),
|
||||
void *ctx1, void *ctx2);
|
||||
int wpa_auth_rekey_gtk(struct wpa_authenticator *wpa_auth);
|
||||
int wpa_auth_rekey_ptk(struct wpa_state_machine *sm);
|
||||
int wpa_auth_rekey_ptk(struct wpa_state_machine *sm, int early_install);
|
||||
void wpa_auth_set_ptk_rekey_timer(struct wpa_state_machine *sm);
|
||||
|
||||
#endif /* WPA_AUTH_H */
|
||||
|
@ -160,6 +160,7 @@ struct wpa_state_machine {
|
||||
#endif /* CONFIG_DPP2 */
|
||||
|
||||
#ifdef CONFIG_TESTING_OPTIONS
|
||||
int early_install;
|
||||
void (*eapol_status_cb)(void *ctx1, void *ctx2);
|
||||
void *eapol_status_cb_ctx1;
|
||||
void *eapol_status_cb_ctx2;
|
||||
|
Loading…
Reference in New Issue
Block a user