ruby_smb 1.0.3 → 2.0.1

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 (200) 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 -2
  5. data/Gemfile +6 -2
  6. data/README.md +35 -47
  7. data/examples/enum_registry_key.rb +28 -0
  8. data/examples/enum_registry_values.rb +30 -0
  9. data/examples/negotiate.rb +51 -8
  10. data/examples/pipes.rb +2 -1
  11. data/examples/read_file_encryption.rb +56 -0
  12. data/examples/read_registry_key_value.rb +32 -0
  13. data/lib/ruby_smb.rb +4 -1
  14. data/lib/ruby_smb/client.rb +233 -22
  15. data/lib/ruby_smb/client/authentication.rb +70 -33
  16. data/lib/ruby_smb/client/echo.rb +20 -2
  17. data/lib/ruby_smb/client/encryption.rb +62 -0
  18. data/lib/ruby_smb/client/negotiation.rb +172 -24
  19. data/lib/ruby_smb/client/signing.rb +19 -0
  20. data/lib/ruby_smb/client/tree_connect.rb +24 -18
  21. data/lib/ruby_smb/client/utils.rb +8 -7
  22. data/lib/ruby_smb/client/winreg.rb +46 -0
  23. data/lib/ruby_smb/crypto.rb +30 -0
  24. data/lib/ruby_smb/dcerpc.rb +38 -0
  25. data/lib/ruby_smb/dcerpc/bind.rb +2 -2
  26. data/lib/ruby_smb/dcerpc/bind_ack.rb +2 -2
  27. data/lib/ruby_smb/dcerpc/error.rb +3 -0
  28. data/lib/ruby_smb/dcerpc/ndr.rb +95 -16
  29. data/lib/ruby_smb/dcerpc/pdu_header.rb +1 -1
  30. data/lib/ruby_smb/dcerpc/request.rb +28 -9
  31. data/lib/ruby_smb/dcerpc/rrp_unicode_string.rb +35 -0
  32. data/lib/ruby_smb/dcerpc/srvsvc.rb +10 -0
  33. data/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all.rb +9 -0
  34. data/lib/ruby_smb/dcerpc/winreg.rb +340 -0
  35. data/lib/ruby_smb/dcerpc/winreg/close_key_request.rb +24 -0
  36. data/lib/ruby_smb/dcerpc/winreg/close_key_response.rb +27 -0
  37. data/lib/ruby_smb/dcerpc/winreg/enum_key_request.rb +45 -0
  38. data/lib/ruby_smb/dcerpc/winreg/enum_key_response.rb +42 -0
  39. data/lib/ruby_smb/dcerpc/winreg/enum_value_request.rb +39 -0
  40. data/lib/ruby_smb/dcerpc/winreg/enum_value_response.rb +36 -0
  41. data/lib/ruby_smb/dcerpc/winreg/open_key_request.rb +34 -0
  42. data/lib/ruby_smb/dcerpc/winreg/open_key_response.rb +25 -0
  43. data/lib/ruby_smb/dcerpc/winreg/open_root_key_request.rb +43 -0
  44. data/lib/ruby_smb/dcerpc/winreg/open_root_key_response.rb +35 -0
  45. data/lib/ruby_smb/dcerpc/winreg/query_info_key_request.rb +27 -0
  46. data/lib/ruby_smb/dcerpc/winreg/query_info_key_response.rb +40 -0
  47. data/lib/ruby_smb/dcerpc/winreg/query_value_request.rb +39 -0
  48. data/lib/ruby_smb/dcerpc/winreg/query_value_response.rb +57 -0
  49. data/lib/ruby_smb/dcerpc/winreg/regsam.rb +40 -0
  50. data/lib/ruby_smb/dispatcher/socket.rb +4 -3
  51. data/lib/ruby_smb/error.rb +68 -2
  52. data/lib/ruby_smb/generic_packet.rb +33 -4
  53. data/lib/ruby_smb/smb1/commands.rb +1 -1
  54. data/lib/ruby_smb/smb1/file.rb +66 -15
  55. data/lib/ruby_smb/smb1/packet/close_request.rb +2 -5
  56. data/lib/ruby_smb/smb1/packet/close_response.rb +2 -1
  57. data/lib/ruby_smb/smb1/packet/echo_request.rb +2 -4
  58. data/lib/ruby_smb/smb1/packet/echo_response.rb +2 -1
  59. data/lib/ruby_smb/smb1/packet/empty_packet.rb +10 -1
  60. data/lib/ruby_smb/smb1/packet/logoff_request.rb +2 -4
  61. data/lib/ruby_smb/smb1/packet/logoff_response.rb +2 -1
  62. data/lib/ruby_smb/smb1/packet/negotiate_request.rb +2 -5
  63. data/lib/ruby_smb/smb1/packet/negotiate_response.rb +3 -7
  64. data/lib/ruby_smb/smb1/packet/negotiate_response_extended.rb +4 -4
  65. data/lib/ruby_smb/smb1/packet/nt_create_andx_request.rb +2 -4
  66. data/lib/ruby_smb/smb1/packet/nt_create_andx_response.rb +2 -1
  67. data/lib/ruby_smb/smb1/packet/nt_trans/create_request.rb +2 -1
  68. data/lib/ruby_smb/smb1/packet/nt_trans/create_response.rb +2 -1
  69. data/lib/ruby_smb/smb1/packet/nt_trans/request.rb +2 -4
  70. data/lib/ruby_smb/smb1/packet/nt_trans/response.rb +2 -1
  71. data/lib/ruby_smb/smb1/packet/read_andx_request.rb +2 -5
  72. data/lib/ruby_smb/smb1/packet/read_andx_response.rb +2 -1
  73. data/lib/ruby_smb/smb1/packet/session_setup_legacy_request.rb +2 -1
  74. data/lib/ruby_smb/smb1/packet/session_setup_legacy_response.rb +3 -2
  75. data/lib/ruby_smb/smb1/packet/session_setup_request.rb +2 -5
  76. data/lib/ruby_smb/smb1/packet/session_setup_response.rb +3 -2
  77. data/lib/ruby_smb/smb1/packet/trans/peek_nmpipe_request.rb +0 -1
  78. data/lib/ruby_smb/smb1/packet/trans/peek_nmpipe_response.rb +3 -2
  79. data/lib/ruby_smb/smb1/packet/trans/request.rb +2 -5
  80. data/lib/ruby_smb/smb1/packet/trans/response.rb +2 -1
  81. data/lib/ruby_smb/smb1/packet/trans/transact_nmpipe_request.rb +1 -1
  82. data/lib/ruby_smb/smb1/packet/trans/transact_nmpipe_response.rb +1 -1
  83. data/lib/ruby_smb/smb1/packet/trans2/find_first2_request.rb +2 -1
  84. data/lib/ruby_smb/smb1/packet/trans2/find_first2_response.rb +8 -2
  85. data/lib/ruby_smb/smb1/packet/trans2/find_next2_request.rb +2 -1
  86. data/lib/ruby_smb/smb1/packet/trans2/find_next2_response.rb +8 -2
  87. data/lib/ruby_smb/smb1/packet/trans2/open2_request.rb +2 -1
  88. data/lib/ruby_smb/smb1/packet/trans2/open2_response.rb +2 -1
  89. data/lib/ruby_smb/smb1/packet/trans2/request.rb +2 -4
  90. data/lib/ruby_smb/smb1/packet/trans2/request_secondary.rb +2 -4
  91. data/lib/ruby_smb/smb1/packet/trans2/response.rb +2 -1
  92. data/lib/ruby_smb/smb1/packet/trans2/set_file_information_request.rb +2 -1
  93. data/lib/ruby_smb/smb1/packet/trans2/set_file_information_response.rb +2 -1
  94. data/lib/ruby_smb/smb1/packet/tree_connect_request.rb +2 -4
  95. data/lib/ruby_smb/smb1/packet/tree_connect_response.rb +13 -3
  96. data/lib/ruby_smb/smb1/packet/tree_disconnect_request.rb +2 -4
  97. data/lib/ruby_smb/smb1/packet/tree_disconnect_response.rb +2 -1
  98. data/lib/ruby_smb/smb1/packet/write_andx_request.rb +3 -6
  99. data/lib/ruby_smb/smb1/packet/write_andx_response.rb +2 -1
  100. data/lib/ruby_smb/smb1/pipe.rb +87 -6
  101. data/lib/ruby_smb/smb1/tree.rb +50 -3
  102. data/lib/ruby_smb/smb2/bit_field/session_flags.rb +2 -1
  103. data/lib/ruby_smb/smb2/bit_field/share_flags.rb +6 -4
  104. data/lib/ruby_smb/smb2/file.rb +103 -25
  105. data/lib/ruby_smb/smb2/negotiate_context.rb +108 -0
  106. data/lib/ruby_smb/smb2/packet.rb +2 -0
  107. data/lib/ruby_smb/smb2/packet/close_request.rb +2 -4
  108. data/lib/ruby_smb/smb2/packet/close_response.rb +2 -1
  109. data/lib/ruby_smb/smb2/packet/compression_transform_header.rb +41 -0
  110. data/lib/ruby_smb/smb2/packet/create_request.rb +2 -4
  111. data/lib/ruby_smb/smb2/packet/create_response.rb +2 -1
  112. data/lib/ruby_smb/smb2/packet/echo_request.rb +2 -4
  113. data/lib/ruby_smb/smb2/packet/echo_response.rb +2 -1
  114. data/lib/ruby_smb/smb2/packet/error_packet.rb +15 -3
  115. data/lib/ruby_smb/smb2/packet/ioctl_request.rb +2 -5
  116. data/lib/ruby_smb/smb2/packet/ioctl_response.rb +2 -1
  117. data/lib/ruby_smb/smb2/packet/logoff_request.rb +2 -4
  118. data/lib/ruby_smb/smb2/packet/logoff_response.rb +2 -1
  119. data/lib/ruby_smb/smb2/packet/negotiate_request.rb +51 -17
  120. data/lib/ruby_smb/smb2/packet/negotiate_response.rb +52 -5
  121. data/lib/ruby_smb/smb2/packet/query_directory_request.rb +2 -4
  122. data/lib/ruby_smb/smb2/packet/query_directory_response.rb +8 -2
  123. data/lib/ruby_smb/smb2/packet/read_request.rb +2 -4
  124. data/lib/ruby_smb/smb2/packet/read_response.rb +2 -1
  125. data/lib/ruby_smb/smb2/packet/session_setup_request.rb +2 -5
  126. data/lib/ruby_smb/smb2/packet/session_setup_response.rb +2 -1
  127. data/lib/ruby_smb/smb2/packet/set_info_request.rb +2 -4
  128. data/lib/ruby_smb/smb2/packet/set_info_response.rb +2 -1
  129. data/lib/ruby_smb/smb2/packet/transform_header.rb +84 -0
  130. data/lib/ruby_smb/smb2/packet/tree_connect_request.rb +93 -10
  131. data/lib/ruby_smb/smb2/packet/tree_connect_response.rb +10 -22
  132. data/lib/ruby_smb/smb2/packet/tree_disconnect_request.rb +2 -4
  133. data/lib/ruby_smb/smb2/packet/tree_disconnect_response.rb +2 -1
  134. data/lib/ruby_smb/smb2/packet/write_request.rb +2 -4
  135. data/lib/ruby_smb/smb2/packet/write_response.rb +2 -1
  136. data/lib/ruby_smb/smb2/pipe.rb +86 -12
  137. data/lib/ruby_smb/smb2/smb2_header.rb +1 -1
  138. data/lib/ruby_smb/smb2/tree.rb +65 -21
  139. data/lib/ruby_smb/version.rb +1 -1
  140. data/ruby_smb.gemspec +5 -3
  141. data/spec/lib/ruby_smb/client_spec.rb +1612 -108
  142. data/spec/lib/ruby_smb/crypto_spec.rb +25 -0
  143. data/spec/lib/ruby_smb/dcerpc/bind_ack_spec.rb +2 -2
  144. data/spec/lib/ruby_smb/dcerpc/bind_spec.rb +2 -2
  145. data/spec/lib/ruby_smb/dcerpc/ndr_spec.rb +410 -0
  146. data/spec/lib/ruby_smb/dcerpc/request_spec.rb +50 -7
  147. data/spec/lib/ruby_smb/dcerpc/rrp_unicode_string_spec.rb +98 -0
  148. data/spec/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all_spec.rb +13 -0
  149. data/spec/lib/ruby_smb/dcerpc/srvsvc_spec.rb +60 -0
  150. data/spec/lib/ruby_smb/dcerpc/winreg/close_key_request_spec.rb +28 -0
  151. data/spec/lib/ruby_smb/dcerpc/winreg/close_key_response_spec.rb +36 -0
  152. data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_request_spec.rb +108 -0
  153. data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_response_spec.rb +97 -0
  154. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_request_spec.rb +94 -0
  155. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_response_spec.rb +82 -0
  156. data/spec/lib/ruby_smb/dcerpc/winreg/open_key_request_spec.rb +74 -0
  157. data/spec/lib/ruby_smb/dcerpc/winreg/open_key_response_spec.rb +35 -0
  158. data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_request_spec.rb +90 -0
  159. data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_response_spec.rb +38 -0
  160. data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_request_spec.rb +39 -0
  161. data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_response_spec.rb +113 -0
  162. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_request_spec.rb +88 -0
  163. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_response_spec.rb +150 -0
  164. data/spec/lib/ruby_smb/dcerpc/winreg/regsam_spec.rb +32 -0
  165. data/spec/lib/ruby_smb/dcerpc/winreg_spec.rb +710 -0
  166. data/spec/lib/ruby_smb/dcerpc_spec.rb +81 -0
  167. data/spec/lib/ruby_smb/dispatcher/socket_spec.rb +2 -2
  168. data/spec/lib/ruby_smb/error_spec.rb +59 -0
  169. data/spec/lib/ruby_smb/generic_packet_spec.rb +52 -4
  170. data/spec/lib/ruby_smb/smb1/file_spec.rb +191 -2
  171. data/spec/lib/ruby_smb/smb1/packet/empty_packet_spec.rb +68 -0
  172. data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_request_spec.rb +2 -2
  173. data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_response_spec.rb +2 -2
  174. data/spec/lib/ruby_smb/smb1/packet/session_setup_request_spec.rb +2 -2
  175. data/spec/lib/ruby_smb/smb1/packet/session_setup_response_spec.rb +1 -1
  176. data/spec/lib/ruby_smb/smb1/packet/trans2/find_first2_response_spec.rb +11 -2
  177. data/spec/lib/ruby_smb/smb1/packet/trans2/find_next2_response_spec.rb +11 -2
  178. data/spec/lib/ruby_smb/smb1/packet/tree_connect_response_spec.rb +40 -0
  179. data/spec/lib/ruby_smb/smb1/pipe_spec.rb +272 -149
  180. data/spec/lib/ruby_smb/smb1/tree_spec.rb +44 -7
  181. data/spec/lib/ruby_smb/smb2/bit_field/session_flags_spec.rb +9 -0
  182. data/spec/lib/ruby_smb/smb2/bit_field/share_flags_spec.rb +27 -0
  183. data/spec/lib/ruby_smb/smb2/file_spec.rb +323 -6
  184. data/spec/lib/ruby_smb/smb2/negotiate_context_spec.rb +332 -0
  185. data/spec/lib/ruby_smb/smb2/packet/compression_transform_header_spec.rb +108 -0
  186. data/spec/lib/ruby_smb/smb2/packet/error_packet_spec.rb +78 -0
  187. data/spec/lib/ruby_smb/smb2/packet/negotiate_request_spec.rb +138 -3
  188. data/spec/lib/ruby_smb/smb2/packet/negotiate_response_spec.rb +120 -2
  189. data/spec/lib/ruby_smb/smb2/packet/query_directory_response_spec.rb +8 -0
  190. data/spec/lib/ruby_smb/smb2/packet/transform_header_spec.rb +220 -0
  191. data/spec/lib/ruby_smb/smb2/packet/tree_connect_request_spec.rb +339 -9
  192. data/spec/lib/ruby_smb/smb2/packet/tree_connect_response_spec.rb +3 -22
  193. data/spec/lib/ruby_smb/smb2/pipe_spec.rb +286 -149
  194. data/spec/lib/ruby_smb/smb2/smb2_header_spec.rb +2 -2
  195. data/spec/lib/ruby_smb/smb2/tree_spec.rb +261 -2
  196. metadata +191 -83
  197. metadata.gz.sig +0 -0
  198. data/lib/ruby_smb/smb1/dcerpc.rb +0 -67
  199. data/lib/ruby_smb/smb2/dcerpc.rb +0 -70
  200. data/spec/lib/ruby_smb/smb1/packet/error_packet_spec.rb +0 -37
@@ -16,7 +16,16 @@ module RubySMB
16
16
  (count - 1).times do
17
17
  raw_response = dispatcher.recv_packet
18
18
  end
19
- RubySMB::SMB1::Packet::EchoResponse.read(raw_response)
19
+ response = RubySMB::SMB1::Packet::EchoResponse.read(raw_response)
20
+ unless response.valid?
21
+ raise RubySMB::Error::InvalidPacket.new(
22
+ expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
23
+ expected_cmd: RubySMB::SMB1::Packet::EchoResponse::COMMAND,
24
+ received_proto: response.smb_header.protocol,
25
+ received_cmd: response.smb_header.command
26
+ )
27
+ end
28
+ response
20
29
  end
21
30
 
22
31
  # Sends an ECHO request packet and returns the
@@ -26,7 +35,16 @@ module RubySMB
26
35
  def smb2_echo
27
36
  request = RubySMB::SMB2::Packet::EchoRequest.new
28
37
  raw_response = send_recv(request)
29
- RubySMB::SMB2::Packet::EchoResponse.read(raw_response)
38
+ response = RubySMB::SMB2::Packet::EchoResponse.read(raw_response)
39
+ unless response.valid?
40
+ raise RubySMB::Error::InvalidPacket.new(
41
+ expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
42
+ expected_cmd: RubySMB::SMB2::Packet::EchoResponse::COMMAND,
43
+ received_proto: response.smb2_header.protocol,
44
+ received_cmd: response.smb2_header.command
45
+ )
46
+ end
47
+ response
30
48
  end
31
49
  end
32
50
  end
@@ -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,30 @@ 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
+
28
+ # If the response contains the SMB2 wildcard revision number dialect;
29
+ # it indicates that the server implements SMB 2.1 or future dialect
30
+ # revisions and expects the client to send a subsequent SMB2 Negotiate
31
+ # request to negotiate the actual SMB 2 Protocol revision to be used.
32
+ # The wildcard revision number is sent only in response to a
33
+ # multi-protocol negotiate request with the "SMB 2.???" dialect string.
34
+ if @dialect == '0x02ff'
35
+ self.smb2_message_id += 1
36
+ version = negotiate
37
+ end
38
+ version
39
+ rescue RubySMB::Error::InvalidPacket, Errno::ECONNRESET, RubySMB::Error::CommunicationError => e
40
+ version = request_packet.packet_smb_version
41
+ version = 'SMB3' if version == 'SMB2' && !@smb2 && @smb3
42
+ version = 'SMB2 or SMB3' if version == 'SMB2' && @smb2 && @smb3
43
+ error = "Unable to negotiate #{version} with the remote host: #{e.message}"
24
44
  raise RubySMB::Error::NegotiationFailure, error
25
45
  end
26
46
 
@@ -32,8 +52,8 @@ module RubySMB
32
52
  def negotiate_request
33
53
  if smb1
34
54
  smb1_negotiate_request
35
- elsif smb2
36
- smb2_negotiate_request
55
+ else
56
+ smb2_3_negotiate_request
37
57
  end
38
58
  end
39
59
 
@@ -47,23 +67,38 @@ module RubySMB
47
67
  def negotiate_response(raw_data)
48
68
  response = nil
49
69
  if smb1
50
- begin
51
- packet = RubySMB::SMB1::Packet::NegotiateResponseExtended.read raw_data
52
- rescue StandardError => e
53
- raise RubySMB::Error::InvalidPacket, "Not a Valid SMB1 Negoitate Response #{e.message}"
54
- end
70
+ packet = RubySMB::SMB1::Packet::NegotiateResponseExtended.read raw_data
55
71
  response = packet if packet.valid?
56
72
  end
57
- if smb2 && response.nil?
58
- begin
59
- packet = RubySMB::SMB2::Packet::NegotiateResponse.read raw_data
60
- rescue StandardError => e
61
- raise RubySMB::Error::InvalidPacket, "Not a Valid SMB2 Negoitate Response #{e.message}"
62
- end
63
- response = packet
73
+ if (smb2 || smb3) && response.nil?
74
+ packet = RubySMB::SMB2::Packet::NegotiateResponse.read raw_data
75
+ response = packet if packet.valid?
64
76
  end
65
77
  if response.nil?
66
- raise RubySMB::Error::InvalidPacket, 'No Valid Negotiate Response found'
78
+ if packet.packet_smb_version == 'SMB1'
79
+ extended_security = if packet.is_a? RubySMB::SMB1::Packet::NegotiateResponseExtended
80
+ packet.parameter_block.capabilities.extended_security
81
+ else
82
+ "n/a"
83
+ end
84
+ raise RubySMB::Error::InvalidPacket.new(
85
+ expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
86
+ expected_cmd: RubySMB::SMB1::Packet::NegotiateResponseExtended::COMMAND,
87
+ expected_custom: "extended_security=1",
88
+ received_proto: packet.smb_header.protocol,
89
+ received_cmd: packet.smb_header.command,
90
+ received_custom: "extended_security=#{extended_security}"
91
+ )
92
+ elsif packet.packet_smb_version == 'SMB2'
93
+ raise RubySMB::Error::InvalidPacket.new(
94
+ expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
95
+ expected_cmd: RubySMB::SMB2::Packet::NegotiateResponse::COMMAND,
96
+ received_proto: packet.smb2_header.protocol,
97
+ received_cmd: packet.smb2_header.command
98
+ )
99
+ else
100
+ raise RubySMB::Error::InvalidPacket, 'Unknown SMB protocol version'
101
+ end
67
102
  end
68
103
  response
69
104
  end
@@ -80,26 +115,83 @@ module RubySMB
80
115
  when RubySMB::SMB1::Packet::NegotiateResponseExtended
81
116
  self.smb1 = true
82
117
  self.smb2 = false
118
+ self.smb3 = false
83
119
  self.signing_required = packet.parameter_block.security_mode.security_signatures_required == 1
84
120
  self.dialect = packet.negotiated_dialect.to_s
85
121
  # MaxBufferSize is largest message server will receive, measured from start of the SMB header. Subtract 260
86
122
  # for protocol overhead. Then this value can be used for max read/write size without having to factor in
87
123
  # protocol overhead every time.
88
124
  self.server_max_buffer_size = packet.parameter_block.max_buffer_size - 260
125
+ self.negotiated_smb_version = 1
89
126
  'SMB1'
90
127
  when RubySMB::SMB2::Packet::NegotiateResponse
91
128
  self.smb1 = false
92
- self.smb2 = true
93
- self.signing_required = packet.security_mode.signing_required == 1
129
+ unless packet.dialect_revision.to_i == 0x02ff
130
+ self.smb2 = packet.dialect_revision.to_i >= 0x0200 && packet.dialect_revision.to_i < 0x0300
131
+ self.smb3 = packet.dialect_revision.to_i >= 0x0300 && packet.dialect_revision.to_i < 0x0400
132
+ # Only enable session encryption if the server supports it
133
+ @session_encrypt_data = self.smb3 && @session_encrypt_data && packet.capabilities.encryption == 1
134
+ end
135
+ self.signing_required = packet.security_mode.signing_required == 1 if self.smb2 || self.smb3
94
136
  self.dialect = "0x%04x" % packet.dialect_revision
95
137
  self.server_max_read_size = packet.max_read_size
96
138
  self.server_max_write_size = packet.max_write_size
97
139
  self.server_max_transact_size = packet.max_transact_size
98
140
  # This value is used in SMB1 only but calculate a valid value anyway
99
141
  self.server_max_buffer_size = [self.server_max_read_size, self.server_max_write_size, self.server_max_transact_size].min
100
- 'SMB2'
142
+ self.negotiated_smb_version = self.smb2 ? 2 : 3
143
+ self.server_guid = packet.server_guid
144
+ self.server_start_time = packet.server_start_time.to_time if packet.server_start_time != 0
145
+ self.server_system_time = packet.system_time.to_time if packet.system_time != 0
146
+ return "SMB#{self.negotiated_smb_version}"
147
+ else
148
+ error = 'Unable to negotiate with remote host'
149
+ if packet.status_code == WindowsError::NTStatus::STATUS_NOT_SUPPORTED
150
+ error << ", SMB2" if @smb2
151
+ error << ", SMB3" if @smb3
152
+ error << ' not supported'
153
+ end
154
+ raise RubySMB::Error::NegotiationFailure, error
101
155
  end
156
+ end
102
157
 
158
+ def parse_smb3_encryption_data(request_packet, response_packet)
159
+ nc = response_packet.find_negotiate_context(
160
+ RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
161
+ )
162
+ @preauth_integrity_hash_algorithm = RubySMB::SMB2::PreauthIntegrityCapabilities::HASH_ALGORITM_MAP[nc&.data&.hash_algorithms&.first]
163
+ unless @preauth_integrity_hash_algorithm
164
+ raise RubySMB::Error::EncryptionError.new(
165
+ 'Unable to retrieve the Preauth Integrity Hash Algorithm from the Negotiate response'
166
+ )
167
+ end
168
+ # Set the encryption the client will use, prioritizing AES_128_GCM over AES_128_CCM
169
+ nc = response_packet.find_negotiate_context(
170
+ RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
171
+ )
172
+ @server_encryption_algorithms = nc&.data&.ciphers&.to_ary
173
+ if @server_encryption_algorithms.nil? || @server_encryption_algorithms.empty?
174
+ raise RubySMB::Error::EncryptionError.new(
175
+ 'Unable to retrieve the encryption cipher list supported by the server from the Negotiate response'
176
+ )
177
+ end
178
+ if @server_encryption_algorithms.include?(RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM)
179
+ @encryption_algorithm = RubySMB::SMB2::EncryptionCapabilities::ENCRYPTION_ALGORITHM_MAP[RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM]
180
+ else
181
+ @encryption_algorithm = RubySMB::SMB2::EncryptionCapabilities::ENCRYPTION_ALGORITHM_MAP[@server_encryption_algorithms.first]
182
+ end
183
+ unless @encryption_algorithm
184
+ raise RubySMB::Error::EncryptionError.new(
185
+ 'Unable to retrieve the encryption cipher list supported by the server from the Negotiate response'
186
+ )
187
+ end
188
+ update_preauth_hash(request_packet)
189
+ update_preauth_hash(response_packet)
190
+
191
+ nc = response_packet.find_negotiate_context(
192
+ RubySMB::SMB2::NegotiateContext::SMB2_COMPRESSION_CAPABILITIES
193
+ )
194
+ @server_compression_algorithms = nc&.data&.compression_algorithms&.to_ary || []
103
195
  end
104
196
 
105
197
  # Create a {RubySMB::SMB1::Packet::NegotiateRequest} packet with the
@@ -112,10 +204,17 @@ module RubySMB
112
204
  # while being guaranteed to work with any modern Windows system. We can get more sophisticated
113
205
  # with switching this on and off at a later date if the need arises.
114
206
  packet.smb_header.flags2.extended_security = 1
207
+ # Recent Mac OS X requires the unicode flag to be set on the Negotiate
208
+ # SMB Header request, even if this packet does not contain string fields
209
+ # (see Flags2 SMB_FLAGS2_UNICODE definition in "2.2.3.1 The SMB Header"
210
+ # documentation:
211
+ # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/69a29f73-de0c-45a6-a1aa-8ceeea42217f
212
+ packet.smb_header.flags2.unicode = 1
115
213
  # There is no real good reason to ever send an SMB1 Negotiate packet
116
214
  # to Negotiate strictly SMB2, but the protocol WILL support it
117
215
  packet.add_dialect(SMB1_DIALECT_SMB1_DEFAULT) if smb1
118
216
  packet.add_dialect(SMB1_DIALECT_SMB2_DEFAULT) if smb2
217
+ packet.add_dialect(SMB1_DIALECT_SMB2_WILDCARD) if smb2 || smb3
119
218
  packet
120
219
  end
121
220
 
@@ -124,11 +223,60 @@ module RubySMB
124
223
  # may want to communicate over SMB1
125
224
  #
126
225
  # @ return [RubySMB::SMB2::Packet::NegotiateRequest] a completed SMB2 Negotiate Request packet
127
- def smb2_negotiate_request
226
+ def smb2_3_negotiate_request
128
227
  packet = RubySMB::SMB2::Packet::NegotiateRequest.new
129
228
  packet.security_mode.signing_enabled = 1
130
- packet.add_dialect(SMB2_DIALECT_DEFAULT)
131
229
  packet.client_guid = SecureRandom.random_bytes(16)
230
+ packet.set_dialects(SMB2_DIALECT_DEFAULT.map {|d| d.to_i(16)}) if smb2
231
+ packet = add_smb3_to_negotiate_request(packet) if smb3
232
+ packet
233
+ end
234
+
235
+ # This adds SMBv3 specific information: SMBv3 supported dialects,
236
+ # encryption capability, Negotiate Contexts if the dialect requires them
237
+ #
238
+ # @param packet [RubySMB::SMB2::Packet::NegotiateRequest] the NegotiateRequest
239
+ # to add SMB3 specific info to
240
+ # @param dialects [Array<String>] the dialects to negotiate. This must be
241
+ # an array of strings. Default is SMB3_DIALECT_DEFAULT
242
+ # @return [RubySMB::SMB2::Packet::NegotiateRequest] a completed SMB3 Negotiate Request packet
243
+ # @raise [ArgumentError] if dialects is not an array of strings
244
+ def add_smb3_to_negotiate_request(packet, dialects = SMB3_DIALECT_DEFAULT)
245
+ dialects.each do |dialect|
246
+ raise ArgumentError, 'Must be an array of strings' unless dialect.is_a? String
247
+ packet.add_dialect(dialect.to_i(16))
248
+ end
249
+ packet.capabilities.encryption = 1
250
+
251
+ if packet.dialects.include?(0x0311)
252
+ nc = RubySMB::SMB2::NegotiateContext.new(
253
+ context_type: RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
254
+ )
255
+ nc.data.hash_algorithms << RubySMB::SMB2::PreauthIntegrityCapabilities::SHA_512
256
+ nc.data.salt = SecureRandom.random_bytes(32)
257
+ packet.add_negotiate_context(nc)
258
+
259
+ @preauth_integrity_hash_value = "\x00" * 64
260
+ nc = RubySMB::SMB2::NegotiateContext.new(
261
+ context_type: RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
262
+ )
263
+ nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM
264
+ nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM
265
+ packet.add_negotiate_context(nc)
266
+
267
+ nc = RubySMB::SMB2::NegotiateContext.new(
268
+ context_type: RubySMB::SMB2::NegotiateContext::SMB2_COMPRESSION_CAPABILITIES
269
+ )
270
+ # Adding all possible compression algorithm even if we don't support
271
+ # them yet. This will force the server to disclose the support
272
+ # algorithms in the repsonse.
273
+ nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZNT1
274
+ nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZ77
275
+ nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZ77_Huffman
276
+ nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::Pattern_V1
277
+ packet.add_negotiate_context(nc)
278
+ end
279
+
132
280
  packet
133
281
  end
134
282
  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
@@ -17,11 +17,7 @@ module RubySMB
17
17
  request.smb_header.tid = 65_535
18
18
  request.data_block.path = share
19
19
  raw_response = send_recv(request)
20
- begin
21
- response = RubySMB::SMB1::Packet::TreeConnectResponse.read(raw_response)
22
- rescue EOFError
23
- response = RubySMB::SMB1::Packet::EmptyPacket.read(raw_response)
24
- end
20
+ response = RubySMB::SMB1::Packet::TreeConnectResponse.read(raw_response)
25
21
  smb1_tree_from_response(share, response)
26
22
  end
27
23
 
@@ -30,12 +26,19 @@ module RubySMB
30
26
  # @param share [String] the share path to connect to
31
27
  # @param response [RubySMB::SMB1::Packet::TreeConnectResponse] the response packet to parse into our Tree
32
28
  # @return [RubySMB::SMB1::Tree]
29
+ # @raise [RubySMB::Error::InvalidPacket] if the response command is not a TreeConnectResponse packet
30
+ # @raise [RubySMB::Error::UnexpectedStatusCode] if the response status code is not STATUS_SUCCESS
33
31
  def smb1_tree_from_response(share, response)
34
- unless response.smb_header.command == RubySMB::SMB1::Commands::SMB_COM_TREE_CONNECT
35
- raise RubySMB::Error::InvalidPacket, 'Not a TreeConnectResponse'
32
+ unless response.valid?
33
+ raise RubySMB::Error::InvalidPacket.new(
34
+ expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
35
+ expected_cmd: RubySMB::SMB1::Packet::TreeConnectResponse::COMMAND,
36
+ received_proto: response.smb_header.protocol,
37
+ received_cmd: response.smb_header.command
38
+ )
36
39
  end
37
40
  unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
38
- raise RubySMB::Error::UnexpectedStatusCode, response.status_code.name
41
+ raise RubySMB::Error::UnexpectedStatusCode, response.status_code
39
42
  end
40
43
  RubySMB::SMB1::Tree.new(client: self, share: share, response: response)
41
44
  end
@@ -52,13 +55,9 @@ module RubySMB
52
55
  def smb2_tree_connect(share)
53
56
  request = RubySMB::SMB2::Packet::TreeConnectRequest.new
54
57
  request.smb2_header.tree_id = 65_535
55
- request.encode_path(share)
58
+ request.path = share
56
59
  raw_response = send_recv(request)
57
- begin
58
- response = RubySMB::SMB2::Packet::TreeConnectResponse.read(raw_response)
59
- rescue EOFError
60
- response = RubySMB::SMB2::Packet::ErrorPacket.read(raw_response)
61
- end
60
+ response = RubySMB::SMB2::Packet::TreeConnectResponse.read(raw_response)
62
61
  smb2_tree_from_response(share, response)
63
62
  end
64
63
 
@@ -67,14 +66,21 @@ module RubySMB
67
66
  # @param share [String] the share path to connect to
68
67
  # @param response [RubySMB::SMB2::Packet::TreeConnectResponse] the response packet to parse into our Tree
69
68
  # @return [RubySMB::SMB2::Tree]
69
+ # @raise [RubySMB::Error::InvalidPacket] if the response command is not a TreeConnectResponse packet
70
+ # @raise [RubySMB::Error::UnexpectedStatusCode] if the response status code is not STATUS_SUCCESS
70
71
  def smb2_tree_from_response(share, response)
71
- unless response.smb2_header.command == RubySMB::SMB2::Commands::TREE_CONNECT
72
- raise RubySMB::Error::InvalidPacket, 'Not a TreeConnectResponse'
72
+ unless response.valid?
73
+ raise RubySMB::Error::InvalidPacket.new(
74
+ expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
75
+ expected_cmd: RubySMB::SMB2::Packet::TreeConnectResponse::COMMAND,
76
+ received_proto: response.smb2_header.protocol,
77
+ received_cmd: response.smb2_header.command
78
+ )
73
79
  end
74
80
  unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
75
- raise RubySMB::Error::UnexpectedStatusCode, response.status_code.name
81
+ raise RubySMB::Error::UnexpectedStatusCode, response.status_code
76
82
  end
77
- 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)
78
84
  end
79
85
  end
80
86
  end