Path: blob/master/modules/auxiliary/analyze/crack_osx.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::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['auto', { 'Description' => 'Auto-selection of cracker' }]27],28'DefaultAction' => 'auto',29'Notes' => {30'Stability' => [CRASH_SAFE],31'SideEffects' => [],32'Reliability' => []33}34)3536register_options(37[38OptBool.new('XSHA', [false, 'Include XSHA hashes from 10.4-10.6', true]),39OptBool.new('XSHA512', [false, 'Include XSHA512 hashes from 10.7', true]),40OptBool.new('PBKDF2', [false, 'Include PBKDF2-HMAC-SHA512 hashes from 10.8+', true]),41OptBool.new('INCREMENTAL', [false, 'Run in incremental mode', true]),42OptBool.new('WORDLIST', [false, 'Run in wordlist mode', true])43]44)45end4647def show_command(cracker_instance)48return unless datastore['ShowCommand']4950if @cracker_type == 'john'51cmd = cracker_instance.john_crack_command52elsif @cracker_type == 'hashcat'53cmd = cracker_instance.hashcat_crack_command54end55print_status(" Cracking Command: #{cmd.join(' ')}")56end5758def check_results(passwords, results, hash_type, method)59passwords.each do |password_line|60password_line.chomp!61next if password_line.blank?6263fields = password_line.split(':')64cred = { 'hash_type' => hash_type, 'method' => method }65# If we don't have an expected minimum number of fields, this is probably not a hash line66if @cracker_type == 'john'67next unless fields.count >= 36869cred['username'] = fields.shift70cred['core_id'] = fields.pop71unless hash_type == 'PBKDF2-HMAC-SHA512'724.times { fields.pop } # Get rid of extra :73end74cred['password'] = fields.join(':') # Anything left must be the password. This accounts for passwords with semi-colons in it75elsif @cracker_type == 'hashcat'76next unless fields.count >= 37778cred['core_id'] = fields.shift79cred['hash'] = fields.shift80cred['password'] = fields.join(':') # Anything left must be the password. This accounts for passwords with semi-colons in it81next if cred['core_id'].include?("Hashfile '") && cred['core_id'].include?("' on line ") # skip error lines8283# we don't have the username since we overloaded it with the core_id (since its a better fit for us)84# so we can now just go grab the username from the DB85cred['username'] = framework.db.creds(workspace: myworkspace, id: cred['core_id'])[0].public.username86end87results = process_cracker_results(results, cred)88end8990results91end9293def run94tbl = cracker_results_table95cracker = new_password_cracker(action.name)96if action.name == 'auto'97@cracker_type = cracker.get_type98else99@cracker_type = action.name100end101102# array of hashes in jtr_format in the db, converted to an OR combined regex103hash_types_to_crack = []104hash_types_to_crack << 'xsha' if datastore['XSHA']105hash_types_to_crack << 'xsha512' if datastore['XSHA512']106hash_types_to_crack << 'PBKDF2-HMAC-SHA512' if datastore['PBKDF2']107jobs_to_do = []108109# build our job list110hash_types_to_crack.each do |hash_type|111job = hash_job(hash_type, @cracker_type)112if job.nil?113print_status("No #{hash_type} found to crack")114else115jobs_to_do << job116end117end118119# bail early of no jobs to do120if jobs_to_do.empty?121print_good("No uncracked password hashes found for: #{hash_types_to_crack.join(', ')}")122return123end124125# array of arrays for cracked passwords.126# Inner array format: db_id, hash_type, username, password, method_of_crack127results = []128129# generate our wordlist and close the file handle.130wordlist = wordlist_file131unless wordlist132print_error('This module cannot run without a database connected. Use db_connect to connect to a database.')133return134end135136wordlist.close137print_status "Wordlist file written out to #{wordlist.path}"138139cleanup_files = [wordlist.path]140141jobs_to_do.each do |job|142format = job['type']143hash_file = Rex::Quickfile.new("hashes_#{job['type']}_")144hash_file.puts job['formatted_hashlist']145hash_file.close146cracker.hash_path = hash_file.path147cleanup_files << hash_file.path148# dupe our original cracker so we can safely change options between each run149cracker_instance = cracker.dup150cracker_instance.format = format151if @cracker_type == 'john'152cracker_instance.fork = datastore['FORK']153end154155# first check if anything has already been cracked so we don't report it incorrectly156print_status "Checking #{format} hashes already cracked..."157results = check_results(cracker_instance.each_cracked_password, results, format, 'Already Cracked/POT')158vprint_good(append_results(tbl, results)) unless results.empty?159160if @cracker_type == 'john'161print_status "Cracking #{format} hashes in single mode..."162cracker_instance.mode_single(wordlist.path)163show_command cracker_instance164cracker_instance.crack do |line|165vprint_status line.chomp166end167results = check_results(cracker_instance.each_cracked_password, results, format, 'Single')168vprint_good(append_results(tbl, results)) unless results.empty?169job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list170next if job['cred_ids_left_to_crack'].empty?171172print_status "Cracking #{format} hashes in normal mode..."173cracker_instance.mode_normal174show_command cracker_instance175cracker_instance.crack do |line|176vprint_status line.chomp177end178results = check_results(cracker_instance.each_cracked_password, results, format, 'Normal')179vprint_good(append_results(tbl, results)) unless results.empty?180job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list181next if job['cred_ids_left_to_crack'].empty?182end183184if datastore['INCREMENTAL']185print_status "Cracking #{format} hashes in incremental mode..."186cracker_instance.mode_incremental187show_command cracker_instance188cracker_instance.crack do |line|189vprint_status line.chomp190end191results = check_results(cracker_instance.each_cracked_password, results, format, 'Incremental')192vprint_good(append_results(tbl, results)) unless results.empty?193job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list194next if job['cred_ids_left_to_crack'].empty?195end196197if datastore['WORDLIST']198print_status "Cracking #{format} hashes in wordlist mode..."199cracker_instance.mode_wordlist(wordlist.path)200# Turn on KoreLogic rules if the user asked for it201if @cracker_type == 'john' && datastore['KORELOGIC']202cracker_instance.rules = 'KoreLogicRules'203print_status 'Applying KoreLogic ruleset...'204end205show_command cracker_instance206cracker_instance.crack do |line|207vprint_status line.chomp208end209210results = check_results(cracker_instance.each_cracked_password, results, format, 'Wordlist')211vprint_good(append_results(tbl, results)) unless results.empty?212job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list213next if job['cred_ids_left_to_crack'].empty?214end215216# give a final print of results217print_good(append_results(tbl, results))218end219if datastore['DeleteTempFiles']220cleanup_files.each do |f|221File.delete(f)222end223end224end225end226227228