22 Commits

Author SHA1 Message Date
derv82
e0ec0e72b2 Change version to 2.1.0
See https://github.com/derv82/wifite/issues/130 for more info.
2018-04-06 15:55:58 -04:00
derv82
699578abed Allow spaces in target selection (e.g. 3, 4, 7-8) 2018-04-01 15:57:58 -04:00
derv82
57ad097d49 --check: Detect BSSID based on Wifite's handshake filename format. 2018-04-01 15:31:12 -04:00
derv82
a4110b4cf9 Put quotes around Tshark filter ... otherwise tshark locks up 2018-04-01 15:23:56 -04:00
derv82
e8c0843bdf Use stdbuf only if it exists 2018-04-01 01:09:57 -04:00
derv82
5db801b414 Cleaning up handshake code 2018-04-01 01:03:10 -04:00
derv82
2cd6116a8c Move Pyrit logic to tools/pyrit.py 2018-04-01 00:55:41 -04:00
derv82
a2dbf4c382 Moving Tshark logic into /tools/tshark.py
Added tests for analyzing handshakes
2018-04-01 00:37:28 -04:00
derv82
acc8e296d5 Dim the [+] box colors 2018-03-31 23:59:50 -04:00
derv82
72382cf381 Updating README for latest developments. 2018-03-31 23:36:53 -04:00
derv82
3eddcaa59f Support for Python3
That was fun.
2018-03-31 23:02:33 -04:00
derv82
1ad17472b2 Tshark is optional, falls-back to Wash for WPS-detection.
Should resolve #77
2018-03-31 18:57:11 -04:00
derv82
528741f89f Fix bully: --force when deriving PSK from PIN 2018-03-31 18:40:04 -04:00
derv82
909b10e517 Make tshark non-required.
* WPS-detection is not required for non-WPS attacks.
* Handshake analysis can be done using aircrack, cowpatty, pyrit, etc.
2018-03-31 18:05:47 -04:00
derv82
b0bd57b1a3 Improve messaging during chopchop & fragment attacks
Bugfix when chopchop fails.
Displays aircrack's error message when chopchop fails.

For improving WEP attacks in #27
2018-03-25 16:03:20 -04:00
derv82
d8da6798de Update README. Add ideas to TODO after reading the first 20 issues on wifite v1 :( 2018-03-25 04:48:55 -04:00
derv82
19c38bd06c Fetch PSK when reaver fails to find it using Bully
Also changed reaver --timeout from 10 seconds (default) to 4 seconds.

Resolves #76
2018-03-24 15:02:11 -04:00
derv82
a488cf86f1 Bugfix: Don't infinitely loop while calculating Hex & ASCII key from WEP attack.
* Simplified HEX/ASCII conversion. Avoids infinite loop 🤔
* Added integration test: python -m wifite.tools.aircrack

Should resolve "hanging" issues during WEP attacks such as #27.
2018-03-24 14:10:48 -04:00
derv82
34d6b69b48 Fixing whitespace (tabs) on 'baddriver' methods 2018-03-17 17:42:44 -04:00
derv82
ad0265cd92 Merge branch 'refactor' 2018-03-17 17:30:11 -04:00
derv
98e1eef3a8 Merge pull request #74 from schoonc/patch-1
Update Dockerfile
2018-03-23 23:20:53 -07:00
schoonc
641dba0e63 Update Dockerfile 2018-03-18 12:45:50 +03:00
28 changed files with 769 additions and 394 deletions

View File

@@ -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

105
README.md
View File

@@ -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?
-----------
* 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
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.
Full Feature List
-----------------
* Reaver Pixie-Dust attack (`--pixie`)
* Reaver WPS PIN attack (`--reaver`)
* WPA handshake capture (`--no-reaver`)
* 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 <channel>` option)
* Provides commands to crack captured WPA handshakes (via `--crack` command)
Brief Feature List
------------------
Support
-------
Wifite2 is designed entirely for the latest version of Kali Rolling release (tested on Kali 2016.2, updated May 2017).
* 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 <channel>` 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`.
This means only the latest versions of these programs are supported: Aircrack-ng suite, reaver, tshark, cowpatty.
Linux Distribution Support
--------------------------
Other pen-testing distributions (such as BackBox) have outdated versions of these suites; these distributions are not supported.
Wifite2 is designed specifically for the latest version of **Kali**'s rolling release (tested on Kali 2017.2, updated Jan 2018).
Other pen-testing distributions (such as BackBox) have outdated versions of the tools used by Wifite; 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)

46
TODO.md
View File

@@ -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":

Binary file not shown.

View File

@@ -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 @@ class Arguments(object):
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))

View File

@@ -8,6 +8,7 @@ from ..tools.aircrack import Aircrack
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
@@ -85,6 +86,10 @@ class AttackWEP(Attack):
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:
@@ -94,12 +99,7 @@ class AttackWEP(Attack):
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 +109,7 @@ class AttackWEP(Attack):
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 +142,7 @@ class AttackWEP(Attack):
# 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)
@@ -162,7 +159,7 @@ class AttackWEP(Attack):
# 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
@@ -187,8 +184,8 @@ class AttackWEP(Attack):
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)

View File

@@ -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
@@ -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):
@@ -108,7 +108,7 @@ class Configuration(object):
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 @@ class Configuration(object):
@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 @@ class Configuration(object):
''' 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 @@ class Configuration(object):
@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 @@ class Configuration(object):
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 @@ class Configuration(object):
if __name__ == '__main__':
Configuration.initialize(False)
print Configuration.dump()
print(Configuration.dump())

View File

@@ -30,7 +30,7 @@ class Client(object):
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 @@ class Client(object):
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)

View File

@@ -3,9 +3,10 @@
from ..util.process import Process
from ..util.color import Color
from ..tools.tshark import Tshark
from ..tools.pyrit import Pyrit
import re
import os
import re, os
class Handshake(object):
def __init__(self, capfile, bssid=None, essid=None):
@@ -18,15 +19,21 @@ class Handshake(object):
Tries to find BSSID and ESSID from cap file.
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 = 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 +41,8 @@ class Handshake(object):
# 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,142 +64,20 @@ class Handshake(object):
if not self.bssid or not self.essid:
self.divine_bssid_and_essid()
if len(self.tshark_handshakes()) > 0:
return True
if len(self.tshark_handshakes()) > 0: return True
if len(self.pyrit_handshakes()) > 0: return True
if len(self.pyrit_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
# 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
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'):
raise Exception('tshark is required to find ESSID')
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
]
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.iteritems():
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):
@@ -219,83 +102,33 @@ class Handshake(object):
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 []
''' 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)
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:
# This AccessPoint is not what we're looking for
hit_Target = False
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]
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()
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):
@@ -316,8 +149,7 @@ class Handshake(object):
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)
@@ -335,32 +167,41 @@ class Handshake(object):
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__':
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("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())

View File

@@ -98,4 +98,4 @@ class Interface(object):
if __name__ == '__main__':
mac = Interface.get_mac()
print 'wlan0mon mac address:', mac
print('wlan0mon mac address:', mac)

View File

@@ -31,7 +31,7 @@ class CrackResult(object):
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:

View File

@@ -147,5 +147,5 @@ if __name__ == '__main__':
t = Target(fields)
t.clients.append("asdf")
t.clients.append("asdf")
print t.to_str()
print(t.to_str())

View File

@@ -45,8 +45,8 @@ if __name__ == '__main__':
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'])

View File

@@ -2,6 +2,7 @@
# -*- coding: utf-8 -*-
from ..util.process import Process
from ..util.input import xrange
from ..config import Configuration
import os
@@ -39,44 +40,59 @@ class Aircrack(object):
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]
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)
# 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
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)

View File

@@ -29,7 +29,7 @@ class WEPAttackType(object):
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 @@ class WEPAttackType(object):
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
@@ -90,17 +90,25 @@ class Aireplay(Thread):
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 @@ class Aireplay(Thread):
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 @@ class Aireplay(Thread):
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
@@ -378,12 +438,12 @@ class Aireplay(Thread):
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(',')
@@ -395,13 +455,13 @@ if __name__ == '__main__':
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)
'''

View File

@@ -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 @@ class Airmon(object):
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))
@@ -114,6 +115,7 @@ class Airmon(object):
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}")

View File

@@ -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 @@ class Airodump(object):
# Check targets for WPS
if not self.skip_wps:
capfile = csv_filename[:-3] + 'cap'
try:
Tshark.check_for_wps_and_update_targets(capfile, targets)
except Exception as 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
@@ -175,8 +180,13 @@ class Airodump(object):
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

View File

@@ -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
@@ -26,8 +26,14 @@ class Bully(Attack):
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 @@ class Bully(Attack):
"-v", "4",
"--pixiewps",
Configuration.interface
]
])
self.bully_proc = None
@@ -214,7 +220,49 @@ class Bully(Attack):
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',
'--force',
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 +270,4 @@ if __name__ == '__main__':
target = Target(fields)
b = Bully(target)
b.parse_line(stdout)
'''

59
wifite/tools/pyrit.py Normal file
View File

@@ -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)

View File

@@ -6,6 +6,7 @@ from ..config import Configuration
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 @@ class Reaver(Attack):
'--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,8 +82,16 @@ class Reaver(Attack):
# 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)
@@ -258,7 +268,7 @@ executing pixiewps -e d0141b15656e96b85fcead2e8e76330d2b1ac1576bb026e7a328c0e1ba
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

View File

@@ -10,6 +10,140 @@ class Tshark(object):
def __init__(self):
pass
@staticmethod
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
'-n', # Don't resolve addresses
# Extract beacon frames
'-Y', '"wlan.fc.type_subtype == 0x08 || wlan.fc.type_subtype == 0x05"',
]
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):
'''
@@ -21,9 +155,9 @@ class Tshark(object):
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 +172,6 @@ class Tshark(object):
]
p = Process(command)
try:
p.wait()
lines = p.stdout()
@@ -63,6 +196,7 @@ 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
@@ -79,6 +213,8 @@ if __name__ == '__main__':
# 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
'''
print(Tshark.bssids_with_handshakes(test_file, bssid=target_bssid))

75
wifite/tools/wash.py Normal file
View File

@@ -0,0 +1,75 @@
#!/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

View File

@@ -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}]'
}
@@ -63,9 +63,9 @@ class Color(object):
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 @@ class Color(object):
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")

View File

@@ -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

17
wifite/util/input.py Normal file
View File

@@ -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

View File

@@ -36,9 +36,14 @@ class Process(object):
pid.wait()
(stdout, stderr) = pid.communicate()
if Configuration.verbose > 1 and stdout.strip() != '':
# 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.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 +96,14 @@ class Process(object):
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
@@ -114,6 +119,13 @@ class Process(object):
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 @@ class Process(object):
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 @@ class Process(object):
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')

View File

@@ -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 @@ class Scanner(object):
try:
self.targets = airodump.get_targets()
except Exception, e:
except Exception as e:
break
if self.found_target():
@@ -198,7 +199,8 @@ class Scanner(object):
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:
@@ -219,7 +221,7 @@ if __name__ == '__main__':
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:

View File

@@ -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
@@ -47,8 +48,8 @@ class Wifite(object):
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
@@ -95,7 +96,7 @@ class Wifite(object):
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 @@ class Wifite(object):
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: