diff --git a/lib/msf/core/auxiliary/jtr.rb b/lib/msf/core/auxiliary/jtr.rb new file mode 100644 index 0000000000..7f19ef83fa --- /dev/null +++ b/lib/msf/core/auxiliary/jtr.rb @@ -0,0 +1,197 @@ +require 'open3' +require 'rex/proto/ntlm/crypt' + +module Msf + +### +# +# This module provides methods for working with John the Ripper +# +### +module Auxiliary::JohnTheRipper + include Msf::Auxiliary::Report + + # + # Initializes an instance of an auxiliary module that calls out to John the Ripper (jtr) + # + + def initialize(info = {}) + super + + register_options( + [ + OptPath.new('JOHN_BASE', [false, 'The directory containing John the Ripper (src, run, doc)']), + OptPath.new('JOHN_PATH', [false, 'The absolute path to the John the Ripper executable']) + ], Msf::Auxiliary::JohnTheRipper + ) + + @run_path = nil + @john_path = ::File.join(Msf::Config.install_root, "data", "john") + + autodetect_platform + end + + def autodetect_platform + cpuinfo_base = ::File.join(Msf::Config.install_root, "data", "cpuinfo") + + case ::RUBY_PLATFORM + when /mingw|cygwin|mswin/ + data = `#{cpuinfo_base}/cpuinfo.exe` + case data + when /sse2/ + @run_path = "run.win32.sse2/john.exe" + when /mmx/ + @run_path = "run.win32.mmx/john.exe" + else + @run_path = "run.win32.any/john.exe" + end + when /x86_64-linux/ + data = `#{cpuinfo_base}/cpuinfo.ia64.bin` + case data + when /mmx/ + @run_path = "run.linux.x64.mmx/john" + else + @run_path = "run.linux.x86.any/john" + end + when /i[\d]86-linux/ + data = `#{cpuinfo_base}/cpuinfo.ia32.bin` + case data + when /sse2/ + @run_path = "run.linux.x86.sse2/john" + when /mmx/ + @run_path = "run.linux.x86.mmx/john" + else + @run_path = "run.linux.x86.any/john" + end + end + end + + def john_session_id + @session_id ||= ::Rex::Text.rand_text_alphanumeric(8) + end + + def john_cracked_passwords + ret = {} + pot = ::File.join( ::File.dirname( john_binary_path ), "john.pot" ) + return ret if not ::File.exist?(pot) + ::File.open(pot, "rb") do |fd| + fd.each_line do |line| + hash,clear = line.sub(/\r?\n$/, '').split(",", 2) + ret[hash] = clear + end + end + ret + end + + def john_show_passwords(hfile, format=nil) + res = {:cracked => 0, :uncracked => 0, :users => {} } + cmd = [ john_binary_path, "--show", "--conf=" + ::File.join(john_base_path, "confs", "john.conf"), hfile] + if format + cmd << "--format=" + format + end + + ::IO.popen(cmd, "rb") do |fd| + fd.each_line do |line| + if line =~ /(\d+) password hash cracked, (\d+) left/m + res[:cracked] = $1.to_i + res[:uncracked] = $2.to_i + end + + bits = line.split(':') + next if not bits[2] + res[ :users ][ bits[0] ] = bits[1] + end + end + res + end + + def john_wordlist_path + ::File.join(john_base_path, "wordlists", "password.lst") + end + + def john_binary_path + if datastore['JOHN_PATH'] and ::File.file?(datastore['JOHN_PATH']) + return datastore['JOHN_PATH'] + end + if not @run_path + if ::RUBY_PLATFORM =~ /mingw|cygwin|mswin/ + ::File.join(john_base_path, "john.exe") + else + ::File.join(john_base_path, "john") + end + else + ::File.join(john_base_path, @run_path) + end + end + + def john_base_path + if datastore['JOHN_BASE'] and ::File.directory?(datastore['JOHN_BASE']) + return datastore['JOHN_BASE'] + end + if datastore['JOHN_PATH'] and ::File.file?(datastore['JOHN_PATH']) + return ::File.dirname( datastore['JOHN_PATH'] ) + end + @john_path + end + + def john_expand_word(str) + res = [str] + str.split(/\W+/) {|w| res << w } + res.uniq + end + + def john_lm_upper_to_ntlm(pwd, hash) + pwd = pwd.upcase + hash = hash.upcase + Rex::Text.permute_case(pwd) do |str| + if hash == Rex::Proto::NTLM::Crypt.ntlm_hash(str).unpack("H*")[0].upcase + return str + end + end + nil + end + + + def john_crack(hfile, opts={}) + + res = {:cracked => 0, :uncracked => 0, :users => {} } + + cmd = [ john_binary_path, "--session=" + john_session_id] + + if opts[:conf] + cmd << ( "--conf=" + opts[:conf] ) + else + cmd << ( "--conf=" + ::File.join(john_base_path, "confs", "john.conf") ) + end + + if opts[:format] + cmd << ( "--format=" + opts[:format] ) + end + + if opts[:wordlist] + cmd << ( "--wordlist=" + opts[:wordlist] ) + end + + if opts[:incremental] + cmd << ( "--incremental=" + opts[:incremental] ) + end + + if opts[:single] + cmd << ( "--single=" + opts[:single] ) + end + + if opts[:rules] + cmd << ( "--rules=" + opts[:rules] ) + end + + cmd << hfile + + ::IO.popen(cmd, "rb") do |fd| + fd.each_line do |line| + print_status("Output: #{line.strip}") + end + end + res + end +end +end diff --git a/lib/msf/core/auxiliary/mixins.rb b/lib/msf/core/auxiliary/mixins.rb index b1bdd02e01..d32dacac14 100644 --- a/lib/msf/core/auxiliary/mixins.rb +++ b/lib/msf/core/auxiliary/mixins.rb @@ -19,3 +19,4 @@ require 'msf/core/auxiliary/login' require 'msf/core/auxiliary/rservices' require 'msf/core/auxiliary/cisco' require 'msf/core/auxiliary/nmap' +require 'msf/core/auxiliary/jtr' diff --git a/modules/auxiliary/analyze/jtr_crack_fast.rb b/modules/auxiliary/analyze/jtr_crack_fast.rb new file mode 100644 index 0000000000..0995ff6ead --- /dev/null +++ b/modules/auxiliary/analyze/jtr_crack_fast.rb @@ -0,0 +1,150 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +# +## + + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Auxiliary::JohnTheRipper + + def initialize + super( + 'Name' => 'John the Ripper Password Cracker (Fast Mode)', + 'Version' => '$Revision$', + 'Description' => %Q{ + This module uses John the Ripper to identify weak passwords that have been + acquired as hashed files (loot) or raw LANMAN/NTLM hashes (hashdump). The goal + of this module is to find trivial passwords in a short amount of time. To + crack complex passwords or use large wordlists, John the Ripper should be + used outside of Metasploit. This initial version just handles LM/NTLM credentials + from hashdump and uses the standard wordlist and rules. + }, + 'Author' => 'hdm', + 'License' => MSF_LICENSE # JtR itself is GPLv2, but this wrapper is MSF (BSD) + ) + end + + def run + wordlist = Rex::Quickfile.new("jtrtmp") + hashlist = Rex::Quickfile.new("jtrtmp") + + begin + # Seed the wordlist with usernames, passwords, and hostnames + seed = [] + + myworkspace.hosts.find(:all).each {|o| seed << john_expand_word( o.name ) if o.name } + myworkspace.creds.each do |o| + seed << john_expand_word( o.user ) if o.user + seed << john_expand_word( o.pass ) if (o.pass and o.ptype !~ /hash/) + end + + # Grab any known passwords out of the john.pot file + john_cracked_passwords.values {|v| seed << v } + + # Write the seed file + wordlist.write( seed.flatten.uniq.join("\n") + "\n" ) + + # Append the standard JtR wordlist as well + ::File.open(john_wordlist_path, "rb") do |fd| + wordlist.write fd.read(fd.stat.size) + end + + # Close the wordlist to prevent sharing violations (windows) + wordlist.close + + # Create a PWDUMP style input file for SMB Hashes + smb_hashes = myworkspace.creds.select{|x| x.ptype == "smb_hash" } + smb_hashes.each do |cred| + hashlist.write( "cred_#{cred[:id]}:#{cred[:id]}:#{cred[:pass]}:::\n" ) + end + hashlist.close + + if smb_hashes.length > 0 + cracked_ntlm = {} + cracked_lm = {} + added = [] + + # Crack this in LANMAN format first + john_crack(hashlist.path, :wordlist => wordlist.path, :rules => 'single', :format => 'lm') + + # Parse cracked passwords and permute LANMAN->NTLM as needed + cracked = john_show_passwords(hashlist.path, 'lm') + cracked[:users].each_pair do |k,v| + next if v == "" + next if (v[0,7] == "???????" or v[7,7] == "???????") + next if not k =~ /^cred_(\d+)/m + cid = $1.to_i + + cracked_lm[k] = v + + cred_find = smb_hashes.select{|x| x[:id] == cid} + next if cred_find.length == 0 + + cred = cred_find.first + ntlm = cred.pass.split(":", 2).last + done = john_lm_upper_to_ntlm(v, ntlm) + cracked_ntlm[k] = done if done + end + + # Append any cracked values to the wordlist + tfd = ::File.open(wordlist.path, "ab") + cracked_lm.values.each {|w| if not added.include?(w); tfd.write( w + "\n" ); added << w; end } + cracked_ntlm.values.each {|w| if not added.include?(w); tfd.write( w + "\n" ); added << w; end } + tfd.close + + # Crack this in NTLM format + # Crack this in LANMAN format first + john_crack(hashlist.path, :wordlist => wordlist.path, :rules => 'single', :format => 'nt') + + # Parse cracked passwords + cracked = john_show_passwords(hashlist.path, 'nt') + cracked[:users].each_pair do |k,v| + next if cracked_ntlm[k] + cracked_ntlm[k] = v + end + + # Append any cracked values to the wordlist + tfd = ::File.open(wordlist.path, "ab") + cracked_ntlm.values.each {|w| if not added.include?(w); tfd.write( w + "\n" ); added << w; end } + tfd.close + + # Store the cracked results based on user_id => cred.id + cracked_ntlm.each_pair do |k,v| + next if not k =~ /^cred_(\d+)/m + cid = $1.to_i + + cred_find = smb_hashes.select{|x| x[:id] == cid} + next if cred_find.length == 0 + cred = cred_find.first + + print_good("Cracked: #{cred.user}:#{v} (#{cred.service.host.address}:#{cred.service.port})") + report_auth_info( + :host => cred.service.host, + :service => cred.service, + :user => cred.user, + :pass => v, + :type => "password", + :source_id => cred[:id], + :source_type => 'cracked' + ) + end + end + + # XXX: Enter other hash types here (shadow, etc) + + rescue ::Timeout::Error + ensure + wordlist.close rescue nil + hashlist.close rescue nil + ::File.unlink(wordlist.path) rescue nil + ::File.unlink(hashlist.path) rescue nil + end + end +end +