commit 33dff208ed8aa55321d2edd80a4a2e87e7ff4622 Author: derv82 Date: Wed May 27 09:17:09 2015 -0700 Initial commit, basic helper classes created diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b948985 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.swp +*.pyc diff --git a/py/Airmon.py b/py/Airmon.py new file mode 100644 index 0000000..d2612e6 --- /dev/null +++ b/py/Airmon.py @@ -0,0 +1,144 @@ +#!/usr/bin/python + +from Interface import Interface +from Process import Process +from Color import Color + +class Airmon(object): + ''' Wrapper around the 'airmon-ng' program ''' + + def __init__(self): + self.refresh() + + def refresh(self): + ''' Get airmon-recognized interfaces ''' + self.interfaces = Airmon.get_interfaces() + + def print_menu(self): + ''' Prints menu ''' + print Interface.menu_header() + for (index, iface) in enumerate(self.interfaces): + Color.pl(" {G}%d{W}. %s" % (index + 1, iface)) + + def get(self, index): + ''' Gets interface at index (starts at 1) ''' + if type(index) == str: + index = int(index) + return self.interfaces[index - 1] + + + @staticmethod + def get_interfaces(): + ''' + Returns: + List of Interface objects known by airmon-ng + ''' + interfaces = [] + p = Process('airmon-ng') + for line in p.stdout().split('\n'): + # Ignore blank/header lines + if len(line) == 0: continue + if line.startswith('Interface'): continue + if line.startswith('PHY'): continue + + # Strip out interface information + fields = line.split("\t") + while '' in fields: + fields.remove('') + # Add Interface object to list + interfaces.append(Interface(fields)) + return interfaces + + @staticmethod + def start(iface): + ''' + Starts an interface (iface) in monitor mode + Args: + iface - The interface to start in monitor mode + Either an instance of Interface object, + or the name of the interface (string). + Returns: + Name of the interface put into monitor mode. + Throws: + Exception - If an interface can't be put into monitor mode + ''' + # Get interface name from input + if type(iface) == Interface: + iface = iface.name + + # Call airmon-ng + Color.p("{+} enabling {G}monitor mode{W} on {C}%s{W}... " % iface) + (out,err) = Process.call(['airmon-ng', 'start', iface]) + + # Find the interface put into monitor mode (if any) + mon_iface = None + for line in out.split('\n'): + if 'monitor mode' in line and 'enabled' in line and ' on ' in line: + mon_iface = line.split(' on ')[1] + if ']' in mon_iface: + mon_iface = mon_iface.split(']')[1] + if ')' in mon_iface: + mon_iface = mon_iface.split(')')[0] + break + + if mon_iface == None: + # Airmon did not enable monitor mode on an interface + Color.pl("{R}failed{W}") + + mon_ifaces = Airmon.get_interfaces_in_monitor_mode() + + # Assert that there is an interface in monitor mode + if len(mon_ifaces) == 0: + Color.pl("{R}failed{W}") + raise Exception("iwconfig does not see any interfaces in Mode:Monitor") + + # Assert that the interface enabled by airmon-ng is in monitor mode + if mon_iface not in mon_ifaces: + Color.pl("{R}failed{W}") + raise Exception("iwconfig does not see %s in Mode:Monitor" % mon_iface) + + # No errors found; the device 'mon_iface' was put into MM. + Color.pl("{G}enabled {C}%s{W}" % mon_iface) + return mon_iface + + + @staticmethod + def stop(iface): + # TODO (this) + pass + + + @staticmethod + def get_interfaces_in_monitor_mode(): + ''' + Uses 'iwconfig' to find all interfaces in monitor mode + Returns: + List of interface names that are in monitor mode + ''' + interfaces = [] + (out, err) = Process.call("iwconfig") + for line in out.split("\n"): + if len(line) == 0: continue + if line[0] != ' ': + iface = line.split(' ')[0] + if '\t' in iface: + iface = iface.split('\t')[0] + if 'Mode:Monitor' in line and iface not in interfaces: + interfaces.append(iface) + return interfaces + + +if __name__ == '__main__': + print "Interfaces in monitor mode:", + print ','.join(Airmon.get_interfaces_in_monitor_mode()) + print '' + + a = Airmon() + a.print_menu() + count = len(a.interfaces) + question = Color.s("Select interface ({G}1-%d{W}): " % (count)) + choice = raw_input(question) + Color.pl("You chose: {G}%s{W}" % a.get(choice).name) + + #a.start(a.interfaces[0]) + diff --git a/py/Arguments.py b/py/Arguments.py new file mode 100644 index 0000000..fe9f76e --- /dev/null +++ b/py/Arguments.py @@ -0,0 +1,98 @@ +#!/usr/bin/python + +import argparse + +class Arguments(object): + def __init__(self): + self.args = self.get_arguments() + + def get_arguments(self): + description = 'Wrapper script around aircrack-ng and reaver' + description += ' https://github.com/derv82/wifite' + parser = argparse.ArgumentParser( + description=description) + + # Global variables + glob = parser.add_argument_group('SETTINGS') + glob.add_argument('-i', + action='store', + dest='interface', + metavar='interface', + type=str, + help='Wireless interface to use (default: ask)') + glob.add_argument('-c', + action='store', + dest='channel', + metavar='channel', + type=int, + help='Wireless channel to scan (default: all channels)') + + # WEP + wep = parser.add_argument_group('WEP-RELATED') + wep.add_argument('--wep', + action='store_true', + dest='wep_only', + help='Only target WEP-encrypted networks (ignores WPA)') + + # WPA + wep = parser.add_argument_group('WPA-RELATED') + wep.add_argument('--wpa', + action='store_true', + dest='wpa_only', + help='Only target WPA-encrypted networks (ignores WEP)') + + # WPS + wep = parser.add_argument_group('WPS-RELATED') + wep.add_argument('--wps', + action='store_true', + dest='wps_only', + help='Only target WPS-encrypted networks (ignores WEP/nonWPS)') + wep.add_argument('--pixie', + action='store_true', + dest='pixie_only', + help='Only use the WPS Pixie-Dust attack (do not crack PINs)') + + # Cracking + crack = parser.add_argument_group('CRACKING') + crack.add_argument('--cracked', + action='store_true', + dest='cracked', + help='Display previously-cracked access points') + crack.add_argument('--check', + action='store', + metavar='[file]', + dest='check', + help='Check a .cap file for WPA handshakes') + crack.add_argument('--crack-wpa', + action='store', + type=str, + dest='crackwpa', + metavar='[file]', + help='Crack a .cap file containing a WPA handshake') + crack.add_argument('--crack-wep', + action='store', + type=str, + dest='crackwep', + metavar='[file]', + help='Crack a .cap file containing WEP IVS') + crack.add_argument('--dict', + action='store', + type=str, + dest='wordlist', + metavar='[file]', + help='Dictionary/wordlist to use for cracking') + + # Misc + commands = parser.add_argument_group('FUNCTIONS') + commands.add_argument('--update', + action='store_true', + dest='update', + help='Update to latest version of Wifite (on github)') + + return parser.parse_args() + +if __name__ == '__main__': + a = Arguments() + args = a.args + print args + diff --git a/py/CapFile.py b/py/CapFile.py new file mode 100644 index 0000000..1169cdc --- /dev/null +++ b/py/CapFile.py @@ -0,0 +1,19 @@ +#!/usr/bin/python + +class CapFile(object): + """ + Holds data about an access point's .cap file, + including filename, AP's ESSID & BSSID. + """ + def __init__(self, filename, ssid, bssid): + self.filename = filename + self.ssid = ssid + self.bssid = bssid + + def __str__(self): + return self.filename + +if __name__ == '__main__': + c = CapFile("cap-01.cap", "My Router", "AA:BB:CC:DD:EE:FF") + print c + diff --git a/py/Client.py b/py/Client.py new file mode 100644 index 0000000..ed748b9 --- /dev/null +++ b/py/Client.py @@ -0,0 +1,11 @@ + +class Client: + """ + Holds data for a Client (device connected to Access Point/Router) + """ + + def __init__(self, bssid, station, power): + self.bssid = bssid + self.station = station + self.power = power + diff --git a/py/Color.py b/py/Color.py new file mode 100644 index 0000000..8506f1e --- /dev/null +++ b/py/Color.py @@ -0,0 +1,58 @@ +#!/usr/bin/python + +import sys + +class Color(object): + ''' Helper object for easily printing colored text to the terminal. ''' + + # Basic console colors + 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 + } + + # Helper string replacements + replacements = { + '{+}': '{W}[{G}+{W}]', + '{!}': '{W}[{R}!{W}]' + } + + @staticmethod + def p(text): + ''' + Prints text using colored format on same line. + Example: + Color.p("{R}This text is red. {W} This text is white") + ''' + sys.stdout.write(Color.s(text)) + sys.stdout.flush() + + @staticmethod + def pl(text): + ''' + Prints text using colored format with trailing new line. + ''' + Color.p('%s\n' % text) + + @staticmethod + def s(text): + ''' Returns colored string ''' + output = text + for (key,value) in Color.replacements.iteritems(): + output = output.replace(key, value) + for (key,value) in Color.colors.iteritems(): + output = output.replace("{%s}" % key, value) + return output + +if __name__ == '__main__': + Color.pl("{R}Testing{G}One{C}Two{P}Three{W}Done") + print Color.s("{C}Testing{P}String{W}") + Color.pl("{+} Good line") + Color.pl("{!} Danger") + diff --git a/py/Configuration.py b/py/Configuration.py new file mode 100644 index 0000000..7c4a70e --- /dev/null +++ b/py/Configuration.py @@ -0,0 +1,110 @@ +#!/usr/bin/python + +import os + +class Configuration(object): + ''' Stores configuration variables for Wifite. ''' + + def __init__(self): + ''' Sets up default initial configuration values ''' + self.temp_dir = None # Temporary directory + + self.version = 2.00 # Program version + self.tx_power = 0 # Wifi transmit power (0 is default) + self.interface = None + self.target_channel = None # User-defined channel to scan + self.target_essid = None # User-defined AP name + self.target_bssid = None # User-defined AP BSSID + self.pillage = False # "Pillage" mode to attack everything + + # WEP variables + self.wep_only = False # Only attack WEP networks + self.wep_pps = 6000 # Packets per second + self.wep_timeout = 600 # Seconds to wait before failing + # WEP-specific attacks + self.wep_fragment = True + self.wep_caffelatte = True + self.wep_p0841 = True + self.wep_hirte = True + # Number of IVS at which we start cracking + self.wep_crack_at_ivs = 10000 + + # WPA variables + self.wpa_only = False # Only attack WPA networks + self.wpa_deauth_timeout = 10 # Seconds to wait between deauths + self.wpa_attack_timeout = 500 # Seconds to wait before failing + self.wpa_handshake_dir = "hs" # Directory to store handshakes + + # Default dictionary for cracking + self.wordlist = None + wordlists = [ + '/usr/share/wfuzz/wordlist/fuzzdb/wordlists-user-passwd/passwds/phpbb.txt', + '/usr/share/fuzzdb/wordlists-user-passwd/passwds/phpbb.txt' + ] + for wlist in wordlists: + if os.path.exists(wlist): + self.wordlist = wlist + break + + # WPS variables + self.wps_only = False # Only attack WPS networks + self.pixie_only = False # Only use Pixie attack on WPS + self.wps_timeout = 600 # Seconds to wait before failing + self.wps_max_retries = 20 # Retries before failing + + + def load_from_arguments(self, args): + ''' Sets configuration values based on Argument.args object ''' + if args.channel: self.target_channel = args.channel + if args.interface: self.interface = args.interface + if args.wep_only: self.wep_only = args.wep_only + if args.wpa_only: self.wpa_only = args.wpa_only + if args.wps_only: self.wps_only = args.wps_only + if args.pixie_only: self.pixie_only = args.pixie_only + if args.wordlist: self.wordlist = args.wordlist + + + def temp(self): + ''' Creates and/or returns the temporary directory ''' + if self.temp_dir == None: + self.temp_dir = self.create_temp() + return self.temp_dir + + def create_temp(self): + ''' Creates and returns a temporary directory ''' + from tempfile import mkdtemp + tmp = mkdtemp(prefix='wifite') + if not tmp.endswith(os.sep): + tmp += os.sep + return tmp + + def delete_temp(self): + ''' Remove temp files and folder ''' + if self.temp_dir == None: return + if os.path.exists(self.temp_dir): + for f in os.listdir(self.temp_dir): + os.remove(self.temp_dir + f) + os.rmdir(self.temp_dir) + + + def exit_gracefully(self, code=0): + ''' Deletes temp and exist with the given code ''' + self.delete_temp() + exit(code) + + def __str__(self): + ''' (Colorful) string representation of the configuration ''' + from Color import Color + result = Color.s('{W}Wifite Configuration{W}\n') + result += Color.s('{W}--------------------{W}\n') + for (key,val) in sorted(c.__dict__.iteritems()): + result += Color.s("{G}%s{W}:\t{C}%s{W}\n" % (key,val)) + return result + +if __name__ == '__main__': + c = Configuration() + from Arguments import Arguments + a = Arguments() + c.load_from_arguments(a.args) + print c + diff --git a/py/Interface.py b/py/Interface.py new file mode 100644 index 0000000..82aea95 --- /dev/null +++ b/py/Interface.py @@ -0,0 +1,70 @@ +#!/usr/bin/python + +from Color import Color + +class Interface(object): + ''' + Represents an 'interface' known by airmon-ng + ''' + + # Max length of fields. + # Used for printing a table of interfaces. + PHY_LEN = 6 + NAME_LEN = 12 + DRIVER_LEN = 12 + CHIPSET_LEN = 30 + + def __init__(self, fields): + ''' + Initializes & stores info about an interface. + + Args: + Fields - list of fields + 0: PHY + 1: NAME + 2: DRIVER + 3: CHIPSET + ''' + if len(fields) == 3: + fields.insert(0, 'phyX') + if len(fields) != 4: + raise Exception("Expected 4, got %d in %s" % (len(fields), fields)) + self.phy = fields[0].strip() + self.name = fields[1].strip() + self.driver = fields[2].strip() + self.chipset = fields[3].strip() + + def __str__(self): + ''' Colored string representation of interface ''' + s = Color.s("{W}%s" % self.phy) + s += ' ' * max(Interface.PHY_LEN - len(self.phy), 0) + + s += Color.s("{G}%s" % self.name) + s += ' ' * max(Interface.NAME_LEN - len(self.name), 0) + + s += Color.s("{C}%s" % self.driver) + s += ' ' * max(Interface.DRIVER_LEN - len(self.driver), 0) + + s += Color.s("{W}%s" % self.chipset) + s += ' ' * max(Interface.CHIPSET_LEN - len(self.chipset), 0) + return s + + @staticmethod + def menu_header(): + ''' Colored header row for interfaces ''' + s = ' ' + s += 'PHY' + s += ' ' * (Interface.PHY_LEN - len("PHY")) + + s += 'Interface' + s += ' ' * (Interface.NAME_LEN - len("Interface")) + s += 'Driver' + s += ' ' * (Interface.DRIVER_LEN - len("Driver")) + + s += 'Chipset' + s += ' ' * (Interface.CHIPSET_LEN - len("Chipset")) + + s += '\n---' + s += '-' * (Interface.PHY_LEN + Interface.NAME_LEN + Interface.DRIVER_LEN + Interface.CHIPSET_LEN) + return s + diff --git a/py/Process.py b/py/Process.py new file mode 100644 index 0000000..8b9e1b4 --- /dev/null +++ b/py/Process.py @@ -0,0 +1,107 @@ +#!/usr/bin/python + +from subprocess import Popen, call, PIPE + +class Process(object): + ''' Represents a running/ran process ''' + + @staticmethod + def devnull(): + ''' Helper method for opening devnull ''' + return open('/dev/null', 'w') + + @staticmethod + def call(command): + ''' + Calls a command (either string or list of args). + Returns tuple: + (stdout, stderr) + ''' + if type(command) != str or ' ' in command: + shell = True + else: + shell = False + pid = Popen(command, stdout=PIPE, stderr=PIPE, shell=shell) + pid.wait() + return pid.communicate() + + @staticmethod + def exists(program): + ''' Checks if program is installed on this system ''' + p = Process(['which', program]) + if p.stdout().strip() == '' and p.stderr().strip() == '': + return False + return True + + + def __init__(self, command): + ''' Starts executing command ''' + if type(command) == str: + # Commands have to be a list + command = command.split(' ') + self.command = command + self.out = None + self.err = None + self.pid = Popen(command, stdout=PIPE, stderr=PIPE) + + def stdout(self): + ''' Waits for process to finish, returns stdout output ''' + self.get_output() + return self.out + + def stderr(self): + ''' Waits for process to finish, returns stderr output ''' + self.get_output() + return self.err + + def get_output(self): + ''' Waits for process to finish, returns stdout & stderr ''' + if self.pid.poll() == None: + self.pid.wait() + if self.out == None: + (self.out, self.err) = self.pid.communicate() + + def poll(self): + ''' Returns exit code if process is dead, otherwise "None" ''' + return self.pid.poll() + + def kill(self): + ''' Kill current process ''' + if self.pid.poll() == None: + # Process is already killed + return + try: + self.pid.kill() + except OSError, e: + if 'No such process' in e.__str__(): + return + raise e + + def interrupt(self): + ''' Send interrupt to current process ''' + from signal import SIGINT + from os import kill + try: + kill(self.pid.pid, SIGINT) + except OSError, e: + if 'No such process' in e.__str__(): + return + raise e # process cannot be killed + +if __name__ == '__main__': + p = Process('ls') + print p.stdout(), p.stderr() + p.interrupt() + + # Calling as list of arguments + (out,err) = Process.call(['ls', '-lah']) + print out,err + + print '\n---------------------\n' + + # Calling as string + (out,err) = Process.call('ls -l | head -2') + print out,err + + print '"reaver" exists:', Process.exists('reaver') + diff --git a/py/__init__.py b/py/__init__.py new file mode 100644 index 0000000..e69de29