Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# Core dependencies for wifite2
# These should match pyproject.toml [project.dependencies]
chardet>=7.4.3
requests>=2.33.1
requests>=2.34.2
rich>=15.0.0
scapy>=2.7.1rc1; python_version < '4'
setuptools>=82.0.1

# Development dependencies (optional)
# For development, install with: pip install -e ".[dev]"
# (see [project.optional-dependencies] in pyproject.toml for dev pins)
urllib3>=2.6.3 # not directly required, pinned by Snyk to avoid a vulnerability
urllib3>=2.7.0 # not directly required, pinned by Snyk to avoid a vulnerability

# Development / test dependencies are defined once in pyproject.toml
# ([project.optional-dependencies].dev). Install them with: pip install -e ".[dev]"
24 changes: 23 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,25 @@
with open('README.md', 'r', encoding='utf-8') as fh:
long_description = fh.read()


def _read_requirements():
"""Parse runtime dependencies from requirements.txt.

Keeps a single source of truth for runtime deps (shared with the
`pip install -r requirements.txt` path) instead of re-listing them here.
Strips blank lines and comments while preserving PEP 508 environment
markers (e.g. "scapy>=...; python_version < '4'").
"""
requirements = []
with open('requirements.txt', 'r', encoding='utf-8') as fh:
for line in fh:
line = line.split('#', 1)[0].strip()
if line:
requirements.append(line)
return requirements
Comment on lines +22 to +28

setup(
name='wifite',
name='wifite2',
version=Configuration.version,
author='kimocoder',
author_email='christian@aircrack-ng.org',
Expand All @@ -22,6 +39,11 @@
},
license='GNU GPLv2',
scripts=['bin/wifite'],
python_requires='>=3.10',
install_requires=_read_requirements(),
# Dev/test extras are defined once in pyproject.toml
# ([project.optional-dependencies].dev), which poetry-core uses as the build
# backend. Install them with: pip install -e ".[dev]"
description='Wireless Network Auditor for Linux & Android',
long_description=long_description,
long_description_content_type='text/markdown',
Expand Down
5 changes: 4 additions & 1 deletion tests/test_Handshake.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,10 @@ def testHandshakeTshark(self):
def testHandshakeCowpatty(self):
print("\nTesting handshake with cowpatty...")
hs_file = self.getFile('handshake_exists.cap')
hs = Handshake(hs_file, bssid='A4:2B:8C:16:6B:3A')
# cowpatty -c needs an ESSID, so pass it explicitly. (tshark/aircrack
# tests don't need this — only cowpatty enforces it.)
hs = Handshake(hs_file, bssid='A4:2B:8C:16:6B:3A',
essid='Test Router Please Ignore')
assert (len(hs.cowpatty_handshakes()) > 0), f'Expected len>0 but got len({len(hs.cowpatty_handshakes())})'

@pytest.mark.timeout(15)
Expand Down
4 changes: 2 additions & 2 deletions wifite/attack/owe.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,8 +237,8 @@ def _capture_owe_exchange(self):
if client.station not in clients_seen:
clients_seen.add(client.station)
Color.pl('{+} {G}New client:{W} %s' % client.station)
except Exception:
pass
except Exception as e:
log_debug('AttackOWE', 'Client tracking error: %s' % e)

# Deauth to trigger re-association
if deauth_timer.ended() and len(clients_seen) > 0:
Expand Down
96 changes: 38 additions & 58 deletions wifite/model/handshake.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,74 +147,54 @@ def aircrack_handshakes(self):
else:
return []

def hcxpcapngtool_handshakes(self):
"""
Returns tuple (BSSID,None) if hcxpcapngtool can extract valid handshake data.
Supports both .cap and .pcapng capture files.
"""
if not Process.exists('hcxpcapngtool'):
return []

import tempfile

# Create a temporary hash file to test if hcxpcapngtool can extract data
hash_file = None
try:
with tempfile.NamedTemporaryFile(mode='w', suffix='.22000', delete=False) as tmp:
hash_file = tmp.name

command = [
'hcxpcapngtool',
'-o', hash_file,
self.capfile
]

proc = Process(command, devnull=False)

# Check if hash file was created and has content
if os.path.exists(hash_file) and os.path.getsize(hash_file) > 0:
# Successfully extracted handshake data
result = [(self.bssid, None)] if self.bssid else [(None, self.essid)]

# Clean up temp file
try:
os.remove(hash_file)
except OSError:
pass

return result
else:
# No valid handshake data found
try:
if os.path.exists(hash_file):
os.remove(hash_file)
except OSError:
pass
return []

except Exception:
# If anything goes wrong, clean up and return empty
try:
if hash_file and os.path.exists(hash_file):
os.remove(hash_file)
except (OSError, NameError):
pass
return []

def analyze(self):
"""Prints analysis of handshake capfile"""
self.divine_bssid_and_essid()

if Tshark.exists():
Handshake.print_pairs(self.tshark_handshakes(), 'tshark')
self._print_tshark_analysis()

if Process.exists('cowpatty'):
Handshake.print_pairs(self.cowpatty_handshakes(), 'cowpatty')

Handshake.print_pairs(self.aircrack_handshakes(), 'aircrack')

if Process.exists('hcxpcapngtool'):
Handshake.print_pairs(self.hcxpcapngtool_handshakes(), 'hcxpcapng')

def _print_tshark_analysis(self):
"""
Print tshark verdict for this capfile.

On a full 4-way handshake, emits the standard 'contains a valid handshake'
line. Otherwise inspects the EAPOL frames tshark *did* see and reports
which messages are present and which are missing, so the user can tell
the difference between "no EAPOL at all" and "M1+M2+M3 but no M4
(still crackable by hashcat/cowpatty)".
"""
# Single tshark parse yields both the full-handshake verdict and, when
# absent, the partial-4-way breakdown — no need to read the file twice.
bssids, summary = Tshark.eapol_analysis(self.capfile, bssid=self.bssid)
if bssids:
Handshake.print_pairs([(bssid, None) for bssid in bssids], 'tshark')
return

tool_str = '{C}%s{W}: ' % 'tshark'.rjust(8)

if not summary:
Color.pl('{!} %s.cap file contains {O}no EAPOL frames{W} '
'({R}no handshake to crack{W})' % tool_str)
Comment on lines +181 to +183
return

for bssid, msgs in summary.items():
found = sorted(msgs)
missing = [m for m in (1, 2, 3, 4) if m not in msgs]
found_str = ', '.join('M%d' % m for m in found)
missing_str = ', '.join('M%d' % m for m in missing)
crackable = 2 in msgs and (1 in msgs or 3 in msgs)
verdict = ('{G}crackable partial{W}'
if crackable else
'{R}not crackable{W}')
Color.pl('{!} %s.cap file has {O}partial 4-way{W} for ({O}%s{W}): '
'found {C}%s{W}, missing {R}%s{W} — %s'
% (tool_str, bssid.upper(), found_str, missing_str, verdict))

def strip(self, outfile=None):
# XXX: This method might break aircrack-ng, use at own risk.
Expand Down
11 changes: 7 additions & 4 deletions wifite/native/deauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,10 +351,13 @@ def run(self):
self.packets_sent += sent
self.bursts_sent += 1
self.last_burst_time = time.time()

except Exception:
pass


except Exception as e:
# Best-effort burst loop: keep running, but leave a trace so a
# persistent failure isn't completely invisible.
from ..util.logger import log_debug
log_debug('ScapyDeauth', 'Deauth burst failed: %s' % e)

# Wait for next burst
self._stop_event.wait(timeout=self.interval)

Expand Down
7 changes: 5 additions & 2 deletions wifite/tools/john.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,11 @@ def _read_stderr(self):
# Progress line: "0g 0:00:00:03 13% (ETA: ...) 0g/s 500p/s ..."
if re.match(r'\d+g\s+\d+:\d+:\d+:\d+', line):
self._parse_progress(line)
except Exception:
pass
except Exception as e:
# Reader thread is best-effort (progress display only); log so a
# crash here doesn't silently stop progress updates without a trace.
from ..util.logger import log_debug
log_debug('John', 'Progress reader thread stopped: %s' % e)

def _parse_progress(self, line):
"""Update internal status from a john stderr progress line."""
Expand Down
73 changes: 70 additions & 3 deletions wifite/tools/tshark.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,8 @@ def bssids_with_handshakes(cls, capfile, bssid=None):
'-n',
'-Y', 'eapol'
]
tshark = Process(command, devnull=False)

target_client_msg_nums = Tshark._build_target_client_handshake_map(tshark.stdout(), bssid=bssid)
with Process(command, devnull=False) as tshark:
target_client_msg_nums = Tshark._build_target_client_handshake_map(tshark.stdout(), bssid=bssid)

bssids = set()
for (target_client, num) in list(target_client_msg_nums.items()):
Expand All @@ -128,6 +127,74 @@ def bssids_with_handshakes(cls, capfile, bssid=None):

return list(bssids)

@staticmethod
def _build_eapol_summary(output, bssid=None):
"""Build a per-AP map of which 4-way messages appear in tshark output.

Returns dict mapping bssid (lowercase) -> set of int msg numbers in
{1,2,3,4}.
"""
summary = {}
for line in output.split('\n'):
src, dst, index, total = Tshark._extract_src_dst_index_total(line)
if src is None:
continue
index = int(index)
total = int(total)
if total != 4:
continue
# Odd indexes (1, 3) are AP→client; even (2, 4) are client→AP.
target = src if index % 2 == 1 else dst
if bssid is not None and bssid.lower() != target.lower():
continue
summary.setdefault(target.lower(), set()).add(index)
return summary

@classmethod
def eapol_message_summary(cls, capfile, bssid=None):
"""
Inspect EAPOL traffic and return a per-AP map of which 4-way messages
were observed.

Returns:
dict mapping bssid (lowercase) -> set of int msg numbers in {1,2,3,4}.
Empty dict if tshark isn't available or no EAPOL frames are present.
"""
if not cls.exists():
return {}

command = ['tshark', '-r', capfile, '-n', '-Y', 'eapol']
with Process(command, devnull=False) as tshark:
output = tshark.stdout()
return cls._build_eapol_summary(output, bssid=bssid)
Comment on lines +166 to +169

@classmethod
def eapol_analysis(cls, capfile, bssid=None):
"""Run tshark once and derive both EAPOL verdicts from a single parse.

Combines the work of bssids_with_handshakes() and
eapol_message_summary() so the capfile is only read once when the caller
needs both (e.g. handshake analysis that falls back to a partial-4-way
report when no complete handshake is present).

Returns:
(bssids, summary) tuple where `bssids` is the list of APs with a
complete sequential 4-way handshake and `summary` is the per-AP map
of observed message numbers (see eapol_message_summary). Both are
empty if tshark is unavailable or no EAPOL frames are present.
"""
if not cls.exists():
return [], {}

command = ['tshark', '-r', capfile, '-n', '-Y', 'eapol']
with Process(command, devnull=False) as tshark:
output = tshark.stdout()

handshake_map = cls._build_target_client_handshake_map(output, bssid=bssid)
bssids = list({key.split(',')[0] for key, num in handshake_map.items() if num == 4})
summary = cls._build_eapol_summary(output, bssid=bssid)
return bssids, summary
Comment on lines +189 to +196

@classmethod
def bssids_with_handshakes_native(cls, capfile, bssid=None):
"""
Expand Down
8 changes: 4 additions & 4 deletions wifite/ui/tui.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,16 +152,16 @@ def update(self, renderable):
if self.should_update():
try:
self.live.update(renderable, refresh=True)
except Exception as e:
except Exception:
# If update fails, try to recover or fail gracefully
try:
# Attempt to refresh without update
if self.live is not None:
self.live.refresh()
except (AttributeError, TypeError, ImportError):
# Complete failure - stop TUI
except Exception:
# Complete failure — stop TUI so the caller can fall back
# to classic output instead of hammering a broken Live.
self.is_running = False
pass

def __enter__(self):
"""
Expand Down
4 changes: 3 additions & 1 deletion wifite/util/tui_logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ def initialize(cls, enabled: bool = False, debug_mode: bool = False, log_file: s
Args:
enabled: Whether logging is enabled
debug_mode: Whether debug mode is active
log_file: Path to log file (default: /tmp/wifite_tui.log)
log_file: Path to log file (default: ~/.config/wifite/wifite_tui.log,
in an owner-only 0700 directory; falls back to ~ if that
directory can't be created)
"""
cls._enabled = enabled
cls._debug_mode = debug_mode
Expand Down
Loading