@@ -395,13 +395,12 @@ def _run_dual_interface(self):
395395 self .view .add_log (f"Error: { str (e )} " )
396396
397397 # Try to cleanup interfaces
398- try :
398+ import contextlib
399+ with contextlib .suppress (Exception ):
399400 if self .capture_interface :
400401 Airmon .stop (self .capture_interface )
401402 if self .deauth_interface :
402403 Airmon .stop (self .deauth_interface )
403- except :
404- pass
405404
406405 return None
407406
@@ -976,8 +975,184 @@ def _capture_handshake_dual_hcxdump(self):
976975
977976 return handshake
978977
978+ def _capture_handshake_single_hcxdump (self ):
979+ """
980+ Capture handshake using single interface mode with hcxdumptool.
981+
982+ Uses hcxdumptool's built-in deauth capabilities for a single interface,
983+ creating .pcapng files that work better with modern cracking tools.
984+
985+ Returns:
986+ Handshake object if captured, None otherwise
987+ """
988+ from ..tools .hcxdumptool import HcxDumpTool
989+ from ..util .logger import log_info , log_debug , log_error
990+
991+ handshake = None
992+
993+ # Try to load existing handshake first
994+ if not Configuration .ignore_old_handshakes :
995+ bssid = self .target .bssid
996+ essid = self .target .essid if self .target .essid_known else None
997+ handshake = self .load_handshake (bssid = bssid , essid = essid )
998+ if handshake :
999+ Color .pattack ('WPA' , self .target , 'Handshake capture' ,
1000+ 'found {G}existing handshake{W} for {C}%s{W}' % handshake .essid )
1001+ Color .pl ('\n {+} Using handshake from {C}%s{W}' % handshake .capfile )
1002+ return handshake
1003+
1004+ # Configure output file
1005+ output_file = Configuration .temp ('hcxdump_single.pcapng' )
1006+
1007+ # Configure deauth based on PMF
1008+ pmf_required = hasattr (self .target , 'pmf_required' ) and self .target .pmf_required
1009+
1010+ # Start capture with hcxdumptool
1011+ with HcxDumpTool (interface = Configuration .interface ,
1012+ channel = self .target .channel ,
1013+ target_bssid = self .target .bssid ,
1014+ output_file = output_file ,
1015+ enable_deauth = True , # Use hcxdumptool's built-in deauth
1016+ pmf_required = pmf_required ) as hcxdump :
1017+
1018+ Color .clear_entire_line ()
1019+ Color .pattack ('WPA' , self .target , 'Handshake capture' ,
1020+ 'Starting hcxdumptool [HCX]...' )
1021+
1022+ log_info ('AttackWPA' , f'hcxdumptool capture started on { Configuration .interface } ' )
1023+
1024+ if self .view :
1025+ self .view .add_log ('hcxdumptool capture started' )
1026+
1027+ # Initialize timers
1028+ timeout_timer = Timer (Configuration .wpa_attack_timeout )
1029+
1030+ while handshake is None and not timeout_timer .ended ():
1031+ step_timer = Timer (1 )
1032+
1033+ # Update TUI view if available
1034+ if self .view :
1035+ self .view .refresh_if_needed ()
1036+ self .view .update_progress ({
1037+ 'status' : f'Listening for handshake [HCX]' ,
1038+ 'metrics' : {
1039+ 'Mode' : 'HCX (hcxdumptool)' ,
1040+ 'Interface' : Configuration .interface ,
1041+ 'Timeout' : str (timeout_timer ),
1042+ 'Deauth' : 'Built-in' if not pmf_required else 'Disabled (PMF)'
1043+ }
1044+ })
1045+
1046+ Color .clear_entire_line ()
1047+ Color .pattack ('WPA' ,
1048+ self .target ,
1049+ 'Handshake capture' ,
1050+ 'Listening [HCX]. (timeout:{R}%s{W})' % timeout_timer )
1051+
1052+ # Check if hcxdumptool is still running
1053+ if not hcxdump .is_running ():
1054+ log_error ('AttackWPA' , 'hcxdumptool process died unexpectedly during capture' )
1055+ Color .pl ('\n {!} {R}hcxdumptool process died unexpectedly{W}' )
1056+ Color .pl ('{!} {O}Check interface and permissions{W}' )
1057+ if self .view :
1058+ self .view .add_log ('hcxdumptool process died unexpectedly' )
1059+ return None
1060+
1061+ # Check if capture file has data
1062+ if not hcxdump .has_captured_data ():
1063+ # No data yet, wait
1064+ time .sleep (step_timer .remaining ())
1065+ continue
1066+
1067+ # Check for handshake in the capture file
1068+ bssid = self .target .bssid
1069+ essid = self .target .essid if self .target .essid_known else None
1070+
1071+ log_debug ('AttackWPA' , f'Checking for handshake in hcxdumptool capture (BSSID: { bssid } )' )
1072+
1073+ handshake = Handshake (output_file , bssid = bssid , essid = essid )
1074+ if handshake .has_handshake ():
1075+ # We got a handshake
1076+ log_info ('AttackWPA' , f'Handshake captured successfully for { bssid } [HCX]' )
1077+
1078+ Color .clear_entire_line ()
1079+ Color .pattack ('WPA' ,
1080+ self .target ,
1081+ 'Handshake capture' ,
1082+ '{G}Captured handshake{W} [HCX]' )
1083+ Color .pl ('' )
1084+
1085+ # Update TUI view
1086+ if self .view :
1087+ self .view .add_log ('Captured handshake!' )
1088+ self .view .update_progress ({
1089+ 'status' : 'Handshake captured successfully' ,
1090+ 'progress' : 1.0 ,
1091+ 'metrics' : {
1092+ 'Handshake' : '✓' ,
1093+ 'Mode' : 'HCX (hcxdumptool)' ,
1094+ 'Interface' : Configuration .interface
1095+ }
1096+ })
1097+
1098+ # Upload to wpa-sec if configured
1099+ if WpaSecUploader .should_upload ():
1100+ # hcxdumptool creates .pcapng files which may contain SAE handshakes
1101+ capture_type = 'sae' if handshake .capfile .endswith ('.pcapng' ) else 'handshake'
1102+ if self .view :
1103+ self .view .add_log ("Checking wpa-sec upload configuration..." )
1104+ WpaSecUploader .upload_capture (
1105+ handshake .capfile ,
1106+ self .target .bssid ,
1107+ self .target .essid ,
1108+ capture_type = capture_type ,
1109+ view = self .view
1110+ )
1111+
1112+ break
1113+
1114+ # No handshake yet
1115+ handshake = None
1116+
1117+ time .sleep (step_timer .remaining ())
1118+
1119+ if handshake is None :
1120+ Color .pl ('\n {!} {O}WPA handshake capture {R}FAILED:{O} Timed out after %d seconds' % (
1121+ Configuration .wpa_attack_timeout ))
1122+ else :
1123+ # Save copy of handshake
1124+ self .save_handshake (handshake )
1125+
1126+ return handshake
1127+
9791128 def capture_handshake (self ):
9801129 """Returns captured or stored handshake, otherwise None."""
1130+ # Check if hcxdump mode is requested for single interface
1131+ if Configuration .use_hcxdump :
1132+ from ..tools .hcxdumptool import HcxDumpTool
1133+ from ..util .logger import log_info , log_warning , log_debug
1134+
1135+ log_debug ('AttackWPA' , 'Checking hcxdumptool availability for single interface mode' )
1136+
1137+ # Check if hcxdumptool is available
1138+ if not HcxDumpTool .exists ():
1139+ log_warning ('AttackWPA' , 'hcxdumptool not found, falling back to airodump-ng' )
1140+ Color .pl ('{!} {O}hcxdumptool not found, falling back to airodump-ng{W}' )
1141+ elif not HcxDumpTool .check_minimum_version ('6.2.0' ):
1142+ current_version = HcxDumpTool .check_version ()
1143+ log_warning ('AttackWPA' , f'hcxdumptool version { current_version } is insufficient (need 6.2.0+), falling back to airodump-ng' )
1144+ Color .pl ('{!} {O}hcxdumptool version {R}%s{O} is insufficient (need 6.2.0+){W}' % (current_version or 'unknown' ))
1145+ Color .pl ('{!} {O}Falling back to airodump-ng{W}' )
1146+ else :
1147+ # Use hcxdumptool for single interface capture
1148+ log_info ('AttackWPA' , 'Using hcxdumptool for single interface capture' )
1149+ Color .pl ('{+} {G}Using hcxdumptool for single interface capture{W}' )
1150+ if self .view :
1151+ self .view .add_log ('Using hcxdumptool mode for capture' )
1152+ self .view .set_attack_type ("WPA Handshake Capture [HCX]" )
1153+ return self ._capture_handshake_single_hcxdump ()
1154+
1155+ # Default: use airodump-ng
9811156 handshake = None
9821157
9831158 # First, start Airodump process
0 commit comments