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
@@ -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.to_s
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.to_s
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
- # This Message ID should always be 1, but thanks to Multi-Protocol Negotiation
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
- rescue RubySMB::Error::InvalidPacket, Errno::ECONNRESET
22
- error = 'Unable to Negotiate with remote host'
23
- error << ', SMB1 may be disabled' if smb1 && !smb2
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
- elsif smb2
36
- smb2_negotiate_request
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
- self.smb2 = true
108
- self.signing_required = packet.security_mode.signing_required == 1
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
- 'SMB2'
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 smb2_negotiate_request
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.name
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.encode_path(share)
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.name
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
- file = last_tree.open_file(filename: path.sub(/^\\/, ''), delete: true)
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(share)
63
- @tree_connects.detect{|tree| tree.id == share }.disconnect!
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