Skip to content

Commit 4269ca4

Browse files
committed
hcxdumptool made it's way inside with us
1 parent 2e9ae23 commit 4269ca4

26 files changed

Lines changed: 7514 additions & 147 deletions

README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,26 @@ Wifite is designed to use all known methods for retrieving the password of a wir
2828

2929
Run wifite, select your targets, and Wifite will automatically start trying to capture or crack the password.
3030

31+
### Quick Start with Dual Interfaces
32+
33+
If you have two wireless adapters, wifite can use them simultaneously for significantly improved performance:
34+
35+
```bash
36+
# Automatic dual interface mode (wifite detects and assigns interfaces)
37+
sudo wifite --dual-interface
38+
39+
# Evil Twin attack with dual interfaces (30-50% faster)
40+
sudo wifite --dual-interface --eviltwin
41+
42+
# WPA attack with dual interfaces (continuous capture, no packet loss)
43+
sudo wifite --dual-interface --wpa
44+
45+
# Manual interface selection
46+
sudo wifite --interface-primary wlan0 --interface-secondary wlan1
47+
```
48+
49+
**Benefits:** Eliminates mode switching, enables parallel operations, improves reliability. See the [Dual Interface Guide](docs/DUAL_INTERFACE_GUIDE.md) for complete details.
50+
3151
Supported Operating Systems
3252
---------------------------
3353

@@ -186,6 +206,12 @@ Features
186206
* **Smart Deauthentication** - Automatically forces clients to rogue AP
187207
* **📖 Complete Guide:** [Evil Twin Attack Guide](docs/EVILTWIN_GUIDE.md)
188208
* **🔧 Troubleshooting:** [Evil Twin Troubleshooting](docs/EVILTWIN_TROUBLESHOOTING.md)
209+
* **Dual Wireless Interface Support** - Use two adapters simultaneously for improved performance (use with: `--dual-interface`)
210+
* **Parallel Operations** - Eliminates mode switching delays (30-50% faster attacks)
211+
* **Continuous Capture** - No packet loss during deauthentication
212+
* **Automatic Assignment** - Intelligently assigns interfaces based on capabilities
213+
* **Manual Control** - Specify primary and secondary interfaces manually
214+
* **Backward Compatible** - Seamlessly falls back to single interface mode
189215

190216
### Smart Features
191217
* **Automatic Target Detection** - Scans and identifies vulnerable networks
@@ -525,6 +551,11 @@ For Evil Twin specific help:
525551
sudo wifite -h -v | grep -A 20 "EVIL TWIN"
526552
```
527553

554+
For Dual Interface specific help:
555+
```bash
556+
sudo wifite -h -v | grep -A 20 "DUAL INTERFACE"
557+
```
558+
528559

529560
Contributing
530561
------------

tests/test_dual_hcxdump_capture.py

Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
4+
"""
5+
Integration tests for hcxdumptool-based dual interface WPA capture.
6+
7+
Tests core functionality of the hcxdump capture method.
8+
"""
9+
10+
import unittest
11+
import sys
12+
from unittest.mock import Mock, patch, MagicMock
13+
14+
# Mock sys.argv to prevent argparse from reading test arguments
15+
original_argv = sys.argv
16+
sys.argv = ['wifite']
17+
18+
from wifite.config import Configuration
19+
from wifite.model.target import Target
20+
from wifite.model.interface_info import InterfaceAssignment
21+
22+
# Set required Configuration attributes
23+
Configuration.interface = 'wlan0'
24+
Configuration.wpa_attack_timeout = 10
25+
Configuration.wpa_deauth_timeout = 2
26+
Configuration.use_hcxdump = True
27+
Configuration.no_deauth = False
28+
Configuration.ignore_old_handshakes = True
29+
30+
from wifite.attack.wpa import AttackWPA
31+
32+
# Restore original argv
33+
sys.argv = original_argv
34+
35+
36+
class TestDualHcxdumpCapture(unittest.TestCase):
37+
"""Test hcxdumptool-based dual interface capture."""
38+
39+
def setUp(self):
40+
"""Set up test fixtures."""
41+
self.mock_target = Mock(spec=Target)
42+
self.mock_target.bssid = 'AA:BB:CC:DD:EE:FF'
43+
self.mock_target.essid = 'TestNetwork'
44+
self.mock_target.channel = 6
45+
self.mock_target.pmf_required = False
46+
47+
# Create dual interface assignment
48+
self.assignment = InterfaceAssignment(
49+
attack_type='wpa',
50+
primary='wlan0mon',
51+
secondary='wlan1mon',
52+
primary_role='Handshake capture',
53+
secondary_role='Deauthentication'
54+
)
55+
56+
def test_method_exists(self):
57+
"""Test that _capture_handshake_dual_hcxdump method exists."""
58+
attack = AttackWPA(self.mock_target)
59+
self.assertTrue(hasattr(attack, '_capture_handshake_dual_hcxdump'))
60+
self.assertTrue(callable(getattr(attack, '_capture_handshake_dual_hcxdump')))
61+
62+
63+
class TestParallelDeauth(unittest.TestCase):
64+
"""Test parallel deauthentication functionality."""
65+
66+
def setUp(self):
67+
"""Set up test fixtures."""
68+
self.mock_target = Mock(spec=Target)
69+
self.mock_target.bssid = 'AA:BB:CC:DD:EE:FF'
70+
self.mock_target.essid = 'TestNetwork'
71+
72+
Configuration.no_deauth = False
73+
74+
def test_method_exists(self):
75+
"""Test that _deauth_parallel method exists."""
76+
attack = AttackWPA(self.mock_target)
77+
self.assertTrue(hasattr(attack, '_deauth_parallel'))
78+
self.assertTrue(callable(getattr(attack, '_deauth_parallel')))
79+
80+
@patch('wifite.util.color.Color')
81+
def test_parallel_deauth_respects_no_deauth(self, mock_color):
82+
"""Test that parallel deauth respects no_deauth configuration."""
83+
Configuration.no_deauth = True
84+
85+
# Create attack
86+
attack = AttackWPA(self.mock_target)
87+
attack.capture_interface = 'wlan0mon'
88+
attack.deauth_interface = 'wlan1mon'
89+
90+
# Run parallel deauth - should return immediately
91+
attack._deauth_parallel(self.mock_target)
92+
93+
# No assertions needed - just verify it doesn't crash
94+
95+
96+
class TestErrorHandling(unittest.TestCase):
97+
"""Test error handling and fallback scenarios."""
98+
99+
def setUp(self):
100+
"""Set up test fixtures."""
101+
self.mock_target = Mock(spec=Target)
102+
self.mock_target.bssid = 'AA:BB:CC:DD:EE:FF'
103+
self.mock_target.essid = 'TestNetwork'
104+
self.mock_target.essid_known = True
105+
self.mock_target.channel = 6
106+
self.mock_target.power = -50
107+
self.mock_target.pmf_required = False
108+
109+
# Create dual interface assignment
110+
self.assignment = InterfaceAssignment(
111+
attack_type='wpa',
112+
primary='wlan0mon',
113+
secondary='wlan1mon',
114+
primary_role='Handshake capture',
115+
secondary_role='Deauthentication'
116+
)
117+
118+
Configuration.use_hcxdump = True
119+
Configuration.ignore_old_handshakes = True
120+
121+
@patch('wifite.tools.hcxdumptool.HcxDumpTool')
122+
@patch('wifite.util.color.Color')
123+
@patch('wifite.tools.airmon.Airmon')
124+
def test_fallback_when_tool_not_found(self, mock_airmon, mock_color, mock_hcxdump):
125+
"""Test fallback to airodump-ng when hcxdumptool not found."""
126+
# Mock hcxdumptool not existing
127+
mock_hcxdump.exists.return_value = False
128+
mock_hcxdump.dependency_url = 'https://github.com/ZerBea/hcxdumptool'
129+
130+
# Mock airmon to return monitor interfaces
131+
mock_airmon.start.side_effect = ['wlan0mon', 'wlan1mon']
132+
mock_airmon.stop.return_value = True
133+
134+
# Create attack
135+
attack = AttackWPA(self.mock_target)
136+
attack.interface_assignment = self.assignment
137+
138+
# Mock the airodump fallback method
139+
with patch.object(attack, '_capture_handshake_dual_airodump') as mock_airodump:
140+
mock_airodump.return_value = None
141+
142+
# Run dual interface attack
143+
result = attack._run_dual_interface()
144+
145+
# Verify fallback was called
146+
mock_airodump.assert_called_once()
147+
148+
@patch('wifite.tools.hcxdumptool.HcxDumpTool')
149+
@patch('wifite.util.color.Color')
150+
@patch('wifite.tools.airmon.Airmon')
151+
def test_fallback_when_version_insufficient(self, mock_airmon, mock_color, mock_hcxdump):
152+
"""Test fallback to airodump-ng when hcxdumptool version insufficient."""
153+
# Mock hcxdumptool existing but with insufficient version
154+
mock_hcxdump.exists.return_value = True
155+
mock_hcxdump.check_minimum_version.return_value = False
156+
mock_hcxdump.check_version.return_value = '5.0.0'
157+
158+
# Mock airmon to return monitor interfaces
159+
mock_airmon.start.side_effect = ['wlan0mon', 'wlan1mon']
160+
mock_airmon.stop.return_value = True
161+
162+
# Create attack
163+
attack = AttackWPA(self.mock_target)
164+
attack.interface_assignment = self.assignment
165+
166+
# Mock the airodump fallback method
167+
with patch.object(attack, '_capture_handshake_dual_airodump') as mock_airodump:
168+
mock_airodump.return_value = None
169+
170+
# Run dual interface attack
171+
result = attack._run_dual_interface()
172+
173+
# Verify fallback was called
174+
mock_airodump.assert_called_once()
175+
176+
@patch('wifite.tools.hcxdumptool.HcxDumpTool')
177+
@patch('wifite.tools.hcxdumptool.HcxPcapngTool')
178+
@patch('wifite.util.timer.Timer')
179+
@patch('time.sleep')
180+
@patch('wifite.util.color.Color')
181+
@patch('os.path.exists')
182+
@patch('os.path.getsize')
183+
def test_recovery_from_process_failure(self, mock_getsize, mock_exists, mock_color,
184+
mock_sleep, mock_timer, mock_hcxpcapng, mock_hcxdump):
185+
"""Test recovery when hcxdumptool process dies during capture."""
186+
# Mock hcxdumptool available
187+
mock_hcxdump.exists.return_value = True
188+
mock_hcxdump.check_minimum_version.return_value = True
189+
190+
# Create mock hcxdump instance that dies
191+
mock_instance = MagicMock()
192+
mock_instance.is_running.return_value = False # Process died
193+
mock_instance.has_captured_data.return_value = False
194+
mock_instance.__enter__ = MagicMock(return_value=mock_instance)
195+
mock_instance.__exit__ = MagicMock(return_value=False)
196+
mock_hcxdump.return_value = mock_instance
197+
198+
# Mock timer - create a function that returns properly configured timer instances
199+
def create_timer(*args, **kwargs):
200+
timer = MagicMock()
201+
timer.ended.return_value = False
202+
timer.remaining.return_value = 0
203+
timer.__str__ = MagicMock(return_value='0:00')
204+
return timer
205+
mock_timer.side_effect = create_timer
206+
207+
# Create attack
208+
attack = AttackWPA(self.mock_target)
209+
attack.interface_assignment = self.assignment
210+
attack.capture_interface = 'wlan0mon'
211+
attack.deauth_interface = 'wlan1mon'
212+
attack.clients = []
213+
214+
# Mock the airodump fallback method
215+
with patch.object(attack, '_capture_handshake_dual_airodump') as mock_airodump:
216+
mock_airodump.return_value = None
217+
218+
# Run capture - should detect process death and fall back
219+
result = attack._capture_handshake_dual_hcxdump()
220+
221+
# Verify fallback was called
222+
mock_airodump.assert_called_once()
223+
224+
@patch('wifite.tools.hcxdumptool.HcxDumpTool')
225+
@patch('wifite.tools.hcxdumptool.HcxPcapngTool')
226+
@patch('wifite.util.timer.Timer')
227+
@patch('time.sleep')
228+
@patch('wifite.util.color.Color')
229+
@patch('os.path.exists')
230+
@patch('os.path.getsize')
231+
@patch('os.remove')
232+
def test_large_file_warning(self, mock_remove, mock_getsize, mock_exists, mock_color,
233+
mock_sleep, mock_timer, mock_hcxpcapng, mock_hcxdump):
234+
"""Test warning displayed when capture file exceeds 50MB."""
235+
# Mock hcxdumptool available
236+
mock_hcxdump.exists.return_value = True
237+
mock_hcxdump.check_minimum_version.return_value = True
238+
239+
# Create mock hcxdump instance
240+
mock_instance = MagicMock()
241+
mock_instance.is_running.return_value = True
242+
mock_instance.has_captured_data.return_value = True
243+
mock_instance.__enter__ = MagicMock(return_value=mock_instance)
244+
mock_instance.__exit__ = MagicMock(return_value=False)
245+
mock_hcxdump.return_value = mock_instance
246+
247+
# Mock large file size (60MB)
248+
mock_exists.return_value = True
249+
mock_getsize.return_value = 60 * 1024 * 1024
250+
251+
# Mock timer - create a function that returns properly configured timer instances
252+
timer_call_count = [0]
253+
def create_timer(*args, **kwargs):
254+
timer = MagicMock()
255+
# First two timers (timeout and deauth) don't end, third one (step) ends
256+
if timer_call_count[0] < 2:
257+
timer.ended.return_value = False
258+
else:
259+
timer.ended.side_effect = [False, True] # Run once then end
260+
timer.remaining.return_value = 0
261+
timer.__str__ = MagicMock(return_value='0:00')
262+
timer_call_count[0] += 1
263+
return timer
264+
mock_timer.side_effect = create_timer
265+
266+
# Mock conversion to fail (no handshake)
267+
mock_hcxpcapng.convert_to_hashcat.return_value = False
268+
269+
# Create attack
270+
attack = AttackWPA(self.mock_target)
271+
attack.interface_assignment = self.assignment
272+
attack.capture_interface = 'wlan0mon'
273+
attack.deauth_interface = 'wlan1mon'
274+
attack.clients = []
275+
276+
# Run capture
277+
result = attack._capture_handshake_dual_hcxdump()
278+
279+
# Verify warning was displayed (check Color.pl was called with warning)
280+
warning_calls = [call for call in mock_color.pl.call_args_list
281+
if 'Warning' in str(call) and 'large' in str(call).lower()]
282+
self.assertTrue(len(warning_calls) > 0, "Large file warning should be displayed")
283+
284+
285+
if __name__ == '__main__':
286+
unittest.main()

0 commit comments

Comments
 (0)