This commit adds a basic "analyzer" module for creds
git-svn-id: file:///home/svn/framework3/trunk@13136 4d416f70-5f16-0410-b530-b9f4589650daunstable
parent
a4ea859a4f
commit
7754f0d8f2
|
@ -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
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
Loading…
Reference in New Issue