|
@@ -7,25 +7,111 @@ 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
|
|
|
+the 4-way handshake or group key handshake, take the following steps:
|
|
|
|
|
|
-
|
|
|
+1. Compile our modified hostapd instance. This only needs to be done once.
|
|
|
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
+ cd ../hostapd
|
|
|
+ cp defconfig .config
|
|
|
+ make -j 2
|
|
|
|
|
|
-
|
|
|
+2. The hardware encryption engine of some Wi-Fi NICs have bugs that interfere
|
|
|
+ with our script. So disable hardware encryption by executing:
|
|
|
|
|
|
-
|
|
|
-MSG3_TRANSMIT_INTERVAL = 2
|
|
|
+ ./disable-hwcrypto.sh
|
|
|
+
|
|
|
+ This only needs to be done once. It's recommended to reboot after executing
|
|
|
+ this script. We tested this script with an Intel Dual Band Wireless-AC 7260
|
|
|
+ and a TP-Link TL-WN722N.
|
|
|
+
|
|
|
+3. Execute this script. Accepted parameters are:
|
|
|
+
|
|
|
+ --group Test the group key handshake instead of the 4-way handshake
|
|
|
+ --debug Show more debug messages
|
|
|
+
|
|
|
+ All other supplied arguments are passed on to hostapd.
|
|
|
+ The two examples you will always need are:
|
|
|
+
|
|
|
+ {name}
|
|
|
+ {name} --group
|
|
|
+
|
|
|
+ The first one tests for key reinstallations in the 4-way handshake (see
|
|
|
+ step 5), and the second one for key reinstallations in the group key
|
|
|
+ handshake (see step 6).
|
|
|
+
|
|
|
+4. Connect with the client being tested to the network testnetwork using
|
|
|
+ password abcdefgh.
|
|
|
+
|
|
|
+ Note that you can change these and other settings of the AP by modifying
|
|
|
+ hostapd.conf.
|
|
|
+
|
|
|
+
|
|
|
+5. To test key reinstallations in the 4-way handshake, the script will keep
|
|
|
+ sending encrypted message 3's to the client. To start the script execute:
|
|
|
+
|
|
|
+ {name}
|
|
|
+
|
|
|
+5a. The script monitors traffic sent by the client to see if the pairwise
|
|
|
+ key is being reinstalled. To assure the client is sending enough frames,
|
|
|
+ you can ping the AP: ping 192.168.100.254 .
|
|
|
+
|
|
|
+ If the client is vulnerable, the script will show something like:
|
|
|
+ [19:02:37] 78:31:c1:c4:88:92: IV reuse detected (IV=1, seq=10). Client is vulnerable to pairwise key reinstallations in the 4-way handshake!
|
|
|
+
|
|
|
+ If the client is patched, the script will show (this can take a minute):
|
|
|
+ [18:58:11] 90:18:7c:6e:6b:20: client DOESN'T seem vulnerable to pairwise key reinstallation in the 4-way handshake.
|
|
|
+
|
|
|
+5b. Once the client has requested an IP using DHCP, the script tests for
|
|
|
+ reinstallations of the group key by sending broadcast ARP requests to the
|
|
|
+ client using an already used (replayed) packet number (= IV). The client
|
|
|
+ *must* request an IP using DHCP for this test to start.
|
|
|
+
|
|
|
+ If the client is vulnerable, the script will show something like:
|
|
|
+ [19:03:08] 78:31:c1:c4:88:92: Received 5 unique replies to replayed broadcast ARP requests. Client is vulnerable to group
|
|
|
+ [19:03:08] key reinstallations in the 4-way handshake (or client accepts replayed broadcast frames)!
|
|
|
+
|
|
|
+ If the client is patched, the script will show (this can take a minute):
|
|
|
+ [19:03:08] 78:31:c1:c4:88:92: client DOESN'T seem vulnerable to group key reinstallation in the 4-way handshake handshake.
|
|
|
+
|
|
|
+ Note that this scripts *indirectly* tests for reinstallations of the group
|
|
|
+ key, by testing if replayed broadcast frames are accepted by the client.
|
|
|
+
|
|
|
+
|
|
|
+6. To test key reinstallations in the group key handshake, the script will keep
|
|
|
+ performing new group key handshakes using an identical (static) group key.
|
|
|
+ The client *must* request an IP using DHCP for this test to start. To start
|
|
|
+ the script execute:
|
|
|
+
|
|
|
+ {name} --group
|
|
|
+
|
|
|
+ The working and output of the script is similar to the one of step 5b.
|
|
|
+
|
|
|
+
|
|
|
+7. Some final recommendations:
|
|
|
+
|
|
|
+ 7a. Perform these tests in a room with little interference. A *high* amount
|
|
|
+ of packet loss will make this script unreliable!
|
|
|
+ 7b. Manually inspect network traffic to confirm the output of the script:
|
|
|
+ - Use an extra Wi-Fi NIC in monitor mode to check pairwise key reinstalls
|
|
|
+ by monitoring the IVs of frames sent by the client.
|
|
|
+ - Capture traffic on the client to see if the replayed broadcast ARP
|
|
|
+ requests are accepted or not.
|
|
|
+ 7c. If the client can use multiple Wi-Fi radios/NICs, test using a few
|
|
|
+ different ones.
|
|
|
+"""
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+HANDSHAKE_TRANSMIT_INTERVAL = 2
|
|
|
|
|
|
|
|
|
|
|
@@ -131,6 +217,7 @@ def dot11_get_priority(p):
|
|
|
if not Dot11QoS in p: return 0
|
|
|
return ord(str(p[Dot11QoS])[0])
|
|
|
|
|
|
+
|
|
|
|
|
|
|
|
|
class IvInfo():
|
|
@@ -152,7 +239,8 @@ class ClientState():
|
|
|
self.mac = clientmac
|
|
|
self.TK = None
|
|
|
self.vuln_4way = ClientState.UNKNOWN
|
|
|
- self.vuln_group = ClientState.UNKNOWN
|
|
|
+ self.vuln_group = ClientState.UNKNOWN
|
|
|
+
|
|
|
|
|
|
self.ivs = dict()
|
|
|
self.encdata_prev = None
|
|
@@ -162,7 +250,6 @@ class ClientState():
|
|
|
self.groupkey_grouphs = test_group_hs
|
|
|
|
|
|
def groupkey_reset(self):
|
|
|
-
|
|
|
self.groupkey_state = ClientState.IDLE
|
|
|
self.groupkey_prev_canary_time = 0
|
|
|
self.groupkey_num_canaries = 0
|
|
@@ -175,18 +262,26 @@ class ClientState():
|
|
|
|
|
|
def get_encryption_key(self, hostapd_ctrl):
|
|
|
if self.TK is None:
|
|
|
+
|
|
|
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")
|
|
|
return self.TK
|
|
|
|
|
|
def decrypt(self, p, hostapd_ctrl):
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
payload = str(p.wepdata[4:-4])
|
|
|
llcsnap, packet = payload[:8], payload[8:]
|
|
|
|
|
|
if payload.startswith("\xAA\xAA\x03\x00\x00\x00"):
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
plaintext = payload
|
|
|
else:
|
|
|
client = self.mac
|
|
@@ -216,7 +311,7 @@ class ClientState():
|
|
|
return iv > max(self.ivs.keys())
|
|
|
|
|
|
def check_pairwise_reinstall(self, p):
|
|
|
-
|
|
|
+
|
|
|
if self.is_iv_reused(p):
|
|
|
if self.vuln_4way != ClientState.VULNERABLE:
|
|
|
iv = dot11_get_iv(p)
|
|
@@ -225,13 +320,13 @@ class ClientState():
|
|
|
"Client is vulnerable to pairwise key reinstallations in the 4-way handshake!") % (self.mac, iv, seq), color="green")
|
|
|
self.vuln_4way = ClientState.VULNERABLE
|
|
|
|
|
|
-
|
|
|
+
|
|
|
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 * MSG3_TRANSMIT_INTERVAL + 1 <= p.time:
|
|
|
+ elif self.encdata_prev + 2 * HANDSHAKE_TRANSMIT_INTERVAL + 1 <= p.time:
|
|
|
self.encdata_intervals += 1
|
|
|
self.encdata_prev = p.time
|
|
|
log(DEBUG, "%s: no pairwise IV resets seem to have occured for one interval" % self.mac)
|
|
@@ -271,12 +366,13 @@ class ClientState():
|
|
|
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))
|
|
@@ -284,7 +380,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
|
|
@@ -310,7 +406,6 @@ class KRAckAttackClient():
|
|
|
self.hostapd_ctrl = None
|
|
|
|
|
|
self.dhcp = None
|
|
|
- self.arp = None
|
|
|
self.group_ip = None
|
|
|
self.group_arp = None
|
|
|
|
|
@@ -325,9 +420,6 @@ class KRAckAttackClient():
|
|
|
log(DEBUG, "%s: Removing ClientState object" % clientmac)
|
|
|
|
|
|
def handle_replay(self, p):
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
if not Dot11WEP in p: return
|
|
|
|
|
|
|
|
@@ -335,11 +427,6 @@ class KRAckAttackClient():
|
|
|
header = Ether(dst=self.apmac, src=clientmac)
|
|
|
header.time = p.time
|
|
|
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- payload = str(p.wepdata[4:-4])
|
|
|
-
|
|
|
|
|
|
client = self.clients[clientmac]
|
|
|
plaintext = client.decrypt(p, self.hostapd_ctrl)
|
|
@@ -391,7 +478,6 @@ class KRAckAttackClient():
|
|
|
|
|
|
def process_eth_rx(self, p):
|
|
|
self.dhcp.reply(p)
|
|
|
- self.arp.reply(p)
|
|
|
self.group_arp.reply(p)
|
|
|
|
|
|
clientmac = p[Ether].src
|
|
@@ -435,13 +521,15 @@ class KRAckAttackClient():
|
|
|
self.sock_mon = MitmSocket(type=ETH_P_ALL, iface=self.nic_mon)
|
|
|
self.sock_eth = L2Socket(type=ETH_P_ALL, iface=self.nic_iface)
|
|
|
|
|
|
+
|
|
|
self.dhcp = DHCP_sock(sock=self.sock_eth,
|
|
|
domain='krackattack.com',
|
|
|
pool=Net('192.168.100.0/24'),
|
|
|
network='192.168.100.0/24',
|
|
|
gw='192.168.100.254',
|
|
|
renewal_time=600, lease_time=3600)
|
|
|
- self.arp = ARP_sock(sock=self.sock_eth, IP_addr='192.168.100.254', ARP_addr=self.apmac)
|
|
|
+
|
|
|
+ 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)
|
|
@@ -461,7 +549,7 @@ class KRAckAttackClient():
|
|
|
if self.sock_eth in sel[0]: self.handle_eth_rx()
|
|
|
|
|
|
if time.time() > self.next_arp:
|
|
|
- self.next_arp = time.time() + MSG3_TRANSMIT_INTERVAL
|
|
|
+ self.next_arp = time.time() + HANDSHAKE_TRANSMIT_INTERVAL
|
|
|
for client in self.clients.values():
|
|
|
|
|
|
if client.vuln_group != ClientState.VULNERABLE and client.mac in self.dhcp.leases:
|
|
@@ -515,8 +603,8 @@ def hostapd_read_config(config):
|
|
|
log(ERROR, " >%s<" % line, showtime=False)
|
|
|
quit(1)
|
|
|
|
|
|
-
|
|
|
|
|
|
+
|
|
|
if argv_get_interface() is not None:
|
|
|
interface = argv_get_interface()
|
|
|
|
|
@@ -524,8 +612,7 @@ def hostapd_read_config(config):
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
if "--help" in sys.argv or "-h" in sys.argv:
|
|
|
-
|
|
|
-
|
|
|
+ print USAGE.format(name=sys.argv[0])
|
|
|
quit(1)
|
|
|
|
|
|
test_grouphs = argv_pop_argument("--group")
|