Path: blob/master/modules/auxiliary/analyze/crack_windows.rb
19593 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Auxiliary6include Msf::Auxiliary::PasswordCracker7include Msf::Exploit::Deprecated8moved_from 'auxiliary/analyze/jtr_windows_fast'910def initialize11super(12'Name' => 'Password Cracker: Windows',13'Description' => %(14This module uses John the Ripper or Hashcat to identify weak passwords that have been15acquired from Windows systems.16LANMAN is format 3000 in hashcat.17NTLM is format 1000 in hashcat.18MSCASH is format 1100 in hashcat.19MSCASH2 is format 2100 in hashcat.20NetNTLM is format 5500 in hashcat.21NetNTLMv2 is format 5600 in hashcat.22),23'Author' => [24'theLightCosine',25'hdm',26'h00die' # hashcat integration27],28'License' => MSF_LICENSE, # JtR itself is GPLv2, but this wrapper is MSF (BSD)29'Actions' => [30['john', { 'Description' => 'Use John the Ripper' }],31['hashcat', { 'Description' => 'Use Hashcat' }],32],33'DefaultAction' => 'john',34'Notes' => {35'Stability' => [CRASH_SAFE],36'SideEffects' => [],37'Reliability' => []38}39)4041register_options(42[43OptBool.new('NTLM', [false, 'Crack NTLM hashes', true]),44OptBool.new('LANMAN', [false, 'Crack LANMAN hashes', true]),45OptBool.new('MSCASH', [false, 'Crack M$ CASH hashes (1 and 2)', true]),46OptBool.new('NETNTLM', [false, 'Crack NetNTLM', true]),47OptBool.new('NETNTLMV2', [false, 'Crack NetNTLMv2', true]),48OptBool.new('INCREMENTAL', [false, 'Run in incremental mode', true]),49OptBool.new('WORDLIST', [false, 'Run in wordlist mode', true]),50OptBool.new('NORMAL', [false, 'Run in normal mode (John the Ripper only)', true])51]52)53end5455def half_lm_regex56# ^\?{7} is ??????? which is JTR format, so password would be ???????D57# ^[notfound] is hashcat format, so password would be [notfound]D58/^[?{7}|\[notfound\]]/59end6061def show_command(cracker_instance)62return unless datastore['ShowCommand']6364if action.name == 'john'65cmd = cracker_instance.john_crack_command66elsif action.name == 'hashcat'67cmd = cracker_instance.hashcat_crack_command68end69print_status(" Cracking Command: #{cmd.join(' ')}")70end7172# we have to overload the process_cracker_results from password_cracker.rb since LANMAN73# is a special case where we may need to do some combining74def process_cracker_results(results, cred)75return results if cred['core_id'].nil? # make sure we have good data7677# make sure we dont add the same one again78if results.select { |r| r.first == cred['core_id'] }.empty?79results << [cred['core_id'], cred['hash_type'], cred['username'], cred['password'], cred['method']]80end8182# however, a special case for LANMAN where it may come back as ???????D (jtr) or [notfound]D (hashcat)83# we want to overwrite the one that was there *if* we have something better.84results.map! do |r|85if r.first == cred['core_id'] &&86r[3] =~ half_lm_regex87[cred['core_id'], cred['hash_type'], cred['username'], cred['password'], cred['method']]88else89r90end91end9293create_cracked_credential(username: cred['username'], password: cred['password'], core_id: cred['core_id'])94results95end9697def check_results(passwords, results, hash_type, method)98passwords.each do |password_line|99password_line.chomp!100next if password_line.blank?101102fields = password_line.split(':')103cred = { 'hash_type' => hash_type, 'method' => method }104if action.name == 'john'105# If we don't have an expected minimum number of fields, this is probably not a hash line106next unless fields.count > 2107108cred['username'] = fields.shift109cred['core_id'] = fields.pop110case hash_type111when 'mscash', 'mscash2', 'netntlm', 'netntlmv2'112cred['password'] = fields.shift113when 'lm', 'nt'114# If we don't have an expected minimum number of fields, this is probably not a NTLM hash115next unless fields.count >= 61161172.times { fields.pop } # Get rid of extra :118nt_hash = fields.pop119fields.pop120fields.pop121password = fields.join(':') # Anything left must be the password. This accounts for passwords with semi-colons in it122if hash_type == 'lm' && password.blank?123if nt_hash == Metasploit::Credential::NTLMHash::BLANK_NT_HASH124password = ''125else126next127end128end129130# password can be nil if the hash is broken (i.e., the NT and131# LM sides don't actually match) or if john was only able to132# crack one half of the LM hash. In the latter case, we'll133# have a line like:134# username:???????WORD:...:...:::135cred['password'] = john_lm_upper_to_ntlm(password, nt_hash)136end137next if cred['password'].nil?138elsif action.name == 'hashcat'139next unless fields.count >= 2140141cred['core_id'] = fields.shift142143if ['netntlm', 'netntlmv2'].include? hash_type144# we could grab the username here, but no need since we grab it later based on core_id, which is safer1456.times { fields.shift } # Get rid of a bunch of extra fields146else147cred['hash'] = fields.shift148end149150fields.pop if hash_type == 'mscash' # Get rid of username151152cred['password'] = fields.join(':') # Anything left must be the password. This accounts for passwords with semi-colons in it153next if cred['core_id'].include?("Hashfile '") && cred['core_id'].include?("' on line ") # skip error lines154155# we don't have the username since we overloaded it with the core_id (since its a better fit for us)156# so we can now just go grab the username from the DB157cred['username'] = framework.db.creds(workspace: myworkspace, id: cred['core_id'])[0].public.username158end159results = process_cracker_results(results, cred)160end161results162end163164def run165tbl = cracker_results_table166167# array of hashes in jtr_format in the db, converted to an OR combined regex168hash_types_to_crack = []169hash_types_to_crack << 'lm' if datastore['LANMAN']170hash_types_to_crack << 'nt' if datastore['NTLM']171hash_types_to_crack << 'mscash' if datastore['MSCASH']172hash_types_to_crack << 'mscash2' if datastore['MSCASH']173hash_types_to_crack << 'netntlm' if datastore['NETNTLM']174hash_types_to_crack << 'netntlmv2' if datastore['NETNTLMV2']175176jobs_to_do = []177178# build our job list179hash_types_to_crack.each do |hash_type|180job = hash_job(hash_type, action.name)181if job.nil?182print_status("No #{hash_type} found to crack")183else184jobs_to_do << job185end186end187188# bail early of no jobs to do189if jobs_to_do.empty?190print_good("No uncracked password hashes found for: #{hash_types_to_crack.join(', ')}")191return192end193194# array of arrays for cracked passwords.195# Inner array format: db_id, hash_type, username, password, method_of_crack196results = []197198cracker = new_password_cracker(action.name)199200# generate our wordlist and close the file handle.201wordlist = wordlist_file202unless wordlist203print_error('This module cannot run without a database connected. Use db_connect to connect to a database.')204return205end206207wordlist.close208print_status "Wordlist file written out to #{wordlist.path}"209210cleanup_files = [wordlist.path]211212jobs_to_do.each do |job|213format = job['type']214hash_file = Rex::Quickfile.new("hashes_#{job['type']}_")215hash_file.puts job['formatted_hashlist']216hash_file.close217cracker.hash_path = hash_file.path218cleanup_files << hash_file.path219# dupe our original cracker so we can safely change options between each run220cracker_instance = cracker.dup221cracker_instance.format = format222if action.name == 'john'223cracker_instance.fork = datastore['FORK']224end225226# first check if anything has already been cracked so we don't report it incorrectly227print_status "Checking #{format} hashes already cracked..."228results = check_results(cracker_instance.each_cracked_password, results, format, 'Already Cracked/POT')229vprint_good(append_results(tbl, results)) unless results.empty?230job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list231next if job['cred_ids_left_to_crack'].empty?232233if action.name == 'john'234print_status "Cracking #{format} hashes in single mode..."235cracker_instance.mode_single(wordlist.path)236show_command cracker_instance237cracker_instance.crack do |line|238vprint_status line.chomp239end240results = check_results(cracker_instance.each_cracked_password, results, format, 'Single')241vprint_good(append_results(tbl, results)) unless results.empty?242job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list243next if job['cred_ids_left_to_crack'].empty?244245if datastore['NORMAL']246print_status "Cracking #{format} hashes in normal mode..."247cracker_instance.mode_normal248show_command cracker_instance249cracker_instance.crack do |line|250vprint_status line.chomp251end252results = check_results(cracker_instance.each_cracked_password, results, format, 'Normal')253vprint_good(append_results(tbl, results)) unless results.empty?254job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list255next if job['cred_ids_left_to_crack'].empty?256end257end258259if datastore['INCREMENTAL']260print_status "Cracking #{format} hashes in incremental mode..."261cracker_instance.mode_incremental262show_command cracker_instance263cracker_instance.crack do |line|264vprint_status line.chomp265end266results = check_results(cracker_instance.each_cracked_password, results, format, 'Incremental')267vprint_good(append_results(tbl, results)) unless results.empty?268job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list269next if job['cred_ids_left_to_crack'].empty?270end271272if datastore['WORDLIST']273print_status "Cracking #{format} hashes in wordlist mode..."274cracker_instance.mode_wordlist(wordlist.path)275# Turn on KoreLogic rules if the user asked for it276if action.name == 'john' && datastore['KORELOGIC']277cracker_instance.rules = 'KoreLogicRules'278print_status 'Applying KoreLogic ruleset...'279end280show_command cracker_instance281cracker_instance.crack do |line|282vprint_status line.chomp283end284285results = check_results(cracker_instance.each_cracked_password, results, format, 'Wordlist')286287vprint_good(append_results(tbl, results)) unless results.empty?288job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list289next if job['cred_ids_left_to_crack'].empty?290end291292# give a final print of results293print_good(append_results(tbl, results))294end295if datastore['DeleteTempFiles']296cleanup_files.each do |f|297File.delete(f)298end299end300end301end302303304