ruby_smb 1.1.0 → 2.0.4

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 (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