ruby_smb 1.0.2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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 +200 -20
  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 +160 -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 +5 -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 +41 -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 +51 -4
  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 +1563 -104
  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 +3 -1
  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,29 @@ module RubySMB
17
17
  # internally to be able to retrieve the negotiated dialect later on.
18
18
  # This is only valid for SMB1.
19
19
  response_packet.dialects = request_packet.dialects if response_packet.respond_to? :dialects=
20
- parse_negotiate_response(response_packet)
21
- rescue RubySMB::Error::InvalidPacket, Errno::ECONNRESET
22
- error = 'Unable to Negotiate with remote host'
23
- error << ', SMB1 may be disabled' if smb1 && !smb2
20
+ version = parse_negotiate_response(response_packet)
21
+ case @dialect
22
+ when '0x0300', '0x0302'
23
+ @encryption_algorithm = RubySMB::SMB2::EncryptionCapabilities::ENCRYPTION_ALGORITHM_MAP[RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM]
24
+ when '0x0311'
25
+ parse_smb3_encryption_data(request_packet, response_packet)
26
+ end
27
+ # If the response contains the SMB2 wildcard revision number dialect;
28
+ # it indicates that the server implements SMB 2.1 or future dialect
29
+ # revisions and expects the client to send a subsequent SMB2 Negotiate
30
+ # request to negotiate the actual SMB 2 Protocol revision to be used.
31
+ # The wildcard revision number is sent only in response to a
32
+ # multi-protocol negotiate request with the "SMB 2.???" dialect string.
33
+ if @dialect == '0x02ff'
34
+ self.smb2_message_id += 1
35
+ version = negotiate
36
+ end
37
+ version
38
+ rescue RubySMB::Error::InvalidPacket, Errno::ECONNRESET, RubySMB::Error::CommunicationError => e
39
+ version = request_packet.packet_smb_version
40
+ version = 'SMB3' if version == 'SMB2' && !@smb2 && @smb3
41
+ version = 'SMB2 or SMB3' if version == 'SMB2' && @smb2 && @smb3
42
+ error = "Unable to negotiate #{version} with the remote host: #{e.message}"
24
43
  raise RubySMB::Error::NegotiationFailure, error
25
44
  end
26
45
 
@@ -32,8 +51,8 @@ module RubySMB
32
51
  def negotiate_request
33
52
  if smb1
34
53
  smb1_negotiate_request
35
- elsif smb2
36
- smb2_negotiate_request
54
+ else
55
+ smb2_3_negotiate_request
37
56
  end
38
57
  end
39
58
 
@@ -47,23 +66,38 @@ module RubySMB
47
66
  def negotiate_response(raw_data)
48
67
  response = nil
49
68
  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
69
+ packet = RubySMB::SMB1::Packet::NegotiateResponseExtended.read raw_data
55
70
  response = packet if packet.valid?
56
71
  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
72
+ if (smb2 || smb3) && response.nil?
73
+ packet = RubySMB::SMB2::Packet::NegotiateResponse.read raw_data
74
+ response = packet if packet.valid?
64
75
  end
65
76
  if response.nil?
66
- raise RubySMB::Error::InvalidPacket, 'No Valid Negotiate Response found'
77
+ if packet.packet_smb_version == 'SMB1'
78
+ extended_security = if packet.is_a? RubySMB::SMB1::Packet::NegotiateResponseExtended
79
+ packet.parameter_block.capabilities.extended_security
80
+ else
81
+ "n/a"
82
+ end
83
+ raise RubySMB::Error::InvalidPacket.new(
84
+ expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
85
+ expected_cmd: RubySMB::SMB1::Packet::NegotiateResponseExtended::COMMAND,
86
+ expected_custom: "extended_security=1",
87
+ received_proto: packet.smb_header.protocol,
88
+ received_cmd: packet.smb_header.command,
89
+ received_custom: "extended_security=#{extended_security}"
90
+ )
91
+ elsif packet.packet_smb_version == 'SMB2'
92
+ raise RubySMB::Error::InvalidPacket.new(
93
+ expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
94
+ expected_cmd: RubySMB::SMB2::Packet::NegotiateResponse::COMMAND,
95
+ received_proto: packet.smb2_header.protocol,
96
+ received_cmd: packet.smb2_header.command
97
+ )
98
+ else
99
+ raise RubySMB::Error::InvalidPacket, 'Unknown SMB protocol version'
100
+ end
67
101
  end
68
102
  response
69
103
  end
@@ -80,26 +114,78 @@ module RubySMB
80
114
  when RubySMB::SMB1::Packet::NegotiateResponseExtended
81
115
  self.smb1 = true
82
116
  self.smb2 = false
117
+ self.smb3 = false
83
118
  self.signing_required = packet.parameter_block.security_mode.security_signatures_required == 1
84
119
  self.dialect = packet.negotiated_dialect.to_s
85
120
  # MaxBufferSize is largest message server will receive, measured from start of the SMB header. Subtract 260
86
121
  # for protocol overhead. Then this value can be used for max read/write size without having to factor in
87
122
  # protocol overhead every time.
88
123
  self.server_max_buffer_size = packet.parameter_block.max_buffer_size - 260
124
+ self.negotiated_smb_version = 1
89
125
  'SMB1'
90
126
  when RubySMB::SMB2::Packet::NegotiateResponse
91
127
  self.smb1 = false
92
- self.smb2 = true
93
- self.signing_required = packet.security_mode.signing_required == 1
128
+ unless packet.dialect_revision.to_i == 0x02ff
129
+ self.smb2 = packet.dialect_revision.to_i >= 0x0200 && packet.dialect_revision.to_i < 0x0300
130
+ self.smb3 = packet.dialect_revision.to_i >= 0x0300 && packet.dialect_revision.to_i < 0x0400
131
+ end
132
+ self.signing_required = packet.security_mode.signing_required == 1 if self.smb2 || self.smb3
94
133
  self.dialect = "0x%04x" % packet.dialect_revision
95
134
  self.server_max_read_size = packet.max_read_size
96
135
  self.server_max_write_size = packet.max_write_size
97
136
  self.server_max_transact_size = packet.max_transact_size
98
137
  # This value is used in SMB1 only but calculate a valid value anyway
99
138
  self.server_max_buffer_size = [self.server_max_read_size, self.server_max_write_size, self.server_max_transact_size].min
100
- 'SMB2'
139
+ self.negotiated_smb_version = self.smb2 ? 2 : 3
140
+ return "SMB#{self.negotiated_smb_version}"
141
+ else
142
+ error = 'Unable to negotiate with remote host'
143
+ if packet.status_code == WindowsError::NTStatus::STATUS_NOT_SUPPORTED
144
+ error << ", SMB2" if @smb2
145
+ error << ", SMB3" if @smb3
146
+ error << ' not supported'
147
+ end
148
+ raise RubySMB::Error::NegotiationFailure, error
149
+ end
150
+ end
151
+
152
+ def parse_smb3_encryption_data(request_packet, response_packet)
153
+ nc = response_packet.find_negotiate_context(
154
+ RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
155
+ )
156
+ @preauth_integrity_hash_algorithm = RubySMB::SMB2::PreauthIntegrityCapabilities::HASH_ALGORITM_MAP[nc&.data&.hash_algorithms&.first]
157
+ unless @preauth_integrity_hash_algorithm
158
+ raise RubySMB::Error::EncryptionError.new(
159
+ 'Unable to retrieve the Preauth Integrity Hash Algorithm from the Negotiate response'
160
+ )
161
+ end
162
+ # Set the encryption the client will use, prioritizing AES_128_GCM over AES_128_CCM
163
+ nc = response_packet.find_negotiate_context(
164
+ RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
165
+ )
166
+ @server_encryption_algorithms = nc&.data&.ciphers&.to_ary
167
+ if @server_encryption_algorithms.nil? || @server_encryption_algorithms.empty?
168
+ raise RubySMB::Error::EncryptionError.new(
169
+ 'Unable to retrieve the encryption cipher list supported by the server from the Negotiate response'
170
+ )
101
171
  end
172
+ if @server_encryption_algorithms.include?(RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM)
173
+ @encryption_algorithm = RubySMB::SMB2::EncryptionCapabilities::ENCRYPTION_ALGORITHM_MAP[RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM]
174
+ else
175
+ @encryption_algorithm = RubySMB::SMB2::EncryptionCapabilities::ENCRYPTION_ALGORITHM_MAP[@server_encryption_algorithms.first]
176
+ end
177
+ unless @encryption_algorithm
178
+ raise RubySMB::Error::EncryptionError.new(
179
+ 'Unable to retrieve the encryption cipher list supported by the server from the Negotiate response'
180
+ )
181
+ end
182
+ update_preauth_hash(request_packet)
183
+ update_preauth_hash(response_packet)
102
184
 
185
+ nc = response_packet.find_negotiate_context(
186
+ RubySMB::SMB2::NegotiateContext::SMB2_COMPRESSION_CAPABILITIES
187
+ )
188
+ @server_compression_algorithms = nc&.data&.compression_algorithms&.to_ary || []
103
189
  end
104
190
 
105
191
  # Create a {RubySMB::SMB1::Packet::NegotiateRequest} packet with the
@@ -116,6 +202,7 @@ module RubySMB
116
202
  # to Negotiate strictly SMB2, but the protocol WILL support it
117
203
  packet.add_dialect(SMB1_DIALECT_SMB1_DEFAULT) if smb1
118
204
  packet.add_dialect(SMB1_DIALECT_SMB2_DEFAULT) if smb2
205
+ packet.add_dialect(SMB1_DIALECT_SMB2_WILDCARD) if smb2 || smb3
119
206
  packet
120
207
  end
121
208
 
@@ -124,11 +211,60 @@ module RubySMB
124
211
  # may want to communicate over SMB1
125
212
  #
126
213
  # @ return [RubySMB::SMB2::Packet::NegotiateRequest] a completed SMB2 Negotiate Request packet
127
- def smb2_negotiate_request
214
+ def smb2_3_negotiate_request
128
215
  packet = RubySMB::SMB2::Packet::NegotiateRequest.new
129
216
  packet.security_mode.signing_enabled = 1
130
- packet.add_dialect(SMB2_DIALECT_DEFAULT)
131
217
  packet.client_guid = SecureRandom.random_bytes(16)
218
+ packet.set_dialects(SMB2_DIALECT_DEFAULT.map {|d| d.to_i(16)}) if smb2
219
+ packet = add_smb3_to_negotiate_request(packet) if smb3
220
+ packet
221
+ end
222
+
223
+ # This adds SMBv3 specific information: SMBv3 supported dialects,
224
+ # encryption capability, Negotiate Contexts if the dialect requires them
225
+ #
226
+ # @param packet [RubySMB::SMB2::Packet::NegotiateRequest] the NegotiateRequest
227
+ # to add SMB3 specific info to
228
+ # @param dialects [Array<String>] the dialects to negotiate. This must be
229
+ # an array of strings. Default is SMB3_DIALECT_DEFAULT
230
+ # @return [RubySMB::SMB2::Packet::NegotiateRequest] a completed SMB3 Negotiate Request packet
231
+ # @raise [ArgumentError] if dialects is not an array of strings
232
+ def add_smb3_to_negotiate_request(packet, dialects = SMB3_DIALECT_DEFAULT)
233
+ dialects.each do |dialect|
234
+ raise ArgumentError, 'Must be an array of strings' unless dialect.is_a? String
235
+ packet.add_dialect(dialect.to_i(16))
236
+ end
237
+ packet.capabilities.encryption = 1
238
+
239
+ if packet.dialects.include?(0x0311)
240
+ nc = RubySMB::SMB2::NegotiateContext.new(
241
+ context_type: RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
242
+ )
243
+ nc.data.hash_algorithms << RubySMB::SMB2::PreauthIntegrityCapabilities::SHA_512
244
+ nc.data.salt = SecureRandom.random_bytes(32)
245
+ packet.add_negotiate_context(nc)
246
+
247
+ @preauth_integrity_hash_value = "\x00" * 64
248
+ nc = RubySMB::SMB2::NegotiateContext.new(
249
+ context_type: RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
250
+ )
251
+ nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM
252
+ nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM
253
+ packet.add_negotiate_context(nc)
254
+
255
+ nc = RubySMB::SMB2::NegotiateContext.new(
256
+ context_type: RubySMB::SMB2::NegotiateContext::SMB2_COMPRESSION_CAPABILITIES
257
+ )
258
+ # Adding all possible compression algorithm even if we don't support
259
+ # them yet. This will force the server to disclose the support
260
+ # algorithms in the repsonse.
261
+ nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZNT1
262
+ nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZ77
263
+ nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZ77_Huffman
264
+ nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::Pattern_V1
265
+ packet.add_negotiate_context(nc)
266
+ end
267
+
132
268
  packet
133
269
  end
134
270
  end
@@ -40,6 +40,25 @@ module RubySMB
40
40
  end
41
41
  packet
42
42
  end
43
+
44
+ def smb3_sign(packet)
45
+ if !session_key.empty? && (signing_required || packet.is_a?(RubySMB::SMB2::Packet::TreeConnectRequest))
46
+ case @dialect
47
+ when '0x0300', '0x0302'
48
+ signing_key = RubySMB::Crypto::KDF.counter_mode(@session_key, "SMB2AESCMAC\x00", "SmbSign\x00")
49
+ when '0x0311'
50
+ signing_key = RubySMB::Crypto::KDF.counter_mode(@session_key, "SMBSigningKey\x00", @preauth_integrity_hash_value)
51
+ else
52
+ raise RubySMB::Error::SigningError.new('Dialect is incompatible with SMBv3 signing')
53
+ end
54
+
55
+ packet.smb2_header.flags.signed = 1
56
+ packet.smb2_header.signature = "\x00" * 16
57
+ hmac = OpenSSL::CMAC.digest('AES', signing_key, packet.to_binary_s)
58
+ packet.smb2_header.signature = hmac[0, 16]
59
+ end
60
+ packet
61
+ end
43
62
  end
44
63
  end
45
64
  end
@@ -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
@@ -40,27 +40,28 @@ module RubySMB
40
40
  end
41
41
 
42
42
  #Writes data to an open file handle
43
- def write(file_id, offset = 0, data = '', do_recv = true)
43
+ def write(file_id = last_file_id, offset = 0, data = '', do_recv = true)
44
44
  @open_files[file_id].send_recv_write(data: data, offset: offset)
45
45
  end
46
46
 
47
- def read(file_id, offset = 0, length = last_file.size)
47
+ def read(file_id = last_file_id, offset = 0, length = last_file.size, do_recv = true)
48
48
  data = @open_files[file_id].send_recv_read(read_length: length, offset: offset)
49
49
  data.bytes
50
50
  end
51
51
 
52
- def delete(path)
53
- file = last_tree.open_file(filename: path.sub(/^\\/, ''), delete: true)
52
+ def delete(path, tree_id = last_tree_id, do_recv = true)
53
+ tree = @tree_connects.detect{ |tree| tree.id == tree_id }
54
+ file = tree.open_file(filename: path.sub(/^\\/, ''), delete: true)
54
55
  file.delete
55
56
  file.close
56
57
  end
57
58
 
58
- def close(file_id, tree_id)
59
+ def close(file_id = last_file_id, tree_id = last_tree_id, do_recv = true)
59
60
  @open_files[file_id].close
60
61
  end
61
62
 
62
- def tree_disconnect(share)
63
- @tree_connects.detect{|tree| tree.id == share }.disconnect!
63
+ def tree_disconnect(tree_id = last_tree_id, do_recv = true)
64
+ @tree_connects.detect{|tree| tree.id == tree_id }.disconnect!
64
65
  end
65
66
 
66
67
  def native_os