Skip to content

Commit cab79ac

Browse files
authored
Merge pull request #537 from kimocoder/fix/latent-nameerror-bugs
Fix/latent nameerror bugs
2 parents d7628c6 + e6813e5 commit cab79ac

52 files changed

Lines changed: 249 additions & 213 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

tests/test_cleanup.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ def test_get_errors(self):
144144
self.assertIn('error1', errors)
145145
self.assertIn('error2', errors)
146146

147-
@patch('wifite.util.cleanup.subprocess.run')
147+
@patch('wifite.util.process.subprocess.run')
148148
def test_remove_iptables_rule(self, mock_run):
149149
"""Test removing an iptables rule."""
150150
mock_run.return_value = Mock(returncode=0)
@@ -155,7 +155,7 @@ def test_remove_iptables_rule(self, mock_run):
155155
self.assertTrue(result)
156156
mock_run.assert_called_once()
157157

158-
@patch('wifite.util.cleanup.subprocess.run')
158+
@patch('wifite.util.process.subprocess.run')
159159
def test_check_conflicting_processes(self, mock_run):
160160
"""Test checking for conflicting processes."""
161161
# Mock pgrep returning PIDs
@@ -166,7 +166,7 @@ def test_check_conflicting_processes(self, mock_run):
166166
# Should find some processes
167167
self.assertIsInstance(conflicting, list)
168168

169-
@patch('wifite.util.cleanup.subprocess.run')
169+
@patch('wifite.util.process.subprocess.run')
170170
def test_kill_orphaned_processes(self, mock_run):
171171
"""Test killing orphaned processes."""
172172
# Mock pgrep returning PIDs

tests/test_wpa3_detection_performance.py

Lines changed: 95 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
"""
1010

1111
import unittest
12-
import time
1312
from wifite.model.target import Target
1413
from wifite.util.wpa3 import WPA3Detector
1514

@@ -57,104 +56,105 @@ def setUp(self):
5756
''
5857
]
5958

60-
def test_cache_performance_improvement(self):
61-
"""Benchmark cache performance improvement."""
62-
target = Target(self.wpa3_transition_fields)
63-
iterations = 1000
64-
65-
# Measure time without cache (fresh detection each time)
66-
start_time = time.time()
67-
for _ in range(iterations):
68-
WPA3Detector.detect_wpa3_capability(target, use_cache=False)
69-
no_cache_time = time.time() - start_time
70-
71-
# Set cache once
72-
wpa3_info_dict = WPA3Detector.detect_wpa3_capability(target, use_cache=False)
59+
def test_cache_short_circuits_detection(self):
60+
"""Cached detection returns the stored result without recomputing.
61+
62+
Behavioural check (deterministic, not timing-based): when a target
63+
already carries a ``wpa3_info`` cache, ``use_cache=True`` must return
64+
that cached value verbatim, while ``use_cache=False`` must ignore the
65+
cache and recompute from the target's encryption fields. We prove this
66+
by seeding the cache with a sentinel that deliberately disagrees with
67+
what the fields would produce.
68+
"""
7369
from wifite.util.wpa3 import WPA3Info
74-
target.wpa3_info = WPA3Info.from_dict(wpa3_info_dict)
75-
76-
# Measure time with cache
77-
start_time = time.time()
78-
for _ in range(iterations):
79-
WPA3Detector.detect_wpa3_capability(target, use_cache=True)
80-
cache_time = time.time() - start_time
81-
82-
# Cache should be significantly faster
83-
speedup = no_cache_time / cache_time if cache_time > 0 else float('inf')
84-
85-
print(f"\nCache Performance Benchmark ({iterations} iterations):")
86-
print(f" Without cache: {no_cache_time:.4f}s")
87-
print(f" With cache: {cache_time:.4f}s")
88-
print(f" Speedup: {speedup:.2f}x")
89-
90-
# Cache should be at least 2x faster
91-
self.assertGreater(speedup, 2.0,
92-
f"Cache speedup {speedup:.2f}x is less than expected 2x")
93-
94-
def test_early_return_performance(self):
95-
"""Benchmark early return optimization for WPA2-only targets."""
96-
wpa2_target = Target(self.wpa2_only_fields)
97-
wpa3_target = Target(self.wpa3_transition_fields)
98-
iterations = 1000
99-
100-
# Measure WPA2-only detection (should be faster with early return)
101-
start_time = time.time()
102-
for _ in range(iterations):
103-
WPA3Detector.detect_wpa3_capability(wpa2_target, use_cache=False)
104-
wpa2_time = time.time() - start_time
105-
106-
# Measure WPA3 detection (full processing)
107-
start_time = time.time()
108-
for _ in range(iterations):
109-
WPA3Detector.detect_wpa3_capability(wpa3_target, use_cache=False)
110-
wpa3_time = time.time() - start_time
111-
112-
print(f"\nEarly Return Benchmark ({iterations} iterations):")
113-
print(f" WPA2-only (early return): {wpa2_time:.4f}s")
114-
print(f" WPA3 (full processing): {wpa3_time:.4f}s")
115-
print(f" Ratio: {wpa3_time/wpa2_time:.2f}x")
116-
117-
# WPA2-only should be faster or similar (early return optimization)
118-
# Allow some variance due to system load
119-
self.assertLessEqual(wpa2_time, wpa3_time * 1.5,
120-
"WPA2-only detection should benefit from early return")
12170

122-
def test_helper_method_cache_usage(self):
123-
"""Benchmark helper methods using cache vs fresh detection."""
12471
target = Target(self.wpa3_transition_fields)
125-
iterations = 1000
126-
127-
# Without cache - helper methods trigger full detection
128-
start_time = time.time()
129-
for _ in range(iterations):
130-
target.wpa3_info = None # Clear cache
131-
WPA3Detector.identify_transition_mode(target)
132-
WPA3Detector.check_pmf_status(target)
133-
WPA3Detector.get_supported_sae_groups(target)
134-
no_cache_time = time.time() - start_time
135-
136-
# With cache - helper methods use cached data
137-
wpa3_info_dict = WPA3Detector.detect_wpa3_capability(target, use_cache=False)
72+
73+
# What a fresh (uncached) detection derives from the fields.
74+
fresh = WPA3Detector.detect_wpa3_capability(target, use_cache=False)
75+
self.assertTrue(fresh['has_wpa3'],
76+
'Fixture should detect WPA3 from the encryption fields')
77+
78+
# Seed the cache with a sentinel that disagrees with the fields, so a
79+
# cache hit is distinguishable from a recompute.
80+
sentinel = WPA3Info.from_dict({
81+
'has_wpa3': False,
82+
'has_wpa2': True,
83+
'is_transition': False,
84+
'pmf_status': WPA3Detector.PMF_DISABLED,
85+
'sae_groups': [],
86+
'dragonblood_vulnerable': False,
87+
})
88+
target.wpa3_info = sentinel
89+
90+
# Cache hit: must return the sentinel, proving detection was skipped.
91+
cached = WPA3Detector.detect_wpa3_capability(target, use_cache=True)
92+
self.assertEqual(cached, sentinel.to_dict(),
93+
'Cached path must return the stored wpa3_info verbatim')
94+
self.assertFalse(cached['has_wpa3'],
95+
'Cached path must not recompute from the fields')
96+
97+
# Cache bypass: must recompute and match the fresh detection.
98+
recomputed = WPA3Detector.detect_wpa3_capability(target, use_cache=False)
99+
self.assertEqual(recomputed, fresh,
100+
'use_cache=False must ignore the cache and recompute')
101+
self.assertTrue(recomputed['has_wpa3'])
102+
103+
def test_early_return_for_wpa2_only(self):
104+
"""WPA2-only targets take the early-return branch (no WPA3 work).
105+
106+
Behavioural check (deterministic): a target whose fields advertise no
107+
WPA3/SAE must short-circuit to the WPA2-only result shape, while a
108+
WPA3 transition target must go through full detection. This exercises
109+
the same early-return optimisation the old benchmark targeted, without
110+
asserting on wall-clock time.
111+
"""
112+
wpa2 = WPA3Detector.detect_wpa3_capability(
113+
Target(self.wpa2_only_fields), use_cache=False)
114+
wpa3 = WPA3Detector.detect_wpa3_capability(
115+
Target(self.wpa3_transition_fields), use_cache=False)
116+
117+
# Early-return branch: WPA2-only, not transition, no SAE groups.
118+
self.assertFalse(wpa2['has_wpa3'])
119+
self.assertFalse(wpa2['is_transition'])
120+
self.assertEqual(wpa2['sae_groups'], [])
121+
self.assertFalse(wpa2['dragonblood_vulnerable'])
122+
123+
# Full-detection branch still flags WPA3.
124+
self.assertTrue(wpa3['has_wpa3'])
125+
126+
def test_helper_methods_use_cache(self):
127+
"""Helper methods read cached wpa3_info instead of recomputing.
128+
129+
Deterministic check: seed the cache with a sentinel that disagrees
130+
with the target's fields, then confirm each helper returns the cached
131+
value. Clearing the cache makes them recompute from the fields.
132+
"""
138133
from wifite.util.wpa3 import WPA3Info
139-
target.wpa3_info = WPA3Info.from_dict(wpa3_info_dict)
140-
141-
start_time = time.time()
142-
for _ in range(iterations):
143-
WPA3Detector.identify_transition_mode(target)
144-
WPA3Detector.check_pmf_status(target)
145-
WPA3Detector.get_supported_sae_groups(target)
146-
cache_time = time.time() - start_time
147-
148-
speedup = no_cache_time / cache_time if cache_time > 0 else float('inf')
149-
150-
print(f"\nHelper Method Cache Benchmark ({iterations} iterations):")
151-
print(f" Without cache: {no_cache_time:.4f}s")
152-
print(f" With cache: {cache_time:.4f}s")
153-
print(f" Speedup: {speedup:.2f}x")
154-
155-
# Cache should provide significant speedup
156-
self.assertGreater(speedup, 2.0,
157-
f"Helper method cache speedup {speedup:.2f}x is less than expected")
134+
135+
target = Target(self.wpa3_transition_fields)
136+
137+
sentinel = WPA3Info.from_dict({
138+
'has_wpa3': True,
139+
'has_wpa2': True,
140+
'is_transition': False, # disagrees with the fields
141+
'pmf_status': WPA3Detector.PMF_REQUIRED,
142+
'sae_groups': [21], # not the default [19]
143+
'dragonblood_vulnerable': False,
144+
})
145+
target.wpa3_info = sentinel
146+
147+
# Cache hit: helpers return the sentinel's values.
148+
self.assertEqual(WPA3Detector.identify_transition_mode(target),
149+
sentinel.is_transition)
150+
self.assertEqual(WPA3Detector.check_pmf_status(target),
151+
sentinel.pmf_status)
152+
self.assertEqual(WPA3Detector.get_supported_sae_groups(target),
153+
sentinel.sae_groups)
154+
155+
# Cache cleared: helpers recompute from the fields (transition mode).
156+
target.wpa3_info = None
157+
self.assertTrue(WPA3Detector.identify_transition_mode(target))
158158

159159

160160
if __name__ == '__main__':

wifite/args.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ def __init__(self, configuration):
1414
# Hack: Check for -v before parsing args;
1515
# so we know which commands to display.
1616
self.verbose = '-v' in sys.argv or '-hv' in sys.argv or '-vh' in sys.argv
17+
# Activate Kalidroid MiniTerminal output mode as early as possible (before
18+
# any option-echo prints) so the whole run streams scrollback-friendly
19+
# output to the Kalidroid app's MiniTerminal.
20+
if '--kalidroid' in sys.argv:
21+
Color.kalidroid = True
1722
self.config = configuration
1823
self.args = self.get_arguments()
1924

@@ -270,6 +275,15 @@ def _add_global_args(self, glob):
270275
help=self._verbose(
271276
'Write debug log to {C}[path]{W} (implies {C}-vv{W} minimum verbosity)'))
272277

278+
glob.add_argument('--kalidroid',
279+
action='store_true',
280+
default=False,
281+
dest='kalidroid',
282+
help=self._verbose(
283+
'Emit {C}MiniTerminal{W}-friendly output for the {C}Kalidroid{W} app: '
284+
'flatten carriage-return progress redraws into discrete lines and '
285+
'skip clear-line escapes (default: {G}off{W})'))
286+
273287
glob.add_argument('-i',
274288
action='store',
275289
dest='interface',

wifite/attack/all.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from ..config import Configuration
1313
from ..model.target import WPSState
1414
from ..util.color import Color
15-
from ..util.logger import log_info, log_debug
15+
from ..util.logger import log_info
1616
from ..util.wpa3_tools import WPA3ToolChecker
1717
from ..util.memory import MemoryMonitor, get_infinite_monitor
1818

wifite/attack/attack_monitor.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,7 @@
1313
from ..util.color import Color
1414
from ..tools.tshark import TsharkMonitor
1515
from ..util.process import Process
16-
import os
1716
import time
18-
import re
1917

2018
# TUI imports (optional)
2119
try:

wifite/attack/eviltwin.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,12 @@
1212
import re
1313
import time
1414
import signal
15-
from typing import Optional, List
15+
from typing import TYPE_CHECKING
1616
from enum import Enum
1717

18+
if TYPE_CHECKING:
19+
from ..util.session import EvilTwinAttackState
20+
1821
from ..model.attack import Attack
1922
from ..model.eviltwin_result import CrackResultEvilTwin
2023
from ..config import Configuration
@@ -25,7 +28,6 @@
2528
from ..util.cleanup import CleanupManager
2629
from ..util.adaptive_deauth import AdaptiveDeauthManager
2730
from ..util.process import Process
28-
from ..tools.aireplay import Aireplay
2931

3032

3133
class AttackState(Enum):
@@ -251,7 +253,6 @@ def _get_interface_assignment(self):
251253
# This is a fallback for when the attack is run directly
252254
import contextlib
253255
with contextlib.suppress(ImportError, AttributeError):
254-
from ..wifite import Wifite
255256
# Note: This is a fallback and may not always work
256257
# The preferred approach is to set interface_assignment before calling run()
257258
log_debug('EvilTwin', 'No interface assignment available, will use single interface mode')
@@ -1472,7 +1473,6 @@ def _send_deauth(self, client_mac: str, count: int = 5):
14721473
count: Number of deauth packets to send
14731474
"""
14741475
try:
1475-
from ..tools.aireplay import Aireplay
14761476
from ..util.process import Process
14771477

14781478
# Verify deauth interface is available
@@ -2074,7 +2074,6 @@ def restore_state_from_session(self, state: 'EvilTwinAttackState') -> bool:
20742074
True if state was restored successfully, False otherwise
20752075
"""
20762076
try:
2077-
from ..util.session import EvilTwinAttackState
20782077

20792078
# Restore configuration
20802079
self.interface_ap = state.interface_ap or self.interface_ap
@@ -2137,7 +2136,6 @@ def is_attack_running() -> bool:
21372136
Returns:
21382137
True if an attack is running, False otherwise
21392138
"""
2140-
import subprocess
21412139

21422140
try:
21432141
# Check for hostapd processes with wifite config
@@ -2168,7 +2166,6 @@ def cleanup_orphaned_processes(self) -> None:
21682166
other processes that may have been left running from a previous
21692167
interrupted attack.
21702168
"""
2171-
import subprocess
21722169

21732170
log_info('EvilTwin', 'Checking for orphaned processes')
21742171

wifite/attack/pmkid.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
# Check for native PMKID availability
2222
try:
23-
from ..native.pmkid import ScapyPMKID, PMKIDResult as NativePMKIDResult
23+
from ..native.pmkid import ScapyPMKID
2424
NATIVE_PMKID_AVAILABLE = ScapyPMKID.is_available()
2525
except BaseException:
2626
NATIVE_PMKID_AVAILABLE = False
@@ -565,7 +565,7 @@ def crack_pmkid_file(self, pmkid_file):
565565
def _handle_pmkid_crack_success(self, key, pmkid_file):
566566
# Successfully cracked.
567567
if self.view:
568-
self.view.add_log(f"Successfully cracked PMKID!")
568+
self.view.add_log("Successfully cracked PMKID!")
569569
self.view.add_log(f"Password: {mask_sensitive(key)}")
570570
self.view.update_progress({
571571
'progress': 1.0,
@@ -633,7 +633,7 @@ def capture_pmkid_native(self):
633633
def on_pmkid_captured(result):
634634
log_info('AttackPMKID', f'Native capture found PMKID: {result.pmkid[:16]}...')
635635
if self.view:
636-
self.view.add_log(f'PMKID captured!')
636+
self.view.add_log('PMKID captured!')
637637

638638
# Use ScapyPMKID capture
639639
result = ScapyPMKID.capture(

wifite/attack/portal/credential_handler.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
from typing import Optional, Callable, Dict, List, Tuple
1313
from queue import Queue, Empty, Full
1414
from dataclasses import dataclass
15-
from datetime import datetime
1615

1716
from ...util.logger import log_info, log_error, log_warning, log_debug
1817

wifite/attack/portal/server.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import os
1111
import threading
1212
import time
13-
from http.server import HTTPServer, ThreadingHTTPServer, BaseHTTPRequestHandler
13+
from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
1414
from urllib.parse import parse_qs, urlparse
1515
from typing import Optional, Callable, Dict, Any
1616
import socket

wifite/attack/portal/templates.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,8 @@
99

1010
import html
1111
import os
12-
from typing import Dict, Any, Optional
1312

14-
from ...util.logger import log_info, log_error, log_warning, log_debug
13+
from ...util.logger import log_error, log_debug
1514

1615

1716
class TemplateRenderer:

0 commit comments

Comments
 (0)