Skip to content

Commit 3b2e927

Browse files
committed
Merge PR
1 parent c9a4fe7 commit 3b2e927

File tree

18 files changed

+330
-141
lines changed

18 files changed

+330
-141
lines changed

wifite/args.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,22 @@ def _add_global_args(self, glob):
254254
help=Color.s(
255255
'Shows more options ({C}-h -v{W}). Prints commands and outputs. (default: {G}quiet{W})'))
256256

257+
glob.add_argument('--debug',
258+
action='store_true',
259+
default=False,
260+
dest='debug',
261+
help=self._verbose(
262+
'Shorthand for {C}-vvv{W}: max verbosity with file logging to {C}~/.wifite/wifite.log{W}'))
263+
264+
glob.add_argument('--log-file',
265+
action='store',
266+
dest='log_file',
267+
metavar='[path]',
268+
type=str,
269+
default=None,
270+
help=self._verbose(
271+
'Write debug log to {C}[path]{W} (implies {C}-vv{W} minimum verbosity)'))
272+
257273
glob.add_argument('-i',
258274
action='store',
259275
dest='interface',
@@ -366,6 +382,14 @@ def _add_global_args(self, glob):
366382
help=self._verbose(
367383
'Hides targets with ESSIDs that match the given text. Can be used more than once.'))
368384
glob.add_argument('--ignore-essid', help=argparse.SUPPRESS, action='append', dest='ignore_essids', type=str)
385+
glob.add_argument('--ignore-essids-file',
386+
action='store',
387+
dest='ignore_essids_file',
388+
metavar='[file]',
389+
type=str,
390+
default=None,
391+
help=self._verbose(
392+
'Hides targets whose ESSID appears in {C}[file]{W} (one ESSID per line).'))
369393

370394
glob.add_argument('-ic',
371395
'--ignore-cracked',

wifite/attack/all.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from ..config import Configuration
1212
from ..model.target import WPSState
1313
from ..util.color import Color
14+
from ..util.logger import log_info, log_debug
1415
from ..util.wpa3_tools import WPA3ToolChecker
1516
from ..util.memory import MemoryMonitor, get_infinite_monitor
1617

@@ -82,6 +83,9 @@ def attack_single(cls, target, targets_remaining, session=None, session_mgr=None
8283
Returns: True if attacks should continue, False otherwise.
8384
"""
8485
global attack
86+
log_info('AttackAll', 'Targeting %s (%s) enc=%s auth=%s wps=%s pwr=%s (%d remaining)' % (
87+
target.essid or '(hidden)', target.bssid, target.encryption,
88+
target.authentication, target.wps, target.power, targets_remaining))
8589
if 'MGT' in target.authentication:
8690
Color.pl("\n{!}{O}Skipping. Target is using {C}WPA-Enterprise {O}and can not be cracked.")
8791
# Mark as failed in session
@@ -268,7 +272,7 @@ def user_wants_to_continue(cls, targets_remaining, attacks_remaining=0):
268272
Answer.ExitOrReturn if the user wants to stop the remaining attacks
269273
"""
270274
if attacks_remaining == 0 and targets_remaining == 0:
271-
return # No targets or attacksleft, drop out
275+
return Answer.ExitOrReturn # No targets or attacks left, drop out
272276

273277
prompt_list = []
274278
if attacks_remaining > 0:

wifite/attack/wep.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#!/usr/bin/env python
22
# -*- coding: utf-8 -*-
33

4+
import subprocess
45
import time
56

67
from ..config import Configuration
@@ -11,6 +12,7 @@
1112
from ..tools.airodump import Airodump
1213
from ..tools.ip import Ip
1314
from ..util.color import Color
15+
from ..util.logger import log_debug, log_info, log_warning
1416
from ..util.output import OutputManager
1517

1618

@@ -42,7 +44,11 @@ def run(self):
4244
Including airodump-ng starting, cracking, etc.
4345
Returns: True if attack is successful, false otherwise
4446
"""
45-
47+
48+
log_info('AttackWEP', 'Starting WEP attack on %s (%s) ch %s' % (
49+
self.target.essid or '?', self.target.bssid, self.target.channel))
50+
attack_start = time.time()
51+
4652
# Start TUI view if available
4753
if self.view:
4854
self.view.start()
@@ -125,9 +131,9 @@ def run(self):
125131
client_mac = airodump_target.clients[0].station
126132

127133
if keep_ivs and current_ivs > airodump_target.ivs:
128-
# We now have less IVS than before; A new attack must have started.
129-
# Track how many we have in-total.
130-
previous_ivs += total_ivs
134+
# We now have less IVs than before; a new attack must have started.
135+
# Save the accumulated total so far before resetting.
136+
previous_ivs = total_ivs
131137
current_ivs = airodump_target.ivs
132138
total_ivs = previous_ivs + current_ivs
133139

@@ -159,6 +165,8 @@ def run(self):
159165
Airodump.delete_airodump_temp_files('wep')
160166

161167
self.success = True
168+
log_info('AttackWEP', 'WEP attack on %s cracked in %.1fs (%d IVs)' % (
169+
self.target.bssid, time.time() - attack_start, total_ivs))
162170
return self.success
163171

164172
if aircrack and aircrack.is_running():
@@ -215,7 +223,7 @@ def run(self):
215223
wep_attack_type = WEPAttackType('forgedreplay')
216224
attack_name = 'forgedreplay'
217225
aireplay = Aireplay(self.target,
218-
'forgedreplay',
226+
wep_attack_type,
219227
client_mac=client_mac,
220228
replay_file=replay_file)
221229
time_unchanged_ivs = time.time() # Reset unchanged IVs time (it may have taken a
@@ -288,6 +296,8 @@ def run(self):
288296
Airodump.delete_airodump_temp_files('wep')
289297

290298
self.success = False
299+
log_info('AttackWEP', 'WEP attack on %s finished in %.1fs — no key' % (
300+
self.target.bssid, time.time() - attack_start))
291301
return self.success
292302

293303
@staticmethod

wifite/attack/wpa.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from ..util.color import Color
1111
from ..util.timer import Timer
1212
from ..util.output import OutputManager
13+
from ..util.logger import log_debug, log_info, log_warning, log_error
1314
from ..model.handshake import Handshake
1415
from ..model.wpa_result import CrackResultWPA
1516
from ..util.wpasec_uploader import WpaSecUploader
@@ -299,7 +300,12 @@ def _get_interface_assignment(self):
299300

300301
def run(self):
301302
"""Initiates full WPA handshake capture attack."""
302-
303+
304+
log_info('AttackWPA', 'Starting WPA attack on %s (%s) ch %s pwr %s' % (
305+
self.target.essid or '?', self.target.bssid,
306+
self.target.channel, self.target.power))
307+
self._attack_start_time = time.time()
308+
303309
# Start TUI view if available
304310
if self.view:
305311
self.view.start()
@@ -446,6 +452,10 @@ def run(self):
446452
self.crack_result = CrackResultWPA(handshake.bssid, handshake.essid, handshake.capfile, key)
447453
self.crack_result.dump()
448454
self.success = True
455+
456+
elapsed = time.time() - getattr(self, '_attack_start_time', time.time())
457+
log_info('AttackWPA', 'WPA attack on %s finished in %.1fs — %s' % (
458+
self.target.bssid, elapsed, 'SUCCESS' if self.success else 'no key'))
449459
return self.success
450460

451461
def _handle_attack_failure(self, message):

wifite/attack/wpa3.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from ..tools.aireplay import Aireplay
1717
from ..config import Configuration
1818
from ..util.color import Color
19+
from ..util.logger import log_info, log_debug
1920
from ..util.timer import Timer
2021
from ..util.output import OutputManager
2122
from ..util.wpa3 import WPA3Detector, WPA3Info
@@ -71,6 +72,10 @@ def run(self):
7172
Returns:
7273
bool: True if attack succeeded, False otherwise
7374
"""
75+
log_info('AttackWPA3', 'Starting WPA3 attack on %s (%s) ch %s' % (
76+
self.target.essid or '?', self.target.bssid, self.target.channel))
77+
attack_start = time.time()
78+
7479
# Check for required WPA3 tools
7580
if not self._check_wpa3_tools():
7681
return False
@@ -96,16 +101,21 @@ def run(self):
96101

97102
# Execute strategy based on selection
98103
if self.attack_strategy == WPA3AttackStrategy.DOWNGRADE:
99-
return self._execute_downgrade_strategy()
104+
result = self._execute_downgrade_strategy()
100105
elif self.attack_strategy == WPA3AttackStrategy.DRAGONBLOOD:
101-
return self._execute_dragonblood_strategy()
106+
result = self._execute_dragonblood_strategy()
102107
elif self.attack_strategy == WPA3AttackStrategy.SAE_CAPTURE:
103-
return self._execute_sae_capture_strategy()
108+
result = self._execute_sae_capture_strategy()
104109
elif self.attack_strategy == WPA3AttackStrategy.PASSIVE:
105-
return self._execute_passive_strategy()
110+
result = self._execute_passive_strategy()
106111
else:
107112
Color.pl('{!} {R}Unknown attack strategy: %s{W}' % self.attack_strategy)
108-
return False
113+
result = False
114+
115+
log_info('AttackWPA3', 'WPA3 attack on %s finished in %.1fs — %s' % (
116+
self.target.bssid, time.time() - attack_start,
117+
'SUCCESS' if result else 'failed'))
118+
return result
109119

110120
def _check_wpa3_tools(self):
111121
"""

wifite/attack/wps.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
#!/usr/bin/env python
22
# -*- coding: utf-8 -*-
33

4+
import time
5+
46
from ..model.attack import Attack
57
from ..util.color import Color
8+
from ..util.logger import log_info, log_debug
69
from ..config import Configuration
710
from ..util.output import OutputManager
811
from ..tools.bully import Bully
@@ -35,7 +38,12 @@ def __init__(self, target, pixie_dust=False, null_pin=False):
3538

3639
def run(self):
3740
""" Run all WPS-related attacks """
38-
41+
42+
mode = 'pixie-dust' if self.pixie_dust else ('null-pin' if self.null_pin else 'pin')
43+
log_info('AttackWPS', 'Starting WPS %s attack on %s (%s)' % (
44+
mode, self.target.essid or '?', self.target.bssid))
45+
self._attack_start = time.time()
46+
3947
# Start TUI view if available
4048
if self.view:
4149
self.view.start()
@@ -96,6 +104,7 @@ def _handle_wps_skip(self, message):
96104
return False
97105

98106
def run_bully(self):
107+
log_debug('AttackWPS', 'Using bully for WPS attack')
99108
bully = Bully(self.target, pixie_dust=self.pixie_dust)
100109
# Pass the view to bully for TUI updates
101110
if self.view:
@@ -104,14 +113,21 @@ def run_bully(self):
104113
bully.stop()
105114
self.crack_result = bully.crack_result
106115
self.success = self.crack_result is not None
116+
log_info('AttackWPS', 'WPS bully attack on %s finished in %.1fs — %s' % (
117+
self.target.bssid, time.time() - getattr(self, '_attack_start', time.time()),
118+
'SUCCESS' if self.success else 'no pin'))
107119
return self.success
108120

109121
def run_reaver(self):
122+
log_debug('AttackWPS', 'Using reaver for WPS attack')
110123
reaver = Reaver(self.target, pixie_dust=self.pixie_dust, null_pin=self.null_pin)
111124
# Pass the view to reaver for TUI updates
112125
if self.view:
113126
reaver.attack_view = self.view
114127
reaver.run()
115128
self.crack_result = reaver.crack_result
116129
self.success = self.crack_result is not None
130+
log_info('AttackWPS', 'WPS reaver attack on %s finished in %.1fs — %s' % (
131+
self.target.bssid, time.time() - getattr(self, '_attack_start', time.time()),
132+
'SUCCESS' if self.success else 'no pin'))
117133
return self.success

wifite/config.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,21 @@ def parse_settings_args(cls, args):
719719
Color.pl('{+} {C}option: {O}ignoring ESSID(s): {R}%s{W}' %
720720
', '.join(args.ignore_essids))
721721

722+
if args.ignore_essids_file is not None:
723+
try:
724+
with open(args.ignore_essids_file, 'r', encoding='utf-8', errors='replace') as fh:
725+
file_essids = [line.strip() for line in fh if line.strip() and not line.startswith('#')]
726+
if file_essids:
727+
cls.ignore_essids = list(set((cls.ignore_essids or []) + file_essids))
728+
Color.pl('{+} {C}option: {O}ignoring {R}%d{O} ESSID(s) from file {R}%s{W}' %
729+
(len(file_essids), args.ignore_essids_file))
730+
else:
731+
Color.pl('{!} {O}ignore-essids-file {R}%s{O} is empty or has only comments{W}' %
732+
args.ignore_essids_file)
733+
except (OSError, IOError) as e:
734+
Color.pl('{!} {R}Could not read ignore-essids-file {O}%s{R}: %s{W}' %
735+
(args.ignore_essids_file, str(e)))
736+
722737
from .model.result import CrackResult
723738
cls.ignore_cracked = CrackResult.load_ignored_bssids(args.ignore_cracked)
724739

@@ -747,14 +762,28 @@ def parse_settings_args(cls, args):
747762
Color.pl('{+} {C}option:{W} using {G}classic text mode{W} (TUI disabled)')
748763
# else: use_tui remains False (classic mode is default)
749764

765+
# --debug is shorthand for -vvv + file logging
766+
if args.debug:
767+
args.verbose = max(args.verbose, 3)
768+
750769
if args.verbose:
751770
cls.verbose = args.verbose
752771
Color.pl('{+} {C}option:{W} verbosity level {G}%d{W}' % args.verbose)
753772

754-
# Update logger with verbosity level
773+
# Determine log file: explicit --log-file wins, then --debug default, then -vv+ default
755774
from .util.logger import Logger
756-
log_file = os.path.join(os.path.expanduser('~'), '.wifite', 'wifite.log') if args.verbose >= 2 else None
775+
log_file = getattr(args, 'log_file', None)
776+
if log_file is None and args.verbose >= 2:
777+
log_file = os.path.join(os.path.expanduser('~'), '.wifite', 'wifite.log')
778+
if log_file:
779+
Color.pl('{+} {C}option:{W} logging to {G}%s{W}' % log_file)
757780
Logger.initialize(log_file=log_file, verbose=args.verbose, enabled=True)
781+
elif getattr(args, 'log_file', None):
782+
# --log-file without -v: enable at least verbose=2 so the log is useful
783+
cls.verbose = max(cls.verbose, 2)
784+
from .util.logger import Logger
785+
Color.pl('{+} {C}option:{W} logging to {G}%s{W} (verbose level raised to 2)' % args.log_file)
786+
Logger.initialize(log_file=args.log_file, verbose=cls.verbose, enabled=True)
758787

759788
if args.kill_conflicting_processes:
760789
cls.kill_conflicting_processes = True

wifite/model/handshake.py

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from ..util.process import Process
55
from ..util.color import Color
6+
from ..util.logger import log_debug, log_info
67
from ..tools.tshark import Tshark
78

89
import re
@@ -37,7 +38,7 @@ def divine_bssid_and_essid(self):
3738
# Tshark failed us, nothing else we can do.
3839
raise ValueError(f'Cannot find BSSID or ESSID in cap file {self.capfile}')
3940

40-
if not self.essid and not self.bssid:
41+
if not self.essid and not self.bssid and len(pairs) > 0:
4142
# We do not know the bssid nor the essid
4243
# TODO: Display menu for user to select from list
4344
# HACK: Just use the first one we see
@@ -60,18 +61,29 @@ def divine_bssid_and_essid(self):
6061
Color.pl('\n{+} Discovered essid "{C}%s{W}"' % essid)
6162
self.essid = essid
6263
break
64+
6365
def has_handshake(self):
6466
if not self.bssid or not self.essid:
6567
self.divine_bssid_and_essid()
6668

67-
#return len(self.tshark_handshakes()) > 0
69+
log_debug('Handshake', 'Checking %s for handshake (bssid=%s essid=%s)' % (
70+
self.capfile, self.bssid, self.essid))
71+
6872
# Prefer strict validators. Do NOT accept aircrack alone as proof.
6973
# Tshark requires the full 4-way (strict) — keep it.
70-
if len(self.tshark_handshakes()) > 0:
74+
tshark_results = self.tshark_handshakes()
75+
if len(tshark_results) > 0:
76+
log_info('Handshake', 'tshark confirmed handshake in %s' % self.capfile)
7177
return True
7278

7379
# cowpatty can be reliable for 2&3 captures
74-
return len(self.cowpatty_handshakes()) > 0
80+
cowpatty_results = self.cowpatty_handshakes()
81+
if len(cowpatty_results) > 0:
82+
log_info('Handshake', 'cowpatty confirmed handshake in %s' % self.capfile)
83+
return True
84+
85+
log_debug('Handshake', 'No valid handshake found in %s' % self.capfile)
86+
return False
7587

7688
def tshark_handshakes(self):
7789
"""Returns list[tuple] of BSSID & ESSID pairs (ESSIDs are always `None`)."""
@@ -98,20 +110,21 @@ def cowpatty_handshakes(self):
98110
command.append('-2')
99111
command.extend([
100112
'-r', self.capfile,
101-
'-c' # Check for handshake (requires ESSID)
113+
'-c', # Check for handshake
102114
])
103-
104-
# Add ESSID if available (required for -c option)
115+
116+
# Add ESSID (required for -c option)
105117
if self.essid:
106-
command.append(self.essid)
118+
command.extend(['-s', self.essid])
119+
else:
120+
return [] # cowpatty -c requires an ESSID
107121

108122
proc = Process(command, devnull=False)
109123
return next(
110124
(
111125
[(None, self.essid)]
112126
for line in proc.stdout().split('\n')
113-
if 'Collected all necessary data to '
114-
'mount crack against WPA' in line
127+
if 'Collected all necessary data to mount crack against WPA' in line
115128
),
116129
[],
117130
)

0 commit comments

Comments
 (0)