lib/ruby_smb/ntlm/client.rb
module RubySMB::NTLM
module Message
def deflag
security_buffers.inject(head_size) do |cur, a|
a[1].offset = cur
cur += a[1].data_size
has_flag?(:UNICODE) ? cur + cur % 2 : cur
end
end
def serialize
deflag
@alist.map { |n, f| f.serialize }.join + security_buffers.map { |n, f| f.value + (has_flag?(:UNICODE) ? "\x00".b * (f.value.length % 2) : '') }.join
end
end
class Client < Net::NTLM::Client
class Session < Net::NTLM::Client::Session
def authenticate!
calculate_user_session_key!
type3_opts = {
:lm_response => is_anonymous? ? "\x00".b : lmv2_resp,
:ntlm_response => is_anonymous? ? '' : ntlmv2_resp,
:domain => domain,
:user => username,
:workstation => workstation,
:flag => (challenge_message.flag & client.flags)
}
t3 = Net::NTLM::Message::Type3.create type3_opts
t3.extend(Message)
if negotiate_key_exchange?
t3.enable(:session_key)
rc4 = OpenSSL::Cipher.new("rc4")
rc4.encrypt
rc4.key = user_session_key
sk = rc4.update exported_session_key
sk << rc4.final
t3.session_key = sk
end
t3
end
def is_anonymous?
username == '' && password == ''
end
private
def use_oem_strings?
# @see https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/99d90ff4-957f-4c8a-80e4-5bfe5a9a9832
!challenge_message.has_flag?(:UNICODE) && challenge_message.has_flag?(:OEM)
end
def ntlmv2_hash
@ntlmv2_hash ||= RubySMB::NTLM.ntlmv2_hash(username, password, domain, {:client_challenge => client_challenge, :unicode => !use_oem_strings?})
end
def calculate_user_session_key!
if is_anonymous?
# see MS-NLMP section 3.4
@user_session_key = "\x00".b * 16
else
@user_session_key = OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmv2_hash, nt_proof_str)
end
end
end
def init_context(resp = nil, channel_binding = nil)
if resp.nil?
@session = nil
type1_message
else
@session = Client::Session.new(self, Net::NTLM::Message.decode64(resp), channel_binding)
@session.authenticate!
end
end
end
end