ruby_smb 2.0.8 → 2.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.github/workflows/verify.yml +5 -15
- data/examples/auth_capture.rb +71 -0
- data/lib/ruby_smb/client/negotiation.rb +9 -11
- data/lib/ruby_smb/client.rb +30 -25
- data/lib/ruby_smb/dialect.rb +45 -0
- data/lib/ruby_smb/dispatcher/base.rb +1 -1
- data/lib/ruby_smb/gss/provider/authenticator.rb +42 -0
- data/lib/ruby_smb/gss/provider/ntlm.rb +303 -0
- data/lib/ruby_smb/gss/provider.rb +35 -0
- data/lib/ruby_smb/gss.rb +56 -63
- data/lib/ruby_smb/ntlm.rb +45 -0
- data/lib/ruby_smb/server/server_client/negotiation.rb +156 -0
- data/lib/ruby_smb/server/server_client/session_setup.rb +82 -0
- data/lib/ruby_smb/server/server_client.rb +162 -0
- data/lib/ruby_smb/server.rb +54 -0
- data/lib/ruby_smb/signing.rb +59 -0
- data/lib/ruby_smb/smb1/packet/negotiate_response.rb +11 -11
- data/lib/ruby_smb/smb1/packet/negotiate_response_extended.rb +1 -1
- data/lib/ruby_smb/smb1/packet/session_setup_request.rb +1 -1
- data/lib/ruby_smb/smb1/tree.rb +1 -1
- data/lib/ruby_smb/smb2/negotiate_context.rb +18 -2
- data/lib/ruby_smb/smb2/packet/negotiate_request.rb +9 -0
- data/lib/ruby_smb/smb2/packet/negotiate_response.rb +0 -1
- data/lib/ruby_smb/smb2/packet/session_setup_response.rb +2 -2
- data/lib/ruby_smb/smb2/packet/tree_connect_request.rb +1 -1
- data/lib/ruby_smb/smb2/tree.rb +1 -1
- data/lib/ruby_smb/smb2.rb +3 -1
- data/lib/ruby_smb/version.rb +1 -1
- data/lib/ruby_smb.rb +2 -1
- data/spec/lib/ruby_smb/client_spec.rb +24 -16
- data/spec/lib/ruby_smb/gss/provider/ntlm/account_spec.rb +32 -0
- data/spec/lib/ruby_smb/gss/provider/ntlm/authenticator_spec.rb +101 -0
- data/spec/lib/ruby_smb/gss/provider/ntlm/os_version_spec.rb +32 -0
- data/spec/lib/ruby_smb/gss/provider/ntlm_spec.rb +113 -0
- data/spec/lib/ruby_smb/server/server_client_spec.rb +156 -0
- data/spec/lib/ruby_smb/server_spec.rb +32 -0
- data/spec/lib/ruby_smb/smb1/tree_spec.rb +4 -4
- data/spec/lib/ruby_smb/smb2/negotiate_context_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb2/tree_spec.rb +5 -5
- data.tar.gz.sig +0 -0
- metadata +25 -3
- metadata.gz.sig +0 -0
- data/lib/ruby_smb/client/signing.rb +0 -64
@@ -0,0 +1,82 @@
|
|
1
|
+
module RubySMB
|
2
|
+
class Server
|
3
|
+
class ServerClient
|
4
|
+
module SessionSetup
|
5
|
+
#
|
6
|
+
# Setup a new session based on the negotiated dialect. Once session setup is complete, the state will be updated
|
7
|
+
# to :authenticated.
|
8
|
+
#
|
9
|
+
# @param [String] raw_request the session setup request to process
|
10
|
+
def handle_session_setup(raw_request)
|
11
|
+
response = nil
|
12
|
+
|
13
|
+
case metadialect.order
|
14
|
+
when Dialect::ORDER_SMB1
|
15
|
+
request = SMB1::Packet::SessionSetupRequest.read(raw_request)
|
16
|
+
response = do_session_setup_smb1(request)
|
17
|
+
when Dialect::ORDER_SMB2
|
18
|
+
request = SMB2::Packet::SessionSetupRequest.read(raw_request)
|
19
|
+
response = do_session_setup_smb2(request)
|
20
|
+
end
|
21
|
+
|
22
|
+
if response.nil?
|
23
|
+
disconnect!
|
24
|
+
else
|
25
|
+
send_packet(response)
|
26
|
+
end
|
27
|
+
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def do_session_setup_smb1(request)
|
32
|
+
gss_result = process_gss(request.data_block.security_blob)
|
33
|
+
return if gss_result.nil?
|
34
|
+
|
35
|
+
response = SMB1::Packet::SessionSetupResponse.new
|
36
|
+
response.smb_header.pid_low = request.smb_header.pid_low
|
37
|
+
response.smb_header.uid = rand(0x10000)
|
38
|
+
response.smb_header.mid = request.smb_header.mid
|
39
|
+
response.smb_header.nt_status = gss_result.nt_status.value
|
40
|
+
response.smb_header.flags.reply = true
|
41
|
+
response.smb_header.flags2.unicode = true
|
42
|
+
response.smb_header.flags2.extended_security = true
|
43
|
+
unless gss_result.buffer.nil?
|
44
|
+
response.parameter_block.security_blob_length = gss_result.buffer.length
|
45
|
+
response.data_block.security_blob = gss_result.buffer
|
46
|
+
end
|
47
|
+
|
48
|
+
if gss_result.nt_status == WindowsError::NTStatus::STATUS_SUCCESS
|
49
|
+
@state = :authenticated
|
50
|
+
@identity = gss_result.identity
|
51
|
+
end
|
52
|
+
|
53
|
+
response
|
54
|
+
end
|
55
|
+
|
56
|
+
def do_session_setup_smb2(request)
|
57
|
+
gss_result = process_gss(request.buffer)
|
58
|
+
return if gss_result.nil?
|
59
|
+
|
60
|
+
response = SMB2::Packet::SessionSetupResponse.new
|
61
|
+
response.smb2_header.nt_status = gss_result.nt_status.value
|
62
|
+
response.smb2_header.credits = 1
|
63
|
+
response.smb2_header.message_id = request.smb2_header.message_id
|
64
|
+
response.smb2_header.session_id = @session_id = @session_id || SecureRandom.random_bytes(4).unpack1('V')
|
65
|
+
response.buffer = gss_result.buffer
|
66
|
+
|
67
|
+
update_preauth_hash(request) if @dialect == '0x0311'
|
68
|
+
if gss_result.nt_status == WindowsError::NTStatus::STATUS_SUCCESS
|
69
|
+
@state = :authenticated
|
70
|
+
@identity = gss_result.identity
|
71
|
+
@session_key = @gss_authenticator.session_key
|
72
|
+
elsif gss_result.nt_status == WindowsError::NTStatus::STATUS_MORE_PROCESSING_REQUIRED && @dialect == '0x0311'
|
73
|
+
update_preauth_hash(response)
|
74
|
+
end
|
75
|
+
|
76
|
+
response
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
@@ -0,0 +1,162 @@
|
|
1
|
+
module RubySMB
|
2
|
+
class Server
|
3
|
+
# This class represents a single connected client to the server. It stores and processes connection specific related
|
4
|
+
# information.
|
5
|
+
class ServerClient
|
6
|
+
|
7
|
+
require 'ruby_smb/dialect'
|
8
|
+
require 'ruby_smb/signing'
|
9
|
+
require 'ruby_smb/server/server_client/negotiation'
|
10
|
+
require 'ruby_smb/server/server_client/session_setup'
|
11
|
+
|
12
|
+
include RubySMB::Signing
|
13
|
+
include RubySMB::Server::ServerClient::Negotiation
|
14
|
+
include RubySMB::Server::ServerClient::SessionSetup
|
15
|
+
|
16
|
+
attr_reader :dialect, :identity, :state, :session_key
|
17
|
+
|
18
|
+
# @param [Server] server the server that accepted this connection
|
19
|
+
# @param [Dispatcher::Socket] dispatcher the connection's socket dispatcher
|
20
|
+
def initialize(server, dispatcher)
|
21
|
+
@server = server
|
22
|
+
@dispatcher = dispatcher
|
23
|
+
@state = :negotiate
|
24
|
+
@dialect = nil
|
25
|
+
@session_id = nil
|
26
|
+
@session_key = nil
|
27
|
+
@gss_authenticator = server.gss_provider.new_authenticator(self)
|
28
|
+
@identity = nil
|
29
|
+
@tree_connections = {}
|
30
|
+
@preauth_integrity_hash_algorithm = nil
|
31
|
+
@preauth_integrity_hash_value = nil
|
32
|
+
end
|
33
|
+
|
34
|
+
#
|
35
|
+
# The dialects metadata definition.
|
36
|
+
#
|
37
|
+
# @return [Dialect::Definition]
|
38
|
+
def metadialect
|
39
|
+
Dialect::ALL[@dialect]
|
40
|
+
end
|
41
|
+
|
42
|
+
#
|
43
|
+
# The peername of the connected socket. This is a combination of the IPv4 or IPv6 address and port number.
|
44
|
+
#
|
45
|
+
# @example Parse the value into an IP address
|
46
|
+
# ::Socket::unpack_sockaddr_in(server_client.getpeername)
|
47
|
+
#
|
48
|
+
# @return [String]
|
49
|
+
def getpeername
|
50
|
+
@dispatcher.tcp_socket.getpeername
|
51
|
+
end
|
52
|
+
|
53
|
+
#
|
54
|
+
# Handle an authenticated request. This is the main handler for all requests after the connection has been
|
55
|
+
# authenticated.
|
56
|
+
#
|
57
|
+
# @param [String] raw_request the request that should be handled
|
58
|
+
def handle_authenticated(raw_request)
|
59
|
+
response = nil
|
60
|
+
|
61
|
+
case raw_request[0...4].unpack1('L>')
|
62
|
+
when RubySMB::SMB1::SMB_PROTOCOL_ID
|
63
|
+
raise NotImplementedError
|
64
|
+
when RubySMB::SMB2::SMB2_PROTOCOL_ID
|
65
|
+
raise NotImplementedError
|
66
|
+
end
|
67
|
+
|
68
|
+
if response.nil?
|
69
|
+
disconnect!
|
70
|
+
return
|
71
|
+
end
|
72
|
+
|
73
|
+
send_packet(response)
|
74
|
+
end
|
75
|
+
|
76
|
+
#
|
77
|
+
# Process a GSS authentication buffer. If no buffer is specified, the request is assumed to be the first in the
|
78
|
+
# negotiation sequence.
|
79
|
+
#
|
80
|
+
# @param [String, nil] buffer the request GSS request buffer that should be processed
|
81
|
+
# @return [Gss::Provider::Result] the result of the processed GSS request
|
82
|
+
def process_gss(buffer=nil)
|
83
|
+
@gss_authenticator.process(buffer)
|
84
|
+
end
|
85
|
+
|
86
|
+
#
|
87
|
+
# Run the processing loop to receive and handle requests. This loop runs until an exception occurs or the
|
88
|
+
# dispatcher socket is closed.
|
89
|
+
#
|
90
|
+
def run
|
91
|
+
loop do
|
92
|
+
begin
|
93
|
+
raw_request = recv_packet
|
94
|
+
rescue RubySMB::Error::CommunicationError
|
95
|
+
break
|
96
|
+
end
|
97
|
+
|
98
|
+
case @state
|
99
|
+
when :negotiate
|
100
|
+
handle_negotiate(raw_request)
|
101
|
+
when :session_setup
|
102
|
+
handle_session_setup(raw_request)
|
103
|
+
when :authenticated
|
104
|
+
handle_authenticated(raw_request)
|
105
|
+
end
|
106
|
+
|
107
|
+
break if @dispatcher.tcp_socket.closed?
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
#
|
112
|
+
# Disconnect the remote client.
|
113
|
+
#
|
114
|
+
def disconnect!
|
115
|
+
@state = nil
|
116
|
+
@dispatcher.tcp_socket.close
|
117
|
+
end
|
118
|
+
|
119
|
+
#
|
120
|
+
# Receive a single SMB packet from the dispatcher.
|
121
|
+
#
|
122
|
+
# @return [String] the raw packet
|
123
|
+
def recv_packet
|
124
|
+
@dispatcher.recv_packet
|
125
|
+
end
|
126
|
+
|
127
|
+
#
|
128
|
+
# Send a single SMB packet using the dispatcher. If necessary, the packet will be signed.
|
129
|
+
#
|
130
|
+
# @param [GenericPacket] packet the packet to send
|
131
|
+
def send_packet(packet)
|
132
|
+
if @state == :authenticated && @identity != Gss::Provider::IDENTITY_ANONYMOUS && !@session_key.nil?
|
133
|
+
case metadialect.family
|
134
|
+
when Dialect::FAMILY_SMB2
|
135
|
+
packet = smb2_sign(packet)
|
136
|
+
when Dialect::FAMILY_SMB3
|
137
|
+
packet = smb3_sign(packet)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
@dispatcher.send_packet(packet)
|
142
|
+
end
|
143
|
+
|
144
|
+
#
|
145
|
+
# Update the preauth integrity hash as used by dialect 3.1.1 for various cryptographic operations. The algorithm
|
146
|
+
# and hash values must have been initialized prior to calling this.
|
147
|
+
#
|
148
|
+
# @param [String] data the data with which to update the preauth integrity hash
|
149
|
+
def update_preauth_hash(data)
|
150
|
+
unless @preauth_integrity_hash_algorithm
|
151
|
+
raise RubySMB::Error::EncryptionError.new(
|
152
|
+
'Cannot compute the Preauth Integrity Hash value: Preauth Integrity Hash Algorithm is nil'
|
153
|
+
)
|
154
|
+
end
|
155
|
+
@preauth_integrity_hash_value = OpenSSL::Digest.digest(
|
156
|
+
@preauth_integrity_hash_algorithm,
|
157
|
+
@preauth_integrity_hash_value + data.to_binary_s
|
158
|
+
)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
module RubySMB
|
4
|
+
# This class provides the SMB server core. Settings that are relevant server wide are managed by this object.
|
5
|
+
# Currently, the server only supports negotiating and authenticating requests. No other server functionality is
|
6
|
+
# available at this time. The negotiating and authentication is supported for SMB versions 1 through 3.1.1.
|
7
|
+
class Server
|
8
|
+
require 'ruby_smb/server/server_client'
|
9
|
+
require 'ruby_smb/gss/provider/ntlm'
|
10
|
+
|
11
|
+
Connection = Struct.new(:client, :thread)
|
12
|
+
|
13
|
+
# @param server_sock the socket on which the server should listen
|
14
|
+
# @param [Gss::Provider] the authentication provider
|
15
|
+
def initialize(server_sock: nil, gss_provider: nil)
|
16
|
+
server_sock = ::TCPServer.new(445) if server_sock.nil?
|
17
|
+
|
18
|
+
@guid = Random.new.bytes(16)
|
19
|
+
@socket = server_sock
|
20
|
+
@connections = []
|
21
|
+
@gss_provider = gss_provider || Gss::Provider::NTLM.new
|
22
|
+
# reject the wildcard dialect because it's not a real dialect we can use for this purpose
|
23
|
+
@dialects = RubySMB::Dialect::ALL.keys.reject { |dialect| dialect == "0x%04x" % RubySMB::SMB2::SMB2_WILDCARD_REVISION }.reverse
|
24
|
+
end
|
25
|
+
|
26
|
+
# Run the server and accept any connections. For each connection, the block will be executed if specified. When the
|
27
|
+
# block returns false, the loop will exit and the server will no long accept new connections.
|
28
|
+
def run(&block)
|
29
|
+
loop do
|
30
|
+
sock = @socket.accept
|
31
|
+
server_client = ServerClient.new(self, RubySMB::Dispatcher::Socket.new(sock))
|
32
|
+
@connections << Connection.new(server_client, Thread.new { server_client.run })
|
33
|
+
|
34
|
+
break unless block.nil? || block.call(server_client)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# The dialects that this server will negotiate with clients, in ascending order of preference.
|
39
|
+
# @!attribute [r] dialects
|
40
|
+
# @return [Array<String>]
|
41
|
+
attr_accessor :dialects
|
42
|
+
|
43
|
+
# The GSS Provider instance that this server will use to authenticate
|
44
|
+
# incoming client connections.
|
45
|
+
# @!attribute [r] gss_provider
|
46
|
+
# @return [RubySMB::Gss::Provider::Base]
|
47
|
+
attr_reader :gss_provider
|
48
|
+
|
49
|
+
# The 16 byte GUID that uniquely identifies this server instance.
|
50
|
+
# @!attribute [r] guid
|
51
|
+
attr_reader :guid
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module RubySMB
|
2
|
+
# Contains the methods for handling packet signing
|
3
|
+
module Signing
|
4
|
+
# The NTLM Session Key used for signing
|
5
|
+
# @!attribute [rw] session_key
|
6
|
+
# @return [String]
|
7
|
+
attr_accessor :session_key
|
8
|
+
|
9
|
+
# Take an SMB1 packet and sign it.
|
10
|
+
#
|
11
|
+
# @param packet [RubySMB::GenericPacket] the packet to sign
|
12
|
+
# @return [RubySMB::GenericPacket] the signed packet
|
13
|
+
def smb1_sign(packet)
|
14
|
+
# Pack the Sequence counter into a int64le
|
15
|
+
packed_sequence_counter = [sequence_counter].pack('Q<')
|
16
|
+
packet.smb_header.security_features = packed_sequence_counter
|
17
|
+
signature = OpenSSL::Digest::MD5.digest(session_key + packet.to_binary_s)[0, 8]
|
18
|
+
packet.smb_header.security_features = signature
|
19
|
+
@sequence_counter += 1
|
20
|
+
|
21
|
+
packet
|
22
|
+
end
|
23
|
+
|
24
|
+
# Take an SMB2 packet and sign it.
|
25
|
+
#
|
26
|
+
# @param packet [RubySMB::GenericPacket] the packet to sign
|
27
|
+
# @return [RubySMB::GenericPacket] the signed packet
|
28
|
+
def smb2_sign(packet)
|
29
|
+
packet.smb2_header.flags.signed = 1
|
30
|
+
packet.smb2_header.signature = "\x00" * 16
|
31
|
+
hmac = OpenSSL::HMAC.digest(OpenSSL::Digest.new('SHA256'), session_key, packet.to_binary_s)
|
32
|
+
packet.smb2_header.signature = hmac[0, 16]
|
33
|
+
|
34
|
+
packet
|
35
|
+
end
|
36
|
+
|
37
|
+
# Take an SMB3 packet and sign it.
|
38
|
+
#
|
39
|
+
# @param packet [RubySMB::GenericPacket] the packet to sign
|
40
|
+
# @return [RubySMB::GenericPacket] the signed packet
|
41
|
+
def smb3_sign(packet)
|
42
|
+
case @dialect
|
43
|
+
when '0x0300', '0x0302'
|
44
|
+
signing_key = Crypto::KDF.counter_mode(@session_key, "SMB2AESCMAC\x00", "SmbSign\x00")
|
45
|
+
when '0x0311'
|
46
|
+
signing_key = Crypto::KDF.counter_mode(@session_key, "SMBSigningKey\x00", @preauth_integrity_hash_value)
|
47
|
+
else
|
48
|
+
raise Error::SigningError.new("Dialect #{@dialect.inspect} is incompatible with SMBv3 signing")
|
49
|
+
end
|
50
|
+
|
51
|
+
packet.smb2_header.flags.signed = 1
|
52
|
+
packet.smb2_header.signature = "\x00" * 16
|
53
|
+
hmac = OpenSSL::CMAC.digest('AES', signing_key, packet.to_binary_s)
|
54
|
+
packet.smb2_header.signature = hmac[0, 16]
|
55
|
+
|
56
|
+
packet
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -8,17 +8,17 @@ module RubySMB
|
|
8
8
|
|
9
9
|
# An SMB_Parameters Block as defined by the {NegotiateResponse}.
|
10
10
|
class ParameterBlock < RubySMB::SMB1::ParameterBlock
|
11
|
-
uint16 :dialect_index,
|
12
|
-
security_mode :security_mode
|
13
|
-
uint16 :max_mpx_count, label: 'Max Multiplex Count'
|
14
|
-
uint16 :max_number_vcs, label: 'Max Virtual Circuits'
|
15
|
-
uint32 :max_buffer_size, label: 'Max Buffer Size'
|
16
|
-
uint32 :max_raw_size, label: 'Max Raw Size'
|
17
|
-
uint32 :session_key, label: 'Session Key'
|
18
|
-
capabilities :capabilities
|
19
|
-
file_time :system_time, label: 'Server System Time'
|
20
|
-
int16 :server_time_zone, label: 'Server TimeZone'
|
21
|
-
uint8 :challenge_length, label: 'Challenge Length', initial_value: 0x08
|
11
|
+
uint16 :dialect_index, label: 'Dialect Index'
|
12
|
+
security_mode :security_mode, onlyif: -> { dialect_index != 0xffff }
|
13
|
+
uint16 :max_mpx_count, label: 'Max Multiplex Count', onlyif: -> { dialect_index != 0xffff }
|
14
|
+
uint16 :max_number_vcs, label: 'Max Virtual Circuits', onlyif: -> { dialect_index != 0xffff }
|
15
|
+
uint32 :max_buffer_size, label: 'Max Buffer Size', onlyif: -> { dialect_index != 0xffff }
|
16
|
+
uint32 :max_raw_size, label: 'Max Raw Size', onlyif: -> { dialect_index != 0xffff }
|
17
|
+
uint32 :session_key, label: 'Session Key', onlyif: -> { dialect_index != 0xffff }
|
18
|
+
capabilities :capabilities, onlyif: -> { dialect_index != 0xffff }
|
19
|
+
file_time :system_time, label: 'Server System Time', onlyif: -> { dialect_index != 0xffff }
|
20
|
+
int16 :server_time_zone, label: 'Server TimeZone', onlyif: -> { dialect_index != 0xffff }
|
21
|
+
uint8 :challenge_length, label: 'Challenge Length', initial_value: 0x08, onlyif: -> { dialect_index != 0xffff }
|
22
22
|
end
|
23
23
|
|
24
24
|
# An SMB_Data Block as defined by the {NegotiateResponse}
|
@@ -8,7 +8,7 @@ module RubySMB
|
|
8
8
|
|
9
9
|
# An SMB_Parameters Block as defined by the {NegotiateResponseExtended}.
|
10
10
|
class ParameterBlock < RubySMB::SMB1::ParameterBlock
|
11
|
-
uint16 :dialect_index,
|
11
|
+
uint16 :dialect_index, label: 'Dialect Index'
|
12
12
|
security_mode :security_mode
|
13
13
|
uint16 :max_mpx_count, label: 'Max Multiplex Count'
|
14
14
|
uint16 :max_number_vcs, label: 'Max Virtual Circuits'
|
@@ -47,7 +47,7 @@ module RubySMB
|
|
47
47
|
|
48
48
|
# Takes an NTLM Type 3 Message and creates the GSS Security Blob
|
49
49
|
# for it and sets it in the {RubySMB::SMB1::Packet::SessionSetupRequest::DataBlock#security_blob}
|
50
|
-
# field. It also
|
50
|
+
# field. It also automatically sets the length in
|
51
51
|
# {RubySMB::SMB1::Packet::SessionSetupRequest::ParameterBlock#security_blob_length}
|
52
52
|
#
|
53
53
|
# @param type3_message [String] the serialized Type 3 NTLM message
|
data/lib/ruby_smb/smb1/tree.rb
CHANGED
@@ -69,9 +69,22 @@ module RubySMB
|
|
69
69
|
class NetnameNegotiateContextId < BinData::Record
|
70
70
|
endian :little
|
71
71
|
|
72
|
-
|
72
|
+
count_bytes_remaining :bytes_remaining
|
73
|
+
default_parameter data_length: nil
|
74
|
+
hide :bytes_remaining
|
75
|
+
|
76
|
+
string16 :net_name, label: 'Net Name', read_length: -> { data_length.nil? ? bytes_remaining : data_length }
|
73
77
|
end
|
74
78
|
|
79
|
+
# An SMB2 TRANSPORT_CAPABILITIES context struct as defined in
|
80
|
+
# [2.2.3.1.5 SMB2_TRANSPORT_CAPABILITIES](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/450a1888-a645-4988-8638-5a11f4617545)
|
81
|
+
class TransportCapabilities < BinData::Record
|
82
|
+
SMB2_ACCEPT_TRANSPORT_LEVEL_SECURITY = 1 # Transport security is offered to skip SMB2 encryption on this connection.
|
83
|
+
|
84
|
+
endian :little
|
85
|
+
|
86
|
+
uint32 :flags, label: 'Flags'
|
87
|
+
end
|
75
88
|
|
76
89
|
# An SMB2 NEGOTIATE_CONTEXT struct as defined in
|
77
90
|
# [2.2.3.1 SMB2 NEGOTIATE_CONTEXT Request Values](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/15332256-522e-4a53-8cd7-0bd17678a2f7)
|
@@ -84,6 +97,8 @@ module RubySMB
|
|
84
97
|
SMB2_COMPRESSION_CAPABILITIES = 0x0003
|
85
98
|
# The NegotiateContext Data field contains the server name to which the client connects.
|
86
99
|
SMB2_NETNAME_NEGOTIATE_CONTEXT_ID = 0x0005
|
100
|
+
# The NegotiateContext Data field contains the transport capabilities, as specified in section 2.2.3.1.5.
|
101
|
+
SMB2_TRANSPORT_CAPABILITIES = 0x0006
|
87
102
|
|
88
103
|
endian :little
|
89
104
|
|
@@ -95,7 +110,8 @@ module RubySMB
|
|
95
110
|
preauth_integrity_capabilities SMB2_PREAUTH_INTEGRITY_CAPABILITIES, label: 'Preauthentication Integrity Capabilities'
|
96
111
|
encryption_capabilities SMB2_ENCRYPTION_CAPABILITIES, label: 'Encryption Capabilities'
|
97
112
|
compression_capabilities SMB2_COMPRESSION_CAPABILITIES, label: 'Compression Capabilities'
|
98
|
-
netname_negotiate_context_id SMB2_NETNAME_NEGOTIATE_CONTEXT_ID, label: 'Netname Negotiate Context ID'
|
113
|
+
netname_negotiate_context_id SMB2_NETNAME_NEGOTIATE_CONTEXT_ID, label: 'Netname Negotiate Context ID', data_length: :data_length
|
114
|
+
transport_capabilities SMB2_TRANSPORT_CAPABILITIES, label: 'Transport Capabilities'
|
99
115
|
end
|
100
116
|
|
101
117
|
def pad_length
|
@@ -64,6 +64,15 @@ module RubySMB
|
|
64
64
|
self.negotiate_context_list
|
65
65
|
end
|
66
66
|
|
67
|
+
# Find the first Negotiate Context structure that matches the given
|
68
|
+
# context type
|
69
|
+
#
|
70
|
+
# @param [Integer] the Negotiate Context structure you wish to add
|
71
|
+
# @return [NegotiateContext] the Negotiate Context structure or nil if
|
72
|
+
# not found
|
73
|
+
def find_negotiate_context(type)
|
74
|
+
negotiate_context_list.find { |nc| nc.context_type == type }
|
75
|
+
end
|
67
76
|
|
68
77
|
private
|
69
78
|
|
@@ -11,8 +11,8 @@ module RubySMB
|
|
11
11
|
uint16 :structure_size, label: 'Structure Size', initial_value: 9
|
12
12
|
session_flags :session_flags
|
13
13
|
uint16 :security_buffer_offset, label: 'Security Buffer Offset', initial_value: 0x48
|
14
|
-
uint16 :security_buffer_length, label: 'Security Buffer Length'
|
15
|
-
string :buffer, label: 'Security Buffer',
|
14
|
+
uint16 :security_buffer_length, label: 'Security Buffer Length', initial_value: -> { buffer.length }
|
15
|
+
string :buffer, label: 'Security Buffer', read_length: -> { security_buffer_length }
|
16
16
|
|
17
17
|
def initialize_instance
|
18
18
|
super
|
@@ -101,7 +101,7 @@ module RubySMB
|
|
101
101
|
path.to_binary_s.length
|
102
102
|
end
|
103
103
|
end
|
104
|
-
string16 :path, label: 'Path Buffer', onlyif: -> { flags != SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT }
|
104
|
+
string16 :path, label: 'Path Buffer', onlyif: -> { flags != SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT }, read_length: -> { path_length }
|
105
105
|
tree_connect_request_extension :tree_connect_request_extension, label: 'Tree Connect Request Extension', onlyif: -> { flags == SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT }
|
106
106
|
end
|
107
107
|
end
|
data/lib/ruby_smb/smb2/tree.rb
CHANGED
@@ -61,7 +61,7 @@ module RubySMB
|
|
61
61
|
opts = opts.dup
|
62
62
|
opts[:filename] = opts[:filename].dup
|
63
63
|
opts[:filename] = opts[:filename][1..-1] if opts[:filename].start_with? '\\'
|
64
|
-
open_file(opts)
|
64
|
+
open_file(**opts)
|
65
65
|
end
|
66
66
|
|
67
67
|
def open_file(filename:, attributes: nil, options: nil, disposition: RubySMB::Dispositions::FILE_OPEN,
|
data/lib/ruby_smb/smb2.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
module RubySMB
|
2
2
|
# A packet parsing and manipulation library for the SMB2 protocol
|
3
3
|
#
|
4
|
-
# [[MS-SMB2] Server
|
4
|
+
# [[MS-SMB2] Server Message Block (SMB) Protocol Versions 2 and 3](https://msdn.microsoft.com/en-us/library/cc246482.aspx)
|
5
5
|
module SMB2
|
6
6
|
# Protocol ID value. Translates to \xFESMB
|
7
7
|
SMB2_PROTOCOL_ID = 0xFE534D42
|
8
|
+
# Wildcard revision, see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/63abf97c-0d09-47e2-88d6-6bfa552949a5
|
9
|
+
SMB2_WILDCARD_REVISION = 0x02ff
|
8
10
|
|
9
11
|
require 'ruby_smb/smb2/info_type'
|
10
12
|
require 'ruby_smb/smb2/commands'
|
data/lib/ruby_smb/version.rb
CHANGED
data/lib/ruby_smb.rb
CHANGED
@@ -21,10 +21,11 @@ module RubySMB
|
|
21
21
|
require 'ruby_smb/generic_packet'
|
22
22
|
require 'ruby_smb/dispatcher'
|
23
23
|
require 'ruby_smb/version'
|
24
|
-
require 'ruby_smb/version'
|
25
24
|
require 'ruby_smb/smb2'
|
26
25
|
require 'ruby_smb/smb1'
|
27
26
|
require 'ruby_smb/client'
|
28
27
|
require 'ruby_smb/crypto'
|
29
28
|
require 'ruby_smb/compression'
|
29
|
+
require 'ruby_smb/server'
|
30
|
+
require 'ruby_smb/dialect'
|
30
31
|
end
|
@@ -209,6 +209,8 @@ RSpec.describe RubySMB::Client do
|
|
209
209
|
|
210
210
|
context 'when signing' do
|
211
211
|
it 'calls #smb1_sign if it is an SMB1 packet' do
|
212
|
+
allow(client).to receive(:signing_required).and_return(true)
|
213
|
+
allow(client).to receive(:session_key).and_return(Random.new.bytes(16))
|
212
214
|
expect(client).to receive(:smb1_sign).with(smb1_request).and_call_original
|
213
215
|
client.send_recv(smb1_request)
|
214
216
|
end
|
@@ -223,15 +225,11 @@ RSpec.describe RubySMB::Client do
|
|
223
225
|
|
224
226
|
it 'calls #smb2_sign if it is an SMB2 client' do
|
225
227
|
allow(smb2_client).to receive(:is_status_pending?).and_return(false)
|
228
|
+
allow(smb2_client).to receive(:signing_required).and_return(true)
|
229
|
+
allow(smb2_client).to receive(:session_key).and_return(Random.new.bytes(16))
|
226
230
|
expect(smb2_client).to receive(:smb2_sign).with(smb2_request).and_call_original
|
227
231
|
smb2_client.send_recv(smb2_request)
|
228
232
|
end
|
229
|
-
|
230
|
-
it 'calls #smb3_sign if it is an SMB3 client' do
|
231
|
-
allow(smb3_client).to receive(:is_status_pending?).and_return(false)
|
232
|
-
expect(smb3_client).to receive(:smb3_sign).with(smb2_request).and_call_original
|
233
|
-
smb3_client.send_recv(smb2_request)
|
234
|
-
end
|
235
233
|
end
|
236
234
|
end
|
237
235
|
|
@@ -809,6 +807,18 @@ RSpec.describe RubySMB::Client do
|
|
809
807
|
smb1_extended_response.to_binary_s
|
810
808
|
}
|
811
809
|
|
810
|
+
let(:smb1_response) {
|
811
|
+
packet = RubySMB::SMB1::Packet::NegotiateResponse.new
|
812
|
+
smb1_capabilities_dup = smb1_capabilities.dup
|
813
|
+
smb1_capabilities_dup[:extended_security] = 0
|
814
|
+
|
815
|
+
packet.parameter_block.capabilities = smb1_capabilities_dup
|
816
|
+
packet
|
817
|
+
}
|
818
|
+
let(:smb1_response_raw) {
|
819
|
+
smb1_response.to_binary_s
|
820
|
+
}
|
821
|
+
|
812
822
|
let(:smb2_response) { RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x200) }
|
813
823
|
let(:smb3_response) { RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x300) }
|
814
824
|
|
@@ -997,10 +1007,14 @@ RSpec.describe RubySMB::Client do
|
|
997
1007
|
|
998
1008
|
describe '#negotiate_response' do
|
999
1009
|
context 'with only SMB1' do
|
1000
|
-
it 'returns a properly formed packet' do
|
1010
|
+
it 'returns a properly formed NegotiateResponseExtended packet if extended_security is set as 1' do
|
1001
1011
|
expect(smb1_client.negotiate_response(smb1_extended_response_raw)).to eq smb1_extended_response
|
1002
1012
|
end
|
1003
1013
|
|
1014
|
+
it 'returns a properly formed NegotiateResponse packet if extended_security is set as 0' do
|
1015
|
+
expect(smb1_client.negotiate_response(smb1_response_raw)).to eq smb1_response
|
1016
|
+
end
|
1017
|
+
|
1004
1018
|
it 'raises an exception if the response is not a SMB packet' do
|
1005
1019
|
expect { smb1_client.negotiate_response(random_junk) }.to raise_error(RubySMB::Error::InvalidPacket)
|
1006
1020
|
end
|
@@ -1015,12 +1029,6 @@ RSpec.describe RubySMB::Client do
|
|
1015
1029
|
bogus_response.smb_header.command = 0xff
|
1016
1030
|
expect { smb1_client.negotiate_response(bogus_response.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
|
1017
1031
|
end
|
1018
|
-
|
1019
|
-
it 'considers the response invalid if Extended Security is not enabled' do
|
1020
|
-
bogus_response = smb1_extended_response
|
1021
|
-
bogus_response.parameter_block.capabilities.extended_security = 0
|
1022
|
-
expect { smb1_client.negotiate_response(bogus_response.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
|
1023
|
-
end
|
1024
1032
|
end
|
1025
1033
|
|
1026
1034
|
context 'with only SMB2' do
|
@@ -2077,7 +2085,7 @@ RSpec.describe RubySMB::Client do
|
|
2077
2085
|
it 'generates the HMAC based on the packet and the NTLM session key and signs the packet with it' do
|
2078
2086
|
smb2_client.session_key = 'foo'
|
2079
2087
|
smb2_client.signing_required = true
|
2080
|
-
expect(OpenSSL::HMAC).to receive(:digest).with(instance_of(OpenSSL::Digest
|
2088
|
+
expect(OpenSSL::HMAC).to receive(:digest).with(instance_of(OpenSSL::Digest), smb2_client.session_key, request1.to_binary_s).and_return(fake_hmac)
|
2081
2089
|
expect(smb2_client.smb2_sign(request1).smb2_header.signature).to eq fake_hmac
|
2082
2090
|
end
|
2083
2091
|
end
|
@@ -2177,7 +2185,7 @@ RSpec.describe RubySMB::Client do
|
|
2177
2185
|
smb3_client.dialect = '0x0202'
|
2178
2186
|
expect { smb3_client.smb3_sign(request) }.to raise_error(
|
2179
2187
|
RubySMB::Error::SigningError,
|
2180
|
-
'Dialect is incompatible with SMBv3 signing'
|
2188
|
+
'Dialect "0x0202" is incompatible with SMBv3 signing'
|
2181
2189
|
)
|
2182
2190
|
end
|
2183
2191
|
end
|
@@ -2227,7 +2235,7 @@ RSpec.describe RubySMB::Client do
|
|
2227
2235
|
smb3_client.dialect = '0x0202'
|
2228
2236
|
expect { smb3_client.smb3_sign(request) }.to raise_error(
|
2229
2237
|
RubySMB::Error::SigningError,
|
2230
|
-
'Dialect is incompatible with SMBv3 signing'
|
2238
|
+
'Dialect "0x0202" is incompatible with SMBv3 signing'
|
2231
2239
|
)
|
2232
2240
|
end
|
2233
2241
|
end
|