ruby_smb 1.1.0 → 2.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (163) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.travis.yml +3 -5
  5. data/Gemfile +6 -2
  6. data/examples/anonymous_auth.rb +3 -3
  7. data/examples/append_file.rb +10 -8
  8. data/examples/authenticate.rb +9 -5
  9. data/examples/delete_file.rb +8 -6
  10. data/examples/enum_registry_key.rb +5 -4
  11. data/examples/enum_registry_values.rb +5 -4
  12. data/examples/list_directory.rb +8 -6
  13. data/examples/negotiate.rb +51 -8
  14. data/examples/negotiate_with_netbios_service.rb +9 -5
  15. data/examples/net_share_enum_all.rb +6 -4
  16. data/examples/pipes.rb +11 -12
  17. data/examples/query_service_status.rb +64 -0
  18. data/examples/read_file.rb +8 -6
  19. data/examples/read_file_encryption.rb +56 -0
  20. data/examples/read_registry_key_value.rb +6 -5
  21. data/examples/rename_file.rb +9 -7
  22. data/examples/tree_connect.rb +7 -5
  23. data/examples/write_file.rb +9 -7
  24. data/lib/ruby_smb.rb +4 -0
  25. data/lib/ruby_smb/client.rb +246 -26
  26. data/lib/ruby_smb/client/authentication.rb +32 -18
  27. data/lib/ruby_smb/client/echo.rb +2 -4
  28. data/lib/ruby_smb/client/encryption.rb +62 -0
  29. data/lib/ruby_smb/client/negotiation.rb +156 -16
  30. data/lib/ruby_smb/client/signing.rb +19 -0
  31. data/lib/ruby_smb/client/tree_connect.rb +6 -8
  32. data/lib/ruby_smb/client/utils.rb +24 -17
  33. data/lib/ruby_smb/client/winreg.rb +1 -1
  34. data/lib/ruby_smb/crypto.rb +30 -0
  35. data/lib/ruby_smb/dcerpc.rb +2 -0
  36. data/lib/ruby_smb/dcerpc/error.rb +3 -0
  37. data/lib/ruby_smb/dcerpc/ndr.rb +209 -44
  38. data/lib/ruby_smb/dcerpc/request.rb +13 -0
  39. data/lib/ruby_smb/dcerpc/rpc_security_attributes.rb +34 -0
  40. data/lib/ruby_smb/dcerpc/rrp_unicode_string.rb +9 -6
  41. data/lib/ruby_smb/dcerpc/svcctl.rb +479 -0
  42. data/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_request.rb +48 -0
  43. data/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_response.rb +26 -0
  44. data/lib/ruby_smb/dcerpc/svcctl/close_service_handle_request.rb +25 -0
  45. data/lib/ruby_smb/dcerpc/svcctl/close_service_handle_response.rb +26 -0
  46. data/lib/ruby_smb/dcerpc/svcctl/control_service_request.rb +26 -0
  47. data/lib/ruby_smb/dcerpc/svcctl/control_service_response.rb +26 -0
  48. data/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_request.rb +35 -0
  49. data/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_response.rb +23 -0
  50. data/lib/ruby_smb/dcerpc/svcctl/open_service_w_request.rb +31 -0
  51. data/lib/ruby_smb/dcerpc/svcctl/open_service_w_response.rb +23 -0
  52. data/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_request.rb +25 -0
  53. data/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_response.rb +44 -0
  54. data/lib/ruby_smb/dcerpc/svcctl/query_service_status_request.rb +23 -0
  55. data/lib/ruby_smb/dcerpc/svcctl/query_service_status_response.rb +27 -0
  56. data/lib/ruby_smb/dcerpc/svcctl/service_status.rb +25 -0
  57. data/lib/ruby_smb/dcerpc/svcctl/start_service_w_request.rb +27 -0
  58. data/lib/ruby_smb/dcerpc/svcctl/start_service_w_response.rb +25 -0
  59. data/lib/ruby_smb/dcerpc/winreg.rb +98 -17
  60. data/lib/ruby_smb/dcerpc/winreg/create_key_request.rb +73 -0
  61. data/lib/ruby_smb/dcerpc/winreg/create_key_response.rb +36 -0
  62. data/lib/ruby_smb/dcerpc/winreg/enum_key_request.rb +1 -1
  63. data/lib/ruby_smb/dcerpc/winreg/enum_value_request.rb +1 -1
  64. data/lib/ruby_smb/dcerpc/winreg/enum_value_response.rb +1 -1
  65. data/lib/ruby_smb/dcerpc/winreg/open_root_key_request.rb +4 -4
  66. data/lib/ruby_smb/dcerpc/winreg/query_info_key_request.rb +1 -1
  67. data/lib/ruby_smb/dcerpc/winreg/query_value_request.rb +7 -6
  68. data/lib/ruby_smb/dcerpc/winreg/query_value_response.rb +10 -10
  69. data/lib/ruby_smb/dcerpc/winreg/save_key_request.rb +37 -0
  70. data/lib/ruby_smb/dcerpc/winreg/save_key_response.rb +23 -0
  71. data/lib/ruby_smb/dispatcher/base.rb +1 -1
  72. data/lib/ruby_smb/dispatcher/socket.rb +5 -4
  73. data/lib/ruby_smb/error.rb +49 -6
  74. data/lib/ruby_smb/field/stringz16.rb +17 -1
  75. data/lib/ruby_smb/generic_packet.rb +11 -1
  76. data/lib/ruby_smb/nbss/session_header.rb +4 -4
  77. data/lib/ruby_smb/smb1/commands.rb +1 -1
  78. data/lib/ruby_smb/smb1/file.rb +13 -28
  79. data/lib/ruby_smb/smb1/packet/session_setup_legacy_request.rb +1 -1
  80. data/lib/ruby_smb/smb1/packet/session_setup_legacy_response.rb +2 -2
  81. data/lib/ruby_smb/smb1/packet/session_setup_request.rb +1 -1
  82. data/lib/ruby_smb/smb1/packet/session_setup_response.rb +2 -2
  83. data/lib/ruby_smb/smb1/packet/write_andx_request.rb +1 -1
  84. data/lib/ruby_smb/smb1/pipe.rb +8 -8
  85. data/lib/ruby_smb/smb1/tree.rb +25 -12
  86. data/lib/ruby_smb/smb2/bit_field/session_flags.rb +2 -1
  87. data/lib/ruby_smb/smb2/bit_field/share_flags.rb +6 -4
  88. data/lib/ruby_smb/smb2/file.rb +59 -77
  89. data/lib/ruby_smb/smb2/negotiate_context.rb +108 -0
  90. data/lib/ruby_smb/smb2/packet.rb +2 -0
  91. data/lib/ruby_smb/smb2/packet/compression_transform_header.rb +41 -0
  92. data/lib/ruby_smb/smb2/packet/negotiate_request.rb +51 -14
  93. data/lib/ruby_smb/smb2/packet/negotiate_response.rb +50 -4
  94. data/lib/ruby_smb/smb2/packet/transform_header.rb +84 -0
  95. data/lib/ruby_smb/smb2/packet/tree_connect_request.rb +92 -6
  96. data/lib/ruby_smb/smb2/packet/tree_connect_response.rb +8 -26
  97. data/lib/ruby_smb/smb2/pipe.rb +8 -20
  98. data/lib/ruby_smb/smb2/smb2_header.rb +1 -1
  99. data/lib/ruby_smb/smb2/tree.rb +44 -28
  100. data/lib/ruby_smb/version.rb +1 -1
  101. data/ruby_smb.gemspec +3 -1
  102. data/spec/lib/ruby_smb/client_spec.rb +1408 -70
  103. data/spec/lib/ruby_smb/crypto_spec.rb +25 -0
  104. data/spec/lib/ruby_smb/dcerpc/ndr_spec.rb +1396 -77
  105. data/spec/lib/ruby_smb/dcerpc/rpc_security_attributes_spec.rb +161 -0
  106. data/spec/lib/ruby_smb/dcerpc/rrp_unicode_string_spec.rb +49 -12
  107. data/spec/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_request_spec.rb +191 -0
  108. data/spec/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_response_spec.rb +38 -0
  109. data/spec/lib/ruby_smb/dcerpc/svcctl/close_service_handle_request_spec.rb +30 -0
  110. data/spec/lib/ruby_smb/dcerpc/svcctl/close_service_handle_response_spec.rb +38 -0
  111. data/spec/lib/ruby_smb/dcerpc/svcctl/control_service_request_spec.rb +39 -0
  112. data/spec/lib/ruby_smb/dcerpc/svcctl/control_service_response_spec.rb +38 -0
  113. data/spec/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_request_spec.rb +78 -0
  114. data/spec/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_response_spec.rb +38 -0
  115. data/spec/lib/ruby_smb/dcerpc/svcctl/open_service_w_request_spec.rb +59 -0
  116. data/spec/lib/ruby_smb/dcerpc/svcctl/open_service_w_response_spec.rb +38 -0
  117. data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_request_spec.rb +38 -0
  118. data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_response_spec.rb +152 -0
  119. data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_status_request_spec.rb +30 -0
  120. data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_status_response_spec.rb +38 -0
  121. data/spec/lib/ruby_smb/dcerpc/svcctl/service_status_spec.rb +72 -0
  122. data/spec/lib/ruby_smb/dcerpc/svcctl/start_service_w_request_spec.rb +46 -0
  123. data/spec/lib/ruby_smb/dcerpc/svcctl/start_service_w_response_spec.rb +30 -0
  124. data/spec/lib/ruby_smb/dcerpc/svcctl_spec.rb +512 -0
  125. data/spec/lib/ruby_smb/dcerpc/winreg/create_key_request_spec.rb +110 -0
  126. data/spec/lib/ruby_smb/dcerpc/winreg/create_key_response_spec.rb +44 -0
  127. data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_request_spec.rb +0 -4
  128. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_request_spec.rb +2 -2
  129. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_response_spec.rb +2 -2
  130. data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_request_spec.rb +9 -4
  131. data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_request_spec.rb +0 -4
  132. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_request_spec.rb +17 -17
  133. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_response_spec.rb +11 -23
  134. data/spec/lib/ruby_smb/dcerpc/winreg/save_key_request_spec.rb +57 -0
  135. data/spec/lib/ruby_smb/dcerpc/winreg/save_key_response_spec.rb +22 -0
  136. data/spec/lib/ruby_smb/dcerpc/winreg_spec.rb +227 -41
  137. data/spec/lib/ruby_smb/dispatcher/socket_spec.rb +12 -12
  138. data/spec/lib/ruby_smb/error_spec.rb +88 -0
  139. data/spec/lib/ruby_smb/field/stringz16_spec.rb +12 -0
  140. data/spec/lib/ruby_smb/generic_packet_spec.rb +7 -0
  141. data/spec/lib/ruby_smb/nbss/session_header_spec.rb +4 -11
  142. data/spec/lib/ruby_smb/smb1/file_spec.rb +1 -3
  143. data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_request_spec.rb +2 -2
  144. data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_response_spec.rb +2 -2
  145. data/spec/lib/ruby_smb/smb1/packet/session_setup_request_spec.rb +2 -2
  146. data/spec/lib/ruby_smb/smb1/packet/session_setup_response_spec.rb +1 -1
  147. data/spec/lib/ruby_smb/smb1/pipe_spec.rb +30 -5
  148. data/spec/lib/ruby_smb/smb1/tree_spec.rb +22 -0
  149. data/spec/lib/ruby_smb/smb2/bit_field/session_flags_spec.rb +9 -0
  150. data/spec/lib/ruby_smb/smb2/bit_field/share_flags_spec.rb +27 -0
  151. data/spec/lib/ruby_smb/smb2/file_spec.rb +147 -71
  152. data/spec/lib/ruby_smb/smb2/negotiate_context_spec.rb +332 -0
  153. data/spec/lib/ruby_smb/smb2/packet/compression_transform_header_spec.rb +108 -0
  154. data/spec/lib/ruby_smb/smb2/packet/negotiate_request_spec.rb +138 -3
  155. data/spec/lib/ruby_smb/smb2/packet/negotiate_response_spec.rb +120 -2
  156. data/spec/lib/ruby_smb/smb2/packet/transform_header_spec.rb +220 -0
  157. data/spec/lib/ruby_smb/smb2/packet/tree_connect_request_spec.rb +339 -9
  158. data/spec/lib/ruby_smb/smb2/packet/tree_connect_response_spec.rb +3 -30
  159. data/spec/lib/ruby_smb/smb2/pipe_spec.rb +9 -45
  160. data/spec/lib/ruby_smb/smb2/smb2_header_spec.rb +2 -2
  161. data/spec/lib/ruby_smb/smb2/tree_spec.rb +111 -9
  162. metadata +194 -75
  163. metadata.gz.sig +2 -1
@@ -60,8 +60,7 @@ module RubySMB
60
60
  raise RubySMB::Error::InvalidPacket.new(
61
61
  expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
62
62
  expected_cmd: RubySMB::SMB1::Packet::SessionSetupLegacyResponse::COMMAND,
63
- received_proto: packet.smb_header.protocol,
64
- received_cmd: packet.smb_header.command
63
+ packet: packet
65
64
  )
66
65
  end
67
66
  packet
@@ -154,8 +153,7 @@ module RubySMB
154
153
  raise RubySMB::Error::InvalidPacket.new(
155
154
  expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
156
155
  expected_cmd: RubySMB::SMB1::Packet::SessionSetupResponse::COMMAND,
157
- received_proto: packet.smb_header.protocol,
158
- received_cmd: packet.smb_header.command
156
+ packet: packet
159
157
  )
160
158
  end
161
159
  packet
@@ -168,14 +166,13 @@ module RubySMB
168
166
  raise RubySMB::Error::InvalidPacket.new(
169
167
  expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
170
168
  expected_cmd: RubySMB::SMB1::Packet::SessionSetupResponse::COMMAND,
171
- received_proto: packet.smb_header.protocol,
172
- received_cmd: packet.smb_header.command
169
+ packet: packet
173
170
  )
174
171
  end
175
172
 
176
173
  status_code = packet.status_code
177
174
  unless status_code.name == 'STATUS_MORE_PROCESSING_REQUIRED'
178
- raise RubySMB::Error::UnexpectedStatusCode, status_code.to_s
175
+ raise RubySMB::Error::UnexpectedStatusCode, status_code
179
176
  end
180
177
 
181
178
  packet
@@ -201,6 +198,9 @@ module RubySMB
201
198
  def smb2_authenticate
202
199
  response = smb2_ntlmssp_negotiate
203
200
  challenge_packet = smb2_ntlmssp_challenge_packet(response)
201
+ if @dialect == '0x0311'
202
+ update_preauth_hash(challenge_packet)
203
+ end
204
204
  @session_id = challenge_packet.smb2_header.session_id
205
205
  type2_b64_message = smb2_type2_message(challenge_packet)
206
206
  type3_message = @ntlm_client.init_context(type2_b64_message)
@@ -213,6 +213,16 @@ module RubySMB
213
213
  raw = smb2_ntlmssp_authenticate(type3_message, @session_id)
214
214
  response = smb2_ntlmssp_final_packet(raw)
215
215
 
216
+ if @smb3 && !@session_encrypt_data && response.session_flags.encrypt_data == 1
217
+ @session_encrypt_data = true
218
+ end
219
+ ######
220
+ # DEBUG
221
+ #puts "Session ID = #{@session_id.to_binary_s.each_byte.map {|e| '%02x' % e}.join}"
222
+ #puts "Session key = #{@session_key.each_byte.map {|e| '%02x' % e}.join}"
223
+ #puts "PreAuthHash = #{@preauth_integrity_hash_value.each_byte.map {|e| '%02x' % e}.join}" if @preauth_integrity_hash_value
224
+ ######
225
+
216
226
  response.status_code
217
227
  end
218
228
 
@@ -223,8 +233,7 @@ module RubySMB
223
233
  raise RubySMB::Error::InvalidPacket.new(
224
234
  expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
225
235
  expected_cmd: RubySMB::SMB2::Packet::SessionSetupResponse::COMMAND,
226
- received_proto: packet.smb2_header.protocol,
227
- received_cmd: packet.smb2_header.command
236
+ packet: packet
228
237
  )
229
238
  end
230
239
 
@@ -238,14 +247,13 @@ module RubySMB
238
247
  raise RubySMB::Error::InvalidPacket.new(
239
248
  expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
240
249
  expected_cmd: RubySMB::SMB2::Packet::SessionSetupResponse::COMMAND,
241
- received_proto: packet.smb2_header.protocol,
242
- received_cmd: packet.smb2_header.command
250
+ packet: packet
243
251
  )
244
252
  end
245
253
 
246
254
  status_code = packet.status_code
247
255
  unless status_code.name == 'STATUS_MORE_PROCESSING_REQUIRED'
248
- raise RubySMB::Error::UnexpectedStatusCode, status_code.to_s
256
+ raise RubySMB::Error::UnexpectedStatusCode, status_code
249
257
  end
250
258
  packet
251
259
  end
@@ -256,7 +264,11 @@ module RubySMB
256
264
  # @return [String] the binary string response from the server
257
265
  def smb2_ntlmssp_negotiate
258
266
  packet = smb2_ntlmssp_negotiate_packet
259
- send_recv(packet)
267
+ response = send_recv(packet)
268
+ if @dialect == '0x0311'
269
+ update_preauth_hash(packet)
270
+ end
271
+ response
260
272
  end
261
273
 
262
274
  # Creates the {RubySMB::SMB2::Packet::SessionSetupRequest} packet
@@ -268,10 +280,7 @@ module RubySMB
268
280
  type1_message = ntlm_client.init_context
269
281
  packet = RubySMB::SMB2::Packet::SessionSetupRequest.new
270
282
  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
283
+ packet.security_mode.signing_enabled = 1
275
284
  packet
276
285
  end
277
286
 
@@ -294,7 +303,11 @@ module RubySMB
294
303
  # @return [String] the raw binary response from the server
295
304
  def smb2_ntlmssp_authenticate(type3_message, user_id)
296
305
  packet = smb2_ntlmssp_auth_packet(type3_message, user_id)
297
- send_recv(packet)
306
+ response = send_recv(packet)
307
+ if @dialect == '0x0311'
308
+ update_preauth_hash(packet)
309
+ end
310
+ response
298
311
  end
299
312
 
300
313
  # Generates the {RubySMB::SMB2::Packet::SessionSetupRequest} packet
@@ -307,6 +320,7 @@ module RubySMB
307
320
  packet = RubySMB::SMB2::Packet::SessionSetupRequest.new
308
321
  packet.smb2_header.session_id = session_id
309
322
  packet.set_type3_blob(type3_message.serialize)
323
+ packet.security_mode.signing_enabled = 1
310
324
  packet
311
325
  end
312
326
 
@@ -21,8 +21,7 @@ module RubySMB
21
21
  raise RubySMB::Error::InvalidPacket.new(
22
22
  expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
23
23
  expected_cmd: RubySMB::SMB1::Packet::EchoResponse::COMMAND,
24
- received_proto: response.smb_header.protocol,
25
- received_cmd: response.smb_header.command
24
+ packet: response
26
25
  )
27
26
  end
28
27
  response
@@ -40,8 +39,7 @@ module RubySMB
40
39
  raise RubySMB::Error::InvalidPacket.new(
41
40
  expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
42
41
  expected_cmd: RubySMB::SMB2::Packet::EchoResponse::COMMAND,
43
- received_proto: response.smb2_header.protocol,
44
- received_cmd: response.smb2_header.command
42
+ packet: response
45
43
  )
46
44
  end
47
45
  response
@@ -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,28 @@ 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
+ if @dialect == '0x0311'
22
+ update_preauth_hash(request_packet)
23
+ update_preauth_hash(response_packet)
24
+ end
25
+
26
+ # If the response contains the SMB2 wildcard revision number dialect;
27
+ # it indicates that the server implements SMB 2.1 or future dialect
28
+ # revisions and expects the client to send a subsequent SMB2 Negotiate
29
+ # request to negotiate the actual SMB 2 Protocol revision to be used.
30
+ # The wildcard revision number is sent only in response to a
31
+ # multi-protocol negotiate request with the "SMB 2.???" dialect string.
32
+ if @dialect == '0x02ff'
33
+ self.smb2_message_id += 1
34
+ version = negotiate
35
+ end
36
+ version
37
+ rescue RubySMB::Error::InvalidPacket, Errno::ECONNRESET, RubySMB::Error::CommunicationError => e
38
+ version = request_packet.packet_smb_version
39
+ version = 'SMB3' if version == 'SMB2' && !@smb2 && @smb3
40
+ version = 'SMB2 or SMB3' if version == 'SMB2' && @smb2 && @smb3
41
+ error = "Unable to negotiate #{version} with the remote host: #{e.message}"
24
42
  raise RubySMB::Error::NegotiationFailure, error
25
43
  end
26
44
 
@@ -32,8 +50,8 @@ module RubySMB
32
50
  def negotiate_request
33
51
  if smb1
34
52
  smb1_negotiate_request
35
- elsif smb2
36
- smb2_negotiate_request
53
+ else
54
+ smb2_3_negotiate_request
37
55
  end
38
56
  end
39
57
 
@@ -50,7 +68,7 @@ module RubySMB
50
68
  packet = RubySMB::SMB1::Packet::NegotiateResponseExtended.read raw_data
51
69
  response = packet if packet.valid?
52
70
  end
53
- if smb2 && response.nil?
71
+ if (smb2 || smb3) && response.nil?
54
72
  packet = RubySMB::SMB2::Packet::NegotiateResponse.read raw_data
55
73
  response = packet if packet.valid?
56
74
  end
@@ -65,16 +83,14 @@ module RubySMB
65
83
  expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
66
84
  expected_cmd: RubySMB::SMB1::Packet::NegotiateResponseExtended::COMMAND,
67
85
  expected_custom: "extended_security=1",
68
- received_proto: packet.smb_header.protocol,
69
- received_cmd: packet.smb_header.command,
86
+ packet: packet,
70
87
  received_custom: "extended_security=#{extended_security}"
71
88
  )
72
89
  elsif packet.packet_smb_version == 'SMB2'
73
90
  raise RubySMB::Error::InvalidPacket.new(
74
91
  expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
75
92
  expected_cmd: RubySMB::SMB2::Packet::NegotiateResponse::COMMAND,
76
- received_proto: packet.smb2_header.protocol,
77
- received_cmd: packet.smb2_header.command
93
+ packet: packet
78
94
  )
79
95
  else
80
96
  raise RubySMB::Error::InvalidPacket, 'Unknown SMB protocol version'
@@ -95,26 +111,94 @@ module RubySMB
95
111
  when RubySMB::SMB1::Packet::NegotiateResponseExtended
96
112
  self.smb1 = true
97
113
  self.smb2 = false
114
+ self.smb3 = false
98
115
  self.signing_required = packet.parameter_block.security_mode.security_signatures_required == 1
99
116
  self.dialect = packet.negotiated_dialect.to_s
100
117
  # MaxBufferSize is largest message server will receive, measured from start of the SMB header. Subtract 260
101
118
  # for protocol overhead. Then this value can be used for max read/write size without having to factor in
102
119
  # protocol overhead every time.
103
120
  self.server_max_buffer_size = packet.parameter_block.max_buffer_size - 260
121
+ self.negotiated_smb_version = 1
122
+ self.session_encrypt_data = false
104
123
  'SMB1'
105
124
  when RubySMB::SMB2::Packet::NegotiateResponse
106
125
  self.smb1 = false
107
- self.smb2 = true
108
- self.signing_required = packet.security_mode.signing_required == 1
126
+ unless packet.dialect_revision.to_i == 0x02ff
127
+ self.smb2 = packet.dialect_revision.to_i >= 0x0200 && packet.dialect_revision.to_i < 0x0300
128
+ self.smb3 = packet.dialect_revision.to_i >= 0x0300 && packet.dialect_revision.to_i < 0x0400
129
+ end
130
+ self.signing_required = packet.security_mode.signing_required == 1 if self.smb2 || self.smb3
109
131
  self.dialect = "0x%04x" % packet.dialect_revision
110
132
  self.server_max_read_size = packet.max_read_size
111
133
  self.server_max_write_size = packet.max_write_size
112
134
  self.server_max_transact_size = packet.max_transact_size
113
135
  # This value is used in SMB1 only but calculate a valid value anyway
114
136
  self.server_max_buffer_size = [self.server_max_read_size, self.server_max_write_size, self.server_max_transact_size].min
115
- 'SMB2'
137
+ self.negotiated_smb_version = self.smb2 ? 2 : 3
138
+ self.server_guid = packet.server_guid
139
+ self.server_start_time = packet.server_start_time.to_time if packet.server_start_time != 0
140
+ self.server_system_time = packet.system_time.to_time if packet.system_time != 0
141
+ self.server_supports_multi_credit = self.dialect != '0x0202' && packet&.capabilities&.large_mtu == 1
142
+ case self.dialect
143
+ when '0x02ff'
144
+ when '0x0300', '0x0302'
145
+ if packet&.capabilities&.encryption == 1
146
+ self.encryption_algorithm = RubySMB::SMB2::EncryptionCapabilities::ENCRYPTION_ALGORITHM_MAP[RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM]
147
+ end
148
+ self.session_encrypt_data = self.session_encrypt_data && !self.encryption_algorithm.nil?
149
+ when '0x0311'
150
+ parse_smb3_capabilities(packet)
151
+ self.session_encrypt_data = self.session_encrypt_data && !self.encryption_algorithm.nil?
152
+ else
153
+ self.session_encrypt_data = false
154
+ end
155
+ return "SMB#{self.negotiated_smb_version}"
156
+ else
157
+ error = 'Unable to negotiate with remote host'
158
+ if packet.status_code == WindowsError::NTStatus::STATUS_NOT_SUPPORTED
159
+ error << ", SMB2" if @smb2
160
+ error << ", SMB3" if @smb3
161
+ error << ' not supported'
162
+ end
163
+ raise RubySMB::Error::NegotiationFailure, error
164
+ end
165
+ end
166
+
167
+ def parse_smb3_capabilities(response_packet)
168
+ nc = response_packet.find_negotiate_context(
169
+ RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
170
+ )
171
+ @preauth_integrity_hash_algorithm = RubySMB::SMB2::PreauthIntegrityCapabilities::HASH_ALGORITM_MAP[nc&.data&.hash_algorithms&.first]
172
+ unless @preauth_integrity_hash_algorithm
173
+ raise RubySMB::Error::EncryptionError.new(
174
+ 'Unable to retrieve the Preauth Integrity Hash Algorithm from the Negotiate response'
175
+ )
176
+ end
177
+ # Set the encryption the client will use, prioritizing AES_128_GCM over AES_128_CCM
178
+ nc = response_packet.find_negotiate_context(
179
+ RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
180
+ )
181
+ @server_encryption_algorithms = nc&.data&.ciphers&.to_ary
182
+ if @server_encryption_algorithms.nil? || @server_encryption_algorithms.empty?
183
+ raise RubySMB::Error::EncryptionError.new(
184
+ 'Unable to retrieve the encryption cipher list supported by the server from the Negotiate response'
185
+ )
186
+ end
187
+ if @server_encryption_algorithms.include?(RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM)
188
+ @encryption_algorithm = RubySMB::SMB2::EncryptionCapabilities::ENCRYPTION_ALGORITHM_MAP[RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM]
189
+ else
190
+ @encryption_algorithm = RubySMB::SMB2::EncryptionCapabilities::ENCRYPTION_ALGORITHM_MAP[@server_encryption_algorithms.first]
191
+ end
192
+ unless @encryption_algorithm
193
+ raise RubySMB::Error::EncryptionError.new(
194
+ 'Unable to retrieve the encryption cipher list supported by the server from the Negotiate response'
195
+ )
116
196
  end
117
197
 
198
+ nc = response_packet.find_negotiate_context(
199
+ RubySMB::SMB2::NegotiateContext::SMB2_COMPRESSION_CAPABILITIES
200
+ )
201
+ @server_compression_algorithms = nc&.data&.compression_algorithms&.to_ary || []
118
202
  end
119
203
 
120
204
  # Create a {RubySMB::SMB1::Packet::NegotiateRequest} packet with the
@@ -127,10 +211,17 @@ module RubySMB
127
211
  # while being guaranteed to work with any modern Windows system. We can get more sophisticated
128
212
  # with switching this on and off at a later date if the need arises.
129
213
  packet.smb_header.flags2.extended_security = 1
214
+ # Recent Mac OS X requires the unicode flag to be set on the Negotiate
215
+ # SMB Header request, even if this packet does not contain string fields
216
+ # (see Flags2 SMB_FLAGS2_UNICODE definition in "2.2.3.1 The SMB Header"
217
+ # documentation:
218
+ # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/69a29f73-de0c-45a6-a1aa-8ceeea42217f
219
+ packet.smb_header.flags2.unicode = 1
130
220
  # There is no real good reason to ever send an SMB1 Negotiate packet
131
221
  # to Negotiate strictly SMB2, but the protocol WILL support it
132
222
  packet.add_dialect(SMB1_DIALECT_SMB1_DEFAULT) if smb1
133
223
  packet.add_dialect(SMB1_DIALECT_SMB2_DEFAULT) if smb2
224
+ packet.add_dialect(SMB1_DIALECT_SMB2_WILDCARD) if smb2 || smb3
134
225
  packet
135
226
  end
136
227
 
@@ -139,11 +230,60 @@ module RubySMB
139
230
  # may want to communicate over SMB1
140
231
  #
141
232
  # @ return [RubySMB::SMB2::Packet::NegotiateRequest] a completed SMB2 Negotiate Request packet
142
- def smb2_negotiate_request
233
+ def smb2_3_negotiate_request
143
234
  packet = RubySMB::SMB2::Packet::NegotiateRequest.new
144
235
  packet.security_mode.signing_enabled = 1
145
- packet.add_dialect(SMB2_DIALECT_DEFAULT)
146
236
  packet.client_guid = SecureRandom.random_bytes(16)
237
+ packet.set_dialects(SMB2_DIALECT_DEFAULT.map {|d| d.to_i(16)}) if smb2
238
+ packet = add_smb3_to_negotiate_request(packet) if smb3
239
+ packet
240
+ end
241
+
242
+ # This adds SMBv3 specific information: SMBv3 supported dialects,
243
+ # encryption capability, Negotiate Contexts if the dialect requires them
244
+ #
245
+ # @param packet [RubySMB::SMB2::Packet::NegotiateRequest] the NegotiateRequest
246
+ # to add SMB3 specific info to
247
+ # @param dialects [Array<String>] the dialects to negotiate. This must be
248
+ # an array of strings. Default is SMB3_DIALECT_DEFAULT
249
+ # @return [RubySMB::SMB2::Packet::NegotiateRequest] a completed SMB3 Negotiate Request packet
250
+ # @raise [ArgumentError] if dialects is not an array of strings
251
+ def add_smb3_to_negotiate_request(packet, dialects = SMB3_DIALECT_DEFAULT)
252
+ dialects.each do |dialect|
253
+ raise ArgumentError, 'Must be an array of strings' unless dialect.is_a? String
254
+ packet.add_dialect(dialect.to_i(16))
255
+ end
256
+ packet.capabilities.encryption = 1
257
+
258
+ if packet.dialects.include?(0x0311)
259
+ nc = RubySMB::SMB2::NegotiateContext.new(
260
+ context_type: RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
261
+ )
262
+ nc.data.hash_algorithms << RubySMB::SMB2::PreauthIntegrityCapabilities::SHA_512
263
+ nc.data.salt = SecureRandom.random_bytes(32)
264
+ packet.add_negotiate_context(nc)
265
+
266
+ @preauth_integrity_hash_value = "\x00" * 64
267
+ nc = RubySMB::SMB2::NegotiateContext.new(
268
+ context_type: RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
269
+ )
270
+ nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM
271
+ nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM
272
+ packet.add_negotiate_context(nc)
273
+
274
+ nc = RubySMB::SMB2::NegotiateContext.new(
275
+ context_type: RubySMB::SMB2::NegotiateContext::SMB2_COMPRESSION_CAPABILITIES
276
+ )
277
+ # Adding all possible compression algorithm even if we don't support
278
+ # them yet. This will force the server to disclose the support
279
+ # algorithms in the repsonse.
280
+ nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZNT1
281
+ nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZ77
282
+ nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZ77_Huffman
283
+ nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::Pattern_V1
284
+ packet.add_negotiate_context(nc)
285
+ end
286
+
147
287
  packet
148
288
  end
149
289
  end