ruby_smb 1.1.0 → 2.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (163) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.travis.yml +3 -5
  5. data/Gemfile +6 -2
  6. data/examples/anonymous_auth.rb +3 -3
  7. data/examples/append_file.rb +10 -8
  8. data/examples/authenticate.rb +9 -5
  9. data/examples/delete_file.rb +8 -6
  10. data/examples/enum_registry_key.rb +5 -4
  11. data/examples/enum_registry_values.rb +5 -4
  12. data/examples/list_directory.rb +8 -6
  13. data/examples/negotiate.rb +51 -8
  14. data/examples/negotiate_with_netbios_service.rb +9 -5
  15. data/examples/net_share_enum_all.rb +6 -4
  16. data/examples/pipes.rb +11 -12
  17. data/examples/query_service_status.rb +64 -0
  18. data/examples/read_file.rb +8 -6
  19. data/examples/read_file_encryption.rb +56 -0
  20. data/examples/read_registry_key_value.rb +6 -5
  21. data/examples/rename_file.rb +9 -7
  22. data/examples/tree_connect.rb +7 -5
  23. data/examples/write_file.rb +9 -7
  24. data/lib/ruby_smb.rb +4 -0
  25. data/lib/ruby_smb/client.rb +246 -26
  26. data/lib/ruby_smb/client/authentication.rb +32 -18
  27. data/lib/ruby_smb/client/echo.rb +2 -4
  28. data/lib/ruby_smb/client/encryption.rb +62 -0
  29. data/lib/ruby_smb/client/negotiation.rb +156 -16
  30. data/lib/ruby_smb/client/signing.rb +19 -0
  31. data/lib/ruby_smb/client/tree_connect.rb +6 -8
  32. data/lib/ruby_smb/client/utils.rb +24 -17
  33. data/lib/ruby_smb/client/winreg.rb +1 -1
  34. data/lib/ruby_smb/crypto.rb +30 -0
  35. data/lib/ruby_smb/dcerpc.rb +2 -0
  36. data/lib/ruby_smb/dcerpc/error.rb +3 -0
  37. data/lib/ruby_smb/dcerpc/ndr.rb +209 -44
  38. data/lib/ruby_smb/dcerpc/request.rb +13 -0
  39. data/lib/ruby_smb/dcerpc/rpc_security_attributes.rb +34 -0
  40. data/lib/ruby_smb/dcerpc/rrp_unicode_string.rb +9 -6
  41. data/lib/ruby_smb/dcerpc/svcctl.rb +479 -0
  42. data/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_request.rb +48 -0
  43. data/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_response.rb +26 -0
  44. data/lib/ruby_smb/dcerpc/svcctl/close_service_handle_request.rb +25 -0
  45. data/lib/ruby_smb/dcerpc/svcctl/close_service_handle_response.rb +26 -0
  46. data/lib/ruby_smb/dcerpc/svcctl/control_service_request.rb +26 -0
  47. data/lib/ruby_smb/dcerpc/svcctl/control_service_response.rb +26 -0
  48. data/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_request.rb +35 -0
  49. data/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_response.rb +23 -0
  50. data/lib/ruby_smb/dcerpc/svcctl/open_service_w_request.rb +31 -0
  51. data/lib/ruby_smb/dcerpc/svcctl/open_service_w_response.rb +23 -0
  52. data/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_request.rb +25 -0
  53. data/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_response.rb +44 -0
  54. data/lib/ruby_smb/dcerpc/svcctl/query_service_status_request.rb +23 -0
  55. data/lib/ruby_smb/dcerpc/svcctl/query_service_status_response.rb +27 -0
  56. data/lib/ruby_smb/dcerpc/svcctl/service_status.rb +25 -0
  57. data/lib/ruby_smb/dcerpc/svcctl/start_service_w_request.rb +27 -0
  58. data/lib/ruby_smb/dcerpc/svcctl/start_service_w_response.rb +25 -0
  59. data/lib/ruby_smb/dcerpc/winreg.rb +98 -17
  60. data/lib/ruby_smb/dcerpc/winreg/create_key_request.rb +73 -0
  61. data/lib/ruby_smb/dcerpc/winreg/create_key_response.rb +36 -0
  62. data/lib/ruby_smb/dcerpc/winreg/enum_key_request.rb +1 -1
  63. data/lib/ruby_smb/dcerpc/winreg/enum_value_request.rb +1 -1
  64. data/lib/ruby_smb/dcerpc/winreg/enum_value_response.rb +1 -1
  65. data/lib/ruby_smb/dcerpc/winreg/open_root_key_request.rb +4 -4
  66. data/lib/ruby_smb/dcerpc/winreg/query_info_key_request.rb +1 -1
  67. data/lib/ruby_smb/dcerpc/winreg/query_value_request.rb +7 -6
  68. data/lib/ruby_smb/dcerpc/winreg/query_value_response.rb +10 -10
  69. data/lib/ruby_smb/dcerpc/winreg/save_key_request.rb +37 -0
  70. data/lib/ruby_smb/dcerpc/winreg/save_key_response.rb +23 -0
  71. data/lib/ruby_smb/dispatcher/base.rb +1 -1
  72. data/lib/ruby_smb/dispatcher/socket.rb +5 -4
  73. data/lib/ruby_smb/error.rb +49 -6
  74. data/lib/ruby_smb/field/stringz16.rb +17 -1
  75. data/lib/ruby_smb/generic_packet.rb +11 -1
  76. data/lib/ruby_smb/nbss/session_header.rb +4 -4
  77. data/lib/ruby_smb/smb1/commands.rb +1 -1
  78. data/lib/ruby_smb/smb1/file.rb +13 -28
  79. data/lib/ruby_smb/smb1/packet/session_setup_legacy_request.rb +1 -1
  80. data/lib/ruby_smb/smb1/packet/session_setup_legacy_response.rb +2 -2
  81. data/lib/ruby_smb/smb1/packet/session_setup_request.rb +1 -1
  82. data/lib/ruby_smb/smb1/packet/session_setup_response.rb +2 -2
  83. data/lib/ruby_smb/smb1/packet/write_andx_request.rb +1 -1
  84. data/lib/ruby_smb/smb1/pipe.rb +8 -8
  85. data/lib/ruby_smb/smb1/tree.rb +25 -12
  86. data/lib/ruby_smb/smb2/bit_field/session_flags.rb +2 -1
  87. data/lib/ruby_smb/smb2/bit_field/share_flags.rb +6 -4
  88. data/lib/ruby_smb/smb2/file.rb +59 -77
  89. data/lib/ruby_smb/smb2/negotiate_context.rb +108 -0
  90. data/lib/ruby_smb/smb2/packet.rb +2 -0
  91. data/lib/ruby_smb/smb2/packet/compression_transform_header.rb +41 -0
  92. data/lib/ruby_smb/smb2/packet/negotiate_request.rb +51 -14
  93. data/lib/ruby_smb/smb2/packet/negotiate_response.rb +50 -4
  94. data/lib/ruby_smb/smb2/packet/transform_header.rb +84 -0
  95. data/lib/ruby_smb/smb2/packet/tree_connect_request.rb +92 -6
  96. data/lib/ruby_smb/smb2/packet/tree_connect_response.rb +8 -26
  97. data/lib/ruby_smb/smb2/pipe.rb +8 -20
  98. data/lib/ruby_smb/smb2/smb2_header.rb +1 -1
  99. data/lib/ruby_smb/smb2/tree.rb +44 -28
  100. data/lib/ruby_smb/version.rb +1 -1
  101. data/ruby_smb.gemspec +3 -1
  102. data/spec/lib/ruby_smb/client_spec.rb +1408 -70
  103. data/spec/lib/ruby_smb/crypto_spec.rb +25 -0
  104. data/spec/lib/ruby_smb/dcerpc/ndr_spec.rb +1396 -77
  105. data/spec/lib/ruby_smb/dcerpc/rpc_security_attributes_spec.rb +161 -0
  106. data/spec/lib/ruby_smb/dcerpc/rrp_unicode_string_spec.rb +49 -12
  107. data/spec/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_request_spec.rb +191 -0
  108. data/spec/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_response_spec.rb +38 -0
  109. data/spec/lib/ruby_smb/dcerpc/svcctl/close_service_handle_request_spec.rb +30 -0
  110. data/spec/lib/ruby_smb/dcerpc/svcctl/close_service_handle_response_spec.rb +38 -0
  111. data/spec/lib/ruby_smb/dcerpc/svcctl/control_service_request_spec.rb +39 -0
  112. data/spec/lib/ruby_smb/dcerpc/svcctl/control_service_response_spec.rb +38 -0
  113. data/spec/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_request_spec.rb +78 -0
  114. data/spec/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_response_spec.rb +38 -0
  115. data/spec/lib/ruby_smb/dcerpc/svcctl/open_service_w_request_spec.rb +59 -0
  116. data/spec/lib/ruby_smb/dcerpc/svcctl/open_service_w_response_spec.rb +38 -0
  117. data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_request_spec.rb +38 -0
  118. data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_response_spec.rb +152 -0
  119. data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_status_request_spec.rb +30 -0
  120. data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_status_response_spec.rb +38 -0
  121. data/spec/lib/ruby_smb/dcerpc/svcctl/service_status_spec.rb +72 -0
  122. data/spec/lib/ruby_smb/dcerpc/svcctl/start_service_w_request_spec.rb +46 -0
  123. data/spec/lib/ruby_smb/dcerpc/svcctl/start_service_w_response_spec.rb +30 -0
  124. data/spec/lib/ruby_smb/dcerpc/svcctl_spec.rb +512 -0
  125. data/spec/lib/ruby_smb/dcerpc/winreg/create_key_request_spec.rb +110 -0
  126. data/spec/lib/ruby_smb/dcerpc/winreg/create_key_response_spec.rb +44 -0
  127. data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_request_spec.rb +0 -4
  128. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_request_spec.rb +2 -2
  129. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_response_spec.rb +2 -2
  130. data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_request_spec.rb +9 -4
  131. data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_request_spec.rb +0 -4
  132. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_request_spec.rb +17 -17
  133. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_response_spec.rb +11 -23
  134. data/spec/lib/ruby_smb/dcerpc/winreg/save_key_request_spec.rb +57 -0
  135. data/spec/lib/ruby_smb/dcerpc/winreg/save_key_response_spec.rb +22 -0
  136. data/spec/lib/ruby_smb/dcerpc/winreg_spec.rb +227 -41
  137. data/spec/lib/ruby_smb/dispatcher/socket_spec.rb +12 -12
  138. data/spec/lib/ruby_smb/error_spec.rb +88 -0
  139. data/spec/lib/ruby_smb/field/stringz16_spec.rb +12 -0
  140. data/spec/lib/ruby_smb/generic_packet_spec.rb +7 -0
  141. data/spec/lib/ruby_smb/nbss/session_header_spec.rb +4 -11
  142. data/spec/lib/ruby_smb/smb1/file_spec.rb +1 -3
  143. data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_request_spec.rb +2 -2
  144. data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_response_spec.rb +2 -2
  145. data/spec/lib/ruby_smb/smb1/packet/session_setup_request_spec.rb +2 -2
  146. data/spec/lib/ruby_smb/smb1/packet/session_setup_response_spec.rb +1 -1
  147. data/spec/lib/ruby_smb/smb1/pipe_spec.rb +30 -5
  148. data/spec/lib/ruby_smb/smb1/tree_spec.rb +22 -0
  149. data/spec/lib/ruby_smb/smb2/bit_field/session_flags_spec.rb +9 -0
  150. data/spec/lib/ruby_smb/smb2/bit_field/share_flags_spec.rb +27 -0
  151. data/spec/lib/ruby_smb/smb2/file_spec.rb +147 -71
  152. data/spec/lib/ruby_smb/smb2/negotiate_context_spec.rb +332 -0
  153. data/spec/lib/ruby_smb/smb2/packet/compression_transform_header_spec.rb +108 -0
  154. data/spec/lib/ruby_smb/smb2/packet/negotiate_request_spec.rb +138 -3
  155. data/spec/lib/ruby_smb/smb2/packet/negotiate_response_spec.rb +120 -2
  156. data/spec/lib/ruby_smb/smb2/packet/transform_header_spec.rb +220 -0
  157. data/spec/lib/ruby_smb/smb2/packet/tree_connect_request_spec.rb +339 -9
  158. data/spec/lib/ruby_smb/smb2/packet/tree_connect_response_spec.rb +3 -30
  159. data/spec/lib/ruby_smb/smb2/pipe_spec.rb +9 -45
  160. data/spec/lib/ruby_smb/smb2/smb2_header_spec.rb +2 -2
  161. data/spec/lib/ruby_smb/smb2/tree_spec.rb +111 -9
  162. metadata +194 -75
  163. metadata.gz.sig +2 -1
@@ -6,6 +6,14 @@ module RubySMB
6
6
  class TreeConnectResponse < RubySMB::GenericPacket
7
7
  COMMAND = RubySMB::SMB2::Commands::TREE_CONNECT
8
8
 
9
+ # Share Types
10
+ # Physical disk share
11
+ SMB2_SHARE_TYPE_DISK = 0x01
12
+ # Named pipe share
13
+ SMB2_SHARE_TYPE_PIPE = 0x02
14
+ # Printer share
15
+ SMB2_SHARE_TYPE_PRINT = 0x03
16
+
9
17
  endian :little
10
18
  smb2_header :smb2_header
11
19
  uint16 :structure_size, label: 'Structure Size', initial_value: 16
@@ -20,32 +28,6 @@ module RubySMB
20
28
  smb2_header.flags.reply = 1
21
29
  end
22
30
 
23
- # Returns the ACCESS_MASK for the Maximal Share Access Rights. The packet
24
- # defaults this to a {RubySMB::SMB2::BitField::DirectoryAccessMask}. If it is anything other than
25
- # a directory that has been connected to, it will re-cast it as a {RubySMB::SMB2::BitField::FileAccessMask}
26
- #
27
- # @return [RubySMB::SMB2::BitField::DirectoryAccessMask] if a directory was connected to
28
- # @return [RubySMB::SMB2::BitField::FileAccessMask] if anything else was connected to
29
- # @raise [RubySMB::Error::InvalidBitField] if ACCESS_MASK bit field is not valid
30
- def access_rights
31
- if is_directory?
32
- maximal_access
33
- else
34
- mask = maximal_access.to_binary_s
35
- begin
36
- RubySMB::SMB2::BitField::FileAccessMask.read(mask)
37
- rescue IOError
38
- raise RubySMB::Error::InvalidBitField, 'Invalid ACCESS_MASK for the Maximal Share Access Rights'
39
- end
40
- end
41
- end
42
-
43
- # Checks if the remote Tree is a directory
44
- #
45
- # @return [Boolean]
46
- def is_directory?
47
- share_type == 0x01
48
- end
49
31
  end
50
32
  end
51
33
  end
@@ -17,6 +17,8 @@ module RubySMB
17
17
  extend RubySMB::Dcerpc::Srvsvc
18
18
  when 'winreg'
19
19
  extend RubySMB::Dcerpc::Winreg
20
+ when 'svcctl'
21
+ extend RubySMB::Dcerpc::Svcctl
20
22
  end
21
23
  super(tree: tree, response: response, name: name)
22
24
  end
@@ -40,13 +42,12 @@ module RubySMB
40
42
  raise RubySMB::Error::InvalidPacket.new(
41
43
  expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
42
44
  expected_cmd: RubySMB::SMB2::Packet::IoctlResponse::COMMAND,
43
- received_proto: response.smb2_header.protocol,
44
- received_cmd: response.smb2_header.command
45
+ packet: response
45
46
  )
46
47
  end
47
48
 
48
49
  unless response.status_code == WindowsError::NTStatus::STATUS_BUFFER_OVERFLOW or response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
49
- raise RubySMB::Error::UnexpectedStatusCode, response.status_code.name
50
+ raise RubySMB::Error::UnexpectedStatusCode, response.status_code
50
51
  end
51
52
  response
52
53
  end
@@ -89,6 +90,7 @@ module RubySMB
89
90
  request = set_header_fields(RubySMB::SMB2::Packet::IoctlRequest.new(options))
90
91
  request.ctl_code = 0x0011C017
91
92
  request.flags.is_fsctl = 0x00000001
93
+ # TODO: handle fragmentation when the request size > MAX_XMIT_FRAG
92
94
  request.buffer = action.to_binary_s
93
95
 
94
96
  ioctl_raw_response = @tree.client.send_recv(request)
@@ -97,26 +99,12 @@ module RubySMB
97
99
  raise RubySMB::Error::InvalidPacket.new(
98
100
  expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
99
101
  expected_cmd: RubySMB::SMB2::Packet::IoctlRequest::COMMAND,
100
- received_proto: ioctl_response.smb2_header.protocol,
101
- received_cmd: ioctl_response.smb2_header.command
102
+ packet: ioctl_response
102
103
  )
103
104
  end
104
- # TODO: improve the handling of STATUS_PENDING responses
105
- if ioctl_response.status_code == WindowsError::NTStatus::STATUS_PENDING
106
- sleep 1
107
- ioctl_raw_response = @tree.client.dispatcher.recv_packet
108
- ioctl_response = RubySMB::SMB2::Packet::IoctlResponse.read(ioctl_raw_response)
109
- unless ioctl_response.valid?
110
- raise RubySMB::Error::InvalidPacket.new(
111
- expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
112
- expected_cmd: RubySMB::SMB2::Packet::IoctlRequest::COMMAND,
113
- received_proto: ioctl_response.smb2_header.protocol,
114
- received_cmd: ioctl_response.smb2_header.command
115
- )
116
- end
117
- elsif ![WindowsError::NTStatus::STATUS_SUCCESS,
105
+ unless [WindowsError::NTStatus::STATUS_SUCCESS,
118
106
  WindowsError::NTStatus::STATUS_BUFFER_OVERFLOW].include?(ioctl_response.status_code)
119
- raise RubySMB::Error::UnexpectedStatusCode, ioctl_response.status_code.name
107
+ raise RubySMB::Error::UnexpectedStatusCode, ioctl_response.status_code
120
108
  end
121
109
 
122
110
  raw_data = ioctl_response.output_data
@@ -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,12 +23,18 @@ 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
@@ -38,19 +44,26 @@ module RubySMB
38
44
  def disconnect!
39
45
  request = RubySMB::SMB2::Packet::TreeDisconnectRequest.new
40
46
  request = set_header_fields(request)
41
- raw_response = client.send_recv(request)
47
+ raw_response = client.send_recv(request, encrypt: @tree_connect_encrypt_data)
42
48
  response = RubySMB::SMB2::Packet::TreeDisconnectResponse.read(raw_response)
43
49
  unless response.valid?
44
50
  raise RubySMB::Error::InvalidPacket.new(
45
51
  expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
46
52
  expected_cmd: RubySMB::SMB2::Packet::TreeDisconnectResponse::COMMAND,
47
- received_proto: response.smb2_header.protocol,
48
- received_cmd: response.smb2_header.command
53
+ packet: response
49
54
  )
50
55
  end
51
56
  response.status_code
52
57
  end
53
58
 
59
+ def open_pipe(opts)
60
+ # Make sure we don't modify the caller's hash options
61
+ opts = opts.dup
62
+ opts[:filename] = opts[:filename].dup
63
+ opts[:filename] = opts[:filename][1..-1] if opts[:filename].start_with? '\\'
64
+ open_file(opts)
65
+ end
66
+
54
67
  def open_file(filename:, attributes: nil, options: nil, disposition: RubySMB::Dispositions::FILE_OPEN,
55
68
  impersonation: RubySMB::ImpersonationLevels::SEC_IMPERSONATE, read: true, write: false, delete: false)
56
69
 
@@ -96,26 +109,25 @@ module RubySMB
96
109
  create_request.create_disposition = disposition
97
110
  create_request.name = filename
98
111
 
99
- raw_response = client.send_recv(create_request)
112
+ raw_response = client.send_recv(create_request, encrypt: @tree_connect_encrypt_data)
100
113
  response = RubySMB::SMB2::Packet::CreateResponse.read(raw_response)
101
114
  unless response.valid?
102
115
  raise RubySMB::Error::InvalidPacket.new(
103
116
  expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
104
117
  expected_cmd: RubySMB::SMB2::Packet::CreateResponse::COMMAND,
105
- received_proto: response.smb2_header.protocol,
106
- received_cmd: response.smb2_header.command
118
+ packet: response
107
119
  )
108
120
  end
109
121
  unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
110
- raise RubySMB::Error::UnexpectedStatusCode, response.status_code.name
122
+ raise RubySMB::Error::UnexpectedStatusCode, response.status_code
111
123
  end
112
124
 
113
125
  case @share_type
114
- when 0x01
115
- RubySMB::SMB2::File.new(name: filename, tree: self, response: response)
116
- when 0x02
126
+ when RubySMB::SMB2::Packet::TreeConnectResponse::SMB2_SHARE_TYPE_DISK
127
+ RubySMB::SMB2::File.new(name: filename, tree: self, response: response, encrypt: @tree_connect_encrypt_data)
128
+ when RubySMB::SMB2::Packet::TreeConnectResponse::SMB2_SHARE_TYPE_PIPE
117
129
  RubySMB::SMB2::Pipe.new(name: filename, tree: self, response: response)
118
- # when 0x03
130
+ # when RubySMB::SMB2::TreeConnectResponse::SMB2_SHARE_TYPE_PRINT
119
131
  # it's a printer!
120
132
  else
121
133
  raise RubySMB::Error::RubySMBError, 'Unsupported share type'
@@ -141,21 +153,27 @@ module RubySMB
141
153
  directory_request.file_information_class = type::CLASS_LEVEL
142
154
  directory_request.file_id = file_id
143
155
  directory_request.name = pattern
144
- directory_request.output_length = 65_535
156
+
157
+ max_read = client.server_max_read_size
158
+ max_read = 65536 unless client.server_supports_multi_credit
159
+ credit_charge = 0
160
+ if client.server_supports_multi_credit
161
+ credit_charge = (max_read - 1) / 65536 + 1
162
+ end
163
+ directory_request.output_length = max_read
164
+ directory_request.smb2_header.credit_charge = credit_charge
145
165
 
146
166
  directory_request = set_header_fields(directory_request)
147
167
 
148
168
  files = []
149
-
150
169
  loop do
151
- response = client.send_recv(directory_request)
170
+ response = client.send_recv(directory_request, encrypt: @tree_connect_encrypt_data)
152
171
  directory_response = RubySMB::SMB2::Packet::QueryDirectoryResponse.read(response)
153
172
  unless directory_response.valid?
154
173
  raise RubySMB::Error::InvalidPacket.new(
155
174
  expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
156
175
  expected_cmd: RubySMB::SMB2::Packet::QueryDirectoryResponse::COMMAND,
157
- received_proto: directory_response.smb2_header.protocol,
158
- received_cmd: directory_response.smb2_header.command
176
+ packet: directory_response
159
177
  )
160
178
  end
161
179
 
@@ -164,7 +182,7 @@ module RubySMB
164
182
  break if status_code == WindowsError::NTStatus::STATUS_NO_MORE_FILES
165
183
 
166
184
  unless status_code == WindowsError::NTStatus::STATUS_SUCCESS
167
- raise RubySMB::Error::UnexpectedStatusCode, status_code.to_s
185
+ raise RubySMB::Error::UnexpectedStatusCode, status_code
168
186
  end
169
187
 
170
188
  files += directory_response.results(type)
@@ -193,18 +211,17 @@ module RubySMB
193
211
 
194
212
  create_request = open_directory_packet(directory: directory, disposition: disposition,
195
213
  impersonation: impersonation, read: read, write: write, delete: delete)
196
- raw_response = client.send_recv(create_request)
214
+ raw_response = client.send_recv(create_request, encrypt: @tree_connect_encrypt_data)
197
215
  response = RubySMB::SMB2::Packet::CreateResponse.read(raw_response)
198
216
  unless response.valid?
199
217
  raise RubySMB::Error::InvalidPacket.new(
200
218
  expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
201
219
  expected_cmd: RubySMB::SMB2::Packet::CreateResponse::COMMAND,
202
- received_proto: response.smb2_header.protocol,
203
- received_cmd: response.smb2_header.command
220
+ packet: response
204
221
  )
205
222
  end
206
223
  unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
207
- raise RubySMB::Error::UnexpectedStatusCode, response.status_code.name
224
+ raise RubySMB::Error::UnexpectedStatusCode, response.status_code
208
225
  end
209
226
 
210
227
  response
@@ -250,7 +267,6 @@ module RubySMB
250
267
  # @return [RubySMB::SMB2::Packet] the modified packet.
251
268
  def set_header_fields(request)
252
269
  request.smb2_header.tree_id = id
253
- request.smb2_header.credit_charge = 1
254
270
  request.smb2_header.credits = 256
255
271
  request
256
272
  end
@@ -1,3 +1,3 @@
1
1
  module RubySMB
2
- VERSION = '1.1.0'.freeze
2
+ VERSION = '2.0.4'.freeze
3
3
  end
@@ -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.3.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,11 +6,68 @@ 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 }
12
13
  let(:error_packet) { RubySMB::SMB2::Packet::ErrorPacket.new }
13
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 }
68
+ it { is_expected.to respond_to :pid }
69
+ it { is_expected.to respond_to :server_supports_multi_credit }
70
+
14
71
  describe '#initialize' do
15
72
  it 'should raise an ArgumentError without a valid dispatcher' do
16
73
  expect { described_class.new(nil) }.to raise_error(ArgumentError)
@@ -26,14 +83,21 @@ RSpec.describe RubySMB::Client do
26
83
 
27
84
  it 'accepts an argument to disable smb1 support' do
28
85
  expect(smb2_client.smb1).to be false
86
+ expect(smb3_client.smb1).to be false
29
87
  end
30
88
 
31
89
  it 'accepts an argument to disable smb2 support' do
32
90
  expect(smb1_client.smb2).to be false
91
+ expect(smb3_client.smb2).to be false
92
+ end
93
+
94
+ it 'accepts an argument to disable smb3 support' do
95
+ expect(smb1_client.smb3).to be false
96
+ expect(smb2_client.smb3).to be false
33
97
  end
34
98
 
35
- it 'raises an exception if both SMB1 and SMB2 are disabled' do
36
- expect { described_class.new(dispatcher, smb1: false, smb2: false, username: username, password: password) }.to raise_error(ArgumentError, 'You must enable at least one Protocol')
99
+ it 'raises an exception if SMB1, SMB2 and SMB3 are disabled' do
100
+ 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')
37
101
  end
38
102
 
39
103
  it 'sets the username attribute' do
@@ -44,6 +108,11 @@ RSpec.describe RubySMB::Client do
44
108
  expect(client.password).to eq password
45
109
  end
46
110
 
111
+ it 'sets the session_encrypt_data attribute' do
112
+ client = described_class.new(dispatcher, username: username, password: password, always_encrypt: true)
113
+ expect(client.session_encrypt_data).to eq true
114
+ end
115
+
47
116
  it 'creates an NTLM client' do
48
117
  expect(client.ntlm_client).to be_a Net::NTLM::Client
49
118
  end
@@ -58,7 +127,7 @@ RSpec.describe RubySMB::Client do
58
127
  expect(opt[:workstation]).to eq(local_workstation)
59
128
  expect(opt[:domain]).to eq(domain)
60
129
  flags = Net::NTLM::Client::DEFAULT_FLAGS |
61
- Net::NTLM::FLAGS[:TARGET_INFO] | 0x02000000
130
+ Net::NTLM::FLAGS[:TARGET_INFO] | 0x02000000 ^ Net::NTLM::FLAGS[:OEM]
62
131
  expect(opt[:flags]).to eq(flags)
63
132
  end
64
133
 
@@ -74,30 +143,367 @@ RSpec.describe RubySMB::Client do
74
143
  it 'sets the max_buffer_size to MAX_BUFFER_SIZE' do
75
144
  expect(client.max_buffer_size).to eq RubySMB::Client::MAX_BUFFER_SIZE
76
145
  end
146
+
147
+ it 'sets the server_supports_multi_credit to false' do
148
+ expect(client.server_supports_multi_credit).to be false
149
+ end
150
+
151
+ it 'sets the pid to a random value' do
152
+ 100.times do
153
+ previous_pid = client.pid
154
+ client = described_class.new(dispatcher, username: username, password: password)
155
+ expect(client.pid).to_not eq(previous_pid)
156
+ end
157
+ end
158
+ end
159
+
160
+ describe '#echo' do
161
+ let(:response) { double('Echo Response') }
162
+ before :example do
163
+ allow(response).to receive(:status_code).and_return(WindowsError::NTStatus::STATUS_SUCCESS)
164
+ end
165
+
166
+ context 'with SMB1' do
167
+ it 'calls #smb1_echo with the expected arguments' do
168
+ allow(smb1_client).to receive(:smb1_echo).and_return(response)
169
+ count = 3
170
+ data = 'testing...'
171
+ smb1_client.echo(count: count, data: data)
172
+ expect(smb1_client).to have_received(:smb1_echo).with(count: count, data: data)
173
+ end
174
+ end
175
+
176
+ context 'with SMB2' do
177
+ it 'calls #smb2_echo without arguments' do
178
+ allow(smb2_client).to receive(:smb2_echo).and_return(response)
179
+ smb2_client.echo
180
+ expect(smb2_client).to have_received(:smb2_echo)
181
+ end
182
+ end
183
+
184
+ context 'with SMB3' do
185
+ it 'calls #smb2_echo without arguments' do
186
+ allow(smb3_client).to receive(:smb2_echo).and_return(response)
187
+ smb3_client.echo
188
+ expect(smb3_client).to have_received(:smb2_echo)
189
+ end
190
+ end
191
+
192
+ it 'returns the expected status code' do
193
+ allow(smb2_client).to receive(:smb2_echo).and_return(response)
194
+ expect(smb2_client.echo).to eq(WindowsError::NTStatus::STATUS_SUCCESS)
195
+ end
77
196
  end
78
197
 
79
198
  describe '#send_recv' do
80
199
  let(:smb1_request) { RubySMB::SMB1::Packet::TreeConnectRequest.new }
81
200
  let(:smb2_request) { RubySMB::SMB2::Packet::TreeConnectRequest.new }
201
+ let(:smb2_header) { RubySMB::SMB2::SMB2Header.new }
82
202
 
83
203
  before(:each) do
84
- expect(dispatcher).to receive(:send_packet).and_return(nil)
85
- expect(dispatcher).to receive(:recv_packet).and_return('A')
204
+ allow(client).to receive(:is_status_pending?).and_return(false)
205
+ allow(dispatcher).to receive(:send_packet).and_return(nil)
206
+ allow(dispatcher).to receive(:recv_packet).and_return('A')
207
+ allow(RubySMB::SMB2::SMB2Header).to receive(:read).and_return(smb2_header)
86
208
  end
87
209
 
88
- it 'checks the packet version' do
89
- expect(smb1_request).to receive(:packet_smb_version).and_call_original
90
- client.send_recv(smb1_request)
210
+ context 'when signing' do
211
+ it 'calls #smb1_sign if it is an SMB1 packet' do
212
+ expect(client).to receive(:smb1_sign).with(smb1_request).and_call_original
213
+ client.send_recv(smb1_request)
214
+ end
215
+
216
+ context 'with an SMB2 packet' do
217
+ it 'does not sign a SessionSetupRequest packet' do
218
+ allow(smb2_client).to receive(:is_status_pending?).and_return(false)
219
+ expect(smb2_client).to_not receive(:smb2_sign)
220
+ expect(smb2_client).to_not receive(:smb3_sign)
221
+ smb2_client.send_recv(RubySMB::SMB2::Packet::SessionSetupRequest.new)
222
+ end
223
+
224
+ it 'calls #smb2_sign if it is an SMB2 client' do
225
+ allow(smb2_client).to receive(:is_status_pending?).and_return(false)
226
+ expect(smb2_client).to receive(:smb2_sign).with(smb2_request).and_call_original
227
+ smb2_client.send_recv(smb2_request)
228
+ end
229
+
230
+ it 'calls #smb3_sign if it is an SMB3 client' do
231
+ allow(smb3_client).to receive(:is_status_pending?).and_return(false)
232
+ expect(smb3_client).to receive(:smb3_sign).with(smb2_request).and_call_original
233
+ smb3_client.send_recv(smb2_request)
234
+ end
235
+ end
91
236
  end
92
237
 
93
- it 'calls #smb1_sign if it is an SMB1 packet' do
94
- expect(client).to receive(:smb1_sign).with(smb1_request).and_call_original
238
+ it 'sends the expected packet and gets the response' do
239
+ expect(dispatcher).to receive(:send_packet).with(smb1_request)
240
+ expect(dispatcher).to receive(:recv_packet)
95
241
  client.send_recv(smb1_request)
96
242
  end
97
243
 
98
- it 'calls #smb2_sign if it is an SMB2 packet' do
99
- expect(client).to receive(:smb2_sign).with(smb2_request).and_call_original
100
- client.send_recv(smb2_request)
244
+ context 'with SMB1' do
245
+ it 'does not check if it is a STATUS_PENDING response' do
246
+ expect(smb1_client).to_not receive(:is_status_pending?)
247
+ smb1_client.send_recv(smb1_request)
248
+ end
249
+
250
+ it 'set the #uid SMB header when #user_id is defined' do
251
+ smb1_client.user_id = 333
252
+ smb1_client.send_recv(smb1_request)
253
+ expect(smb1_request.smb_header.uid).to eq(333)
254
+ end
255
+
256
+ it 'does not set the #uid SMB header when #user_id is not defined' do
257
+ smb1_client.send_recv(smb1_request)
258
+ expect(smb1_request.smb_header.uid).to eq(0)
259
+ end
260
+
261
+ it 'set the #pid SMB header when #pid is defined' do
262
+ smb1_client.pid = 333
263
+ smb1_client.send_recv(smb1_request)
264
+ expect(smb1_request.smb_header.pid_low).to eq(333)
265
+ end
266
+
267
+ it 'does not set the #pid SMB header when #pid is not defined' do
268
+ smb1_client.pid = nil
269
+ smb1_client.send_recv(smb1_request)
270
+ expect(smb1_request.smb_header.pid_low).to eq(0)
271
+ end
272
+ end
273
+
274
+ context 'with SMB2' do
275
+ context 'when receiving a STATUS_PENDING response' do
276
+ it 'waits 1 second and reads/decrypts again' do
277
+ allow(smb2_client).to receive(:is_status_pending?).and_return(true, false)
278
+ expect(smb2_client).to receive(:sleep).with(1)
279
+ expect(dispatcher).to receive(:recv_packet).twice
280
+ smb2_client.send_recv(smb2_request)
281
+ end
282
+ end
283
+ end
284
+
285
+ context 'with SMB3 and encryption' do
286
+ before :example do
287
+ smb3_client.dialect = '0x0300'
288
+ allow(smb3_client).to receive(:is_status_pending?).and_return(false)
289
+ end
290
+
291
+ context 'with a SessionSetupRequest' do
292
+ it 'does not encrypt/decrypt' do
293
+ request = RubySMB::SMB2::Packet::SessionSetupRequest.new
294
+ expect(smb3_client).to receive(:send_packet).with(request, encrypt: false)
295
+ expect(smb3_client).to receive(:recv_packet).with(encrypt: false)
296
+ smb3_client.send_recv(request)
297
+ end
298
+ end
299
+
300
+ context 'with a NegotiateRequest' do
301
+ it 'does not encrypt/decrypt' do
302
+ request = RubySMB::SMB2::Packet::NegotiateRequest.new
303
+ expect(smb3_client).to receive(:send_packet).with(request, encrypt: false)
304
+ expect(smb3_client).to receive(:recv_packet).with(encrypt: false)
305
+ smb3_client.send_recv(request)
306
+ end
307
+ end
308
+
309
+ it 'encrypts and decrypts' do
310
+ expect(smb3_client).to receive(:send_packet).with(smb2_request, encrypt: true)
311
+ expect(smb3_client).to receive(:recv_packet).with(encrypt: true)
312
+ smb3_client.send_recv(smb2_request)
313
+ end
314
+
315
+ context 'when receiving a STATUS_PENDING response' do
316
+ it 'waits 1 second and reads/decrypts again' do
317
+ allow(smb3_client).to receive(:is_status_pending?).and_return(true, false)
318
+ expect(smb3_client).to receive(:sleep).with(1)
319
+ expect(smb3_client).to receive(:send_packet).with(smb2_request, encrypt: true)
320
+ expect(smb3_client).to receive(:recv_packet).with(encrypt: true).twice
321
+ smb3_client.send_recv(smb2_request)
322
+ end
323
+ end
324
+ end
325
+
326
+ it 'increments the sequence counter if signing is required and the session key exist' do
327
+ allow(smb2_client).to receive(:is_status_pending?).and_return(false)
328
+ smb2_client.signing_required = true
329
+ smb2_client.session_key = 'key'
330
+ smb2_client.sequence_counter = 0
331
+ smb2_client.send_recv(smb2_request)
332
+ expect(smb2_client.sequence_counter).to eq(1)
333
+ end
334
+
335
+ it 'updates #smb2_message_id with SMB2 header #credit_charge if the server supports multi credits' do
336
+ allow(smb2_client).to receive(:is_status_pending?).and_return(false)
337
+ smb2_client.smb2_message_id = 0
338
+ smb2_client.server_supports_multi_credit = true
339
+ smb2_header.credit_charge = 5
340
+ smb2_client.send_recv(smb2_request)
341
+ expect(smb2_client.smb2_message_id).to eq(5)
342
+ end
343
+
344
+ it 'does not update #msb2_message_id with SMB2 header #credit_charge if the server does not support multi credits' do
345
+ allow(smb2_client).to receive(:is_status_pending?).and_return(false)
346
+ smb2_client.smb2_message_id = 0
347
+ smb2_client.server_supports_multi_credit = false
348
+ smb2_header.credit_charge = 5
349
+ smb2_client.send_recv(smb2_request)
350
+ expect(smb2_client.smb2_message_id).to eq(1)
351
+ end
352
+
353
+ it 'ignores errors thrown when parsing the SMB2 header' do
354
+ allow(RubySMB::SMB2::SMB2Header).to receive(:read).and_raise(IOError)
355
+ expect { smb2_client.send_recv(smb2_request) }.to_not raise_error
356
+ end
357
+ end
358
+
359
+ describe '#is_status_pending?' do
360
+ let(:response) {
361
+ res = RubySMB::SMB2::Packet::SessionSetupRequest.new
362
+ res.smb2_header.nt_status= WindowsError::NTStatus::STATUS_PENDING.value
363
+ res.smb2_header.flags.async_command = 1
364
+ res
365
+ }
366
+
367
+ it 'returns true when the response has a STATUS_PENDING status code and the async_command flag set' do
368
+ expect(client.is_status_pending?(response.smb2_header)).to be true
369
+ end
370
+
371
+ it 'returns false when the response has a STATUS_PENDING status code and the async_command flag not set' do
372
+ response.smb2_header.flags.async_command = 0
373
+ expect(client.is_status_pending?(response.smb2_header)).to be false
374
+ end
375
+
376
+ it 'returns false when the response has no STATUS_PENDING status code but the async_command flag set' do
377
+ response.smb2_header.nt_status= WindowsError::NTStatus::STATUS_SUCCESS.value
378
+ expect(client.is_status_pending?(response.smb2_header)).to be false
379
+ end
380
+ end
381
+
382
+ describe '#can_be_encrypted?' do
383
+ it 'returns true if the packet can be encrypted' do
384
+ packet = RubySMB::SMB2::Packet::TreeConnectRequest.new
385
+ expect(client.can_be_encrypted?(packet)).to be true
386
+ end
387
+
388
+ it 'returns false if it is an SMB1 packet' do
389
+ packet = RubySMB::SMB1::Packet::LogoffRequest.new
390
+ expect(client.can_be_encrypted?(packet)).to be false
391
+ end
392
+
393
+ [RubySMB::SMB2::Packet::SessionSetupRequest, RubySMB::SMB2::Packet::NegotiateRequest].each do |klass|
394
+ it "returns false if the packet is a #{klass}" do
395
+ packet = klass.new
396
+ expect(client.can_be_encrypted?(packet)).to be false
397
+ end
398
+ end
399
+ end
400
+
401
+ describe '#encryption_supported?' do
402
+ ['0x0300', '0x0302', '0x0311'].each do |dialect|
403
+ it "returns true if the dialect is #{dialect}" do
404
+ client.dialect = dialect
405
+ expect(client.encryption_supported?).to be true
406
+ end
407
+ end
408
+
409
+ it "returns false if the dialect does not support encryption" do
410
+ client.dialect = '0x0202'
411
+ expect(client.encryption_supported?).to be false
412
+ end
413
+ end
414
+
415
+ describe '#send_packet' do
416
+ let(:packet) { RubySMB::SMB2::Packet::SessionSetupRequest.new }
417
+ before :example do
418
+ allow(dispatcher).to receive(:send_packet)
419
+ client.dialect = '0x0300'
420
+ end
421
+
422
+ it 'does not encrypt the packet' do
423
+ expect(client).to_not receive(:smb3_encrypt)
424
+ client.send_packet(packet)
425
+ end
426
+
427
+ it 'sends the packet through the dispatcher' do
428
+ client.send_packet(packet)
429
+ expect(dispatcher).to have_received(:send_packet).with(packet)
430
+ end
431
+
432
+ context 'with encryption' do
433
+ it 'creates a Transform request' do
434
+ expect(client).to receive(:smb3_encrypt).with(packet.to_binary_s)
435
+ client.send_packet(packet, encrypt: true)
436
+ end
437
+
438
+ it 'raises an EncryptionError exception if an error occurs while encrypting' do
439
+ allow(client).to receive(:smb3_encrypt).and_raise(RubySMB::Error::RubySMBError.new('Error'))
440
+ expect { client.send_packet(packet, encrypt: true) }.to raise_error(
441
+ RubySMB::Error::EncryptionError,
442
+ "Error while encrypting #{packet.class.name} packet (SMB 0x0300): Error"
443
+ )
444
+ end
445
+
446
+ it 'sends the encrypted packet' do
447
+ encrypted_packet = double('Encrypted packet')
448
+ allow(client).to receive(:smb3_encrypt).and_return(encrypted_packet)
449
+ client.send_packet(packet, encrypt: true)
450
+ expect(dispatcher).to have_received(:send_packet).with(encrypted_packet)
451
+ end
452
+ end
453
+ end
454
+
455
+ describe '#recv_packet' do
456
+ let(:packet) { RubySMB::SMB2::Packet::SessionSetupRequest.new }
457
+ before :example do
458
+ allow(dispatcher).to receive(:recv_packet).and_return(packet.to_binary_s)
459
+ client.dialect = '0x0300'
460
+ allow(client).to receive(:smb3_decrypt)
461
+ end
462
+
463
+ it 'reads the response packet' do
464
+ client.recv_packet
465
+ expect(dispatcher).to have_received(:recv_packet)
466
+ end
467
+
468
+ it 'raises an CommunicationError exception if an error occurs while receiving the response' do
469
+ allow(dispatcher).to receive(:recv_packet).and_raise(RubySMB::Error::CommunicationError)
470
+ expect { client.recv_packet }.to raise_error(RubySMB::Error::CommunicationError)
471
+ end
472
+
473
+ context 'with encryption' do
474
+ it 'raises an EncryptionError exception if an error occurs while receiving the response' do
475
+ allow(dispatcher).to receive(:recv_packet).and_raise(RubySMB::Error::CommunicationError)
476
+ expect { client.recv_packet(encrypt: true) }.to raise_error(
477
+ RubySMB::Error::EncryptionError,
478
+ 'Communication error with the remote host: RubySMB::Error::CommunicationError. '\
479
+ 'The server supports encryption but was not able to handle the encrypted request.'
480
+ )
481
+ end
482
+
483
+ it 'parses the response as a Transform response packet' do
484
+ expect(RubySMB::SMB2::Packet::TransformHeader).to receive(:read).with(packet.to_binary_s)
485
+ client.recv_packet(encrypt: true)
486
+ end
487
+
488
+ it 'raises an InvalidPacket exception if an error occurs while parsing the response' do
489
+ allow(RubySMB::SMB2::Packet::TransformHeader).to receive(:read).and_raise(IOError)
490
+ expect { client.recv_packet(encrypt: true) }.to raise_error(RubySMB::Error::InvalidPacket, 'Not a SMB2 TransformHeader packet')
491
+ end
492
+
493
+ it 'decrypts the Transform response packet' do
494
+ transform = double('Transform header packet')
495
+ allow(RubySMB::SMB2::Packet::TransformHeader).to receive(:read).and_return(transform)
496
+ client.recv_packet(encrypt: true)
497
+ expect(client).to have_received(:smb3_decrypt).with(transform)
498
+ end
499
+
500
+ it 'raises an EncryptionError exception if an error occurs while decrypting' do
501
+ allow(client).to receive(:smb3_decrypt).and_raise(RubySMB::Error::RubySMBError)
502
+ expect { client.recv_packet(encrypt: true) }.to raise_error(
503
+ RubySMB::Error::EncryptionError,
504
+ 'Error while decrypting RubySMB::SMB2::Packet::TransformHeader packet (SMB 0x0300}): RubySMB::Error::RubySMBError'
505
+ )
506
+ end
101
507
  end
102
508
  end
103
509
 
@@ -240,6 +646,44 @@ RSpec.describe RubySMB::Client do
240
646
  expect {smb2_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
241
647
  end
242
648
  end
649
+
650
+ context 'with SMB3' do
651
+ let(:raw_response) { double('Raw response') }
652
+ let(:logoff_response) {
653
+ RubySMB::SMB2::Packet::LogoffResponse.new(smb_header: {:command => RubySMB::SMB2::Commands::LOGOFF} )
654
+ }
655
+ before :example do
656
+ allow(smb3_client).to receive(:send_recv).and_return(raw_response)
657
+ allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(logoff_response)
658
+ allow(smb3_client).to receive(:wipe_state!)
659
+ end
660
+
661
+ it 'creates a LogoffRequest packet' do
662
+ expect(RubySMB::SMB2::Packet::LogoffRequest).to receive(:new).and_call_original
663
+ smb3_client.logoff!
664
+ end
665
+
666
+ it 'calls #send_recv' do
667
+ expect(smb3_client).to receive(:send_recv)
668
+ smb3_client.logoff!
669
+ end
670
+
671
+ it 'reads the raw response as a LogoffResponse packet' do
672
+ expect(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).with(raw_response)
673
+ smb3_client.logoff!
674
+ end
675
+
676
+ it 'raise an InvalidPacket exception when the response is an error packet' do
677
+ allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(RubySMB::SMB2::Packet::ErrorPacket.new)
678
+ expect {smb3_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
679
+ end
680
+
681
+ it 'raise an InvalidPacket exception when the response is not a LOGOFF command' do
682
+ logoff_response.smb2_header.command = RubySMB::SMB2::Commands::ECHO
683
+ allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(logoff_response)
684
+ expect {smb3_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
685
+ end
686
+ end
243
687
  end
244
688
 
245
689
  context 'NetBIOS Session Service' do
@@ -310,7 +754,7 @@ RSpec.describe RubySMB::Client do
310
754
  expect(session_packet.session_header.session_packet_type).to eq RubySMB::Nbss::SESSION_REQUEST
311
755
  expect(session_packet.called_name).to eq called_name
312
756
  expect(session_packet.calling_name).to eq calling_name
313
- expect(session_packet.session_header.packet_length).to eq(
757
+ expect(session_packet.session_header.stream_protocol_length).to eq(
314
758
  session_packet.called_name.to_binary_s.size + session_packet.calling_name.to_binary_s.size
315
759
  )
316
760
  end
@@ -365,7 +809,8 @@ RSpec.describe RubySMB::Client do
365
809
  smb1_extended_response.to_binary_s
366
810
  }
367
811
 
368
- let(:smb2_response) { RubySMB::SMB2::Packet::NegotiateResponse.new }
812
+ let(:smb2_response) { RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x200) }
813
+ let(:smb3_response) { RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x300) }
369
814
 
370
815
  describe '#smb1_negotiate_request' do
371
816
  it 'returns an SMB1 Negotiate Request packet' do
@@ -373,33 +818,158 @@ RSpec.describe RubySMB::Client do
373
818
  end
374
819
 
375
820
  it 'sets the default SMB1 Dialect' do
376
- expect(client.smb1_negotiate_request.dialects).to include(buffer_format: 2, dialect_string: RubySMB::Client::SMB1_DIALECT_SMB1_DEFAULT)
821
+ expect(client.smb1_negotiate_request.dialects).to include(
822
+ buffer_format: 2,
823
+ dialect_string: RubySMB::Client::SMB1_DIALECT_SMB1_DEFAULT
824
+ )
377
825
  end
378
826
 
379
827
  it 'sets the SMB2.02 dialect if SMB2 support is enabled' do
380
- expect(client.smb1_negotiate_request.dialects).to include(buffer_format: 2, dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_DEFAULT)
828
+ expect(client.smb1_negotiate_request.dialects).to include(
829
+ buffer_format: 2,
830
+ dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_DEFAULT
831
+ )
381
832
  end
382
833
 
383
834
  it 'excludes the SMB2.02 Dialect if SMB2 support is disabled' do
384
- expect(smb1_client.smb1_negotiate_request.dialects).to_not include(buffer_format: 2, dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_DEFAULT)
835
+ expect(smb1_client.smb1_negotiate_request.dialects).to_not include(
836
+ buffer_format: 2,
837
+ dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_DEFAULT
838
+ )
385
839
  end
386
840
 
387
841
  it 'excludes the default SMB1 Dialect if SMB1 support is disabled' do
388
- expect(smb2_client.smb1_negotiate_request.dialects).to_not include(buffer_format: 2, dialect_string: RubySMB::Client::SMB1_DIALECT_SMB1_DEFAULT)
842
+ expect(smb2_client.smb1_negotiate_request.dialects).to_not include(
843
+ buffer_format: 2,
844
+ dialect_string: RubySMB::Client::SMB1_DIALECT_SMB1_DEFAULT
845
+ )
846
+ end
847
+
848
+ it 'sets the SMB wildcard dialect if SMB2 support is enabled' do
849
+ expect(client.smb1_negotiate_request.dialects).to include(
850
+ buffer_format: 2,
851
+ dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_WILDCARD
852
+ )
853
+ end
854
+
855
+ it 'sets the SMB wildcard dialect if SMB3 support is enabled' do
856
+ expect(smb3_client.smb1_negotiate_request.dialects).to include(
857
+ buffer_format: 2,
858
+ dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_WILDCARD
859
+ )
860
+ end
861
+
862
+ it 'excludes the SMB wildcard dialect if both SMB2 and SMB3 supports are disabled' do
863
+ expect(smb1_client.smb1_negotiate_request.dialects).to_not include(
864
+ buffer_format: 2,
865
+ dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_WILDCARD
866
+ )
389
867
  end
390
868
  end
391
869
 
392
- describe '#smb2_negotiate_request' do
870
+ describe '#smb2_3_negotiate_request' do
393
871
  it 'return an SMB2 Negotiate Request packet' do
394
- expect(client.smb2_negotiate_request).to be_a(RubySMB::SMB2::Packet::NegotiateRequest)
872
+ expect(client.smb2_3_negotiate_request).to be_a(RubySMB::SMB2::Packet::NegotiateRequest)
395
873
  end
396
874
 
397
- it 'sets the default SMB2 Dialect' do
398
- expect(client.smb2_negotiate_request.dialects).to include(RubySMB::Client::SMB2_DIALECT_DEFAULT)
875
+ it 'sets the default SMB2 Dialect if SMB2 support is enabled' do
876
+ expect(client.smb2_3_negotiate_request.dialects).to include(
877
+ *(RubySMB::Client::SMB2_DIALECT_DEFAULT.map {|d| d.to_i(16)})
878
+ )
879
+ end
880
+
881
+ it 'does not set the default SMB2 Dialect if SMB2 support is disabled' do
882
+ expect(smb3_client.smb2_3_negotiate_request.dialects).to_not include(
883
+ *(RubySMB::Client::SMB2_DIALECT_DEFAULT.map {|d| d.to_i(16)})
884
+ )
399
885
  end
400
886
 
401
887
  it 'sets the Message ID to 0' do
402
- expect(client.smb2_negotiate_request.smb2_header.message_id).to eq 0
888
+ expect(client.smb2_3_negotiate_request.smb2_header.message_id).to eq 0
889
+ end
890
+
891
+ it 'adds SMB3 dialects if if SMB3 support is enabled' do
892
+ expect(client.smb2_3_negotiate_request.dialects).to include(
893
+ *(RubySMB::Client::SMB3_DIALECT_DEFAULT.map {|d| d.to_i(16)})
894
+ )
895
+ end
896
+
897
+ it 'does not set the default SMB3 Dialect if SMB3 support is disabled' do
898
+ expect(smb2_client.smb2_3_negotiate_request.dialects).to_not include(
899
+ *(RubySMB::Client::SMB3_DIALECT_DEFAULT.map {|d| d.to_i(16)})
900
+ )
901
+ end
902
+ end
903
+
904
+ describe '#add_smb3_to_negotiate_request' do
905
+ let(:negotiate_request) { RubySMB::SMB2::Packet::NegotiateRequest.new }
906
+
907
+ it 'adds the default SMB3 dialects' do
908
+ expect(client.add_smb3_to_negotiate_request(negotiate_request).dialects).to include(
909
+ *(RubySMB::Client::SMB3_DIALECT_DEFAULT.map {|d| d.to_i(16)})
910
+ )
911
+ end
912
+
913
+ it 'raises the expected exception when the dialects is not an array of strings' do
914
+ dialects = ['0x0300', 0x0302, '0x0311']
915
+ expect { client.add_smb3_to_negotiate_request(negotiate_request, dialects) }.to raise_error(ArgumentError)
916
+ end
917
+
918
+ it 'sets encryption capability flag' do
919
+ expect(client.add_smb3_to_negotiate_request(negotiate_request).capabilities.encryption).to eq(1)
920
+ end
921
+
922
+ context 'when the negotiate packet includes the 0x0311 dialect' do
923
+ before :example do
924
+ client.add_smb3_to_negotiate_request(negotiate_request, ['0x0311'])
925
+ end
926
+
927
+ it 'adds 3 Negotiate Contexts' do
928
+ expect(negotiate_request.negotiate_context_info.negotiate_context_count).to eq(3)
929
+ end
930
+
931
+ it 'adds a Preauth Integrity Negotiate Context with the expected hash algorithms' do
932
+ nc = negotiate_request.negotiate_context_list.select do |n|
933
+ n.context_type == RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
934
+ end
935
+ expect(nc.length).to eq(1)
936
+ expect(nc.first.data.hash_algorithms).to eq([RubySMB::SMB2::PreauthIntegrityCapabilities::SHA_512])
937
+ end
938
+
939
+ it 'adds Encryption Negotiate Contexts with the expected encryption algorithms' do
940
+ nc = negotiate_request.negotiate_context_list.select do |n|
941
+ n.context_type == RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
942
+ end
943
+ expect(nc.length).to eq(1)
944
+ expect(nc.first.data.ciphers).to eq(
945
+ [
946
+ RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM,
947
+ RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM
948
+ ]
949
+ )
950
+ end
951
+
952
+ it 'adds Compression Negotiate Contexts with the expected compression algorithms' do
953
+ nc = negotiate_request.negotiate_context_list.select do |n|
954
+ n.context_type == RubySMB::SMB2::NegotiateContext::SMB2_COMPRESSION_CAPABILITIES
955
+ end
956
+ expect(nc.length).to eq(1)
957
+ expect(nc.first.data.compression_algorithms).to eq(
958
+ [
959
+ RubySMB::SMB2::CompressionCapabilities::LZNT1,
960
+ RubySMB::SMB2::CompressionCapabilities::LZ77,
961
+ RubySMB::SMB2::CompressionCapabilities::LZ77_Huffman,
962
+ RubySMB::SMB2::CompressionCapabilities::Pattern_V1
963
+ ]
964
+ )
965
+ end
966
+ end
967
+
968
+ context 'when the negotiate packet does not include the 0x0311 dialect' do
969
+ it 'does not add any Negotiate Context' do
970
+ client.add_smb3_to_negotiate_request(negotiate_request, ['0x0300', '0x0302'])
971
+ expect(negotiate_request.negotiate_context_list?). to be false
972
+ end
403
973
  end
404
974
  end
405
975
 
@@ -414,10 +984,15 @@ RSpec.describe RubySMB::Client do
414
984
  client.negotiate_request
415
985
  end
416
986
 
417
- it 'calls #smb2_negotiate_request if SMB2 is enabled' do
418
- expect(smb2_client).to receive(:smb2_negotiate_request)
987
+ it 'calls #smb2_3_negotiate_request if SMB2 is enabled' do
988
+ expect(smb2_client).to receive(:smb2_3_negotiate_request)
419
989
  smb2_client.negotiate_request
420
990
  end
991
+
992
+ it 'calls #smb2_3_negotiate_request if SMB3 is enabled' do
993
+ expect(smb3_client).to receive(:smb2_3_negotiate_request)
994
+ smb3_client.negotiate_request
995
+ end
421
996
  end
422
997
 
423
998
  describe '#negotiate_response' do
@@ -464,12 +1039,28 @@ RSpec.describe RubySMB::Client do
464
1039
  end
465
1040
  end
466
1041
 
467
- context 'with SMB1 and SMB2 enabled' do
1042
+ context 'with only SMB3' do
1043
+ it 'returns a properly formed packet' do
1044
+ expect(smb3_client.negotiate_response(smb2_response.to_binary_s)).to eq smb2_response
1045
+ end
1046
+
1047
+ it 'raises an exception if the Response is invalid' do
1048
+ expect { smb3_client.negotiate_response(random_junk) }.to raise_error(RubySMB::Error::InvalidPacket)
1049
+ end
1050
+
1051
+ it 'considers the response invalid if it is not an actual Negotiate Response' do
1052
+ bogus_response = smb2_response
1053
+ bogus_response.smb2_header.command = RubySMB::SMB2::Commands::ECHO
1054
+ expect { smb3_client.negotiate_response(bogus_response.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
1055
+ end
1056
+ end
1057
+
1058
+ context 'with SMB1, SMB2 and SMB3 enabled' do
468
1059
  it 'returns an SMB1 NegotiateResponse if it looks like SMB1' do
469
1060
  expect(client.negotiate_response(smb1_extended_response_raw)).to eq smb1_extended_response
470
1061
  end
471
1062
 
472
- it 'returns an SMB2 NegotiateResponse if it looks like SMB2' do
1063
+ it 'returns an SMB2 NegotiateResponse if it looks like SMB2 or SMB3' do
473
1064
  expect(client.negotiate_response(smb2_response.to_binary_s)).to eq smb2_response
474
1065
  end
475
1066
  end
@@ -477,9 +1068,10 @@ RSpec.describe RubySMB::Client do
477
1068
 
478
1069
  describe '#parse_negotiate_response' do
479
1070
  context 'when SMB1 was Negotiated' do
480
- it 'turns off SMB2 support' do
1071
+ it 'turns off SMB2 and SMB3 support' do
481
1072
  client.parse_negotiate_response(smb1_extended_response)
482
1073
  expect(client.smb2).to be false
1074
+ expect(client.smb3).to be false
483
1075
  end
484
1076
 
485
1077
  it 'sets whether or not signing is required' do
@@ -502,12 +1094,18 @@ RSpec.describe RubySMB::Client do
502
1094
  it 'returns the string \'SMB1\'' do
503
1095
  expect(client.parse_negotiate_response(smb1_extended_response)).to eq ('SMB1')
504
1096
  end
1097
+
1098
+ it 'sets #negotiated_smb_version to 1' do
1099
+ client.parse_negotiate_response(smb1_extended_response)
1100
+ expect(client.negotiated_smb_version).to eq(1)
1101
+ end
505
1102
  end
506
1103
 
507
1104
  context 'when SMB2 was negotiated' do
508
- it 'turns off SMB1 support' do
1105
+ it 'turns off SMB1 and SMB3 support' do
509
1106
  client.parse_negotiate_response(smb2_response)
510
1107
  expect(client.smb1).to be false
1108
+ expect(client.smb3).to be false
511
1109
  end
512
1110
 
513
1111
  it 'sets whether or not signing is required' do
@@ -525,10 +1123,142 @@ RSpec.describe RubySMB::Client do
525
1123
  it 'returns the string \'SMB2\'' do
526
1124
  expect(client.parse_negotiate_response(smb2_response)).to eq ('SMB2')
527
1125
  end
1126
+
1127
+ it 'sets #server_supports_multi_credit to true when the response has #large_mtu capability set' do
1128
+ smb2_response.capabilities.large_mtu = 1
1129
+ client.parse_negotiate_response(smb2_response)
1130
+ expect(client.server_supports_multi_credit).to be true
1131
+ end
1132
+
1133
+ it 'sets #server_supports_multi_credit to false when the dialect is 0x0202' do
1134
+ smb2_response.dialect_revision = 0x0202
1135
+ smb2_response.capabilities.large_mtu = 1 # just to make sure it won't affect the result
1136
+ client.parse_negotiate_response(smb2_response)
1137
+ expect(client.server_supports_multi_credit).to be false
1138
+ end
1139
+ end
1140
+
1141
+ context 'when SMB3 was negotiated' do
1142
+ it 'turns off SMB1 and SMB2 support' do
1143
+ client.parse_negotiate_response(smb3_response)
1144
+ expect(client.smb1).to be false
1145
+ expect(client.smb2).to be false
1146
+ end
1147
+
1148
+ it 'sets whether or not signing is required' do
1149
+ smb3_response.security_mode.signing_required = 1
1150
+ client.parse_negotiate_response(smb3_response)
1151
+ expect(client.signing_required).to be true
1152
+ end
1153
+
1154
+ it 'sets #dialect to the negotiated dialect' do
1155
+ client.parse_negotiate_response(smb3_response)
1156
+ expect(client.dialect).to eq '0x0300'
1157
+ end
1158
+
1159
+ it 'returns the string \'SMB2\'' do
1160
+ expect(client.parse_negotiate_response(smb3_response)).to eq ('SMB3')
1161
+ end
1162
+
1163
+ it 'sets #server_supports_multi_credit to true when the response has #large_mtu capability set' do
1164
+ smb3_response.capabilities.large_mtu = 1
1165
+ client.parse_negotiate_response(smb3_response)
1166
+ expect(client.server_supports_multi_credit).to be true
1167
+ end
1168
+
1169
+ context 'when the server supports encryption' do
1170
+ before :example do
1171
+ smb3_response.capabilities.encryption = 1
1172
+ end
1173
+
1174
+ it 'sets the expected encryption algorithm' do
1175
+ client.parse_negotiate_response(smb3_response)
1176
+ expect(client.encryption_algorithm).to eq(RubySMB::SMB2::EncryptionCapabilities::ENCRYPTION_ALGORITHM_MAP[RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM])
1177
+ end
1178
+
1179
+ it 'keeps session encryption enabled if it was already' do
1180
+ client.session_encrypt_data = true
1181
+ client.parse_negotiate_response(smb3_response)
1182
+ expect(client.session_encrypt_data).to be true
1183
+ end
1184
+
1185
+ it 'keeps session encryption disabled if it was already' do
1186
+ client.session_encrypt_data = false
1187
+ client.parse_negotiate_response(smb3_response)
1188
+ expect(client.session_encrypt_data).to be false
1189
+ end
1190
+ end
1191
+
1192
+ context 'when the server does not support encryption' do
1193
+ before :example do
1194
+ smb3_response.capabilities.encryption = 0
1195
+ end
1196
+
1197
+ it 'disables session encryption if it was already enabled' do
1198
+ client.session_encrypt_data = true
1199
+ client.parse_negotiate_response(smb3_response)
1200
+ expect(client.session_encrypt_data).to be false
1201
+ end
1202
+
1203
+ it 'keeps session encryption disabled if it was already' do
1204
+ client.session_encrypt_data = false
1205
+ client.parse_negotiate_response(smb3_response)
1206
+ expect(client.session_encrypt_data).to be false
1207
+ end
1208
+ end
1209
+ end
1210
+
1211
+ context 'when the response contains the SMB2 wildcard revision number dialect' do
1212
+ it 'only turns off SMB1 support' do
1213
+ smb2_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x02ff)
1214
+ client.parse_negotiate_response(smb2_response)
1215
+ expect(client.smb1).to be false
1216
+ expect(client.smb2).to be true
1217
+ expect(client.smb3).to be true
1218
+ end
1219
+ end
1220
+
1221
+ context 'when the negotiation failed' do
1222
+ context 'with a STATUS_NOT_SUPPORTED status code' do
1223
+ before :example do
1224
+ error_packet.smb2_header.nt_status = WindowsError::NTStatus::STATUS_NOT_SUPPORTED.value
1225
+ end
1226
+
1227
+ it 'raises the expected exception with SMB2' do
1228
+ expect { smb2_client.parse_negotiate_response(error_packet) }.to raise_error(
1229
+ RubySMB::Error::NegotiationFailure,
1230
+ 'Unable to negotiate with remote host, SMB2 not supported'
1231
+ )
1232
+ end
1233
+
1234
+ it 'raises the expected exception with SMB3' do
1235
+ expect { smb3_client.parse_negotiate_response(error_packet) }.to raise_error(
1236
+ RubySMB::Error::NegotiationFailure,
1237
+ 'Unable to negotiate with remote host, SMB3 not supported'
1238
+ )
1239
+ end
1240
+ end
1241
+
1242
+ context 'with an unknown status code' do
1243
+ it 'raises the expected exception' do
1244
+ expect { client.parse_negotiate_response(empty_packet) }.to raise_error(
1245
+ RubySMB::Error::NegotiationFailure,
1246
+ 'Unable to negotiate with remote host'
1247
+ )
1248
+ end
1249
+ end
528
1250
  end
529
1251
  end
530
1252
 
531
1253
  describe '#negotiate' do
1254
+ let(:request_packet) { client.smb1_negotiate_request }
1255
+ before :example do
1256
+ allow(client).to receive(:negotiate_request)
1257
+ allow(client).to receive(:send_recv)
1258
+ allow(client).to receive(:negotiate_response)
1259
+ allow(client).to receive(:parse_negotiate_response)
1260
+ end
1261
+
532
1262
  it 'calls the backing methods' do
533
1263
  expect(client).to receive(:negotiate_request)
534
1264
  expect(client).to receive(:send_recv)
@@ -537,18 +1267,247 @@ RSpec.describe RubySMB::Client do
537
1267
  client.negotiate
538
1268
  end
539
1269
 
540
- it 'sets the response-packet #dialects array with the dialects sent in the request' do
541
- request_packet = client.smb1_negotiate_request
542
- allow(client).to receive(:negotiate_request).and_return(request_packet)
543
- allow(client).to receive(:send_recv)
544
- allow(client).to receive(:negotiate_response).and_return(smb1_extended_response)
545
- expect(smb1_extended_response).to receive(:dialects=).with(request_packet.dialects)
546
- client.negotiate
1270
+ context 'with SMB1' do
1271
+ it 'sets the response-packet #dialects array with the dialects sent in the request' do
1272
+ request_packet = client.smb1_negotiate_request
1273
+ allow(client).to receive(:negotiate_request).and_return(request_packet)
1274
+ allow(client).to receive(:negotiate_response).and_return(smb1_extended_response)
1275
+ expect(smb1_extended_response).to receive(:dialects=).with(request_packet.dialects)
1276
+ client.negotiate
1277
+ end
547
1278
  end
548
1279
 
549
- it 'raise the expected exception if an error occurs' do
550
- allow(client).to receive(:send_recv).and_raise(RubySMB::Error::InvalidPacket)
551
- expect { client.negotiate }.to raise_error(RubySMB::Error::NegotiationFailure)
1280
+ context "with 0x0311 dialect" do
1281
+ it 'calls #parse_negotiate_response and updates the preauth hash' do
1282
+ client.dialect = '0x0311'
1283
+ request_packet = client.smb2_3_negotiate_request
1284
+ allow(client).to receive(:negotiate_request).and_return(request_packet)
1285
+ allow(client).to receive(:negotiate_response).and_return(smb3_response)
1286
+ expect(client).to receive(:parse_negotiate_response).with(smb3_response)
1287
+ expect(client).to receive(:update_preauth_hash).with(request_packet)
1288
+ expect(client).to receive(:update_preauth_hash).with(smb3_response)
1289
+ client.negotiate
1290
+ end
1291
+ end
1292
+
1293
+ context 'with a wildcard revision number response' do
1294
+ before :example do
1295
+ client.dialect = '0x02ff'
1296
+ allow(client).to receive(:smb2_message_id=) do
1297
+ client.dialect = '0x0202'
1298
+ end
1299
+ end
1300
+
1301
+ it 'increments the message ID' do
1302
+ expect(client).to receive(:smb2_message_id=).with(1)
1303
+ client.negotiate
1304
+ end
1305
+
1306
+ it 're-negotiates' do
1307
+ expect(client).to receive(:negotiate_request).twice
1308
+ expect(client).to receive(:send_recv).twice
1309
+ expect(client).to receive(:negotiate_response).twice
1310
+ expect(client).to receive(:parse_negotiate_response).twice
1311
+ client.negotiate
1312
+ end
1313
+ end
1314
+
1315
+ context 'when an error occurs' do
1316
+ before :example do
1317
+ allow(client).to receive(:negotiate_request).and_return(request_packet)
1318
+ allow(client).to receive(:send_recv).and_raise(RubySMB::Error::InvalidPacket)
1319
+ client.smb1 = false
1320
+ client.smb2 = false
1321
+ client.smb3 = false
1322
+ end
1323
+
1324
+ context 'with SMB1' do
1325
+ let(:request_packet) { client.smb1_negotiate_request }
1326
+
1327
+ it 'raise the expected exception' do
1328
+ client.smb1 = true
1329
+ expect { client.negotiate }.to raise_error(
1330
+ RubySMB::Error::NegotiationFailure,
1331
+ "Unable to negotiate SMB1 with the remote host: RubySMB::Error::InvalidPacket"
1332
+ )
1333
+ end
1334
+ end
1335
+
1336
+ context 'with SMB2' do
1337
+ let(:request_packet) { client.smb2_3_negotiate_request }
1338
+
1339
+ it 'raise the expected exception' do
1340
+ client.smb2 = true
1341
+ expect { client.negotiate }.to raise_error(
1342
+ RubySMB::Error::NegotiationFailure,
1343
+ "Unable to negotiate SMB2 with the remote host: RubySMB::Error::InvalidPacket"
1344
+ )
1345
+ end
1346
+ end
1347
+
1348
+ context 'with SMB3' do
1349
+ let(:request_packet) { client.smb2_3_negotiate_request }
1350
+
1351
+ it 'raise the expected exception' do
1352
+ client.smb3 = true
1353
+ expect { client.negotiate }.to raise_error(
1354
+ RubySMB::Error::NegotiationFailure,
1355
+ "Unable to negotiate SMB3 with the remote host: RubySMB::Error::InvalidPacket"
1356
+ )
1357
+ end
1358
+ end
1359
+ end
1360
+
1361
+ describe '#parse_smb3_capabilities' do
1362
+ let(:request_packet) { client.smb2_3_negotiate_request }
1363
+ let(:smb3_response) { RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311) }
1364
+ let(:nc_encryption) do
1365
+ nc = RubySMB::SMB2::NegotiateContext.new(
1366
+ context_type: RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
1367
+ )
1368
+ nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM
1369
+ nc
1370
+ end
1371
+ let(:nc_integrity) do
1372
+ nc = RubySMB::SMB2::NegotiateContext.new(
1373
+ context_type: RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
1374
+ )
1375
+ nc.data.hash_algorithms << RubySMB::SMB2::PreauthIntegrityCapabilities::SHA_512
1376
+ nc
1377
+ end
1378
+
1379
+ before :example do
1380
+ allow(smb3_client).to receive(:update_preauth_hash)
1381
+ smb3_response.add_negotiate_context(nc_encryption)
1382
+ smb3_response.add_negotiate_context(nc_integrity)
1383
+ end
1384
+
1385
+ context 'when selecting the integrity hash algorithm' do
1386
+ context 'with one algorithm' do
1387
+ it 'selects the expected algorithm' do
1388
+ smb3_client.parse_smb3_capabilities(smb3_response)
1389
+ expect(smb3_client.preauth_integrity_hash_algorithm).to eq('SHA512')
1390
+ end
1391
+ end
1392
+
1393
+ context 'with multiple algorithms' do
1394
+ it 'selects the first algorithm' do
1395
+ nc = smb3_response.find_negotiate_context(
1396
+ RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
1397
+ )
1398
+ nc.data.hash_algorithms << 3
1399
+ smb3_client.parse_smb3_capabilities(smb3_response)
1400
+ expect(smb3_client.preauth_integrity_hash_algorithm).to eq('SHA512')
1401
+ end
1402
+ end
1403
+
1404
+ context 'without integrity negotiate context' do
1405
+ it 'raises the expected exception' do
1406
+ smb3_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311)
1407
+ smb3_response.add_negotiate_context(nc_encryption)
1408
+ expect { smb3_client.parse_smb3_capabilities(smb3_response) }.to raise_error(
1409
+ RubySMB::Error::EncryptionError,
1410
+ 'Unable to retrieve the Preauth Integrity Hash Algorithm from the Negotiate response'
1411
+ )
1412
+ end
1413
+ end
1414
+
1415
+ context 'with an unknown integrity hash algorithm' do
1416
+ it 'raises the expected exception' do
1417
+ smb3_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311)
1418
+ smb3_response.add_negotiate_context(nc_encryption)
1419
+ nc = RubySMB::SMB2::NegotiateContext.new(
1420
+ context_type: RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
1421
+ )
1422
+ nc.data.hash_algorithms << 5
1423
+ smb3_response.add_negotiate_context(nc)
1424
+ expect { smb3_client.parse_smb3_capabilities(smb3_response) }.to raise_error(
1425
+ RubySMB::Error::EncryptionError,
1426
+ 'Unable to retrieve the Preauth Integrity Hash Algorithm from the Negotiate response'
1427
+ )
1428
+ end
1429
+ end
1430
+ end
1431
+
1432
+ context 'when selecting the encryption algorithm' do
1433
+ context 'with one algorithm' do
1434
+ it 'selects the expected algorithm' do
1435
+ smb3_client.parse_smb3_capabilities(smb3_response)
1436
+ expect(smb3_client.encryption_algorithm).to eq('AES-128-CCM')
1437
+ end
1438
+ end
1439
+
1440
+ context 'with multiple algorithms' do
1441
+ it 'selects the AES-128-GCM algorithm if included' do
1442
+ nc = smb3_response.find_negotiate_context(
1443
+ RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
1444
+ )
1445
+ nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM
1446
+ smb3_client.parse_smb3_capabilities(smb3_response)
1447
+ expect(smb3_client.encryption_algorithm).to eq('AES-128-GCM')
1448
+ end
1449
+
1450
+ it 'selects the first algorithm if AES-128-GCM is not included' do
1451
+ nc = smb3_response.find_negotiate_context(
1452
+ RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
1453
+ )
1454
+ nc.data.ciphers << 3
1455
+ smb3_client.parse_smb3_capabilities(smb3_response)
1456
+ expect(smb3_client.encryption_algorithm).to eq('AES-128-CCM')
1457
+ end
1458
+
1459
+ it 'keep tracks of the server supported algorithms' do
1460
+ nc = smb3_response.find_negotiate_context(
1461
+ RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
1462
+ )
1463
+ nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM
1464
+ smb3_client.parse_smb3_capabilities(smb3_response)
1465
+ expect(smb3_client.server_encryption_algorithms).to eq([1, 2])
1466
+ end
1467
+ end
1468
+
1469
+ context 'without encryption context' do
1470
+ it 'raises the expected exception' do
1471
+ smb3_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311)
1472
+ smb3_response.add_negotiate_context(nc_integrity)
1473
+ expect { smb3_client.parse_smb3_capabilities(smb3_response) }.to raise_error(
1474
+ RubySMB::Error::EncryptionError,
1475
+ 'Unable to retrieve the encryption cipher list supported by the server from the Negotiate response'
1476
+ )
1477
+ end
1478
+ end
1479
+
1480
+ context 'with an unknown encryption algorithm' do
1481
+ it 'raises the expected exception' do
1482
+ smb3_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311)
1483
+ smb3_response.add_negotiate_context(nc_integrity)
1484
+ nc = RubySMB::SMB2::NegotiateContext.new(
1485
+ context_type: RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
1486
+ )
1487
+ nc.data.ciphers << 14
1488
+ smb3_response.add_negotiate_context(nc)
1489
+ expect { smb3_client.parse_smb3_capabilities(smb3_response) }.to raise_error(
1490
+ RubySMB::Error::EncryptionError,
1491
+ 'Unable to retrieve the encryption cipher list supported by the server from the Negotiate response'
1492
+ )
1493
+ end
1494
+ end
1495
+ end
1496
+
1497
+ context 'when selecting the compression algorithm' do
1498
+ it 'keep tracks of the server supported algorithms' do
1499
+ nc = RubySMB::SMB2::NegotiateContext.new(
1500
+ context_type: RubySMB::SMB2::NegotiateContext::SMB2_COMPRESSION_CAPABILITIES
1501
+ )
1502
+ nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZNT1
1503
+ nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZ77
1504
+ nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZ77_Huffman
1505
+ nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::Pattern_V1
1506
+ smb3_response.add_negotiate_context(nc)
1507
+ smb3_client.parse_smb3_capabilities(smb3_response)
1508
+ expect(smb3_client.server_compression_algorithms).to eq([1, 2, 3, 4])
1509
+ end
1510
+ end
552
1511
  end
553
1512
  end
554
1513
  end
@@ -875,6 +1834,39 @@ RSpec.describe RubySMB::Client do
875
1834
  smb2_client.smb2_authenticate
876
1835
  expect(smb2_client.os_version).to eq '6.1.7601'
877
1836
  end
1837
+
1838
+ ['0x0202', '0x0210', '0x0300', '0x0302'].each do |dialect|
1839
+ it "does not update the preauth hash with dialect #{dialect}" do
1840
+ smb2_client.dialect = dialect
1841
+ expect(smb2_client).to_not receive(:update_preauth_hash)
1842
+ smb2_client.smb2_authenticate
1843
+ end
1844
+ end
1845
+
1846
+ it "updates the preauth hash with dialect 0x0311" do
1847
+ smb2_client.dialect = '0x0311'
1848
+ expect(smb2_client).to receive(:update_preauth_hash).with(response_packet)
1849
+ smb2_client.smb2_authenticate
1850
+ end
1851
+
1852
+ context 'when setting the session_encrypt_data parameter' do
1853
+ before :example do
1854
+ smb2_client.smb3 = true
1855
+ smb2_client.session_encrypt_data = false
1856
+ end
1857
+
1858
+ it 'sets the session_encrypt_data parameter to true if the server requires encryption' do
1859
+ final_response_packet.session_flags.encrypt_data = 1
1860
+ smb2_client.smb2_authenticate
1861
+ expect(smb2_client.session_encrypt_data).to be true
1862
+ end
1863
+
1864
+ it 'does not set the session_encrypt_data parameter if the server does not require encryption' do
1865
+ final_response_packet.session_flags.encrypt_data = 0
1866
+ smb2_client.smb2_authenticate
1867
+ expect(smb2_client.session_encrypt_data).to be false
1868
+ end
1869
+ end
878
1870
  end
879
1871
 
880
1872
  describe '#smb2_ntlmssp_negotiate_packet' do
@@ -890,20 +1882,34 @@ RSpec.describe RubySMB::Client do
890
1882
  smb2_client.smb2_ntlmssp_negotiate_packet
891
1883
  end
892
1884
 
893
- it 'sets the message ID in the packet header to 1' do
894
- expect(smb2_client.smb2_ntlmssp_negotiate_packet.smb2_header.message_id).to eq 1
895
- end
896
-
897
- it 'increments client#smb2_message_id' do
898
- expect { smb2_client.smb2_ntlmssp_negotiate_packet }.to change(smb2_client, :smb2_message_id).to(2)
1885
+ it 'enables signing' do
1886
+ expect(smb2_client.smb2_ntlmssp_negotiate_packet.security_mode.signing_enabled).to eq 1
899
1887
  end
900
1888
  end
901
1889
 
902
1890
  describe '#smb2_ntlmssp_negotiate' do
1891
+ before :example do
1892
+ allow(smb2_client).to receive(:smb2_ntlmssp_negotiate_packet).and_return(negotiate_packet)
1893
+ allow(smb2_client).to receive(:send_recv)
1894
+ end
1895
+
903
1896
  it 'sends the request packet and receives a response' do
904
- expect(smb2_client).to receive(:smb2_ntlmssp_negotiate_packet).and_return(negotiate_packet)
905
- expect(dispatcher).to receive(:send_packet).with(negotiate_packet)
906
- expect(dispatcher).to receive(:recv_packet)
1897
+ expect(smb2_client).to receive(:smb2_ntlmssp_negotiate_packet)
1898
+ expect(smb2_client).to receive(:send_recv).with(negotiate_packet)
1899
+ smb2_client.smb2_ntlmssp_negotiate
1900
+ end
1901
+
1902
+ ['0x0202', '0x0210', '0x0300', '0x0302'].each do |dialect|
1903
+ it "does not update the preauth hash with dialect #{dialect}" do
1904
+ smb2_client.dialect = dialect
1905
+ expect(smb2_client).to_not receive(:update_preauth_hash)
1906
+ smb2_client.smb2_ntlmssp_negotiate
1907
+ end
1908
+ end
1909
+
1910
+ it "updates the preauth hash with dialect 0x0311" do
1911
+ smb2_client.dialect = '0x0311'
1912
+ expect(smb2_client).to receive(:update_preauth_hash).with(negotiate_packet)
907
1913
  smb2_client.smb2_ntlmssp_negotiate
908
1914
  end
909
1915
  end
@@ -961,13 +1967,35 @@ RSpec.describe RubySMB::Client do
961
1967
  it 'sets the session ID on the request packet' do
962
1968
  expect(smb2_client.smb2_ntlmssp_auth_packet(type3_message, session_id).smb2_header.session_id).to eq session_id
963
1969
  end
1970
+
1971
+ it 'enables signing' do
1972
+ expect(smb2_client.smb2_ntlmssp_auth_packet(type3_message, session_id).security_mode.signing_enabled).to eq 1
1973
+ end
964
1974
  end
965
1975
 
966
1976
  describe '#smb2_ntlmssp_authenticate' do
1977
+ before :example do
1978
+ allow(smb2_client).to receive(:smb2_ntlmssp_auth_packet).and_return(negotiate_packet)
1979
+ allow(smb2_client).to receive(:send_recv)
1980
+ end
1981
+
967
1982
  it 'sends the request packet and receives a response' do
968
- expect(smb2_client).to receive(:smb2_ntlmssp_auth_packet).and_return(negotiate_packet)
969
- expect(dispatcher).to receive(:send_packet).with(negotiate_packet)
970
- expect(dispatcher).to receive(:recv_packet)
1983
+ expect(smb2_client).to receive(:smb2_ntlmssp_auth_packet)
1984
+ expect(smb2_client).to receive(:send_recv).with(negotiate_packet)
1985
+ smb2_client.smb2_ntlmssp_authenticate(type3_message, session_id)
1986
+ end
1987
+
1988
+ ['0x0202', '0x0210', '0x0300', '0x0302'].each do |dialect|
1989
+ it "does not update the preauth hash with dialect #{dialect}" do
1990
+ smb2_client.dialect = dialect
1991
+ expect(smb2_client).to_not receive(:update_preauth_hash)
1992
+ smb2_client.smb2_ntlmssp_authenticate(type3_message, session_id)
1993
+ end
1994
+ end
1995
+
1996
+ it "updates the preauth hash with dialect 0x0311" do
1997
+ smb2_client.dialect = '0x0311'
1998
+ expect(smb2_client).to receive(:update_preauth_hash).with(negotiate_packet)
971
1999
  smb2_client.smb2_ntlmssp_authenticate(type3_message, session_id)
972
2000
  end
973
2001
  end
@@ -1103,6 +2131,108 @@ RSpec.describe RubySMB::Client do
1103
2131
  end
1104
2132
  end
1105
2133
  end
2134
+
2135
+ describe '#smb3_sign' do
2136
+ context 'if signing is required and we have a session key' do
2137
+ let(:request) {
2138
+ packet = RubySMB::SMB2::Packet::SessionSetupRequest.new
2139
+ packet.smb2_header.flags.signed = 1
2140
+ packet.smb2_header.signature = "\x00" * 16
2141
+ packet
2142
+ }
2143
+ let(:session_key) { 'Session Key' }
2144
+ before :example do
2145
+ smb3_client.session_key = session_key
2146
+ smb3_client.signing_required = true
2147
+ end
2148
+
2149
+ ['0x0300', '0x0302'].each do |dialect|
2150
+ context "with #{dialect} dialect" do
2151
+ it 'generates the signing key based on the session key and specific strings, and sign the packet with CMAC' do
2152
+ smb3_client.dialect = dialect
2153
+ fake_hash = "\x34\xc0\x40\xfe\x87\xcf\x49\x3d\x37\x87\x52\xd0\xd5\xf5\xfb\x86".b
2154
+ signing_key = RubySMB::Crypto::KDF.counter_mode(session_key, "SMB2AESCMAC\x00", "SmbSign\x00")
2155
+ expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(session_key, "SMB2AESCMAC\x00", "SmbSign\x00").and_call_original
2156
+ expect(OpenSSL::CMAC).to receive(:digest).with('AES', signing_key, request.to_binary_s).and_call_original
2157
+ expect(smb3_client.smb3_sign(request).smb2_header.signature).to eq fake_hash
2158
+ end
2159
+ end
2160
+ end
2161
+
2162
+ context "with 0x0311 dialect" do
2163
+ it 'generates the signing key based on the session key, the preauth integrity hash and specific strings, and sign the packet with CMAC' do
2164
+ smb3_client.dialect = '0x0311'
2165
+ preauth_integrity_hash_value = 'Preauth Integrity Hash'
2166
+ fake_hash = "\x0e\x49\x6f\x8e\x74\x7c\xf2\xa0\x88\x5e\x9d\x54\xff\x0d\x0d\xfa".b
2167
+ smb3_client.preauth_integrity_hash_value = preauth_integrity_hash_value
2168
+ signing_key = RubySMB::Crypto::KDF.counter_mode(session_key, "SMBSigningKey\x00", preauth_integrity_hash_value)
2169
+ expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(session_key, "SMBSigningKey\x00", preauth_integrity_hash_value).and_call_original
2170
+ expect(OpenSSL::CMAC).to receive(:digest).with('AES', signing_key, request.to_binary_s).and_call_original
2171
+ expect(smb3_client.smb3_sign(request).smb2_header.signature).to eq fake_hash
2172
+ end
2173
+ end
2174
+
2175
+ context 'with an incompatible dialect' do
2176
+ it 'raises the expected exception' do
2177
+ smb3_client.dialect = '0x0202'
2178
+ expect { smb3_client.smb3_sign(request) }.to raise_error(
2179
+ RubySMB::Error::SigningError,
2180
+ 'Dialect is incompatible with SMBv3 signing'
2181
+ )
2182
+ end
2183
+ end
2184
+ end
2185
+
2186
+ context 'if signing is not required but it is a TreeConnectRequest and we have a session key' do
2187
+ let(:request) {
2188
+ packet = RubySMB::SMB2::Packet::TreeConnectRequest.new
2189
+ packet.smb2_header.flags.signed = 1
2190
+ packet.smb2_header.signature = "\x00" * 16
2191
+ packet
2192
+ }
2193
+ let(:session_key) { 'Session Key' }
2194
+ before :example do
2195
+ smb3_client.session_key = session_key
2196
+ smb3_client.signing_required = false
2197
+ end
2198
+
2199
+ ['0x0300', '0x0302'].each do |dialect|
2200
+ context "with #{dialect} dialect" do
2201
+ it 'generates the signing key based on the session key and specific strings, and sign the packet with CMAC' do
2202
+ smb3_client.dialect = dialect
2203
+ fake_hash = "\x34\x9e\x28\xb9\x50\x08\x34\x31\xc0\x83\x9d\xba\x56\xa5\x70\xa4".b
2204
+ signing_key = RubySMB::Crypto::KDF.counter_mode(session_key, "SMB2AESCMAC\x00", "SmbSign\x00")
2205
+ expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(session_key, "SMB2AESCMAC\x00", "SmbSign\x00").and_call_original
2206
+ expect(OpenSSL::CMAC).to receive(:digest).with('AES', signing_key, request.to_binary_s).and_call_original
2207
+ expect(smb3_client.smb3_sign(request).smb2_header.signature).to eq fake_hash
2208
+ end
2209
+ end
2210
+ end
2211
+
2212
+ context "with 0x0311 dialect" do
2213
+ it 'generates the signing key based on the session key, the preauth integrity hash and specific strings, and sign the packet with CMAC' do
2214
+ smb3_client.dialect = '0x0311'
2215
+ preauth_integrity_hash_value = 'Preauth Integrity Hash'
2216
+ fake_hash = "\x83\xd9\x31\x39\x60\x46\xbe\x1e\x29\x34\xc8\xcf\x8c\x8e\xb4\x73".b
2217
+ smb3_client.preauth_integrity_hash_value = preauth_integrity_hash_value
2218
+ signing_key = RubySMB::Crypto::KDF.counter_mode(session_key, "SMBSigningKey\x00", preauth_integrity_hash_value)
2219
+ expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(session_key, "SMBSigningKey\x00", preauth_integrity_hash_value).and_call_original
2220
+ expect(OpenSSL::CMAC).to receive(:digest).with('AES', signing_key, request.to_binary_s).and_call_original
2221
+ expect(smb3_client.smb3_sign(request).smb2_header.signature).to eq fake_hash
2222
+ end
2223
+ end
2224
+
2225
+ context 'with an incompatible dialect' do
2226
+ it 'raises the expected exception' do
2227
+ smb3_client.dialect = '0x0202'
2228
+ expect { smb3_client.smb3_sign(request) }.to raise_error(
2229
+ RubySMB::Error::SigningError,
2230
+ 'Dialect is incompatible with SMBv3 signing'
2231
+ )
2232
+ end
2233
+ end
2234
+ end
2235
+ end
1106
2236
  end
1107
2237
 
1108
2238
  context '#increment_smb_message_id' do
@@ -1156,7 +2286,10 @@ RSpec.describe RubySMB::Client do
1156
2286
 
1157
2287
  it 'raises an UnexpectedStatusCode exception if we do not get STATUS_SUCCESS' do
1158
2288
  response.smb_header.nt_status = 0xc0000015
1159
- expect { smb1_client.smb1_tree_from_response(path, response) }.to raise_error(RubySMB::Error::UnexpectedStatusCode, 'STATUS_NONEXISTENT_SECTOR')
2289
+ expect { smb1_client.smb1_tree_from_response(path, response) }.to raise_error(
2290
+ RubySMB::Error::UnexpectedStatusCode,
2291
+ 'The server responded with an unexpected status code: STATUS_NONEXISTENT_SECTOR'
2292
+ )
1160
2293
  end
1161
2294
 
1162
2295
  it 'creates a new Tree from itself, the share path, and the response packet' do
@@ -1177,11 +2310,14 @@ RSpec.describe RubySMB::Client do
1177
2310
  }
1178
2311
 
1179
2312
  describe '#smb2_tree_connect' do
1180
- it 'builds and sends a TreeconnectRequest for the supplied share' do
2313
+ it 'builds and sends the expected TreeconnectRequest for the supplied share' do
1181
2314
  allow(RubySMB::SMB2::Packet::TreeConnectRequest).to receive(:new).and_return(request)
1182
- modified_request = request
1183
- modified_request.encode_path(path)
1184
- expect(smb2_client).to receive(:send_recv).with(modified_request).and_return(response.to_binary_s)
2315
+ expect(smb2_client).to receive(:send_recv) do |req|
2316
+ expect(req).to eq(request)
2317
+ expect(req.smb2_header.tree_id).to eq(65_535)
2318
+ expect(req.path).to eq(path.encode('UTF-16LE'))
2319
+ response.to_binary_s
2320
+ end
1185
2321
  smb2_client.smb2_tree_connect(path)
1186
2322
  end
1187
2323
 
@@ -1200,11 +2336,20 @@ RSpec.describe RubySMB::Client do
1200
2336
 
1201
2337
  it 'raises an UnexpectedStatusCode exception if we do not get STATUS_SUCCESS' do
1202
2338
  response.smb2_header.nt_status = 0xc0000015
1203
- expect { smb2_client.smb2_tree_from_response(path, response) }.to raise_error(RubySMB::Error::UnexpectedStatusCode, 'STATUS_NONEXISTENT_SECTOR')
2339
+ expect { smb2_client.smb2_tree_from_response(path, response) }.to raise_error(
2340
+ RubySMB::Error::UnexpectedStatusCode,
2341
+ 'The server responded with an unexpected status code: STATUS_NONEXISTENT_SECTOR'
2342
+ )
1204
2343
  end
1205
2344
 
1206
2345
  it 'creates a new Tree from itself, the share path, and the response packet' do
1207
- expect(RubySMB::SMB2::Tree).to receive(:new).with(client: smb2_client, share: path, response: response)
2346
+ expect(RubySMB::SMB2::Tree).to receive(:new).with(client: smb2_client, share: path, response: response, encrypt: false)
2347
+ smb2_client.smb2_tree_from_response(path, response)
2348
+ end
2349
+
2350
+ it 'creates a new with encryption set if the response requires it' do
2351
+ response.share_flags.encrypt = 1
2352
+ expect(RubySMB::SMB2::Tree).to receive(:new).with(client: smb2_client, share: path, response: response, encrypt: true)
1208
2353
  smb2_client.smb2_tree_from_response(path, response)
1209
2354
  end
1210
2355
  end
@@ -1214,7 +2359,7 @@ RSpec.describe RubySMB::Client do
1214
2359
  let(:named_pipe){ double("Named Pipe") }
1215
2360
 
1216
2361
  before :example do
1217
- allow(tree).to receive(:open_file).and_return(named_pipe)
2362
+ allow(tree).to receive(:open_pipe).and_return(named_pipe)
1218
2363
  allow(named_pipe).to receive(:net_share_enum_all)
1219
2364
  end
1220
2365
 
@@ -1229,8 +2374,8 @@ RSpec.describe RubySMB::Client do
1229
2374
  smb1_client.net_share_enum_all(sock.peeraddr)
1230
2375
  end
1231
2376
 
1232
- it 'it calls the Tree #open_file method to open "srvsvc" named pipe' do
1233
- expect(tree).to receive(:open_file).with(filename: "srvsvc", write: true, read: true).and_return(named_pipe)
2377
+ it 'it calls the Tree #open_pipe method to open "srvsvc" named pipe' do
2378
+ expect(tree).to receive(:open_pipe).with(filename: "srvsvc", write: true, read: true).and_return(named_pipe)
1234
2379
  smb1_client.net_share_enum_all(sock.peeraddr)
1235
2380
  end
1236
2381
 
@@ -1252,8 +2397,8 @@ RSpec.describe RubySMB::Client do
1252
2397
  smb2_client.net_share_enum_all(sock.peeraddr)
1253
2398
  end
1254
2399
 
1255
- it 'it calls the Tree #open_file method to open "srvsvc" named pipe' do
1256
- expect(tree).to receive(:open_file).with(filename: "srvsvc", write: true, read: true).and_return(named_pipe)
2400
+ it 'it calls the Tree #open_pipe method to open "srvsvc" named pipe' do
2401
+ expect(tree).to receive(:open_pipe).with(filename: "srvsvc", write: true, read: true).and_return(named_pipe)
1257
2402
  smb2_client.net_share_enum_all(sock.peeraddr)
1258
2403
  end
1259
2404
 
@@ -1301,7 +2446,7 @@ RSpec.describe RubySMB::Client do
1301
2446
  end
1302
2447
 
1303
2448
  it 'raise an InvalidPacket exception when the response is not valid' do
1304
- echo_response.smb_header.command = RubySMB::SMB1::Commands::SMB_COM_SESSION_SETUP
2449
+ echo_response.smb_header.command = RubySMB::SMB1::Commands::SMB_COM_SESSION_SETUP_ANDX
1305
2450
  allow(smb1_client).to receive(:send_recv).and_return(echo_response.to_binary_s)
1306
2451
  expect { smb1_client.echo }.to raise_error(RubySMB::Error::InvalidPacket)
1307
2452
  end
@@ -1334,7 +2479,7 @@ RSpec.describe RubySMB::Client do
1334
2479
  before :example do
1335
2480
  allow(ipc_tree).to receive_messages(
1336
2481
  :share => share,
1337
- :open_file => named_pipe
2482
+ :open_pipe => named_pipe
1338
2483
  )
1339
2484
  allow(client).to receive(:tree_connect).and_return(ipc_tree)
1340
2485
  end
@@ -1356,9 +2501,9 @@ RSpec.describe RubySMB::Client do
1356
2501
  expect(client).to have_received(:tree_connect).with(share)
1357
2502
  end
1358
2503
 
1359
- it 'open \'winreg\' file on the IPC$ Tree' do
2504
+ it 'open \'winreg\' pipe on the IPC$ Tree' do
1360
2505
  client.connect_to_winreg(host)
1361
- expect(ipc_tree).to have_received(:open_file).with(filename: "winreg", write: true, read: true)
2506
+ expect(ipc_tree).to have_received(:open_pipe).with(filename: "winreg", write: true, read: true)
1362
2507
  end
1363
2508
 
1364
2509
  it 'returns the expected opened named pipe' do
@@ -1473,5 +2618,198 @@ RSpec.describe RubySMB::Client do
1473
2618
  end
1474
2619
  end
1475
2620
  end
2621
+
2622
+ describe '#update_preauth_hash' do
2623
+ it 'raises an EncryptionError exception if the preauth integrity hash algorithm is not known' do
2624
+ expect { client.update_preauth_hash('Test') }.to raise_error(
2625
+ RubySMB::Error::EncryptionError,
2626
+ 'Cannot compute the Preauth Integrity Hash value: Preauth Integrity Hash Algorithm is nil'
2627
+ )
2628
+ end
2629
+
2630
+ it 'computes the hash value' do
2631
+ packet = RubySMB::SMB2::Packet::EchoRequest.new
2632
+ data = 'Previous hash'
2633
+ algo = RubySMB::SMB2::PreauthIntegrityCapabilities::HASH_ALGORITM_MAP[
2634
+ RubySMB::SMB2::PreauthIntegrityCapabilities::SHA_512
2635
+ ]
2636
+ client.preauth_integrity_hash_algorithm = algo
2637
+ client.preauth_integrity_hash_value = data
2638
+ hash = OpenSSL::Digest.digest(algo, data + packet.to_binary_s)
2639
+ client.update_preauth_hash(packet)
2640
+ expect(client.preauth_integrity_hash_value).to eq(hash)
2641
+ end
2642
+ end
2643
+
2644
+ context 'Encryption' do
2645
+ describe '#smb3_encrypt' do
2646
+ let(:transform_packet) { double('TransformHeader packet') }
2647
+ let(:session_key) { "\x5c\x00\x4a\x3b\xf0\xa2\x4f\x75\x4c\xb2\x74\x0a\xcf\xc4\x8e\x1a".b }
2648
+ let(:data) { RubySMB::SMB2::Packet::TreeConnectRequest.new.to_binary_s }
2649
+
2650
+ before :example do
2651
+ allow(RubySMB::SMB2::Packet::TransformHeader).to receive(:new).and_return(transform_packet)
2652
+ allow(transform_packet).to receive(:encrypt)
2653
+ client.session_key = session_key
2654
+ end
2655
+
2656
+ it 'does not generate a new client encryption key if it already exists' do
2657
+ client.client_encryption_key = 'key'
2658
+ expect(RubySMB::Crypto::KDF).to_not receive(:counter_mode)
2659
+ expect(client.client_encryption_key).to eq('key')
2660
+ client.smb3_encrypt(data)
2661
+ end
2662
+
2663
+ ['0x0300', '0x0302'].each do |dialect|
2664
+ context "with #{dialect} dialect" do
2665
+ before :example do
2666
+ client.dialect = dialect
2667
+ end
2668
+
2669
+ it 'generates the client encryption key with the expected parameters' do
2670
+ expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(
2671
+ session_key,
2672
+ "SMB2AESCCM\x00",
2673
+ "ServerIn \x00"
2674
+ ).and_call_original
2675
+ client.smb3_encrypt(data)
2676
+ end
2677
+ end
2678
+ end
2679
+
2680
+ context 'with 0x0311 dialect' do
2681
+ it 'generates the client encryption key with the expected parameters' do
2682
+ client.preauth_integrity_hash_value = ''
2683
+ client.dialect = '0x0311'
2684
+ expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(
2685
+ session_key,
2686
+ "SMBC2SCipherKey\x00",
2687
+ ''
2688
+ ).and_call_original
2689
+ client.smb3_encrypt(data)
2690
+ end
2691
+ end
2692
+
2693
+ it 'raises the expected exception if the dialect is incompatible' do
2694
+ client.dialect = '0x0202'
2695
+ expect { client.smb3_encrypt(data) }.to raise_error(RubySMB::Error::EncryptionError)
2696
+ end
2697
+
2698
+ it 'creates a TransformHeader packet and encrypt the data' do
2699
+ client.dialect = '0x0300'
2700
+ client.encryption_algorithm = 'AES-128-CCM'
2701
+ client.session_id = 123
2702
+ client.smb3_encrypt(data)
2703
+ expect(RubySMB::SMB2::Packet::TransformHeader).to have_received(:new).with(flags: 1, session_id: 123)
2704
+ expect(transform_packet).to have_received(:encrypt).with(data, client.client_encryption_key, algorithm: 'AES-128-CCM')
2705
+ end
2706
+
2707
+ it 'generates the expected client encryption key with 0x0302 dialect' do
2708
+ client.dialect = '0x0302'
2709
+ expected_enc_key =
2710
+ "\xa4\xfa\x23\xc1\xb0\x65\x84\xce\x47\x08\x5b\xe0\x64\x98\xd7\x87".b
2711
+ client.smb3_encrypt(data)
2712
+ expect(client.client_encryption_key).to eq expected_enc_key
2713
+ end
2714
+
2715
+ it 'generates the expected client encryption key with 0x0311 dialect' do
2716
+ client.dialect = '0x0311'
2717
+ client.session_key =
2718
+ "\x5c\x00\x4a\x3b\xf0\xa2\x4f\x75\x4c\xb2\x74\x0a\xcf\xc4\x8e\x1a".b
2719
+ client.preauth_integrity_hash_value =
2720
+ "\x57\x77\x7d\x47\xc2\xa9\xc8\x23\x6e\x8a\xfa\x39\xe8\x77\x2f\xb0\xb6"\
2721
+ "\x01\xba\x85\x58\x77\xf5\x01\xa0\xf0\x31\x69\x6a\x64\x49\x1c\x61\xdb"\
2722
+ "\x57\x34\x19\x1b\x80\x33\x9a\xfa\x1d\x6c\x3f\xca\x44\x68\x78\x5b\xb9"\
2723
+ "\xda\x41\xfa\x83\xe5\xa9\x6f\xcf\x44\xbc\xe5\x26\x6e".b
2724
+ expected_enc_key =
2725
+ "\xc7\x4e\xfe\x4d\x15\x48\x5b\x0b\x71\x45\x49\x26\x8a\xd9\x6c\xaa".b
2726
+ client.smb3_encrypt(data)
2727
+ expect(client.client_encryption_key).to eq expected_enc_key
2728
+ end
2729
+ end
2730
+
2731
+ describe '#smb3_decrypt' do
2732
+ let(:transform_packet) { double('TransformHeader packet') }
2733
+ let(:session_key) { "\x5c\x00\x4a\x3b\xf0\xa2\x4f\x75\x4c\xb2\x74\x0a\xcf\xc4\x8e\x1a".b }
2734
+
2735
+ before :example do
2736
+ allow(transform_packet).to receive(:decrypt)
2737
+ client.session_key = session_key
2738
+ end
2739
+
2740
+ it 'does not generate a new server encryption key if it already exists' do
2741
+ client.server_encryption_key = 'key'
2742
+ expect(RubySMB::Crypto::KDF).to_not receive(:counter_mode)
2743
+ expect(client.server_encryption_key).to eq('key')
2744
+ client.smb3_decrypt(transform_packet)
2745
+ end
2746
+
2747
+ ['0x0300', '0x0302'].each do |dialect|
2748
+ context "with #{dialect} dialect" do
2749
+ before :example do
2750
+ client.dialect = dialect
2751
+ end
2752
+
2753
+ it 'generates the client encryption key with the expected parameters' do
2754
+ expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(
2755
+ session_key,
2756
+ "SMB2AESCCM\x00",
2757
+ "ServerOut\x00"
2758
+ ).and_call_original
2759
+ client.smb3_decrypt(transform_packet)
2760
+ end
2761
+ end
2762
+ end
2763
+
2764
+ context 'with 0x0311 dialect' do
2765
+ it 'generates the client encryption key with the expected parameters' do
2766
+ client.preauth_integrity_hash_value = ''
2767
+ client.dialect = '0x0311'
2768
+ expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(
2769
+ session_key,
2770
+ "SMBS2CCipherKey\x00",
2771
+ ''
2772
+ ).and_call_original
2773
+ client.smb3_decrypt(transform_packet)
2774
+ end
2775
+ end
2776
+
2777
+ it 'raises the expected exception if the dialect is incompatible' do
2778
+ client.dialect = '0x0202'
2779
+ expect { client.smb3_decrypt(transform_packet) }.to raise_error(RubySMB::Error::EncryptionError)
2780
+ end
2781
+
2782
+ it 'creates a TransformHeader packet and encrypt the data' do
2783
+ client.dialect = '0x0300'
2784
+ client.encryption_algorithm = 'AES-128-CCM'
2785
+ client.session_id = 123
2786
+ client.smb3_decrypt(transform_packet)
2787
+ expect(transform_packet).to have_received(:decrypt).with(client.server_encryption_key, algorithm: 'AES-128-CCM')
2788
+ end
2789
+
2790
+ it 'generates the expected server encryption key with 0x0302 dialect' do
2791
+ client.dialect = '0x0302'
2792
+ expected_enc_key =
2793
+ "\x65\x21\xd3\x6d\xe9\xe3\x5a\x66\x09\x61\xae\x3e\xc6\x49\x6b\xdf".b
2794
+ client.smb3_decrypt(transform_packet)
2795
+ expect(client.server_encryption_key).to eq expected_enc_key
2796
+ end
2797
+
2798
+ it 'generates the expected server encryption key with 0x0311 dialect' do
2799
+ client.dialect = '0x0311'
2800
+ client.session_key =
2801
+ "\x5c\x00\x4a\x3b\xf0\xa2\x4f\x75\x4c\xb2\x74\x0a\xcf\xc4\x8e\x1a".b
2802
+ client.preauth_integrity_hash_value =
2803
+ "\x57\x77\x7d\x47\xc2\xa9\xc8\x23\x6e\x8a\xfa\x39\xe8\x77\x2f\xb0\xb6"\
2804
+ "\x01\xba\x85\x58\x77\xf5\x01\xa0\xf0\x31\x69\x6a\x64\x49\x1c\x61\xdb"\
2805
+ "\x57\x34\x19\x1b\x80\x33\x9a\xfa\x1d\x6c\x3f\xca\x44\x68\x78\x5b\xb9"\
2806
+ "\xda\x41\xfa\x83\xe5\xa9\x6f\xcf\x44\xbc\xe5\x26\x6e".b
2807
+ expected_enc_key =
2808
+ "\x8c\x2c\x31\x15\x66\xba\xa9\xab\xcf\xb2\x47\x8d\x72\xd5\xd7\x4a".b
2809
+ client.smb3_decrypt(transform_packet)
2810
+ expect(client.server_encryption_key).to eq expected_enc_key
2811
+ end
2812
+ end
2813
+ end
1476
2814
  end
1477
2815