Path: blob/master/modules/auxiliary/analyze/crack_windows.rb
70746 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.22krb5tgs is format 13100 in hashcat.23krb5tgs-aes128 is format 19600 in hashcat.24krb5tgs-aes256 is format 19700 in hashcat.25krb5asrep is format 18200 in hashcat.26timeroast is format 31300 in hashcat.27),28'Author' => [29'theLightCosine',30'hdm',31'h00die' # hashcat integration32],33'References' => [34[ 'ATT&CK', Mitre::Attack::Technique::T1110_002_PASSWORD_CRACKING ]35],36'License' => MSF_LICENSE, # JtR itself is GPLv2, but this wrapper is MSF (BSD)37'Actions' => [38['john', { 'Description' => 'Use John the Ripper' }],39['hashcat', { 'Description' => 'Use Hashcat' }],40['auto', { 'Description' => 'Auto-selection of cracker' }]41],42'DefaultAction' => 'auto',43'Notes' => {44'Stability' => [CRASH_SAFE],45'SideEffects' => [],46'Reliability' => []47}48)4950register_options(51[52OptBool.new('NTLM', [false, 'Crack NTLM hashes', true]),53OptBool.new('LANMAN', [false, 'Crack LANMAN hashes', true]),54OptBool.new('MSCASH', [false, 'Crack M$ CASH hashes (1 and 2)', true]),55OptBool.new('NETNTLM', [false, 'Crack NetNTLM', true]),56OptBool.new('NETNTLMV2', [false, 'Crack NetNTLMv2', true]),57OptBool.new('KERBEROS', [false, 'Crack krb5/timeroast related hashes', true]),58OptBool.new('INCREMENTAL', [false, 'Run in incremental mode', true]),59OptBool.new('WORDLIST', [false, 'Run in wordlist mode', true]),60OptBool.new('NORMAL', [false, 'Run in normal mode (John the Ripper only)', true])61]62)63end6465def half_lm_regex66# ^\?{7} is ??????? which is JTR format, so password would be ???????D67# ^[notfound] is hashcat format, so password would be [notfound]D68/^[?{7}|\[notfound\]]/69end7071def show_command(cracker_instance)72return unless datastore['ShowCommand']7374if @cracker_type == 'john'75cmd = cracker_instance.john_crack_command76elsif @cracker_type == 'hashcat'77cmd = cracker_instance.hashcat_crack_command78end79print_status(" Cracking Command: #{cmd.join(' ')}")80end8182# we have to overload the process_cracker_results from password_cracker.rb since LANMAN83# is a special case where we may need to do some combining84def process_cracker_results(results, cred)85return results if cred['core_id'].nil? # make sure we have good data8687# make sure we dont add the same one again88if results.select { |r| r.first == cred['core_id'] }.empty?89results << [cred['core_id'], cred['hash_type'], cred['username'], cred['password'], cred['method']]90end9192# however, a special case for LANMAN where it may come back as ???????D (jtr) or [notfound]D (hashcat)93# we want to overwrite the one that was there *if* we have something better.94results.map! do |r|95if r.first == cred['core_id'] &&96r[3] =~ half_lm_regex97[cred['core_id'], cred['hash_type'], cred['username'], cred['password'], cred['method']]98else99r100end101end102103create_cracked_credential(username: cred['username'], password: cred['password'], core_id: cred['core_id'])104results105end106107def check_results(passwords, results, hash_type, method)108passwords.each do |password_line|109password_line.chomp!110next if password_line.blank?111112fields = password_line.split(':')113cred = { 'hash_type' => hash_type, 'method' => method }114if @cracker_type == 'john'115# If we don't have an expected minimum number of fields, this is probably not a hash line116next unless fields.count >= 2 # krb5asrep and similar kerberoast fields have 2 fields only117118case hash_type119when 'krb5asrep', 'krb5tgs'120cred['core_id'] = fields.shift121cred['password'] = fields.pop122when 'mscash', 'mscash2', 'netntlm', 'netntlmv2'123cred['username'] = fields.shift124cred['core_id'] = fields.pop125cred['password'] = fields.shift126when 'lm', 'nt'127# If we don't have an expected minimum number of fields, this is probably not a NTLM hash128next unless fields.count >= 6129130cred['username'] = fields.shift131cred['core_id'] = fields.pop1321332.times { fields.pop } # Get rid of extra :134nt_hash = fields.pop135fields.pop136fields.pop137password = fields.join(':') # Anything left must be the password. This accounts for passwords with semi-colons in it138if hash_type == 'lm' && password.blank?139if nt_hash == Metasploit::Credential::NTLMHash::BLANK_NT_HASH140password = ''141else142next143end144end145146# password can be nil if the hash is broken (i.e., the NT and147# LM sides don't actually match) or if john was only able to148# crack one half of the LM hash. In the latter case, we'll149# have a line like:150# username:???????WORD:...:...:::151cred['password'] = john_lm_upper_to_ntlm(password, nt_hash)152end153next if cred['password'].nil?154elsif @cracker_type == 'hashcat'155next unless fields.count >= 2156157cred['core_id'] = fields.shift158159case hash_type160when 'netntlm', 'netntlmv2'161# we could grab the username here, but no need since we grab it later based on core_id, which is safer1626.times { fields.shift } # Get rid of a bunch of extra fields163when 'krb5asrep'1642.times { fields.shift } # Get rid of extra hash fields165else # 'krb5tgs'166cred['hash'] = fields.shift167end168169fields.pop if ['mscash'].include? hash_type # Get rid of username170171cred['password'] = fields.join(':') # Anything left must be the password. This accounts for passwords with semi-colons in it172next if cred['core_id'].include?("Hashfile '") && cred['core_id'].include?("' on line ") # skip error lines173174# we don't have the username since we overloaded it with the core_id (since its a better fit for us)175# so we can now just go grab the username from the DB176cred['username'] = framework.db.creds(workspace: myworkspace, id: cred['core_id'])[0].public.username177end178results = process_cracker_results(results, cred)179end180results181end182183def run184tbl = cracker_results_table185cracker = new_password_cracker(action.name)186if action.name == 'auto'187@cracker_type = cracker.get_type188else189@cracker_type = action.name190end191# array of hashes in jtr_format in the db, converted to an OR combined regex192hash_types_to_crack = []193hash_types_to_crack << 'lm' if datastore['LANMAN']194hash_types_to_crack << 'nt' if datastore['NTLM']195hash_types_to_crack << 'mscash' if datastore['MSCASH']196hash_types_to_crack << 'mscash2' if datastore['MSCASH']197hash_types_to_crack << 'netntlm' if datastore['NETNTLM']198hash_types_to_crack << 'netntlmv2' if datastore['NETNTLMV2']199hash_types_to_crack << 'krb5tgs' if datastore['KERBEROS']200hash_types_to_crack << 'krb5tgs-aes128' if datastore['KERBEROS']201hash_types_to_crack << 'krb5tgs-aes256' if datastore['KERBEROS']202hash_types_to_crack << 'krb5asrep' if datastore['KERBEROS']203hash_types_to_crack << 'timeroast' if datastore['KERBEROS']204205jobs_to_do = []206207# build our job list208hash_types_to_crack.each do |hash_type|209job = hash_job(hash_type, @cracker_type)210if job.nil?211print_status("No #{hash_type} found to crack")212else213jobs_to_do << job214end215end216217# bail early of no jobs to do218if jobs_to_do.empty?219print_good("No uncracked password hashes found for: #{hash_types_to_crack.join(', ')}")220return221end222223# array of arrays for cracked passwords.224# Inner array format: db_id, hash_type, username, password, method_of_crack225results = []226227# generate our wordlist and close the file handle.228wordlist = wordlist_file229unless wordlist230print_error('This module cannot run without a database connected. Use db_connect to connect to a database.')231return232end233234wordlist.close235print_status "Wordlist file written out to #{wordlist.path}"236237cleanup_files = [wordlist.path]238239jobs_to_do.each do |job|240format = job['type']241hash_file = Rex::Quickfile.new("hashes_#{job['type']}_")242hash_file.puts job['formatted_hashlist']243hash_file.close244cracker.hash_path = hash_file.path245cleanup_files << hash_file.path246# dupe our original cracker so we can safely change options between each run247cracker_instance = cracker.dup248cracker_instance.format = format249if @cracker_type == 'john'250cracker_instance.fork = datastore['FORK']251end252253# first check if anything has already been cracked so we don't report it incorrectly254print_status "Checking #{format} hashes already cracked..."255results = check_results(cracker_instance.each_cracked_password, results, format, 'Already Cracked/POT')256vprint_good(append_results(tbl, results)) unless results.empty?257job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list258next if job['cred_ids_left_to_crack'].empty?259260if @cracker_type == 'john'261print_status "Cracking #{format} hashes in single mode..."262cracker_instance.mode_single(wordlist.path)263show_command cracker_instance264cracker_instance.crack do |line|265vprint_status line.chomp266end267results = check_results(cracker_instance.each_cracked_password, results, format, 'Single')268vprint_good(append_results(tbl, results)) unless results.empty?269job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list270next if job['cred_ids_left_to_crack'].empty?271272if datastore['NORMAL']273print_status "Cracking #{format} hashes in normal mode..."274cracker_instance.mode_normal275show_command cracker_instance276cracker_instance.crack do |line|277vprint_status line.chomp278end279results = check_results(cracker_instance.each_cracked_password, results, format, 'Normal')280vprint_good(append_results(tbl, results)) unless results.empty?281job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list282next if job['cred_ids_left_to_crack'].empty?283end284end285286if datastore['INCREMENTAL']287print_status "Cracking #{format} hashes in incremental mode..."288cracker_instance.mode_incremental289show_command cracker_instance290cracker_instance.crack do |line|291vprint_status line.chomp292end293results = check_results(cracker_instance.each_cracked_password, results, format, 'Incremental')294vprint_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?297end298299if datastore['WORDLIST']300print_status "Cracking #{format} hashes in wordlist mode..."301cracker_instance.mode_wordlist(wordlist.path)302# Turn on KoreLogic rules if the user asked for it303if @cracker_type == 'john' && datastore['KORELOGIC']304cracker_instance.rules = 'KoreLogicRules'305print_status 'Applying KoreLogic ruleset...'306end307show_command cracker_instance308cracker_instance.crack do |line|309vprint_status line.chomp310end311312results = check_results(cracker_instance.each_cracked_password, results, format, 'Wordlist')313314vprint_good(append_results(tbl, results)) unless results.empty?315job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list316next if job['cred_ids_left_to_crack'].empty?317end318319# give a final print of results320print_good(append_results(tbl, results))321end322if datastore['DeleteTempFiles']323cleanup_files.each do |f|324File.delete(f)325end326end327end328end329330331