|
@@ -7,8 +7,6 @@ from datetime import datetime
|
|
|
from wpaspy import Ctrl
|
|
|
from Cryptodome.Cipher import AES
|
|
|
|
|
|
-
|
|
|
-
|
|
|
USAGE = """{name} - Tool to test Key Reinstallation Attacks against clients
|
|
|
|
|
|
To test wheter a client is vulnerable to Key Reinstallation Attack against
|
|
@@ -108,7 +106,7 @@ the 4-way handshake or group key handshake, take the following steps:
|
|
|
|
|
|
|
|
|
|
|
|
-
|
|
|
+
|
|
|
|
|
|
|
|
|
HANDSHAKE_TRANSMIT_INTERVAL = 2
|
|
@@ -227,6 +225,7 @@ class IvInfo():
|
|
|
self.time = p.time
|
|
|
|
|
|
def is_reused(self, p):
|
|
|
+ """Check if frame p reuses an IV and is not a retransmitted frame"""
|
|
|
iv = dot11_get_iv(p)
|
|
|
seq = dot11_get_seqnum(p)
|
|
|
return self.iv == iv and self.seq != seq and p.time >= self.time + 1
|
|
@@ -240,11 +239,11 @@ class ClientState():
|
|
|
self.TK = None
|
|
|
self.vuln_4way = ClientState.UNKNOWN
|
|
|
self.vuln_group = ClientState.UNKNOWN
|
|
|
-
|
|
|
+
|
|
|
|
|
|
- self.ivs = dict()
|
|
|
- self.encdata_prev = None
|
|
|
- self.encdata_intervals = 0
|
|
|
+ self.ivs = dict()
|
|
|
+ self.pairkey_sent_time_prev_iv = None
|
|
|
+ self.pairkey_intervals_no_iv_reuse = 0
|
|
|
|
|
|
self.groupkey_reset()
|
|
|
self.groupkey_grouphs = test_group_hs
|
|
@@ -265,7 +264,7 @@ class ClientState():
|
|
|
|
|
|
while hostapd_ctrl.pending():
|
|
|
hostapd_ctrl.recv()
|
|
|
-
|
|
|
+
|
|
|
response = hostapd_ctrl.request("GET_TK " + self.mac)
|
|
|
if not "FAIL" in response:
|
|
|
self.TK = response.strip().decode("hex")
|
|
@@ -300,7 +299,7 @@ class ClientState():
|
|
|
self.ivs[iv] = IvInfo(p)
|
|
|
|
|
|
def is_iv_reused(self, p):
|
|
|
- """Returns True if this is an *observed* IV reuse"""
|
|
|
+ """Returns True if this is an *observed* IV reuse and not just a retransmission"""
|
|
|
iv = dot11_get_iv(p)
|
|
|
return iv in self.ivs and self.ivs[iv].is_reused(p)
|
|
|
|
|
@@ -311,7 +310,9 @@ class ClientState():
|
|
|
return iv > max(self.ivs.keys())
|
|
|
|
|
|
def check_pairwise_reinstall(self, p):
|
|
|
-
|
|
|
+ """Inspect whether the IV is reused, or whether the client seem to be patched"""
|
|
|
+
|
|
|
+
|
|
|
if self.is_iv_reused(p):
|
|
|
if self.vuln_4way != ClientState.VULNERABLE:
|
|
|
iv = dot11_get_iv(p)
|
|
@@ -324,26 +325,31 @@ class ClientState():
|
|
|
elif self.vuln_4way == ClientState.UNKNOWN and self.is_new_iv(p):
|
|
|
|
|
|
|
|
|
- if self.encdata_prev is None:
|
|
|
- self.encdata_prev = p.time
|
|
|
- elif self.encdata_prev + 2 * HANDSHAKE_TRANSMIT_INTERVAL + 1 <= p.time:
|
|
|
- self.encdata_intervals += 1
|
|
|
- self.encdata_prev = p.time
|
|
|
+ if self.pairkey_sent_time_prev_iv is None:
|
|
|
+ self.pairkey_sent_time_prev_iv = p.time
|
|
|
+ elif self.pairkey_sent_time_prev_iv + 2 * HANDSHAKE_TRANSMIT_INTERVAL + 1 <= p.time:
|
|
|
+ self.pairkey_intervals_no_iv_reuse += 1
|
|
|
+ self.pairkey_sent_time_prev_iv = p.time
|
|
|
log(DEBUG, "%s: no pairwise IV resets seem to have occured for one interval" % self.mac)
|
|
|
|
|
|
-
|
|
|
-
|
|
|
- if self.encdata_intervals >= 5 and self.vuln_4way == ClientState.UNKNOWN:
|
|
|
+
|
|
|
+
|
|
|
+ if self.pairkey_intervals_no_iv_reuse >= 5 and self.vuln_4way == ClientState.UNKNOWN:
|
|
|
self.vuln_4way = ClientState.PATCHED
|
|
|
log(INFO, "%s: client DOESN'T seem vulnerable to pairwise key reinstallation in the 4-way handshake." % self.mac, color="green")
|
|
|
|
|
|
def groupkey_handle_canary(self, p):
|
|
|
+ """Handle replies to the replayed ARP broadcast request (which reuses an IV)"""
|
|
|
+
|
|
|
+
|
|
|
if not self.groupkey_state in [ClientState.STARTED, ClientState.GOT_CANARY]: return
|
|
|
if self.groupkey_prev_canary_time + 1 > p.time: return
|
|
|
|
|
|
self.groupkey_num_canaries += 1
|
|
|
- log(DEBUG, "%s: received broadcast ARP replay number %d\n" % (self.mac, self.groupkey_num_canaries))
|
|
|
+ log(DEBUG, "%s: received %d replies to the replayed broadcast ARP requests\n" % (self.mac, self.groupkey_num_canaries))
|
|
|
|
|
|
+
|
|
|
+
|
|
|
if self.groupkey_num_canaries >= 5:
|
|
|
assert self.vuln_group != ClientState.VULNERABLE
|
|
|
log(INFO, "%s: Received %d unique replies to replayed broadcast ARP requests. Client is vulnerable to group" \
|
|
@@ -353,26 +359,30 @@ class ClientState():
|
|
|
self.vuln_group = ClientState.VULNERABLE
|
|
|
self.groupkey_state = ClientState.FINISHED
|
|
|
|
|
|
+
|
|
|
else:
|
|
|
self.groupkey_state = ClientState.GOT_CANARY
|
|
|
|
|
|
self.groupkey_prev_canary_time = p.time
|
|
|
|
|
|
def groupkey_track_request(self):
|
|
|
+ """Track when we went broadcast ARP requests, and determine if a client seems patched"""
|
|
|
+
|
|
|
if self.vuln_group != ClientState.UNKNOWN: return
|
|
|
hstype = "group key" if self.groupkey_grouphs else "4-way"
|
|
|
|
|
|
+
|
|
|
if self.groupkey_state == ClientState.IDLE:
|
|
|
log(STATUS, "%s: client has IP address -> testing for group key reinstallation in the %s handshake" % (self.mac, hstype))
|
|
|
self.groupkey_state = ClientState.STARTED
|
|
|
|
|
|
if self.groupkey_requests_sent == 3:
|
|
|
-
|
|
|
+
|
|
|
if self.groupkey_state == ClientState.GOT_CANARY:
|
|
|
log(DEBUG, "%s: got a reply to broadcast ARP during this interval" % self.mac)
|
|
|
self.groupkey_state = ClientState.STARTED
|
|
|
|
|
|
-
|
|
|
+
|
|
|
elif self.groupkey_state == ClientState.STARTED:
|
|
|
self.groupkey_patched_intervals += 1
|
|
|
log(DEBUG, "%s: no group IV resets seem to have occured for %d interval(s)" % (self.mac, self.groupkey_patched_intervals))
|
|
@@ -380,7 +390,7 @@ class ClientState():
|
|
|
|
|
|
self.groupkey_requests_sent = 0
|
|
|
|
|
|
-
|
|
|
+
|
|
|
if self.groupkey_patched_intervals >= 5 and self.vuln_group == ClientState.UNKNOWN:
|
|
|
log(INFO, "%s: client DOESN'T seem vulnerable to group key reinstallation in the %s handshake." % (self.mac, hstype), color="green")
|
|
|
self.vuln_group = ClientState.PATCHED
|
|
@@ -420,6 +430,8 @@ class KRAckAttackClient():
|
|
|
log(DEBUG, "%s: Removing ClientState object" % clientmac)
|
|
|
|
|
|
def handle_replay(self, p):
|
|
|
+ """Replayed frames (caused by a pairwise key reinstallation) are rejected by the kernel.
|
|
|
+ This process these frames manually so we can still test reinstallations of the group key."""
|
|
|
if not Dot11WEP in p: return
|
|
|
|
|
|
|
|
@@ -452,16 +464,19 @@ class KRAckAttackClient():
|
|
|
if p == None: return
|
|
|
if p.type == 1: return
|
|
|
|
|
|
-
|
|
|
-
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
|
|
|
|
|
|
clientmac, apmac = (p.addr1, p.addr2) if (p.FCfield & 2) != 0 else (p.addr2, p.addr1)
|
|
|
if apmac != self.apmac: return None
|
|
|
|
|
|
+
|
|
|
if Dot11Deauth in p or Dot11Disas in p:
|
|
|
self.reset_client_info(clientmac)
|
|
|
|
|
|
+
|
|
|
elif p.addr1 == self.apmac and Dot11WEP in p:
|
|
|
if not clientmac in self.clients:
|
|
|
self.clients[clientmac] = ClientState(clientmac, test_group_hs=self.test_grouphs)
|
|
@@ -531,23 +546,25 @@ class KRAckAttackClient():
|
|
|
|
|
|
subprocess.check_output(["ifconfig", self.nic_iface, "192.168.100.254"])
|
|
|
|
|
|
+
|
|
|
self.group_ip = self.dhcp.pool.pop()
|
|
|
self.group_arp = ARP_sock(sock=self.sock_eth, IP_addr=self.group_ip, ARP_addr=self.apmac)
|
|
|
|
|
|
-
|
|
|
+
|
|
|
if test_grouphs:
|
|
|
self.hostapd_ctrl.request("START_GROUP_TESTS")
|
|
|
self.test_grouphs = True
|
|
|
|
|
|
log(STATUS, "Ready. Connect to this Access Point to start the tests. Make sure the client requests an IP using DHCP!", color="green")
|
|
|
|
|
|
-
|
|
|
+
|
|
|
self.next_arp = time.time() + 1
|
|
|
while True:
|
|
|
sel = select.select([self.sock_mon, self.sock_eth], [], [], 1)
|
|
|
if self.sock_mon in sel[0]: self.handle_mon_rx()
|
|
|
if self.sock_eth in sel[0]: self.handle_eth_rx()
|
|
|
|
|
|
+
|
|
|
if time.time() > self.next_arp:
|
|
|
self.next_arp = time.time() + HANDSHAKE_TRANSMIT_INTERVAL
|
|
|
for client in self.clients.values():
|