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
@@ -175,7 +175,7 @@ module RubySMB
|
|
175
175
|
|
176
176
|
status_code = packet.status_code
|
177
177
|
unless status_code.name == 'STATUS_MORE_PROCESSING_REQUIRED'
|
178
|
-
raise RubySMB::Error::UnexpectedStatusCode, status_code
|
178
|
+
raise RubySMB::Error::UnexpectedStatusCode, status_code
|
179
179
|
end
|
180
180
|
|
181
181
|
packet
|
@@ -201,6 +201,9 @@ module RubySMB
|
|
201
201
|
def smb2_authenticate
|
202
202
|
response = smb2_ntlmssp_negotiate
|
203
203
|
challenge_packet = smb2_ntlmssp_challenge_packet(response)
|
204
|
+
if @dialect == '0x0311'
|
205
|
+
update_preauth_hash(challenge_packet)
|
206
|
+
end
|
204
207
|
@session_id = challenge_packet.smb2_header.session_id
|
205
208
|
type2_b64_message = smb2_type2_message(challenge_packet)
|
206
209
|
type3_message = @ntlm_client.init_context(type2_b64_message)
|
@@ -213,6 +216,16 @@ module RubySMB
|
|
213
216
|
raw = smb2_ntlmssp_authenticate(type3_message, @session_id)
|
214
217
|
response = smb2_ntlmssp_final_packet(raw)
|
215
218
|
|
219
|
+
if @smb3 && !@encryption_required && response.session_flags.encrypt_data == 1
|
220
|
+
@encryption_required = true
|
221
|
+
end
|
222
|
+
######
|
223
|
+
# DEBUG
|
224
|
+
#puts "Session ID = #{@session_id.to_binary_s.each_byte.map {|e| '%02x' % e}.join}"
|
225
|
+
#puts "Session key = #{@session_key.each_byte.map {|e| '%02x' % e}.join}"
|
226
|
+
#puts "PreAuthHash = #{@preauth_integrity_hash_value.each_byte.map {|e| '%02x' % e}.join}" if @preauth_integrity_hash_value
|
227
|
+
######
|
228
|
+
|
216
229
|
response.status_code
|
217
230
|
end
|
218
231
|
|
@@ -245,7 +258,7 @@ module RubySMB
|
|
245
258
|
|
246
259
|
status_code = packet.status_code
|
247
260
|
unless status_code.name == 'STATUS_MORE_PROCESSING_REQUIRED'
|
248
|
-
raise RubySMB::Error::UnexpectedStatusCode, status_code
|
261
|
+
raise RubySMB::Error::UnexpectedStatusCode, status_code
|
249
262
|
end
|
250
263
|
packet
|
251
264
|
end
|
@@ -256,7 +269,11 @@ module RubySMB
|
|
256
269
|
# @return [String] the binary string response from the server
|
257
270
|
def smb2_ntlmssp_negotiate
|
258
271
|
packet = smb2_ntlmssp_negotiate_packet
|
259
|
-
send_recv(packet)
|
272
|
+
response = send_recv(packet)
|
273
|
+
if @dialect == '0x0311'
|
274
|
+
update_preauth_hash(packet)
|
275
|
+
end
|
276
|
+
response
|
260
277
|
end
|
261
278
|
|
262
279
|
# Creates the {RubySMB::SMB2::Packet::SessionSetupRequest} packet
|
@@ -268,10 +285,7 @@ module RubySMB
|
|
268
285
|
type1_message = ntlm_client.init_context
|
269
286
|
packet = RubySMB::SMB2::Packet::SessionSetupRequest.new
|
270
287
|
packet.set_type1_blob(type1_message.serialize)
|
271
|
-
|
272
|
-
# the Message ID can be out of sync at this point so we re-synch it here.
|
273
|
-
packet.smb2_header.message_id = 1
|
274
|
-
self.smb2_message_id = 2
|
288
|
+
packet.security_mode.signing_enabled = 1
|
275
289
|
packet
|
276
290
|
end
|
277
291
|
|
@@ -294,7 +308,11 @@ module RubySMB
|
|
294
308
|
# @return [String] the raw binary response from the server
|
295
309
|
def smb2_ntlmssp_authenticate(type3_message, user_id)
|
296
310
|
packet = smb2_ntlmssp_auth_packet(type3_message, user_id)
|
297
|
-
send_recv(packet)
|
311
|
+
response = send_recv(packet)
|
312
|
+
if @dialect == '0x0311'
|
313
|
+
update_preauth_hash(packet)
|
314
|
+
end
|
315
|
+
response
|
298
316
|
end
|
299
317
|
|
300
318
|
# Generates the {RubySMB::SMB2::Packet::SessionSetupRequest} packet
|
@@ -307,6 +325,7 @@ module RubySMB
|
|
307
325
|
packet = RubySMB::SMB2::Packet::SessionSetupRequest.new
|
308
326
|
packet.smb2_header.session_id = session_id
|
309
327
|
packet.set_type3_blob(type3_message.serialize)
|
328
|
+
packet.security_mode.signing_enabled = 1
|
310
329
|
packet
|
311
330
|
end
|
312
331
|
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module RubySMB
|
2
|
+
class Client
|
3
|
+
# Contains the methods for handling encryption / decryption
|
4
|
+
module Encryption
|
5
|
+
def smb3_encrypt(data)
|
6
|
+
unless @client_encryption_key
|
7
|
+
case @dialect
|
8
|
+
when '0x0300', '0x0302'
|
9
|
+
@client_encryption_key = RubySMB::Crypto::KDF.counter_mode(
|
10
|
+
@session_key,
|
11
|
+
"SMB2AESCCM\x00",
|
12
|
+
"ServerIn \x00"
|
13
|
+
)
|
14
|
+
when '0x0311'
|
15
|
+
@client_encryption_key = RubySMB::Crypto::KDF.counter_mode(
|
16
|
+
@session_key,
|
17
|
+
"SMBC2SCipherKey\x00",
|
18
|
+
@preauth_integrity_hash_value
|
19
|
+
)
|
20
|
+
else
|
21
|
+
raise RubySMB::Error::EncryptionError.new('Dialect is incompatible with SMBv3 encryption')
|
22
|
+
end
|
23
|
+
######
|
24
|
+
# DEBUG
|
25
|
+
#puts "Client encryption key = #{@client_encryption_key.each_byte.map {|e| '%02x' % e}.join}"
|
26
|
+
######
|
27
|
+
end
|
28
|
+
|
29
|
+
th = RubySMB::SMB2::Packet::TransformHeader.new(flags: 1, session_id: @session_id)
|
30
|
+
th.encrypt(data, @client_encryption_key, algorithm: @encryption_algorithm)
|
31
|
+
th
|
32
|
+
end
|
33
|
+
|
34
|
+
def smb3_decrypt(th)
|
35
|
+
unless @server_encryption_key
|
36
|
+
case @dialect
|
37
|
+
when '0x0300', '0x0302'
|
38
|
+
@server_encryption_key = RubySMB::Crypto::KDF.counter_mode(
|
39
|
+
@session_key,
|
40
|
+
"SMB2AESCCM\x00",
|
41
|
+
"ServerOut\x00"
|
42
|
+
)
|
43
|
+
when '0x0311'
|
44
|
+
@server_encryption_key = RubySMB::Crypto::KDF.counter_mode(
|
45
|
+
@session_key,
|
46
|
+
"SMBS2CCipherKey\x00",
|
47
|
+
@preauth_integrity_hash_value
|
48
|
+
)
|
49
|
+
else
|
50
|
+
raise RubySMB::Error::EncryptionError.new('Dialect is incompatible with SMBv3 decryption')
|
51
|
+
end
|
52
|
+
######
|
53
|
+
# DEBUG
|
54
|
+
#puts "Server encryption key = #{@server_encryption_key.each_byte.map {|e| '%02x' % e}.join}"
|
55
|
+
######
|
56
|
+
end
|
57
|
+
|
58
|
+
th.decrypt(@server_encryption_key, algorithm: @encryption_algorithm)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -17,10 +17,29 @@ module RubySMB
|
|
17
17
|
# internally to be able to retrieve the negotiated dialect later on.
|
18
18
|
# This is only valid for SMB1.
|
19
19
|
response_packet.dialects = request_packet.dialects if response_packet.respond_to? :dialects=
|
20
|
-
parse_negotiate_response(response_packet)
|
21
|
-
|
22
|
-
|
23
|
-
|
20
|
+
version = parse_negotiate_response(response_packet)
|
21
|
+
case @dialect
|
22
|
+
when '0x0300', '0x0302'
|
23
|
+
@encryption_algorithm = RubySMB::SMB2::EncryptionCapabilities::ENCRYPTION_ALGORITHM_MAP[RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM]
|
24
|
+
when '0x0311'
|
25
|
+
parse_smb3_encryption_data(request_packet, response_packet)
|
26
|
+
end
|
27
|
+
# If the response contains the SMB2 wildcard revision number dialect;
|
28
|
+
# it indicates that the server implements SMB 2.1 or future dialect
|
29
|
+
# revisions and expects the client to send a subsequent SMB2 Negotiate
|
30
|
+
# request to negotiate the actual SMB 2 Protocol revision to be used.
|
31
|
+
# The wildcard revision number is sent only in response to a
|
32
|
+
# multi-protocol negotiate request with the "SMB 2.???" dialect string.
|
33
|
+
if @dialect == '0x02ff'
|
34
|
+
self.smb2_message_id += 1
|
35
|
+
version = negotiate
|
36
|
+
end
|
37
|
+
version
|
38
|
+
rescue RubySMB::Error::InvalidPacket, Errno::ECONNRESET, RubySMB::Error::CommunicationError => e
|
39
|
+
version = request_packet.packet_smb_version
|
40
|
+
version = 'SMB3' if version == 'SMB2' && !@smb2 && @smb3
|
41
|
+
version = 'SMB2 or SMB3' if version == 'SMB2' && @smb2 && @smb3
|
42
|
+
error = "Unable to negotiate #{version} with the remote host: #{e.message}"
|
24
43
|
raise RubySMB::Error::NegotiationFailure, error
|
25
44
|
end
|
26
45
|
|
@@ -32,8 +51,8 @@ module RubySMB
|
|
32
51
|
def negotiate_request
|
33
52
|
if smb1
|
34
53
|
smb1_negotiate_request
|
35
|
-
|
36
|
-
|
54
|
+
else
|
55
|
+
smb2_3_negotiate_request
|
37
56
|
end
|
38
57
|
end
|
39
58
|
|
@@ -50,7 +69,7 @@ module RubySMB
|
|
50
69
|
packet = RubySMB::SMB1::Packet::NegotiateResponseExtended.read raw_data
|
51
70
|
response = packet if packet.valid?
|
52
71
|
end
|
53
|
-
if smb2 && response.nil?
|
72
|
+
if (smb2 || smb3) && response.nil?
|
54
73
|
packet = RubySMB::SMB2::Packet::NegotiateResponse.read raw_data
|
55
74
|
response = packet if packet.valid?
|
56
75
|
end
|
@@ -95,26 +114,78 @@ module RubySMB
|
|
95
114
|
when RubySMB::SMB1::Packet::NegotiateResponseExtended
|
96
115
|
self.smb1 = true
|
97
116
|
self.smb2 = false
|
117
|
+
self.smb3 = false
|
98
118
|
self.signing_required = packet.parameter_block.security_mode.security_signatures_required == 1
|
99
119
|
self.dialect = packet.negotiated_dialect.to_s
|
100
120
|
# MaxBufferSize is largest message server will receive, measured from start of the SMB header. Subtract 260
|
101
121
|
# for protocol overhead. Then this value can be used for max read/write size without having to factor in
|
102
122
|
# protocol overhead every time.
|
103
123
|
self.server_max_buffer_size = packet.parameter_block.max_buffer_size - 260
|
124
|
+
self.negotiated_smb_version = 1
|
104
125
|
'SMB1'
|
105
126
|
when RubySMB::SMB2::Packet::NegotiateResponse
|
106
127
|
self.smb1 = false
|
107
|
-
|
108
|
-
|
128
|
+
unless packet.dialect_revision.to_i == 0x02ff
|
129
|
+
self.smb2 = packet.dialect_revision.to_i >= 0x0200 && packet.dialect_revision.to_i < 0x0300
|
130
|
+
self.smb3 = packet.dialect_revision.to_i >= 0x0300 && packet.dialect_revision.to_i < 0x0400
|
131
|
+
end
|
132
|
+
self.signing_required = packet.security_mode.signing_required == 1 if self.smb2 || self.smb3
|
109
133
|
self.dialect = "0x%04x" % packet.dialect_revision
|
110
134
|
self.server_max_read_size = packet.max_read_size
|
111
135
|
self.server_max_write_size = packet.max_write_size
|
112
136
|
self.server_max_transact_size = packet.max_transact_size
|
113
137
|
# This value is used in SMB1 only but calculate a valid value anyway
|
114
138
|
self.server_max_buffer_size = [self.server_max_read_size, self.server_max_write_size, self.server_max_transact_size].min
|
115
|
-
|
139
|
+
self.negotiated_smb_version = self.smb2 ? 2 : 3
|
140
|
+
return "SMB#{self.negotiated_smb_version}"
|
141
|
+
else
|
142
|
+
error = 'Unable to negotiate with remote host'
|
143
|
+
if packet.status_code == WindowsError::NTStatus::STATUS_NOT_SUPPORTED
|
144
|
+
error << ", SMB2" if @smb2
|
145
|
+
error << ", SMB3" if @smb3
|
146
|
+
error << ' not supported'
|
147
|
+
end
|
148
|
+
raise RubySMB::Error::NegotiationFailure, error
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def parse_smb3_encryption_data(request_packet, response_packet)
|
153
|
+
nc = response_packet.find_negotiate_context(
|
154
|
+
RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
|
155
|
+
)
|
156
|
+
@preauth_integrity_hash_algorithm = RubySMB::SMB2::PreauthIntegrityCapabilities::HASH_ALGORITM_MAP[nc&.data&.hash_algorithms&.first]
|
157
|
+
unless @preauth_integrity_hash_algorithm
|
158
|
+
raise RubySMB::Error::EncryptionError.new(
|
159
|
+
'Unable to retrieve the Preauth Integrity Hash Algorithm from the Negotiate response'
|
160
|
+
)
|
161
|
+
end
|
162
|
+
# Set the encryption the client will use, prioritizing AES_128_GCM over AES_128_CCM
|
163
|
+
nc = response_packet.find_negotiate_context(
|
164
|
+
RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
|
165
|
+
)
|
166
|
+
@server_encryption_algorithms = nc&.data&.ciphers&.to_ary
|
167
|
+
if @server_encryption_algorithms.nil? || @server_encryption_algorithms.empty?
|
168
|
+
raise RubySMB::Error::EncryptionError.new(
|
169
|
+
'Unable to retrieve the encryption cipher list supported by the server from the Negotiate response'
|
170
|
+
)
|
171
|
+
end
|
172
|
+
if @server_encryption_algorithms.include?(RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM)
|
173
|
+
@encryption_algorithm = RubySMB::SMB2::EncryptionCapabilities::ENCRYPTION_ALGORITHM_MAP[RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM]
|
174
|
+
else
|
175
|
+
@encryption_algorithm = RubySMB::SMB2::EncryptionCapabilities::ENCRYPTION_ALGORITHM_MAP[@server_encryption_algorithms.first]
|
176
|
+
end
|
177
|
+
unless @encryption_algorithm
|
178
|
+
raise RubySMB::Error::EncryptionError.new(
|
179
|
+
'Unable to retrieve the encryption cipher list supported by the server from the Negotiate response'
|
180
|
+
)
|
116
181
|
end
|
182
|
+
update_preauth_hash(request_packet)
|
183
|
+
update_preauth_hash(response_packet)
|
117
184
|
|
185
|
+
nc = response_packet.find_negotiate_context(
|
186
|
+
RubySMB::SMB2::NegotiateContext::SMB2_COMPRESSION_CAPABILITIES
|
187
|
+
)
|
188
|
+
@server_compression_algorithms = nc&.data&.compression_algorithms&.to_ary || []
|
118
189
|
end
|
119
190
|
|
120
191
|
# Create a {RubySMB::SMB1::Packet::NegotiateRequest} packet with the
|
@@ -131,6 +202,7 @@ module RubySMB
|
|
131
202
|
# to Negotiate strictly SMB2, but the protocol WILL support it
|
132
203
|
packet.add_dialect(SMB1_DIALECT_SMB1_DEFAULT) if smb1
|
133
204
|
packet.add_dialect(SMB1_DIALECT_SMB2_DEFAULT) if smb2
|
205
|
+
packet.add_dialect(SMB1_DIALECT_SMB2_WILDCARD) if smb2 || smb3
|
134
206
|
packet
|
135
207
|
end
|
136
208
|
|
@@ -139,11 +211,60 @@ module RubySMB
|
|
139
211
|
# may want to communicate over SMB1
|
140
212
|
#
|
141
213
|
# @ return [RubySMB::SMB2::Packet::NegotiateRequest] a completed SMB2 Negotiate Request packet
|
142
|
-
def
|
214
|
+
def smb2_3_negotiate_request
|
143
215
|
packet = RubySMB::SMB2::Packet::NegotiateRequest.new
|
144
216
|
packet.security_mode.signing_enabled = 1
|
145
|
-
packet.add_dialect(SMB2_DIALECT_DEFAULT)
|
146
217
|
packet.client_guid = SecureRandom.random_bytes(16)
|
218
|
+
packet.set_dialects(SMB2_DIALECT_DEFAULT.map {|d| d.to_i(16)}) if smb2
|
219
|
+
packet = add_smb3_to_negotiate_request(packet) if smb3
|
220
|
+
packet
|
221
|
+
end
|
222
|
+
|
223
|
+
# This adds SMBv3 specific information: SMBv3 supported dialects,
|
224
|
+
# encryption capability, Negotiate Contexts if the dialect requires them
|
225
|
+
#
|
226
|
+
# @param packet [RubySMB::SMB2::Packet::NegotiateRequest] the NegotiateRequest
|
227
|
+
# to add SMB3 specific info to
|
228
|
+
# @param dialects [Array<String>] the dialects to negotiate. This must be
|
229
|
+
# an array of strings. Default is SMB3_DIALECT_DEFAULT
|
230
|
+
# @return [RubySMB::SMB2::Packet::NegotiateRequest] a completed SMB3 Negotiate Request packet
|
231
|
+
# @raise [ArgumentError] if dialects is not an array of strings
|
232
|
+
def add_smb3_to_negotiate_request(packet, dialects = SMB3_DIALECT_DEFAULT)
|
233
|
+
dialects.each do |dialect|
|
234
|
+
raise ArgumentError, 'Must be an array of strings' unless dialect.is_a? String
|
235
|
+
packet.add_dialect(dialect.to_i(16))
|
236
|
+
end
|
237
|
+
packet.capabilities.encryption = 1
|
238
|
+
|
239
|
+
if packet.dialects.include?(0x0311)
|
240
|
+
nc = RubySMB::SMB2::NegotiateContext.new(
|
241
|
+
context_type: RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
|
242
|
+
)
|
243
|
+
nc.data.hash_algorithms << RubySMB::SMB2::PreauthIntegrityCapabilities::SHA_512
|
244
|
+
nc.data.salt = SecureRandom.random_bytes(32)
|
245
|
+
packet.add_negotiate_context(nc)
|
246
|
+
|
247
|
+
@preauth_integrity_hash_value = "\x00" * 64
|
248
|
+
nc = RubySMB::SMB2::NegotiateContext.new(
|
249
|
+
context_type: RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
|
250
|
+
)
|
251
|
+
nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM
|
252
|
+
nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM
|
253
|
+
packet.add_negotiate_context(nc)
|
254
|
+
|
255
|
+
nc = RubySMB::SMB2::NegotiateContext.new(
|
256
|
+
context_type: RubySMB::SMB2::NegotiateContext::SMB2_COMPRESSION_CAPABILITIES
|
257
|
+
)
|
258
|
+
# Adding all possible compression algorithm even if we don't support
|
259
|
+
# them yet. This will force the server to disclose the support
|
260
|
+
# algorithms in the repsonse.
|
261
|
+
nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZNT1
|
262
|
+
nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZ77
|
263
|
+
nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZ77_Huffman
|
264
|
+
nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::Pattern_V1
|
265
|
+
packet.add_negotiate_context(nc)
|
266
|
+
end
|
267
|
+
|
147
268
|
packet
|
148
269
|
end
|
149
270
|
end
|
@@ -40,6 +40,25 @@ module RubySMB
|
|
40
40
|
end
|
41
41
|
packet
|
42
42
|
end
|
43
|
+
|
44
|
+
def smb3_sign(packet)
|
45
|
+
if !session_key.empty? && (signing_required || packet.is_a?(RubySMB::SMB2::Packet::TreeConnectRequest))
|
46
|
+
case @dialect
|
47
|
+
when '0x0300', '0x0302'
|
48
|
+
signing_key = RubySMB::Crypto::KDF.counter_mode(@session_key, "SMB2AESCMAC\x00", "SmbSign\x00")
|
49
|
+
when '0x0311'
|
50
|
+
signing_key = RubySMB::Crypto::KDF.counter_mode(@session_key, "SMBSigningKey\x00", @preauth_integrity_hash_value)
|
51
|
+
else
|
52
|
+
raise RubySMB::Error::SigningError.new('Dialect is incompatible with SMBv3 signing')
|
53
|
+
end
|
54
|
+
|
55
|
+
packet.smb2_header.flags.signed = 1
|
56
|
+
packet.smb2_header.signature = "\x00" * 16
|
57
|
+
hmac = OpenSSL::CMAC.digest('AES', signing_key, packet.to_binary_s)
|
58
|
+
packet.smb2_header.signature = hmac[0, 16]
|
59
|
+
end
|
60
|
+
packet
|
61
|
+
end
|
43
62
|
end
|
44
63
|
end
|
45
64
|
end
|
@@ -38,7 +38,7 @@ module RubySMB
|
|
38
38
|
)
|
39
39
|
end
|
40
40
|
unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
|
41
|
-
raise RubySMB::Error::UnexpectedStatusCode, response.status_code
|
41
|
+
raise RubySMB::Error::UnexpectedStatusCode, response.status_code
|
42
42
|
end
|
43
43
|
RubySMB::SMB1::Tree.new(client: self, share: share, response: response)
|
44
44
|
end
|
@@ -55,7 +55,7 @@ module RubySMB
|
|
55
55
|
def smb2_tree_connect(share)
|
56
56
|
request = RubySMB::SMB2::Packet::TreeConnectRequest.new
|
57
57
|
request.smb2_header.tree_id = 65_535
|
58
|
-
request.
|
58
|
+
request.path = share
|
59
59
|
raw_response = send_recv(request)
|
60
60
|
response = RubySMB::SMB2::Packet::TreeConnectResponse.read(raw_response)
|
61
61
|
smb2_tree_from_response(share, response)
|
@@ -78,9 +78,9 @@ module RubySMB
|
|
78
78
|
)
|
79
79
|
end
|
80
80
|
unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
|
81
|
-
raise RubySMB::Error::UnexpectedStatusCode, response.status_code
|
81
|
+
raise RubySMB::Error::UnexpectedStatusCode, response.status_code
|
82
82
|
end
|
83
|
-
RubySMB::SMB2::Tree.new(client: self, share: share, response: response)
|
83
|
+
RubySMB::SMB2::Tree.new(client: self, share: share, response: response, encrypt: response.share_flags.encrypt == 1)
|
84
84
|
end
|
85
85
|
end
|
86
86
|
end
|
@@ -40,27 +40,28 @@ module RubySMB
|
|
40
40
|
end
|
41
41
|
|
42
42
|
#Writes data to an open file handle
|
43
|
-
def write(file_id, offset = 0, data = '', do_recv = true)
|
43
|
+
def write(file_id = last_file_id, offset = 0, data = '', do_recv = true)
|
44
44
|
@open_files[file_id].send_recv_write(data: data, offset: offset)
|
45
45
|
end
|
46
46
|
|
47
|
-
def read(file_id, offset = 0, length = last_file.size)
|
47
|
+
def read(file_id = last_file_id, offset = 0, length = last_file.size, do_recv = true)
|
48
48
|
data = @open_files[file_id].send_recv_read(read_length: length, offset: offset)
|
49
49
|
data.bytes
|
50
50
|
end
|
51
51
|
|
52
|
-
def delete(path)
|
53
|
-
|
52
|
+
def delete(path, tree_id = last_tree_id, do_recv = true)
|
53
|
+
tree = @tree_connects.detect{ |tree| tree.id == tree_id }
|
54
|
+
file = tree.open_file(filename: path.sub(/^\\/, ''), delete: true)
|
54
55
|
file.delete
|
55
56
|
file.close
|
56
57
|
end
|
57
58
|
|
58
|
-
def close(file_id, tree_id)
|
59
|
+
def close(file_id = last_file_id, tree_id = last_tree_id, do_recv = true)
|
59
60
|
@open_files[file_id].close
|
60
61
|
end
|
61
62
|
|
62
|
-
def tree_disconnect(
|
63
|
-
@tree_connects.detect{|tree| tree.id ==
|
63
|
+
def tree_disconnect(tree_id = last_tree_id, do_recv = true)
|
64
|
+
@tree_connects.detect{|tree| tree.id == tree_id }.disconnect!
|
64
65
|
end
|
65
66
|
|
66
67
|
def native_os
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module RubySMB
|
2
|
+
module Crypto
|
3
|
+
module KDF
|
4
|
+
def self.counter_mode(ki, label, context, length: 128)
|
5
|
+
digest = OpenSSL::Digest.new('SHA256')
|
6
|
+
r = 32
|
7
|
+
|
8
|
+
n = length / 256
|
9
|
+
n = 1 if n == 0
|
10
|
+
|
11
|
+
raise ArgumentError if n > 2**r - 1
|
12
|
+
result = ""
|
13
|
+
|
14
|
+
n.times do |i|
|
15
|
+
input = [i + 1].pack('L>')
|
16
|
+
input << label
|
17
|
+
input << "\x00"
|
18
|
+
input << context
|
19
|
+
input << [length].pack('L>')
|
20
|
+
k = OpenSSL::HMAC.digest(digest, ki, input)
|
21
|
+
result << k
|
22
|
+
end
|
23
|
+
|
24
|
+
return result[0...(length / 8)]
|
25
|
+
rescue OpenSSL::OpenSSLError => e
|
26
|
+
raise RubySMB::Error::EncryptionError, "Crypto::KDF.counter_mode OpenSSL error: #{e.message}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|