2011-03-27 20:14:56 +00:00
|
|
|
require 'rex/proto/ntlm/constants'
|
|
|
|
require 'rex/proto/ntlm/crypt'
|
2011-04-03 17:02:23 +00:00
|
|
|
require 'rex/proto/ntlm/exceptions'
|
2011-03-27 20:14:56 +00:00
|
|
|
|
2011-03-07 19:57:53 +00:00
|
|
|
module Rex
|
|
|
|
module Proto
|
|
|
|
module NTLM
|
|
|
|
class Utils
|
|
|
|
|
2011-03-27 20:14:56 +00:00
|
|
|
CONST = Rex::Proto::NTLM::Constants
|
|
|
|
CRYPT = Rex::Proto::NTLM::Crypt
|
2011-04-03 17:02:23 +00:00
|
|
|
XCEPT = Rex::Proto::NTLM::Exceptions
|
2011-03-27 20:14:56 +00:00
|
|
|
|
2011-03-07 19:57:53 +00:00
|
|
|
#duplicate from lib/rex/proto/smb/utils cause we only need this fonction from Rex::Proto::SMB::Utils
|
|
|
|
# Convert a unix timestamp to a 64-bit signed server time
|
|
|
|
def self.time_unix_to_smb(unix_time)
|
|
|
|
t64 = (unix_time + 11644473600) * 10000000
|
|
|
|
thi = (t64 & 0xffffffff00000000) >> 32
|
|
|
|
tlo = (t64 & 0x00000000ffffffff)
|
|
|
|
return [thi, tlo]
|
|
|
|
end
|
|
|
|
|
2011-03-27 20:14:56 +00:00
|
|
|
# Determine whether the password is a known hash format
|
|
|
|
def self.is_pass_ntlm_hash?(str)
|
|
|
|
str.downcase =~ /^[0-9a-f]{32}:[0-9a-f]{32}$/
|
|
|
|
end
|
|
|
|
|
2011-03-07 19:57:53 +00:00
|
|
|
#
|
|
|
|
# Prepends an ASN1 formatted length field to a piece of data
|
|
|
|
#
|
|
|
|
def self.asn1encode(str = '')
|
|
|
|
res = ''
|
|
|
|
|
|
|
|
# If the high bit of the first byte is 1, it contains the number of
|
|
|
|
# length bytes that follow
|
|
|
|
|
|
|
|
case str.length
|
|
|
|
when 0 .. 0x7F
|
|
|
|
res = [str.length].pack('C') + str
|
|
|
|
when 0x80 .. 0xFF
|
|
|
|
res = [0x81, str.length].pack('CC') + str
|
|
|
|
when 0x100 .. 0xFFFF
|
|
|
|
res = [0x82, str.length].pack('Cn') + str
|
|
|
|
when 0x10000 .. 0xffffff
|
|
|
|
res = [0x83, str.length >> 16, str.length & 0xFFFF].pack('CCn') + str
|
|
|
|
when 0x1000000 .. 0xffffffff
|
|
|
|
res = [0x84, str.length].pack('CN') + str
|
|
|
|
else
|
|
|
|
raise "ASN1 str too long"
|
|
|
|
end
|
|
|
|
return res
|
|
|
|
end
|
|
|
|
|
2011-03-09 16:57:33 +00:00
|
|
|
# GSS functions
|
|
|
|
|
|
|
|
# GSS BLOB usefull for SMB_NEGOCIATE_RESPONSE message
|
|
|
|
# mechTypes: 2 items :
|
|
|
|
# -MechType: 1.3.6.1.4.1.311.2.2.30 (SNMPv2-SMI::enterprises.311.2.2.30)
|
|
|
|
# -MechType: 1.3.6.1.4.1.311.2.2.10 (NTLMSSP - Microsoft NTLM Security Support Provider)
|
|
|
|
#
|
|
|
|
# this is the default on Win7
|
2011-03-07 19:57:53 +00:00
|
|
|
def self.make_simple_negotiate_secblob_resp
|
|
|
|
blob =
|
|
|
|
"\x60" + self.asn1encode(
|
|
|
|
"\x06" + self.asn1encode(
|
|
|
|
"\x2b\x06\x01\x05\x05\x02"
|
|
|
|
) +
|
|
|
|
"\xa0" + self.asn1encode(
|
|
|
|
"\x30" + self.asn1encode(
|
|
|
|
"\xa0" + self.asn1encode(
|
|
|
|
"\x30" + self.asn1encode(
|
|
|
|
"\x06" + self.asn1encode(
|
|
|
|
"\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a"
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
return blob
|
|
|
|
end
|
|
|
|
|
2011-03-09 16:57:33 +00:00
|
|
|
# GSS BLOB usefull for SMB_NEGOCIATE_RESPONSE message
|
|
|
|
# mechTypes: 4 items :
|
|
|
|
# MechType: 1.2.840.48018.1.2.2 (MS KRB5 - Microsoft Kerberos 5)
|
|
|
|
# MechType: 1.2.840.113554.1.2.2 (KRB5 - Kerberos 5)
|
|
|
|
# MechType: 1.2.840.113554.1.2.2.3 (KRB5 - Kerberos 5 - User to User)
|
|
|
|
# MechType: 1.3.6.1.4.1.311.2.2.10 (NTLMSSP - Microsoft NTLM Security Support Provider)
|
|
|
|
# mechListMIC:
|
|
|
|
# principal: account@domain
|
2011-03-07 19:57:53 +00:00
|
|
|
def self.make_negotiate_secblob_resp(account, domain)
|
|
|
|
blob =
|
|
|
|
"\x60" + self.asn1encode(
|
|
|
|
"\x06" + self.asn1encode(
|
|
|
|
"\x2b\x06\x01\x05\x05\x02"
|
|
|
|
) +
|
|
|
|
"\xa0" + self.asn1encode(
|
|
|
|
"\x30" + self.asn1encode(
|
|
|
|
"\xa0" + self.asn1encode(
|
|
|
|
"\x30" + self.asn1encode(
|
|
|
|
"\x06" + self.asn1encode(
|
|
|
|
"\x2a\x86\x48\x82\xf7\x12\x01\x02\x02"
|
|
|
|
) +
|
|
|
|
"\x06" + self.asn1encode(
|
|
|
|
"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02"
|
|
|
|
) +
|
|
|
|
"\x06" + self.asn1encode(
|
|
|
|
"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x03"
|
|
|
|
) +
|
|
|
|
"\x06" + self.asn1encode(
|
|
|
|
"\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a"
|
|
|
|
)
|
|
|
|
)
|
|
|
|
) +
|
|
|
|
"\xa3" + self.asn1encode(
|
|
|
|
"\x30" + self.asn1encode(
|
|
|
|
"\xa0" + self.asn1encode(
|
|
|
|
"\x1b" + self.asn1encode(
|
|
|
|
account + '@' + domain
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
return blob
|
|
|
|
end
|
|
|
|
|
2011-03-27 00:24:19 +00:00
|
|
|
# BLOB without GSS usefull for ntlmssp type 1 message
|
|
|
|
def self.make_ntlmssp_blob_init(domain = 'WORKGROUP', name = 'WORKSTATION', flags=0x80201)
|
|
|
|
blob = "NTLMSSP\x00" +
|
|
|
|
[1, flags].pack('VV') +
|
|
|
|
|
|
|
|
[
|
|
|
|
domain.length, #length
|
|
|
|
domain.length, #max length
|
|
|
|
32
|
|
|
|
].pack('vvV') +
|
|
|
|
|
|
|
|
[
|
|
|
|
name.length, #length
|
|
|
|
name.length, #max length
|
|
|
|
domain.length + 32
|
|
|
|
].pack('vvV') +
|
|
|
|
|
|
|
|
domain + name
|
|
|
|
return blob
|
|
|
|
end
|
2011-03-07 19:57:53 +00:00
|
|
|
|
2011-03-09 16:57:33 +00:00
|
|
|
# GSS BLOB usefull for ntlmssp type 1 message
|
2011-03-07 19:57:53 +00:00
|
|
|
def self.make_ntlmssp_secblob_init(domain = 'WORKGROUP', name = 'WORKSTATION', flags=0x80201)
|
|
|
|
blob =
|
|
|
|
"\x60" + self.asn1encode(
|
|
|
|
"\x06" + self.asn1encode(
|
|
|
|
"\x2b\x06\x01\x05\x05\x02"
|
|
|
|
) +
|
|
|
|
"\xa0" + self.asn1encode(
|
|
|
|
"\x30" + self.asn1encode(
|
|
|
|
"\xa0" + self.asn1encode(
|
|
|
|
"\x30" + self.asn1encode(
|
|
|
|
"\x06" + self.asn1encode(
|
|
|
|
"\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a"
|
|
|
|
)
|
|
|
|
)
|
|
|
|
) +
|
|
|
|
"\xa2" + self.asn1encode(
|
|
|
|
"\x04" + self.asn1encode(
|
2011-03-27 00:24:19 +00:00
|
|
|
make_ntlmssp_blob_init(domain, name, flags)
|
2011-03-07 19:57:53 +00:00
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
return blob
|
|
|
|
end
|
|
|
|
|
|
|
|
|
2011-03-09 16:57:33 +00:00
|
|
|
# BLOB without GSS usefull for ntlm type 2 message
|
2011-03-07 19:57:53 +00:00
|
|
|
def self.make_ntlmssp_blob_chall(win_domain, win_name, dns_domain, dns_name, chall, flags)
|
|
|
|
|
|
|
|
addr_list = ''
|
|
|
|
addr_list << [2, win_domain.length].pack('vv') + win_domain
|
|
|
|
addr_list << [1, win_name.length].pack('vv') + win_name
|
|
|
|
addr_list << [4, dns_domain.length].pack('vv') + dns_domain
|
|
|
|
addr_list << [3, dns_name.length].pack('vv') + dns_name
|
|
|
|
addr_list << [0, 0].pack('vv')
|
|
|
|
|
|
|
|
ptr = 0
|
|
|
|
blob = "NTLMSSP\x00" +
|
|
|
|
[2].pack('V') +
|
|
|
|
[
|
|
|
|
win_domain.length, # length
|
|
|
|
win_domain.length, # max length
|
|
|
|
(ptr += 48) # offset
|
|
|
|
].pack('vvV') +
|
|
|
|
[ flags ].pack('V') +
|
|
|
|
chall +
|
|
|
|
"\x00\x00\x00\x00\x00\x00\x00\x00" +
|
|
|
|
[
|
|
|
|
addr_list.length, # length
|
|
|
|
addr_list.length, # max length
|
|
|
|
(ptr += win_domain.length)
|
|
|
|
].pack('vvV') +
|
|
|
|
win_domain +
|
|
|
|
addr_list
|
|
|
|
return blob
|
|
|
|
end
|
|
|
|
|
2011-03-27 00:24:19 +00:00
|
|
|
# GSS BLOB usefull for ntlmssp type 2 message
|
|
|
|
def self.make_ntlmssp_secblob_chall(win_domain, win_name, dns_domain, dns_name, chall, flags)
|
|
|
|
|
|
|
|
blob =
|
|
|
|
"\xa1" + self.asn1encode(
|
|
|
|
"\x30" + self.asn1encode(
|
|
|
|
"\xa0" + self.asn1encode(
|
|
|
|
"\x0a" + self.asn1encode(
|
|
|
|
"\x01"
|
|
|
|
)
|
|
|
|
) +
|
|
|
|
"\xa1" + self.asn1encode(
|
|
|
|
"\x06" + self.asn1encode(
|
|
|
|
"\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a"
|
|
|
|
)
|
|
|
|
) +
|
|
|
|
"\xa2" + self.asn1encode(
|
|
|
|
"\x04" + self.asn1encode(
|
|
|
|
make_ntlmssp_blob_chall(win_domain, win_name, dns_domain, dns_name, chall, flags)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
2011-03-07 19:57:53 +00:00
|
|
|
|
2011-03-27 00:24:19 +00:00
|
|
|
return blob
|
|
|
|
end
|
2011-03-07 19:57:53 +00:00
|
|
|
|
2011-03-27 00:24:19 +00:00
|
|
|
# BLOB without GSS Usefull for ntlmssp type 3 message
|
|
|
|
def self.make_ntlmssp_blob_auth(domain, name, user, lm, ntlm, enc_session_key, flags = 0x080201)
|
2011-03-07 19:57:53 +00:00
|
|
|
lm ||= "\x00" * 24
|
|
|
|
ntlm ||= "\x00" * 24
|
|
|
|
|
|
|
|
domain_uni = Rex::Text.to_unicode(domain)
|
|
|
|
user_uni = Rex::Text.to_unicode(user)
|
|
|
|
name_uni = Rex::Text.to_unicode(name)
|
|
|
|
session = enc_session_key
|
|
|
|
|
|
|
|
ptr = 64
|
2011-03-27 00:24:19 +00:00
|
|
|
|
|
|
|
blob = "NTLMSSP\x00" +
|
|
|
|
[ 3 ].pack('V') +
|
|
|
|
|
|
|
|
[ # Lan Manager Response
|
|
|
|
lm.length,
|
|
|
|
lm.length,
|
|
|
|
(ptr)
|
|
|
|
].pack('vvV') +
|
|
|
|
|
|
|
|
[ # NTLM Manager Response
|
|
|
|
ntlm.length,
|
|
|
|
ntlm.length,
|
|
|
|
(ptr += lm.length)
|
|
|
|
].pack('vvV') +
|
|
|
|
|
|
|
|
[ # Domain Name
|
|
|
|
domain_uni.length,
|
|
|
|
domain_uni.length,
|
|
|
|
(ptr += ntlm.length)
|
|
|
|
].pack('vvV') +
|
|
|
|
|
|
|
|
[ # Username
|
|
|
|
user_uni.length,
|
|
|
|
user_uni.length,
|
|
|
|
(ptr += domain_uni.length)
|
|
|
|
].pack('vvV') +
|
|
|
|
|
|
|
|
[ # Hostname
|
|
|
|
name_uni.length,
|
|
|
|
name_uni.length,
|
|
|
|
(ptr += user_uni.length)
|
|
|
|
].pack('vvV') +
|
|
|
|
|
|
|
|
[ # Session Key (none)
|
|
|
|
session.length,
|
|
|
|
session.length,
|
|
|
|
(ptr += name_uni.length)
|
|
|
|
].pack('vvV') +
|
|
|
|
|
|
|
|
[ flags ].pack('V') +
|
|
|
|
|
|
|
|
lm +
|
|
|
|
ntlm +
|
|
|
|
domain_uni +
|
|
|
|
user_uni +
|
|
|
|
name_uni +
|
|
|
|
session + "\x00"
|
|
|
|
return blob
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
# GSS BLOB Usefull for ntlmssp type 3 message
|
|
|
|
def self.make_ntlmssp_secblob_auth(domain, name, user, lm, ntlm, enc_session_key, flags = 0x080201)
|
|
|
|
|
2011-03-07 19:57:53 +00:00
|
|
|
blob =
|
|
|
|
"\xa1" + self.asn1encode(
|
|
|
|
"\x30" + self.asn1encode(
|
|
|
|
"\xa2" + self.asn1encode(
|
|
|
|
"\x04" + self.asn1encode(
|
2011-03-27 00:24:19 +00:00
|
|
|
make_ntlmssp_blob_auth(domain, name, user, lm, ntlm, enc_session_key, flags )
|
2011-03-07 19:57:53 +00:00
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
return blob
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
# GSS BLOB Usefull for SMB Success
|
|
|
|
def self.make_ntlmv2_secblob_success
|
|
|
|
blob =
|
|
|
|
"\xa1" + self.asn1encode(
|
|
|
|
"\x30" + self.asn1encode(
|
|
|
|
"\xa0" + self.asn1encode(
|
|
|
|
"\x0a" + self.asn1encode(
|
|
|
|
"\x00"
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
return blob
|
|
|
|
end
|
|
|
|
|
2011-03-27 20:14:56 +00:00
|
|
|
# Return the correct ntlmflags upon the configuration
|
|
|
|
def self.make_ntlm_flags(opt = {})
|
|
|
|
|
|
|
|
signing = opt[:signing] != nil ? opt[:signing] : false
|
|
|
|
usentlm2_session = opt[:usentlm2_session] != nil ? opt[:usentlm2_session] : true
|
|
|
|
use_ntlmv2 = opt[:use_ntlmv2] != nil ? opt[:use_ntlmv2] : false
|
|
|
|
send_lm = opt[:send_lm] != nil ? opt[:send_lm] : true
|
|
|
|
send_ntlm = opt[:send_ntlm] != nil ? opt[:send_ntlm] : true
|
|
|
|
use_lanman_key = opt[:use_lanman_key] != nil ? opt[:use_lanman_key] : false
|
|
|
|
|
|
|
|
if signing
|
|
|
|
ntlmssp_flags = 0xe2088215
|
|
|
|
else
|
|
|
|
|
|
|
|
ntlmssp_flags = 0xa2080205
|
|
|
|
end
|
|
|
|
|
|
|
|
if usentlm2_session
|
|
|
|
if use_ntlmv2
|
|
|
|
#set Negotiate Target Info
|
|
|
|
ntlmssp_flags |= CONST::NEGOTIATE_TARGET_INFO
|
|
|
|
end
|
|
|
|
|
|
|
|
else
|
|
|
|
#remove the ntlm2_session flag
|
|
|
|
ntlmssp_flags &= 0xfff7ffff
|
|
|
|
#set lanmanflag only when lm and ntlm are sent
|
|
|
|
if send_lm
|
|
|
|
ntlmssp_flags |= CONST::NEGOTIATE_LMKEY if use_lanman_key
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
#we can also downgrade ntlm2_session when we send only lmv1
|
|
|
|
ntlmssp_flags &= 0xfff7ffff if usentlm2_session && (not use_ntlmv2) && (not send_ntlm)
|
|
|
|
|
|
|
|
return ntlmssp_flags
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
# Parse an ntlm type 2 challenge blob and return usefull data
|
|
|
|
def self.parse_ntlm_type_2_blob(blob)
|
|
|
|
data = {}
|
|
|
|
# Extract the NTLM challenge key the lazy way
|
|
|
|
cidx = blob.index("NTLMSSP\x00\x02\x00\x00\x00")
|
|
|
|
|
|
|
|
if not cidx
|
2011-04-03 17:02:23 +00:00
|
|
|
raise XCEPT::NTLMMissingChallenge
|
2011-03-27 20:14:56 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
data[:challenge_key] = blob[cidx + 24, 8]
|
|
|
|
|
|
|
|
data[:server_ntlmssp_flags] = blob[cidx + 20, 4].unpack("V")[0]
|
|
|
|
|
|
|
|
# Extract the address list from the blob
|
|
|
|
alist_len,alist_mlen,alist_off = blob[cidx + 40, 8].unpack("vvV")
|
|
|
|
alist_buf = blob[cidx + alist_off, alist_len]
|
|
|
|
|
|
|
|
while(alist_buf.length > 0)
|
|
|
|
atype, alen = alist_buf.slice!(0,4).unpack('vv')
|
|
|
|
break if atype == 0x00
|
|
|
|
addr = alist_buf.slice!(0, alen)
|
|
|
|
case atype
|
|
|
|
when 1
|
|
|
|
#netbios name
|
|
|
|
data[:default_name] = addr.gsub("\x00", '')
|
|
|
|
when 2
|
|
|
|
#netbios domain
|
|
|
|
data[:default_domain] = addr.gsub("\x00", '')
|
|
|
|
when 3
|
|
|
|
#dns name
|
|
|
|
data[:dns_host_name] = addr.gsub("\x00", '')
|
|
|
|
when 4
|
|
|
|
#dns domain
|
|
|
|
data[:dns_domain_name] = addr.gsub("\x00", '')
|
|
|
|
when 5
|
|
|
|
#The FQDN of the forest.
|
|
|
|
when 6
|
|
|
|
#A 32-bit value indicating server or client configuration
|
|
|
|
when 7
|
|
|
|
#Client time
|
|
|
|
data[:chall_MsvAvTimestamp] = addr
|
|
|
|
when 8
|
|
|
|
#A Restriction_Encoding structure
|
|
|
|
when 9
|
|
|
|
#The SPN of the target server.
|
|
|
|
when 10
|
|
|
|
#A channel bindings hash.
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return data
|
|
|
|
end
|
|
|
|
|
2011-03-09 16:57:33 +00:00
|
|
|
# This function return an ntlmv2 client challenge
|
2011-03-16 00:27:44 +00:00
|
|
|
# This is a partial implementation, full description is in [MS-NLMP].pdf around 3.1.5.2.1 :-/
|
2011-03-16 00:12:07 +00:00
|
|
|
def self.make_ntlmv2_clientchallenge(win_domain, win_name, dns_domain, dns_name,
|
|
|
|
client_challenge = nil, chall_MsvAvTimestamp = nil, spnopt = {})
|
2011-03-07 19:57:53 +00:00
|
|
|
|
|
|
|
client_challenge ||= Rex::Text.rand_text(8)
|
2011-03-09 16:57:33 +00:00
|
|
|
# We have to set the timestamps here to the one in the challenge message from server if present
|
2011-03-27 20:14:56 +00:00
|
|
|
# If we don't do that, recent server like Seven/2008 will send a STATUS_INVALID_PARAMETER error packet
|
|
|
|
timestamp = chall_MsvAvTimestamp != '' ? chall_MsvAvTimestamp : self.time_unix_to_smb(Time.now.to_i).reverse.pack("VV")
|
2011-03-09 16:57:33 +00:00
|
|
|
# Make those values unicode as requested
|
2011-03-07 19:57:53 +00:00
|
|
|
win_domain = Rex::Text.to_unicode(win_domain)
|
|
|
|
win_name = Rex::Text.to_unicode(win_name)
|
|
|
|
dns_domain = Rex::Text.to_unicode(dns_domain)
|
|
|
|
dns_name = Rex::Text.to_unicode(dns_name)
|
2011-03-09 16:57:33 +00:00
|
|
|
# Make the AV_PAIRs
|
2011-03-07 19:57:53 +00:00
|
|
|
addr_list = ''
|
|
|
|
addr_list << [2, win_domain.length].pack('vv') + win_domain
|
|
|
|
addr_list << [1, win_name.length].pack('vv') + win_name
|
|
|
|
addr_list << [4, dns_domain.length].pack('vv') + dns_domain
|
|
|
|
addr_list << [3, dns_name.length].pack('vv') + dns_name
|
|
|
|
addr_list << [7, 8].pack('vv') + timestamp
|
|
|
|
|
2011-03-16 00:27:44 +00:00
|
|
|
# Windows Seven / 2008r2 Request this type if in local security policies,
|
2011-03-16 00:12:07 +00:00
|
|
|
# Microsoft network server : Server SPN target name validation level is set to <Required from client>
|
2011-03-16 00:27:44 +00:00
|
|
|
# otherwise it send an STATUS_ACCESS_DENIED packet
|
2011-03-16 00:12:07 +00:00
|
|
|
if spnopt[:use_spn]
|
|
|
|
spn= Rex::Text.to_unicode("cifs/#{spnopt[:name] || 'unknow'}")
|
|
|
|
addr_list << [9, spn.length].pack('vv') + spn
|
|
|
|
end
|
|
|
|
|
2011-03-09 16:57:33 +00:00
|
|
|
# MAY BE USEFUL FOR FUTURE
|
|
|
|
# Seven (client) add at least one more av that is of type MsAvRestrictions (8)
|
|
|
|
# maybe this will be usefull with future windows OSs but has no use at all for the moment afaik
|
|
|
|
# restriction_encoding = [48,0,0,0].pack("VVV") + # Size, Z4, IntegrityLevel, SubjectIntegrityLevel
|
|
|
|
# Rex::Text.rand_text(32) # MachineId generated on startup on win7 and above
|
|
|
|
# addr_list << [8, restriction_encoding.length].pack('vv') + restriction_encoding
|
|
|
|
|
|
|
|
# Seven (client) and maybe others versions also add an av of type MsvChannelBindings (10) but the hash is "\x00" * 16
|
|
|
|
# addr_list << [10, 16].pack('vv') + "\x00" * 16
|
|
|
|
|
2011-03-16 00:12:07 +00:00
|
|
|
|
2011-03-07 19:57:53 +00:00
|
|
|
addr_list << [0, 0].pack('vv')
|
|
|
|
ntlm_clientchallenge = [1,1,0,0].pack("CCvV") + #RespType, HiRespType, Reserved1, Reserved2
|
|
|
|
timestamp + #Timestamp
|
|
|
|
client_challenge + #clientchallenge
|
|
|
|
[0].pack("V") + #Reserved3
|
|
|
|
addr_list + "\x00" * 4
|
|
|
|
|
|
|
|
end
|
|
|
|
|
2011-03-27 20:14:56 +00:00
|
|
|
# create lm/ntlm responses
|
|
|
|
def self.create_lm_ntlm_responses(user, pass, challenge_key, domain = '', default_name = '', default_domain = '',
|
|
|
|
dns_host_name = '', dns_domain_name = '', chall_MsvAvTimestamp = nil, spnopt = {}, opt = {} )
|
|
|
|
|
|
|
|
usentlm2_session = opt[:usentlm2_session] != nil ? opt[:usentlm2_session] : true
|
|
|
|
use_ntlmv2 = opt[:use_ntlmv2] != nil ? opt[:use_ntlmv2] : false
|
|
|
|
send_lm = opt[:send_lm] != nil ? opt[:send_lm] : true
|
|
|
|
send_ntlm = opt[:send_ntlm] != nil ? opt[:send_ntlm] : true
|
|
|
|
|
|
|
|
#calculate the lm/ntlm response
|
|
|
|
resp_lm = "\x00" * 24
|
|
|
|
resp_ntlm = "\x00" * 24
|
|
|
|
|
|
|
|
client_challenge = Rex::Text.rand_text(8)
|
|
|
|
ntlm_cli_challenge = ''
|
|
|
|
if send_ntlm #should be default
|
|
|
|
if usentlm2_session
|
|
|
|
if use_ntlmv2
|
|
|
|
ntlm_cli_challenge = self.make_ntlmv2_clientchallenge(default_domain, default_name, dns_domain_name,
|
|
|
|
dns_host_name,client_challenge ,
|
|
|
|
chall_MsvAvTimestamp, spnopt)
|
|
|
|
if self.is_pass_ntlm_hash?(pass)
|
|
|
|
argntlm = {
|
|
|
|
:ntlmv2_hash => CRYPT::ntlmv2_hash(
|
|
|
|
user,
|
|
|
|
[ pass.upcase()[33,65] ].pack('H32'),
|
|
|
|
domain,{:pass_is_hash => true}
|
|
|
|
),
|
|
|
|
:challenge => challenge_key
|
|
|
|
}
|
|
|
|
else
|
|
|
|
argntlm = {
|
|
|
|
:ntlmv2_hash => CRYPT::ntlmv2_hash(user, pass, domain),
|
|
|
|
:challenge => challenge_key
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
optntlm = { :nt_client_challenge => ntlm_cli_challenge}
|
|
|
|
ntlmv2_response = CRYPT::ntlmv2_response(argntlm,optntlm)
|
|
|
|
resp_ntlm = ntlmv2_response
|
|
|
|
|
|
|
|
if send_lm
|
|
|
|
if self.is_pass_ntlm_hash?(pass)
|
|
|
|
arglm = {
|
|
|
|
:ntlmv2_hash => CRYPT::ntlmv2_hash(
|
|
|
|
user,
|
|
|
|
[ pass.upcase()[33,65] ].pack('H32'),
|
|
|
|
domain,{:pass_is_hash => true}
|
|
|
|
),
|
|
|
|
:challenge => challenge_key
|
|
|
|
}
|
|
|
|
else
|
|
|
|
arglm = {
|
|
|
|
:ntlmv2_hash => CRYPT::ntlmv2_hash(user,pass, domain),
|
|
|
|
:challenge => challenge_key
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
optlm = { :client_challenge => client_challenge }
|
|
|
|
resp_lm = CRYPT::lmv2_response(arglm, optlm)
|
|
|
|
else
|
|
|
|
resp_lm = "\x00" * 24
|
|
|
|
end
|
|
|
|
|
|
|
|
else # ntlm2_session
|
|
|
|
if self.is_pass_ntlm_hash?(pass)
|
|
|
|
argntlm = {
|
|
|
|
:ntlm_hash => [ pass.upcase()[33,65] ].pack('H32'),
|
|
|
|
:challenge => challenge_key
|
|
|
|
}
|
|
|
|
else
|
|
|
|
argntlm = {
|
|
|
|
:ntlm_hash => CRYPT::ntlm_hash(pass),
|
|
|
|
:challenge => challenge_key
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
optntlm = { :client_challenge => client_challenge}
|
|
|
|
resp_ntlm = CRYPT::ntlm2_session(argntlm,optntlm).join[24,24]
|
|
|
|
|
|
|
|
# Generate the fake LANMAN hash
|
|
|
|
resp_lm = client_challenge + ("\x00" * 16)
|
|
|
|
end
|
|
|
|
|
|
|
|
else # we use lmv1/ntlmv1
|
|
|
|
if self.is_pass_ntlm_hash?(pass)
|
|
|
|
argntlm = {
|
|
|
|
:ntlm_hash => [ pass.upcase()[33,65] ].pack('H32'),
|
|
|
|
:challenge => challenge_key
|
|
|
|
}
|
|
|
|
else
|
|
|
|
argntlm = {
|
|
|
|
:ntlm_hash => CRYPT::ntlm_hash(pass),
|
|
|
|
:challenge => challenge_key
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
resp_ntlm = CRYPT::ntlm_response(argntlm)
|
|
|
|
if send_lm
|
|
|
|
if self.is_pass_ntlm_hash?(pass)
|
|
|
|
arglm = {
|
|
|
|
:lm_hash => [ pass.upcase()[0,32] ].pack('H32'),
|
|
|
|
:challenge => challenge_key
|
|
|
|
}
|
|
|
|
else
|
|
|
|
arglm = {
|
|
|
|
:lm_hash => CRYPT::lm_hash(pass),
|
|
|
|
:challenge => challenge_key
|
|
|
|
}
|
|
|
|
end
|
|
|
|
resp_lm = CRYPT::lm_response(arglm)
|
|
|
|
else
|
|
|
|
#when windows does not send lm in ntlmv1 type response,
|
|
|
|
# it gives lm response the same value as ntlm response
|
|
|
|
resp_lm = resp_ntlm
|
|
|
|
end
|
|
|
|
end
|
|
|
|
else #send_ntlm = false
|
|
|
|
#lmv2
|
|
|
|
if usentlm2_session && use_ntlmv2
|
|
|
|
if self.is_pass_ntlm_hash?(pass)
|
|
|
|
arglm = {
|
|
|
|
:ntlmv2_hash => CRYPT::ntlmv2_hash(
|
|
|
|
user,
|
|
|
|
[ pass.upcase()[33,65] ].pack('H32'),
|
|
|
|
domain,{:pass_is_hash => true}
|
|
|
|
),
|
|
|
|
:challenge => challenge_key
|
|
|
|
}
|
|
|
|
else
|
|
|
|
arglm = {
|
|
|
|
:ntlmv2_hash => CRYPT::ntlmv2_hash(user,pass, domain),
|
|
|
|
:challenge => challenge_key
|
|
|
|
}
|
|
|
|
end
|
|
|
|
optlm = { :client_challenge => client_challenge }
|
|
|
|
resp_lm = CRYPT::lmv2_response(arglm, optlm)
|
|
|
|
else
|
|
|
|
if self.is_pass_ntlm_hash?(pass)
|
|
|
|
arglm = {
|
|
|
|
:lm_hash => [ pass.upcase()[0,32] ].pack('H32'),
|
|
|
|
:challenge => challenge_key
|
|
|
|
}
|
|
|
|
else
|
|
|
|
arglm = {
|
|
|
|
:lm_hash => CRYPT::lm_hash(pass),
|
|
|
|
:challenge => challenge_key
|
|
|
|
}
|
|
|
|
end
|
|
|
|
resp_lm = CRYPT::lm_response(arglm)
|
|
|
|
end
|
|
|
|
resp_ntlm = ""
|
|
|
|
end
|
|
|
|
return resp_lm, resp_ntlm, client_challenge, ntlm_cli_challenge
|
|
|
|
end
|
|
|
|
|
|
|
|
# create the session key
|
2011-04-01 20:58:11 +00:00
|
|
|
def self.create_session_key(ntlmssp_flags, server_ntlmssp_flags, user, pass, domain, challenge_key,
|
2011-03-27 20:14:56 +00:00
|
|
|
client_challenge = '', ntlm_cli_challenge = '' , opt = {} )
|
|
|
|
|
|
|
|
usentlm2_session = opt[:usentlm2_session] != nil ? opt[:usentlm2_session] : true
|
|
|
|
use_ntlmv2 = opt[:use_ntlmv2] != nil ? opt[:use_ntlmv2] : false
|
|
|
|
send_lm = opt[:send_lm] != nil ? opt[:send_lm] : true
|
|
|
|
send_ntlm = opt[:send_ntlm] != nil ? opt[:send_ntlm] : true
|
|
|
|
use_lanman_key = opt[:use_lanman_key] != nil ? opt[:use_lanman_key] : false
|
|
|
|
|
|
|
|
# Create the sessionkey (aka signing key, aka mackey) and encrypted session key
|
|
|
|
# Server will decide for key_size and key_exchange
|
|
|
|
enc_session_key = ''
|
|
|
|
signing_key = ''
|
|
|
|
|
|
|
|
# Set default key size and key exchange values
|
|
|
|
key_size = 40
|
|
|
|
key_exchange = false
|
|
|
|
# Remove ntlmssp.negotiate56
|
|
|
|
ntlmssp_flags &= 0x7fffffff
|
|
|
|
# Remove ntlmssp.negotiatekeyexch
|
|
|
|
ntlmssp_flags &= 0xbfffffff
|
|
|
|
# Remove ntlmssp.negotiate128
|
|
|
|
ntlmssp_flags &= 0xdfffffff
|
|
|
|
# Check the keyexchange
|
|
|
|
if server_ntlmssp_flags & CONST::NEGOTIATE_KEY_EXCH != 0 then
|
|
|
|
key_exchange = true
|
|
|
|
ntlmssp_flags |= CONST::NEGOTIATE_KEY_EXCH
|
|
|
|
end
|
|
|
|
# Check 128bits
|
|
|
|
if server_ntlmssp_flags & CONST::NEGOTIATE_128 != 0 then
|
|
|
|
key_size = 128
|
|
|
|
ntlmssp_flags |= CONST::NEGOTIATE_128
|
|
|
|
ntlmssp_flags |= CONST::NEGOTIATE_56
|
|
|
|
# Check 56bits
|
|
|
|
else
|
|
|
|
if server_ntlmssp_flags & CONST::NEGOTIATE_56 != 0 then
|
|
|
|
key_size = 56
|
|
|
|
ntlmssp_flags |= CONST::NEGOTIATE_56
|
|
|
|
end
|
|
|
|
end
|
|
|
|
# Generate the user session key
|
|
|
|
lanman_weak = false
|
|
|
|
if send_ntlm # Should be default
|
|
|
|
if usentlm2_session
|
|
|
|
if use_ntlmv2
|
|
|
|
if self.is_pass_ntlm_hash?(pass)
|
|
|
|
user_session_key = CRYPT::ntlmv2_user_session_key(user,
|
|
|
|
[ pass.upcase()[33,65] ].pack('H32'),
|
|
|
|
domain,
|
|
|
|
challenge_key, ntlm_cli_challenge,
|
|
|
|
{:pass_is_hash => true})
|
|
|
|
else
|
|
|
|
user_session_key = CRYPT::ntlmv2_user_session_key(user, pass, domain,
|
|
|
|
challenge_key, ntlm_cli_challenge)
|
|
|
|
end
|
|
|
|
else
|
|
|
|
if self.is_pass_ntlm_hash?(pass)
|
|
|
|
user_session_key = CRYPT::ntlm2_session_user_session_key([ pass.upcase()[33,65] ].pack('H32'),
|
|
|
|
challenge_key,
|
|
|
|
client_challenge,
|
|
|
|
{:pass_is_hash => true})
|
|
|
|
else
|
|
|
|
user_session_key = CRYPT::ntlm2_session_user_session_key(pass, challenge_key,
|
|
|
|
client_challenge)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
else # lmv1/ntlmv1
|
|
|
|
# lanman_key may also be used without ntlm response but it is not so much used
|
|
|
|
# so we don't care about this feature
|
|
|
|
if send_lm && use_lanman_key
|
|
|
|
if self.is_pass_ntlm_hash?(pass)
|
|
|
|
user_session_key = CRYPT::lanman_session_key([ pass.upcase()[0,32] ].pack('H32'),
|
|
|
|
challenge_key,
|
|
|
|
{:pass_is_hash => true})
|
|
|
|
else
|
|
|
|
user_session_key = CRYPT::lanman_session_key(pass, challenge_key)
|
|
|
|
end
|
|
|
|
lanman_weak = true
|
|
|
|
|
|
|
|
|
|
|
|
else
|
|
|
|
if self.is_pass_ntlm_hash?(pass)
|
|
|
|
user_session_key = CRYPT::ntlmv1_user_session_key([ pass.upcase()[33,65] ].pack('H32'),
|
|
|
|
{:pass_is_hash => true})
|
|
|
|
else
|
|
|
|
user_session_key = CRYPT::ntlmv1_user_session_key(pass)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
else
|
|
|
|
if usentlm2_session && use_ntlmv2
|
|
|
|
if self.is_pass_ntlm_hash?(pass)
|
|
|
|
user_session_key = CRYPT::lmv2_user_session_key(user, [ pass.upcase()[33,65] ].pack('H32'),
|
|
|
|
domain,
|
|
|
|
challenge_key, client_challenge,
|
|
|
|
{:pass_is_hash => true})
|
|
|
|
else
|
|
|
|
user_session_key = CRYPT::lmv2_user_session_key(user, pass, domain,
|
|
|
|
challenge_key, client_challenge)
|
|
|
|
end
|
|
|
|
else
|
|
|
|
if self.is_pass_ntlm_hash?(pass)
|
|
|
|
user_session_key = CRYPT::lmv1_user_session_key([ pass.upcase()[0,32] ].pack('H32'),
|
|
|
|
{:pass_is_hash => true})
|
|
|
|
else
|
|
|
|
user_session_key = CRYPT::lmv1_user_session_key(pass)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
user_session_key = CRYPT::make_weak_sessionkey(user_session_key,key_size, lanman_weak)
|
|
|
|
|
|
|
|
# Sessionkey and encrypted session key
|
|
|
|
if key_exchange
|
|
|
|
signing_key = Rex::Text.rand_text(16)
|
|
|
|
enc_session_key = CRYPT::encrypt_sessionkey(signing_key, user_session_key)
|
|
|
|
else
|
|
|
|
signing_key = user_session_key
|
|
|
|
end
|
|
|
|
|
2011-04-01 20:58:11 +00:00
|
|
|
return signing_key, enc_session_key, ntlmssp_flags
|
2011-03-27 20:14:56 +00:00
|
|
|
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
2011-03-07 19:57:53 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|