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.
- checksums.yaml +5 -5
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +1 -4
- data/.travis.yml +3 -5
- data/Gemfile +6 -2
- data/examples/negotiate.rb +51 -8
- data/examples/read_file_encryption.rb +56 -0
- data/lib/ruby_smb.rb +4 -0
- data/lib/ruby_smb/client.rb +172 -16
- data/lib/ruby_smb/client/authentication.rb +27 -8
- data/lib/ruby_smb/client/encryption.rb +62 -0
- data/lib/ruby_smb/client/negotiation.rb +133 -12
- data/lib/ruby_smb/client/signing.rb +19 -0
- data/lib/ruby_smb/client/tree_connect.rb +4 -4
- data/lib/ruby_smb/client/utils.rb +8 -7
- data/lib/ruby_smb/crypto.rb +30 -0
- data/lib/ruby_smb/dispatcher/socket.rb +2 -2
- data/lib/ruby_smb/error.rb +28 -1
- data/lib/ruby_smb/smb1/commands.rb +1 -1
- data/lib/ruby_smb/smb1/file.rb +4 -4
- data/lib/ruby_smb/smb1/packet/session_setup_legacy_request.rb +1 -1
- data/lib/ruby_smb/smb1/packet/session_setup_legacy_response.rb +2 -2
- data/lib/ruby_smb/smb1/packet/session_setup_request.rb +1 -1
- data/lib/ruby_smb/smb1/packet/session_setup_response.rb +2 -2
- data/lib/ruby_smb/smb1/packet/write_andx_request.rb +1 -1
- data/lib/ruby_smb/smb1/pipe.rb +2 -2
- data/lib/ruby_smb/smb1/tree.rb +3 -3
- data/lib/ruby_smb/smb2/bit_field/session_flags.rb +2 -1
- data/lib/ruby_smb/smb2/bit_field/share_flags.rb +6 -4
- data/lib/ruby_smb/smb2/file.rb +25 -43
- data/lib/ruby_smb/smb2/negotiate_context.rb +108 -0
- data/lib/ruby_smb/smb2/packet.rb +2 -0
- data/lib/ruby_smb/smb2/packet/compression_transform_header.rb +41 -0
- data/lib/ruby_smb/smb2/packet/negotiate_request.rb +51 -14
- data/lib/ruby_smb/smb2/packet/negotiate_response.rb +49 -3
- data/lib/ruby_smb/smb2/packet/transform_header.rb +84 -0
- data/lib/ruby_smb/smb2/packet/tree_connect_request.rb +92 -6
- data/lib/ruby_smb/smb2/packet/tree_connect_response.rb +8 -26
- data/lib/ruby_smb/smb2/pipe.rb +3 -16
- data/lib/ruby_smb/smb2/smb2_header.rb +1 -1
- data/lib/ruby_smb/smb2/tree.rb +23 -17
- data/lib/ruby_smb/version.rb +1 -1
- data/ruby_smb.gemspec +3 -1
- data/spec/lib/ruby_smb/client_spec.rb +1256 -57
- data/spec/lib/ruby_smb/crypto_spec.rb +25 -0
- data/spec/lib/ruby_smb/error_spec.rb +59 -0
- data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_request_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_response_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb1/packet/session_setup_request_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb1/packet/session_setup_response_spec.rb +1 -1
- data/spec/lib/ruby_smb/smb2/bit_field/session_flags_spec.rb +9 -0
- data/spec/lib/ruby_smb/smb2/bit_field/share_flags_spec.rb +27 -0
- data/spec/lib/ruby_smb/smb2/file_spec.rb +86 -62
- data/spec/lib/ruby_smb/smb2/negotiate_context_spec.rb +332 -0
- data/spec/lib/ruby_smb/smb2/packet/compression_transform_header_spec.rb +108 -0
- data/spec/lib/ruby_smb/smb2/packet/negotiate_request_spec.rb +138 -3
- data/spec/lib/ruby_smb/smb2/packet/negotiate_response_spec.rb +120 -2
- data/spec/lib/ruby_smb/smb2/packet/transform_header_spec.rb +220 -0
- data/spec/lib/ruby_smb/smb2/packet/tree_connect_request_spec.rb +339 -9
- data/spec/lib/ruby_smb/smb2/packet/tree_connect_response_spec.rb +3 -30
- data/spec/lib/ruby_smb/smb2/pipe_spec.rb +0 -40
- data/spec/lib/ruby_smb/smb2/smb2_header_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb2/tree_spec.rb +53 -8
- metadata +124 -75
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: c76e06c7e09d56565d9253523f519ac12eccc193201b8f4dcd99a8ed07b37991
|
4
|
+
data.tar.gz: b533650ae3c9c16e75f2c5b987f53055894f0a99e87151dc5838922882d52ade
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 82cbd9940e99aea529d23046e26b05f92bcfff3e03ee8df852b0c44f7b50ef517e4549f3e46b6890f55f0c5fc34e624821701cea6b807c008d1cbb03f0e4f242
|
7
|
+
data.tar.gz: 7d91fa41048597a8c376af79b32374588ffdb79b114bacfb1b43e482f22298f6439fe92a13753abffeb7b329ba3f09f4131a23bf8e63d62c5f9f85da40c5f5e0
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data.tar.gz.sig
CHANGED
@@ -1,4 +1 @@
|
|
1
|
-
|
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�_࠘4�m��ң��1�Q��S�"B'��M���=�g_|i�ЈJz�����������MN-��s��O��&窓�Ӿ�~��*E�l�a�A<>��F�%�_�?�����n�1R�~��z
|
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
@@ -1,11 +1,15 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
2
|
gemspec
|
3
3
|
|
4
|
-
|
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
|
12
|
+
gem "coveralls", '~>0.8.23', :require => false
|
9
13
|
# Testing
|
10
14
|
gem 'rspec'
|
11
15
|
# Coverage reports
|
data/examples/negotiate.rb
CHANGED
@@ -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
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
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
|
data/lib/ruby_smb.rb
CHANGED
@@ -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
|
data/lib/ruby_smb/client.rb
CHANGED
@@ -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
|
26
|
-
|
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
|
-
|
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
|
-
|
262
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
353
|
-
|
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
|