Skip to content

Commit 7e95691

Browse files
committed
OWO: add OWO detection support
1 parent f05128c commit 7e95691

7 files changed

Lines changed: 595 additions & 34 deletions

File tree

wifite/model/target.py

Lines changed: 77 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,10 @@ def __init__(self, fields):
139139
# For compatibility with existing logic that expects a single string:
140140
self.encryption = self.primary_encryption # Overwrite with primary for now
141141
self.authentication = self.primary_authentication # Overwrite with primary for now
142+
143+
# WPA3 information (will be populated by scanner)
144+
self.wpa3_info = None
145+
142146
self.validate()
143147

144148
def __eq__(self, other):
@@ -169,6 +173,36 @@ def transfer_info(self, other):
169173
if hasattr(self, 'primary_authentication'):
170174
other.primary_authentication = self.primary_authentication
171175
other.full_authentication_string = self.full_authentication_string
176+
if hasattr(self, 'wpa3_info'):
177+
other.wpa3_info = self.wpa3_info
178+
179+
@property
180+
def is_wpa3(self):
181+
"""Check if target supports WPA3."""
182+
if self.wpa3_info is None:
183+
return False
184+
return self.wpa3_info.has_wpa3
185+
186+
@property
187+
def is_transition(self):
188+
"""Check if target is in WPA3 transition mode (supports both WPA2 and WPA3)."""
189+
if self.wpa3_info is None:
190+
return False
191+
return self.wpa3_info.is_transition
192+
193+
@property
194+
def pmf_status(self):
195+
"""Get PMF (Protected Management Frames) status."""
196+
if self.wpa3_info is None:
197+
return 'disabled'
198+
return self.wpa3_info.pmf_status
199+
200+
@property
201+
def is_dragonblood_vulnerable(self):
202+
"""Check if target is vulnerable to Dragonblood attacks."""
203+
if self.wpa3_info is None:
204+
return False
205+
return self.wpa3_info.dragonblood_vulnerable
172206

173207
def validate(self):
174208
""" Checks that the target is valid. """
@@ -232,37 +266,53 @@ def to_str(self, show_bssid=False, show_manufacturer=False):
232266
channel = Color.s(f'{channel_color}{str(self.channel).rjust(3)}')
233267

234268
# Use primary_encryption and primary_authentication for display
235-
display_encryption = self.primary_encryption.rjust(4) # Adjusted rjust for WPA3
236-
auth_suffix = ''
237-
if self.primary_encryption == 'WPA3':
238-
display_encryption = Color.s('{P}%s' % display_encryption) # Purple for WPA3
239-
if self.primary_authentication == 'SAE':
240-
auth_suffix = Color.s('{P}-S') # Purple for SAE
241-
elif self.primary_authentication == 'MGT':
242-
auth_suffix = Color.s('{R}-E') # Red for Enterprise
243-
elif self.primary_encryption == 'WPA2':
244-
display_encryption = Color.s('{O}%s' % display_encryption) # Orange for WPA2
245-
if self.primary_authentication == 'PSK':
246-
auth_suffix = Color.s('{O}-P')
247-
elif self.primary_authentication == 'MGT':
248-
auth_suffix = Color.s('{R}-E')
249-
elif self.primary_encryption == 'WPA':
250-
display_encryption = Color.s('{O}%s' % display_encryption) # Orange for WPA
251-
if self.primary_authentication == 'PSK':
252-
auth_suffix = Color.s('{O}-P')
253-
elif self.primary_authentication == 'MGT':
254-
auth_suffix = Color.s('{R}-E')
255-
elif self.primary_encryption == 'WEP':
256-
display_encryption = Color.s('{G}%s' % display_encryption) # Green for WEP
257-
elif self.primary_encryption == 'OWE':
258-
display_encryption = Color.s('{B}%s' % display_encryption) # Blue for OWE
269+
# Check for WPA3 transition mode
270+
if self.is_transition:
271+
# Show "W23" for transition mode (WPA2/WPA3)
272+
display_encryption = Color.s('{P}W23') # Purple for WPA3 transition
273+
auth_suffix = ''
274+
# Add PMF indicator for transition mode
275+
if self.pmf_status == 'required':
276+
auth_suffix = Color.s('{P}+') # PMF required
277+
elif self.pmf_status == 'optional':
278+
auth_suffix = Color.s('{O}~') # PMF optional
259279
else:
260-
display_encryption = Color.s('{W}%s' % display_encryption) # White for others
280+
display_encryption = self.primary_encryption.rjust(4) # Adjusted rjust for WPA3
281+
auth_suffix = ''
282+
if self.primary_encryption == 'WPA3':
283+
display_encryption = Color.s('{P}%s' % display_encryption) # Purple for WPA3
284+
if self.primary_authentication == 'SAE':
285+
auth_suffix = Color.s('{P}-S') # Purple for SAE
286+
elif self.primary_authentication == 'MGT':
287+
auth_suffix = Color.s('{R}-E') # Red for Enterprise
288+
# Add PMF indicator for WPA3-only
289+
if self.pmf_status == 'required':
290+
auth_suffix += Color.s('{P}+') # PMF required
291+
elif self.primary_encryption == 'WPA2':
292+
display_encryption = Color.s('{O}%s' % display_encryption) # Orange for WPA2
293+
if self.primary_authentication == 'PSK':
294+
auth_suffix = Color.s('{O}-P')
295+
elif self.primary_authentication == 'MGT':
296+
auth_suffix = Color.s('{R}-E')
297+
elif self.primary_encryption == 'WPA':
298+
display_encryption = Color.s('{O}%s' % display_encryption) # Orange for WPA
299+
if self.primary_authentication == 'PSK':
300+
auth_suffix = Color.s('{O}-P')
301+
elif self.primary_authentication == 'MGT':
302+
auth_suffix = Color.s('{R}-E')
303+
elif self.primary_encryption == 'WEP':
304+
display_encryption = Color.s('{G}%s' % display_encryption) # Green for WEP
305+
elif self.primary_encryption == 'OWE':
306+
display_encryption = Color.s('{B}%s' % display_encryption) # Blue for OWE
307+
else:
308+
display_encryption = Color.s('{W}%s' % display_encryption) # White for others
261309

262310
# Calculate padding for ENCR column based on its content length
263-
# Max length of ENCR (e.g. WPA2-P) is 6. OPN is 3.
311+
# Max length of ENCR (e.g. WPA2-P or W23+) is now variable
264312
# Pad with spaces to ensure alignment
265-
encryption_padding = " " * (5 - len(self.primary_encryption + self.primary_authentication)) # Max length of WPA2-P is 6 (WPA2 + -P)
313+
base_len = 3 if self.is_transition else len(self.primary_encryption)
314+
suffix_len = len(auth_suffix.replace(Color.s('{P}'), '').replace(Color.s('{O}'), '').replace(Color.s('{R}'), '').replace(Color.s('{W}'), ''))
315+
encryption_padding = " " * max(0, 7 - base_len - suffix_len)
266316
encryption_display_string = f"{display_encryption}{auth_suffix}{encryption_padding}"
267317

268318
power = f'{str(self.power).rjust(3)}db'

wifite/tools/airodump.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from .tshark import Tshark
66
from .wash import Wash
77
from ..util.process import Process
8+
from ..util.wpa3 import WPA3Detector, WPA3Info
89
from ..config import Configuration
910
from ..model.target import Target, WPSState
1011
from ..model.client import Client
@@ -199,6 +200,9 @@ def get_targets(self, old_targets=None, apply_filter=True, target_archives=None)
199200
# No tshark, or it failed. Fall-back to wash
200201
Wash.check_for_wps_and_update_targets(capfile, new_targets)
201202

203+
# Detect WPA3 capabilities for all targets
204+
Airodump.detect_wpa3_capabilities(new_targets)
205+
202206
if apply_filter:
203207
# Filter targets based on encryption, WPS capability & power
204208
new_targets = Airodump.filter_targets(new_targets, skip_wps=self.skip_wps)
@@ -286,6 +290,32 @@ def get_targets_from_csv(csv_filename):
286290

287291
return targets2
288292

293+
@staticmethod
294+
def detect_wpa3_capabilities(targets):
295+
"""
296+
Detect and store WPA3 capabilities for all targets.
297+
298+
This method analyzes each target's encryption and authentication
299+
information to determine WPA3 support, transition mode, PMF status,
300+
and other WPA3-related capabilities.
301+
302+
Performance: Detection results are cached in target.wpa3_info.
303+
Subsequent calls to WPA3Detector methods will use cached data.
304+
305+
Args:
306+
targets: List of Target objects to analyze
307+
"""
308+
for target in targets:
309+
# Skip if already detected (cached)
310+
if hasattr(target, 'wpa3_info') and target.wpa3_info is not None:
311+
continue
312+
313+
# Detect WPA3 capability (use_cache=False since we're setting it)
314+
wpa3_info_dict = WPA3Detector.detect_wpa3_capability(target, use_cache=False)
315+
316+
# Create WPA3Info object and attach to target for caching
317+
target.wpa3_info = WPA3Info.from_dict(wpa3_info_dict)
318+
289319
@staticmethod
290320
def filter_targets(targets5, skip_wps=False):
291321
""" Filters targets based on Configuration """

wifite/ui/components.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,16 +61,45 @@ class EncryptionBadge:
6161
}
6262

6363
@staticmethod
64-
def render(encryption_type: str) -> Text:
64+
def render(encryption_type: str, target=None) -> Text:
6565
"""
6666
Render encryption type as a colored badge.
6767
6868
Args:
6969
encryption_type: Encryption type string (e.g., "WPA2", "WEP")
70+
target: Optional Target object for WPA3 transition mode detection
7071
7172
Returns:
7273
Rich Text object with colored encryption badge
7374
"""
75+
# Check for WPA3 transition mode
76+
if target and hasattr(target, 'is_transition') and target.is_transition:
77+
text = Text("W23", style="magenta") # Magenta for transition mode
78+
# Add PMF indicator
79+
if hasattr(target, 'pmf_status'):
80+
if target.pmf_status == 'required':
81+
text.append("+", style="green") # PMF required
82+
elif target.pmf_status == 'optional':
83+
text.append("~", style="yellow") # PMF optional
84+
# Add Dragonblood vulnerability indicator
85+
if hasattr(target, 'wpa3_info') and target.wpa3_info and target.wpa3_info.dragonblood_vulnerable:
86+
text.append("!", style="red bold") # Dragonblood vulnerable
87+
return text
88+
89+
# Check for WPA3-only with PMF indicator
90+
if target and hasattr(target, 'is_wpa3') and target.is_wpa3:
91+
enc_upper = encryption_type.upper()
92+
color = EncryptionBadge.COLORS.get(enc_upper, "white")
93+
text = Text(encryption_type, style=color)
94+
# Add PMF indicator for WPA3
95+
if hasattr(target, 'pmf_status') and target.pmf_status == 'required':
96+
text.append("+", style="green") # PMF required
97+
# Add Dragonblood vulnerability indicator
98+
if hasattr(target, 'wpa3_info') and target.wpa3_info and target.wpa3_info.dragonblood_vulnerable:
99+
text.append("!", style="red bold") # Dragonblood vulnerable
100+
return text
101+
102+
# Standard encryption display
74103
enc_upper = encryption_type.upper()
75104
color = EncryptionBadge.COLORS.get(enc_upper, "white")
76105
return Text(encryption_type, style=color)

wifite/ui/scanner_view.py

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,20 @@
2020
class ScannerView:
2121
"""Interactive scanning interface with real-time updates."""
2222

23-
def __init__(self, tui_controller):
23+
def __init__(self, tui_controller, session=None):
2424
"""
2525
Initialize scanner view.
2626
2727
Args:
2828
tui_controller: TUIController instance for rendering
29+
session: Optional SessionState for resumed sessions
2930
"""
3031
self.tui = tui_controller
3132
self.targets = []
3233
self.scan_start_time = time.time()
3334
self.decloaking = False
3435
self.max_visible_targets = 50 # Limit displayed targets for performance
36+
self.session = session # Store session for resume status display
3537

3638
def update_targets(self, targets: List, decloaking: bool = False):
3739
"""
@@ -79,7 +81,8 @@ def _render_header(self) -> Panel:
7981

8082
# Count targets by encryption type
8183
wep_count = sum(1 for t in self.targets if 'WEP' in t.encryption)
82-
wpa_count = sum(1 for t in self.targets if 'WPA' in t.encryption)
84+
wpa_count = sum(1 for t in self.targets if 'WPA' in t.encryption and not hasattr(t, 'is_wpa3') or not t.is_wpa3)
85+
wpa3_count = sum(1 for t in self.targets if hasattr(t, 'is_wpa3') and t.is_wpa3)
8386
wps_count = sum(1 for t in self.targets if hasattr(t, 'wps') and t.wps)
8487

8588
# Count clients
@@ -88,7 +91,33 @@ def _render_header(self) -> Panel:
8891
# Build header text - single line for compactness
8992
header = Text()
9093
header.append("wifite2 ", style="bold cyan")
91-
header.append("- Scanning", style="bold yellow")
94+
95+
# Show resume indicator if this is a resumed session
96+
if self.session:
97+
header.append("- ", style="white")
98+
header.append("RESUMED SESSION", style="bold magenta")
99+
header.append(" - Scanning", style="bold yellow")
100+
101+
# Add session progress info
102+
summary = self.session.get_progress_summary()
103+
header.append(f" | Progress: ", style="white")
104+
header.append(f"{summary['completed']}", style="green")
105+
header.append("/", style="white")
106+
header.append(f"{summary['total']}", style="cyan")
107+
108+
# Add session age
109+
age_hours = summary['age_hours']
110+
if age_hours < 1:
111+
age_str = f"{int(age_hours * 60)}m"
112+
elif age_hours < 24:
113+
age_str = f"{int(age_hours)}h"
114+
else:
115+
age_str = f"{int(age_hours / 24)}d"
116+
header.append(f" | Age: ", style="white")
117+
header.append(age_str, style="yellow")
118+
else:
119+
header.append("- Scanning", style="bold yellow")
120+
92121
if self.decloaking:
93122
header.append(" & decloaking", style="yellow")
94123
header.append(f" {minutes:02d}:{seconds:02d} | ", style="white")
@@ -98,6 +127,8 @@ def _render_header(self) -> Panel:
98127
header.append(f"{wep_count}", style="red")
99128
header.append(f" | WPA: ", style="white")
100129
header.append(f"{wpa_count}", style="yellow")
130+
header.append(f" | WPA3: ", style="white")
131+
header.append(f"{wpa3_count}", style="magenta")
101132
header.append(f" | WPS: ", style="white")
102133
header.append(f"{wps_count}", style="cyan")
103134
header.append(f" | Clients: ", style="white")
@@ -141,7 +172,7 @@ def _render_targets_table(self) -> Table:
141172

142173
table.add_column("CH", style="white", width=3, justify="center")
143174
table.add_column("PWR", style="white", width=5, justify="center")
144-
table.add_column("ENC", style="white", width=8)
175+
table.add_column("ENC", style="white", width=12) # Increased width for WPA3 indicators
145176
table.add_column("WPS", style="white", width=4, justify="center")
146177
table.add_column("CLIENTS", style="white", width=7, justify="right")
147178

@@ -237,14 +268,31 @@ def _format_power(self, target) -> Text:
237268
def _format_encryption(self, target) -> Text:
238269
"""
239270
Format encryption type with color coding.
271+
Shows WPA3/Transition/WPA2 status, PMF indicators, and Dragonblood markers.
240272
241273
Args:
242274
target: Target object
243275
244276
Returns:
245277
Rich Text with colored encryption badge
246278
"""
247-
return EncryptionBadge.render(target.encryption)
279+
enc_text = EncryptionBadge.render(target.encryption, target)
280+
281+
# Add PMF indicator if WPA3 info is available
282+
if hasattr(target, 'wpa3_info') and target.wpa3_info:
283+
if target.wpa3_info.pmf_status == 'required':
284+
enc_text.append(" ", style="white")
285+
enc_text.append("🛡", style="cyan") # Shield for PMF required
286+
elif target.wpa3_info.pmf_status == 'optional':
287+
enc_text.append(" ", style="white")
288+
enc_text.append("◐", style="yellow") # Half-circle for PMF optional
289+
290+
# Add Dragonblood vulnerability marker
291+
if hasattr(target, 'is_dragonblood_vulnerable') and target.is_dragonblood_vulnerable:
292+
enc_text.append(" ", style="white")
293+
enc_text.append("⚠", style="red bold") # Warning for vulnerability
294+
295+
return enc_text
248296

249297
def _format_wps(self, target) -> Text:
250298
"""

wifite/ui/selector_view.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,7 @@ def _format_encryption(self, target) -> Text:
342342
Returns:
343343
Rich Text with colored encryption badge
344344
"""
345-
return EncryptionBadge.render(target.encryption)
345+
return EncryptionBadge.render(target.encryption, target)
346346

347347
def _format_wps(self, target) -> Text:
348348
"""

0 commit comments

Comments
 (0)