Path: blob/master/modules/auxiliary/analyze/crack_windows.rb
21537 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['auto', { 'Description' => 'Auto-selection of cracker' }]33],34'DefaultAction' => 'auto',35'Notes' => {36'Stability' => [CRASH_SAFE],37'SideEffects' => [],38'Reliability' => []39}40)4142register_options(43[44OptBool.new('NTLM', [false, 'Crack NTLM hashes', true]),45OptBool.new('LANMAN', [false, 'Crack LANMAN hashes', true]),46OptBool.new('MSCASH', [false, 'Crack M$ CASH hashes (1 and 2)', true]),47OptBool.new('NETNTLM', [false, 'Crack NetNTLM', true]),48OptBool.new('NETNTLMV2', [false, 'Crack NetNTLMv2', true]),49OptBool.new('INCREMENTAL', [false, 'Run in incremental mode', true]),50OptBool.new('WORDLIST', [false, 'Run in wordlist mode', true]),51OptBool.new('NORMAL', [false, 'Run in normal mode (John the Ripper only)', true])52]53)54end5556def half_lm_regex57# ^\?{7} is ??????? which is JTR format, so password would be ???????D58# ^[notfound] is hashcat format, so password would be [notfound]D59/^[?{7}|\[notfound\]]/60end6162def show_command(cracker_instance)63return unless datastore['ShowCommand']6465if @cracker_type == 'john'66cmd = cracker_instance.john_crack_command67elsif @cracker_type == 'hashcat'68cmd = cracker_instance.hashcat_crack_command69end70print_status(" Cracking Command: #{cmd.join(' ')}")71end7273# we have to overload the process_cracker_results from password_cracker.rb since LANMAN74# is a special case where we may need to do some combining75def process_cracker_results(results, cred)76return results if cred['core_id'].nil? # make sure we have good data7778# make sure we dont add the same one again79if results.select { |r| r.first == cred['core_id'] }.empty?80results << [cred['core_id'], cred['hash_type'], cred['username'], cred['password'], cred['method']]81end8283# however, a special case for LANMAN where it may come back as ???????D (jtr) or [notfound]D (hashcat)84# we want to overwrite the one that was there *if* we have something better.85results.map! do |r|86if r.first == cred['core_id'] &&87r[3] =~ half_lm_regex88[cred['core_id'], cred['hash_type'], cred['username'], cred['password'], cred['method']]89else90r91end92end9394create_cracked_credential(username: cred['username'], password: cred['password'], core_id: cred['core_id'])95results96end9798def check_results(passwords, results, hash_type, method)99passwords.each do |password_line|100password_line.chomp!101next if password_line.blank?102103fields = password_line.split(':')104cred = { 'hash_type' => hash_type, 'method' => method }105if @cracker_type == 'john'106# If we don't have an expected minimum number of fields, this is probably not a hash line107next unless fields.count > 2108109cred['username'] = fields.shift110cred['core_id'] = fields.pop111case hash_type112when 'mscash', 'mscash2', 'netntlm', 'netntlmv2'113cred['password'] = fields.shift114when 'lm', 'nt'115# If we don't have an expected minimum number of fields, this is probably not a NTLM hash116next unless fields.count >= 61171182.times { fields.pop } # Get rid of extra :119nt_hash = fields.pop120fields.pop121fields.pop122password = fields.join(':') # Anything left must be the password. This accounts for passwords with semi-colons in it123if hash_type == 'lm' && password.blank?124if nt_hash == Metasploit::Credential::NTLMHash::BLANK_NT_HASH125password = ''126else127next128end129end130131# password can be nil if the hash is broken (i.e., the NT and132# LM sides don't actually match) or if john was only able to133# crack one half of the LM hash. In the latter case, we'll134# have a line like:135# username:???????WORD:...:...:::136cred['password'] = john_lm_upper_to_ntlm(password, nt_hash)137end138next if cred['password'].nil?139elsif @cracker_type == 'hashcat'140next unless fields.count >= 2141142cred['core_id'] = fields.shift143144if ['netntlm', 'netntlmv2'].include? hash_type145# we could grab the username here, but no need since we grab it later based on core_id, which is safer1466.times { fields.shift } # Get rid of a bunch of extra fields147else148cred['hash'] = fields.shift149end150151fields.pop if hash_type == 'mscash' # Get rid of username152153cred['password'] = fields.join(':') # Anything left must be the password. This accounts for passwords with semi-colons in it154next if cred['core_id'].include?("Hashfile '") && cred['core_id'].include?("' on line ") # skip error lines155156# we don't have the username since we overloaded it with the core_id (since its a better fit for us)157# so we can now just go grab the username from the DB158cred['username'] = framework.db.creds(workspace: myworkspace, id: cred['core_id'])[0].public.username159end160results = process_cracker_results(results, cred)161end162results163end164165def run166tbl = cracker_results_table167cracker = new_password_cracker(action.name)168if action.name == 'auto'169@cracker_type = cracker.get_type170else171@cracker_type = action.name172end173# array of hashes in jtr_format in the db, converted to an OR combined regex174hash_types_to_crack = []175hash_types_to_crack << 'lm' if datastore['LANMAN']176hash_types_to_crack << 'nt' if datastore['NTLM']177hash_types_to_crack << 'mscash' if datastore['MSCASH']178hash_types_to_crack << 'mscash2' if datastore['MSCASH']179hash_types_to_crack << 'netntlm' if datastore['NETNTLM']180hash_types_to_crack << 'netntlmv2' if datastore['NETNTLMV2']181182jobs_to_do = []183184# build our job list185hash_types_to_crack.each do |hash_type|186job = hash_job(hash_type, @cracker_type)187if job.nil?188print_status("No #{hash_type} found to crack")189else190jobs_to_do << job191end192end193194# bail early of no jobs to do195if jobs_to_do.empty?196print_good("No uncracked password hashes found for: #{hash_types_to_crack.join(', ')}")197return198end199200# array of arrays for cracked passwords.201# Inner array format: db_id, hash_type, username, password, method_of_crack202results = []203204# generate our wordlist and close the file handle.205wordlist = wordlist_file206unless wordlist207print_error('This module cannot run without a database connected. Use db_connect to connect to a database.')208return209end210211wordlist.close212print_status "Wordlist file written out to #{wordlist.path}"213214cleanup_files = [wordlist.path]215216jobs_to_do.each do |job|217format = job['type']218hash_file = Rex::Quickfile.new("hashes_#{job['type']}_")219hash_file.puts job['formatted_hashlist']220hash_file.close221cracker.hash_path = hash_file.path222cleanup_files << hash_file.path223# dupe our original cracker so we can safely change options between each run224cracker_instance = cracker.dup225cracker_instance.format = format226if @cracker_type == 'john'227cracker_instance.fork = datastore['FORK']228end229230# first check if anything has already been cracked so we don't report it incorrectly231print_status "Checking #{format} hashes already cracked..."232results = check_results(cracker_instance.each_cracked_password, results, format, 'Already Cracked/POT')233vprint_good(append_results(tbl, results)) unless results.empty?234job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list235next if job['cred_ids_left_to_crack'].empty?236237if @cracker_type == 'john'238print_status "Cracking #{format} hashes in single mode..."239cracker_instance.mode_single(wordlist.path)240show_command cracker_instance241cracker_instance.crack do |line|242vprint_status line.chomp243end244results = check_results(cracker_instance.each_cracked_password, results, format, 'Single')245vprint_good(append_results(tbl, results)) unless results.empty?246job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list247next if job['cred_ids_left_to_crack'].empty?248249if datastore['NORMAL']250print_status "Cracking #{format} hashes in normal mode..."251cracker_instance.mode_normal252show_command cracker_instance253cracker_instance.crack do |line|254vprint_status line.chomp255end256results = check_results(cracker_instance.each_cracked_password, results, format, 'Normal')257vprint_good(append_results(tbl, results)) unless results.empty?258job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list259next if job['cred_ids_left_to_crack'].empty?260end261end262263if datastore['INCREMENTAL']264print_status "Cracking #{format} hashes in incremental mode..."265cracker_instance.mode_incremental266show_command cracker_instance267cracker_instance.crack do |line|268vprint_status line.chomp269end270results = check_results(cracker_instance.each_cracked_password, results, format, 'Incremental')271vprint_good(append_results(tbl, results)) unless results.empty?272job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list273next if job['cred_ids_left_to_crack'].empty?274end275276if datastore['WORDLIST']277print_status "Cracking #{format} hashes in wordlist mode..."278cracker_instance.mode_wordlist(wordlist.path)279# Turn on KoreLogic rules if the user asked for it280if @cracker_type == 'john' && datastore['KORELOGIC']281cracker_instance.rules = 'KoreLogicRules'282print_status 'Applying KoreLogic ruleset...'283end284show_command cracker_instance285cracker_instance.crack do |line|286vprint_status line.chomp287end288289results = check_results(cracker_instance.each_cracked_password, results, format, 'Wordlist')290291vprint_good(append_results(tbl, results)) unless results.empty?292job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list293next if job['cred_ids_left_to_crack'].empty?294end295296# give a final print of results297print_good(append_results(tbl, results))298end299if datastore['DeleteTempFiles']300cleanup_files.each do |f|301File.delete(f)302end303end304end305end306307308