Path: blob/master/modules/auxiliary/analyze/crack_osx.rb
19516 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Auxiliary6include Msf::Auxiliary::PasswordCracker78def initialize9super(10'Name' => 'Password Cracker: OSX',11'Description' => %(12This module uses John the Ripper or Hashcat to identify weak passwords that have been13acquired from OSX systems. The module will only crack xsha from OSX 10.4-10.6, xsha51214from 10.7, and PBKDF2 from OSX 10.8+.15XSHA is 122 in hashcat.16XSHA512 is 1722 in hashcat.17PBKDF2 (PBKDF2-HMAC-SHA512) is 7100 in hashcat.18),19'Author' => [20'h00die' # hashcat integration21],22'License' => MSF_LICENSE, # JtR itself is GPLv2, but this wrapper is MSF (BSD)23'Actions' => [24['john', { 'Description' => 'Use John the Ripper' }],25['hashcat', { 'Description' => 'Use Hashcat' }],26],27'DefaultAction' => 'john',28'Notes' => {29'Stability' => [CRASH_SAFE],30'SideEffects' => [],31'Reliability' => []32}33)3435register_options(36[37OptBool.new('XSHA', [false, 'Include XSHA hashes from 10.4-10.6', true]),38OptBool.new('XSHA512', [false, 'Include XSHA512 hashes from 10.7', true]),39OptBool.new('PBKDF2', [false, 'Include PBKDF2-HMAC-SHA512 hashes from 10.8+', true]),40OptBool.new('INCREMENTAL', [false, 'Run in incremental mode', true]),41OptBool.new('WORDLIST', [false, 'Run in wordlist mode', true])42]43)44end4546def show_command(cracker_instance)47return unless datastore['ShowCommand']4849if action.name == 'john'50cmd = cracker_instance.john_crack_command51elsif action.name == 'hashcat'52cmd = cracker_instance.hashcat_crack_command53end54print_status(" Cracking Command: #{cmd.join(' ')}")55end5657def check_results(passwords, results, hash_type, method)58passwords.each do |password_line|59password_line.chomp!60next if password_line.blank?6162fields = password_line.split(':')63cred = { 'hash_type' => hash_type, 'method' => method }64# If we don't have an expected minimum number of fields, this is probably not a hash line65if action.name == 'john'66next unless fields.count >= 36768cred['username'] = fields.shift69cred['core_id'] = fields.pop70unless hash_type == 'PBKDF2-HMAC-SHA512'714.times { fields.pop } # Get rid of extra :72end73cred['password'] = fields.join(':') # Anything left must be the password. This accounts for passwords with semi-colons in it74elsif action.name == 'hashcat'75next unless fields.count >= 37677cred['core_id'] = fields.shift78cred['hash'] = fields.shift79cred['password'] = fields.join(':') # Anything left must be the password. This accounts for passwords with semi-colons in it80next if cred['core_id'].include?("Hashfile '") && cred['core_id'].include?("' on line ") # skip error lines8182# we don't have the username since we overloaded it with the core_id (since its a better fit for us)83# so we can now just go grab the username from the DB84cred['username'] = framework.db.creds(workspace: myworkspace, id: cred['core_id'])[0].public.username85end86results = process_cracker_results(results, cred)87end8889results90end9192def run93tbl = tbl = cracker_results_table9495# array of hashes in jtr_format in the db, converted to an OR combined regex96hash_types_to_crack = []97hash_types_to_crack << 'xsha' if datastore['XSHA']98hash_types_to_crack << 'xsha512' if datastore['XSHA512']99hash_types_to_crack << 'PBKDF2-HMAC-SHA512' if datastore['PBKDF2']100jobs_to_do = []101102# build our job list103hash_types_to_crack.each do |hash_type|104job = hash_job(hash_type, action.name)105if job.nil?106print_status("No #{hash_type} found to crack")107else108jobs_to_do << job109end110end111112# bail early of no jobs to do113if jobs_to_do.empty?114print_good("No uncracked password hashes found for: #{hash_types_to_crack.join(', ')}")115return116end117118# array of arrays for cracked passwords.119# Inner array format: db_id, hash_type, username, password, method_of_crack120results = []121122cracker = new_password_cracker(action.name)123124# generate our wordlist and close the file handle.125wordlist = wordlist_file126unless wordlist127print_error('This module cannot run without a database connected. Use db_connect to connect to a database.')128return129end130131wordlist.close132print_status "Wordlist file written out to #{wordlist.path}"133134cleanup_files = [wordlist.path]135136jobs_to_do.each do |job|137format = job['type']138hash_file = Rex::Quickfile.new("hashes_#{job['type']}_")139hash_file.puts job['formatted_hashlist']140hash_file.close141cracker.hash_path = hash_file.path142cleanup_files << hash_file.path143# dupe our original cracker so we can safely change options between each run144cracker_instance = cracker.dup145cracker_instance.format = format146if action.name == 'john'147cracker_instance.fork = datastore['FORK']148end149150# first check if anything has already been cracked so we don't report it incorrectly151print_status "Checking #{format} hashes already cracked..."152results = check_results(cracker_instance.each_cracked_password, results, format, 'Already Cracked/POT')153vprint_good(append_results(tbl, results)) unless results.empty?154155if action.name == 'john'156print_status "Cracking #{format} hashes in single mode..."157cracker_instance.mode_single(wordlist.path)158show_command cracker_instance159cracker_instance.crack do |line|160vprint_status line.chomp161end162results = check_results(cracker_instance.each_cracked_password, results, format, 'Single')163vprint_good(append_results(tbl, results)) unless results.empty?164job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list165next if job['cred_ids_left_to_crack'].empty?166167print_status "Cracking #{format} hashes in normal mode..."168cracker_instance.mode_normal169show_command cracker_instance170cracker_instance.crack do |line|171vprint_status line.chomp172end173results = check_results(cracker_instance.each_cracked_password, results, format, 'Normal')174vprint_good(append_results(tbl, results)) unless results.empty?175job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list176next if job['cred_ids_left_to_crack'].empty?177end178179if datastore['INCREMENTAL']180print_status "Cracking #{format} hashes in incremental mode..."181cracker_instance.mode_incremental182show_command cracker_instance183cracker_instance.crack do |line|184vprint_status line.chomp185end186results = check_results(cracker_instance.each_cracked_password, results, format, 'Incremental')187vprint_good(append_results(tbl, results)) unless results.empty?188job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list189next if job['cred_ids_left_to_crack'].empty?190end191192if datastore['WORDLIST']193print_status "Cracking #{format} hashes in wordlist mode..."194cracker_instance.mode_wordlist(wordlist.path)195# Turn on KoreLogic rules if the user asked for it196if action.name == 'john' && datastore['KORELOGIC']197cracker_instance.rules = 'KoreLogicRules'198print_status 'Applying KoreLogic ruleset...'199end200show_command cracker_instance201cracker_instance.crack do |line|202vprint_status line.chomp203end204205results = check_results(cracker_instance.each_cracked_password, results, format, 'Wordlist')206vprint_good(append_results(tbl, results)) unless results.empty?207job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list208next if job['cred_ids_left_to_crack'].empty?209end210211# give a final print of results212print_good(append_results(tbl, results))213end214if datastore['DeleteTempFiles']215cleanup_files.each do |f|216File.delete(f)217end218end219end220end221222223