ruby_smb 1.1.0 → 2.0.0

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.
Files changed (65) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +1 -4
  4. data/.travis.yml +3 -5
  5. data/Gemfile +6 -2
  6. data/examples/negotiate.rb +51 -8
  7. data/examples/read_file_encryption.rb +56 -0
  8. data/lib/ruby_smb.rb +4 -0
  9. data/lib/ruby_smb/client.rb +172 -16
  10. data/lib/ruby_smb/client/authentication.rb +27 -8
  11. data/lib/ruby_smb/client/encryption.rb +62 -0
  12. data/lib/ruby_smb/client/negotiation.rb +133 -12
  13. data/lib/ruby_smb/client/signing.rb +19 -0
  14. data/lib/ruby_smb/client/tree_connect.rb +4 -4
  15. data/lib/ruby_smb/client/utils.rb +8 -7
  16. data/lib/ruby_smb/crypto.rb +30 -0
  17. data/lib/ruby_smb/dispatcher/socket.rb +2 -2
  18. data/lib/ruby_smb/error.rb +28 -1
  19. data/lib/ruby_smb/smb1/commands.rb +1 -1
  20. data/lib/ruby_smb/smb1/file.rb +4 -4
  21. data/lib/ruby_smb/smb1/packet/session_setup_legacy_request.rb +1 -1
  22. data/lib/ruby_smb/smb1/packet/session_setup_legacy_response.rb +2 -2
  23. data/lib/ruby_smb/smb1/packet/session_setup_request.rb +1 -1
  24. data/lib/ruby_smb/smb1/packet/session_setup_response.rb +2 -2
  25. data/lib/ruby_smb/smb1/packet/write_andx_request.rb +1 -1
  26. data/lib/ruby_smb/smb1/pipe.rb +2 -2
  27. data/lib/ruby_smb/smb1/tree.rb +3 -3
  28. data/lib/ruby_smb/smb2/bit_field/session_flags.rb +2 -1
  29. data/lib/ruby_smb/smb2/bit_field/share_flags.rb +6 -4
  30. data/lib/ruby_smb/smb2/file.rb +25 -43
  31. data/lib/ruby_smb/smb2/negotiate_context.rb +108 -0
  32. data/lib/ruby_smb/smb2/packet.rb +2 -0
  33. data/lib/ruby_smb/smb2/packet/compression_transform_header.rb +41 -0
  34. data/lib/ruby_smb/smb2/packet/negotiate_request.rb +51 -14
  35. data/lib/ruby_smb/smb2/packet/negotiate_response.rb +49 -3
  36. data/lib/ruby_smb/smb2/packet/transform_header.rb +84 -0
  37. data/lib/ruby_smb/smb2/packet/tree_connect_request.rb +92 -6
  38. data/lib/ruby_smb/smb2/packet/tree_connect_response.rb +8 -26
  39. data/lib/ruby_smb/smb2/pipe.rb +3 -16
  40. data/lib/ruby_smb/smb2/smb2_header.rb +1 -1
  41. data/lib/ruby_smb/smb2/tree.rb +23 -17
  42. data/lib/ruby_smb/version.rb +1 -1
  43. data/ruby_smb.gemspec +3 -1
  44. data/spec/lib/ruby_smb/client_spec.rb +1256 -57
  45. data/spec/lib/ruby_smb/crypto_spec.rb +25 -0
  46. data/spec/lib/ruby_smb/error_spec.rb +59 -0
  47. data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_request_spec.rb +2 -2
  48. data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_response_spec.rb +2 -2
  49. data/spec/lib/ruby_smb/smb1/packet/session_setup_request_spec.rb +2 -2
  50. data/spec/lib/ruby_smb/smb1/packet/session_setup_response_spec.rb +1 -1
  51. data/spec/lib/ruby_smb/smb2/bit_field/session_flags_spec.rb +9 -0
  52. data/spec/lib/ruby_smb/smb2/bit_field/share_flags_spec.rb +27 -0
  53. data/spec/lib/ruby_smb/smb2/file_spec.rb +86 -62
  54. data/spec/lib/ruby_smb/smb2/negotiate_context_spec.rb +332 -0
  55. data/spec/lib/ruby_smb/smb2/packet/compression_transform_header_spec.rb +108 -0
  56. data/spec/lib/ruby_smb/smb2/packet/negotiate_request_spec.rb +138 -3
  57. data/spec/lib/ruby_smb/smb2/packet/negotiate_response_spec.rb +120 -2
  58. data/spec/lib/ruby_smb/smb2/packet/transform_header_spec.rb +220 -0
  59. data/spec/lib/ruby_smb/smb2/packet/tree_connect_request_spec.rb +339 -9
  60. data/spec/lib/ruby_smb/smb2/packet/tree_connect_response_spec.rb +3 -30
  61. data/spec/lib/ruby_smb/smb2/pipe_spec.rb +0 -40
  62. data/spec/lib/ruby_smb/smb2/smb2_header_spec.rb +2 -2
  63. data/spec/lib/ruby_smb/smb2/tree_spec.rb +53 -8
  64. metadata +124 -75
  65. metadata.gz.sig +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 3fe1b1b7bdd2516bd388f86c020a9fa99b70faa0
4
- data.tar.gz: aa11ed030f543c0d83b61929d10c35eea20a8455
2
+ SHA256:
3
+ metadata.gz: c76e06c7e09d56565d9253523f519ac12eccc193201b8f4dcd99a8ed07b37991
4
+ data.tar.gz: b533650ae3c9c16e75f2c5b987f53055894f0a99e87151dc5838922882d52ade
5
5
  SHA512:
6
- metadata.gz: 92cc9708588a610104f8bacc8430fedd537f2ba283bb323b791ff45005a43417cf9570ce63f658b7a38c566570c2b651adb17b6750b3b117279552efaf138cab
7
- data.tar.gz: 071db1651283d080b2adc728cd1f2225e051d52f86608f9f54dd6d79d39330127c3b5179d143ec9e5e155d132af1e128c4c9ca42744264caec8b249e0b0c86ec
6
+ metadata.gz: 82cbd9940e99aea529d23046e26b05f92bcfff3e03ee8df852b0c44f7b50ef517e4549f3e46b6890f55f0c5fc34e624821701cea6b807c008d1cbb03f0e4f242
7
+ data.tar.gz: 7d91fa41048597a8c376af79b32374588ffdb79b114bacfb1b43e482f22298f6439fe92a13753abffeb7b329ba3f09f4131a23bf8e63d62c5f9f85da40c5f5e0
Binary file
data.tar.gz.sig CHANGED
@@ -1,4 +1 @@
1
- VQ��kDfڸZ
2
- =���U<�5/�Am�샜(��Ύm���pe�%u}�TQGh�0,�y��/��E#�M���W(A�˛\Y�M��doF�
3
- �6�T����U��6��1F�ex@��pg�r�F]W�2WM?�%�z�c# 2��*U.��*
4
- }�$��h@�uN�q�'�����c�
1
+ Zp����xD�&Ǻ�(�T�q�Q�6Q��h_࠘4m��ң��1�Q��S�"B'��M���=�g_|i�ЈJz�����������MN-��s��O��&窓�Ӿ�~��*E�l�a�A<>��F�%�_�?�����n�1R�~��z
@@ -1,8 +1,6 @@
1
1
  language: ruby
2
2
  sudo: false
3
3
  rvm:
4
- - '2.3.8'
5
- - '2.4.5'
6
- - '2.5.3'
7
- - '2.6.1'
8
- - 'jruby-9000'
4
+ - '2.5.8'
5
+ - '2.6.6'
6
+ - '2.7.0'
data/Gemfile CHANGED
@@ -1,11 +1,15 @@
1
1
  source 'https://rubygems.org'
2
2
  gemspec
3
3
 
4
- gem 'pry'
4
+ group :development do
5
+ # for development and testing purposes
6
+ gem 'pry-byebug'
7
+ gem 'pry-rescue'
8
+ end
5
9
 
6
10
  group :test do
7
11
  # simplecov test formatter and uploader for Coveralls.io
8
- gem 'coveralls', require: false
12
+ gem "coveralls", '~>0.8.23', :require => false
9
13
  # Testing
10
14
  gem 'rspec'
11
15
  # Coverage reports
@@ -7,18 +7,61 @@
7
7
  require 'bundler/setup'
8
8
  require 'ruby_smb'
9
9
 
10
- def run_negotiation(address, smb1, smb2)
10
+ def run_negotiation(address, smb1, smb2, smb3, opts = {})
11
11
  # Create our socket and add it to the dispatcher
12
12
  sock = TCPSocket.new address, 445
13
13
  dispatcher = RubySMB::Dispatcher::Socket.new(sock)
14
14
 
15
- client = RubySMB::Client.new(dispatcher, smb1: smb1, smb2: smb2, username: 'msfadmin', password: 'msfadmin')
15
+ client = RubySMB::Client.new(dispatcher, smb1: smb1, smb2: smb2, smb3: smb3, username: 'msfadmin', password: 'msfadmin')
16
16
  client.negotiate
17
17
  end
18
18
 
19
- # Negotiate with both SMB1 and SMB2 enabled on the client
20
- run_negotiation(ARGV[0], true, true)
21
- # Negotiate with only SMB1 enabled
22
- run_negotiation(ARGV[0], true, false)
23
- # Negotiate with only SMB2 enabled
24
- run_negotiation(ARGV[0], false, true)
19
+ begin
20
+ puts "Negotiate with only SMB1 enabled..."
21
+ puts " Negotiated version: #{run_negotiation(ARGV[0], true, false, false)}"
22
+ rescue RubySMB::Error::RubySMBError => e
23
+ puts "Error: #{e.message}"
24
+ end
25
+
26
+ begin
27
+ puts "Negotiate with only SMB2 enabled..."
28
+ puts " Negotiated version: #{run_negotiation(ARGV[0], false, true, false)}"
29
+ rescue RubySMB::Error::RubySMBError => e
30
+ puts "Error: #{e.message}"
31
+ end
32
+
33
+ begin
34
+ puts "Negotiate with only SMB3 enabled..."
35
+ puts " Negotiated version: #{run_negotiation(ARGV[0], false, false, true)}"
36
+ rescue RubySMB::Error::RubySMBError => e
37
+ puts "Error: #{e.message}"
38
+ end
39
+
40
+ begin
41
+ puts "Negotiate with both SMB1 and SMB2 enabled on the client..."
42
+ puts " Negotiated version: #{run_negotiation(ARGV[0], true, true, false)}"
43
+ rescue RubySMB::Error::RubySMBError => e
44
+ puts "Error: #{e.message}"
45
+ end
46
+
47
+ begin
48
+ puts "Negotiate with both SMB2 and SMB3 enabled on the client..."
49
+ puts " Negotiated version: #{run_negotiation(ARGV[0], false, true, true)}"
50
+ rescue RubySMB::Error::RubySMBError => e
51
+ puts "Error: #{e.message}"
52
+ end
53
+
54
+ begin
55
+ puts "Negotiate with both SMB1 and SMB3 enabled on the client..."
56
+ puts " Negotiated version: #{run_negotiation(ARGV[0], true, false, true)}"
57
+ rescue RubySMB::Error::RubySMBError => e
58
+ puts "Error: #{e.message}"
59
+ end
60
+
61
+ begin
62
+ puts "Negotiate with SMB1, SMB2 and SMB3 enabled on the client..."
63
+ puts " Negotiated version: #{run_negotiation(ARGV[0], true, true, true)}"
64
+ rescue RubySMB::Error::RubySMBError => e
65
+ puts "Error: #{e.message}"
66
+ end
67
+
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/ruby
2
+
3
+ # This example script is used for testing the reading of a file.
4
+ # It will attempt to connect to a specific share and then read a specified file.
5
+ # Example usage: ruby read_file.rb 192.168.172.138 msfadmin msfadmin TEST_SHARE short.txt
6
+ # This will try to connect to \\192.168.172.138\TEST_SHARE with the msfadmin:msfadmin credentials
7
+ # and read the file short.txt
8
+
9
+ require 'bundler/setup'
10
+ require 'ruby_smb'
11
+
12
+ address = ARGV[0]
13
+ username = ARGV[1]
14
+ password = ARGV[2]
15
+ share = ARGV[3]
16
+ filename = ARGV[4]
17
+ path = "\\\\#{address}\\#{share}"
18
+
19
+ sock = TCPSocket.new address, 445
20
+ dispatcher = RubySMB::Dispatcher::Socket.new(sock)
21
+
22
+ # To require encryption on the server, run this in an elevated Powershell:
23
+ # C:\> Set-SmbServerConfiguration -EncryptData $true
24
+
25
+ # To enable per-share encryption on the server, run this in an elevated Powershell:
26
+ # C:\ Set-SmbServerConfiguration -EncryptData $false
27
+ # C:\ Set-SmbShare -Name <share name> -EncryptData 1
28
+
29
+ # For this encryption to work, it has to be SMBv3. By only setting smb3 to true,
30
+ # we make sure the server will negotiate this version, if it supports it
31
+ opts = {
32
+ smb1: false,
33
+ smb2: false,
34
+ smb3: true,
35
+ username: username,
36
+ password: password,
37
+ }
38
+
39
+ # By default, the client uses encryption even if it is not required by the server. Disable this by setting always_encrypt to false
40
+ #opts[:always_encrypt] = false
41
+
42
+ client = RubySMB::Client.new(dispatcher, opts)
43
+ protocol = client.negotiate
44
+ status = client.authenticate
45
+
46
+ begin
47
+ tree = client.tree_connect(path)
48
+ rescue StandardError => e
49
+ puts "Failed to connect to #{path}: #{e.message}"
50
+ end
51
+
52
+ file = tree.open_file(filename: filename)
53
+
54
+ data = file.read
55
+ puts data
56
+ file.close
@@ -1,6 +1,9 @@
1
1
  require 'bindata'
2
2
  require 'net/ntlm'
3
3
  require 'net/ntlm/client'
4
+ require 'openssl'
5
+ require 'openssl/ccm'
6
+ require 'openssl/cmac'
4
7
  require 'windows_error'
5
8
  require 'windows_error/nt_status'
6
9
  # A packet parsing and manipulation library for the SMB1 and SMB2 protocols
@@ -22,4 +25,5 @@ module RubySMB
22
25
  require 'ruby_smb/smb2'
23
26
  require 'ruby_smb/smb1'
24
27
  require 'ruby_smb/client'
28
+ require 'ruby_smb/crypto'
25
29
  end
@@ -9,6 +9,7 @@ module RubySMB
9
9
  require 'ruby_smb/client/echo'
10
10
  require 'ruby_smb/client/utils'
11
11
  require 'ruby_smb/client/winreg'
12
+ require 'ruby_smb/client/encryption'
12
13
 
13
14
  include RubySMB::Client::Negotiation
14
15
  include RubySMB::Client::Authentication
@@ -17,13 +18,20 @@ module RubySMB
17
18
  include RubySMB::Client::Echo
18
19
  include RubySMB::Client::Utils
19
20
  include RubySMB::Client::Winreg
21
+ include RubySMB::Client::Encryption
20
22
 
21
23
  # The Default SMB1 Dialect string used in an SMB1 Negotiate Request
22
24
  SMB1_DIALECT_SMB1_DEFAULT = 'NT LM 0.12'.freeze
23
25
  # The Default SMB2 Dialect string used in an SMB1 Negotiate Request
24
26
  SMB1_DIALECT_SMB2_DEFAULT = 'SMB 2.002'.freeze
25
- # Dialect value for SMB2 Default (Version 2.02)
26
- SMB2_DIALECT_DEFAULT = 0x0202
27
+ # The SMB2 wildcard revision number Dialect string used in an SMB1 Negotiate Request
28
+ # It indicates that the server implements SMB 2.1 or future dialect revisions
29
+ # Note that this must be used for SMB3
30
+ SMB1_DIALECT_SMB2_WILDCARD = 'SMB 2.???'.freeze
31
+ # Dialect values for SMB2
32
+ SMB2_DIALECT_DEFAULT = ['0x0202', '0x0210']
33
+ # Dialect values for SMB3
34
+ SMB3_DIALECT_DEFAULT = ['0x0300', '0x0302', '0x0311']
27
35
  # The default maximum size of a SMB message that the Client accepts (in bytes)
28
36
  MAX_BUFFER_SIZE = 64512
29
37
  # The default maximum size of a SMB message that the Server accepts (in bytes)
@@ -135,6 +143,11 @@ module RubySMB
135
143
  # @return [Boolean]
136
144
  attr_accessor :smb2
137
145
 
146
+ # Whether or not the Client should support SMB3
147
+ # @!attribute [rw] smb3
148
+ # @return [Boolean]
149
+ attr_accessor :smb3
150
+
138
151
  # Tracks the current SMB2 Message ID that keeps communication in sync
139
152
  # @!attribute [rw] smb2_message_id
140
153
  # @return [Integer]
@@ -178,12 +191,61 @@ module RubySMB
178
191
  # @return [Integer]
179
192
  attr_accessor :server_max_transact_size
180
193
 
194
+ # The algorithm to compute the preauthentication integrity hash (SMB 3.1.1).
195
+ # @!attribute [rw] preauth_integrity_hash_algorithm
196
+ # @return [String]
197
+ attr_accessor :preauth_integrity_hash_algorithm
198
+
199
+ # The preauthentication integrity hash value (SMB 3.1.1).
200
+ # @!attribute [rw] preauth_integrity_hash_value
201
+ # @return [String]
202
+ attr_accessor :preauth_integrity_hash_value
203
+
204
+ # The algorithm for encryption (SMB 3.x).
205
+ # @!attribute [rw] encryption_algorithm
206
+ # @return [String]
207
+ attr_accessor :encryption_algorithm
208
+
209
+ # The client encryption key (SMB 3.x).
210
+ # @!attribute [rw] client_encryption_key
211
+ # @return [String]
212
+ attr_accessor :client_encryption_key
213
+
214
+ # The server encryption key (SMB 3.x).
215
+ # @!attribute [rw] server_encryption_key
216
+ # @return [String]
217
+ attr_accessor :server_encryption_key
218
+
219
+ # Whether or not encryption is required (SMB 3.x)
220
+ # @!attribute [rw] encryption_required
221
+ # @return [Boolean]
222
+ attr_accessor :encryption_required
223
+
224
+ # The encryption algorithms supported by the server (SMB 3.x).
225
+ # @!attribute [rw] server_encryption_algorithms
226
+ # @return [Array<Integer>] list of supported encryption algorithms
227
+ # (constants defined in RubySMB::SMB2::EncryptionCapabilities)
228
+ attr_accessor :server_encryption_algorithms
229
+
230
+ # The compression algorithms supported by the server (SMB 3.x).
231
+ # @!attribute [rw] server_compression_algorithms
232
+ # @return [Array<Integer>] list of supported compression algorithms
233
+ # (constants defined in RubySMB::SMB2::CompressionCapabilities)
234
+ attr_accessor :server_compression_algorithms
235
+
236
+ # The SMB version that has been successfully negotiated. This value is only
237
+ # set after the NEGOTIATE handshake has been performed.
238
+ # @!attribute [rw] negotiated_smb_version
239
+ # @return [Integer] the negotiated SMB version
240
+ attr_accessor :negotiated_smb_version
241
+
181
242
  # @param dispatcher [RubySMB::Dispatcher::Socket] the packet dispatcher to use
182
243
  # @param smb1 [Boolean] whether or not to enable SMB1 support
183
244
  # @param smb2 [Boolean] whether or not to enable SMB2 support
184
- def initialize(dispatcher, smb1: true, smb2: true, username:, password:, domain: '.', local_workstation: 'WORKSTATION')
245
+ # @param smb3 [Boolean] whether or not to enable SMB3 support
246
+ def initialize(dispatcher, smb1: true, smb2: true, smb3: true, username:, password:, domain: '.', local_workstation: 'WORKSTATION', always_encrypt: true)
185
247
  raise ArgumentError, 'No Dispatcher provided' unless dispatcher.is_a? RubySMB::Dispatcher::Base
186
- if smb1 == false && smb2 == false
248
+ if smb1 == false && smb2 == false && smb3 == false
187
249
  raise ArgumentError, 'You must enable at least one Protocol'
188
250
  end
189
251
  @dispatcher = dispatcher
@@ -196,6 +258,7 @@ module RubySMB
196
258
  @signing_required = false
197
259
  @smb1 = smb1
198
260
  @smb2 = smb2
261
+ @smb3 = smb3
199
262
  @username = username.encode('utf-8') || ''.encode('utf-8')
200
263
  @max_buffer_size = MAX_BUFFER_SIZE
201
264
  # These sizes will be modifed during negotiation
@@ -204,6 +267,9 @@ module RubySMB
204
267
  @server_max_write_size = RubySMB::SMB2::File::MAX_PACKET_SIZE
205
268
  @server_max_transact_size = RubySMB::SMB2::File::MAX_PACKET_SIZE
206
269
 
270
+ # SMB 3.x options
271
+ @encryption_required = always_encrypt
272
+
207
273
  negotiate_version_flag = 0x02000000
208
274
  flags = Net::NTLM::Client::DEFAULT_FLAGS |
209
275
  Net::NTLM::FLAGS[:TARGET_INFO] |
@@ -243,7 +309,7 @@ module RubySMB
243
309
  # @param data [String] the data the server should echo back (ignored in SMB2)
244
310
  # @return [WindowsError::ErrorCode] the NTStatus of the last response received
245
311
  def echo(count: 1, data: '')
246
- response = if smb2
312
+ response = if smb2 || smb3
247
313
  smb2_echo
248
314
  else
249
315
  smb1_echo(count: count, data: data)
@@ -258,10 +324,8 @@ module RubySMB
258
324
  # @param packet [RubySMB::GenericPacket] the packet to set the message id for
259
325
  # @return [RubySMB::GenericPacket] the modified packet
260
326
  def increment_smb_message_id(packet)
261
- if packet.smb2_header.message_id.zero? && smb2_message_id != 0
262
- packet.smb2_header.message_id = smb2_message_id
263
- self.smb2_message_id += 1
264
- end
327
+ packet.smb2_header.message_id = smb2_message_id
328
+ self.smb2_message_id += 1
265
329
  packet
266
330
  end
267
331
 
@@ -301,7 +365,7 @@ module RubySMB
301
365
  # @return [WindowsError::ErrorCode] the NTStatus of the response
302
366
  # @raise [RubySMB::Error::InvalidPacket] if the response packet is not a LogoffResponse packet
303
367
  def logoff!
304
- if smb2
368
+ if smb2 || smb3
305
369
  request = RubySMB::SMB2::Packet::LogoffRequest.new
306
370
  raw_response = send_recv(request)
307
371
  response = RubySMB::SMB2::Packet::LogoffResponse.read(raw_response)
@@ -335,8 +399,9 @@ module RubySMB
335
399
  #
336
400
  # @param packet [RubySMB::GenericPacket] the request to be sent
337
401
  # @return [String] the raw response data received
338
- def send_recv(packet)
339
- case packet.packet_smb_version
402
+ def send_recv(packet, encrypt: false)
403
+ version = packet.packet_smb_version
404
+ case version
340
405
  when 'SMB1'
341
406
  packet.smb_header.uid = user_id if user_id
342
407
  packet = smb1_sign(packet)
@@ -344,25 +409,103 @@ module RubySMB
344
409
  packet = increment_smb_message_id(packet)
345
410
  packet.smb2_header.session_id = session_id
346
411
  unless packet.is_a?(RubySMB::SMB2::Packet::SessionSetupRequest)
347
- packet = smb2_sign(packet)
412
+ if self.smb2
413
+ packet = smb2_sign(packet)
414
+ elsif self.smb3
415
+ packet = smb3_sign(packet)
416
+ end
348
417
  end
349
418
  else
350
419
  packet = packet
351
420
  end
352
- dispatcher.send_packet(packet)
353
- raw_response = dispatcher.recv_packet
421
+
422
+ if can_be_encrypted?(packet) && encryption_supported? && (@encryption_required || encrypt)
423
+ send_encrypt(packet)
424
+ raw_response = recv_encrypt
425
+ loop do
426
+ break unless is_status_pending?(raw_response)
427
+ sleep 1
428
+ raw_response = recv_encrypt
429
+ end
430
+ else
431
+ dispatcher.send_packet(packet)
432
+ raw_response = dispatcher.recv_packet
433
+ loop do
434
+ break unless is_status_pending?(raw_response)
435
+ sleep 1
436
+ raw_response = dispatcher.recv_packet
437
+ end unless version == 'SMB1'
438
+ end
354
439
 
355
440
  self.sequence_counter += 1 if signing_required && !session_key.empty?
356
441
  raw_response
357
442
  end
358
443
 
444
+ # Check if the response is an asynchronous operation with STATUS_PENDING
445
+ # status code.
446
+ #
447
+ # @param raw_response [String] the raw response packet
448
+ # @return [Boolean] true if it is a status pending operation, false otherwise
449
+ def is_status_pending?(raw_response)
450
+ smb2_header = RubySMB::SMB2::SMB2Header.read(raw_response)
451
+ value = smb2_header.nt_status.value
452
+ status_code = WindowsError::NTStatus.find_by_retval(value).first
453
+ status_code == WindowsError::NTStatus::STATUS_PENDING &&
454
+ smb2_header.flags.async_command == 1
455
+ end
456
+
457
+ # Check if the request packet can be encrypted. Per the SMB spec,
458
+ # SessionSetupRequest and NegotiateRequest must not be encrypted.
459
+ #
460
+ # @param packet [RubySMB::GenericPacket] the request packet
461
+ # @return [Boolean] true if the packet can be encrypted
462
+ def can_be_encrypted?(packet)
463
+ [RubySMB::SMB2::Packet::SessionSetupRequest, RubySMB::SMB2::Packet::NegotiateRequest].none? do |klass|
464
+ packet.is_a?(klass)
465
+ end
466
+ end
467
+
468
+ # Check if the current dialect support encryption.
469
+ #
470
+ # @return [Boolean] true if encryption is supported
471
+ def encryption_supported?
472
+ ['0x0300', '0x0302', '0x0311'].include?(@dialect)
473
+ end
474
+
475
+ # Encrypt and send a packet
476
+ def send_encrypt(packet)
477
+ begin
478
+ transform_request = smb3_encrypt(packet.to_binary_s)
479
+ rescue RubySMB::Error::RubySMBError => e
480
+ raise RubySMB::Error::EncryptionError, "Error while encrypting #{packet.class.name} packet (SMB #{@dialect}): #{e}"
481
+ end
482
+ dispatcher.send_packet(transform_request)
483
+ end
484
+
485
+ # Receives the raw response through the Dispatcher and decrypt the packet.
486
+ #
487
+ # @return [String] the raw unencrypted packet
488
+ def recv_encrypt
489
+ raw_response = dispatcher.recv_packet
490
+ begin
491
+ transform_response = RubySMB::SMB2::Packet::TransformHeader.read(raw_response)
492
+ rescue IOError
493
+ raise RubySMB::Error::InvalidPacket, 'Not a SMB2 TransformHeader packet'
494
+ end
495
+ begin
496
+ smb3_decrypt(transform_response)
497
+ rescue RubySMB::Error::RubySMBError => e
498
+ raise RubySMB::Error::EncryptionError, "Error while decrypting #{transform_response.class.name} packet (SMB #@dialect}): #{e}"
499
+ end
500
+ end
501
+
359
502
  # Connects to the supplied share
360
503
  #
361
504
  # @param share [String] the path to the share in `\\server\share_name` format
362
505
  # @return [RubySMB::SMB1::Tree] if talking over SMB1
363
506
  # @return [RubySMB::SMB2::Tree] if talking over SMB2
364
507
  def tree_connect(share)
365
- connected_tree = if smb2
508
+ connected_tree = if smb2 || smb3
366
509
  smb2_tree_connect(share)
367
510
  else
368
511
  smb1_tree_connect(share)
@@ -392,6 +535,8 @@ module RubySMB
392
535
  self.session_key = ''
393
536
  self.sequence_counter = 0
394
537
  self.smb2_message_id = 0
538
+ self.client_encryption_key = nil
539
+ self.server_encryption_key = nil
395
540
  end
396
541
 
397
542
  # Requests a NetBIOS Session Service using the provided name.
@@ -434,5 +579,16 @@ module RubySMB
434
579
  session_request
435
580
  end
436
581
 
582
+ def update_preauth_hash(data)
583
+ unless @preauth_integrity_hash_algorithm
584
+ raise RubySMB::Error::EncryptionError.new(
585
+ 'Cannot compute the Preauth Integrity Hash value: Preauth Integrity Hash Algorithm is nil'
586
+ )
587
+ end
588
+ @preauth_integrity_hash_value = OpenSSL::Digest.digest(
589
+ @preauth_integrity_hash_algorithm,
590
+ @preauth_integrity_hash_value + data.to_binary_s
591
+ )
592
+ end
437
593
  end
438
594
  end