This commit fixes much of the NTLM crypt code to work properly again, fixing #3918 as well.
git-svn-id: file:///home/svn/framework3/trunk@11914 4d416f70-5f16-0410-b530-b9f4589650daunstable
parent
e1b48c3f81
commit
612c2e6796
|
@ -11,6 +11,9 @@
|
||||||
# You can distribute/modify this program under the terms of the
|
# You can distribute/modify this program under the terms of the
|
||||||
# Ruby License.
|
# Ruby License.
|
||||||
#
|
#
|
||||||
|
# 2011-03-08 improved through a code merge with Metasploit's SMB::Crypt
|
||||||
|
# -------------------------------------------------------------
|
||||||
|
#
|
||||||
# 2011-02-23 refactored and improved by Alexandre Maloteaux for Metasploit Project
|
# 2011-02-23 refactored and improved by Alexandre Maloteaux for Metasploit Project
|
||||||
# -------------------------------------------------------------
|
# -------------------------------------------------------------
|
||||||
#
|
#
|
||||||
|
@ -63,17 +66,25 @@ BASE = Rex::Proto::NTLM::Base
|
||||||
@@loaded_openssl = true
|
@@loaded_openssl = true
|
||||||
rescue ::Exception
|
rescue ::Exception
|
||||||
end
|
end
|
||||||
|
|
||||||
begin
|
|
||||||
|
|
||||||
def self.gen_keys(str)
|
def self.gen_keys(str)
|
||||||
Rex::Text::split_to_a(str, 7).map{ |str7|
|
str.scan(/.{7}/).map{ |key| des_56_to_64(key) }
|
||||||
bits = Rex::Text::split_to_a(str7.unpack("B*")[0], 7).inject('')\
|
|
||||||
{|ret, tkn| ret += tkn + (tkn.gsub('1', '').size % 2).to_s }
|
|
||||||
[bits].pack("B*")
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.des_56_to_64(ckey56s)
|
||||||
|
ckey64 = []
|
||||||
|
ckey56 = ckey56s.unpack('C*')
|
||||||
|
ckey64[0] = ckey56[0]
|
||||||
|
ckey64[1] = ((ckey56[0] << 7) & 0xFF) | (ckey56[1] >> 1)
|
||||||
|
ckey64[2] = ((ckey56[1] << 6) & 0xFF) | (ckey56[2] >> 2)
|
||||||
|
ckey64[3] = ((ckey56[2] << 5) & 0xFF) | (ckey56[3] >> 3)
|
||||||
|
ckey64[4] = ((ckey56[3] << 4) & 0xFF) | (ckey56[4] >> 4)
|
||||||
|
ckey64[5] = ((ckey56[4] << 3) & 0xFF) | (ckey56[5] >> 5)
|
||||||
|
ckey64[6] = ((ckey56[5] << 2) & 0xFF) | (ckey56[6] >> 6)
|
||||||
|
ckey64[7] = (ckey56[6] << 1) & 0xFF
|
||||||
|
ckey64.pack('C*')
|
||||||
|
end
|
||||||
|
|
||||||
def self.apply_des(plain, keys)
|
def self.apply_des(plain, keys)
|
||||||
raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
|
raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
|
||||||
dec = OpenSSL::Cipher::DES.new
|
dec = OpenSSL::Cipher::DES.new
|
||||||
|
@ -84,8 +95,8 @@ begin
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.lm_hash(password, half = false)
|
def self.lm_hash(password, half = false)
|
||||||
if half then size = 7 else size = 14 end
|
size = half ? 7 : 14
|
||||||
keys = gen_keys password.upcase.ljust(size, "\0")
|
keys = gen_keys(password.upcase.ljust(size, "\0"))
|
||||||
apply_des(CONST::LM_MAGIC, keys).join
|
apply_des(CONST::LM_MAGIC, keys).join
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -95,18 +106,20 @@ begin
|
||||||
unless opt[:unicode]
|
unless opt[:unicode]
|
||||||
pwd = Rex::Text.to_unicode(pwd)
|
pwd = Rex::Text.to_unicode(pwd)
|
||||||
end
|
end
|
||||||
OpenSSL::Digest::MD4.digest pwd
|
OpenSSL::Digest::MD4.digest(pwd)
|
||||||
end
|
end
|
||||||
|
|
||||||
#this hash is used for lmv2/ntlmv2 response calculation
|
# This hash is used for lmv2/ntlmv2 response calculation
|
||||||
def self.ntlmv2_hash(user, password, domain, opt={})
|
def self.ntlmv2_hash(user, password, domain, opt={})
|
||||||
raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
|
raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
|
||||||
|
|
||||||
if opt[:pass_is_hash]
|
if opt[:pass_is_hash]
|
||||||
ntlmhash = password
|
ntlmhash = password
|
||||||
else
|
else
|
||||||
ntlmhash = ntlm_hash(password, opt)
|
ntlmhash = ntlm_hash(password, opt)
|
||||||
end
|
end
|
||||||
#With Win 7 and maybe other OSs we sometimes get the domain not uppercased
|
|
||||||
|
# With Win 7 and maybe other OSs we sometimes get the domain not uppercased
|
||||||
userdomain = user.upcase + domain
|
userdomain = user.upcase + domain
|
||||||
unless opt[:unicode]
|
unless opt[:unicode]
|
||||||
userdomain = Rex::Text.to_unicode(userdomain)
|
userdomain = Rex::Text.to_unicode(userdomain)
|
||||||
|
@ -114,7 +127,7 @@ begin
|
||||||
OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmhash, userdomain)
|
OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmhash, userdomain)
|
||||||
end
|
end
|
||||||
|
|
||||||
# responses
|
# Create the LANMAN response
|
||||||
def self.lm_response(arg, half = false)
|
def self.lm_response(arg, half = false)
|
||||||
begin
|
begin
|
||||||
hash = arg[:lm_hash]
|
hash = arg[:lm_hash]
|
||||||
|
@ -128,61 +141,59 @@ begin
|
||||||
apply_des(chal, keys).join
|
apply_des(chal, keys).join
|
||||||
end
|
end
|
||||||
|
|
||||||
#synonym of lm_response for old compatibility with lib/rex/proto/smb/crypt
|
# Synonym of lm_response for old compatibility with lib/rex/proto/smb/crypt
|
||||||
def self.lanman_des(password, challenge)
|
def self.lanman_des(password, challenge)
|
||||||
arglm = { :lm_hash => self.lm_hash(password),
|
lm_response({
|
||||||
:challenge => challenge }
|
:lm_hash => self.lm_hash(password),
|
||||||
self.lm_response(arglm)
|
:challenge => challenge
|
||||||
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.ntlm_response(arg)
|
def self.ntlm_response(arg)
|
||||||
hash = arg[:ntlm_hash]
|
hash = arg[:ntlm_hash]
|
||||||
chal = arg[:challenge]
|
chal = arg[:challenge]
|
||||||
chal = BASE::pack_int64le(chal) if chal.is_a?(::Integer)
|
chal = BASE::pack_int64le(chal) if chal.is_a?(::Integer)
|
||||||
keys = gen_keys hash.ljust(21, "\0")
|
keys = gen_keys(hash.ljust(21, "\0"))
|
||||||
apply_des(chal, keys).join
|
apply_des(chal, keys).join
|
||||||
end
|
end
|
||||||
|
|
||||||
#synonym of ntlm_response for old compatibility with lib/rex/proto/smb/crypt
|
#synonym of ntlm_response for old compatibility with lib/rex/proto/smb/crypt
|
||||||
def self.ntlm_md4(password, challenge)
|
def self.ntlm_md4(password, challenge)
|
||||||
argntlm = { :ntlm_hash => self.ntlm_hash(password),
|
ntlm_response({
|
||||||
:challenge => challenge }
|
:ntlm_hash => self.ntlm_hash(password),
|
||||||
self.ntlm_response(argntlm)
|
:challenge => challenge
|
||||||
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.ntlmv2_response(arg, opt = {})
|
def self.ntlmv2_response(arg, opt = {})
|
||||||
raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
|
raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
|
||||||
begin
|
|
||||||
key = arg[:ntlmv2_hash]
|
key, chal = arg[:ntlmv2_hash], arg[:challenge]
|
||||||
chal = arg[:challenge]
|
if not (key and chal)
|
||||||
rescue
|
|
||||||
raise ArgumentError , 'ntlmv2_hash and challenge are mandatory'
|
raise ArgumentError , 'ntlmv2_hash and challenge are mandatory'
|
||||||
end
|
end
|
||||||
|
|
||||||
chal = BASE::pack_int64le(chal) if chal.is_a?(::Integer)
|
chal = BASE::pack_int64le(chal) if chal.is_a?(::Integer)
|
||||||
|
bb = nil
|
||||||
|
|
||||||
if opt[:nt_client_challenge]
|
if opt[:nt_client_challenge]
|
||||||
unless opt[:nt_client_challenge].is_a?(::String) && opt[:nt_client_challenge].length > 24
|
if opt[:nt_client_challenge].to_s.length > 24
|
||||||
raise ArgumentError,"nt_client_challenge is not in a correct format "
|
raise ArgumentError,"nt_client_challenge is not in a correct format "
|
||||||
end
|
end
|
||||||
bb = opt[:nt_client_challenge]
|
bb = opt[:nt_client_challenge]
|
||||||
else
|
else
|
||||||
begin
|
if not arg[:target_info]
|
||||||
ti = arg[:target_info]
|
|
||||||
rescue
|
|
||||||
raise ArgumentError, "target_info is mandatory in this case"
|
raise ArgumentError, "target_info is mandatory in this case"
|
||||||
end
|
end
|
||||||
if opt[:client_challenge]
|
|
||||||
cc = opt[:client_challenge]
|
|
||||||
else
|
|
||||||
cc = rand(CONST::MAX64)
|
|
||||||
end
|
|
||||||
cc = BASE::pack_int64le(cc) if cc.is_a?(::Integer)
|
|
||||||
|
|
||||||
if opt[:timestamp]
|
ti = arg[:target_info]
|
||||||
ts = opt[:timestamp]
|
cc = opt[:client_challenge] || rand(CONST::MAX64)
|
||||||
else
|
cc = BASE::pack_int64le(cc) if cc.is_a?(::Integer)
|
||||||
ts = Time.now.to_i
|
|
||||||
end
|
ts = opt[:timestamp] || Time.now.to_i
|
||||||
# epoch -> milsec from Jan 1, 1601
|
|
||||||
|
# Convert the unix timestamp to windows format
|
||||||
|
# epoch -> milsec from Jan 1, 1601
|
||||||
ts = 10000000 * (ts + CONST::TIME_OFFSET)
|
ts = 10000000 * (ts + CONST::TIME_OFFSET)
|
||||||
|
|
||||||
blob = BASE::Blob.new
|
blob = BASE::Blob.new
|
||||||
|
@ -196,7 +207,6 @@ begin
|
||||||
OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, key, chal + bb) + bb
|
OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, key, chal + bb) + bb
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
def self.lmv2_response(arg, opt = {})
|
def self.lmv2_response(arg, opt = {})
|
||||||
raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
|
raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
|
||||||
|
@ -204,52 +214,44 @@ begin
|
||||||
chal = arg[:challenge]
|
chal = arg[:challenge]
|
||||||
|
|
||||||
chal = BASE::pack_int64le(chal) if chal.is_a?(::Integer)
|
chal = BASE::pack_int64le(chal) if chal.is_a?(::Integer)
|
||||||
if opt[:client_challenge]
|
cc = opt[:client_challenge] || rand(CONST::MAX64)
|
||||||
cc = opt[:client_challenge]
|
cc = BASE::pack_int64le(cc) if cc.is_a?(::Integer)
|
||||||
else
|
|
||||||
cc = rand(CONST::MAX64)
|
|
||||||
end
|
|
||||||
cc = BASE::pack_int64le(cc) if cc.is_a?(::Integer)
|
|
||||||
|
|
||||||
OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, key, chal + cc) + cc
|
OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, key, chal + cc) + cc
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.ntlm2_session(arg, opt = {})
|
def self.ntlm2_session(arg, opt = {})
|
||||||
raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
|
raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
|
||||||
begin
|
passwd_hash,chal = arg[:ntlm_hash],arg[:challenge]
|
||||||
passwd_hash = arg[:ntlm_hash]
|
if not passwd_hash and chal
|
||||||
chal = arg[:challenge]
|
raise RuntimeError, "ntlm_hash and challenge are required"
|
||||||
rescue
|
|
||||||
raise ArgumentError
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if opt[:client_challenge]
|
cc = opt[:client_challenge] || rand(CONST::MAX64)
|
||||||
cc = opt[:client_challenge]
|
|
||||||
else
|
|
||||||
cc = rand(CONST::MAX64)
|
|
||||||
end
|
|
||||||
cc = BASE::pack_int64le(cc) if cc.is_a?(Integer)
|
cc = BASE::pack_int64le(cc) if cc.is_a?(Integer)
|
||||||
|
|
||||||
keys = gen_keys passwd_hash.ljust(21, "\0")
|
keys = gen_keys(passwd_hash.ljust(21, "\0"))
|
||||||
session_hash = OpenSSL::Digest::MD5.digest(chal + cc).slice(0, 8)
|
session_hash = OpenSSL::Digest::MD5.digest(chal + cc)[0,8]
|
||||||
response = apply_des(session_hash, keys).join
|
response = apply_des(session_hash, keys).join
|
||||||
[cc.ljust(24, "\0"), response]
|
[cc.ljust(24, "\0"), response]
|
||||||
end
|
end
|
||||||
|
|
||||||
#signing method added for metasploit project
|
#
|
||||||
|
# Signing method added for metasploit project
|
||||||
#Used when only the LMv1 response is provided (i.e., with Win9x clients)
|
#
|
||||||
|
|
||||||
|
# Used when only the LMv1 response is provided (i.e., with Win9x clients)
|
||||||
def self.lmv1_user_session_key(pass )
|
def self.lmv1_user_session_key(pass )
|
||||||
self.lm_hash(pass.upcase[0,7],true).ljust(16,"\x00")
|
self.lm_hash(pass.upcase[0,7],true).ljust(16,"\x00")
|
||||||
end
|
end
|
||||||
|
|
||||||
#This variant is used when the client sends the NTLMv1 response
|
# This variant is used when the client sends the NTLMv1 response
|
||||||
def self.ntlmv1_user_session_key(pass )
|
def self.ntlmv1_user_session_key(pass )
|
||||||
raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
|
raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
|
||||||
OpenSSL::Digest::MD4.digest(self.ntlm_hash(pass))
|
OpenSSL::Digest::MD4.digest(self.ntlm_hash(pass))
|
||||||
end
|
end
|
||||||
|
|
||||||
#Used when NTLMv1 authentication is employed with NTLM2 session security
|
# Used when NTLMv1 authentication is employed with NTLM2 session security
|
||||||
def self.ntlm2_session_user_session_key(pass, srv_chall, cli_chall)
|
def self.ntlm2_session_user_session_key(pass, srv_chall, cli_chall)
|
||||||
raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
|
raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
|
||||||
ntlm_key = self.ntlmv1_user_session_key(pass )
|
ntlm_key = self.ntlmv1_user_session_key(pass )
|
||||||
|
@ -257,7 +259,7 @@ begin
|
||||||
OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlm_key, session_chal)
|
OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlm_key, session_chal)
|
||||||
end
|
end
|
||||||
|
|
||||||
#Used when the LMv2 response is sent
|
# Used when the LMv2 response is sent
|
||||||
def self.lmv2_user_session_key(user, pass, domain, srv_chall, cli_chall)
|
def self.lmv2_user_session_key(user, pass, domain, srv_chall, cli_chall)
|
||||||
raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
|
raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
|
||||||
ntlmv2_key = self.ntlmv2_hash(user, pass, domain)
|
ntlmv2_key = self.ntlmv2_hash(user, pass, domain)
|
||||||
|
@ -265,21 +267,21 @@ begin
|
||||||
OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmv2_key, hash1)
|
OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmv2_key, hash1)
|
||||||
end
|
end
|
||||||
|
|
||||||
#Used when the NTLMv2 response is sent
|
# Used when the NTLMv2 response is sent
|
||||||
class << self; alias_method :ntlmv2_user_session_key, :lmv2_user_session_key; end
|
class << self; alias_method :ntlmv2_user_session_key, :lmv2_user_session_key; end
|
||||||
|
|
||||||
#Used when LAnMan Key flag is set
|
# Used when LanMan Key flag is set
|
||||||
def self.lanman_session_key(pass, srvchall)
|
def self.lanman_session_key(pass, srvchall)
|
||||||
halfhash =self.lm_hash(pass.upcase[0,7],true)
|
halfhash = lm_hash(pass.upcase[0,7],true)
|
||||||
arglm = { :lm_hash => halfhash[0,7],
|
plain = self.lm_response({
|
||||||
:challenge => srvchall }
|
:lm_hash => halfhash[0,7],
|
||||||
plain = self.lm_response(arglm,true)
|
:challenge => srvchall
|
||||||
|
}, true )
|
||||||
key = halfhash + ["bdbdbdbdbdbd"].pack("H*")
|
key = halfhash + ["bdbdbdbdbdbd"].pack("H*")
|
||||||
keys = self.gen_keys(key)
|
keys = self.gen_keys(key)
|
||||||
self.apply_des(plain, keys).join
|
apply_des(plain, keys).join
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
def self.encrypt_sessionkey(session_key, user_session_key)
|
def self.encrypt_sessionkey(session_key, user_session_key)
|
||||||
raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
|
raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
|
||||||
cipher = OpenSSL::Cipher::Cipher.new('rc4')
|
cipher = OpenSSL::Cipher::Cipher.new('rc4')
|
||||||
|
@ -315,9 +317,6 @@ begin
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
rescue LoadError
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -642,9 +642,12 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils
|
||||||
# Authenticate without NTLMSSP
|
# Authenticate without NTLMSSP
|
||||||
def session_setup_no_ntlmssp(user = '', pass = '', domain = '', do_recv = true)
|
def session_setup_no_ntlmssp(user = '', pass = '', domain = '', do_recv = true)
|
||||||
|
|
||||||
|
# Requires a challenge key to have been seen during negotiation
|
||||||
raise XCEPT::NTLM1MissingChallenge if not self.challenge_key
|
raise XCEPT::NTLM1MissingChallenge if not self.challenge_key
|
||||||
#we can not yet handle signing in this situation
|
|
||||||
|
# We can not yet handle signing in this situation
|
||||||
raise XCEPT::NTLM2MissingChallenge if self.require_signing
|
raise XCEPT::NTLM2MissingChallenge if self.require_signing
|
||||||
|
|
||||||
if UTILS.is_pass_ntlm_hash?(pass)
|
if UTILS.is_pass_ntlm_hash?(pass)
|
||||||
arglm = {
|
arglm = {
|
||||||
:lm_hash => [ pass.upcase()[0,32] ].pack('H32'),
|
:lm_hash => [ pass.upcase()[0,32] ].pack('H32'),
|
||||||
|
|
|
@ -12,11 +12,10 @@ class Crypt
|
||||||
@@loaded_openssl = true
|
@@loaded_openssl = true
|
||||||
rescue ::Exception
|
rescue ::Exception
|
||||||
end
|
end
|
||||||
|
|
||||||
begin
|
|
||||||
|
|
||||||
#return a smb packet signed
|
# Return a signed SMB packet
|
||||||
def self.sign_smb_packet(mackey, sequence_counter, data)
|
def self.sign_smb_packet(mackey, sequence_counter, data)
|
||||||
|
raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
|
||||||
seq = Rex::Text::pack_int64le(sequence_counter)
|
seq = Rex::Text::pack_int64le(sequence_counter)
|
||||||
netbios_hdr = data.slice!(0,4)
|
netbios_hdr = data.slice!(0,4)
|
||||||
data[14,8] = seq
|
data[14,8] = seq
|
||||||
|
@ -31,11 +30,6 @@ begin
|
||||||
return signature1 == signature2
|
return signature1 == signature2
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
rescue LoadError
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue