Path: blob/master/modules/auxiliary/analyze/crack_linux.rb
21532 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_linux'910def initialize11super(12'Name' => 'Password Cracker: Linux',13'Description' => %{14This module uses John the Ripper or Hashcat to identify weak passwords that have been15acquired from unshadowed passwd files from Unix/Linux systems. The module will only crack16MD5, BSDi and DES implementations by default. However, it can also crack17Blowfish and SHA(256/512), but it is much slower.18MD5 is format 500 in hashcat.19DES is format 1500 in hashcat.20BSDI is format 12400 in hashcat.21BLOWFISH is format 3200 in hashcat.22SHA256 is format 7400 in hashcat.23SHA512 is format 1800 in hashcat.24},25'Author' => [26'theLightCosine',27'hdm',28'h00die' # hashcat integration29],30'License' => MSF_LICENSE, # JtR itself is GPLv2, but this wrapper is MSF (BSD)31'Actions' => [32['john', { 'Description' => 'Use John the Ripper' }],33['hashcat', { 'Description' => 'Use Hashcat' }],34['auto', { 'Description' => 'Auto-selection of cracker' }]35],36'DefaultAction' => 'auto',37'Notes' => {38'Stability' => [CRASH_SAFE],39'SideEffects' => [],40'Reliability' => []41}42)4344register_options(45[46OptBool.new('MD5', [false, 'Include MD5 hashes', true]),47OptBool.new('DES', [false, 'Indlude DES hashes', true]),48OptBool.new('BSDI', [false, 'Include BSDI hashes', true]),49OptBool.new('BLOWFISH', [false, 'Include BLOWFISH hashes (Very Slow)', false]),50OptBool.new('SHA256', [false, 'Include SHA256 hashes (Very Slow)', false]),51OptBool.new('SHA512', [false, 'Include SHA512 hashes (Very Slow)', false]),52OptBool.new('INCREMENTAL', [false, 'Run in incremental mode', true]),53OptBool.new('WORDLIST', [false, 'Run in wordlist mode', true])54]55)56end5758def show_command(cracker_instance)59return unless datastore['ShowCommand']6061if @cracker_type == 'john'62cmd = cracker_instance.john_crack_command63elsif @cracker_type == 'hashcat'64cmd = cracker_instance.hashcat_crack_command65end66print_status(" Cracking Command: #{cmd.join(' ')}")67end6869def check_results(passwords, results, hash_type, method)70passwords.each do |password_line|71password_line.chomp!72next if password_line.blank?7374fields = password_line.split(':')75cred = { 'hash_type' => hash_type, 'method' => method }7677if @cracker_type == 'john'78next unless fields.count >= 3 # If we don't have an expected minimum number of fields, this is probably not a hash line7980cred['username'] = fields.shift81cred['core_id'] = fields.pop824.times { fields.pop } # Get rid of extra :83cred['password'] = fields.join(':') # Anything left must be the password. This accounts for passwords with semi-colons in it84elsif @cracker_type == 'hashcat'85next unless fields.count >= 2 # If we don't have an expected minimum number of fields, this is probably not a hash line8687cred['core_id'] = fields.shift88cred['hash'] = fields.shift89cred['password'] = fields.join(':') # Anything left must be the password. This accounts for passwords with semi-colons in it90next if cred['core_id'].include?("Hashfile '") && cred['core_id'].include?("' on line ") # skip error lines9192# we don't have the username since we overloaded it with the core_id (since its a better fit for us)93# so we can now just go grab the username from the DB94cred['username'] = framework.db.creds(workspace: myworkspace, id: cred['core_id'])[0].public.username95end96results = process_cracker_results(results, cred)97end9899results100end101102def run103tbl = cracker_results_table104cracker = new_password_cracker(action.name)105if action.name == 'auto'106@cracker_type = cracker.get_type107else108@cracker_type = action.name109end110111# array of hashes in jtr_format in the db, converted to an OR combined regex112hash_types_to_crack = []113hash_types_to_crack << 'md5crypt' if datastore['MD5']114hash_types_to_crack << 'descrypt' if datastore['DES']115hash_types_to_crack << 'bsdicrypt' if datastore['BSDI']116hash_types_to_crack << 'bcrypt' if datastore['BLOWFISH']117hash_types_to_crack << 'sha256crypt' if datastore['SHA256']118hash_types_to_crack << 'sha512crypt' if datastore['SHA512']119120jobs_to_do = []121122# build our job list123hash_types_to_crack.each do |hash_type|124job = hash_job(hash_type, @cracker_type)125if job.nil?126print_status("No #{hash_type} found to crack")127else128jobs_to_do << job129end130end131132# bail early of no jobs to do133if jobs_to_do.empty?134print_good("No uncracked password hashes found for: #{hash_types_to_crack.join(', ')}")135return136end137138# array of arrays for cracked passwords.139# Inner array format: db_id, hash_type, username, password, method_of_crack140results = []141142# generate our wordlist and close the file handle.143wordlist = wordlist_file144unless wordlist145print_error('This module cannot run without a database connected. Use db_connect to connect to a database.')146return147end148149wordlist.close150print_status "Wordlist file written out to #{wordlist.path}"151152cleanup_files = [wordlist.path]153jobs_to_do.each do |job|154format = job['type']155hash_file = Rex::Quickfile.new("hashes_#{job['type']}_")156hash_file.puts job['formatted_hashlist']157hash_file.close158cracker.hash_path = hash_file.path159cleanup_files << hash_file.path160161# dupe our original cracker so we can safely change options between each run162cracker_instance = cracker.dup163cracker_instance.format = format164165if @cracker_type == 'john'166cracker_instance.fork = datastore['FORK']167end168169# first check if anything has already been cracked so we don't report it incorrectly170print_status "Checking #{format} hashes already cracked..."171results = check_results(cracker_instance.each_cracked_password, results, format, 'Already Cracked/POT')172vprint_good(append_results(tbl, results)) unless results.empty?173job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list174next if job['cred_ids_left_to_crack'].empty?175176if @cracker_type == 'john'177print_status "Cracking #{format} hashes in single mode..."178cracker_instance.mode_single(wordlist.path)179show_command cracker_instance180cracker_instance.crack do |line|181vprint_status line.chomp182end183results = check_results(cracker_instance.each_cracked_password, results, format, 'Single')184vprint_good(append_results(tbl, results)) unless results.empty?185job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list186next if job['cred_ids_left_to_crack'].empty?187188print_status "Cracking #{format} hashes in normal mode..."189cracker_instance.mode_normal190show_command cracker_instance191cracker_instance.crack do |line|192vprint_status line.chomp193end194results = check_results(cracker_instance.each_cracked_password, results, format, 'Normal')195vprint_good(append_results(tbl, results)) unless results.empty?196job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list197next if job['cred_ids_left_to_crack'].empty?198end199200if datastore['INCREMENTAL']201print_status "Cracking #{format} hashes in incremental mode..."202cracker_instance.mode_incremental203show_command cracker_instance204cracker_instance.crack do |line|205vprint_status line.chomp206end207results = check_results(cracker_instance.each_cracked_password, results, format, 'Incremental')208vprint_good(append_results(tbl, results)) unless results.empty?209job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list210next if job['cred_ids_left_to_crack'].empty?211end212213next unless datastore['WORDLIST']214215print_status "Cracking #{format} hashes in wordlist mode..."216cracker_instance.mode_wordlist(wordlist.path)217# Turn on KoreLogic rules if the user asked for it218if @cracker_type == 'john' && datastore['KORELOGIC']219cracker_instance.rules = 'KoreLogicRules'220print_status 'Applying KoreLogic ruleset...'221end222show_command cracker_instance223cracker_instance.crack do |line|224vprint_status line.chomp225end226227results = check_results(cracker_instance.each_cracked_password, results, format, 'Wordlist')228vprint_good(append_results(tbl, results)) unless results.empty?229job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list230next if job['cred_ids_left_to_crack'].empty?231end232233# give a final print of results234print_good(append_results(tbl, results))235236if datastore['DeleteTempFiles']237cleanup_files.each do |f|238File.delete(f)239end240end241end242end243244245