Skip to content

Commit

Permalink
Land #272, Bump RubySMB to require rubyntlm>=0.6.5 and remove old wor…
Browse files Browse the repository at this point in the history
…karounds
  • Loading branch information
cdelafuente-r7 committed Oct 10, 2024
2 parents 649182e + 87ca44c commit 6a31fbe
Show file tree
Hide file tree
Showing 14 changed files with 147 additions and 178 deletions.
2 changes: 0 additions & 2 deletions lib/ruby_smb.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,11 @@
require 'openssl/cmac'
require 'windows_error'
require 'windows_error/nt_status'
require 'ruby_smb/ntlm/custom/string_encoder'
# A packet parsing and manipulation library for the SMB1 and SMB2 protocols
#
# [[MS-SMB] Server Message Block (SMB) Protocol Version 1](https://msdn.microsoft.com/en-us/library/cc246482.aspx)
# [[MS-SMB2] Server Message Block (SMB) Protocol Versions 2 and 3](https://msdn.microsoft.com/en-us/library/cc246482.aspx)
module RubySMB
require 'ruby_smb/utils'
require 'ruby_smb/error'
require 'ruby_smb/create_actions'
require 'ruby_smb/dispositions'
Expand Down
10 changes: 5 additions & 5 deletions lib/ruby_smb/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ module RubySMB
class Client
require 'ruby_smb/ntlm'
require 'ruby_smb/signing'
require 'ruby_smb/utils'
require 'ruby_smb/client/negotiation'
require 'ruby_smb/client/authentication'
require 'ruby_smb/client/tree_connect'
Expand Down Expand Up @@ -320,11 +319,12 @@ def initialize(dispatcher, smb1: true, smb2: true, smb3: true, username:, passwo
if smb1 == false && smb2 == false && smb3 == false
raise ArgumentError, 'You must enable at least one Protocol'
end

@dispatcher = dispatcher
@pid = rand(0xFFFF)
@domain = domain
@local_workstation = local_workstation
@password = RubySMB::Utils.safe_encode((password||''), 'utf-8')
@password = (password || '')
@sequence_counter = 0
@session_id = 0x00
@session_key = ''
Expand All @@ -334,7 +334,7 @@ def initialize(dispatcher, smb1: true, smb2: true, smb3: true, username:, passwo
@smb1 = smb1
@smb2 = smb2
@smb3 = smb3
@username = RubySMB::Utils.safe_encode((username||''), 'utf-8')
@username = (username || '')
@max_buffer_size = MAX_BUFFER_SIZE
# These sizes will be modified during negotiation
@server_max_buffer_size = SERVER_MAX_BUFFER_SIZE
Expand Down Expand Up @@ -417,8 +417,8 @@ def session_setup(user, pass, domain, do_recv=true,
local_workstation: self.local_workstation, ntlm_flags: NTLM::DEFAULT_CLIENT_FLAGS)
@domain = domain
@local_workstation = local_workstation
@password = RubySMB::Utils.safe_encode((pass||''), 'utf-8')
@username = RubySMB::Utils.safe_encode((user||''), 'utf-8')
@password = (pass || '')
@username = (user || '')

@ntlm_client = RubySMB::NTLM::Client.new(
@username,
Expand Down
6 changes: 2 additions & 4 deletions lib/ruby_smb/dcerpc/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,10 @@ class Client
require 'ruby_smb/dcerpc'
require 'ruby_smb/gss'
require 'ruby_smb/peer_info'
require 'ruby_smb/utils'

include Dcerpc
include Epm
include PeerInfo
include Utils

# The default maximum size of a RPC message that the Client accepts (in bytes)
MAX_BUFFER_SIZE = 64512
Expand Down Expand Up @@ -120,8 +118,8 @@ def initialize(host,
@read_timeout = read_timeout
@domain = domain
@local_workstation = local_workstation
@username = RubySMB::Utils.safe_encode(username, 'utf-8')
@password = RubySMB::Utils.safe_encode(password, 'utf-8')
@username = username
@password = password
@max_buffer_size = MAX_BUFFER_SIZE
@call_id = 1
@ctx_id = 0
Expand Down
4 changes: 2 additions & 2 deletions lib/ruby_smb/dcerpc/icpr.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def buffer
def cert_server_request(attributes:, authority:, csr:)
cert_server_request_request = CertServerRequestRequest.new(
pwsz_authority: authority,
pctb_attribs: { pb: (RubySMB::Utils.safe_encode(attributes.map { |k,v| "#{k}:#{v}" }.join("\n"), 'UTF-16le').force_encoding('ASCII-8bit') + "\x00\x00".b) },
pctb_attribs: { pb: (attributes.map { |k,v| "#{k}:#{v}" }.join("\n").encode('UTF-16LE').force_encoding('ASCII-8BIT') + "\x00\x00".b) },
pctb_request: { pb: csr.to_der }
)

Expand All @@ -53,7 +53,7 @@ def cert_server_request(attributes:, authority:, csr:)
ret = {
certificate: nil,
disposition: cert_server_request_response.pdw_disposition.value,
disposition_message: cert_server_request_response.pctb_disposition_message.buffer.chomp("\x00\x00").force_encoding('utf-16le').encode,
disposition_message: cert_server_request_response.pctb_disposition_message.buffer.chomp("\x00\x00").force_encoding('UTF-16LE').encode,
status: {
CR_DISP_ISSUED => :issued,
CR_DISP_UNDER_SUBMISSION => :submitted,
Expand Down
2 changes: 1 addition & 1 deletion lib/ruby_smb/dcerpc/samr.rb
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ class SamprEncryptedUserPasswordNew < BinData::Record

def self.encrypt_password(password, key)
# see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/5fe3c4c4-e71b-440d-b2fd-8448bfaf6e04
password = RubySMB::Utils.safe_encode(password, 'UTF-16LE').force_encoding('ASCII-8bit')
password = password.encode('UTF-16LE').force_encoding('ASCII-8BIT')
buffer = password.rjust(512, "\x00") + [ password.length ].pack('V')
salt = SecureRandom.random_bytes(16)
key = OpenSSL::Digest::MD5.new(salt + key).digest
Expand Down
10 changes: 3 additions & 7 deletions lib/ruby_smb/gss/provider/ntlm.rb
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,7 @@ def process_ntlm_type3(type3_msg)
case type3_msg.ntlm_version
when :ntlmv1
my_ntlm_response = Net::NTLM::ntlm_response(
ntlm_hash: Net::NTLM::ntlm_hash(
RubySMB::Utils.safe_encode(account.password, 'UTF-16LE'),
unicode: true
),
ntlm_hash: Net::NTLM::ntlm_hash(account.password),
challenge: @server_challenge
)
matches = my_ntlm_response == type3_msg.ntlm_response
Expand All @@ -157,7 +154,7 @@ def process_ntlm_type3(type3_msg)
ntlmv2_hash = Net::NTLM.ntlmv2_hash(
Net::NTLM::EncodeUtil.encode_utf16le(account.username),
Net::NTLM::EncodeUtil.encode_utf16le(account.password),
type3_msg.domain.force_encoding('ASCII-8BIT'), # don't use the account domain because of the special '.' value
type3_msg.domain.dup.force_encoding('ASCII-8BIT'), # don't use the account domain because of the special '.' value
{client_challenge: their_blob[16...24], unicode: true}
)

Expand Down Expand Up @@ -310,8 +307,7 @@ def get_account(username, domain: nil)
domain = @default_domain if domain.nil? || domain == '.'.encode(domain.encoding)
domain = domain.downcase
@accounts.find do |account|
RubySMB::Utils.safe_encode(account.username, username.encoding).downcase == username &&
RubySMB::Utils.safe_encode(account.domain, domain.encoding).downcase == domain
account.username.encode(username.encoding).downcase == username && account.domain.encode(domain.encoding).downcase == domain
end
end

Expand Down
37 changes: 0 additions & 37 deletions lib/ruby_smb/ntlm.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
require 'ruby_smb/ntlm/custom/string_encoder'

module RubySMB
module NTLM
# [[MS-NLMP] 2.2.2.5](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/99d90ff4-957f-4c8a-80e4-5bfe5a9a9832)
Expand Down Expand Up @@ -58,41 +56,6 @@ def to_s
"Version #{major}.#{minor} (Build #{build}); NTLM Current Revision #{ntlm_revision}"
end
end

class << self

# Generate a NTLMv2 Hash
# @param [String] user The username
# @param [String] password The password
# @param [String] target The domain or workstation to authenticate to
# @option opt :unicode (false) Unicode encode the domain
def ntlmv2_hash(user, password, target, opt={})
if Net::NTLM.is_ntlm_hash? password
decoded_password = Net::NTLM::EncodeUtil.decode_utf16le(password)
ntlmhash = [decoded_password.upcase[33,65]].pack('H32')
else
ntlmhash = Net::NTLM.ntlm_hash(password, opt)
end

if opt[:unicode]
# Uppercase operation on username containing non-ASCII characters
# after being unicode encoded with `EncodeUtil.encode_utf16le`
# doesn't play well. Upcase should be done before encoding.
user_upcase = Net::NTLM::EncodeUtil.decode_utf16le(user).upcase
user_upcase = Net::NTLM::EncodeUtil.encode_utf16le(user_upcase)
else
user_upcase = user.upcase
end
userdomain = user_upcase + target

unless opt[:unicode]
userdomain = Net::NTLM::EncodeUtil.encode_utf16le(userdomain)
end
OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmhash, userdomain)
end

end

end
end

Expand Down
77 changes: 3 additions & 74 deletions lib/ruby_smb/ntlm/client.rb
Original file line number Diff line number Diff line change
@@ -1,78 +1,7 @@
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
# There was a bunch of code in here that was necessary in versions up to and including rubyntlm version 0.6.3.
# The class is kept because there are references to it that should be kept in place in case future alterations to
# rubyntlm are required.
end
end
22 changes: 0 additions & 22 deletions lib/ruby_smb/ntlm/custom/string_encoder.rb

This file was deleted.

4 changes: 2 additions & 2 deletions lib/ruby_smb/server/server_client/tree_connect.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ def do_tree_connect_smb1(request, session)
# see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/b062f3e3-1b65-4a9a-854a-0ee432499d8f
response = RubySMB::SMB1::Packet::TreeConnectResponse.new

share_name = RubySMB::Utils.safe_encode(request.data_block.path, 'UTF-8').split('\\', 4).last
share_name = request.data_block.path.split('\\', 4).last
share_provider = @server.shares.transform_keys(&:downcase)[share_name.downcase]
if share_provider.nil?
logger.warn("Received TREE_CONNECT request for non-existent share: #{share_name}")
Expand Down Expand Up @@ -51,7 +51,7 @@ def do_tree_connect_smb2(request, session)
return response
end

share_name = RubySMB::Utils.safe_encode(request.path, 'UTF-8').split('\\', 4).last
share_name = request.path.encode.split('\\', 4).last
share_provider = @server.shares.transform_keys(&:downcase)[share_name.downcase]

if share_provider.nil?
Expand Down
15 changes: 0 additions & 15 deletions lib/ruby_smb/utils.rb

This file was deleted.

11 changes: 9 additions & 2 deletions ruby_smb.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@ require 'ruby_smb/version'
Gem::Specification.new do |spec|
spec.name = 'ruby_smb'
spec.version = RubySMB::VERSION
spec.authors = ['Metasploit Hackers', 'David Maloney', 'James Lee', 'Dev Mohanty', 'Christophe De La Fuente']
spec.authors = [
'Metasploit Hackers',
'David Maloney',
'James Lee',
'Dev Mohanty',
'Christophe De La Fuente',
'Spencer McIntyre'
]
spec.email = ['msfdev@metasploit.com']
spec.summary = 'A pure Ruby implementation of the SMB Protocol Family'
spec.description = ''
Expand All @@ -33,7 +40,7 @@ Gem::Specification.new do |spec|
spec.add_development_dependency 'rake'
spec.add_development_dependency 'yard'

spec.add_runtime_dependency 'rubyntlm'
spec.add_runtime_dependency 'rubyntlm', '>= 0.6.5'
spec.add_runtime_dependency 'windows_error', '>= 0.1.4'
spec.add_runtime_dependency 'bindata', '2.4.15'
spec.add_runtime_dependency 'openssl-ccm'
Expand Down
Loading

0 comments on commit 6a31fbe

Please sign in to comment.