psnuffle : add a smb protocol decoder
git-svn-id: file:///home/svn/framework3/trunk@13375 4d416f70-5f16-0410-b530-b9f4589650daunstable
parent
9dd3c5fbf5
commit
b9bb5c454d
|
@ -0,0 +1,202 @@
|
|||
# Psnuffle password sniffer add-on class for smb
|
||||
# part of psnuffle sniffer auxiliary module
|
||||
#
|
||||
# When db is available reports go into db
|
||||
#
|
||||
|
||||
#Memo :
|
||||
# Authentification without extended security set
|
||||
#1) client -> server : smb_negotiate (0x72) : smb.flags2.extended_sec = 0
|
||||
#2) server -> client : smb_negotiate (0x72) : smb.flags2.extended_sec = 0 and contains server challenge (aka encryption key) and wordcount = 17
|
||||
#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
|
||||
#1) client -> server : 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
|
||||
#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
|
||||
#6) server -> client : smb_setup_andx (0x73) : if status = success then authentification = ok
|
||||
|
||||
class SnifferSMB < BaseProtocolParser
|
||||
|
||||
def register_sigs
|
||||
self.sigs = {
|
||||
:setupandx => /\xffSMB\x73/,
|
||||
:negotiate => /\xffSMB\x72/,
|
||||
}
|
||||
end
|
||||
|
||||
def parse(pkt)
|
||||
# We want to return immediatly if we do not have a packet which is handled by us
|
||||
return unless pkt.is_tcp?
|
||||
return if (pkt.tcp_sport != 445 and pkt.tcp_dport != 445)
|
||||
s = find_session((pkt.tcp_sport == 445) ? get_session_src(pkt) : get_session_dst(pkt))
|
||||
|
||||
self.sigs.each_key do |k|
|
||||
# There is only one pattern per run to test
|
||||
matched = nil
|
||||
matches = nil
|
||||
|
||||
if(pkt.payload =~ self.sigs[k])
|
||||
matched = k
|
||||
matches = $1
|
||||
end
|
||||
|
||||
case matched
|
||||
when :negotiate
|
||||
payload = pkt.payload.dup
|
||||
wordcount = payload[36,1].unpack("C")[0]
|
||||
#negotiate response
|
||||
if wordcount == 17
|
||||
flags2 = payload[14,2].unpack("v")[0]
|
||||
#the server challenge is here
|
||||
if flags2 & 0x800 == 0
|
||||
s[:challenge] = payload[73,8].unpack("H*")[0]
|
||||
s[:last] = :negotiate
|
||||
end
|
||||
end
|
||||
|
||||
when :setupandx
|
||||
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] == :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
|
||||
# No matches, no saved state
|
||||
else
|
||||
sessions[s[:session]].merge!({k => matches})
|
||||
end # end case matched
|
||||
|
||||
end # end of each_key
|
||||
end # end of parse
|
||||
|
||||
#ntlmv1, ntlmv2 or ntlm2_session
|
||||
def detect_ntlm_ver(lmhash, ntlmhash)
|
||||
return "NTLMv2" if ntlmhash.length > 48
|
||||
if lmhash.length == 48 and ntlmhash.length == 48
|
||||
if lmhash != "00" * 24 and lmhash[16,32] == "00" * 16
|
||||
return "NTLM2_SESSION"
|
||||
else
|
||||
return "NTLMv1"
|
||||
end
|
||||
else
|
||||
raise RuntimeError, "Unknow hash type"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue