ruby_smb 3.0.2 → 3.0.5

Sign up to get free protection for your applications and to get access to all the features.
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