Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
79 changes: 79 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Contributing to wifite2

Thank you for your interest in contributing. Please follow these guidelines to keep the project consistent and maintainable.

---

## Development Environment

```bash
# Clone your fork
git clone https://github.com/<your-username>/wifite2.git
cd wifite2

# Create and activate a virtual environment
python3 -m venv .venv
source .venv/bin/activate

# Install in editable mode with all dependencies
pip install -e .
pip install -r requirements.txt
```

---

## Running the Test Suite

```bash
pytest tests/ -v
```

All tests must pass before submitting a pull request. If you add new functionality, add corresponding tests in `tests/`.

Some tests require external tools (`tshark`, `aircrack-ng`, `cowpatty`). Install them on your system or mock them in your test fixtures.

---

## Code Style

- Follow **PEP 8** for all Python code.
- Use the existing `Color` and `Output` utilities (`wifite/util/color.py`) for any user-facing output. Do not use bare `print()` calls.
- Use `log_debug()` / `log_info()` from `wifite/util/logger.py` for internal diagnostic messages.
- Keep functions focused. Refactor large blocks into helper methods rather than adding deeply nested logic.
- Type hints are welcome but not required for existing modules; new modules should include them where practical.

---

## Submitting a Pull Request

1. **Fork** the repository and create a feature branch off `master`:
```bash
git checkout -b feature/my-improvement
```
2. Make your changes, following the style guidelines above.
3. Run the test suite and confirm it passes.
4. Commit with a clear, concise message describing *what* changed and *why*.
5. Push your branch to your fork and open a **Pull Request against `master`**.
6. Fill in the PR description: what problem it solves, how it was tested, and any known limitations.

PRs that break existing tests or lack a description will not be merged.

---

## Testing Requirements

Contributions that touch attack logic, capture parsing, or tool integrations **must** be tested on real hardware or with mocked interfaces. Specifically:

- Use `.cap` / `.pcapng` fixture files in `tests/files/` for capture-related changes.
- Mock external process calls (`aircrack-ng`, `tshark`, etc.) using `unittest.mock` where live hardware is not available.
- Document in your PR description how you verified the change (hardware model, driver, OS, or mock strategy).

---

## Responsible Disclosure

If you discover a security vulnerability in wifite2 itself (not a vulnerability in a third-party tool it wraps), **do not open a public GitHub issue**.

Instead, email the maintainer directly. Provide a clear description of the issue, reproduction steps, and potential impact. Allow reasonable time for a fix before any public disclosure.

Public issues are appropriate for bugs, feature requests, and usage questions, not for 0-day disclosures.
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
chardet>=7.4.3
requests>=2.33.1
rich>=15.0.0
scapy>=2.7.1rc1; python_version < '4'
scapy>=2.6.0; python_version < '4'
setuptools>=82.0.1

# Development dependencies (optional)
Expand Down
6 changes: 6 additions & 0 deletions wifite/args.py
Original file line number Diff line number Diff line change
Expand Up @@ -1106,6 +1106,12 @@ def _add_command_args(commands):
dest='crack_handshake',
help=Color.s('Show commands to crack a captured handshake'))

commands.add_argument('--print-crack-cmd',
action='store_true',
dest='show_cracking_command',
help=Color.s('Print the exact cracking command before running it, '
'for easy copy/pasting into a terminal (default: {G}off{W})'))

commands.add_argument('--syscheck',
action='store_true',
dest='syscheck',
Expand Down
8 changes: 4 additions & 4 deletions wifite/attack/wep.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class AttackWEP(Attack):
Contains logic for attacking a WEP-encrypted access point.
"""

fakeauth_wait = 5 # TODO: Configuration?
fakeauth_wait = 5

def __init__(self, target):
super(AttackWEP, self).__init__(target)
Expand Down Expand Up @@ -206,7 +206,6 @@ def run(self):
if not xor_file:
# If .xor is not there, the process failed.
Color.pl('\n{!} {O}%s attack{R} did not generate a .xor file' % attack_name)
# XXX: For debugging
Color.pl('{?} {O}Command: {R}%s{W}' % ' '.join(aireplay.cmd))
Color.pl('{?} {O}Output:\n{R}%s{W}' % aireplay.get_output())
break
Expand Down Expand Up @@ -381,14 +380,15 @@ def fake_auth(self):
Returns: True if successful, False is unsuccessful.
"""
Color.p('\r{+} attempting {G}fake-authentication{W} with {C}%s{W}...' % self.target.bssid)
fakeauth = Aireplay.fakeauth(self.target, timeout=AttackWEP.fakeauth_wait)
fakeauth_wait = Configuration.wep_fakeauth_time or AttackWEP.fakeauth_wait
fakeauth = Aireplay.fakeauth(self.target, timeout=fakeauth_wait)
Comment on lines +383 to +384

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Expose fake-auth timeout through argument parsing

AttackWEP now reads Configuration.wep_fakeauth_time, but this commit never adds a CLI/config parser path that can set that field, so the timeout remains hard-coded at the default 5 in practice. In runs where fake-auth takes longer (especially with --require-fakeauth), users still cannot increase the wait time despite the new “configurable” code path, and attacks fail as before.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in bef667cd5 by adding --wep-fakeauth-time [seconds] and wiring it through the WEP argument parser so Configuration.wep_fakeauth_time is user-configurable.

if fakeauth:
Color.pl(' {G}success{W}')
else:
Color.pl(' {R}failed{W}')
if Configuration.require_fakeauth:
# Fakeauth is required, fail
raise Exception('Fake-authenticate did not complete within %d seconds' % AttackWEP.fakeauth_wait)
raise Exception('Fake-authenticate did not complete within %d seconds' % fakeauth_wait)
# Warn that fakeauth failed
Color.pl('{!} {O} unable to fake-authenticate with target (%s){W}' % self.target.bssid)
Color.pl('{!} continuing attacks because {G}--require-fakeauth{W} was not set')
Expand Down
4 changes: 4 additions & 0 deletions wifite/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class Configuration:
random_mac = None
random_mac_vendor = None
require_fakeauth = None
show_cracking_command = False
scan_time = None
show_bssids = None
show_cracked = None
Expand Down Expand Up @@ -97,6 +98,7 @@ class Configuration:
wep_attacks = None
wep_crack_at_ivs = None
wep_filter = None
wep_fakeauth_time = 5
wep_keep_ivs = None
wep_pps = None
wep_restart_aircrack = None
Expand Down Expand Up @@ -216,6 +218,8 @@ def load_from_arguments(cls):
cls.check_handshake = args.check_handshake
if args.crack_handshake:
cls.crack_handshake = True
if hasattr(args, 'show_cracking_command') and args.show_cracking_command:
cls.show_cracking_command = True
if args.update_db:
cls.update_db = True
if hasattr(args, 'syscheck') and args.syscheck:
Expand Down
4 changes: 3 additions & 1 deletion wifite/config/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def initialize_defaults(cls):
cls.interface_primary = None # Primary interface name
cls.interface_secondary = None # Secondary interface name
cls.auto_assign_interfaces = True # Auto-assign interfaces (default True)
cls.prefer_dual_interface = True # Prefer dual over single when available (default True)
cls.prefer_dual_interface = False # Prefer dual over single when available (default False)
cls.use_hcxdump = False # Use hcxdumptool for dual interface WPA capture (default False)

# WEP variables
Expand All @@ -70,6 +70,7 @@ def initialize_defaults(cls):
cls.wep_timeout = 600 # Seconds to wait before failing
cls.wep_crack_at_ivs = 10000 # Minimum IVs to start cracking
cls.require_fakeauth = False
cls.wep_fakeauth_time = 5 # Seconds to wait for fake-authentication to complete
cls.wep_restart_stale_ivs = 11 # Seconds to wait before restarting
# Aireplay if IVs don't increase.
# '0' means never restart.
Expand Down Expand Up @@ -152,6 +153,7 @@ def initialize_defaults(cls):
cls.wps_timeout_threshold = 100 # Max number of timeouts

# Commands
cls.show_cracking_command = False # Print exact cracking command to stdout (--print-crack-cmd)
cls.show_cracked = False
cls.show_ignored = False
cls.check_handshake = None
Expand Down
38 changes: 32 additions & 6 deletions wifite/model/handshake.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,38 @@ def divine_bssid_and_essid(self):

if not self.essid and not self.bssid and len(pairs) > 0:
# We do not know the bssid nor the essid
# TODO: Display menu for user to select from list
# HACK: Just use the first one we see
self.bssid = pairs[0][0]
self.essid = pairs[0][1]
Color.pl('{!} {O}Warning{W}: {O}Arbitrarily selected ' +
'{R}bssid{O} {C}%s{O} and {R}essid{O} "{C}%s{O}"{W}' % (self.bssid, self.essid))
if len(pairs) == 1:
self.bssid = pairs[0][0]
self.essid = pairs[0][1]
else:
# Multiple networks in capture; no prior context to choose from.
# TODO: Display menu for user to select from list
chosen_bssid, chosen_essid = pairs[0]
Color.pl(
'{!} {O}Warning{W}: Multiple networks found in capture file. '
'Using first: {C}%s{W} ({C}%s{W}). '
'Use {G}--bssid{W} to target a specific network.' % (chosen_bssid, chosen_essid)
)
self.bssid = chosen_bssid
self.essid = chosen_essid
elif not self.essid and self.bssid and len(pairs) > 1:
# BSSID is already set (e.g. from attack context or filename); try to match it.
matched = next(
((bssid, essid) for (bssid, essid) in pairs if bssid.lower() == self.bssid.lower()),
None,
)
if matched:
self.essid = matched[1]
else:
# Pre-set BSSID not found among captured pairs; warn and fall back to first.
chosen_bssid, chosen_essid = pairs[0]
Color.pl(
'{!} {O}Warning{W}: Multiple networks found in capture file. '
'BSSID {C}%s{W} not matched; using first: {C}%s{W} ({C}%s{W}). '
'Use {G}--bssid{W} to target a specific network.' % (self.bssid, chosen_bssid, chosen_essid)
)
self.bssid = chosen_bssid
self.essid = chosen_essid

if not self.bssid:
# We already know essid
Expand Down
40 changes: 32 additions & 8 deletions wifite/util/crack.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,22 @@
from ..util.sae_crack import SAECracker


# TODO: Bring back the 'print' option, for easy copy/pasting. Just one-liners people can paste into terminal.
def print_cracking_command(tool_name, command_parts):
"""
Print the exact shell command that would be run for cracking,
so users can copy-paste it into a terminal.

Used by cracking helpers that need a consistently formatted command line.

Args:
tool_name: Human-readable label for the tool (e.g. 'aircrack-ng').
command_parts: List of strings forming the shell command (argv style).
"""
import shlex
from ..util.color import Color
cmd_str = ' '.join(shlex.quote(str(p)) for p in command_parts)
Color.pl('{+} {C}[crack-cmd]{W} {G}%s{W}: {W}%s' % (tool_name, cmd_str))


Comment on lines +21 to 37
def decode_hex_essid_if_needed(essid):
"""
Expand Down Expand Up @@ -383,20 +398,23 @@ def crack_4way(cls, hs, tool):
Color.pl('{!} {R}Error: {O}%s{W}' % e)
return None

# show_command is True when verbose or when --print-crack-cmd is set
show_cmd = Configuration.show_cracking_command or Configuration.verbose > 0

wordlists_to_try = Configuration.wordlists if Configuration.wordlists else [Configuration.wordlist]

for wordlist in wordlists_to_try:
wordlist_name = os.path.basename(wordlist)
Color.pl('{+} Trying wordlist: {C}%s{W}' % wordlist_name)

if tool == 'aircrack':
key = Aircrack.crack_handshake(handshake, show_command=True, wordlist=wordlist)
key = Aircrack.crack_handshake(handshake, show_command=show_cmd, wordlist=wordlist)
elif tool == 'hashcat':
key = Hashcat.crack_handshake(handshake, target_is_wpa3_sae=False, show_command=True, wordlist=wordlist)
key = Hashcat.crack_handshake(handshake, target_is_wpa3_sae=False, show_command=show_cmd, wordlist=wordlist)
elif tool == 'john':
key = John.crack_handshake(handshake, show_command=True, wordlist=wordlist)
key = John.crack_handshake(handshake, show_command=show_cmd, wordlist=wordlist)
elif tool == 'cowpatty':
key = Cowpatty.crack_handshake(handshake, show_command=True, wordlist=wordlist)
key = Cowpatty.crack_handshake(handshake, show_command=show_cmd, wordlist=wordlist)

if key is not None:
return CrackResultWPA(hs['bssid'], hs['essid'], hs['filename'], key)
Expand All @@ -408,12 +426,15 @@ def crack_pmkid(cls, hs, tool):
if tool != 'hashcat':
Color.pl('{!} {O}Note: PMKID hashes can only be cracked using {C}hashcat{W}')

# show_command is True when verbose or when --print-crack-cmd is set
show_cmd = Configuration.show_cracking_command or Configuration.verbose > 0

wordlists_to_try = Configuration.wordlists if Configuration.wordlists else [Configuration.wordlist]

for wordlist in wordlists_to_try:
wordlist_name = os.path.basename(wordlist)
Color.pl('{+} Trying wordlist: {C}%s{W}' % wordlist_name)
key2 = Hashcat.crack_pmkid(hs['filename'], verbose=True, wordlist=wordlist)
key2 = Hashcat.crack_pmkid(hs['filename'], verbose=show_cmd, wordlist=wordlist)
if key2 is not None:
return CrackResultPMKID(hs['bssid'], hs['essid'], hs['filename'], key2)

Expand All @@ -426,6 +447,9 @@ def crack_sae(cls, hs, tool):
Color.pl('{!} {O}Note: WPA3-SAE handshakes can only be cracked using {C}hashcat{W}')
return None

# show_command is True when verbose or when --print-crack-cmd is set
show_cmd = Configuration.show_cracking_command or Configuration.verbose > 0

# Create SAEHandshake object
sae_handshake = SAEHandshake(
capfile=hs['filename'],
Expand All @@ -441,8 +465,8 @@ def crack_sae(cls, hs, tool):
key = SAECracker.crack_sae_handshake(
sae_handshake,
wordlist=wordlist,
show_command=True,
verbose=True
show_command=show_cmd,
verbose=show_cmd
)
if key is not None:
return CrackResultSAE(hs['bssid'], hs['essid'], hs['filename'], key)
Expand Down
30 changes: 20 additions & 10 deletions wifite/wifite.py
Original file line number Diff line number Diff line change
Expand Up @@ -932,19 +932,24 @@ def _target_from_state(target_state):
if power > 0:
power = power - 100

# Use the current datetime for first/last seen so restored targets sort
# and display with a reasonable timestamp instead of a year-2000 stub.
from datetime import datetime
now_str = datetime.now().strftime('%Y-%m-%d %H:%M:%S')

fields = [
target_state.bssid, # 0: BSSID
'2000-01-01 00:00:00', # 1: First time seen (placeholder)
'2000-01-01 00:00:00', # 2: Last time seen (placeholder)
now_str, # 1: First time seen (current time; original not stored in session)
now_str, # 2: Last time seen (current time; original not stored in session)
str(target_state.channel), # 3: channel
'54', # 4: Speed (placeholder)
'54', # 4: Speed: airodump-ng "speed" field; not stored in session, 54 Mbps is a safe neutral default
target_state.encryption, # 5: Privacy/Encryption
'CCMP', # 6: Cipher (placeholder)
'', # 7: Authentication (will be derived from encryption)
'CCMP', # 6: Cipher: not stored in session; CCMP is the standard WPA2/WPA3 cipher and a safe default
'', # 7: Authentication (will be derived from encryption below)
str(power), # 8: Power
'10', # 9: beacons (placeholder)
'0', # 10: IV (placeholder)
'0. 0. 0. 0', # 11: LAN IP (placeholder)
'10', # 9: beacons (not stored in session; any non-zero value is a safe placeholder)
'0', # 10: IV (not stored in session; 0 is correct for WPA targets)
'0. 0. 0. 0', # 11: LAN IP (not stored in session; airodump-ng format placeholder)
str(len(target_state.essid)) if target_state.essid else '0', # 12: ID-length
target_state.essid if target_state.essid else '', # 13: ESSID
'' # 14: Key (empty)
Expand Down Expand Up @@ -1362,9 +1367,14 @@ def interface_cleanup():
from .util.logger import log_debug
log_debug('Wifite', f'Interface cleanup thread error: {e}')

# Delete Reaver .pcap quickly
# Delete Reaver .pcap quickly.
# reaver_output.pcap is the legacy bare filename written by older reaver versions
# directly into the cwd. The current Reaver tool class writes to Configuration.temp()
# instead, but we still clean up the legacy name here using an absolute path
# (os.path.abspath) so the removal works regardless of cwd.
Comment on lines +1370 to +1374
try:
subprocess.run(["rm", "-f", "reaver_output.pcap"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=2)
reaver_pcap = os.path.abspath('reaver_output.pcap')
subprocess.run(["rm", "-f", reaver_pcap], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=2)
except Exception as e:
from .util.logger import log_debug
log_debug('Wifite', f'Reaver cleanup error: {e}')
Expand Down