Path: blob/master/modules/auxiliary/analyze/crack_windows.rb
32907 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'References' => [29[ 'ATT&CK', Mitre::Attack::Technique::T1110_002_PASSWORD_CRACKING ]30],31'License' => MSF_LICENSE, # JtR itself is GPLv2, but this wrapper is MSF (BSD)32'Actions' => [33['john', { 'Description' => 'Use John the Ripper' }],34['hashcat', { 'Description' => 'Use Hashcat' }],35['auto', { 'Description' => 'Auto-selection of cracker' }]36],37'DefaultAction' => 'auto',38'Notes' => {39'Stability' => [CRASH_SAFE],40'SideEffects' => [],41'Reliability' => []42}43)4445register_options(46[47OptBool.new('NTLM', [false, 'Crack NTLM hashes', true]),48OptBool.new('LANMAN', [false, 'Crack LANMAN hashes', true]),49OptBool.new('MSCASH', [false, 'Crack M$ CASH hashes (1 and 2)', true]),50OptBool.new('NETNTLM', [false, 'Crack NetNTLM', true]),51OptBool.new('NETNTLMV2', [false, 'Crack NetNTLMv2', true]),52OptBool.new('INCREMENTAL', [false, 'Run in incremental mode', true]),53OptBool.new('WORDLIST', [false, 'Run in wordlist mode', true]),54OptBool.new('NORMAL', [false, 'Run in normal mode (John the Ripper only)', true])55]56)57end5859def half_lm_regex60# ^\?{7} is ??????? which is JTR format, so password would be ???????D61# ^[notfound] is hashcat format, so password would be [notfound]D62/^[?{7}|\[notfound\]]/63end6465def show_command(cracker_instance)66return unless datastore['ShowCommand']6768if @cracker_type == 'john'69cmd = cracker_instance.john_crack_command70elsif @cracker_type == 'hashcat'71cmd = cracker_instance.hashcat_crack_command72end73print_status(" Cracking Command: #{cmd.join(' ')}")74end7576# we have to overload the process_cracker_results from password_cracker.rb since LANMAN77# is a special case where we may need to do some combining78def process_cracker_results(results, cred)79return results if cred['core_id'].nil? # make sure we have good data8081# make sure we dont add the same one again82if results.select { |r| r.first == cred['core_id'] }.empty?83results << [cred['core_id'], cred['hash_type'], cred['username'], cred['password'], cred['method']]84end8586# however, a special case for LANMAN where it may come back as ???????D (jtr) or [notfound]D (hashcat)87# we want to overwrite the one that was there *if* we have something better.88results.map! do |r|89if r.first == cred['core_id'] &&90r[3] =~ half_lm_regex91[cred['core_id'], cred['hash_type'], cred['username'], cred['password'], cred['method']]92else93r94end95end9697create_cracked_credential(username: cred['username'], password: cred['password'], core_id: cred['core_id'])98results99end100101def check_results(passwords, results, hash_type, method)102passwords.each do |password_line|103password_line.chomp!104next if password_line.blank?105106fields = password_line.split(':')107cred = { 'hash_type' => hash_type, 'method' => method }108if @cracker_type == 'john'109# If we don't have an expected minimum number of fields, this is probably not a hash line110next unless fields.count > 2111112cred['username'] = fields.shift113cred['core_id'] = fields.pop114case hash_type115when 'mscash', 'mscash2', 'netntlm', 'netntlmv2'116cred['password'] = fields.shift117when 'lm', 'nt'118# If we don't have an expected minimum number of fields, this is probably not a NTLM hash119next unless fields.count >= 61201212.times { fields.pop } # Get rid of extra :122nt_hash = fields.pop123fields.pop124fields.pop125password = fields.join(':') # Anything left must be the password. This accounts for passwords with semi-colons in it126if hash_type == 'lm' && password.blank?127if nt_hash == Metasploit::Credential::NTLMHash::BLANK_NT_HASH128password = ''129else130next131end132end133134# password can be nil if the hash is broken (i.e., the NT and135# LM sides don't actually match) or if john was only able to136# crack one half of the LM hash. In the latter case, we'll137# have a line like:138# username:???????WORD:...:...:::139cred['password'] = john_lm_upper_to_ntlm(password, nt_hash)140end141next if cred['password'].nil?142elsif @cracker_type == 'hashcat'143next unless fields.count >= 2144145cred['core_id'] = fields.shift146147if ['netntlm', 'netntlmv2'].include? hash_type148# we could grab the username here, but no need since we grab it later based on core_id, which is safer1496.times { fields.shift } # Get rid of a bunch of extra fields150else151cred['hash'] = fields.shift152end153154fields.pop if hash_type == 'mscash' # Get rid of username155156cred['password'] = fields.join(':') # Anything left must be the password. This accounts for passwords with semi-colons in it157next if cred['core_id'].include?("Hashfile '") && cred['core_id'].include?("' on line ") # skip error lines158159# we don't have the username since we overloaded it with the core_id (since its a better fit for us)160# so we can now just go grab the username from the DB161cred['username'] = framework.db.creds(workspace: myworkspace, id: cred['core_id'])[0].public.username162end163results = process_cracker_results(results, cred)164end165results166end167168def run169tbl = cracker_results_table170cracker = new_password_cracker(action.name)171if action.name == 'auto'172@cracker_type = cracker.get_type173else174@cracker_type = action.name175end176# array of hashes in jtr_format in the db, converted to an OR combined regex177hash_types_to_crack = []178hash_types_to_crack << 'lm' if datastore['LANMAN']179hash_types_to_crack << 'nt' if datastore['NTLM']180hash_types_to_crack << 'mscash' if datastore['MSCASH']181hash_types_to_crack << 'mscash2' if datastore['MSCASH']182hash_types_to_crack << 'netntlm' if datastore['NETNTLM']183hash_types_to_crack << 'netntlmv2' if datastore['NETNTLMV2']184185jobs_to_do = []186187# build our job list188hash_types_to_crack.each do |hash_type|189job = hash_job(hash_type, @cracker_type)190if job.nil?191print_status("No #{hash_type} found to crack")192else193jobs_to_do << job194end195end196197# bail early of no jobs to do198if jobs_to_do.empty?199print_good("No uncracked password hashes found for: #{hash_types_to_crack.join(', ')}")200return201end202203# array of arrays for cracked passwords.204# Inner array format: db_id, hash_type, username, password, method_of_crack205results = []206207# generate our wordlist and close the file handle.208wordlist = wordlist_file209unless wordlist210print_error('This module cannot run without a database connected. Use db_connect to connect to a database.')211return212end213214wordlist.close215print_status "Wordlist file written out to #{wordlist.path}"216217cleanup_files = [wordlist.path]218219jobs_to_do.each do |job|220format = job['type']221hash_file = Rex::Quickfile.new("hashes_#{job['type']}_")222hash_file.puts job['formatted_hashlist']223hash_file.close224cracker.hash_path = hash_file.path225cleanup_files << hash_file.path226# dupe our original cracker so we can safely change options between each run227cracker_instance = cracker.dup228cracker_instance.format = format229if @cracker_type == 'john'230cracker_instance.fork = datastore['FORK']231end232233# first check if anything has already been cracked so we don't report it incorrectly234print_status "Checking #{format} hashes already cracked..."235results = check_results(cracker_instance.each_cracked_password, results, format, 'Already Cracked/POT')236vprint_good(append_results(tbl, results)) unless results.empty?237job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list238next if job['cred_ids_left_to_crack'].empty?239240if @cracker_type == 'john'241print_status "Cracking #{format} hashes in single mode..."242cracker_instance.mode_single(wordlist.path)243show_command cracker_instance244cracker_instance.crack do |line|245vprint_status line.chomp246end247results = check_results(cracker_instance.each_cracked_password, results, format, 'Single')248vprint_good(append_results(tbl, results)) unless results.empty?249job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list250next if job['cred_ids_left_to_crack'].empty?251252if datastore['NORMAL']253print_status "Cracking #{format} hashes in normal mode..."254cracker_instance.mode_normal255show_command cracker_instance256cracker_instance.crack do |line|257vprint_status line.chomp258end259results = check_results(cracker_instance.each_cracked_password, results, format, 'Normal')260vprint_good(append_results(tbl, results)) unless results.empty?261job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list262next if job['cred_ids_left_to_crack'].empty?263end264end265266if datastore['INCREMENTAL']267print_status "Cracking #{format} hashes in incremental mode..."268cracker_instance.mode_incremental269show_command cracker_instance270cracker_instance.crack do |line|271vprint_status line.chomp272end273results = check_results(cracker_instance.each_cracked_password, results, format, 'Incremental')274vprint_good(append_results(tbl, results)) unless results.empty?275job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list276next if job['cred_ids_left_to_crack'].empty?277end278279if datastore['WORDLIST']280print_status "Cracking #{format} hashes in wordlist mode..."281cracker_instance.mode_wordlist(wordlist.path)282# Turn on KoreLogic rules if the user asked for it283if @cracker_type == 'john' && datastore['KORELOGIC']284cracker_instance.rules = 'KoreLogicRules'285print_status 'Applying KoreLogic ruleset...'286end287show_command cracker_instance288cracker_instance.crack do |line|289vprint_status line.chomp290end291292results = check_results(cracker_instance.each_cracked_password, results, format, 'Wordlist')293294vprint_good(append_results(tbl, results)) unless results.empty?295job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list296next if job['cred_ids_left_to_crack'].empty?297end298299# give a final print of results300print_good(append_results(tbl, results))301end302if datastore['DeleteTempFiles']303cleanup_files.each do |f|304File.delete(f)305end306end307end308end309310311