diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..23cb790
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ {description}
+ Copyright (C) {year} {fullname}
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ {signature of Ty Coon}, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..63d0b2a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,42 @@
+Wifite 2
+========
+A complete re-write of [`wifite`](https://github.com/derv82/wifite), a Python script for auditing wireless networks.
+
+What's new?
+-----------
+* Cleaner process management -- No longer leaves processes running in the background.
+* UX: Target access points are refreshed every second instead of every 5 seconds.
+* UX: Displays realtime Power level (in db) of currently-attacked target
+
+What's not new?
+---------------
+* Backwards compatibility with the original `wifite`'s arguments.
+* Same text-based interface everyone knows and loves.
+
+Full Feature List
+-----------------
+* Reaver Pixie-Dust attack (`--pixie`)
+* Reaver WPS PIN attack (`--reaver`)
+* WPA handshake capture (`--no-reaver`)
+* 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.
+
+Support
+-------
+Wifite2 is designed entirely for the latest version of Kali Rolling release (tested on Kali 2016.2, updated May 2017).
+
+This means only the latest versions of these programs are supported: Aircrack-ng suite, wash, reaver, tshark, cowpatty.
+
+Other pen-testing distributions (such as BackBox) have outdated versions of these suites; these distributions are not supported.
+
+Screenshots
+-----------
+
+Decloaking & cracking a hidden access point (via the WPA Handshake attack):
+
+
+-------------
+
+Cracking a weak WEP password (using the WEP Replay attack):
+
diff --git a/Wifite.py b/Wifite.py
index 287c1b7..065f72e 100755
--- a/Wifite.py
+++ b/Wifite.py
@@ -9,6 +9,7 @@ from py.AttackWPA import AttackWPA
from py.AttackWPS import AttackWPS
from py.CrackResult import CrackResult
from py.Handshake import Handshake
+from py.CrackHandshake import CrackHandshake
from json import loads
import os
@@ -30,6 +31,8 @@ class Wifite(object):
elif Configuration.check_handshake:
self.check_handshake(Configuration.check_handshake)
+ elif Configuration.crack_handshake:
+ CrackHandshake()
else:
Configuration.get_interface()
self.run()
@@ -70,7 +73,6 @@ class Wifite(object):
hs.analyze()
Color.pl('')
-
def run(self):
'''
Main program.
@@ -172,7 +174,7 @@ if __name__ == '__main__':
w.main()
except Exception, e:
Color.pl('\n{!} {R}Error:{O} %s{W}' % str(e))
- if Configuration.verbose > 0:
+ if Configuration.verbose > 0 or True:
Color.pl('\n{!} {O}Full stack trace below')
from traceback import format_exc
Color.p('\n{!} ')
diff --git a/py/Aireplay.py b/py/Aireplay.py
index b1fdda0..b955d4b 100644
--- a/py/Aireplay.py
+++ b/py/Aireplay.py
@@ -108,7 +108,7 @@ class Aireplay(object):
Configuration.initialize()
if Configuration.interface == None:
raise Exception("Wireless interface must be defined (-i)")
-
+
cmd = ['aireplay-ng']
cmd.append('--ignore-negative-one')
@@ -231,7 +231,7 @@ class Aireplay(object):
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
diff --git a/py/Airodump.py b/py/Airodump.py
index 31deb2a..277742f 100644
--- a/py/Airodump.py
+++ b/py/Airodump.py
@@ -7,7 +7,7 @@ from Target import Target
from Client import Client
from Wash import Wash
-import os
+import os, time
class Airodump(object):
''' Wrapper around airodump-ng program '''
@@ -42,6 +42,11 @@ class Airodump(object):
self.ivs_only = ivs_only
self.skip_wash = skip_wash
+ # For tracking decloaked APs (previously were hidden)
+ self.decloaking = False
+ self.decloaked_targets = []
+ self.decloaked_times = {} # Map of BSSID(str) -> epoch(int) of last deauth
+
def __enter__(self):
'''
@@ -58,12 +63,13 @@ class Airodump(object):
'airodump-ng',
self.interface,
'-a', # Only show associated clients
- '-w', self.csv_file_prefix # Output file prefix
+ '-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', 'abg'])
+ command.extend(['--band', 'a'])
if self.encryption:
command.extend(['--enc', self.encryption])
@@ -145,7 +151,15 @@ class Airodump(object):
# Sort by power
targets.sort(key=lambda x: x.power, reverse=True)
+ for old_target in self.targets:
+ for new_target in targets:
+ 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)
+
self.targets = targets
+ self.deauth_hidden_targets()
return self.targets
@@ -200,11 +214,11 @@ class Airodump(object):
if target.essid_len == 0:
# Ignore empty/blank ESSIDs
- continue
+ pass
if target.channel == "-1":
# Ignore -1 channel
- continue
+ pass
targets.append(target)
return targets
@@ -239,6 +253,41 @@ class Airodump(object):
i += 1
return result
+ def deauth_hidden_targets(self):
+ '''
+ Sends deauths (to broadcast and to each client) for all
+ targets (APs) that have unknown ESSIDs (hidden router names).
+ '''
+ self.decloaking = False
+ # Only deauth if channel is fixed.
+ if self.channel is None: return
+
+ # Reusable deauth command
+ deauth_cmd = [
+ 'aireplay-ng',
+ '-0', # Deauthentication
+ '1', # Number of deauths to perform.
+ '--ignore-negative-one'
+ ]
+ for target in self.targets:
+ 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
+ self.decloaking = True
+ self.decloaked_times[target.bssid] = now
+ if Configuration.verbose > 1:
+ from Color import Color
+ verbout = " [?] Deauthing %s" % target.bssid
+ verbout += " (broadcast & %d clients)" % len(target.clients)
+ Color.pe("\n{C}" + verbout + "{W}")
+ # Deauth broadcast
+ iface = Configuration.interface
+ Process(deauth_cmd + ['-a', target.bssid, iface])
+ # Deauth clients
+ for client in target.clients:
+ Process(deauth_cmd + ['-c', client.bssid, iface])
if __name__ == '__main__':
''' Example usage. wlan0mon should be in Monitor Mode '''
diff --git a/py/Arguments.py b/py/Arguments.py
index e2ff886..7aaa55a 100644
--- a/py/Arguments.py
+++ b/py/Arguments.py
@@ -30,6 +30,11 @@ class Arguments(object):
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)
+ glob.add_argument('-mac',
+ '---random-mac',
+ action='store_true',
+ dest='random_mac',
+ help=Color.s('Randomize wireless card MAC address (default: {G}off{W})'))
glob.add_argument('-5',
'--5ghz',
action='store_true',
@@ -271,7 +276,10 @@ class Arguments(object):
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='', dest='check_handshake')
-
+ commands.add_argument('--crack',
+ action='store_true',
+ dest='crack_handshake',
+ help=Color.s('Show commands to crack a captured handshake'))
return parser.parse_args()
if __name__ == '__main__':
diff --git a/py/Attack.py b/py/Attack.py
index d65a887..eddd92f 100644
--- a/py/Attack.py
+++ b/py/Attack.py
@@ -15,7 +15,7 @@ class Attack(object):
def run(self):
raise Exception("Unimplemented method: run")
-
+
def wait_for_target(self, airodump):
'''
Waits for target to appear in airodump
diff --git a/py/AttackWEP.py b/py/AttackWEP.py
index 074c7ee..dcf301d 100644
--- a/py/AttackWEP.py
+++ b/py/AttackWEP.py
@@ -77,8 +77,8 @@ class AttackWEP(Attack):
while True:
airodump_target = self.wait_for_target(airodump)
- Color.p('\r{+} running {C}%s{W} WEP attack ({G}%d IVs{W}) '
- % (attack_name, airodump_target.ivs))
+ Color.pattack("WEP", airodump_target, "%s attack" % attack_name, "%d IVs" % airodump_target.ivs)
+ #Color.p('\r{+} running {C}%s{W} WEP attack ({G}%d IVs{W}) ' % (attack_name, airodump_target.ivs))
# Check if we cracked it.
if aircrack and aircrack.is_cracked():
@@ -219,8 +219,7 @@ class AttackWEP(Attack):
attacks_remaining = Configuration.wep_attacks[attack_index + 1:]
Color.pl("{+} {G}%d{W} attacks remain ({C}%s{W})" % (len(attacks_remaining), ', '.join(attacks_remaining)))
- prompt = Color.s('{+} type {G}c{W} to {G}continue{W}' +
- ' or {R}s{W} to {R}stop{W}: ')
+ 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:
diff --git a/py/AttackWPA.py b/py/AttackWPA.py
index a941a78..98b3902 100644
--- a/py/AttackWPA.py
+++ b/py/AttackWPA.py
@@ -34,12 +34,10 @@ class AttackWPA(Attack):
# First, start Airodump process
with Airodump(channel=self.target.channel,
target_bssid=self.target.bssid,
- skip_wash=True,
output_file_prefix='wpa') as airodump:
- Color.clear_line()
- Color.p('\r{+} {C}WPA-handshake attack{W}: ')
- Color.p('{O}waiting{W} for target to appear...')
+ Color.clear_entire_line()
+ Color.pattack("WPA", self.target, "Handshake capture", "Waiting for target to appear...")
airodump_target = self.wait_for_target(airodump)
# Get client station MAC addresses
@@ -55,9 +53,10 @@ class AttackWPA(Attack):
while True:
if not deauth_proc or deauth_proc.poll() != None:
# Clear line only if we're not deauthing right now
- Color.p('\r%s\r' % (' ' * 90))
- Color.p('\r{+} {C}WPA-handshake attack{W}: ')
- Color.p('waiting for {C}handshake{W}...')
+ Color.clear_entire_line()
+ Color.pattack("WPA", airodump_target, "Handshake capture", "Waiting for handshake...")
+ #Color.p('\r{+} {C}WPA-handshake attack{W}: ')
+ #Color.p('waiting for {C}handshake{W}...')
time.sleep(1)
@@ -97,8 +96,8 @@ class AttackWPA(Attack):
airodump_target = self.wait_for_target(airodump)
for client in airodump_target.clients:
if client.station not in clients:
- Color.pl('\r{+} discovered {G}client{W}:' +
- ' {C}%s{W}%s' % (client.station, ' ' * 10))
+ Color.clear_entire_line()
+ Color.pl('\r{+} discovered new {G}client{W}: {C}%s{W}' % client.station)
clients.append(client.station)
# Send deauth to a client or broadcast
diff --git a/py/AttackWPS.py b/py/AttackWPS.py
index 1cc9537..dcc15f0 100644
--- a/py/AttackWPS.py
+++ b/py/AttackWPS.py
@@ -2,6 +2,7 @@
# -*- coding: utf-8 -*-
from Attack import Attack
+from Airodump import Airodump
from Color import Color
from Configuration import Configuration
from CrackResultWPS import CrackResultWPS
@@ -37,7 +38,7 @@ class AttackWPS(Attack):
' support the {O}WPS pixie-dust attack{W}')
if Configuration.pixie_only:
- Color.pl('{!} {O}--pixie{R} set, ignoring WPS-PIN attack{W}')
+ Color.pl('\r{!} {O}--pixie{R} set, ignoring WPS-PIN attack{W}')
self.success = False
else:
# Run WPS-PIN attack
@@ -58,113 +59,127 @@ class AttackWPS(Attack):
command = [
'reaver',
- '-i', Configuration.interface,
- '-b', self.target.bssid,
- '-c', self.target.channel,
- '-K', '1', # pixie-dust attack
- '-a', # Automatically restart session
+ '--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
]
-
stdout_write = open(self.stdout_file, 'a')
-
reaver = Process(command, stdout=stdout_write, stderr=Process.devnull())
pin = None
- step = '0) initializing'
+ step = 'initializing'
time_since_last_step = 0
- while True:
- time.sleep(1)
+ with Airodump(channel=self.target.channel,
+ target_bssid=self.target.bssid,
+ skip_wash=False,
+ output_file_prefix='pixie') as airodump:
+
Color.clear_line()
- Color.p('\r{+} {C}WPS pixie-dust attack{W} ')
+ Color.pattack("WPS", self.target, "Pixie Dust", "Waiting for target to appear...")
- stdout_write.flush()
-
- # Check output from reaver process
- stdout = self.get_stdout()
- stdout_last_line = stdout.split('\n')[-1]
-
- (pin, psk, ssid) = self.get_pin_psk_ssid(stdout)
-
- # Check if we cracked it, or if process stopped.
- if (pin and psk and ssid) or reaver.poll() != None:
- reaver.interrupt()
-
- # Check one-last-time for PIN/PSK/SSID, in case of race condition.
- stdout = self.get_stdout()
- (pin, psk, ssid) = AttackWPS.get_pin_psk_ssid(stdout)
-
- # Check if we cracked it.
- if pin and psk and ssid:
- # We cracked it.
- bssid = self.target.bssid
- Color.pl('\n\n{+} {G}successfully cracked WPS PIN and PSK{W}\n')
- self.crack_result = CrackResultWPS(bssid, ssid, pin, psk)
- self.crack_result.dump()
- return True
- else:
- # Failed to crack, reaver proces ended.
- Color.pl('{R}failed: {O}WPS pin not found{W}')
+ 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
- last_step = step
- # Status updates, depending on last line of stdout
- if 'Waiting for beacon from' in stdout_last_line:
- step = '({C}step 1/8{W}) waiting for beacon'
- elif 'Associated with' in stdout_last_line:
- step = '({C}step 2/8{W}) waiting to start session'
- elif 'Starting Cracking Session.' in stdout_last_line:
- step = '({C}step 3/8{W}) waiting to try pin'
- elif 'Trying pin' in stdout_last_line:
- step = '({C}step 4/8{W}) trying pin'
- elif 'Sending EAPOL START request' in stdout_last_line:
- step = '({C}step 5/8{W}) sending eapol start request'
- elif 'Sending identity response' in stdout_last_line:
- step = '({C}step 6/8{W}) sending identity response'
- elif 'Sending M2 message' in stdout_last_line:
- step = '({C}step 7/8{W}) sending m2 message (may take a while)'
- elif 'Detected AP rate limiting,' in stdout_last_line:
- if Configuration.wps_skip_rate_limit:
- Color.pl('{R}failed: {O}hit WPS rate-limit{W}')
- Color.pl('{!} {O}use {R}--skip-rate-limit{O} to ignore' +
- ' this kind of failure in the future{W}')
+ stdout_write.flush()
+
+ # Check output from reaver process
+ stdout = self.get_stdout()
+ stdout_last_line = stdout.split('\n')[-1]
+
+ (pin, psk, ssid) = self.get_pin_psk_ssid(stdout)
+
+ # Check if we cracked it, or if process stopped.
+ if (pin and psk and ssid) or reaver.poll() != None:
+ reaver.interrupt()
+
+ # Check one-last-time for PIN/PSK/SSID, in case of race condition.
+ stdout = self.get_stdout()
+ (pin, psk, ssid) = AttackWPS.get_pin_psk_ssid(stdout)
+
+ # Check if we cracked it.
+ if pin and psk and ssid:
+ # We cracked it.
+ bssid = self.target.bssid
+ Color.clear_line()
+ Color.pattack("WPS", airodump_target, "Pixie-Dust", "{G}successfully cracked WPS PIN and PSK{W}\n")
+ self.crack_result = CrackResultWPS(bssid, ssid, pin, psk)
+ self.crack_result.dump()
+ return True
+ else:
+ # Failed to crack, reaver proces ended.
+ Color.clear_line()
+ Color.pattack("WPS", airodump_target, "Pixie-Dust", "{R}Failed: {O}WPS PIN not found{W}\n")
+ return False
+
+ if 'WPS pin not found' in stdout:
+ Color.pl('{R}failed: {O}WPS pin not found{W}')
break
- step = '({C}step -/8{W}) waiting for AP rate limit'
- if 'WPS pin not found' in stdout:
- Color.pl('{R}failed: {O}WPS pin not found{W}')
- break
+ last_step = step
+ # Status updates, depending on last line of stdout
+ if 'Waiting for beacon from' in stdout_last_line:
+ step = '({C}step 1/8{W}) waiting for beacon'
+ elif 'Associated with' in stdout_last_line:
+ step = '({C}step 2/8{W}) waiting to start session'
+ elif 'Starting Cracking Session.' in stdout_last_line:
+ step = '({C}step 3/8{W}) waiting to try pin'
+ elif 'Trying pin' in stdout_last_line:
+ step = '({C}step 4/8{W}) trying pin'
+ elif 'Sending EAPOL START request' in stdout_last_line:
+ step = '({C}step 5/8{W}) sending eapol start request'
+ elif 'Sending identity response' in stdout_last_line:
+ step = '({C}step 6/8{W}) sending identity response'
+ elif 'Sending M2 message' in stdout_last_line:
+ step = '({C}step 7/8{W}) sending m2 message (may take a while)'
+ elif 'Detected AP rate limiting,' in stdout_last_line:
+ if Configuration.wps_skip_rate_limit:
+ Color.pl('{R}failed: {O}hit WPS rate-limit{W}')
+ Color.pl('{!} {O}use {R}--ignore-ratelimit{O} to ignore' +
+ ' this kind of failure in the future{W}')
+ break
+ step = '({C}step -/8{W}) waiting for AP rate limit'
- if step != last_step:
- # Step changed, reset step timer
- time_since_last_step = 0
- else:
- time_since_last_step += 1
+ if step != last_step:
+ # Step changed, reset step timer
+ time_since_last_step = 0
+ else:
+ time_since_last_step += 1
- if time_since_last_step > Configuration.wps_pixie_step_timeout:
- Color.pl('{R}failed: {O}step-timeout after %d seconds{W}' % Configuration.wps_pixie_step_timeout)
- break
+ if time_since_last_step > Configuration.wps_pixie_step_timeout:
+ Color.pl('{R}failed: {O}step-timeout after %d seconds{W}' % Configuration.wps_pixie_step_timeout)
+ break
- # TODO: Timeout check
- if reaver.running_time() > Configuration.wps_pixie_timeout:
- Color.pl('{R}failed: {O}timeout after %d seconds{W}' % Configuration.wps_pixie_timeout)
- break
+ # TODO: Timeout check
+ if reaver.running_time() > Configuration.wps_pixie_timeout:
+ Color.pl('{R}failed: {O}timeout after %d seconds{W}' % Configuration.wps_pixie_timeout)
+ break
- # Reaver Failure/Timeout check
- fail_count = stdout.count('WPS transaction failed')
- if fail_count > Configuration.wps_fail_threshold:
- Color.pl('{R}failed: {O}too many failures (%d){W}' % fail_count)
- break
- timeout_count = stdout.count('Receive timeout occurred')
- if timeout_count > Configuration.wps_timeout_threshold:
- Color.pl('{R}failed: {O}too many timeouts (%d){W}' % timeout_count)
- break
+ # 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
- # Display status of Pixie-Dust attack
- Color.p('{W}%s{W}' % step)
+ Color.clear_line()
+ Color.pattack("WPS", airodump_target, "Pixie-Dust", step)
- continue
+ time.sleep(1)
+ continue
# Attack failed, already printed reason why
reaver.interrupt()
@@ -182,10 +197,10 @@ class AttackWPS(Attack):
# Start reaver process
command = [
'reaver',
- '-i', Configuration.interface,
- '-b', self.target.bssid,
- '-c', self.target.channel,
- '-a', # Automatically restart session
+ '--interface', Configuration.interface,
+ '--bssid', self.target.bssid,
+ '--channel', self.target.channel,
+ '--session', '/dev/null', # Don't restart session
'-vv' # verbose
]
reaver = Process(command, stdout=stdout_write, stderr=Process.devnull())
@@ -197,141 +212,155 @@ class AttackWPS(Attack):
failures = 0
state = 'initializing'
- while True:
- time.sleep(1)
- percent = 100 * float(pin_current) / float(pin_total)
+ with Airodump(channel=self.target.channel,
+ target_bssid=self.target.bssid,
+ skip_wash=False,
+ output_file_prefix='wps') as airodump:
+
Color.clear_line()
- Color.p('\r{+} {C}WPS PIN attack{W} (')
- Color.p('{G}%.2f%% done{W}, ' % percent)
- Color.p('{G}%d{W}/{G}%d pins{W}, ' % (pin_current, pin_total))
- Color.p('{R}%d/%d failures{W}) ' % (failures, \
- Configuration.wps_fail_threshold))
+ Color.pattack("WPS", self.target, "PIN Attack", "Waiting for target to appear...")
- if failures >= Configuration.wps_fail_threshold:
- Color.pl('{R}failed: {O}too many failures{W}')
- break
+ while True:
+ try:
+ airodump_target = self.wait_for_target(airodump)
+ except Exception as e:
+ Color.pattack("WPS", self.target, "PIN Attack", "{R}failed: {O}%s{W}" % e)
+ Color.pl("")
+ return False
+ time.sleep(1)
+ percent = 100 * float(pin_current) / float(pin_total)
+ Color.clear_line()
+ status = '{G}%.2f%% done{W}, ' % percent
+ status += '{G}%d{W}/{G}%d pins{W}, ' % (pin_current, pin_total)
+ status += '{R}%d/%d failures{W}' % (failures, Configuration.wps_fail_threshold)
+ Color.pattack("WPS", airodump_target, "PIN Attack", status)
- # Get output
- out = self.get_stdout()
-
- # Clear output file
- f = open(self.stdout_file, 'w')
- f.write('')
- f.close()
-
- # CHECK FOR CRACK
-
- (pin, psk, ssid) = AttackWPS.get_pin_psk_ssid(out)
- if pin and psk and ssid:
- # We cracked it.
- self.success = True
- Color.pl('\n{+} {G}successly cracked WPS PIN and PSK{W}\n')
- self.crack_result = CrackResultWPS(self.target.bssid, ssid, pin, psk)
- self.crack_result.dump()
- break
-
-
- # PIN PROGRESS
-
- # Reaver 1.5.*
- match = None
- for match in re.finditer('Pin count advanced: (\d+)\\. Max pin attempts: (\d+)', out):
- # Look at last entry for "Pin count advanced" to get latest pin count
- pass
- if match:
- # Reset failures on successful try
- failures = 0
- groups = match.groups()
- pin_current = int(groups[0])
- pin_total = int(groups[1])
-
- # Reaver 1.3, 1.4
- match = None
- for match in re.finditer('Trying pin (\d+)', out):
- if match:
- pin = int(match.groups()[0])
- if pin not in pins:
- # Reset failures on successful try
- failures = 0
- pins.add(pin)
- pin_current += 1
-
- # Failures
- if 'WPS transaction failed' in out:
- failures += out.count('WPS transaction failed')
- elif 'Receive timeout occurred' in out:
- # Reaver 1.4
- failures += out.count('Receive timeout occurred')
-
- # Status
- if 'Waiting for beacon from' in out: state = '{O}waiting for beacon{W}'
- if 'Starting Cracking Session' in out: state = '{C}cracking{W}'
- # Reaver 1.4
- if 'Trying pin' in out and 'cracking' not in state: state = '{C}cracking{W}'
-
- if 'Detected AP rate limiting' in out:
- state = '{R}rate-limited{W}'
- if Configuration.wps_skip_rate_limit:
- Color.pl(state)
- Color.pl('{!} {R}hit rate limit, stopping{W}\n')
- Color.pl('{!} {O}use {R}--skip-rate-limit{O} to ignore' +
- ' this kind of failure in the future{W}')
+ if failures >= Configuration.wps_fail_threshold:
+ Color.pattack("WPS", airodump_target, "PIN Attack", '{R}failed: {O}too many failures{W}')
+ Color.pl("")
break
- if 'WARNING: Failed to associate with' in out:
- # TODO: Fail after X association failures (instead of just one)
- Color.pl('\n{!} {R}failed to associate with target, {O}stopping{W}')
- break
+ # Get output
+ out = self.get_stdout()
- match = re.search('Estimated Remaining time: ([a-zA-Z0-9]+)', out)
- if match:
- eta = match.groups()[0]
- state = '{C}cracking, ETA: {G}%s{W}' % eta
+ # Clear output file
+ f = open(self.stdout_file, 'w')
+ f.write('')
+ f.close()
- match = re.search('Max time remaining at this rate: ([a-zA-Z0-9:]+)..([0-9]+) pins left to try', out)
- if match:
- eta = match.groups()[0]
- state = '{C}cracking, ETA: {G}%s{W}' % eta
- pins_left = int(match.groups()[1])
+ # CHECK FOR CRACK
- # Divine pin_current & pin_total from this:
- pin_current = 11000 - pins_left
+ (pin, psk, ssid) = AttackWPS.get_pin_psk_ssid(out)
+ if pin and psk and ssid:
+ # We cracked it.
+ self.success = True
+ Color.pl('\n{+} {G}successly cracked WPS PIN and PSK{W}\n')
+ self.crack_result = CrackResultWPS(self.target.bssid, ssid, pin, psk)
+ self.crack_result.dump()
+ break
- # Check if process is still running
- if reaver.pid.poll() != None:
- Color.pl('{R}failed{W}')
- Color.pl('{!} {R}reaver{O} quit unexpectedly{W}')
- self.success = False
- break
- # Output the current state
- Color.p(state)
+ # PIN PROGRESS
- '''
- [+] Waiting for beacon from AA:BB:CC:DD:EE:FF
- [+] Associated with AA:BB:CC:DD:EE:FF (ESSID: )
- [+] Starting Cracking Session. Pin count: 0, Max pin attempts: 11000
- [+] Trying pin 12345670.
- [+] Pin count advanced: 46. Max pin attempts: 11000
- [!] WPS transaction failed (code: 0x02), re-trying last pin
- [!] WPS transaction failed (code: 0x03), re-trying last pin
- [!] WARNING: Failed to associate with 00:24:7B:AB:5C:EE (ESSID: myqwest0445)
- [!] WARNING: Detected AP rate limiting, waiting 60 seconds before re-checking
- [!] WARNING: 25 successive start failures
- [!] WARNING: Failed to associate with B2:B2:DC:A1:35:94 (ESSID: CenturyLink2217)
- [+] 0.55% complete. Elapsed time: 0d0h2m21s.
- [+] Estimated Remaining time: 0d15h11m35s
+ # Reaver 1.5.*
+ match = None
+ for match in re.finditer('Pin count advanced: (\d+)\\. Max pin attempts: (\d+)', out):
+ # Look at last entry for "Pin count advanced" to get latest pin count
+ pass
+ if match:
+ # Reset failures on successful try
+ failures = 0
+ groups = match.groups()
+ pin_current = int(groups[0])
+ pin_total = int(groups[1])
- [+] Pin cracked in 7 seconds
- [+] WPS PIN: '12345678'
- [+] WPA PSK: 'abcdefgh'
- [+] AP SSID: 'Test Router'
+ # Reaver 1.3, 1.4
+ match = None
+ for match in re.finditer('Trying pin (\d+)', out):
+ if match:
+ pin = int(match.groups()[0])
+ if pin not in pins:
+ # Reset failures on successful try
+ failures = 0
+ pins.add(pin)
+ pin_current += 1
- Reaver 1.4:
- [+] Max time remaining at this rate: 18:19:36 (10996 pins left to try)
- [!] WARNING: Receive timeout occurred
+ # Failures
+ if 'WPS transaction failed' in out:
+ failures += out.count('WPS transaction failed')
+ elif 'Receive timeout occurred' in out:
+ # Reaver 1.4
+ failures += out.count('Receive timeout occurred')
- '''
+ # Status
+ if 'Waiting for beacon from' in out: state = '{O}waiting for beacon{W}'
+ if 'Starting Cracking Session' in out: state = '{C}cracking{W}'
+ # Reaver 1.4
+ if 'Trying pin' in out and 'cracking' not in state: state = '{C}cracking{W}'
+
+ if 'Detected AP rate limiting' in out:
+ state = '{R}rate-limited{W}'
+ if Configuration.wps_skip_rate_limit:
+ Color.pl(state)
+ Color.pl('{!} {R}hit rate limit, stopping{W}')
+ Color.pl('{!} {O}use {R}--ignore-ratelimit{O} to ignore' +
+ ' this kind of failure in the future{W}')
+ break
+
+ if 'WARNING: Failed to associate with' in out:
+ # TODO: Fail after X association failures (instead of just one)
+ Color.pl('\n{!} {R}failed to associate with target, {O}stopping{W}')
+ break
+
+ match = re.search('Estimated Remaining time: ([a-zA-Z0-9]+)', out)
+ if match:
+ eta = match.groups()[0]
+ state = '{C}cracking, ETA: {G}%s{W}' % eta
+
+ match = re.search('Max time remaining at this rate: ([a-zA-Z0-9:]+)..([0-9]+) pins left to try', out)
+ if match:
+ eta = match.groups()[0]
+ state = '{C}cracking, ETA: {G}%s{W}' % eta
+ pins_left = int(match.groups()[1])
+
+ # Divine pin_current & pin_total from this:
+ pin_current = 11000 - pins_left
+
+ # Check if process is still running
+ if reaver.pid.poll() != None:
+ Color.pl('{R}failed{W}')
+ Color.pl('{!} {R}reaver{O} quit unexpectedly{W}')
+ self.success = False
+ break
+
+ # Output the current state
+ Color.p(state)
+
+ '''
+ [+] Waiting for beacon from AA:BB:CC:DD:EE:FF
+ [+] Associated with AA:BB:CC:DD:EE:FF (ESSID: )
+ [+] Starting Cracking Session. Pin count: 0, Max pin attempts: 11000
+ [+] Trying pin 12345670.
+ [+] Pin count advanced: 46. Max pin attempts: 11000
+ [!] WPS transaction failed (code: 0x02), re-trying last pin
+ [!] WPS transaction failed (code: 0x03), re-trying last pin
+ [!] WARNING: Failed to associate with 00:24:7B:AB:5C:EE (ESSID: myqwest0445)
+ [!] WARNING: Detected AP rate limiting, waiting 60 seconds before re-checking
+ [!] WARNING: 25 successive start failures
+ [!] WARNING: Failed to associate with B2:B2:DC:A1:35:94 (ESSID: CenturyLink2217)
+ [+] 0.55% complete. Elapsed time: 0d0h2m21s.
+ [+] Estimated Remaining time: 0d15h11m35s
+
+ [+] Pin cracked in 7 seconds
+ [+] WPS PIN: '12345678'
+ [+] WPA PSK: 'abcdefgh'
+ [+] AP SSID: 'Test Router'
+
+ Reaver 1.4:
+ [+] Max time remaining at this rate: 18:19:36 (10996 pins left to try)
+ [!] WARNING: Receive timeout occurred
+
+ '''
reaver.interrupt()
diff --git a/py/Color.py b/py/Color.py
index 0fac0ef..2ea7b06 100644
--- a/py/Color.py
+++ b/py/Color.py
@@ -21,7 +21,7 @@ class Color(object):
# Helper string replacements
replacements = {
'{+}': ' {W}[{G}+{W}]',
- '{!}': ' {W}[{R}!{W}]'
+ '{!}': ' {O}[{R}!{O}]{W}'
}
last_sameline_length = 0
@@ -74,6 +74,24 @@ class Color(object):
sys.stdout.flush()
Color.last_sameline_length = 0
+ @staticmethod
+ def clear_entire_line():
+ import os
+ (rows, columns) = os.popen('stty size', 'r').read().split()
+ 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
+ '''
+ 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))
+
if __name__ == '__main__':
Color.pl("{R}Testing{G}One{C}Two{P}Three{W}Done")
print Color.s("{C}Testing{P}String{W}")
diff --git a/py/Configuration.py b/py/Configuration.py
index 5ee2d7c..7ded288 100644
--- a/py/Configuration.py
+++ b/py/Configuration.py
@@ -2,11 +2,13 @@
# -*- coding: utf-8 -*-
from Color import Color
+from Macchanger import Macchanger
import os
class Configuration(object):
''' Stores configuration variables and functions for Wifite. '''
+ verbose = 0
initialized = False # Flag indicating config has been initialized
temp_dir = None # Temporary directory
@@ -33,6 +35,7 @@ class Configuration(object):
Configuration.target_bssid = None # User-defined AP BSSID
Configuration.five_ghz = False # Scan 5Ghz channels
Configuration.pillage = False # "All" mode to attack everything
+ Configuration.random_mac = False
Configuration.encryption_filter = ['WEP', 'WPA', 'WPS']
@@ -59,7 +62,8 @@ class Configuration(object):
Configuration.wordlist = None
wordlists = [
'/usr/share/wfuzz/wordlist/fuzzdb/wordlists-user-passwd/passwds/phpbb.txt',
- '/usr/share/fuzzdb/wordlists-user-passwd/passwds/phpbb.txt'
+ '/usr/share/fuzzdb/wordlists-user-passwd/passwds/phpbb.txt',
+ '/usr/share/wordlists/fern-wifi/common.txt'
]
for wlist in wordlists:
if os.path.exists(wlist):
@@ -82,6 +86,7 @@ class Configuration(object):
# Commands
Configuration.show_cracked = False
Configuration.check_handshake = None
+ Configuration.crack_handshake = False
# Overwrite config values with arguments (if defined)
Configuration.load_from_arguments()
@@ -96,6 +101,8 @@ class Configuration(object):
# Interface wasn't defined, select it!
from Airmon import Airmon
Configuration.interface = Airmon.ask()
+ if Configuration.random_mac:
+ Macchanger.random()
@staticmethod
@@ -104,6 +111,9 @@ class Configuration(object):
from Arguments import 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')
if args.channel:
Configuration.target_channel = args.channel
Color.pl('{+} {C}option:{W} scanning for targets on channel {G}%s{W}' % args.channel)
@@ -242,8 +252,9 @@ class Configuration(object):
% '{W}, {G}'.join(Configuration.wep_attacks))
# Commands
- if args.cracked: Configuration.show_cracked = True
+ 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
@staticmethod
@@ -276,6 +287,7 @@ class Configuration(object):
def exit_gracefully(code=0):
''' Deletes temp and exist with the given code '''
Configuration.delete_temp()
+ Macchanger.reset_if_changed()
from Airmon import Airmon
Airmon.stop(Configuration.interface)
Airmon.put_interfaces_up()
diff --git a/py/CrackHandshake.py b/py/CrackHandshake.py
new file mode 100644
index 0000000..b17332c
--- /dev/null
+++ b/py/CrackHandshake.py
@@ -0,0 +1,90 @@
+#!/usr/bin/python2.7
+# -*- coding: utf-8 -*-
+
+from Process import Process
+from Color import Color
+from Configuration import Configuration
+from CrackResult import CrackResult
+from datetime import datetime
+
+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)
+
+ def crack_handshake(self, handshake):
+ cap_file = os.path.realpath(handshake["handshake_file"])
+ Color.pl("{+} Different ways to crack {C}%s{W}:" % cap_file)
+ self.print_aircrack(cap_file)
+ self.print_pyrit(cap_file)
+ self.print_john(cap_file)
+ self.print_oclhashcat(cap_file)
+ Color.pl("")
+ # TODO: cowpatty, oclhashcat
+
+ def print_aircrack(self, cap_file):
+ if not Process.exists("aircrack-ng"): return
+ Color.pl("\n {O}# AIRCRACK: CPU-based cracking. Slow.")
+ Color.pl(" {G}aircrack-ng {W}-a 2 -w {C}%s %s{W}" % (self.wordlist, cap_file))
+
+ def print_pyrit(self, cap_file):
+ if not Process.exists("pyrit"): return
+ Color.pl("\n {O}# PYRIT: GPU-based cracking. Fast.")
+ Color.pl(" {G}pyrit {W}-i {C}%s {W}-r {C}%s {W}attack_passthrough{W}" % (self.wordlist, cap_file))
+
+ def print_john(self, cap_file):
+ if not Process.exists("pyrit"): return
+ Color.pl("\n {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 {W}hccap.hccap > 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):
+ if not Process.exists("hashcat"): return
+ Color.pl("\n {O}# OCLHASHCAT: GPU-based cracking. Fast.")
+ # TODO: Generate hccapx automatically
+ hccapx_file = "generated.hccapx" #cap_file
+ Color.pl(" {O}# Visit https://hashcat.net/cap2hccapx to generate a .hccapx file{W}")
+ Color.pl(" {G}hashcat {W}-m 2500 {C}%s %s{W}" % (self.wordlist, hccapx_file))
+
+ def choose_handshake(self):
+ Color.pl("\n{+} Listing captured handshakes...\n")
+ handshakes = CrackResult.load_all()
+ handshakes = [hs for hs in handshakes if "handshake_file" in hs and os.path.exists(hs["handshake_file"])]
+ if len(handshakes) == 0:
+ raise Exception("No handshakes found in %s" % os.path.realpath(CrackResult.cracked_file))
+
+ # Handshakes Header
+ max_essid_len = max([len(hs["essid"]) for hs in handshakes])
+ Color.p(" NUM")
+ Color.p(" " + "ESSID".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 index, hs in enumerate(handshakes):
+ bssid = hs["bssid"]
+ essid = hs["essid"]
+ date = datetime.strftime(datetime.fromtimestamp(hs["date"]), "%Y-%m-%dT%H:%M:%S")
+ Color.p(" {G}%s{W}" % str(index + 1).rjust(3))
+ Color.p(" {C}%s{W}" % essid.ljust(max_essid_len))
+ Color.p(" {C}%s{W}" % bssid)
+ Color.p(" {C}%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 Exception("Invalid input: %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))
+
+ return handshakes[hs_index - 1]
diff --git a/py/CrackResult.py b/py/CrackResult.py
index 2e1f135..5a80d6d 100644
--- a/py/CrackResult.py
+++ b/py/CrackResult.py
@@ -41,6 +41,13 @@ class CrackResult(object):
Color.pl('{+} saved crack result to {C}%s{W} ({G}%d total{W})'
% (name, len(json)))
+ @classmethod
+ def load_all(cls):
+ if not os.path.exists(cls.cracked_file): return []
+ with open(cls.cracked_file, "r") as json_file:
+ json = loads(json_file.read())
+ return json
+
@staticmethod
def load(json):
''' Returns an instance of the appropriate object given a json instance '''
diff --git a/py/Handshake.py b/py/Handshake.py
index a7a4060..0312c2f 100644
--- a/py/Handshake.py
+++ b/py/Handshake.py
@@ -90,22 +90,24 @@ class Handshake(object):
cmd = [
'tshark',
'-r', self.capfile,
- '-R', 'wlan.fc.type_subtype == 0x08',
+ '-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=(.*)$'
+ match = re.search('(%s) [^ ]* (%s).*.*SSID=(.*)$'
% (mac_regex, mac_regex), line)
if match == 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():
+ if self.bssid.lower() == src.lower() or self.bssid.lower() == dst.lower():
essids.add((src, essid))
else:
# We do not know BSSID, add it.
@@ -263,7 +265,7 @@ class Handshake(object):
hit_Target = False
else:
# Line does not contain AccessPoint
- if hit_target and ', good,' in line:
+ if hit_target and ', good' in line:
bssid_essid_pairs.add( (current_bssid, current_essid) )
return [x for x in bssid_essid_pairs]
diff --git a/py/Interface.py b/py/Interface.py
index 6075762..61c2725 100644
--- a/py/Interface.py
+++ b/py/Interface.py
@@ -90,7 +90,7 @@ class Interface(object):
output = Process(['ifconfig', iface]).stdout()
mac_regex = ('[a-zA-Z0-9]{2}-' * 6)[:-1]
- match = re.search('HWaddr (%s)' % mac_regex, output)
+ match = re.search(' (%s)' % mac_regex, output)
if not match:
match = re.search('unspec (%s)' % mac_regex, output)
if not match:
diff --git a/py/Macchanger.py b/py/Macchanger.py
new file mode 100644
index 0000000..5876bdc
--- /dev/null
+++ b/py/Macchanger.py
@@ -0,0 +1,82 @@
+#!/usr/bin/python2.7
+# -*- coding: utf-8 -*-
+
+from Interface import Interface
+from Color import Color
+
+class Macchanger(object):
+ is_init = False
+ is_changed = False
+ original_mac = None
+
+ @classmethod
+ def init(cls):
+ if cls.is_init: return
+ from Configuration import Configuration
+ iface = Configuration.interface
+ if type(iface) == Interface:
+ iface = iface.name
+ cls.original_mac = Interface.get_mac(iface)
+
+ @classmethod
+ def down_macch_up(cls, macch_option):
+ cls.init()
+ from Process import Process
+ from Configuration 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
+
+ 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)
+ 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()))
+ 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
+ return True
+
+ @classmethod
+ def reset(cls):
+ # --permanent to reset to permanent MAC address
+ if not cls.down_macch_up("-p"): return
+ Color.pl("\r{+} {C}macchanger{W}: Resetting MAC address...")
+ from Configuration import Configuration
+ new_mac = Interface.get_mac(Configuration.interface)
+ Color.clear_entire_line()
+ Color.pl("\r{+} {C}macchanger{W}: Reset MAC address back to {C}%s{W}" % new_mac)
+
+ @classmethod
+ def random(cls):
+ # Use --permanent to use random MAC address
+ if not cls.down_macch_up("-r"): return
+ cls.is_changed = True
+ from Configuration import Configuration
+ new_mac = Interface.get_mac(Configuration.interface)
+ Color.clear_entire_line()
+ Color.pl("\r{+} {C}macchanger{W}: Changed MAC address to {C}%s{W}" % new_mac)
+
+ @classmethod
+ def reset_if_changed(cls):
+ if not cls.is_changed: return
+ cls.reset()
diff --git a/py/Scanner.py b/py/Scanner.py
index 0d3c47c..79d4b73 100644
--- a/py/Scanner.py
+++ b/py/Scanner.py
@@ -23,6 +23,7 @@ class Scanner(object):
self.targets = []
self.target = None # Specific target (based on ESSID/BSSID)
+ Color.pl("")
# Loads airodump with interface/channel/etc from Configuration
with Airodump() as airodump:
try:
@@ -48,11 +49,20 @@ class Scanner(object):
client_count = sum(
[len(t.clients)
for t in self.targets])
- Color.p(
- '\r{+} scanning, found' +
- ' {G}%d{W} target(s),' % target_count +
- ' {G}%d{W} clients.' % client_count +
- ' {O}Ctrl+C{W} when ready')
+ 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])
+ Color.clear_entire_line()
+ Color.p(outline)
sleep(1)
except KeyboardInterrupt:
pass
@@ -117,6 +127,7 @@ class Scanner(object):
Target.print_header()
for (index, target) in enumerate(self.targets):
index += 1
+ Color.clear_entire_line()
Color.pl(' {G}%s %s' % (str(index).rjust(3), target))
@staticmethod
@@ -125,6 +136,12 @@ class Scanner(object):
(rows, columns) = os.popen('stty size', 'r').read().split()
return int(rows)
+ @staticmethod
+ def get_terminal_width():
+ import os
+ (rows, columns) = os.popen('stty size', 'r').read().split()
+ return int(columns)
+
def select_targets(self):
''' Asks user to select target(s) '''
@@ -138,6 +155,7 @@ class Scanner(object):
+ " or you may have issues with your wifi card")
self.print_targets()
+ Color.clear_entire_line()
input_str = '{+} select target(s)'
input_str += ' ({G}1-%d{W})' % len(self.targets)
input_str += ' separated by commas, dashes'
diff --git a/py/Target.py b/py/Target.py
index 2a09d81..f26bcc9 100644
--- a/py/Target.py
+++ b/py/Target.py
@@ -103,10 +103,12 @@ class Target(object):
power = Color.s('{%s}%s' % (color, power))
wps = Color.s('{O} n/a')
- if self.wps:
+ if self.wps == True:
wps = Color.s('{G} yes')
- else:
+ elif self.wps == False:
wps = Color.s('{R} no')
+ else:
+ wps = Color.s('{O} n/a')
clients = ' '
if len(self.clients) == 1:
diff --git a/py/Wash.py b/py/Wash.py
index 7376c5b..6256d71 100644
--- a/py/Wash.py
+++ b/py/Wash.py
@@ -2,9 +2,11 @@
# -*- coding: utf-8 -*-
from Process import Process
+import re
class Wash(object):
''' Wrapper for Wash program. '''
+ BSSID_REGEX = re.compile("([A-F0-9\:]{17})", re.IGNORECASE)
def __init__(self):
pass
@@ -30,24 +32,17 @@ class Wash(object):
command = [
'wash',
- '-f', capfile, # Path to cap file
- '-C' # Ignore Frame Check Sum errors
+ '-f', capfile # Path to cap file
]
p = Process(command)
- for line in p.stdout().split('\n'):
- # Ignore irrelevant lines
- if line.strip() == '' or line.startswith('Scanning for'):
- continue
- bssid = line.split(' ')[0]
- for t in targets:
- if t.bssid.lower() == bssid.lower():
- # Update the WPS flag
- t.wps = True
- # Mark other targets as "no" wps support
+ p.wait()
+ if p.poll() != 0:
+ return
+
+ bssids = [bssid.upper() for bssid in Wash.BSSID_REGEX.findall(p.stdout())]
for t in targets:
- if t.wps: continue
- t.wps = False
+ t.wps = t.bssid.upper() in bssids
if __name__ == '__main__':
diff --git a/py/tests/test_Handshake.py b/py/tests/test_Handshake.py
index 21fefc7..b0ac1c9 100644
--- a/py/tests/test_Handshake.py
+++ b/py/tests/test_Handshake.py
@@ -4,7 +4,7 @@
import sys
sys.path.insert(0, '..')
-from Handshake import Handshake
+from py.Handshake import Handshake
import unittest
@@ -20,7 +20,6 @@ class TestHandshake(unittest.TestCase):
def testAnalyze(self):
hs_file = self.getFile('handshake_exists.cap')
- print hs_file
hs = Handshake(hs_file, bssid='A4:2B:8C:16:6B:3A')
try:
hs.analyze()
diff --git a/py/tests/test_Target.py b/py/tests/test_Target.py
index c092d1c..d16f1d9 100644
--- a/py/tests/test_Target.py
+++ b/py/tests/test_Target.py
@@ -1,7 +1,7 @@
#!/usr/bin/python2.7
# -*- coding: utf-8 -*-
-from Airodump import Airodump
+from py.Airodump import Airodump
import unittest
diff --git a/runtests.sh b/runtests.sh
index 3e6f1e3..da9b3ed 100755
--- a/runtests.sh
+++ b/runtests.sh
@@ -1,2 +1,2 @@
#!/bin/sh
-python2.7 -m unittest discover py
+python2.7 -m unittest discover py/tests -v