ruby_smb 2.0.8 → 2.0.12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.github/workflows/verify.yml +5 -15
  4. data/examples/auth_capture.rb +71 -0
  5. data/lib/ruby_smb/client/negotiation.rb +9 -11
  6. data/lib/ruby_smb/client.rb +30 -25
  7. data/lib/ruby_smb/dialect.rb +45 -0
  8. data/lib/ruby_smb/dispatcher/base.rb +1 -1
  9. data/lib/ruby_smb/gss/provider/authenticator.rb +42 -0
  10. data/lib/ruby_smb/gss/provider/ntlm.rb +303 -0
  11. data/lib/ruby_smb/gss/provider.rb +35 -0
  12. data/lib/ruby_smb/gss.rb +56 -63
  13. data/lib/ruby_smb/ntlm.rb +45 -0
  14. data/lib/ruby_smb/server/server_client/negotiation.rb +156 -0
  15. data/lib/ruby_smb/server/server_client/session_setup.rb +82 -0
  16. data/lib/ruby_smb/server/server_client.rb +162 -0
  17. data/lib/ruby_smb/server.rb +54 -0
  18. data/lib/ruby_smb/signing.rb +59 -0
  19. data/lib/ruby_smb/smb1/packet/negotiate_response.rb +11 -11
  20. data/lib/ruby_smb/smb1/packet/negotiate_response_extended.rb +1 -1
  21. data/lib/ruby_smb/smb1/packet/session_setup_request.rb +1 -1
  22. data/lib/ruby_smb/smb1/tree.rb +1 -1
  23. data/lib/ruby_smb/smb2/negotiate_context.rb +18 -2
  24. data/lib/ruby_smb/smb2/packet/negotiate_request.rb +9 -0
  25. data/lib/ruby_smb/smb2/packet/negotiate_response.rb +0 -1
  26. data/lib/ruby_smb/smb2/packet/session_setup_response.rb +2 -2
  27. data/lib/ruby_smb/smb2/packet/tree_connect_request.rb +1 -1
  28. data/lib/ruby_smb/smb2/tree.rb +1 -1
  29. data/lib/ruby_smb/smb2.rb +3 -1
  30. data/lib/ruby_smb/version.rb +1 -1
  31. data/lib/ruby_smb.rb +2 -1
  32. data/spec/lib/ruby_smb/client_spec.rb +24 -16
  33. data/spec/lib/ruby_smb/gss/provider/ntlm/account_spec.rb +32 -0
  34. data/spec/lib/ruby_smb/gss/provider/ntlm/authenticator_spec.rb +101 -0
  35. data/spec/lib/ruby_smb/gss/provider/ntlm/os_version_spec.rb +32 -0
  36. data/spec/lib/ruby_smb/gss/provider/ntlm_spec.rb +113 -0
  37. data/spec/lib/ruby_smb/server/server_client_spec.rb +156 -0
  38. data/spec/lib/ruby_smb/server_spec.rb +32 -0
  39. data/spec/lib/ruby_smb/smb1/tree_spec.rb +4 -4
  40. data/spec/lib/ruby_smb/smb2/negotiate_context_spec.rb +2 -2
  41. data/spec/lib/ruby_smb/smb2/tree_spec.rb +5 -5
  42. data.tar.gz.sig +0 -0
  43. metadata +25 -3
  44. metadata.gz.sig +0 -0
  45. 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, label: '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, label: '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 automaticaly sets the length in
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
@@ -60,7 +60,7 @@ module RubySMB
60
60
  opts = opts.dup
61
61
  opts[:filename] = opts[:filename].dup
62
62
  opts[:filename].prepend('\\') unless opts[:filename].start_with?('\\')
63
- open_file(opts)
63
+ open_file(**opts)
64
64
  end
65
65
 
66
66
  # Open a file on the remote share.
@@ -69,9 +69,22 @@ module RubySMB
69
69
  class NetnameNegotiateContextId < BinData::Record
70
70
  endian :little
71
71
 
72
- stringz16 :net_name, label: 'Net Name'
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
 
@@ -59,7 +59,6 @@ module RubySMB
59
59
  self.negotiate_context_list
60
60
  end
61
61
 
62
-
63
62
  private
64
63
 
65
64
  # Determines the correct length for the padding, so that the next
@@ -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', length: -> { security_buffer_length }
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
@@ -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 Mesage Block (SMB) Protocol Versions 2 and 3](https://msdn.microsoft.com/en-us/library/cc246482.aspx)
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'
@@ -1,3 +1,3 @@
1
1
  module RubySMB
2
- VERSION = '2.0.8'.freeze
2
+ VERSION = '2.0.12'.freeze
3
3
  end
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::SHA256), smb2_client.session_key, request1.to_binary_s).and_return(fake_hmac)
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