ruby_smb 3.0.2 → 3.0.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9a0b34939805bf96ca0b83284b946fc2d96c1e5d7b6e63c67c4d0c40b73d257d
4
- data.tar.gz: f8c1e25526de8821ec484c820b32734e0cfcefce6e34de2a90b25ce876225e61
3
+ metadata.gz: 3a4881649381520e25f5f014f66baac4130dd8a58d4eadd3476198fec3fc9e8d
4
+ data.tar.gz: 809b581025c19ab7ca1162a7b73d342d63b24edd77835744191a0d7c930425fd
5
5
  SHA512:
6
- metadata.gz: b669ae3cdc6e7c875c92890ac15608ab45b8d223eb064756d340e046a5dd3b4273971ebdf8419a033ae87fc75af8f08d7e7ce5466186e2e531adada3efaedab4
7
- data.tar.gz: 052ad0693a7f9ea8b3b767a58ecc710e37f2c9c0ef7c910b8c385b22b9a6eee678596a49de385cb0d0200964e620b171bd667a76b7b393f96a1ebded64e6f5b7
6
+ metadata.gz: 4e340189b0ae87ec98e7ca6bde9d4c30c1eb3a0d3e1f69383869e4803bf3d3d9bbc502cef143a83bad9f91dc1de10ca3bcd4132a8d518a1bb2121a7d4490309d
7
+ data.tar.gz: 0c7198d84416da8b995e50c17f7826abba21affa03cc1c2404e2e2ecaac1322178f76c356d8d026c4a75b7e9d356bd134f1a0df12fe327eca1240b87afaeee0d
checksums.yaml.gz.sig CHANGED
Binary file
@@ -4,14 +4,15 @@
4
4
  # including protocol negotiation and authentication.
5
5
 
6
6
  require 'bundler/setup'
7
+ require 'optparse'
7
8
  require 'ruby_smb'
8
9
 
9
- def run_authentication(address, smb1, smb2, smb3, username, password)
10
+ def run_authentication(address, smb1, smb2, smb3)
10
11
  # Create our socket and add it to the dispatcher
11
12
  sock = TCPSocket.new address, 445
12
13
  dispatcher = RubySMB::Dispatcher::Socket.new(sock)
13
14
 
14
- client = RubySMB::Client.new(dispatcher, smb1: smb1, smb2: smb2, smb3: smb3, username: username, password: password)
15
+ client = RubySMB::Client.new(dispatcher, smb1: smb1, smb2: smb2, smb3: smb3, username: '', password: '')
15
16
  protocol = client.negotiate
16
17
  status = client.authenticate
17
18
  puts "#{protocol} : #{status}"
@@ -22,9 +23,31 @@ def run_authentication(address, smb1, smb2, smb3, username, password)
22
23
  end
23
24
  end
24
25
 
25
- address = ARGV[0]
26
- username = ''
27
- password = ''
26
+ args = ARGV.dup
27
+ options = {
28
+ smbv1: true,
29
+ smbv2: true,
30
+ smbv3: true,
31
+ target: nil
32
+ }
33
+ options[:target ] = args.pop
34
+ optparser = OptionParser.new do |opts|
35
+ opts.banner = "Usage: #{File.basename(__FILE__)} [options] target"
36
+ opts.on("--[no-]smbv1", "Enable or disable SMBv1 (default: #{options[:smbv1] ? 'Enabled' : 'Disabled'})") do |smbv1|
37
+ options[:smbv1] = smbv1
38
+ end
39
+ opts.on("--[no-]smbv2", "Enable or disable SMBv2 (default: #{options[:smbv2] ? 'Enabled' : 'Disabled'})") do |smbv2|
40
+ options[:smbv2] = smbv2
41
+ end
42
+ opts.on("--[no-]smbv3", "Enable or disable SMBv3 (default: #{options[:smbv3] ? 'Enabled' : 'Disabled'})") do |smbv3|
43
+ options[:smbv3] = smbv3
44
+ end
45
+ end
46
+ optparser.parse!(args)
47
+
48
+ if options[:target].nil?
49
+ abort(optparser.help)
50
+ end
28
51
 
29
52
  # Negotiate with only SMB1 enabled
30
- run_authentication(address, true, false, false, username, password)
53
+ run_authentication(options[:target], options[:smbv1], options[:smbv2], options[:smbv3])
@@ -15,13 +15,13 @@ options = {
15
15
  }
16
16
  OptionParser.new do |opts|
17
17
  opts.banner = "Usage: #{File.basename(__FILE__)} [options]"
18
- opts.on("--[no-]smbv1", "Enabled or disable SMBv1 (default: #{options[:smbv1] ? 'Enabled' : 'Disabled'})") do |smbv1|
18
+ opts.on("--[no-]smbv1", "Enable or disable SMBv1 (default: #{options[:smbv1] ? 'Enabled' : 'Disabled'})") do |smbv1|
19
19
  options[:smbv1] = smbv1
20
20
  end
21
- opts.on("--[no-]smbv2", "Enabled or disable SMBv2 (default: #{options[:smbv2] ? 'Enabled' : 'Disabled'})") do |smbv2|
21
+ opts.on("--[no-]smbv2", "Enable or disable SMBv2 (default: #{options[:smbv2] ? 'Enabled' : 'Disabled'})") do |smbv2|
22
22
  options[:smbv2] = smbv2
23
23
  end
24
- opts.on("--[no-]smbv3", "Enabled or disable SMBv3 (default: #{options[:smbv3] ? 'Enabled' : 'Disabled'})") do |smbv3|
24
+ opts.on("--[no-]smbv3", "Enable or disable SMBv3 (default: #{options[:smbv3] ? 'Enabled' : 'Disabled'})") do |smbv3|
25
25
  options[:smbv3] = smbv3
26
26
  end
27
27
  end.parse!
@@ -30,13 +30,13 @@ OptionParser.new do |opts|
30
30
  opts.on("--[no-]anonymous", "Allow anonymous access (default: #{options[:allow_anonymous]})") do |allow_anonymous|
31
31
  options[:allow_anonymous] = allow_anonymous
32
32
  end
33
- opts.on("--[no-]smbv1", "Enabled or disable SMBv1 (default: #{options[:smbv1] ? 'Enabled' : 'Disabled'})") do |smbv1|
33
+ opts.on("--[no-]smbv1", "Enable or disable SMBv1 (default: #{options[:smbv1] ? 'Enabled' : 'Disabled'})") do |smbv1|
34
34
  options[:smbv1] = smbv1
35
35
  end
36
- opts.on("--[no-]smbv2", "Enabled or disable SMBv2 (default: #{options[:smbv2] ? 'Enabled' : 'Disabled'})") do |smbv2|
36
+ opts.on("--[no-]smbv2", "Enable or disable SMBv2 (default: #{options[:smbv2] ? 'Enabled' : 'Disabled'})") do |smbv2|
37
37
  options[:smbv2] = smbv2
38
38
  end
39
- opts.on("--[no-]smbv3", "Enabled or disable SMBv3 (default: #{options[:smbv3] ? 'Enabled' : 'Disabled'})") do |smbv3|
39
+ opts.on("--[no-]smbv3", "Enable or disable SMBv3 (default: #{options[:smbv3] ? 'Enabled' : 'Disabled'})") do |smbv3|
40
40
  options[:smbv3] = smbv3
41
41
  end
42
42
  opts.on("--username USERNAME", "The account's username (default: #{options[:username]})") do |username|
@@ -7,34 +7,75 @@
7
7
  # and read the file short.txt
8
8
 
9
9
  require 'bundler/setup'
10
+ require 'optparse'
10
11
  require 'ruby_smb'
11
12
 
12
- address = ARGV[0]
13
- username = ARGV[1]
14
- password = ARGV[2]
15
- share = ARGV[3]
16
- file = ARGV[4]
17
- smb_versions = ARGV[5]&.split(',') || ['1','2','3']
13
+ args = ARGV.dup
14
+ options = {
15
+ domain: '.',
16
+ username: '',
17
+ password: '',
18
+ share: nil,
19
+ smbv1: true,
20
+ smbv2: true,
21
+ smbv3: true,
22
+ target: nil
23
+ }
24
+ options[:file] = args.pop
25
+ options[:share] = args.pop
26
+ options[:target ] = args.pop
27
+ optparser = OptionParser.new do |opts|
28
+ opts.banner = "Usage: #{File.basename(__FILE__)} [options] target share file"
29
+ opts.on("--[no-]smbv1", "Enable or disable SMBv1 (default: #{options[:smbv1] ? 'Enabled' : 'Disabled'})") do |smbv1|
30
+ options[:smbv1] = smbv1
31
+ end
32
+ opts.on("--[no-]smbv2", "Enable or disable SMBv2 (default: #{options[:smbv2] ? 'Enabled' : 'Disabled'})") do |smbv2|
33
+ options[:smbv2] = smbv2
34
+ end
35
+ opts.on("--[no-]smbv3", "Enable or disable SMBv3 (default: #{options[:smbv3] ? 'Enabled' : 'Disabled'})") do |smbv3|
36
+ options[:smbv3] = smbv3
37
+ end
38
+ opts.on("--username USERNAME", "The account's username (default: #{options[:username]})") do |username|
39
+ if username.include?('\\')
40
+ options[:domain], options[:username] = username.split('\\', 2)
41
+ else
42
+ options[:username] = username
43
+ end
44
+ end
45
+ opts.on("--password PASSWORD", "The account's password (default: #{options[:password]})") do |password|
46
+ options[:password] = password
47
+ end
48
+ end
49
+ optparser.parse!(args)
50
+
51
+ if options[:target].nil? || options[:share].nil? || options[:file].nil?
52
+ abort(optparser.help)
53
+ end
18
54
 
19
- path = "\\\\#{address}\\#{share}"
55
+ path = "\\\\#{options[:target]}\\#{options[:share]}"
20
56
 
21
- sock = TCPSocket.new address, 445
57
+ sock = TCPSocket.new options[:target], 445
22
58
  dispatcher = RubySMB::Dispatcher::Socket.new(sock)
23
59
 
24
- client = RubySMB::Client.new(dispatcher, smb1: smb_versions.include?('1'), smb2: smb_versions.include?('2'), smb3: smb_versions.include?('3'), username: username, password: password)
60
+ client = RubySMB::Client.new(dispatcher, smb1: options[:smbv1], smb2: options[:smbv2], smb3: options[:smbv3], username: options[:username], password: options[:password], domain: options[:domain])
25
61
  protocol = client.negotiate
26
62
  status = client.authenticate
27
63
 
28
64
  puts "#{protocol} : #{status}"
65
+ unless status == WindowsError::NTStatus::STATUS_SUCCESS
66
+ puts 'Authentication failed!'
67
+ exit(1)
68
+ end
29
69
 
30
70
  begin
31
71
  tree = client.tree_connect(path)
32
72
  puts "Connected to #{path} successfully!"
33
73
  rescue StandardError => e
34
74
  puts "Failed to connect to #{path}: #{e.message}"
75
+ exit(1)
35
76
  end
36
77
 
37
- file = tree.open_file(filename: file)
78
+ file = tree.open_file(filename: options[:file])
38
79
 
39
80
  data = file.read
40
81
  puts data
@@ -6,24 +6,64 @@
6
6
  # This will try to connect to \\192.168.172.138\TEST_SHARE with the msfadmin:msfadmin credentials
7
7
 
8
8
  require 'bundler/setup'
9
+ require 'optparse'
9
10
  require 'ruby_smb'
10
11
 
11
- address = ARGV[0]
12
- username = ARGV[1]
13
- password = ARGV[2]
14
- share = ARGV[3]
15
- smb_versions = ARGV[4]&.split(',') || ['1','2','3']
12
+ args = ARGV.dup
13
+ options = {
14
+ domain: '.',
15
+ username: '',
16
+ password: '',
17
+ share: nil,
18
+ smbv1: true,
19
+ smbv2: true,
20
+ smbv3: true,
21
+ target: nil
22
+ }
23
+ options[:share] = args.pop
24
+ options[:target ] = args.pop
25
+ optparser = OptionParser.new do |opts|
26
+ opts.banner = "Usage: #{File.basename(__FILE__)} [options] target share"
27
+ opts.on("--[no-]smbv1", "Enable or disable SMBv1 (default: #{options[:smbv1] ? 'Enabled' : 'Disabled'})") do |smbv1|
28
+ options[:smbv1] = smbv1
29
+ end
30
+ opts.on("--[no-]smbv2", "Enable or disable SMBv2 (default: #{options[:smbv2] ? 'Enabled' : 'Disabled'})") do |smbv2|
31
+ options[:smbv2] = smbv2
32
+ end
33
+ opts.on("--[no-]smbv3", "Enable or disable SMBv3 (default: #{options[:smbv3] ? 'Enabled' : 'Disabled'})") do |smbv3|
34
+ options[:smbv3] = smbv3
35
+ end
36
+ opts.on("--username USERNAME", "The account's username (default: #{options[:username]})") do |username|
37
+ if username.include?('\\')
38
+ options[:domain], options[:username] = username.split('\\', 2)
39
+ else
40
+ options[:username] = username
41
+ end
42
+ end
43
+ opts.on("--password PASSWORD", "The account's password (default: #{options[:password]})") do |password|
44
+ options[:password] = password
45
+ end
46
+ end
47
+ optparser.parse!(args)
48
+
49
+ if options[:target].nil? || options[:share].nil?
50
+ abort(optparser.help)
51
+ end
16
52
 
17
- path = "\\\\#{address}\\#{share}"
53
+ path = "\\\\#{options[:target]}\\#{options[:share]}"
18
54
 
19
- sock = TCPSocket.new address, 445
55
+ sock = TCPSocket.new options[:target], 445
20
56
  dispatcher = RubySMB::Dispatcher::Socket.new(sock)
21
57
 
22
- client = RubySMB::Client.new(dispatcher, smb1: smb_versions.include?('1'), smb2: smb_versions.include?('2'), smb3: smb_versions.include?('3'), username: username, password: password)
58
+ client = RubySMB::Client.new(dispatcher, smb1: options[:smbv1], smb2: options[:smbv2], smb3: options[:smbv3], username: options[:username], password: options[:password], domain: options[:domain])
23
59
  protocol = client.negotiate
24
60
  status = client.authenticate
25
61
 
26
62
  puts "#{protocol} : #{status}"
63
+ unless status == WindowsError::NTStatus::STATUS_SUCCESS
64
+ puts 'Authentication failed!'
65
+ exit(1)
66
+ end
27
67
 
28
68
  begin
29
69
  tree = client.tree_connect(path)
@@ -31,4 +71,5 @@ begin
31
71
  tree.disconnect!
32
72
  rescue StandardError => e
33
73
  puts "Failed to connect to #{path}: #{e.message}"
74
+ exit(1)
34
75
  end
@@ -212,9 +212,17 @@ module RubySMB
212
212
 
213
213
  raw = smb2_ntlmssp_authenticate(type3_message, @session_id)
214
214
  response = smb2_ntlmssp_final_packet(raw)
215
-
216
- if @smb3 && !@session_encrypt_data && response.session_flags.encrypt_data == 1
217
- @session_encrypt_data = true
215
+ @session_is_guest = response.session_flags.guest == 1
216
+
217
+ if @smb3
218
+ if response.session_flags.encrypt_data == 1
219
+ # if the server indicates that encryption is required, enable it
220
+ @session_encrypt_data = true
221
+ elsif (@session_is_guest && password != '') || (username == '' && password == '')
222
+ # disable encryption when necessary
223
+ @session_encrypt_data = false
224
+ end
225
+ # otherwise, leave encryption to the default value that it was initialized to
218
226
  end
219
227
  ######
220
228
  # DEBUG
@@ -251,7 +251,7 @@ module RubySMB
251
251
  raise ArgumentError, 'Must be an array of strings' unless dialect.is_a? String
252
252
  packet.add_dialect(dialect.to_i(16))
253
253
  end
254
- packet.capabilities.encryption = 1
254
+ packet.capabilities.encryption = @session_encrypt_data ? 1 : 0
255
255
 
256
256
  if packet.dialects.include?(0x0311)
257
257
  nc = RubySMB::SMB2::NegotiateContext.new(
@@ -29,10 +29,26 @@ module RubySMB
29
29
  # It indicates that the server implements SMB 2.1 or future dialect revisions
30
30
  # Note that this must be used for SMB3
31
31
  SMB1_DIALECT_SMB2_WILDCARD = 'SMB 2.???'.freeze
32
+
33
+ SMB2_DIALECT_0202 = '0x0202'.freeze
34
+ SMB2_DIALECT_0210 = '0x0210'.freeze
35
+ SMB2_DIALECT_0300 = '0x0300'.freeze
36
+ SMB2_DIALECT_0302 = '0x0302'.freeze
37
+ SMB2_DIALECT_0311 = '0x0311'.freeze
38
+
32
39
  # Dialect values for SMB2
33
- SMB2_DIALECT_DEFAULT = ['0x0202', '0x0210']
40
+ SMB2_DIALECT_DEFAULT = [
41
+ SMB2_DIALECT_0202,
42
+ SMB2_DIALECT_0210,
43
+ ].freeze
44
+
34
45
  # Dialect values for SMB3
35
- SMB3_DIALECT_DEFAULT = ['0x0300', '0x0302', '0x0311']
46
+ SMB3_DIALECT_DEFAULT = [
47
+ SMB2_DIALECT_0300,
48
+ SMB2_DIALECT_0302,
49
+ SMB2_DIALECT_0311
50
+ ].freeze
51
+
36
52
  # The default maximum size of a SMB message that the Client accepts (in bytes)
37
53
  MAX_BUFFER_SIZE = 64512
38
54
  # The default maximum size of a SMB message that the Server accepts (in bytes)
@@ -129,6 +145,11 @@ module RubySMB
129
145
  # @return [Integer]
130
146
  attr_accessor :session_id
131
147
 
148
+ # Whether or not the current session has the guest flag set
149
+ # @!attribute [rw] session_is_guest
150
+ # @return [Boolean]
151
+ attr_accessor :session_is_guest
152
+
132
153
  # Whether or not the Server requires signing
133
154
  # @!attribute [rw] signing_enabled
134
155
  # @return [Boolean]
@@ -292,6 +313,7 @@ module RubySMB
292
313
  @sequence_counter = 0
293
314
  @session_id = 0x00
294
315
  @session_key = ''
316
+ @session_is_guest = false
295
317
  @signing_required = false
296
318
  @smb1 = smb1
297
319
  @smb2 = smb2
@@ -306,6 +328,8 @@ module RubySMB
306
328
  @server_supports_multi_credit = false
307
329
 
308
330
  # SMB 3.x options
331
+ # this merely initializes the default value for session encryption, it may be changed as necessary when a
332
+ # session setup response is received
309
333
  @session_encrypt_data = always_encrypt
310
334
 
311
335
  @ntlm_client = Net::NTLM::Client.new(
@@ -444,8 +468,13 @@ module RubySMB
444
468
  unless packet.is_a?(RubySMB::SMB2::Packet::SessionSetupRequest) || session_key.empty?
445
469
  if self.smb2 && signing_required
446
470
  packet = smb2_sign(packet)
447
- elsif self.smb3 && (signing_required || packet.is_a?(RubySMB::SMB2::Packet::TreeConnectRequest))
448
- packet = smb3_sign(packet)
471
+ elsif self.smb3
472
+ # see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/652e0c14-5014-4470-999d-b174d7b2da87
473
+ if @dialect == '0x0311' && (signing_required || (!@session_is_guest && packet.is_a?(RubySMB::SMB2::Packet::TreeConnectRequest)))
474
+ packet = smb3_sign(packet)
475
+ elsif signing_required
476
+ packet = smb3_sign(packet)
477
+ end
449
478
  end
450
479
  end
451
480
  else
@@ -589,6 +618,7 @@ module RubySMB
589
618
  self.session_id = 0x00
590
619
  self.user_id = 0x00
591
620
  self.session_key = ''
621
+ self.session_is_guest = false
592
622
  self.sequence_counter = 0
593
623
  self.smb2_message_id = 0
594
624
  self.client_encryption_key = nil
@@ -13,6 +13,7 @@ module RubySMB
13
13
  @user_id = user_id
14
14
  @state = state
15
15
  @signing_required = false
16
+ @metadata = {}
16
17
  # tree id => provider processor instance
17
18
  @tree_connect_table = {}
18
19
  @creation_time = Time.now
@@ -62,6 +63,11 @@ module RubySMB
62
63
  # @return [Hash]
63
64
  attr_accessor :tree_connect_table
64
65
 
66
+ # Untyped hash for storing additional arbitrary metadata about the current session
67
+ # @!attribute [rw] metadaa
68
+ # @return [Hash]
69
+ attr_accessor :metadata
70
+
65
71
  # The time at which this session was created.
66
72
  # @!attribute [r] creation_time
67
73
  # @return [Time]
@@ -59,8 +59,8 @@ module RubySMB
59
59
  # Make sure we don't modify the caller's hash options
60
60
  opts = opts.dup
61
61
  opts[:filename] = opts[:filename].dup
62
- opts[:filename].prepend('\\') unless opts[:filename].start_with?('\\')
63
- open_file(**opts)
62
+ opts[:filename].prepend('\\') unless opts[:filename].start_with?('\\'.encode(opts[:filename].encoding))
63
+ _open(**opts)
64
64
  end
65
65
 
66
66
  # Open a file on the remote share.
@@ -80,83 +80,12 @@ module RubySMB
80
80
  # @return [RubySMB::SMB1::File] handle to the created file
81
81
  # @raise [RubySMB::Error::InvalidPacket] if the response command is not SMB_COM_NT_CREATE_ANDX
82
82
  # @raise [RubySMB::Error::UnexpectedStatusCode] if the response NTStatus is not STATUS_SUCCESS
83
- def open_file(filename:, flags: nil, options: nil, disposition: RubySMB::Dispositions::FILE_OPEN,
84
- impersonation: RubySMB::ImpersonationLevels::SEC_IMPERSONATE, read: true, write: false, delete: false)
85
- nt_create_andx_request = RubySMB::SMB1::Packet::NtCreateAndxRequest.new
86
- nt_create_andx_request = set_header_fields(nt_create_andx_request)
87
-
88
- nt_create_andx_request.parameter_block.ext_file_attributes.normal = 1
89
-
90
- if flags
91
- nt_create_andx_request.parameter_block.flags = flags
92
- else
93
- nt_create_andx_request.parameter_block.flags.request_extended_response = 1
94
- end
95
-
96
- if options
97
- nt_create_andx_request.parameter_block.create_options = options
98
- else
99
- nt_create_andx_request.parameter_block.create_options.directory_file = 0
100
- nt_create_andx_request.parameter_block.create_options.non_directory_file = 1
101
- end
102
-
103
- if read
104
- nt_create_andx_request.parameter_block.share_access.share_read = 1
105
- nt_create_andx_request.parameter_block.desired_access.read_data = 1
106
- nt_create_andx_request.parameter_block.desired_access.read_ea = 1
107
- nt_create_andx_request.parameter_block.desired_access.read_attr = 1
108
- nt_create_andx_request.parameter_block.desired_access.read_control = 1
109
- end
110
-
111
- if write
112
- nt_create_andx_request.parameter_block.share_access.share_write = 1
113
- nt_create_andx_request.parameter_block.desired_access.write_data = 1
114
- nt_create_andx_request.parameter_block.desired_access.append_data = 1
115
- nt_create_andx_request.parameter_block.desired_access.write_ea = 1
116
- nt_create_andx_request.parameter_block.desired_access.write_attr = 1
117
- end
118
-
119
- if delete
120
- nt_create_andx_request.parameter_block.share_access.share_delete = 1
121
- nt_create_andx_request.parameter_block.desired_access.delete_access = 1
122
- end
123
-
124
- nt_create_andx_request.parameter_block.impersonation_level = impersonation
125
- nt_create_andx_request.parameter_block.create_disposition = disposition
126
-
127
- unicode_enabled = nt_create_andx_request.smb_header.flags2.unicode == 1
128
- nt_create_andx_request.data_block.file_name = add_null_termination(str: filename, unicode: unicode_enabled)
129
-
130
- raw_response = @client.send_recv(nt_create_andx_request)
131
- response = RubySMB::SMB1::Packet::NtCreateAndxResponse.read(raw_response)
132
- unless response.valid?
133
- if response.is_a?(RubySMB::SMB1::Packet::EmptyPacket) &&
134
- response.smb_header.protocol == RubySMB::SMB1::SMB_PROTOCOL_ID &&
135
- response.smb_header.command == response.original_command
136
- raise RubySMB::Error::InvalidPacket.new(
137
- 'The response seems to be an SMB1 NtCreateAndxResponse but an '\
138
- 'error occurs while parsing it. It is probably missing the '\
139
- 'required extended information.'
140
- )
141
- end
142
- raise RubySMB::Error::InvalidPacket.new(
143
- expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
144
- expected_cmd: RubySMB::SMB1::Packet::NtCreateAndxResponse::COMMAND,
145
- packet: response
146
- )
147
- end
148
- unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
149
- raise RubySMB::Error::UnexpectedStatusCode, response.status_code
150
- end
151
-
152
- case response.parameter_block.resource_type
153
- when RubySMB::SMB1::ResourceType::BYTE_MODE_PIPE, RubySMB::SMB1::ResourceType::MESSAGE_MODE_PIPE
154
- RubySMB::SMB1::Pipe.new(name: filename, tree: self, response: response)
155
- when RubySMB::SMB1::ResourceType::DISK
156
- RubySMB::SMB1::File.new(name: filename, tree: self, response: response)
157
- else
158
- raise RubySMB::Error::RubySMBError
159
- end
83
+ def open_file(opts)
84
+ # Make sure we don't modify the caller's hash options
85
+ opts = opts.dup
86
+ opts[:filename] = opts[:filename].dup
87
+ opts[:filename] = opts[:filename][1..-1] if opts[:filename].start_with?('\\'.encode(opts[:filename].encoding))
88
+ _open(**opts)
160
89
  end
161
90
 
162
91
  # List `directory` on the remote share.
@@ -264,6 +193,85 @@ module RubySMB
264
193
 
265
194
  private
266
195
 
196
+ def _open(filename:, flags: nil, options: nil, disposition: RubySMB::Dispositions::FILE_OPEN,
197
+ impersonation: RubySMB::ImpersonationLevels::SEC_IMPERSONATE, read: true, write: false, delete: false)
198
+ nt_create_andx_request = RubySMB::SMB1::Packet::NtCreateAndxRequest.new
199
+ nt_create_andx_request = set_header_fields(nt_create_andx_request)
200
+
201
+ nt_create_andx_request.parameter_block.ext_file_attributes.normal = 1
202
+
203
+ if flags
204
+ nt_create_andx_request.parameter_block.flags = flags
205
+ else
206
+ nt_create_andx_request.parameter_block.flags.request_extended_response = 1
207
+ end
208
+
209
+ if options
210
+ nt_create_andx_request.parameter_block.create_options = options
211
+ else
212
+ nt_create_andx_request.parameter_block.create_options.directory_file = 0
213
+ nt_create_andx_request.parameter_block.create_options.non_directory_file = 1
214
+ end
215
+
216
+ if read
217
+ nt_create_andx_request.parameter_block.share_access.share_read = 1
218
+ nt_create_andx_request.parameter_block.desired_access.read_data = 1
219
+ nt_create_andx_request.parameter_block.desired_access.read_ea = 1
220
+ nt_create_andx_request.parameter_block.desired_access.read_attr = 1
221
+ nt_create_andx_request.parameter_block.desired_access.read_control = 1
222
+ end
223
+
224
+ if write
225
+ nt_create_andx_request.parameter_block.share_access.share_write = 1
226
+ nt_create_andx_request.parameter_block.desired_access.write_data = 1
227
+ nt_create_andx_request.parameter_block.desired_access.append_data = 1
228
+ nt_create_andx_request.parameter_block.desired_access.write_ea = 1
229
+ nt_create_andx_request.parameter_block.desired_access.write_attr = 1
230
+ end
231
+
232
+ if delete
233
+ nt_create_andx_request.parameter_block.share_access.share_delete = 1
234
+ nt_create_andx_request.parameter_block.desired_access.delete_access = 1
235
+ end
236
+
237
+ nt_create_andx_request.parameter_block.impersonation_level = impersonation
238
+ nt_create_andx_request.parameter_block.create_disposition = disposition
239
+
240
+ unicode_enabled = nt_create_andx_request.smb_header.flags2.unicode == 1
241
+ nt_create_andx_request.data_block.file_name = add_null_termination(str: filename, unicode: unicode_enabled)
242
+
243
+ raw_response = @client.send_recv(nt_create_andx_request)
244
+ response = RubySMB::SMB1::Packet::NtCreateAndxResponse.read(raw_response)
245
+ unless response.valid?
246
+ if response.is_a?(RubySMB::SMB1::Packet::EmptyPacket) &&
247
+ response.smb_header.protocol == RubySMB::SMB1::SMB_PROTOCOL_ID &&
248
+ response.smb_header.command == response.original_command
249
+ raise RubySMB::Error::InvalidPacket.new(
250
+ 'The response seems to be an SMB1 NtCreateAndxResponse but an '\
251
+ 'error occurs while parsing it. It is probably missing the '\
252
+ 'required extended information.'
253
+ )
254
+ end
255
+ raise RubySMB::Error::InvalidPacket.new(
256
+ expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
257
+ expected_cmd: RubySMB::SMB1::Packet::NtCreateAndxResponse::COMMAND,
258
+ packet: response
259
+ )
260
+ end
261
+ unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
262
+ raise RubySMB::Error::UnexpectedStatusCode, response.status_code
263
+ end
264
+
265
+ case response.parameter_block.resource_type
266
+ when RubySMB::SMB1::ResourceType::BYTE_MODE_PIPE, RubySMB::SMB1::ResourceType::MESSAGE_MODE_PIPE
267
+ RubySMB::SMB1::Pipe.new(name: filename, tree: self, response: response)
268
+ when RubySMB::SMB1::ResourceType::DISK
269
+ RubySMB::SMB1::File.new(name: filename, tree: self, response: response)
270
+ else
271
+ raise RubySMB::Error::RubySMBError
272
+ end
273
+ end
274
+
267
275
  # Sets ParameterBlock options for FIND_FIRST2 and
268
276
  # FIND_NEXT2 requests. In particular we need to do this
269
277
  # to tell the server to ignore the Trans2DataBlock as we are
@@ -18,6 +18,17 @@ module RubySMB
18
18
  uint64 :previous_session_id, label: 'Previous Session ID'
19
19
  string :buffer, label: 'Security Buffer', length: -> { security_buffer_length }
20
20
 
21
+ # Takes the specified security buffer string and inserts it into the {RubySMB::SMB2::Packet::SessionSetupRequest#buffer}
22
+ # as well as updating the {RubySMB::SMB2::Packet::SessionSetupRequest#security_buffer_length}
23
+ # This method DOES NOT wrap the security buffer in any way.
24
+ #
25
+ # @param buffer [String] the security buffer
26
+ # @return [void]
27
+ def set_security_buffer(buffer)
28
+ self.security_buffer_length = buffer.length
29
+ self.buffer = buffer
30
+ end
31
+
21
32
  # Takes a serialized NTLM Type 1 message and wraps it in the GSS ASN1 encoding
22
33
  # and inserts it into the {RubySMB::SMB2::Packet::SessionSetupRequest#buffer}
23
34
  # as well as updating the {RubySMB::SMB2::Packet::SessionSetupRequest#security_buffer_length}
@@ -60,78 +60,16 @@ module RubySMB
60
60
  # Make sure we don't modify the caller's hash options
61
61
  opts = opts.dup
62
62
  opts[:filename] = opts[:filename].dup
63
- opts[:filename] = opts[:filename][1..-1] if opts[:filename].start_with? '\\'
64
- open_file(**opts)
63
+ opts[:filename] = opts[:filename][1..-1] if opts[:filename].start_with?('\\'.encode(opts[:filename].encoding))
64
+ _open(**opts)
65
65
  end
66
66
 
67
- def open_file(filename:, attributes: nil, options: nil, disposition: RubySMB::Dispositions::FILE_OPEN,
68
- impersonation: RubySMB::ImpersonationLevels::SEC_IMPERSONATE, read: true, write: false, delete: false)
69
-
70
- create_request = RubySMB::SMB2::Packet::CreateRequest.new
71
- create_request = set_header_fields(create_request)
72
-
73
- # If the user supplied file attributes, use those, otherwise set some
74
- # sane defaults.
75
- if attributes
76
- create_request.file_attributes = attributes
77
- else
78
- create_request.file_attributes.directory = 0
79
- create_request.file_attributes.normal = 1
80
- end
81
-
82
- # If the user supplied Create Options, use those, otherwise set some
83
- # sane defaults.
84
- if options
85
- create_request.create_options = options
86
- else
87
- create_request.create_options.directory_file = 0
88
- create_request.create_options.non_directory_file = 1
89
- end
90
-
91
- if read
92
- create_request.share_access.read_access = 1
93
- create_request.desired_access.read_data = 1
94
- end
95
-
96
- if write
97
- create_request.share_access.write_access = 1
98
- create_request.desired_access.write_data = 1
99
- create_request.desired_access.append_data = 1
100
- end
101
-
102
- if delete
103
- create_request.share_access.delete_access = 1
104
- create_request.desired_access.delete_access = 1
105
- end
106
-
107
- create_request.requested_oplock = 0xff
108
- create_request.impersonation_level = impersonation
109
- create_request.create_disposition = disposition
110
- create_request.name = filename
111
-
112
- raw_response = client.send_recv(create_request, encrypt: @tree_connect_encrypt_data)
113
- response = RubySMB::SMB2::Packet::CreateResponse.read(raw_response)
114
- unless response.valid?
115
- raise RubySMB::Error::InvalidPacket.new(
116
- expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
117
- expected_cmd: RubySMB::SMB2::Packet::CreateResponse::COMMAND,
118
- packet: response
119
- )
120
- end
121
- unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
122
- raise RubySMB::Error::UnexpectedStatusCode, response.status_code
123
- end
124
-
125
- case @share_type
126
- when RubySMB::SMB2::Packet::TreeConnectResponse::SMB2_SHARE_TYPE_DISK
127
- RubySMB::SMB2::File.new(name: filename, tree: self, response: response, encrypt: @tree_connect_encrypt_data)
128
- when RubySMB::SMB2::Packet::TreeConnectResponse::SMB2_SHARE_TYPE_PIPE
129
- RubySMB::SMB2::Pipe.new(name: filename, tree: self, response: response)
130
- # when RubySMB::SMB2::TreeConnectResponse::SMB2_SHARE_TYPE_PRINT
131
- # it's a printer!
132
- else
133
- raise RubySMB::Error::RubySMBError, 'Unsupported share type'
134
- end
67
+ def open_file(opts)
68
+ # Make sure we don't modify the caller's hash options
69
+ opts = opts.dup
70
+ opts[:filename] = opts[:filename].dup
71
+ opts[:filename] = opts[:filename][1..-1] if opts[:filename].start_with?('\\'.encode(opts[:filename].encoding))
72
+ _open(**opts)
135
73
  end
136
74
 
137
75
  # List `directory` on the remote share.
@@ -270,6 +208,78 @@ module RubySMB
270
208
  request.smb2_header.credits = 256
271
209
  request
272
210
  end
211
+
212
+ private
213
+
214
+ def _open(filename:, attributes: nil, options: nil, disposition: RubySMB::Dispositions::FILE_OPEN,
215
+ impersonation: RubySMB::ImpersonationLevels::SEC_IMPERSONATE, read: true, write: false, delete: false)
216
+
217
+ create_request = RubySMB::SMB2::Packet::CreateRequest.new
218
+ create_request = set_header_fields(create_request)
219
+
220
+ # If the user supplied file attributes, use those, otherwise set some
221
+ # sane defaults.
222
+ if attributes
223
+ create_request.file_attributes = attributes
224
+ else
225
+ create_request.file_attributes.directory = 0
226
+ create_request.file_attributes.normal = 1
227
+ end
228
+
229
+ # If the user supplied Create Options, use those, otherwise set some
230
+ # sane defaults.
231
+ if options
232
+ create_request.create_options = options
233
+ else
234
+ create_request.create_options.directory_file = 0
235
+ create_request.create_options.non_directory_file = 1
236
+ end
237
+
238
+ if read
239
+ create_request.share_access.read_access = 1
240
+ create_request.desired_access.read_data = 1
241
+ end
242
+
243
+ if write
244
+ create_request.share_access.write_access = 1
245
+ create_request.desired_access.write_data = 1
246
+ create_request.desired_access.append_data = 1
247
+ end
248
+
249
+ if delete
250
+ create_request.share_access.delete_access = 1
251
+ create_request.desired_access.delete_access = 1
252
+ end
253
+
254
+ create_request.requested_oplock = 0xff
255
+ create_request.impersonation_level = impersonation
256
+ create_request.create_disposition = disposition
257
+ create_request.name = filename
258
+
259
+ raw_response = client.send_recv(create_request, encrypt: @tree_connect_encrypt_data)
260
+ response = RubySMB::SMB2::Packet::CreateResponse.read(raw_response)
261
+ unless response.valid?
262
+ raise RubySMB::Error::InvalidPacket.new(
263
+ expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
264
+ expected_cmd: RubySMB::SMB2::Packet::CreateResponse::COMMAND,
265
+ packet: response
266
+ )
267
+ end
268
+ unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
269
+ raise RubySMB::Error::UnexpectedStatusCode, response.status_code
270
+ end
271
+
272
+ case @share_type
273
+ when RubySMB::SMB2::Packet::TreeConnectResponse::SMB2_SHARE_TYPE_DISK
274
+ RubySMB::SMB2::File.new(name: filename, tree: self, response: response, encrypt: @tree_connect_encrypt_data)
275
+ when RubySMB::SMB2::Packet::TreeConnectResponse::SMB2_SHARE_TYPE_PIPE
276
+ RubySMB::SMB2::Pipe.new(name: filename, tree: self, response: response)
277
+ # when RubySMB::SMB2::TreeConnectResponse::SMB2_SHARE_TYPE_PRINT
278
+ # it's a printer!
279
+ else
280
+ raise RubySMB::Error::RubySMBError, 'Unsupported share type'
281
+ end
282
+ end
273
283
  end
274
284
  end
275
285
  end
@@ -1,3 +1,3 @@
1
1
  module RubySMB
2
- VERSION = '3.0.2'.freeze
2
+ VERSION = '3.0.5'.freeze
3
3
  end
@@ -557,6 +557,16 @@ RSpec.describe RubySMB::Client do
557
557
  end
558
558
  end
559
559
 
560
+ describe '#wipe_state!' do
561
+ context 'with SMB1' do
562
+ it { expect { smb1_client.wipe_state! }.to_not raise_error }
563
+ end
564
+
565
+ context 'with SMB2' do
566
+ it { expect { smb2_client.wipe_state! }.to_not raise_error }
567
+ end
568
+ end
569
+
560
570
  describe '#logoff!' do
561
571
  context 'with SMB1' do
562
572
  let(:raw_response) { double('Raw response') }
@@ -118,6 +118,11 @@ RSpec.describe RubySMB::SMB1::Tree do
118
118
  end
119
119
  tree.open_file(filename: unicode_filename.chop)
120
120
  end
121
+
122
+ it 'removes the leading \\ from the filename if needed' do
123
+ expect(tree).to receive(:_open).with(filename: filename)
124
+ tree.open_file(filename: '\\' + filename)
125
+ end
121
126
  end
122
127
 
123
128
  describe 'flags' do
@@ -495,17 +500,17 @@ RSpec.describe RubySMB::SMB1::Tree do
495
500
  describe '#open_pipe' do
496
501
  let(:opts) { { filename: 'test', write: true } }
497
502
  before :example do
498
- allow(tree).to receive(:open_file)
503
+ allow(tree).to receive(:_open)
499
504
  end
500
505
 
501
506
  it 'calls #open_file with the provided options' do
502
507
  opts[:filename] ='\\test'
503
- expect(tree).to receive(:open_file).with(opts)
508
+ expect(tree).to receive(:_open).with(opts)
504
509
  tree.open_pipe(**opts)
505
510
  end
506
511
 
507
512
  it 'prepends the filename with \\ if needed' do
508
- expect(tree).to receive(:open_file).with(filename: '\\test', write: true)
513
+ expect(tree).to receive(:_open).with(filename: '\\test', write: true)
509
514
  tree.open_pipe(**opts)
510
515
  end
511
516
 
@@ -348,6 +348,11 @@ RSpec.describe RubySMB::SMB2::Tree do
348
348
  end
349
349
  tree.open_file(filename: filename)
350
350
  end
351
+
352
+ it 'removes the leading \\ from the filename if needed' do
353
+ expect(tree).to receive(:_open).with(filename: filename)
354
+ tree.open_file(filename: "\\".encode('UTF-16LE') + filename)
355
+ end
351
356
  end
352
357
 
353
358
  describe 'attributes' do
@@ -535,17 +540,17 @@ RSpec.describe RubySMB::SMB2::Tree do
535
540
  describe '#open_pipe' do
536
541
  let(:opts) { { filename: '\\test', write: true } }
537
542
  before :example do
538
- allow(tree).to receive(:open_file)
543
+ allow(tree).to receive(:_open)
539
544
  end
540
545
 
541
546
  it 'calls #open_file with the provided options' do
542
547
  opts[:filename] ='test'
543
- expect(tree).to receive(:open_file).with(**opts)
548
+ expect(tree).to receive(:_open).with(**opts)
544
549
  tree.open_pipe(**opts)
545
550
  end
546
551
 
547
- it 'remove the leading \\ from the filename if needed' do
548
- expect(tree).to receive(:open_file).with(filename: 'test', write: true)
552
+ it 'removes the leading \\ from the filename if needed' do
553
+ expect(tree).to receive(:_open).with(filename: 'test', write: true)
549
554
  tree.open_pipe(**opts)
550
555
  end
551
556
 
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_smb
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.2
4
+ version: 3.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Metasploit Hackers
@@ -97,7 +97,7 @@ cert_chain:
97
97
  EknWpNgVhohbot1lfVAMmIhdtOVaRVcQQixWPwprDj/ydB8ryDMDosIMcw+fkoXU
98
98
  9GJsSaSRRYQ9UUkVL27b64okU8D48m8=
99
99
  -----END CERTIFICATE-----
100
- date: 2022-02-04 00:00:00.000000000 Z
100
+ date: 2022-03-01 00:00:00.000000000 Z
101
101
  dependencies:
102
102
  - !ruby/object:Gem::Dependency
103
103
  name: redcarpet
metadata.gz.sig CHANGED
Binary file