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
@@ -4,6 +4,8 @@ module RubySMB
4
4
  # An SMB2 Write Request Packet as defined in
5
5
  # [2.2.21 SMB2 WRITE Request](https://msdn.microsoft.com/en-us/library/cc246532.aspx)
6
6
  class WriteRequest < RubySMB::GenericPacket
7
+ COMMAND = RubySMB::SMB2::Commands::WRITE
8
+
7
9
  endian :little
8
10
 
9
11
  smb2_header :smb2_header
@@ -19,10 +21,6 @@ module RubySMB
19
21
  uint32 :flags, label: 'Flags'
20
22
  string :buffer, label: 'Write Data Buffer'
21
23
 
22
- def initialize_instance
23
- super
24
- smb2_header.command = RubySMB::SMB2::Commands::WRITE
25
- end
26
24
  end
27
25
  end
28
26
  end
@@ -4,6 +4,8 @@ module RubySMB
4
4
  # An SMB2 Write Response Packet as defined in
5
5
  # [2.2.22 SMB2 WRITE Response](https://msdn.microsoft.com/en-us/library/cc246533.aspx)
6
6
  class WriteResponse < RubySMB::GenericPacket
7
+ COMMAND = RubySMB::SMB2::Commands::WRITE
8
+
7
9
  endian :little
8
10
 
9
11
  smb2_header :smb2_header
@@ -17,7 +19,6 @@ module RubySMB
17
19
 
18
20
  def initialize_instance
19
21
  super
20
- smb2_header.command = RubySMB::SMB2::Commands::WRITE
21
22
  smb2_header.flags.reply = 1
22
23
  end
23
24
  end
@@ -3,18 +3,29 @@ module RubySMB
3
3
  # Represents a pipe on the Remote server that we can perform
4
4
  # various I/O operations on.
5
5
  class Pipe < File
6
- require 'ruby_smb/smb2/dcerpc'
6
+ require 'ruby_smb/dcerpc'
7
7
 
8
- include RubySMB::SMB2::Dcerpc
8
+ include RubySMB::Dcerpc
9
9
 
10
10
  STATUS_CONNECTED = 0x00000003
11
11
  STATUS_CLOSING = 0x00000004
12
12
 
13
+ def initialize(tree:, response:, name:)
14
+ raise ArgumentError, 'No Name Provided' if name.nil?
15
+ case name
16
+ when 'srvsvc'
17
+ extend RubySMB::Dcerpc::Srvsvc
18
+ when 'winreg'
19
+ extend RubySMB::Dcerpc::Winreg
20
+ end
21
+ super(tree: tree, response: response, name: name)
22
+ end
23
+
13
24
  # Performs a peek operation on the named pipe
14
25
  #
15
26
  # @param peek_size [Integer] Amount of data to peek
16
27
  # @return [RubySMB::SMB2::Packet::IoctlResponse]
17
- # @raise [RubySMB::Error::InvalidPacket] if not a valid FSCTL_PIPE_PEEK response
28
+ # @raise [RubySMB::Error::InvalidPacket] if not a valid FIoctlResponse response
18
29
  # @raise [RubySMB::Error::UnexpectedStatusCode] If status is not STATUS_BUFFER_OVERFLOW or STATUS_SUCCESS
19
30
  def peek(peek_size: 0)
20
31
  packet = RubySMB::SMB2::Packet::IoctlRequest.new
@@ -24,18 +35,18 @@ module RubySMB
24
35
  packet.max_output_response = 16 + peek_size
25
36
  packet = set_header_fields(packet)
26
37
  raw_response = @tree.client.send_recv(packet)
27
- begin
28
- response = RubySMB::SMB2::Packet::IoctlResponse.read(raw_response)
29
- rescue IOError
30
- response = RubySMB::SMB2::Packet::ErrorPacket.read(raw_response)
38
+ response = RubySMB::SMB2::Packet::IoctlResponse.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::IoctlResponse::COMMAND,
43
+ received_proto: response.smb2_header.protocol,
44
+ received_cmd: response.smb2_header.command
45
+ )
31
46
  end
32
47
 
33
48
  unless response.status_code == WindowsError::NTStatus::STATUS_BUFFER_OVERFLOW or response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
34
- raise RubySMB::Error::UnexpectedStatusCode, response.status_code.name
35
- end
36
-
37
- unless response.smb2_header.command == RubySMB::SMB2::Commands::IOCTL
38
- raise RubySMB::Error::InvalidPacket, 'Not an IoctlResponse packet'
49
+ raise RubySMB::Error::UnexpectedStatusCode, response.status_code
39
50
  end
40
51
  response
41
52
  end
@@ -67,6 +78,69 @@ module RubySMB
67
78
  state == STATUS_CONNECTED
68
79
  end
69
80
 
81
+ def dcerpc_request(stub_packet, options={})
82
+ options.merge!(endpoint: stub_packet.class.name.split('::').at(-2))
83
+ dcerpc_request = RubySMB::Dcerpc::Request.new({ opnum: stub_packet.opnum }, options)
84
+ dcerpc_request.stub.read(stub_packet.to_binary_s)
85
+ ioctl_send_recv(dcerpc_request, options)
86
+ end
87
+
88
+ def ioctl_send_recv(action, options={})
89
+ request = set_header_fields(RubySMB::SMB2::Packet::IoctlRequest.new(options))
90
+ request.ctl_code = 0x0011C017
91
+ request.flags.is_fsctl = 0x00000001
92
+ request.buffer = action.to_binary_s
93
+
94
+ ioctl_raw_response = @tree.client.send_recv(request)
95
+ ioctl_response = RubySMB::SMB2::Packet::IoctlResponse.read(ioctl_raw_response)
96
+ unless ioctl_response.valid?
97
+ raise RubySMB::Error::InvalidPacket.new(
98
+ expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
99
+ expected_cmd: RubySMB::SMB2::Packet::IoctlRequest::COMMAND,
100
+ received_proto: ioctl_response.smb2_header.protocol,
101
+ received_cmd: ioctl_response.smb2_header.command
102
+ )
103
+ end
104
+ unless [WindowsError::NTStatus::STATUS_SUCCESS,
105
+ WindowsError::NTStatus::STATUS_BUFFER_OVERFLOW].include?(ioctl_response.status_code)
106
+ raise RubySMB::Error::UnexpectedStatusCode, ioctl_response.status_code
107
+ end
108
+
109
+ raw_data = ioctl_response.output_data
110
+ if ioctl_response.status_code == WindowsError::NTStatus::STATUS_BUFFER_OVERFLOW
111
+ raw_data << read(bytes: @tree.client.max_buffer_size - ioctl_response.output_count)
112
+ dcerpc_response = dcerpc_response_from_raw_response(raw_data)
113
+ unless dcerpc_response.pdu_header.pfc_flags.first_frag == 1
114
+ raise RubySMB::Dcerpc::Error::InvalidPacket, "Not the first fragment"
115
+ end
116
+ stub_data = dcerpc_response.stub.to_s
117
+
118
+ loop do
119
+ break if dcerpc_response.pdu_header.pfc_flags.last_frag == 1
120
+ raw_data = read(bytes: @tree.client.max_buffer_size)
121
+ dcerpc_response = dcerpc_response_from_raw_response(raw_data)
122
+ stub_data << dcerpc_response.stub.to_s
123
+ end
124
+ stub_data
125
+ else
126
+ dcerpc_response = dcerpc_response_from_raw_response(raw_data)
127
+ dcerpc_response.stub.to_s
128
+ end
129
+ end
130
+
131
+
132
+ private
133
+
134
+ def dcerpc_response_from_raw_response(raw_data)
135
+ dcerpc_response = RubySMB::Dcerpc::Response.read(raw_data)
136
+ unless dcerpc_response.pdu_header.ptype == RubySMB::Dcerpc::PTypes::RESPONSE
137
+ raise RubySMB::Dcerpc::Error::InvalidPacket, "Not a Response packet"
138
+ end
139
+ dcerpc_response
140
+ rescue IOError
141
+ raise RubySMB::Dcerpc::Error::InvalidPacket, "Error reading the DCERPC response"
142
+ end
143
+
70
144
  end
71
145
  end
72
146
  end
@@ -6,7 +6,7 @@ module RubySMB
6
6
  endian :little
7
7
  bit32 :protocol, label: 'Protocol ID Field', initial_value: RubySMB::SMB2::SMB2_PROTOCOL_ID
8
8
  uint16 :structure_size, label: 'Header Structure Size', initial_value: 64
9
- uint16 :credit_charge, label: 'Credit Charge', initial_value: 0
9
+ uint16 :credit_charge, label: 'Credit Charge', initial_value: 1
10
10
  nt_status :nt_status, label: 'NT Status', initial_value: 0
11
11
  uint16 :command, label: 'Command'
12
12
  uint16 :credits, label: 'Credit Request/Response'
@@ -23,22 +23,37 @@ module RubySMB
23
23
  # @return [Integer]
24
24
  attr_accessor :id
25
25
 
26
- def initialize(client:, share:, response:)
27
- @client = client
28
- @share = share
29
- @id = response.smb2_header.tree_id
30
- @permissions = response.maximal_access
31
- @share_type = response.share_type
26
+ # Whether or not the share associated with this tree connect needs to be encrypted (SMB 3.x)
27
+ # @!attribute [rw] tree_connect_encrypt_data
28
+ # @return [Boolean]
29
+ attr_accessor :tree_connect_encrypt_data
30
+
31
+ def initialize(client:, share:, response:, encrypt: false)
32
+ @client = client
33
+ @share = share
34
+ @id = response.smb2_header.tree_id
35
+ @permissions = response.maximal_access
36
+ @share_type = response.share_type
37
+ @tree_connect_encrypt_data = encrypt
32
38
  end
33
39
 
34
40
  # Disconnects this Tree from the current session
35
41
  #
36
42
  # @return [WindowsError::ErrorCode] the NTStatus sent back by the server.
43
+ # @raise [RubySMB::Error::InvalidPacket] if the response is not a TreeDisconnectResponse packet
37
44
  def disconnect!
38
45
  request = RubySMB::SMB2::Packet::TreeDisconnectRequest.new
39
46
  request = set_header_fields(request)
40
- raw_response = client.send_recv(request)
47
+ raw_response = client.send_recv(request, encrypt: @tree_connect_encrypt_data)
41
48
  response = RubySMB::SMB2::Packet::TreeDisconnectResponse.read(raw_response)
49
+ unless response.valid?
50
+ raise RubySMB::Error::InvalidPacket.new(
51
+ expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
52
+ expected_cmd: RubySMB::SMB2::Packet::TreeDisconnectResponse::COMMAND,
53
+ received_proto: response.smb2_header.protocol,
54
+ received_cmd: response.smb2_header.command
55
+ )
56
+ end
42
57
  response.status_code
43
58
  end
44
59
 
@@ -87,22 +102,29 @@ module RubySMB
87
102
  create_request.create_disposition = disposition
88
103
  create_request.name = filename
89
104
 
90
- raw_response = client.send_recv(create_request)
105
+ raw_response = client.send_recv(create_request, encrypt: @tree_connect_encrypt_data)
91
106
  response = RubySMB::SMB2::Packet::CreateResponse.read(raw_response)
92
-
93
- if response.is_a?(RubySMB::SMB2::Packet::ErrorPacket)
94
- raise RubySMB::Error::RubySMBError
107
+ unless response.valid?
108
+ raise RubySMB::Error::InvalidPacket.new(
109
+ expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
110
+ expected_cmd: RubySMB::SMB2::Packet::CreateResponse::COMMAND,
111
+ received_proto: response.smb2_header.protocol,
112
+ received_cmd: response.smb2_header.command
113
+ )
114
+ end
115
+ unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
116
+ raise RubySMB::Error::UnexpectedStatusCode, response.status_code
95
117
  end
96
118
 
97
119
  case @share_type
98
- when 0x01
99
- RubySMB::SMB2::File.new(name: filename, tree: self, response: response)
100
- when 0x02
120
+ when RubySMB::SMB2::Packet::TreeConnectResponse::SMB2_SHARE_TYPE_DISK
121
+ RubySMB::SMB2::File.new(name: filename, tree: self, response: response, encrypt: @tree_connect_encrypt_data)
122
+ when RubySMB::SMB2::Packet::TreeConnectResponse::SMB2_SHARE_TYPE_PIPE
101
123
  RubySMB::SMB2::Pipe.new(name: filename, tree: self, response: response)
102
- # when 0x03
124
+ # when RubySMB::SMB2::TreeConnectResponse::SMB2_SHARE_TYPE_PRINT
103
125
  # it's a printer!
104
126
  else
105
- raise RubySMB::Error::RubySMBError
127
+ raise RubySMB::Error::RubySMBError, 'Unsupported share type'
106
128
  end
107
129
  end
108
130
 
@@ -116,6 +138,7 @@ module RubySMB
116
138
  # @param pattern [String] search pattern
117
139
  # @param type [Class] file information class
118
140
  # @return [Array] array of directory structures
141
+ # @raise [RubySMB::Error::InvalidPacket] if the response is not a QueryDirectoryResponse packet
119
142
  def list(directory: nil, pattern: '*', type: RubySMB::Fscc::FileInformation::FileIdFullDirectoryInformation)
120
143
  create_response = open_directory(directory: directory)
121
144
  file_id = create_response.file_id
@@ -131,16 +154,23 @@ module RubySMB
131
154
  files = []
132
155
 
133
156
  loop do
134
- response = client.send_recv(directory_request)
157
+ response = client.send_recv(directory_request, encrypt: @tree_connect_encrypt_data)
135
158
  directory_response = RubySMB::SMB2::Packet::QueryDirectoryResponse.read(response)
159
+ unless directory_response.valid?
160
+ raise RubySMB::Error::InvalidPacket.new(
161
+ expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
162
+ expected_cmd: RubySMB::SMB2::Packet::QueryDirectoryResponse::COMMAND,
163
+ received_proto: directory_response.smb2_header.protocol,
164
+ received_cmd: directory_response.smb2_header.command
165
+ )
166
+ end
136
167
 
137
168
  status_code = directory_response.smb2_header.nt_status.to_nt_status
138
169
 
139
170
  break if status_code == WindowsError::NTStatus::STATUS_NO_MORE_FILES
140
171
 
141
172
  unless status_code == WindowsError::NTStatus::STATUS_SUCCESS
142
-
143
- raise RubySMB::Error::UnexpectedStatusCode, status_code.to_s
173
+ raise RubySMB::Error::UnexpectedStatusCode, status_code
144
174
  end
145
175
 
146
176
  files += directory_response.results(type)
@@ -162,14 +192,28 @@ module RubySMB
162
192
  # @param write [Boolean] whether to request write access
163
193
  # @param delete [Boolean] whether to request delete access
164
194
  # @return [RubySMB::SMB2::Packet::CreateResponse] the response packet returned from the server
195
+ # @raise [RubySMB::Error::InvalidPacket] if the response is not a CreateResponse packet
165
196
  def open_directory(directory: nil, disposition: RubySMB::Dispositions::FILE_OPEN,
166
197
  impersonation: RubySMB::ImpersonationLevels::SEC_IMPERSONATE,
167
198
  read: true, write: false, delete: false)
168
199
 
169
200
  create_request = open_directory_packet(directory: directory, disposition: disposition,
170
201
  impersonation: impersonation, read: read, write: write, delete: delete)
171
- raw_response = client.send_recv(create_request)
172
- RubySMB::SMB2::Packet::CreateResponse.read(raw_response)
202
+ raw_response = client.send_recv(create_request, encrypt: @tree_connect_encrypt_data)
203
+ response = RubySMB::SMB2::Packet::CreateResponse.read(raw_response)
204
+ unless response.valid?
205
+ raise RubySMB::Error::InvalidPacket.new(
206
+ expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
207
+ expected_cmd: RubySMB::SMB2::Packet::CreateResponse::COMMAND,
208
+ received_proto: response.smb2_header.protocol,
209
+ received_cmd: response.smb2_header.command
210
+ )
211
+ end
212
+ unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
213
+ raise RubySMB::Error::UnexpectedStatusCode, response.status_code
214
+ end
215
+
216
+ response
173
217
  end
174
218
 
175
219
  # Creates the Packet for the #open_directory method.
@@ -1,3 +1,3 @@
1
1
  module RubySMB
2
- VERSION = '1.0.3'.freeze
2
+ VERSION = '2.0.1'.freeze
3
3
  end
@@ -6,8 +6,8 @@ require 'ruby_smb/version'
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = 'ruby_smb'
8
8
  spec.version = RubySMB::VERSION
9
- spec.authors = ['David Maloney', 'James Lee', 'Dev Mohanty', 'Christophe De La Fuente']
10
- spec.email = ['DMaloney@rapid7.com', 'egypt@metasploit.com', 'dev_mohanty@rapid7.com', 'paristvinternet-github@yahoo.com']
9
+ spec.authors = ['Metasploit Hackers', 'David Maloney', 'James Lee', 'Dev Mohanty', 'Christophe De La Fuente']
10
+ spec.email = ['msfdev@metasploit.com']
11
11
  spec.summary = 'A pure Ruby implementation of the SMB Protocol Family'
12
12
  spec.description = ''
13
13
  spec.homepage = 'https://github.com/rapid7/ruby_smb'
@@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
26
26
  spec.platform = Gem::Platform::RUBY
27
27
  end
28
28
 
29
- spec.required_ruby_version = '>= 2.2.0'
29
+ spec.required_ruby_version = '>= 2.5'
30
30
 
31
31
  spec.add_development_dependency 'bundler'
32
32
  spec.add_development_dependency 'fivemat'
@@ -36,4 +36,6 @@ Gem::Specification.new do |spec|
36
36
  spec.add_runtime_dependency 'rubyntlm'
37
37
  spec.add_runtime_dependency 'windows_error'
38
38
  spec.add_runtime_dependency 'bindata'
39
+ spec.add_runtime_dependency 'openssl-ccm'
40
+ spec.add_runtime_dependency 'openssl-cmac'
39
41
  end
@@ -6,9 +6,65 @@ RSpec.describe RubySMB::Client do
6
6
  let(:username) { 'msfadmin' }
7
7
  let(:password) { 'msfpasswd' }
8
8
  subject(:client) { described_class.new(dispatcher, username: username, password: password) }
9
- let(:smb1_client) { described_class.new(dispatcher, smb2: false, username: username, password: password) }
10
- let(:smb2_client) { described_class.new(dispatcher, smb1: false, username: username, password: password) }
9
+ let(:smb1_client) { described_class.new(dispatcher, smb2: false, smb3: false, username: username, password: password) }
10
+ let(:smb2_client) { described_class.new(dispatcher, smb1: false, smb3: false, username: username, password: password) }
11
+ let(:smb3_client) { described_class.new(dispatcher, smb1: false, smb2: false, username: username, password: password) }
11
12
  let(:empty_packet) { RubySMB::SMB1::Packet::EmptyPacket.new }
13
+ let(:error_packet) { RubySMB::SMB2::Packet::ErrorPacket.new }
14
+
15
+ it { is_expected.to respond_to :dispatcher }
16
+ it { is_expected.to respond_to :domain }
17
+ it { is_expected.to respond_to :local_workstation }
18
+ it { is_expected.to respond_to :ntlm_client }
19
+ it { is_expected.to respond_to :password }
20
+ it { is_expected.to respond_to :peer_native_os }
21
+ it { is_expected.to respond_to :peer_native_lm }
22
+ it { is_expected.to respond_to :primary_domain }
23
+ it { is_expected.to respond_to :default_name }
24
+ it { is_expected.to respond_to :default_domain }
25
+ it { is_expected.to respond_to :dns_host_name }
26
+ it { is_expected.to respond_to :dns_domain_name }
27
+ it { is_expected.to respond_to :dns_tree_name }
28
+ it { is_expected.to respond_to :os_version }
29
+ it { is_expected.to respond_to :dialect }
30
+ it { is_expected.to respond_to :sequence_counter }
31
+ it { is_expected.to respond_to :session_id }
32
+ it { is_expected.to respond_to :signing_required }
33
+ it { is_expected.to respond_to :smb1 }
34
+ it { is_expected.to respond_to :smb2 }
35
+ it { is_expected.to respond_to :smb3 }
36
+ it { is_expected.to respond_to :smb2_message_id }
37
+ it { is_expected.to respond_to :username }
38
+ it { is_expected.to respond_to :user_id }
39
+ it { is_expected.to respond_to :max_buffer_size }
40
+ it { is_expected.to respond_to :server_max_buffer_size }
41
+ it { is_expected.to respond_to :server_max_write_size }
42
+ it { is_expected.to respond_to :server_max_read_size }
43
+ it { is_expected.to respond_to :server_max_transact_size }
44
+ it { is_expected.to respond_to :preauth_integrity_hash_algorithm }
45
+ it { is_expected.to respond_to :preauth_integrity_hash_value }
46
+ it { is_expected.to respond_to :encryption_algorithm }
47
+ it { is_expected.to respond_to :client_encryption_key }
48
+ it { is_expected.to respond_to :server_encryption_key }
49
+ it { is_expected.to respond_to :session_encrypt_data }
50
+ it { is_expected.to respond_to :server_encryption_algorithms }
51
+ it { is_expected.to respond_to :server_compression_algorithms }
52
+ it { is_expected.to respond_to :negotiated_smb_version }
53
+ it { is_expected.to respond_to :session_key }
54
+ it { is_expected.to respond_to :tree_connects }
55
+ it { is_expected.to respond_to :open_files }
56
+ it { is_expected.to respond_to :use_ntlmv2 }
57
+ it { is_expected.to respond_to :usentlm2_session }
58
+ it { is_expected.to respond_to :send_lm }
59
+ it { is_expected.to respond_to :use_lanman_key }
60
+ it { is_expected.to respond_to :send_ntlm }
61
+ it { is_expected.to respond_to :spnopt }
62
+ it { is_expected.to respond_to :evasion_opts }
63
+ it { is_expected.to respond_to :native_os }
64
+ it { is_expected.to respond_to :native_lm }
65
+ it { is_expected.to respond_to :verify_signature }
66
+ it { is_expected.to respond_to :auth_user }
67
+ it { is_expected.to respond_to :last_file_id }
12
68
 
13
69
  describe '#initialize' do
14
70
  it 'should raise an ArgumentError without a valid dispatcher' do
@@ -25,14 +81,21 @@ RSpec.describe RubySMB::Client do
25
81
 
26
82
  it 'accepts an argument to disable smb1 support' do
27
83
  expect(smb2_client.smb1).to be false
84
+ expect(smb3_client.smb1).to be false
28
85
  end
29
86
 
30
87
  it 'accepts an argument to disable smb2 support' do
31
88
  expect(smb1_client.smb2).to be false
89
+ expect(smb3_client.smb2).to be false
32
90
  end
33
91
 
34
- it 'raises an exception if both SMB1 and SMB2 are disabled' do
35
- expect { described_class.new(dispatcher, smb1: false, smb2: false, username: username, password: password) }.to raise_error(ArgumentError, 'You must enable at least one Protocol')
92
+ it 'accepts an argument to disable smb3 support' do
93
+ expect(smb1_client.smb3).to be false
94
+ expect(smb2_client.smb3).to be false
95
+ end
96
+
97
+ it 'raises an exception if SMB1, SMB2 and SMB3 are disabled' do
98
+ expect { described_class.new(dispatcher, smb1: false, smb2: false, smb3: false, username: username, password: password) }.to raise_error(ArgumentError, 'You must enable at least one Protocol')
36
99
  end
37
100
 
38
101
  it 'sets the username attribute' do
@@ -43,6 +106,11 @@ RSpec.describe RubySMB::Client do
43
106
  expect(client.password).to eq password
44
107
  end
45
108
 
109
+ it 'sets the session_encrypt_data attribute' do
110
+ client = described_class.new(dispatcher, username: username, password: password, always_encrypt: true)
111
+ expect(client.session_encrypt_data).to eq true
112
+ end
113
+
46
114
  it 'creates an NTLM client' do
47
115
  expect(client.ntlm_client).to be_a Net::NTLM::Client
48
116
  end
@@ -57,7 +125,7 @@ RSpec.describe RubySMB::Client do
57
125
  expect(opt[:workstation]).to eq(local_workstation)
58
126
  expect(opt[:domain]).to eq(domain)
59
127
  flags = Net::NTLM::Client::DEFAULT_FLAGS |
60
- Net::NTLM::FLAGS[:TARGET_INFO] | 0x02000000
128
+ Net::NTLM::FLAGS[:TARGET_INFO] | 0x02000000 ^ Net::NTLM::FLAGS[:OEM]
61
129
  expect(opt[:flags]).to eq(flags)
62
130
  end
63
131
 
@@ -75,28 +143,280 @@ RSpec.describe RubySMB::Client do
75
143
  end
76
144
  end
77
145
 
146
+ describe '#echo' do
147
+ let(:response) { double('Echo Response') }
148
+ before :example do
149
+ allow(response).to receive(:status_code).and_return(WindowsError::NTStatus::STATUS_SUCCESS)
150
+ end
151
+
152
+ context 'with SMB1' do
153
+ it 'calls #smb1_echo with the expected arguments' do
154
+ allow(smb1_client).to receive(:smb1_echo).and_return(response)
155
+ count = 3
156
+ data = 'testing...'
157
+ smb1_client.echo(count: count, data: data)
158
+ expect(smb1_client).to have_received(:smb1_echo).with(count: count, data: data)
159
+ end
160
+ end
161
+
162
+ context 'with SMB2' do
163
+ it 'calls #smb2_echo without arguments' do
164
+ allow(smb2_client).to receive(:smb2_echo).and_return(response)
165
+ smb2_client.echo
166
+ expect(smb2_client).to have_received(:smb2_echo)
167
+ end
168
+ end
169
+
170
+ context 'with SMB3' do
171
+ it 'calls #smb2_echo without arguments' do
172
+ allow(smb3_client).to receive(:smb2_echo).and_return(response)
173
+ smb3_client.echo
174
+ expect(smb3_client).to have_received(:smb2_echo)
175
+ end
176
+ end
177
+
178
+ it 'returns the expected status code' do
179
+ allow(smb2_client).to receive(:smb2_echo).and_return(response)
180
+ expect(smb2_client.echo).to eq(WindowsError::NTStatus::STATUS_SUCCESS)
181
+ end
182
+ end
183
+
78
184
  describe '#send_recv' do
79
185
  let(:smb1_request) { RubySMB::SMB1::Packet::TreeConnectRequest.new }
80
186
  let(:smb2_request) { RubySMB::SMB2::Packet::TreeConnectRequest.new }
81
187
 
82
188
  before(:each) do
83
- expect(dispatcher).to receive(:send_packet).and_return(nil)
84
- expect(dispatcher).to receive(:recv_packet).and_return('A')
189
+ allow(client).to receive(:is_status_pending?).and_return(false)
190
+ allow(dispatcher).to receive(:send_packet).and_return(nil)
191
+ allow(dispatcher).to receive(:recv_packet).and_return('A')
85
192
  end
86
193
 
87
- it 'checks the packet version' do
88
- expect(smb1_request).to receive(:packet_smb_version).and_call_original
89
- client.send_recv(smb1_request)
194
+ context 'when signing' do
195
+ it 'calls #smb1_sign if it is an SMB1 packet' do
196
+ expect(client).to receive(:smb1_sign).with(smb1_request).and_call_original
197
+ client.send_recv(smb1_request)
198
+ end
199
+
200
+ context 'with an SMB2 packet' do
201
+ it 'does not sign a SessionSetupRequest packet' do
202
+ expect(smb2_client).to_not receive(:smb2_sign)
203
+ expect(smb2_client).to_not receive(:smb3_sign)
204
+ client.send_recv(RubySMB::SMB2::Packet::SessionSetupRequest.new)
205
+ end
206
+
207
+ it 'calls #smb2_sign if it is an SMB2 client' do
208
+ allow(smb2_client).to receive(:is_status_pending?).and_return(false)
209
+ expect(smb2_client).to receive(:smb2_sign).with(smb2_request).and_call_original
210
+ smb2_client.send_recv(smb2_request)
211
+ end
212
+
213
+ it 'calls #smb3_sign if it is an SMB3 client' do
214
+ allow(smb3_client).to receive(:is_status_pending?).and_return(false)
215
+ expect(smb3_client).to receive(:smb3_sign).with(smb2_request).and_call_original
216
+ smb3_client.send_recv(smb2_request)
217
+ end
218
+ end
90
219
  end
91
220
 
92
- it 'calls #smb1_sign if it is an SMB1 packet' do
93
- expect(client).to receive(:smb1_sign).with(smb1_request).and_call_original
221
+ it 'sends the expected packet and gets the response' do
222
+ expect(dispatcher).to receive(:send_packet).with(smb1_request)
223
+ expect(dispatcher).to receive(:recv_packet)
94
224
  client.send_recv(smb1_request)
95
225
  end
96
226
 
97
- it 'calls #smb2_sign if it is an SMB2 packet' do
98
- expect(client).to receive(:smb2_sign).with(smb2_request).and_call_original
99
- client.send_recv(smb2_request)
227
+ context 'with SMB1' do
228
+ it 'does not check if it is a STATUS_PENDING response' do
229
+ expect(smb1_client).to_not receive(:is_status_pending?)
230
+ smb1_client.send_recv(smb1_request)
231
+ end
232
+ end
233
+
234
+ context 'with SMB2' do
235
+ context 'when receiving a STATUS_PENDING response' do
236
+ it 'waits 1 second and reads/decrypts again' do
237
+ allow(smb2_client).to receive(:is_status_pending?).and_return(true, false)
238
+ expect(smb2_client).to receive(:sleep).with(1)
239
+ expect(dispatcher).to receive(:recv_packet).twice
240
+ smb2_client.send_recv(smb2_request)
241
+ end
242
+ end
243
+ end
244
+
245
+ context 'with SMB3 and encryption' do
246
+ before :example do
247
+ smb3_client.dialect = '0x0300'
248
+ allow(smb3_client).to receive(:is_status_pending?).and_return(false)
249
+ end
250
+
251
+ context 'with a SessionSetupRequest' do
252
+ it 'does not encrypt/decrypt' do
253
+ request = RubySMB::SMB2::Packet::SessionSetupRequest.new
254
+ expect(smb3_client).to_not receive(:send_encrypt).with(request)
255
+ expect(smb3_client).to_not receive(:recv_encrypt)
256
+ expect(dispatcher).to receive(:send_packet).with(request)
257
+ expect(dispatcher).to receive(:recv_packet)
258
+ smb3_client.send_recv(request)
259
+ end
260
+ end
261
+
262
+ context 'with a NegotiateRequest' do
263
+ it 'does not encrypt/decrypt' do
264
+ request = RubySMB::SMB2::Packet::NegotiateRequest.new
265
+ expect(smb3_client).to_not receive(:send_encrypt).with(request)
266
+ expect(smb3_client).to_not receive(:recv_encrypt)
267
+ expect(dispatcher).to receive(:send_packet).with(request)
268
+ expect(dispatcher).to receive(:recv_packet)
269
+ smb3_client.send_recv(request)
270
+ end
271
+ end
272
+
273
+ it 'encrypts and decrypts' do
274
+ expect(smb3_client).to receive(:send_encrypt).with(smb2_request)
275
+ expect(smb3_client).to receive(:recv_encrypt)
276
+ smb3_client.send_recv(smb2_request)
277
+ end
278
+
279
+ context 'when receiving a STATUS_PENDING response' do
280
+ it 'waits 1 second and reads/decrypts again' do
281
+ allow(smb3_client).to receive(:is_status_pending?).and_return(true, false)
282
+ expect(smb3_client).to receive(:sleep).with(1)
283
+ expect(smb3_client).to receive(:send_encrypt).with(smb2_request)
284
+ expect(smb3_client).to receive(:recv_encrypt).twice
285
+ smb3_client.send_recv(smb2_request)
286
+ end
287
+ end
288
+ end
289
+ end
290
+
291
+ describe '#is_status_pending?' do
292
+ let(:response) {
293
+ res = RubySMB::SMB2::Packet::SessionSetupRequest.new
294
+ res.smb2_header.nt_status= WindowsError::NTStatus::STATUS_PENDING.value
295
+ res.smb2_header.flags.async_command = 1
296
+ res
297
+ }
298
+
299
+ it 'returns true when the response has a STATUS_PENDING status code and the async_command flag set' do
300
+ expect(client.is_status_pending?(response.to_binary_s)).to be true
301
+ end
302
+
303
+ it 'returns false when the response has a STATUS_PENDING status code and the async_command flag not set' do
304
+ response.smb2_header.flags.async_command = 0
305
+ expect(client.is_status_pending?(response.to_binary_s)).to be false
306
+ end
307
+
308
+ it 'returns false when the response has no STATUS_PENDING status code but the async_command flag set' do
309
+ response.smb2_header.nt_status= WindowsError::NTStatus::STATUS_SUCCESS.value
310
+ expect(client.is_status_pending?(response.to_binary_s)).to be false
311
+ end
312
+ end
313
+
314
+ describe '#can_be_encrypted?' do
315
+ it 'returns true if the packet can be encrypted' do
316
+ packet = RubySMB::SMB2::Packet::TreeConnectRequest.new
317
+ expect(client.can_be_encrypted?(packet)).to be true
318
+ end
319
+
320
+ it 'returns false if it is an SMB1 packet' do
321
+ packet = RubySMB::SMB1::Packet::LogoffRequest.new
322
+ expect(client.can_be_encrypted?(packet)).to be false
323
+ end
324
+
325
+ [RubySMB::SMB2::Packet::SessionSetupRequest, RubySMB::SMB2::Packet::NegotiateRequest].each do |klass|
326
+ it "returns false if the packet is a #{klass}" do
327
+ packet = klass.new
328
+ expect(client.can_be_encrypted?(packet)).to be false
329
+ end
330
+ end
331
+ end
332
+
333
+ describe '#encryption_supported?' do
334
+ ['0x0300', '0x0302', '0x0311'].each do |dialect|
335
+ it "returns true if the dialect is #{dialect}" do
336
+ client.dialect = dialect
337
+ expect(client.encryption_supported?).to be true
338
+ end
339
+ end
340
+
341
+ it "returns false if the dialect does not support encryption" do
342
+ client.dialect = '0x0202'
343
+ expect(client.encryption_supported?).to be false
344
+ end
345
+ end
346
+
347
+ describe '#send_encrypt' do
348
+ let(:packet) { RubySMB::SMB2::Packet::SessionSetupRequest.new }
349
+ before :example do
350
+ allow(dispatcher).to receive(:send_packet)
351
+ client.dialect = '0x0300'
352
+ end
353
+
354
+ it 'creates a Transform request' do
355
+ expect(client).to receive(:smb3_encrypt).with(packet.to_binary_s)
356
+ client.send_encrypt(packet)
357
+ end
358
+
359
+ it 'raises an EncryptionError exception if an error occurs while encrypting' do
360
+ allow(client).to receive(:smb3_encrypt).and_raise(RubySMB::Error::RubySMBError.new('Error'))
361
+ expect { client.send_encrypt(packet) }.to raise_error(
362
+ RubySMB::Error::EncryptionError,
363
+ "Error while encrypting #{packet.class.name} packet (SMB 0x0300): Error"
364
+ )
365
+ end
366
+
367
+ it 'sends the encrypted packet' do
368
+ encrypted_packet = double('Encrypted packet')
369
+ allow(client).to receive(:smb3_encrypt).and_return(encrypted_packet)
370
+ client.send_encrypt(packet)
371
+ expect(dispatcher).to have_received(:send_packet).with(encrypted_packet)
372
+ end
373
+ end
374
+
375
+ describe '#recv_encrypt' do
376
+ let(:packet) { RubySMB::SMB2::Packet::SessionSetupRequest.new }
377
+ before :example do
378
+ allow(dispatcher).to receive(:recv_packet).and_return(packet.to_binary_s)
379
+ client.dialect = '0x0300'
380
+ allow(client).to receive(:smb3_decrypt)
381
+ end
382
+
383
+ it 'reads the response packet' do
384
+ client.recv_encrypt
385
+ expect(dispatcher).to have_received(:recv_packet)
386
+ end
387
+
388
+ it 'raises an EncryptionError exception if an error occurs while receiving the response' do
389
+ allow(dispatcher).to receive(:recv_packet).and_raise(RubySMB::Error::CommunicationError)
390
+ expect { client.recv_encrypt }.to raise_error(
391
+ RubySMB::Error::EncryptionError,
392
+ 'Communication error with the remote host: RubySMB::Error::CommunicationError. '\
393
+ 'The server supports encryption but was not able to handle the encrypted request.'
394
+ )
395
+ end
396
+
397
+ it 'parses the response as a Transform response packet' do
398
+ expect(RubySMB::SMB2::Packet::TransformHeader).to receive(:read).with(packet.to_binary_s)
399
+ client.recv_encrypt
400
+ end
401
+
402
+ it 'raises an InvalidPacket exception if an error occurs while parsing the response' do
403
+ allow(RubySMB::SMB2::Packet::TransformHeader).to receive(:read).and_raise(IOError)
404
+ expect { client.recv_encrypt }.to raise_error(RubySMB::Error::InvalidPacket, 'Not a SMB2 TransformHeader packet')
405
+ end
406
+
407
+ it 'decrypts the Transform response packet' do
408
+ transform = double('Transform header packet')
409
+ allow(RubySMB::SMB2::Packet::TransformHeader).to receive(:read).and_return(transform)
410
+ client.recv_encrypt
411
+ expect(client).to have_received(:smb3_decrypt).with(transform)
412
+ end
413
+
414
+ it 'raises an EncryptionError exception if an error occurs while decrypting' do
415
+ allow(client).to receive(:smb3_decrypt).and_raise(RubySMB::Error::RubySMBError)
416
+ expect { client.recv_encrypt }.to raise_error(
417
+ RubySMB::Error::EncryptionError,
418
+ 'Error while decrypting RubySMB::SMB2::Packet::TransformHeader packet (SMB 0x0300}): RubySMB::Error::RubySMBError'
419
+ )
100
420
  end
101
421
  end
102
422
 
@@ -153,6 +473,132 @@ RSpec.describe RubySMB::Client do
153
473
  end
154
474
  end
155
475
 
476
+ describe '#logoff!' do
477
+ context 'with SMB1' do
478
+ let(:raw_response) { double('Raw response') }
479
+ let(:logoff_response) {
480
+ RubySMB::SMB1::Packet::LogoffResponse.new(smb_header: {:command => RubySMB::SMB1::Commands::SMB_COM_LOGOFF} )
481
+ }
482
+ before :example do
483
+ allow(smb1_client).to receive(:send_recv).and_return(raw_response)
484
+ allow(RubySMB::SMB1::Packet::LogoffResponse).to receive(:read).and_return(logoff_response)
485
+ allow(smb1_client).to receive(:wipe_state!)
486
+ end
487
+
488
+ it 'creates a LogoffRequest packet' do
489
+ expect(RubySMB::SMB1::Packet::LogoffRequest).to receive(:new).and_call_original
490
+ smb1_client.logoff!
491
+ end
492
+
493
+ it 'calls #send_recv' do
494
+ expect(smb1_client).to receive(:send_recv)
495
+ smb1_client.logoff!
496
+ end
497
+
498
+ it 'reads the raw response as a LogoffResponse packet' do
499
+ expect(RubySMB::SMB1::Packet::LogoffResponse).to receive(:read).with(raw_response)
500
+ smb1_client.logoff!
501
+ end
502
+
503
+ it 'raise an InvalidPacket exception when the response is an empty packet' do
504
+ allow(RubySMB::SMB1::Packet::LogoffResponse).to receive(:read).and_return(RubySMB::SMB1::Packet::EmptyPacket.new)
505
+ expect {smb1_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
506
+ end
507
+
508
+ it 'raise an InvalidPacket exception when the response is not valid' do
509
+ allow(logoff_response).to receive(:valid?).and_return(false)
510
+ expect {smb1_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
511
+ end
512
+
513
+ it 'calls #wipe_state!' do
514
+ expect(smb1_client).to receive(:wipe_state!)
515
+ smb1_client.logoff!
516
+ end
517
+
518
+ it 'returns the expected status code' do
519
+ logoff_response.smb_header.nt_status = WindowsError::NTStatus::STATUS_PENDING.value
520
+ allow(RubySMB::SMB1::Packet::LogoffResponse).to receive(:read).and_return(logoff_response)
521
+ expect(smb1_client.logoff!).to eq(WindowsError::NTStatus::STATUS_PENDING)
522
+ end
523
+ end
524
+
525
+ context 'with SMB2' do
526
+ let(:raw_response) { double('Raw response') }
527
+ let(:logoff_response) {
528
+ RubySMB::SMB2::Packet::LogoffResponse.new(smb_header: {:command => RubySMB::SMB2::Commands::LOGOFF} )
529
+ }
530
+ before :example do
531
+ allow(smb2_client).to receive(:send_recv).and_return(raw_response)
532
+ allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(logoff_response)
533
+ allow(smb2_client).to receive(:wipe_state!)
534
+ end
535
+
536
+ it 'creates a LogoffRequest packet' do
537
+ expect(RubySMB::SMB2::Packet::LogoffRequest).to receive(:new).and_call_original
538
+ smb2_client.logoff!
539
+ end
540
+
541
+ it 'calls #send_recv' do
542
+ expect(smb2_client).to receive(:send_recv)
543
+ smb2_client.logoff!
544
+ end
545
+
546
+ it 'reads the raw response as a LogoffResponse packet' do
547
+ expect(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).with(raw_response)
548
+ smb2_client.logoff!
549
+ end
550
+
551
+ it 'raise an InvalidPacket exception when the response is an error packet' do
552
+ allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(RubySMB::SMB2::Packet::ErrorPacket.new)
553
+ expect {smb2_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
554
+ end
555
+
556
+ it 'raise an InvalidPacket exception when the response is not a LOGOFF command' do
557
+ logoff_response.smb2_header.command = RubySMB::SMB2::Commands::ECHO
558
+ allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(logoff_response)
559
+ expect {smb2_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
560
+ end
561
+ end
562
+
563
+ context 'with SMB3' do
564
+ let(:raw_response) { double('Raw response') }
565
+ let(:logoff_response) {
566
+ RubySMB::SMB2::Packet::LogoffResponse.new(smb_header: {:command => RubySMB::SMB2::Commands::LOGOFF} )
567
+ }
568
+ before :example do
569
+ allow(smb3_client).to receive(:send_recv).and_return(raw_response)
570
+ allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(logoff_response)
571
+ allow(smb3_client).to receive(:wipe_state!)
572
+ end
573
+
574
+ it 'creates a LogoffRequest packet' do
575
+ expect(RubySMB::SMB2::Packet::LogoffRequest).to receive(:new).and_call_original
576
+ smb3_client.logoff!
577
+ end
578
+
579
+ it 'calls #send_recv' do
580
+ expect(smb3_client).to receive(:send_recv)
581
+ smb3_client.logoff!
582
+ end
583
+
584
+ it 'reads the raw response as a LogoffResponse packet' do
585
+ expect(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).with(raw_response)
586
+ smb3_client.logoff!
587
+ end
588
+
589
+ it 'raise an InvalidPacket exception when the response is an error packet' do
590
+ allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(RubySMB::SMB2::Packet::ErrorPacket.new)
591
+ expect {smb3_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
592
+ end
593
+
594
+ it 'raise an InvalidPacket exception when the response is not a LOGOFF command' do
595
+ logoff_response.smb2_header.command = RubySMB::SMB2::Commands::ECHO
596
+ allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(logoff_response)
597
+ expect {smb3_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
598
+ end
599
+ end
600
+ end
601
+
156
602
  context 'NetBIOS Session Service' do
157
603
  describe '#session_request' do
158
604
  let(:session_header) { RubySMB::Nbss::SessionHeader.new }
@@ -197,6 +643,11 @@ RSpec.describe RubySMB::Client do
197
643
  allow(dispatcher).to receive(:recv_packet).and_return(negative_session_response.to_binary_s)
198
644
  expect { client.session_request }.to raise_error(RubySMB::Error::NetBiosSessionService)
199
645
  end
646
+
647
+ it 'raises an InvalidPacket exception when an error occurs while reading' do
648
+ allow(RubySMB::Nbss::SessionHeader).to receive(:read).and_raise(IOError)
649
+ expect { client.session_request }.to raise_error(RubySMB::Error::InvalidPacket)
650
+ end
200
651
  end
201
652
 
202
653
  describe '#session_request_packet' do
@@ -271,7 +722,8 @@ RSpec.describe RubySMB::Client do
271
722
  smb1_extended_response.to_binary_s
272
723
  }
273
724
 
274
- let(:smb2_response) { RubySMB::SMB2::Packet::NegotiateResponse.new }
725
+ let(:smb2_response) { RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x200) }
726
+ let(:smb3_response) { RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x300) }
275
727
 
276
728
  describe '#smb1_negotiate_request' do
277
729
  it 'returns an SMB1 Negotiate Request packet' do
@@ -279,33 +731,158 @@ RSpec.describe RubySMB::Client do
279
731
  end
280
732
 
281
733
  it 'sets the default SMB1 Dialect' do
282
- expect(client.smb1_negotiate_request.dialects).to include(buffer_format: 2, dialect_string: RubySMB::Client::SMB1_DIALECT_SMB1_DEFAULT)
734
+ expect(client.smb1_negotiate_request.dialects).to include(
735
+ buffer_format: 2,
736
+ dialect_string: RubySMB::Client::SMB1_DIALECT_SMB1_DEFAULT
737
+ )
283
738
  end
284
739
 
285
740
  it 'sets the SMB2.02 dialect if SMB2 support is enabled' do
286
- expect(client.smb1_negotiate_request.dialects).to include(buffer_format: 2, dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_DEFAULT)
741
+ expect(client.smb1_negotiate_request.dialects).to include(
742
+ buffer_format: 2,
743
+ dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_DEFAULT
744
+ )
287
745
  end
288
746
 
289
747
  it 'excludes the SMB2.02 Dialect if SMB2 support is disabled' do
290
- expect(smb1_client.smb1_negotiate_request.dialects).to_not include(buffer_format: 2, dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_DEFAULT)
748
+ expect(smb1_client.smb1_negotiate_request.dialects).to_not include(
749
+ buffer_format: 2,
750
+ dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_DEFAULT
751
+ )
291
752
  end
292
753
 
293
754
  it 'excludes the default SMB1 Dialect if SMB1 support is disabled' do
294
- expect(smb2_client.smb1_negotiate_request.dialects).to_not include(buffer_format: 2, dialect_string: RubySMB::Client::SMB1_DIALECT_SMB1_DEFAULT)
755
+ expect(smb2_client.smb1_negotiate_request.dialects).to_not include(
756
+ buffer_format: 2,
757
+ dialect_string: RubySMB::Client::SMB1_DIALECT_SMB1_DEFAULT
758
+ )
759
+ end
760
+
761
+ it 'sets the SMB wildcard dialect if SMB2 support is enabled' do
762
+ expect(client.smb1_negotiate_request.dialects).to include(
763
+ buffer_format: 2,
764
+ dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_WILDCARD
765
+ )
766
+ end
767
+
768
+ it 'sets the SMB wildcard dialect if SMB3 support is enabled' do
769
+ expect(smb3_client.smb1_negotiate_request.dialects).to include(
770
+ buffer_format: 2,
771
+ dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_WILDCARD
772
+ )
773
+ end
774
+
775
+ it 'excludes the SMB wildcard dialect if both SMB2 and SMB3 supports are disabled' do
776
+ expect(smb1_client.smb1_negotiate_request.dialects).to_not include(
777
+ buffer_format: 2,
778
+ dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_WILDCARD
779
+ )
295
780
  end
296
781
  end
297
782
 
298
- describe '#smb2_negotiate_request' do
783
+ describe '#smb2_3_negotiate_request' do
299
784
  it 'return an SMB2 Negotiate Request packet' do
300
- expect(client.smb2_negotiate_request).to be_a(RubySMB::SMB2::Packet::NegotiateRequest)
785
+ expect(client.smb2_3_negotiate_request).to be_a(RubySMB::SMB2::Packet::NegotiateRequest)
786
+ end
787
+
788
+ it 'sets the default SMB2 Dialect if SMB2 support is enabled' do
789
+ expect(client.smb2_3_negotiate_request.dialects).to include(
790
+ *(RubySMB::Client::SMB2_DIALECT_DEFAULT.map {|d| d.to_i(16)})
791
+ )
301
792
  end
302
793
 
303
- it 'sets the default SMB2 Dialect' do
304
- expect(client.smb2_negotiate_request.dialects).to include(RubySMB::Client::SMB2_DIALECT_DEFAULT)
794
+ it 'does not set the default SMB2 Dialect if SMB2 support is disabled' do
795
+ expect(smb3_client.smb2_3_negotiate_request.dialects).to_not include(
796
+ *(RubySMB::Client::SMB2_DIALECT_DEFAULT.map {|d| d.to_i(16)})
797
+ )
305
798
  end
306
799
 
307
800
  it 'sets the Message ID to 0' do
308
- expect(client.smb2_negotiate_request.smb2_header.message_id).to eq 0
801
+ expect(client.smb2_3_negotiate_request.smb2_header.message_id).to eq 0
802
+ end
803
+
804
+ it 'adds SMB3 dialects if if SMB3 support is enabled' do
805
+ expect(client.smb2_3_negotiate_request.dialects).to include(
806
+ *(RubySMB::Client::SMB3_DIALECT_DEFAULT.map {|d| d.to_i(16)})
807
+ )
808
+ end
809
+
810
+ it 'does not set the default SMB3 Dialect if SMB3 support is disabled' do
811
+ expect(smb2_client.smb2_3_negotiate_request.dialects).to_not include(
812
+ *(RubySMB::Client::SMB3_DIALECT_DEFAULT.map {|d| d.to_i(16)})
813
+ )
814
+ end
815
+ end
816
+
817
+ describe '#add_smb3_to_negotiate_request' do
818
+ let(:negotiate_request) { RubySMB::SMB2::Packet::NegotiateRequest.new }
819
+
820
+ it 'adds the default SMB3 dialects' do
821
+ expect(client.add_smb3_to_negotiate_request(negotiate_request).dialects).to include(
822
+ *(RubySMB::Client::SMB3_DIALECT_DEFAULT.map {|d| d.to_i(16)})
823
+ )
824
+ end
825
+
826
+ it 'raises the expected exception when the dialects is not an array of strings' do
827
+ dialects = ['0x0300', 0x0302, '0x0311']
828
+ expect { client.add_smb3_to_negotiate_request(negotiate_request, dialects) }.to raise_error(ArgumentError)
829
+ end
830
+
831
+ it 'sets encryption capability flag' do
832
+ expect(client.add_smb3_to_negotiate_request(negotiate_request).capabilities.encryption).to eq(1)
833
+ end
834
+
835
+ context 'when the negotiate packet includes the 0x0311 dialect' do
836
+ before :example do
837
+ client.add_smb3_to_negotiate_request(negotiate_request, ['0x0311'])
838
+ end
839
+
840
+ it 'adds 3 Negotiate Contexts' do
841
+ expect(negotiate_request.negotiate_context_info.negotiate_context_count).to eq(3)
842
+ end
843
+
844
+ it 'adds a Preauth Integrity Negotiate Context with the expected hash algorithms' do
845
+ nc = negotiate_request.negotiate_context_list.select do |n|
846
+ n.context_type == RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
847
+ end
848
+ expect(nc.length).to eq(1)
849
+ expect(nc.first.data.hash_algorithms).to eq([RubySMB::SMB2::PreauthIntegrityCapabilities::SHA_512])
850
+ end
851
+
852
+ it 'adds Encryption Negotiate Contexts with the expected encryption algorithms' do
853
+ nc = negotiate_request.negotiate_context_list.select do |n|
854
+ n.context_type == RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
855
+ end
856
+ expect(nc.length).to eq(1)
857
+ expect(nc.first.data.ciphers).to eq(
858
+ [
859
+ RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM,
860
+ RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM
861
+ ]
862
+ )
863
+ end
864
+
865
+ it 'adds Compression Negotiate Contexts with the expected compression algorithms' do
866
+ nc = negotiate_request.negotiate_context_list.select do |n|
867
+ n.context_type == RubySMB::SMB2::NegotiateContext::SMB2_COMPRESSION_CAPABILITIES
868
+ end
869
+ expect(nc.length).to eq(1)
870
+ expect(nc.first.data.compression_algorithms).to eq(
871
+ [
872
+ RubySMB::SMB2::CompressionCapabilities::LZNT1,
873
+ RubySMB::SMB2::CompressionCapabilities::LZ77,
874
+ RubySMB::SMB2::CompressionCapabilities::LZ77_Huffman,
875
+ RubySMB::SMB2::CompressionCapabilities::Pattern_V1
876
+ ]
877
+ )
878
+ end
879
+ end
880
+
881
+ context 'when the negotiate packet does not include the 0x0311 dialect' do
882
+ it 'does not add any Negotiate Context' do
883
+ client.add_smb3_to_negotiate_request(negotiate_request, ['0x0300', '0x0302'])
884
+ expect(negotiate_request.negotiate_context_list?). to be false
885
+ end
309
886
  end
310
887
  end
311
888
 
@@ -320,10 +897,15 @@ RSpec.describe RubySMB::Client do
320
897
  client.negotiate_request
321
898
  end
322
899
 
323
- it 'calls #smb2_negotiate_request if SMB2 is enabled' do
324
- expect(smb2_client).to receive(:smb2_negotiate_request)
900
+ it 'calls #smb2_3_negotiate_request if SMB2 is enabled' do
901
+ expect(smb2_client).to receive(:smb2_3_negotiate_request)
325
902
  smb2_client.negotiate_request
326
903
  end
904
+
905
+ it 'calls #smb2_3_negotiate_request if SMB3 is enabled' do
906
+ expect(smb3_client).to receive(:smb2_3_negotiate_request)
907
+ smb3_client.negotiate_request
908
+ end
327
909
  end
328
910
 
329
911
  describe '#negotiate_response' do
@@ -332,10 +914,15 @@ RSpec.describe RubySMB::Client do
332
914
  expect(smb1_client.negotiate_response(smb1_extended_response_raw)).to eq smb1_extended_response
333
915
  end
334
916
 
335
- it 'raises an exception if the Response is invalid' do
917
+ it 'raises an exception if the response is not a SMB packet' do
336
918
  expect { smb1_client.negotiate_response(random_junk) }.to raise_error(RubySMB::Error::InvalidPacket)
337
919
  end
338
920
 
921
+ it 'raises an InvalidPacket error if the response is not a valid response' do
922
+ empty_packet.smb_header.command = RubySMB::SMB2::Commands::NEGOTIATE
923
+ expect { smb1_client.negotiate_response(empty_packet.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
924
+ end
925
+
339
926
  it 'considers the response invalid if it is not an actual Negotiate Response' do
340
927
  bogus_response = smb1_extended_response
341
928
  bogus_response.smb_header.command = 0xff
@@ -357,14 +944,36 @@ RSpec.describe RubySMB::Client do
357
944
  it 'raises an exception if the Response is invalid' do
358
945
  expect { smb2_client.negotiate_response(random_junk) }.to raise_error(RubySMB::Error::InvalidPacket)
359
946
  end
947
+
948
+ it 'considers the response invalid if it is not an actual Negotiate Response' do
949
+ bogus_response = smb2_response
950
+ bogus_response.smb2_header.command = RubySMB::SMB2::Commands::ECHO
951
+ expect { smb2_client.negotiate_response(bogus_response.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
952
+ end
953
+ end
954
+
955
+ context 'with only SMB3' do
956
+ it 'returns a properly formed packet' do
957
+ expect(smb3_client.negotiate_response(smb2_response.to_binary_s)).to eq smb2_response
958
+ end
959
+
960
+ it 'raises an exception if the Response is invalid' do
961
+ expect { smb3_client.negotiate_response(random_junk) }.to raise_error(RubySMB::Error::InvalidPacket)
962
+ end
963
+
964
+ it 'considers the response invalid if it is not an actual Negotiate Response' do
965
+ bogus_response = smb2_response
966
+ bogus_response.smb2_header.command = RubySMB::SMB2::Commands::ECHO
967
+ expect { smb3_client.negotiate_response(bogus_response.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
968
+ end
360
969
  end
361
970
 
362
- context 'with SMB1 and SMB2 enabled' do
971
+ context 'with SMB1, SMB2 and SMB3 enabled' do
363
972
  it 'returns an SMB1 NegotiateResponse if it looks like SMB1' do
364
973
  expect(client.negotiate_response(smb1_extended_response_raw)).to eq smb1_extended_response
365
974
  end
366
975
 
367
- it 'returns an SMB2 NegotiateResponse if it looks like SMB2' do
976
+ it 'returns an SMB2 NegotiateResponse if it looks like SMB2 or SMB3' do
368
977
  expect(client.negotiate_response(smb2_response.to_binary_s)).to eq smb2_response
369
978
  end
370
979
  end
@@ -372,9 +981,10 @@ RSpec.describe RubySMB::Client do
372
981
 
373
982
  describe '#parse_negotiate_response' do
374
983
  context 'when SMB1 was Negotiated' do
375
- it 'turns off SMB2 support' do
984
+ it 'turns off SMB2 and SMB3 support' do
376
985
  client.parse_negotiate_response(smb1_extended_response)
377
986
  expect(client.smb2).to be false
987
+ expect(client.smb3).to be false
378
988
  end
379
989
 
380
990
  it 'sets whether or not signing is required' do
@@ -383,67 +993,427 @@ RSpec.describe RubySMB::Client do
383
993
  expect(client.signing_required).to be true
384
994
  end
385
995
 
386
- it 'sets #dialect to the negotiated dialect' do
387
- smb1_extended_response.dialects = [
388
- RubySMB::SMB1::Dialect.new(dialect_string: 'A'),
389
- RubySMB::SMB1::Dialect.new(dialect_string: 'B'),
390
- RubySMB::SMB1::Dialect.new(dialect_string: 'C'),
391
- ]
392
- smb1_extended_response.parameter_block.dialect_index = 1
393
- client.parse_negotiate_response(smb1_extended_response)
394
- expect(client.dialect).to eq 'B'
395
- end
996
+ it 'sets #dialect to the negotiated dialect' do
997
+ smb1_extended_response.dialects = [
998
+ RubySMB::SMB1::Dialect.new(dialect_string: 'A'),
999
+ RubySMB::SMB1::Dialect.new(dialect_string: 'B'),
1000
+ RubySMB::SMB1::Dialect.new(dialect_string: 'C'),
1001
+ ]
1002
+ smb1_extended_response.parameter_block.dialect_index = 1
1003
+ client.parse_negotiate_response(smb1_extended_response)
1004
+ expect(client.dialect).to eq 'B'
1005
+ end
1006
+
1007
+ it 'returns the string \'SMB1\'' do
1008
+ expect(client.parse_negotiate_response(smb1_extended_response)).to eq ('SMB1')
1009
+ end
1010
+
1011
+ it 'sets #negotiated_smb_version to 1' do
1012
+ client.parse_negotiate_response(smb1_extended_response)
1013
+ expect(client.negotiated_smb_version).to eq(1)
1014
+ end
1015
+ end
1016
+
1017
+ context 'when SMB2 was negotiated' do
1018
+ it 'turns off SMB1 and SMB3 support' do
1019
+ client.parse_negotiate_response(smb2_response)
1020
+ expect(client.smb1).to be false
1021
+ expect(client.smb3).to be false
1022
+ end
1023
+
1024
+ it 'sets whether or not signing is required' do
1025
+ smb2_response.security_mode.signing_required = 1
1026
+ client.parse_negotiate_response(smb2_response)
1027
+ expect(client.signing_required).to be true
1028
+ end
1029
+
1030
+ it 'sets #dialect to the negotiated dialect' do
1031
+ smb2_response.dialect_revision = 2
1032
+ client.parse_negotiate_response(smb2_response)
1033
+ expect(client.dialect).to eq '0x0002'
1034
+ end
1035
+
1036
+ it 'returns the string \'SMB2\'' do
1037
+ expect(client.parse_negotiate_response(smb2_response)).to eq ('SMB2')
1038
+ end
1039
+ end
1040
+
1041
+ context 'when SMB3 was negotiated' do
1042
+ it 'turns off SMB1 and SMB2 support' do
1043
+ client.parse_negotiate_response(smb3_response)
1044
+ expect(client.smb1).to be false
1045
+ expect(client.smb2).to be false
1046
+ end
1047
+
1048
+ it 'sets whether or not signing is required' do
1049
+ smb3_response.security_mode.signing_required = 1
1050
+ client.parse_negotiate_response(smb3_response)
1051
+ expect(client.signing_required).to be true
1052
+ end
1053
+
1054
+ it 'sets #dialect to the negotiated dialect' do
1055
+ client.parse_negotiate_response(smb3_response)
1056
+ expect(client.dialect).to eq '0x0300'
1057
+ end
1058
+
1059
+ it 'returns the string \'SMB2\'' do
1060
+ expect(client.parse_negotiate_response(smb3_response)).to eq ('SMB3')
1061
+ end
1062
+
1063
+ context 'when the server supports encryption' do
1064
+ before :example do
1065
+ smb3_response.capabilities.encryption = 1
1066
+ end
1067
+
1068
+ it 'keeps session encryption enabled if it was already' do
1069
+ client.session_encrypt_data = true
1070
+ client.parse_negotiate_response(smb3_response)
1071
+ expect(client.session_encrypt_data).to be true
1072
+ end
1073
+
1074
+ it 'keeps session encryption disabled if it was already' do
1075
+ client.session_encrypt_data = false
1076
+ client.parse_negotiate_response(smb3_response)
1077
+ expect(client.session_encrypt_data).to be false
1078
+ end
1079
+ end
1080
+
1081
+ context 'when the server does not support encryption' do
1082
+ before :example do
1083
+ smb3_response.capabilities.encryption = 0
1084
+ end
1085
+
1086
+ it 'disables session encryption if it was already enabled' do
1087
+ client.session_encrypt_data = true
1088
+ client.parse_negotiate_response(smb3_response)
1089
+ expect(client.session_encrypt_data).to be false
1090
+ end
1091
+
1092
+ it 'keeps session encryption disabled if it was already' do
1093
+ client.session_encrypt_data = false
1094
+ client.parse_negotiate_response(smb3_response)
1095
+ expect(client.session_encrypt_data).to be false
1096
+ end
1097
+ end
1098
+ end
1099
+
1100
+ context 'when the response contains the SMB2 wildcard revision number dialect' do
1101
+ it 'only turns off SMB1 support' do
1102
+ smb2_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x02ff)
1103
+ client.parse_negotiate_response(smb2_response)
1104
+ expect(client.smb1).to be false
1105
+ expect(client.smb2).to be true
1106
+ expect(client.smb3).to be true
1107
+ end
1108
+ end
1109
+
1110
+ context 'when the negotiation failed' do
1111
+ context 'with a STATUS_NOT_SUPPORTED status code' do
1112
+ before :example do
1113
+ error_packet.smb2_header.nt_status = WindowsError::NTStatus::STATUS_NOT_SUPPORTED.value
1114
+ end
1115
+
1116
+ it 'raises the expected exception with SMB2' do
1117
+ expect { smb2_client.parse_negotiate_response(error_packet) }.to raise_error(
1118
+ RubySMB::Error::NegotiationFailure,
1119
+ 'Unable to negotiate with remote host, SMB2 not supported'
1120
+ )
1121
+ end
1122
+
1123
+ it 'raises the expected exception with SMB3' do
1124
+ expect { smb3_client.parse_negotiate_response(error_packet) }.to raise_error(
1125
+ RubySMB::Error::NegotiationFailure,
1126
+ 'Unable to negotiate with remote host, SMB3 not supported'
1127
+ )
1128
+ end
1129
+ end
1130
+
1131
+ context 'with an unknown status code' do
1132
+ it 'raises the expected exception' do
1133
+ expect { client.parse_negotiate_response(empty_packet) }.to raise_error(
1134
+ RubySMB::Error::NegotiationFailure,
1135
+ 'Unable to negotiate with remote host'
1136
+ )
1137
+ end
1138
+ end
1139
+ end
1140
+ end
1141
+
1142
+ describe '#negotiate' do
1143
+ let(:request_packet) { client.smb1_negotiate_request }
1144
+ before :example do
1145
+ allow(client).to receive(:negotiate_request)
1146
+ allow(client).to receive(:send_recv)
1147
+ allow(client).to receive(:negotiate_response)
1148
+ allow(client).to receive(:parse_negotiate_response)
1149
+ end
1150
+
1151
+ it 'calls the backing methods' do
1152
+ expect(client).to receive(:negotiate_request)
1153
+ expect(client).to receive(:send_recv)
1154
+ expect(client).to receive(:negotiate_response)
1155
+ expect(client).to receive(:parse_negotiate_response)
1156
+ client.negotiate
1157
+ end
1158
+
1159
+ context 'with SMB1' do
1160
+ it 'sets the response-packet #dialects array with the dialects sent in the request' do
1161
+ request_packet = client.smb1_negotiate_request
1162
+ allow(client).to receive(:negotiate_request).and_return(request_packet)
1163
+ allow(client).to receive(:negotiate_response).and_return(smb1_extended_response)
1164
+ expect(smb1_extended_response).to receive(:dialects=).with(request_packet.dialects)
1165
+ client.negotiate
1166
+ end
1167
+ end
1168
+
1169
+ ['0x0300', '0x0302'].each do |dialect|
1170
+ context "with #{dialect} dialect" do
1171
+ before :example do
1172
+ client.dialect = dialect
1173
+ end
1174
+
1175
+ it 'sets the expected encryption algorithm' do
1176
+ client.negotiate
1177
+ expect(client.encryption_algorithm).to eq(RubySMB::SMB2::EncryptionCapabilities::ENCRYPTION_ALGORITHM_MAP[RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM])
1178
+ end
1179
+ end
1180
+ end
1181
+
1182
+ context "with 0x0311 dialect" do
1183
+ it 'calls #parse_smb3_encryption_data' do
1184
+ client.dialect = '0x0311'
1185
+ request_packet = client.smb2_3_negotiate_request
1186
+ allow(client).to receive(:negotiate_request).and_return(request_packet)
1187
+ allow(client).to receive(:negotiate_response).and_return(smb3_response)
1188
+ expect(client).to receive(:parse_smb3_encryption_data).with(request_packet, smb3_response)
1189
+ client.negotiate
1190
+ end
1191
+ end
1192
+
1193
+ context 'with a wildcard revision number response' do
1194
+ before :example do
1195
+ client.dialect = '0x02ff'
1196
+ allow(client).to receive(:smb2_message_id=) do
1197
+ client.dialect = '0x0202'
1198
+ end
1199
+ end
1200
+
1201
+ it 'increments the message ID' do
1202
+ expect(client).to receive(:smb2_message_id=).with(1)
1203
+ client.negotiate
1204
+ end
1205
+
1206
+ it 're-negotiates' do
1207
+ expect(client).to receive(:negotiate_request).twice
1208
+ expect(client).to receive(:send_recv).twice
1209
+ expect(client).to receive(:negotiate_response).twice
1210
+ expect(client).to receive(:parse_negotiate_response).twice
1211
+ client.negotiate
1212
+ end
1213
+ end
1214
+
1215
+ context 'when an error occurs' do
1216
+ before :example do
1217
+ allow(client).to receive(:negotiate_request).and_return(request_packet)
1218
+ allow(client).to receive(:send_recv).and_raise(RubySMB::Error::InvalidPacket)
1219
+ client.smb1 = false
1220
+ client.smb2 = false
1221
+ client.smb3 = false
1222
+ end
1223
+
1224
+ context 'with SMB1' do
1225
+ let(:request_packet) { client.smb1_negotiate_request }
1226
+
1227
+ it 'raise the expected exception' do
1228
+ client.smb1 = true
1229
+ expect { client.negotiate }.to raise_error(
1230
+ RubySMB::Error::NegotiationFailure,
1231
+ "Unable to negotiate SMB1 with the remote host: RubySMB::Error::InvalidPacket"
1232
+ )
1233
+ end
1234
+ end
1235
+
1236
+ context 'with SMB2' do
1237
+ let(:request_packet) { client.smb2_3_negotiate_request }
1238
+
1239
+ it 'raise the expected exception' do
1240
+ client.smb2 = true
1241
+ expect { client.negotiate }.to raise_error(
1242
+ RubySMB::Error::NegotiationFailure,
1243
+ "Unable to negotiate SMB2 with the remote host: RubySMB::Error::InvalidPacket"
1244
+ )
1245
+ end
1246
+ end
1247
+
1248
+ context 'with SMB3' do
1249
+ let(:request_packet) { client.smb2_3_negotiate_request }
396
1250
 
397
- it 'returns the string \'SMB1\'' do
398
- expect(client.parse_negotiate_response(smb1_extended_response)).to eq ('SMB1')
1251
+ it 'raise the expected exception' do
1252
+ client.smb3 = true
1253
+ expect { client.negotiate }.to raise_error(
1254
+ RubySMB::Error::NegotiationFailure,
1255
+ "Unable to negotiate SMB3 with the remote host: RubySMB::Error::InvalidPacket"
1256
+ )
1257
+ end
399
1258
  end
400
1259
  end
401
1260
 
402
- context 'when SMB2 was negotiated' do
403
- it 'turns off SMB1 support' do
404
- client.parse_negotiate_response(smb2_response)
405
- expect(client.smb1).to be false
1261
+ describe '#parse_smb3_encryption_data' do
1262
+ let(:request_packet) { client.smb2_3_negotiate_request }
1263
+ let(:smb3_response) { RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311) }
1264
+ let(:nc_encryption) do
1265
+ nc = RubySMB::SMB2::NegotiateContext.new(
1266
+ context_type: RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
1267
+ )
1268
+ nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM
1269
+ nc
406
1270
  end
407
-
408
- it 'sets whether or not signing is required' do
409
- smb2_response.security_mode.signing_required = 1
410
- client.parse_negotiate_response(smb2_response)
411
- expect(client.signing_required).to be true
1271
+ let(:nc_integrity) do
1272
+ nc = RubySMB::SMB2::NegotiateContext.new(
1273
+ context_type: RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
1274
+ )
1275
+ nc.data.hash_algorithms << RubySMB::SMB2::PreauthIntegrityCapabilities::SHA_512
1276
+ nc
412
1277
  end
413
1278
 
414
- it 'sets #dialect to the negotiated dialect' do
415
- smb2_response.dialect_revision = 2
416
- client.parse_negotiate_response(smb2_response)
417
- expect(client.dialect).to eq '0x0002'
1279
+ before :example do
1280
+ allow(smb3_client).to receive(:update_preauth_hash)
1281
+ smb3_response.add_negotiate_context(nc_encryption)
1282
+ smb3_response.add_negotiate_context(nc_integrity)
418
1283
  end
419
1284
 
420
- it 'returns the string \'SMB2\'' do
421
- expect(client.parse_negotiate_response(smb2_response)).to eq ('SMB2')
1285
+ context 'when selecting the integrity hash algorithm' do
1286
+ context 'with one algorithm' do
1287
+ it 'selects the expected algorithm' do
1288
+ smb3_client.parse_smb3_encryption_data(request_packet, smb3_response)
1289
+ expect(smb3_client.preauth_integrity_hash_algorithm).to eq('SHA512')
1290
+ end
1291
+ end
1292
+
1293
+ context 'with multiple algorithms' do
1294
+ it 'selects the first algorithm' do
1295
+ nc = smb3_response.find_negotiate_context(
1296
+ RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
1297
+ )
1298
+ nc.data.hash_algorithms << 3
1299
+ smb3_client.parse_smb3_encryption_data(request_packet, smb3_response)
1300
+ expect(smb3_client.preauth_integrity_hash_algorithm).to eq('SHA512')
1301
+ end
1302
+ end
1303
+
1304
+ context 'without integrity negotiate context' do
1305
+ it 'raises the expected exception' do
1306
+ smb3_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311)
1307
+ smb3_response.add_negotiate_context(nc_encryption)
1308
+ expect { smb3_client.parse_smb3_encryption_data(request_packet, smb3_response) }.to raise_error(
1309
+ RubySMB::Error::EncryptionError,
1310
+ 'Unable to retrieve the Preauth Integrity Hash Algorithm from the Negotiate response'
1311
+ )
1312
+ end
1313
+ end
1314
+
1315
+ context 'with an unknown integrity hash algorithm' do
1316
+ it 'raises the expected exception' do
1317
+ smb3_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311)
1318
+ smb3_response.add_negotiate_context(nc_encryption)
1319
+ nc = RubySMB::SMB2::NegotiateContext.new(
1320
+ context_type: RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
1321
+ )
1322
+ nc.data.hash_algorithms << 5
1323
+ smb3_response.add_negotiate_context(nc)
1324
+ expect { smb3_client.parse_smb3_encryption_data(request_packet, smb3_response) }.to raise_error(
1325
+ RubySMB::Error::EncryptionError,
1326
+ 'Unable to retrieve the Preauth Integrity Hash Algorithm from the Negotiate response'
1327
+ )
1328
+ end
1329
+ end
422
1330
  end
423
- end
424
- end
425
1331
 
426
- describe '#negotiate' do
427
- it 'calls the backing methods' do
428
- expect(client).to receive(:negotiate_request)
429
- expect(client).to receive(:send_recv)
430
- expect(client).to receive(:negotiate_response)
431
- expect(client).to receive(:parse_negotiate_response)
432
- client.negotiate
433
- end
1332
+ context 'when selecting the encryption algorithm' do
1333
+ context 'with one algorithm' do
1334
+ it 'selects the expected algorithm' do
1335
+ smb3_client.parse_smb3_encryption_data(request_packet, smb3_response)
1336
+ expect(smb3_client.encryption_algorithm).to eq('AES-128-CCM')
1337
+ end
1338
+ end
434
1339
 
435
- it 'sets the response-packet #dialects array with the dialects sent in the request' do
436
- request_packet = client.smb1_negotiate_request
437
- allow(client).to receive(:negotiate_request).and_return(request_packet)
438
- allow(client).to receive(:send_recv)
439
- allow(client).to receive(:negotiate_response).and_return(smb1_extended_response)
440
- expect(smb1_extended_response).to receive(:dialects=).with(request_packet.dialects)
441
- client.negotiate
442
- end
1340
+ context 'with multiple algorithms' do
1341
+ it 'selects the AES-128-GCM algorithm if included' do
1342
+ nc = smb3_response.find_negotiate_context(
1343
+ RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
1344
+ )
1345
+ nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM
1346
+ smb3_client.parse_smb3_encryption_data(request_packet, smb3_response)
1347
+ expect(smb3_client.encryption_algorithm).to eq('AES-128-GCM')
1348
+ end
1349
+
1350
+ it 'selects the first algorithm if AES-128-GCM is not included' do
1351
+ nc = smb3_response.find_negotiate_context(
1352
+ RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
1353
+ )
1354
+ nc.data.ciphers << 3
1355
+ smb3_client.parse_smb3_encryption_data(request_packet, smb3_response)
1356
+ expect(smb3_client.encryption_algorithm).to eq('AES-128-CCM')
1357
+ end
1358
+
1359
+ it 'keep tracks of the server supported algorithms' do
1360
+ nc = smb3_response.find_negotiate_context(
1361
+ RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
1362
+ )
1363
+ nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM
1364
+ smb3_client.parse_smb3_encryption_data(request_packet, smb3_response)
1365
+ expect(smb3_client.server_encryption_algorithms).to eq([1, 2])
1366
+ end
1367
+ end
1368
+
1369
+ context 'without encryption context' do
1370
+ it 'raises the expected exception' do
1371
+ smb3_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311)
1372
+ smb3_response.add_negotiate_context(nc_integrity)
1373
+ expect { smb3_client.parse_smb3_encryption_data(request_packet, smb3_response) }.to raise_error(
1374
+ RubySMB::Error::EncryptionError,
1375
+ 'Unable to retrieve the encryption cipher list supported by the server from the Negotiate response'
1376
+ )
1377
+ end
1378
+ end
1379
+
1380
+ context 'with an unknown encryption algorithm' do
1381
+ it 'raises the expected exception' do
1382
+ smb3_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311)
1383
+ smb3_response.add_negotiate_context(nc_integrity)
1384
+ nc = RubySMB::SMB2::NegotiateContext.new(
1385
+ context_type: RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
1386
+ )
1387
+ nc.data.ciphers << 14
1388
+ smb3_response.add_negotiate_context(nc)
1389
+ expect { smb3_client.parse_smb3_encryption_data(request_packet, smb3_response) }.to raise_error(
1390
+ RubySMB::Error::EncryptionError,
1391
+ 'Unable to retrieve the encryption cipher list supported by the server from the Negotiate response'
1392
+ )
1393
+ end
1394
+ end
1395
+ end
1396
+
1397
+ context 'when selecting the compression algorithm' do
1398
+ it 'keep tracks of the server supported algorithms' do
1399
+ nc = RubySMB::SMB2::NegotiateContext.new(
1400
+ context_type: RubySMB::SMB2::NegotiateContext::SMB2_COMPRESSION_CAPABILITIES
1401
+ )
1402
+ nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZNT1
1403
+ nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZ77
1404
+ nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZ77_Huffman
1405
+ nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::Pattern_V1
1406
+ smb3_response.add_negotiate_context(nc)
1407
+ smb3_client.parse_smb3_encryption_data(request_packet, smb3_response)
1408
+ expect(smb3_client.server_compression_algorithms).to eq([1, 2, 3, 4])
1409
+ end
1410
+ end
443
1411
 
444
- it 'raise the expected exception if an error occurs' do
445
- allow(client).to receive(:send_recv).and_raise(RubySMB::Error::InvalidPacket)
446
- expect { client.negotiate }.to raise_error(RubySMB::Error::NegotiationFailure)
1412
+ it 'updates the preauth hash' do
1413
+ expect(smb3_client).to receive(:update_preauth_hash).with(request_packet)
1414
+ expect(smb3_client).to receive(:update_preauth_hash).with(smb3_response)
1415
+ smb3_client.parse_smb3_encryption_data(request_packet, smb3_response)
1416
+ end
447
1417
  end
448
1418
  end
449
1419
  end
@@ -624,7 +1594,7 @@ RSpec.describe RubySMB::Client do
624
1594
  expect { smb1_client.smb1_ntlmssp_challenge_packet(response.to_binary_s) }.to raise_error(RubySMB::Error::UnexpectedStatusCode)
625
1595
  end
626
1596
 
627
- it 'raises an InvalidPacket if the Command field is wrong' do
1597
+ it 'raise an InvalidPacket exception when the response is not valid' do
628
1598
  expect { smb1_client.smb1_ntlmssp_challenge_packet(wrong_command.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
629
1599
  end
630
1600
  end
@@ -645,7 +1615,7 @@ RSpec.describe RubySMB::Client do
645
1615
  expect(smb1_client.smb1_ntlmssp_final_packet(response.to_binary_s)).to eq response
646
1616
  end
647
1617
 
648
- it 'raises an InvalidPacket if the Command field is wrong' do
1618
+ it 'raise an InvalidPacket exception when the response is not valid' do
649
1619
  expect { smb1_client.smb1_ntlmssp_final_packet(wrong_command.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
650
1620
  end
651
1621
  end
@@ -717,12 +1687,7 @@ RSpec.describe RubySMB::Client do
717
1687
  expect(smb1_client.smb1_anonymous_auth_response(anonymous_response.to_binary_s)).to eq anonymous_response
718
1688
  end
719
1689
 
720
- it 'returns an empty packet if the raw data is not a valid response' do
721
- empty_packet.smb_header.command = RubySMB::SMB1::Commands::SMB_COM_SESSION_SETUP
722
- expect(smb1_client.smb1_anonymous_auth_response(empty_packet.to_binary_s)).to eq empty_packet
723
- end
724
-
725
- it 'raises an InvalidPacket error if the command is wrong' do
1690
+ it 'raise an InvalidPacket exception when the response is not valid' do
726
1691
  anonymous_response.smb_header.command = RubySMB::SMB1::Commands::SMB_COM_NEGOTIATE
727
1692
  expect { smb1_client.smb1_anonymous_auth_response(anonymous_response.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
728
1693
  end
@@ -775,6 +1740,39 @@ RSpec.describe RubySMB::Client do
775
1740
  smb2_client.smb2_authenticate
776
1741
  expect(smb2_client.os_version).to eq '6.1.7601'
777
1742
  end
1743
+
1744
+ ['0x0202', '0x0210', '0x0300', '0x0302'].each do |dialect|
1745
+ it "does not update the preauth hash with dialect #{dialect}" do
1746
+ smb2_client.dialect = dialect
1747
+ expect(smb2_client).to_not receive(:update_preauth_hash)
1748
+ smb2_client.smb2_authenticate
1749
+ end
1750
+ end
1751
+
1752
+ it "updates the preauth hash with dialect 0x0311" do
1753
+ smb2_client.dialect = '0x0311'
1754
+ expect(smb2_client).to receive(:update_preauth_hash).with(response_packet)
1755
+ smb2_client.smb2_authenticate
1756
+ end
1757
+
1758
+ context 'when setting the session_encrypt_data parameter' do
1759
+ before :example do
1760
+ smb2_client.smb3 = true
1761
+ smb2_client.session_encrypt_data = false
1762
+ end
1763
+
1764
+ it 'sets the session_encrypt_data parameter to true if the server requires encryption' do
1765
+ final_response_packet.session_flags.encrypt_data = 1
1766
+ smb2_client.smb2_authenticate
1767
+ expect(smb2_client.session_encrypt_data).to be true
1768
+ end
1769
+
1770
+ it 'does not set the session_encrypt_data parameter if the server does not require encryption' do
1771
+ final_response_packet.session_flags.encrypt_data = 0
1772
+ smb2_client.smb2_authenticate
1773
+ expect(smb2_client.session_encrypt_data).to be false
1774
+ end
1775
+ end
778
1776
  end
779
1777
 
780
1778
  describe '#smb2_ntlmssp_negotiate_packet' do
@@ -790,20 +1788,34 @@ RSpec.describe RubySMB::Client do
790
1788
  smb2_client.smb2_ntlmssp_negotiate_packet
791
1789
  end
792
1790
 
793
- it 'sets the message ID in the packet header to 1' do
794
- expect(smb2_client.smb2_ntlmssp_negotiate_packet.smb2_header.message_id).to eq 1
795
- end
796
-
797
- it 'increments client#smb2_message_id' do
798
- expect { smb2_client.smb2_ntlmssp_negotiate_packet }.to change(smb2_client, :smb2_message_id).to(2)
1791
+ it 'enables signing' do
1792
+ expect(smb2_client.smb2_ntlmssp_negotiate_packet.security_mode.signing_enabled).to eq 1
799
1793
  end
800
1794
  end
801
1795
 
802
1796
  describe '#smb2_ntlmssp_negotiate' do
1797
+ before :example do
1798
+ allow(smb2_client).to receive(:smb2_ntlmssp_negotiate_packet).and_return(negotiate_packet)
1799
+ allow(smb2_client).to receive(:send_recv)
1800
+ end
1801
+
803
1802
  it 'sends the request packet and receives a response' do
804
- expect(smb2_client).to receive(:smb2_ntlmssp_negotiate_packet).and_return(negotiate_packet)
805
- expect(dispatcher).to receive(:send_packet).with(negotiate_packet)
806
- expect(dispatcher).to receive(:recv_packet)
1803
+ expect(smb2_client).to receive(:smb2_ntlmssp_negotiate_packet)
1804
+ expect(smb2_client).to receive(:send_recv).with(negotiate_packet)
1805
+ smb2_client.smb2_ntlmssp_negotiate
1806
+ end
1807
+
1808
+ ['0x0202', '0x0210', '0x0300', '0x0302'].each do |dialect|
1809
+ it "does not update the preauth hash with dialect #{dialect}" do
1810
+ smb2_client.dialect = dialect
1811
+ expect(smb2_client).to_not receive(:update_preauth_hash)
1812
+ smb2_client.smb2_ntlmssp_negotiate
1813
+ end
1814
+ end
1815
+
1816
+ it "updates the preauth hash with dialect 0x0311" do
1817
+ smb2_client.dialect = '0x0311'
1818
+ expect(smb2_client).to receive(:update_preauth_hash).with(negotiate_packet)
807
1819
  smb2_client.smb2_ntlmssp_negotiate
808
1820
  end
809
1821
  end
@@ -829,7 +1841,7 @@ RSpec.describe RubySMB::Client do
829
1841
  expect { smb2_client.smb2_ntlmssp_challenge_packet(response.to_binary_s) }.to raise_error(RubySMB::Error::UnexpectedStatusCode)
830
1842
  end
831
1843
 
832
- it 'raises an InvalidPacket if the Command field is wrong' do
1844
+ it 'raise an InvalidPacket exception when the response is not valid' do
833
1845
  expect { smb2_client.smb2_ntlmssp_challenge_packet(wrong_command.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
834
1846
  end
835
1847
  end
@@ -861,13 +1873,35 @@ RSpec.describe RubySMB::Client do
861
1873
  it 'sets the session ID on the request packet' do
862
1874
  expect(smb2_client.smb2_ntlmssp_auth_packet(type3_message, session_id).smb2_header.session_id).to eq session_id
863
1875
  end
1876
+
1877
+ it 'enables signing' do
1878
+ expect(smb2_client.smb2_ntlmssp_auth_packet(type3_message, session_id).security_mode.signing_enabled).to eq 1
1879
+ end
864
1880
  end
865
1881
 
866
1882
  describe '#smb2_ntlmssp_authenticate' do
1883
+ before :example do
1884
+ allow(smb2_client).to receive(:smb2_ntlmssp_auth_packet).and_return(negotiate_packet)
1885
+ allow(smb2_client).to receive(:send_recv)
1886
+ end
1887
+
867
1888
  it 'sends the request packet and receives a response' do
868
- expect(smb2_client).to receive(:smb2_ntlmssp_auth_packet).and_return(negotiate_packet)
869
- expect(dispatcher).to receive(:send_packet).with(negotiate_packet)
870
- expect(dispatcher).to receive(:recv_packet)
1889
+ expect(smb2_client).to receive(:smb2_ntlmssp_auth_packet)
1890
+ expect(smb2_client).to receive(:send_recv).with(negotiate_packet)
1891
+ smb2_client.smb2_ntlmssp_authenticate(type3_message, session_id)
1892
+ end
1893
+
1894
+ ['0x0202', '0x0210', '0x0300', '0x0302'].each do |dialect|
1895
+ it "does not update the preauth hash with dialect #{dialect}" do
1896
+ smb2_client.dialect = dialect
1897
+ expect(smb2_client).to_not receive(:update_preauth_hash)
1898
+ smb2_client.smb2_ntlmssp_authenticate(type3_message, session_id)
1899
+ end
1900
+ end
1901
+
1902
+ it "updates the preauth hash with dialect 0x0311" do
1903
+ smb2_client.dialect = '0x0311'
1904
+ expect(smb2_client).to receive(:update_preauth_hash).with(negotiate_packet)
871
1905
  smb2_client.smb2_ntlmssp_authenticate(type3_message, session_id)
872
1906
  end
873
1907
  end
@@ -888,7 +1922,7 @@ RSpec.describe RubySMB::Client do
888
1922
  expect(smb2_client.smb2_ntlmssp_final_packet(response.to_binary_s)).to eq response
889
1923
  end
890
1924
 
891
- it 'raises an InvalidPacket if the Command field is wrong' do
1925
+ it 'raise an InvalidPacket exception when the response is not valid' do
892
1926
  expect { smb2_client.smb2_ntlmssp_final_packet(wrong_command.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
893
1927
  end
894
1928
  end
@@ -1003,6 +2037,108 @@ RSpec.describe RubySMB::Client do
1003
2037
  end
1004
2038
  end
1005
2039
  end
2040
+
2041
+ describe '#smb3_sign' do
2042
+ context 'if signing is required and we have a session key' do
2043
+ let(:request) {
2044
+ packet = RubySMB::SMB2::Packet::SessionSetupRequest.new
2045
+ packet.smb2_header.flags.signed = 1
2046
+ packet.smb2_header.signature = "\x00" * 16
2047
+ packet
2048
+ }
2049
+ let(:session_key) { 'Session Key' }
2050
+ before :example do
2051
+ smb3_client.session_key = session_key
2052
+ smb3_client.signing_required = true
2053
+ end
2054
+
2055
+ ['0x0300', '0x0302'].each do |dialect|
2056
+ context "with #{dialect} dialect" do
2057
+ it 'generates the signing key based on the session key and specific strings, and sign the packet with CMAC' do
2058
+ smb3_client.dialect = dialect
2059
+ fake_hash = "\x34\xc0\x40\xfe\x87\xcf\x49\x3d\x37\x87\x52\xd0\xd5\xf5\xfb\x86".b
2060
+ signing_key = RubySMB::Crypto::KDF.counter_mode(session_key, "SMB2AESCMAC\x00", "SmbSign\x00")
2061
+ expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(session_key, "SMB2AESCMAC\x00", "SmbSign\x00").and_call_original
2062
+ expect(OpenSSL::CMAC).to receive(:digest).with('AES', signing_key, request.to_binary_s).and_call_original
2063
+ expect(smb3_client.smb3_sign(request).smb2_header.signature).to eq fake_hash
2064
+ end
2065
+ end
2066
+ end
2067
+
2068
+ context "with 0x0311 dialect" do
2069
+ it 'generates the signing key based on the session key, the preauth integrity hash and specific strings, and sign the packet with CMAC' do
2070
+ smb3_client.dialect = '0x0311'
2071
+ preauth_integrity_hash_value = 'Preauth Integrity Hash'
2072
+ fake_hash = "\x0e\x49\x6f\x8e\x74\x7c\xf2\xa0\x88\x5e\x9d\x54\xff\x0d\x0d\xfa".b
2073
+ smb3_client.preauth_integrity_hash_value = preauth_integrity_hash_value
2074
+ signing_key = RubySMB::Crypto::KDF.counter_mode(session_key, "SMBSigningKey\x00", preauth_integrity_hash_value)
2075
+ expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(session_key, "SMBSigningKey\x00", preauth_integrity_hash_value).and_call_original
2076
+ expect(OpenSSL::CMAC).to receive(:digest).with('AES', signing_key, request.to_binary_s).and_call_original
2077
+ expect(smb3_client.smb3_sign(request).smb2_header.signature).to eq fake_hash
2078
+ end
2079
+ end
2080
+
2081
+ context 'with an incompatible dialect' do
2082
+ it 'raises the expected exception' do
2083
+ smb3_client.dialect = '0x0202'
2084
+ expect { smb3_client.smb3_sign(request) }.to raise_error(
2085
+ RubySMB::Error::SigningError,
2086
+ 'Dialect is incompatible with SMBv3 signing'
2087
+ )
2088
+ end
2089
+ end
2090
+ end
2091
+
2092
+ context 'if signing is not required but it is a TreeConnectRequest and we have a session key' do
2093
+ let(:request) {
2094
+ packet = RubySMB::SMB2::Packet::TreeConnectRequest.new
2095
+ packet.smb2_header.flags.signed = 1
2096
+ packet.smb2_header.signature = "\x00" * 16
2097
+ packet
2098
+ }
2099
+ let(:session_key) { 'Session Key' }
2100
+ before :example do
2101
+ smb3_client.session_key = session_key
2102
+ smb3_client.signing_required = false
2103
+ end
2104
+
2105
+ ['0x0300', '0x0302'].each do |dialect|
2106
+ context "with #{dialect} dialect" do
2107
+ it 'generates the signing key based on the session key and specific strings, and sign the packet with CMAC' do
2108
+ smb3_client.dialect = dialect
2109
+ fake_hash = "\x34\x9e\x28\xb9\x50\x08\x34\x31\xc0\x83\x9d\xba\x56\xa5\x70\xa4".b
2110
+ signing_key = RubySMB::Crypto::KDF.counter_mode(session_key, "SMB2AESCMAC\x00", "SmbSign\x00")
2111
+ expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(session_key, "SMB2AESCMAC\x00", "SmbSign\x00").and_call_original
2112
+ expect(OpenSSL::CMAC).to receive(:digest).with('AES', signing_key, request.to_binary_s).and_call_original
2113
+ expect(smb3_client.smb3_sign(request).smb2_header.signature).to eq fake_hash
2114
+ end
2115
+ end
2116
+ end
2117
+
2118
+ context "with 0x0311 dialect" do
2119
+ it 'generates the signing key based on the session key, the preauth integrity hash and specific strings, and sign the packet with CMAC' do
2120
+ smb3_client.dialect = '0x0311'
2121
+ preauth_integrity_hash_value = 'Preauth Integrity Hash'
2122
+ fake_hash = "\x83\xd9\x31\x39\x60\x46\xbe\x1e\x29\x34\xc8\xcf\x8c\x8e\xb4\x73".b
2123
+ smb3_client.preauth_integrity_hash_value = preauth_integrity_hash_value
2124
+ signing_key = RubySMB::Crypto::KDF.counter_mode(session_key, "SMBSigningKey\x00", preauth_integrity_hash_value)
2125
+ expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(session_key, "SMBSigningKey\x00", preauth_integrity_hash_value).and_call_original
2126
+ expect(OpenSSL::CMAC).to receive(:digest).with('AES', signing_key, request.to_binary_s).and_call_original
2127
+ expect(smb3_client.smb3_sign(request).smb2_header.signature).to eq fake_hash
2128
+ end
2129
+ end
2130
+
2131
+ context 'with an incompatible dialect' do
2132
+ it 'raises the expected exception' do
2133
+ smb3_client.dialect = '0x0202'
2134
+ expect { smb3_client.smb3_sign(request) }.to raise_error(
2135
+ RubySMB::Error::SigningError,
2136
+ 'Dialect is incompatible with SMBv3 signing'
2137
+ )
2138
+ end
2139
+ end
2140
+ end
2141
+ end
1006
2142
  end
1007
2143
 
1008
2144
  context '#increment_smb_message_id' do
@@ -1056,7 +2192,10 @@ RSpec.describe RubySMB::Client do
1056
2192
 
1057
2193
  it 'raises an UnexpectedStatusCode exception if we do not get STATUS_SUCCESS' do
1058
2194
  response.smb_header.nt_status = 0xc0000015
1059
- expect { smb1_client.smb1_tree_from_response(path, response) }.to raise_error(RubySMB::Error::UnexpectedStatusCode, 'STATUS_NONEXISTENT_SECTOR')
2195
+ expect { smb1_client.smb1_tree_from_response(path, response) }.to raise_error(
2196
+ RubySMB::Error::UnexpectedStatusCode,
2197
+ 'The server responded with an unexpected status code: STATUS_NONEXISTENT_SECTOR'
2198
+ )
1060
2199
  end
1061
2200
 
1062
2201
  it 'creates a new Tree from itself, the share path, and the response packet' do
@@ -1077,11 +2216,14 @@ RSpec.describe RubySMB::Client do
1077
2216
  }
1078
2217
 
1079
2218
  describe '#smb2_tree_connect' do
1080
- it 'builds and sends a TreeconnectRequest for the supplied share' do
2219
+ it 'builds and sends the expected TreeconnectRequest for the supplied share' do
1081
2220
  allow(RubySMB::SMB2::Packet::TreeConnectRequest).to receive(:new).and_return(request)
1082
- modified_request = request
1083
- modified_request.encode_path(path)
1084
- expect(smb2_client).to receive(:send_recv).with(modified_request).and_return(response.to_binary_s)
2221
+ expect(smb2_client).to receive(:send_recv) do |req|
2222
+ expect(req).to eq(request)
2223
+ expect(req.smb2_header.tree_id).to eq(65_535)
2224
+ expect(req.path).to eq(path.encode('UTF-16LE'))
2225
+ response.to_binary_s
2226
+ end
1085
2227
  smb2_client.smb2_tree_connect(path)
1086
2228
  end
1087
2229
 
@@ -1100,11 +2242,20 @@ RSpec.describe RubySMB::Client do
1100
2242
 
1101
2243
  it 'raises an UnexpectedStatusCode exception if we do not get STATUS_SUCCESS' do
1102
2244
  response.smb2_header.nt_status = 0xc0000015
1103
- expect { smb2_client.smb2_tree_from_response(path, response) }.to raise_error(RubySMB::Error::UnexpectedStatusCode, 'STATUS_NONEXISTENT_SECTOR')
2245
+ expect { smb2_client.smb2_tree_from_response(path, response) }.to raise_error(
2246
+ RubySMB::Error::UnexpectedStatusCode,
2247
+ 'The server responded with an unexpected status code: STATUS_NONEXISTENT_SECTOR'
2248
+ )
1104
2249
  end
1105
2250
 
1106
2251
  it 'creates a new Tree from itself, the share path, and the response packet' do
1107
- expect(RubySMB::SMB2::Tree).to receive(:new).with(client: smb2_client, share: path, response: response)
2252
+ expect(RubySMB::SMB2::Tree).to receive(:new).with(client: smb2_client, share: path, response: response, encrypt: false)
2253
+ smb2_client.smb2_tree_from_response(path, response)
2254
+ end
2255
+
2256
+ it 'creates a new with encryption set if the response requires it' do
2257
+ response.share_flags.encrypt = 1
2258
+ expect(RubySMB::SMB2::Tree).to receive(:new).with(client: smb2_client, share: path, response: response, encrypt: true)
1108
2259
  smb2_client.smb2_tree_from_response(path, response)
1109
2260
  end
1110
2261
  end
@@ -1199,6 +2350,12 @@ RSpec.describe RubySMB::Client do
1199
2350
  expect(smb1_client).to receive(:send_recv).and_return(echo_response.to_binary_s)
1200
2351
  expect(smb1_client.echo).to eq WindowsError::NTStatus::STATUS_ABANDONED
1201
2352
  end
2353
+
2354
+ it 'raise an InvalidPacket exception when the response is not valid' do
2355
+ echo_response.smb_header.command = RubySMB::SMB1::Commands::SMB_COM_SESSION_SETUP_ANDX
2356
+ allow(smb1_client).to receive(:send_recv).and_return(echo_response.to_binary_s)
2357
+ expect { smb1_client.echo }.to raise_error(RubySMB::Error::InvalidPacket)
2358
+ end
1202
2359
  end
1203
2360
 
1204
2361
  context 'with SMB2' do
@@ -1210,8 +2367,355 @@ RSpec.describe RubySMB::Client do
1210
2367
  expect(smb2_client).to receive(:send_recv).with(echo_request).and_return(echo_response.to_binary_s)
1211
2368
  expect(smb2_client.smb2_echo).to eq echo_response
1212
2369
  end
2370
+
2371
+ it 'raise an InvalidPacket exception when the response is not valid' do
2372
+ echo_response.smb2_header.command = RubySMB::SMB2::Commands::SESSION_SETUP
2373
+ allow(smb2_client).to receive(:send_recv).and_return(echo_response.to_binary_s)
2374
+ expect { smb2_client.smb2_echo }.to raise_error(RubySMB::Error::InvalidPacket)
2375
+ end
2376
+ end
2377
+ end
2378
+
2379
+ context 'Winreg' do
2380
+ describe '#connect_to_winreg' do
2381
+ let(:host) { '1.2.3.4' }
2382
+ let(:share) { "\\\\#{host}\\IPC$" }
2383
+ let(:ipc_tree) { double('IPC$ tree') }
2384
+ let(:named_pipe) { double('Named pipe') }
2385
+ before :example do
2386
+ allow(ipc_tree).to receive_messages(
2387
+ :share => share,
2388
+ :open_file => named_pipe
2389
+ )
2390
+ allow(client).to receive(:tree_connect).and_return(ipc_tree)
2391
+ end
2392
+
2393
+ context 'when the client is already connected to the IPC$ share' do
2394
+ before :example do
2395
+ client.tree_connects << ipc_tree
2396
+ allow(ipc_tree).to receive(:share).and_return(share)
2397
+ end
2398
+
2399
+ it 'does not connect to the already connected tree' do
2400
+ client.connect_to_winreg(host)
2401
+ expect(client).to_not have_received(:tree_connect)
2402
+ end
2403
+ end
2404
+
2405
+ it 'calls #tree_connect' do
2406
+ client.connect_to_winreg(host)
2407
+ expect(client).to have_received(:tree_connect).with(share)
2408
+ end
2409
+
2410
+ it 'open \'winreg\' file on the IPC$ Tree' do
2411
+ client.connect_to_winreg(host)
2412
+ expect(ipc_tree).to have_received(:open_file).with(filename: "winreg", write: true, read: true)
2413
+ end
2414
+
2415
+ it 'returns the expected opened named pipe' do
2416
+ expect(client.connect_to_winreg(host)).to eq(named_pipe)
2417
+ end
2418
+
2419
+ context 'when a block is given' do
2420
+ before :example do
2421
+ allow(named_pipe).to receive(:close)
2422
+ end
2423
+
2424
+ it 'yields the expected named_pipe' do
2425
+ client.connect_to_winreg(host) do |np|
2426
+ expect(np).to eq(named_pipe)
2427
+ end
2428
+ end
2429
+
2430
+ it 'closes the named pipe' do
2431
+ client.connect_to_winreg(host) { |np| }
2432
+ expect(named_pipe).to have_received(:close)
2433
+ end
2434
+
2435
+ it 'returns the block return value' do
2436
+ result = double('Result')
2437
+ expect(client.connect_to_winreg(host) { |np| result }).to eq(result)
2438
+ end
2439
+ end
2440
+ end
2441
+
2442
+ describe '#has_registry_key?' do
2443
+ let(:host) { '1.2.3.4' }
2444
+ let(:key) { 'HKLM\\Registry\\Key' }
2445
+ let(:named_pipe) { double('Named pipe') }
2446
+ let(:result) { double('Result') }
2447
+ before :example do
2448
+ allow(client).to receive(:connect_to_winreg).and_yield(named_pipe)
2449
+ allow(named_pipe).to receive(:has_registry_key?).and_return(result)
2450
+ end
2451
+
2452
+ it 'calls #connect_to_winreg to wrap the main logic around' do
2453
+ client.has_registry_key?(host, key)
2454
+ expect(client).to have_received(:connect_to_winreg).with(host)
2455
+ end
2456
+
2457
+ it 'calls Pipe #has_registry_key?' do
2458
+ client.has_registry_key?(host, key)
2459
+ expect(named_pipe).to have_received(:has_registry_key?).with(key)
2460
+ end
2461
+ end
2462
+
2463
+ describe '#read_registry_key_value' do
2464
+ let(:host) { '1.2.3.4' }
2465
+ let(:key) { 'HKLM\\Registry\\Key' }
2466
+ let(:value_name) { 'Value' }
2467
+ let(:named_pipe) { double('Named pipe') }
2468
+ let(:result) { double('Result') }
2469
+ before :example do
2470
+ allow(client).to receive(:connect_to_winreg).and_yield(named_pipe)
2471
+ allow(named_pipe).to receive(:read_registry_key_value).and_return(result)
2472
+ end
2473
+
2474
+ it 'calls #connect_to_winreg to wrap the main logic around' do
2475
+ client.read_registry_key_value(host, key, value_name)
2476
+ expect(client).to have_received(:connect_to_winreg).with(host)
2477
+ end
2478
+
2479
+ it 'calls Pipe #read_registry_key_value' do
2480
+ client.read_registry_key_value(host, key, value_name)
2481
+ expect(named_pipe).to have_received(:read_registry_key_value).with(key, value_name)
2482
+ end
2483
+ end
2484
+
2485
+ describe '#enum_registry_key' do
2486
+ let(:host) { '1.2.3.4' }
2487
+ let(:key) { 'HKLM\\Registry\\Key' }
2488
+ let(:named_pipe) { double('Named pipe') }
2489
+ let(:result) { double('Result') }
2490
+ before :example do
2491
+ allow(client).to receive(:connect_to_winreg).and_yield(named_pipe)
2492
+ allow(named_pipe).to receive(:enum_registry_key).and_return(result)
2493
+ end
2494
+
2495
+ it 'calls #connect_to_winreg to wrap the main logic around' do
2496
+ client.enum_registry_key(host, key)
2497
+ expect(client).to have_received(:connect_to_winreg).with(host)
2498
+ end
2499
+
2500
+ it 'calls Pipe #enum_registry_key' do
2501
+ client.enum_registry_key(host, key)
2502
+ expect(named_pipe).to have_received(:enum_registry_key).with(key)
2503
+ end
2504
+ end
2505
+
2506
+ describe '#enum_registry_values' do
2507
+ let(:host) { '1.2.3.4' }
2508
+ let(:key) { 'HKLM\\Registry\\Key' }
2509
+ let(:named_pipe) { double('Named pipe') }
2510
+ let(:result) { double('Result') }
2511
+ before :example do
2512
+ allow(client).to receive(:connect_to_winreg).and_yield(named_pipe)
2513
+ allow(named_pipe).to receive(:enum_registry_values).and_return(result)
2514
+ end
2515
+
2516
+ it 'calls #connect_to_winreg to wrap the main logic around' do
2517
+ client.enum_registry_values(host, key)
2518
+ expect(client).to have_received(:connect_to_winreg).with(host)
2519
+ end
2520
+
2521
+ it 'calls Pipe #enum_registry_values' do
2522
+ client.enum_registry_values(host, key)
2523
+ expect(named_pipe).to have_received(:enum_registry_values).with(key)
2524
+ end
1213
2525
  end
1214
2526
  end
1215
2527
 
2528
+ describe '#update_preauth_hash' do
2529
+ it 'raises an EncryptionError exception if the preauth integrity hash algorithm is not known' do
2530
+ expect { client.update_preauth_hash('Test') }.to raise_error(
2531
+ RubySMB::Error::EncryptionError,
2532
+ 'Cannot compute the Preauth Integrity Hash value: Preauth Integrity Hash Algorithm is nil'
2533
+ )
2534
+ end
2535
+
2536
+ it 'computes the hash value' do
2537
+ packet = RubySMB::SMB2::Packet::EchoRequest.new
2538
+ data = 'Previous hash'
2539
+ algo = RubySMB::SMB2::PreauthIntegrityCapabilities::HASH_ALGORITM_MAP[
2540
+ RubySMB::SMB2::PreauthIntegrityCapabilities::SHA_512
2541
+ ]
2542
+ client.preauth_integrity_hash_algorithm = algo
2543
+ client.preauth_integrity_hash_value = data
2544
+ hash = OpenSSL::Digest.digest(algo, data + packet.to_binary_s)
2545
+ client.update_preauth_hash(packet)
2546
+ expect(client.preauth_integrity_hash_value).to eq(hash)
2547
+ end
2548
+ end
2549
+
2550
+ context 'Encryption' do
2551
+ describe '#smb3_encrypt' do
2552
+ let(:transform_packet) { double('TransformHeader packet') }
2553
+ let(:session_key) { "\x5c\x00\x4a\x3b\xf0\xa2\x4f\x75\x4c\xb2\x74\x0a\xcf\xc4\x8e\x1a".b }
2554
+ let(:data) { RubySMB::SMB2::Packet::TreeConnectRequest.new.to_binary_s }
2555
+
2556
+ before :example do
2557
+ allow(RubySMB::SMB2::Packet::TransformHeader).to receive(:new).and_return(transform_packet)
2558
+ allow(transform_packet).to receive(:encrypt)
2559
+ client.session_key = session_key
2560
+ end
2561
+
2562
+ it 'does not generate a new client encryption key if it already exists' do
2563
+ client.client_encryption_key = 'key'
2564
+ expect(RubySMB::Crypto::KDF).to_not receive(:counter_mode)
2565
+ expect(client.client_encryption_key).to eq('key')
2566
+ client.smb3_encrypt(data)
2567
+ end
2568
+
2569
+ ['0x0300', '0x0302'].each do |dialect|
2570
+ context "with #{dialect} dialect" do
2571
+ before :example do
2572
+ client.dialect = dialect
2573
+ end
2574
+
2575
+ it 'generates the client encryption key with the expected parameters' do
2576
+ expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(
2577
+ session_key,
2578
+ "SMB2AESCCM\x00",
2579
+ "ServerIn \x00"
2580
+ ).and_call_original
2581
+ client.smb3_encrypt(data)
2582
+ end
2583
+ end
2584
+ end
2585
+
2586
+ context 'with 0x0311 dialect' do
2587
+ it 'generates the client encryption key with the expected parameters' do
2588
+ client.preauth_integrity_hash_value = ''
2589
+ client.dialect = '0x0311'
2590
+ expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(
2591
+ session_key,
2592
+ "SMBC2SCipherKey\x00",
2593
+ ''
2594
+ ).and_call_original
2595
+ client.smb3_encrypt(data)
2596
+ end
2597
+ end
2598
+
2599
+ it 'raises the expected exception if the dialect is incompatible' do
2600
+ client.dialect = '0x0202'
2601
+ expect { client.smb3_encrypt(data) }.to raise_error(RubySMB::Error::EncryptionError)
2602
+ end
2603
+
2604
+ it 'creates a TransformHeader packet and encrypt the data' do
2605
+ client.dialect = '0x0300'
2606
+ client.encryption_algorithm = 'AES-128-CCM'
2607
+ client.session_id = 123
2608
+ client.smb3_encrypt(data)
2609
+ expect(RubySMB::SMB2::Packet::TransformHeader).to have_received(:new).with(flags: 1, session_id: 123)
2610
+ expect(transform_packet).to have_received(:encrypt).with(data, client.client_encryption_key, algorithm: 'AES-128-CCM')
2611
+ end
2612
+
2613
+ it 'generates the expected client encryption key with 0x0302 dialect' do
2614
+ client.dialect = '0x0302'
2615
+ expected_enc_key =
2616
+ "\xa4\xfa\x23\xc1\xb0\x65\x84\xce\x47\x08\x5b\xe0\x64\x98\xd7\x87".b
2617
+ client.smb3_encrypt(data)
2618
+ expect(client.client_encryption_key).to eq expected_enc_key
2619
+ end
2620
+
2621
+ it 'generates the expected client encryption key with 0x0311 dialect' do
2622
+ client.dialect = '0x0311'
2623
+ client.session_key =
2624
+ "\x5c\x00\x4a\x3b\xf0\xa2\x4f\x75\x4c\xb2\x74\x0a\xcf\xc4\x8e\x1a".b
2625
+ client.preauth_integrity_hash_value =
2626
+ "\x57\x77\x7d\x47\xc2\xa9\xc8\x23\x6e\x8a\xfa\x39\xe8\x77\x2f\xb0\xb6"\
2627
+ "\x01\xba\x85\x58\x77\xf5\x01\xa0\xf0\x31\x69\x6a\x64\x49\x1c\x61\xdb"\
2628
+ "\x57\x34\x19\x1b\x80\x33\x9a\xfa\x1d\x6c\x3f\xca\x44\x68\x78\x5b\xb9"\
2629
+ "\xda\x41\xfa\x83\xe5\xa9\x6f\xcf\x44\xbc\xe5\x26\x6e".b
2630
+ expected_enc_key =
2631
+ "\xc7\x4e\xfe\x4d\x15\x48\x5b\x0b\x71\x45\x49\x26\x8a\xd9\x6c\xaa".b
2632
+ client.smb3_encrypt(data)
2633
+ expect(client.client_encryption_key).to eq expected_enc_key
2634
+ end
2635
+ end
2636
+
2637
+ describe '#smb3_decrypt' do
2638
+ let(:transform_packet) { double('TransformHeader packet') }
2639
+ let(:session_key) { "\x5c\x00\x4a\x3b\xf0\xa2\x4f\x75\x4c\xb2\x74\x0a\xcf\xc4\x8e\x1a".b }
2640
+
2641
+ before :example do
2642
+ allow(transform_packet).to receive(:decrypt)
2643
+ client.session_key = session_key
2644
+ end
2645
+
2646
+ it 'does not generate a new server encryption key if it already exists' do
2647
+ client.server_encryption_key = 'key'
2648
+ expect(RubySMB::Crypto::KDF).to_not receive(:counter_mode)
2649
+ expect(client.server_encryption_key).to eq('key')
2650
+ client.smb3_decrypt(transform_packet)
2651
+ end
2652
+
2653
+ ['0x0300', '0x0302'].each do |dialect|
2654
+ context "with #{dialect} dialect" do
2655
+ before :example do
2656
+ client.dialect = dialect
2657
+ end
2658
+
2659
+ it 'generates the client encryption key with the expected parameters' do
2660
+ expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(
2661
+ session_key,
2662
+ "SMB2AESCCM\x00",
2663
+ "ServerOut\x00"
2664
+ ).and_call_original
2665
+ client.smb3_decrypt(transform_packet)
2666
+ end
2667
+ end
2668
+ end
2669
+
2670
+ context 'with 0x0311 dialect' do
2671
+ it 'generates the client encryption key with the expected parameters' do
2672
+ client.preauth_integrity_hash_value = ''
2673
+ client.dialect = '0x0311'
2674
+ expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(
2675
+ session_key,
2676
+ "SMBS2CCipherKey\x00",
2677
+ ''
2678
+ ).and_call_original
2679
+ client.smb3_decrypt(transform_packet)
2680
+ end
2681
+ end
2682
+
2683
+ it 'raises the expected exception if the dialect is incompatible' do
2684
+ client.dialect = '0x0202'
2685
+ expect { client.smb3_decrypt(transform_packet) }.to raise_error(RubySMB::Error::EncryptionError)
2686
+ end
2687
+
2688
+ it 'creates a TransformHeader packet and encrypt the data' do
2689
+ client.dialect = '0x0300'
2690
+ client.encryption_algorithm = 'AES-128-CCM'
2691
+ client.session_id = 123
2692
+ client.smb3_decrypt(transform_packet)
2693
+ expect(transform_packet).to have_received(:decrypt).with(client.server_encryption_key, algorithm: 'AES-128-CCM')
2694
+ end
2695
+
2696
+ it 'generates the expected server encryption key with 0x0302 dialect' do
2697
+ client.dialect = '0x0302'
2698
+ expected_enc_key =
2699
+ "\x65\x21\xd3\x6d\xe9\xe3\x5a\x66\x09\x61\xae\x3e\xc6\x49\x6b\xdf".b
2700
+ client.smb3_decrypt(transform_packet)
2701
+ expect(client.server_encryption_key).to eq expected_enc_key
2702
+ end
2703
+
2704
+ it 'generates the expected server encryption key with 0x0311 dialect' do
2705
+ client.dialect = '0x0311'
2706
+ client.session_key =
2707
+ "\x5c\x00\x4a\x3b\xf0\xa2\x4f\x75\x4c\xb2\x74\x0a\xcf\xc4\x8e\x1a".b
2708
+ client.preauth_integrity_hash_value =
2709
+ "\x57\x77\x7d\x47\xc2\xa9\xc8\x23\x6e\x8a\xfa\x39\xe8\x77\x2f\xb0\xb6"\
2710
+ "\x01\xba\x85\x58\x77\xf5\x01\xa0\xf0\x31\x69\x6a\x64\x49\x1c\x61\xdb"\
2711
+ "\x57\x34\x19\x1b\x80\x33\x9a\xfa\x1d\x6c\x3f\xca\x44\x68\x78\x5b\xb9"\
2712
+ "\xda\x41\xfa\x83\xe5\xa9\x6f\xcf\x44\xbc\xe5\x26\x6e".b
2713
+ expected_enc_key =
2714
+ "\x8c\x2c\x31\x15\x66\xba\xa9\xab\xcf\xb2\x47\x8d\x72\xd5\xd7\x4a".b
2715
+ client.smb3_decrypt(transform_packet)
2716
+ expect(client.server_encryption_key).to eq expected_enc_key
2717
+ end
2718
+ end
2719
+ end
1216
2720
  end
1217
2721