From ecb4b1c7367c9071169fe7f7b88a4ef501ad4fca Mon Sep 17 00:00:00 2001 From: Justin Bollinger Date: Wed, 21 Jan 2026 15:08:42 -0500 Subject: [PATCH] hashview additions --- hate_crack.py | 261 +++++++++++++++++++++++++++++++------------------- 1 file changed, 163 insertions(+), 98 deletions(-) diff --git a/hate_crack.py b/hate_crack.py index 7b247a6..4c6af91 100755 --- a/hate_crack.py +++ b/hate_crack.py @@ -152,6 +152,52 @@ else: print('Invalid path for hashcat binary. Please check configuration and try again.') quit(1) +# Verify hashcat-utils binaries exist and work +hashcat_utils_path = hate_path + '/hashcat-utils/bin' +required_binaries = [ + (hcatExpanderBin, 'expander'), + (hcatCombinatorBin, 'combinator'), +] + +for binary, name in required_binaries: + binary_path = hashcat_utils_path + '/' + binary + if not os.path.isfile(binary_path): + print(f'Error: {name} binary not found at {binary_path}') + print('Please ensure hashcat-utils is properly installed.') + quit(1) + # Check if binary is executable + if not os.access(binary_path, os.X_OK): + print(f'Error: {name} binary at {binary_path} is not executable') + print('Try running: chmod +x {0}'.format(binary_path)) + quit(1) + # Test binary execution + try: + test_result = subprocess.run( + [binary_path], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + timeout=2 + ) + # Binary should show usage and exit with error code (that's expected) + # If we get here without exception, the binary is executable + except subprocess.TimeoutExpired: + # Timeout is fine - means binary is running + pass + except Exception as e: + print(f'Error: {name} binary at {binary_path} failed to execute: {e}') + print('The binary may be compiled for the wrong architecture.') + print('Try recompiling hashcat-utils for your system.') + quit(1) + +# Verify princeprocessor binary +prince_path = hate_path + '/princeprocessor/' + hcatPrinceBin +if not os.path.isfile(prince_path): + print(f'Warning: PRINCE binary not found at {prince_path}') + print('PRINCE attacks will not be available.') +elif not os.access(prince_path, os.X_OK): + print(f'Warning: PRINCE binary at {prince_path} is not executable') + print('Try running: chmod +x {0}'.format(prince_path)) + #verify and convert wordlists to fully qualified paths hcatMiddleBaseList = verify_wordlist_dir(hcatWordlists, hcatMiddleBaseList) hcatThoroughBaseList = verify_wordlist_dir(hcatWordlists, hcatThoroughBaseList) @@ -176,6 +222,7 @@ hcatHybridCount = 0 hcatExtraCount = 0 hcatRecycleCount = 0 hcatProcess = 0 +debug_mode = True # Help @@ -1012,10 +1059,10 @@ class HashviewAPI: 'hash_only': 5, } - def __init__(self, base_url, api_key): + def __init__(self, base_url, api_key, debug=False): self.base_url = base_url.rstrip('/') self.api_key = api_key - self.agent_uuid = None + self.debug = debug self.session = requests.Session() self.session.cookies.set('uuid', api_key) # Disable SSL certificate verification for self-signed certificates @@ -1024,14 +1071,6 @@ class HashviewAPI: import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) - def use_agent_mode(self): - """Switch to using agent UUID for operations that require it""" - if not self.agent_uuid: - raise Exception("No agent registered. Call register_agent() first.") - - # Switch session to use agent UUID - self.session = self.agent_session - def upload_wordlist(self, file_path, wordlist_name=None): if wordlist_name is None: wordlist_name = os.path.basename(file_path) @@ -1066,43 +1105,41 @@ class HashviewAPI: response.raise_for_status() return response.json() - def upload_cracked_hashes(self, file_path, hash_type=1000): - # Read and convert file - API expects hex-encoded plaintext (uppercase) + def upload_cracked_hashes(self, file_path, hash_type='1000'): + # Read file - API expects plaintext format: hash:plaintext print(f"Importing cracked hashes: {os.path.basename(file_path)}") - print(f" Converting plaintext to hex encoding...") + print(f" Reading hash:plaintext pairs...") - converted_lines = [] + valid_lines = [] line_count = 0 with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: for line in f: line = line.strip() + if '31d6cfe0d16ae931b73c59d7e0c089c0' in line: + continue if not line or ':' not in line: continue parts = line.split(':', 1) if len(parts) != 2: - continue + break #might need to add encoding into HEX conversion here hash_value = parts[0].strip() plaintext = parts[1].strip() - # Convert plaintext to hex (uppercase as API expects) - plaintext_hex = plaintext.encode('utf-8').hex().upper() - converted_lines.append(f"{hash_value}:{plaintext_hex}") + # Keep format as-is: hash:plaintext + valid_lines.append(f"{hash_value}:{plaintext}") line_count += 1 - converted_content = '\n'.join(converted_lines) + # Join all lines into a single string with newline separators + converted_content = '\n'.join(valid_lines) print(f" Processed {line_count} hash:plaintext pairs") - # Use the actual endpoint from api_routes.py - url = f"{self.base_url}/v1/uploadCrackFile/{hash_type}" + url = f"{self.base_url}/v1/hashes/import/{hash_type}" - # API expects JSON with 'file' field - payload = { - "file": converted_content - } - headers = {'Content-Type': 'application/json'} + # API expects plain text body with hash:plaintext format + headers = {'Content-Type': 'text/plain'} print(f"\n === REQUEST DETAILS ===") print(f" URL: {url}") @@ -1110,11 +1147,20 @@ class HashviewAPI: print(f" Headers: {headers}") print(f" Cookies: {dict(self.session.cookies)}") print(f" Hash type: {hash_type}") - print(f" Payload preview (first 500 chars):") + print(f" Content preview (first 500 chars):") print(converted_content[:500]) - print(f" Uploading...") - response = self.session.post(url, json=payload, headers=headers) + # Generate curl command for manual testing + print(f"\n === CURL COMMAND ===") + curl_data_preview = converted_content[:200].replace("'", "'\\''") # Escape single quotes + print(f" curl --insecure -X POST '{url}' \\") + print(f" -H 'Content-Type: text/plain' \\") + print(f" --cookie 'uuid={self.api_key}' \\") + print(f" --data '{converted_content}...'") + + print(f"\n Uploading...") + + response = self.session.post(url, data=converted_content, headers=headers) # Debug: print response details print(f"\n === RESPONSE DETAILS ===") @@ -1149,10 +1195,10 @@ class HashviewAPI: return data - def create_customer(self, name, description=""): + def create_customer(self, name): url = f"{self.base_url}/v1/customers/add" headers = {'Content-Type': 'application/json'} - data = {"name": name, "description": description} + data = {"name": name} print(f"Creating customer: {name}") response = self.session.post(url, json=data, headers=headers) @@ -1246,7 +1292,7 @@ class HashviewAPI: return {'output_file': output_file, 'size': file_size} -def hashview_upload(): +def hashview_api(): """Upload data to Hashview API""" global hcatHashFile, hcatHashType @@ -1268,19 +1314,18 @@ def hashview_upload(): print(f"\nConnecting to Hashview at: {hashview_url}") try: - uploader = HashviewAPI(hashview_url, hashview_api_key) + api_harness = HashviewAPI(hashview_url, hashview_api_key, debug=debug_mode) while True: print("\n" + "="*60) print("What would you like to do?") print("="*60) print("\t(1) Upload Cracked Hashes from current session") - print("\t(2) Upload Wordlist") - print("\t(3) Upload Hashfile and Create Job") - print("\t(4) List Customers") - print("\t(5) Create Customer") - print("\t(6) Download Left Hashes") - print("\t(9) Back to Main Menu") + print("\t(2) Create Job") + print("\t(3) List Customers") + print("\t(4) Create Customer") + print("\t(5) Download Left Hashes") + print("\t(99) Back to Main Menu") choice = input("\nSelect an option: ") @@ -1299,6 +1344,11 @@ def hashview_upload(): if os.path.exists(potential_file): session_file = potential_file print(f"Found session file: {session_file}") + elif 'hcatHashFile' in globals() and hcatHashFile: + potential_file = hcatHashFile + "nt.out" + if os.path.exists(potential_file): + session_file = potential_file + print(f"Found session file: {session_file}") except: pass @@ -1309,7 +1359,7 @@ def hashview_upload(): cracked_file = session_file if not cracked_file: - cracked_file = input("Enter path to cracked hashes file (.out format): ").strip() + cracked_file = input(f"Enter path to cracked hashes file (.out format) [hash type: {hcatHashType}]: ").strip() # Validate file exists if not os.path.exists(cracked_file): @@ -1324,25 +1374,13 @@ def hashview_upload(): print(f"Size: {file_size} bytes") print(f"Lines: {line_count}") - # Hash type is always 1000 (NTLM) for import endpoint - hash_type = 1000 - - # Check if agent is registered - if not uploader.agent_uuid: - print("\n⚠ Warning: Upload cracked hashes requires agent authorization") - print("You must first:") - print(" 1. Select option (7) to register as an agent") - print(" 2. Authorize the agent in Hashview web UI") - print(" 3. Then come back and upload") - continue - - # Switch to agent mode for upload - uploader.use_agent_mode() + # Use the same hash type from main menu + hash_type = hcatHashType # Upload print(f"\nUploading to Hashview (hash type: {hash_type})...") try: - result = uploader.upload_cracked_hashes(cracked_file, hash_type) + result = api_harness.upload_cracked_hashes(cracked_file, hash_type) print(f"\n✓ Success: {result.get('msg', 'Cracked hashes uploaded')}") if 'count' in result: print(f" Imported: {result['count']} hashes") @@ -1353,23 +1391,6 @@ def hashview_upload(): traceback.print_exc() elif choice == '2': - # Upload wordlist - wordlist_path = input("\nEnter path to wordlist file: ") - if not os.path.exists(wordlist_path): - print(f"Error: File not found: {wordlist_path}") - continue - - wordlist_name = input(f"Enter wordlist name (default: {os.path.basename(wordlist_path)}): ") or None - - try: - result = uploader.upload_wordlist(wordlist_path, wordlist_name) - print(f"\n✓ Success: {result.get('msg', 'Wordlist uploaded')}") - if 'wordlist_id' in result: - print(f" Wordlist ID: {result['wordlist_id']}") - except Exception as e: - print(f"\n✗ Error uploading wordlist: {str(e)}") - - elif choice == '3': # Upload hashfile and create job hashfile_path = input("\nEnter path to hashfile: ") if not os.path.exists(hashfile_path): @@ -1387,7 +1408,7 @@ def hashview_upload(): hashfile_name = input(f"Enter hashfile name (default: {os.path.basename(hashfile_path)}): ") or None try: - result = uploader.upload_hashfile( + result = api_harness.upload_hashfile( hashfile_path, customer_id, hash_type, file_format, hashfile_name ) print(f"\n✓ Success: {result.get('msg', 'Hashfile uploaded')}") @@ -1404,7 +1425,7 @@ def hashview_upload(): notify_email = input("Send email notifications? (Y/n): ").upper() != 'N' try: - job_result = uploader.create_job( + job_result = api_harness.create_job( job_name, result['hashfile_id'], customer_id, limit_recovered, notify_email ) @@ -1415,17 +1436,17 @@ def hashview_upload(): # Offer to start the job start_now = input("\nStart the job now? (Y/n): ") or "Y" if start_now.upper() == 'Y': - start_result = uploader.start_job(job_result['job_id']) + start_result = api_harness.start_job(job_result['job_id']) print(f"\n✓ Success: {start_result.get('msg', 'Job started')}") except Exception as e: print(f"\n✗ Error creating job: {str(e)}") except Exception as e: print(f"\n✗ Error uploading hashfile: {str(e)}") - elif choice == '4': + elif choice == '3': # List customers try: - result = uploader.list_customers() + result = api_harness.list_customers() if 'customers' in result and result['customers']: print("\n" + "="*60) print("Customers:") @@ -1435,31 +1456,28 @@ def hashview_upload(): for customer in result['customers']: cust_id = customer.get('id', 'N/A') cust_name = customer.get('name', 'N/A') - cust_desc = customer.get('description', '') - print(f"{cust_id:<10} {cust_name:<30} {cust_desc}") + print(f"{cust_id:<10} {cust_name:<30}") else: print("\nNo customers found.") except Exception as e: print(f"\n✗ Error fetching customers: {str(e)}") - elif choice == '5': + elif choice == '4': # Create customer customer_name = input("\nEnter customer name: ") - customer_desc = input("Enter customer description (optional): ") - try: - result = uploader.create_customer(customer_name, customer_desc) + result = api_harness.create_customer(customer_name) print(f"\n✓ Success: {result.get('msg', 'Customer created')}") if 'customer_id' in result: print(f" Customer ID: {result['customer_id']}") except Exception as e: print(f"\n✗ Error creating customer: {str(e)}") - elif choice == '6': + elif choice == '5': # Download left hashes try: # First, list customers to help user select - result = uploader.list_customers() + result = api_harness.list_customers() if 'customers' in result and result['customers']: print("\n" + "="*60) print("Available Customers:") @@ -1474,10 +1492,45 @@ def hashview_upload(): # Get customer ID and hashfile ID directly customer_id = int(input("\nEnter customer ID: ")) - # Note: API doesn't provide hashfile listing - print("\nNote: To find the hashfile ID, visit the Hashview web interface:") - print(f" {hashview_url}/hashfiles") - print(" Look for hashfiles belonging to the selected customer.") + # List hashfiles for the customer + try: + # Note: Using a simple GET to list hashfiles + list_url = f"{hashview_url}/v1/hashfiles" + response = api_harness.session.get(list_url) + response.raise_for_status() + + hashfiles_data = response.json() + + # Parse hashfiles - may be JSON string + if 'hashfiles' in hashfiles_data: + if isinstance(hashfiles_data['hashfiles'], str): + hashfiles = json.loads(hashfiles_data['hashfiles']) + else: + hashfiles = hashfiles_data['hashfiles'] + + # Filter by customer_id + customer_hashfiles = [hf for hf in hashfiles if hf.get('customer_id') == customer_id] + + if customer_hashfiles: + print("\n" + "="*60) + print(f"Hashfiles for Customer ID {customer_id}:") + print("="*60) + print(f"{'ID':<10} {'Name':<30} {'Hash Count':<15}") + print("-" * 60) + for hf in customer_hashfiles: + hf_id = hf.get('id', 'N/A') + hf_name = hf.get('name', 'N/A') + hf_count = hf.get('hash_count', 'N/A') + print(f"{hf_id:<10} {hf_name:<30} {hf_count:<15}") + else: + print(f"\nNo hashfiles found for customer ID {customer_id}") + else: + print("\nCould not retrieve hashfiles list") + except Exception as e: + print(f"\nWarning: Could not list hashfiles: {e}") + print("You may need to manually find the hashfile ID in the web interface.") + + hashfile_id = int(input("\nEnter hashfile ID: ")) @@ -1485,18 +1538,26 @@ def hashview_upload(): output_file = f"left_{customer_id}_{hashfile_id}.txt" # Download the left hashes - download_result = uploader.download_left_hashes( + download_result = api_harness.download_left_hashes( customer_id, hashfile_id, output_file ) print(f"\n✓ Success: Downloaded {download_result['size']} bytes") print(f" File: {download_result['output_file']}") + + # Ask if user wants to switch to this hashfile + switch = input("\nSwitch to this hashfile for cracking? (Y/n): ").strip().lower() + if switch != 'n': + hcatHashFile = download_result['output_file'] + print(f"✓ Switched to hashfile: {hcatHashFile}") + print("\nReturning to main menu to start cracking...") + return # Exit hashview menu and return to main menu except ValueError: print("\n✗ Error: Invalid ID entered. Please enter a numeric ID.") except Exception as e: print(f"\n✗ Error downloading hashes: {str(e)}") - elif choice == '9': + elif choice == '99': break else: print("Invalid option. Please try again.") @@ -1834,10 +1895,14 @@ def main(): global hcatHashType global hcatHashFileOrig global lmHashesFound + global debug_mode + # Parse command line arguments + args = sys.argv[1:] + try: - hcatHashFile = sys.argv[1] - hcatHashType = sys.argv[2] + hcatHashFile = args[0] + hcatHashType = args[1] except IndexError: # No arguments provided - show menu @@ -1864,10 +1929,10 @@ def main(): sys.exit(1) try: - uploader = HashviewAPI(hashview_url, hashview_api_key) + api_harness = HashviewAPI(hashview_url, hashview_api_key) # List customers - result = uploader.list_customers() + result = api_harness.list_customers() if 'customers' in result and result['customers']: print("\n" + "="*60) print("Available Customers:") @@ -1899,7 +1964,7 @@ def main(): output_file = f"left_{customer_id}_{hashfile_id}.txt" # Download the left hashes - download_result = uploader.download_left_hashes( + download_result = api_harness.download_left_hashes( customer_id, hashfile_id, output_file ) print(f"\n✓ Success: Downloaded {download_result['size']} bytes") @@ -2003,7 +2068,7 @@ def main(): print("\t(11) Middle Combinator Attack") print("\t(12) Thorough Combinator Attack") print("\t(13) Bandrel Methodology") - print("\n\t(94) Upload to Hashview") + print("\n\t(94) Hashview") print("\t(95) Analyze hashes with Pipal") print("\t(96) Export Output to Excel Format") print("\t(97) Display Cracked Hashes") @@ -2022,7 +2087,7 @@ def main(): "11": middle_combinator, "12": thorough_combinator, "13": bandrel_method, - "94": hashview_upload, + "94": hashview_api, "95": pipal, "96": export_excel, "97": show_results,