@@ -147,74 +147,54 @@ def aircrack_handshakes(self):
147147 else :
148148 return []
149149
150- def hcxpcapngtool_handshakes (self ):
151- """
152- Returns tuple (BSSID,None) if hcxpcapngtool can extract valid handshake data.
153- Supports both .cap and .pcapng capture files.
154- """
155- if not Process .exists ('hcxpcapngtool' ):
156- return []
157-
158- import tempfile
159-
160- # Create a temporary hash file to test if hcxpcapngtool can extract data
161- hash_file = None
162- try :
163- with tempfile .NamedTemporaryFile (mode = 'w' , suffix = '.22000' , delete = False ) as tmp :
164- hash_file = tmp .name
165-
166- command = [
167- 'hcxpcapngtool' ,
168- '-o' , hash_file ,
169- self .capfile
170- ]
171-
172- proc = Process (command , devnull = False )
173-
174- # Check if hash file was created and has content
175- if os .path .exists (hash_file ) and os .path .getsize (hash_file ) > 0 :
176- # Successfully extracted handshake data
177- result = [(self .bssid , None )] if self .bssid else [(None , self .essid )]
178-
179- # Clean up temp file
180- try :
181- os .remove (hash_file )
182- except OSError :
183- pass
184-
185- return result
186- else :
187- # No valid handshake data found
188- try :
189- if os .path .exists (hash_file ):
190- os .remove (hash_file )
191- except OSError :
192- pass
193- return []
194-
195- except Exception :
196- # If anything goes wrong, clean up and return empty
197- try :
198- if hash_file and os .path .exists (hash_file ):
199- os .remove (hash_file )
200- except (OSError , NameError ):
201- pass
202- return []
203-
204150 def analyze (self ):
205151 """Prints analysis of handshake capfile"""
206152 self .divine_bssid_and_essid ()
207153
208154 if Tshark .exists ():
209- Handshake . print_pairs ( self .tshark_handshakes (), 'tshark' )
155+ self ._print_tshark_analysis ( )
210156
211157 if Process .exists ('cowpatty' ):
212158 Handshake .print_pairs (self .cowpatty_handshakes (), 'cowpatty' )
213159
214160 Handshake .print_pairs (self .aircrack_handshakes (), 'aircrack' )
215-
216- if Process .exists ('hcxpcapngtool' ):
217- Handshake .print_pairs (self .hcxpcapngtool_handshakes (), 'hcxpcapng' )
161+
162+ def _print_tshark_analysis (self ):
163+ """
164+ Print tshark verdict for this capfile.
165+
166+ On a full 4-way handshake, emits the standard 'contains a valid handshake'
167+ line. Otherwise inspects the EAPOL frames tshark *did* see and reports
168+ which messages are present and which are missing, so the user can tell
169+ the difference between "no EAPOL at all" and "M1+M2+M3 but no M4
170+ (still crackable by hashcat/cowpatty)".
171+ """
172+ # Single tshark parse yields both the full-handshake verdict and, when
173+ # absent, the partial-4-way breakdown — no need to read the file twice.
174+ bssids , summary = Tshark .eapol_analysis (self .capfile , bssid = self .bssid )
175+ if bssids :
176+ Handshake .print_pairs ([(bssid , None ) for bssid in bssids ], 'tshark' )
177+ return
178+
179+ tool_str = '{C}%s{W}: ' % 'tshark' .rjust (8 )
180+
181+ if not summary :
182+ Color .pl ('{!} %s.cap file contains {O}no EAPOL frames{W} '
183+ '({R}no handshake to crack{W})' % tool_str )
184+ return
185+
186+ for bssid , msgs in summary .items ():
187+ found = sorted (msgs )
188+ missing = [m for m in (1 , 2 , 3 , 4 ) if m not in msgs ]
189+ found_str = ', ' .join ('M%d' % m for m in found )
190+ missing_str = ', ' .join ('M%d' % m for m in missing )
191+ crackable = 2 in msgs and (1 in msgs or 3 in msgs )
192+ verdict = ('{G}crackable partial{W}'
193+ if crackable else
194+ '{R}not crackable{W}' )
195+ Color .pl ('{!} %s.cap file has {O}partial 4-way{W} for ({O}%s{W}): '
196+ 'found {C}%s{W}, missing {R}%s{W} — %s'
197+ % (tool_str , bssid .upper (), found_str , missing_str , verdict ))
218198
219199 def strip (self , outfile = None ):
220200 # XXX: This method might break aircrack-ng, use at own risk.
0 commit comments