ruby_smb 2.0.2 → 2.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/examples/anonymous_auth.rb +3 -3
  5. data/examples/append_file.rb +10 -8
  6. data/examples/authenticate.rb +9 -5
  7. data/examples/delete_file.rb +8 -6
  8. data/examples/enum_registry_key.rb +5 -4
  9. data/examples/enum_registry_values.rb +5 -4
  10. data/examples/list_directory.rb +8 -6
  11. data/examples/negotiate_with_netbios_service.rb +9 -5
  12. data/examples/net_share_enum_all.rb +6 -4
  13. data/examples/pipes.rb +11 -12
  14. data/examples/query_service_status.rb +64 -0
  15. data/examples/read_file.rb +8 -6
  16. data/examples/read_registry_key_value.rb +6 -5
  17. data/examples/rename_file.rb +9 -7
  18. data/examples/tree_connect.rb +7 -5
  19. data/examples/write_file.rb +9 -7
  20. data/lib/ruby_smb/client.rb +72 -43
  21. data/lib/ruby_smb/client/negotiation.rb +1 -0
  22. data/lib/ruby_smb/dcerpc.rb +2 -0
  23. data/lib/ruby_smb/dcerpc/error.rb +3 -0
  24. data/lib/ruby_smb/dcerpc/ndr.rb +209 -44
  25. data/lib/ruby_smb/dcerpc/request.rb +13 -0
  26. data/lib/ruby_smb/dcerpc/rpc_security_attributes.rb +34 -0
  27. data/lib/ruby_smb/dcerpc/rrp_unicode_string.rb +9 -6
  28. data/lib/ruby_smb/dcerpc/svcctl.rb +479 -0
  29. data/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_request.rb +48 -0
  30. data/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_response.rb +26 -0
  31. data/lib/ruby_smb/dcerpc/svcctl/close_service_handle_request.rb +25 -0
  32. data/lib/ruby_smb/dcerpc/svcctl/close_service_handle_response.rb +26 -0
  33. data/lib/ruby_smb/dcerpc/svcctl/control_service_request.rb +26 -0
  34. data/lib/ruby_smb/dcerpc/svcctl/control_service_response.rb +26 -0
  35. data/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_request.rb +35 -0
  36. data/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_response.rb +23 -0
  37. data/lib/ruby_smb/dcerpc/svcctl/open_service_w_request.rb +31 -0
  38. data/lib/ruby_smb/dcerpc/svcctl/open_service_w_response.rb +23 -0
  39. data/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_request.rb +25 -0
  40. data/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_response.rb +44 -0
  41. data/lib/ruby_smb/dcerpc/svcctl/query_service_status_request.rb +23 -0
  42. data/lib/ruby_smb/dcerpc/svcctl/query_service_status_response.rb +27 -0
  43. data/lib/ruby_smb/dcerpc/svcctl/service_status.rb +25 -0
  44. data/lib/ruby_smb/dcerpc/svcctl/start_service_w_request.rb +27 -0
  45. data/lib/ruby_smb/dcerpc/svcctl/start_service_w_response.rb +25 -0
  46. data/lib/ruby_smb/dcerpc/winreg.rb +98 -17
  47. data/lib/ruby_smb/dcerpc/winreg/create_key_request.rb +73 -0
  48. data/lib/ruby_smb/dcerpc/winreg/create_key_response.rb +36 -0
  49. data/lib/ruby_smb/dcerpc/winreg/enum_key_request.rb +1 -1
  50. data/lib/ruby_smb/dcerpc/winreg/enum_value_request.rb +1 -1
  51. data/lib/ruby_smb/dcerpc/winreg/enum_value_response.rb +1 -1
  52. data/lib/ruby_smb/dcerpc/winreg/open_root_key_request.rb +4 -4
  53. data/lib/ruby_smb/dcerpc/winreg/query_info_key_request.rb +1 -1
  54. data/lib/ruby_smb/dcerpc/winreg/query_value_request.rb +7 -6
  55. data/lib/ruby_smb/dcerpc/winreg/query_value_response.rb +10 -10
  56. data/lib/ruby_smb/dcerpc/winreg/save_key_request.rb +37 -0
  57. data/lib/ruby_smb/dcerpc/winreg/save_key_response.rb +23 -0
  58. data/lib/ruby_smb/dispatcher/base.rb +1 -1
  59. data/lib/ruby_smb/dispatcher/socket.rb +1 -1
  60. data/lib/ruby_smb/field/stringz16.rb +17 -1
  61. data/lib/ruby_smb/nbss/session_header.rb +4 -4
  62. data/lib/ruby_smb/smb1/file.rb +2 -10
  63. data/lib/ruby_smb/smb1/pipe.rb +2 -0
  64. data/lib/ruby_smb/smb2/file.rb +25 -17
  65. data/lib/ruby_smb/smb2/pipe.rb +3 -0
  66. data/lib/ruby_smb/smb2/tree.rb +9 -3
  67. data/lib/ruby_smb/version.rb +1 -1
  68. data/spec/lib/ruby_smb/client_spec.rb +161 -60
  69. data/spec/lib/ruby_smb/dcerpc/ndr_spec.rb +1396 -77
  70. data/spec/lib/ruby_smb/dcerpc/rpc_security_attributes_spec.rb +161 -0
  71. data/spec/lib/ruby_smb/dcerpc/rrp_unicode_string_spec.rb +49 -12
  72. data/spec/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_request_spec.rb +191 -0
  73. data/spec/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_response_spec.rb +38 -0
  74. data/spec/lib/ruby_smb/dcerpc/svcctl/close_service_handle_request_spec.rb +30 -0
  75. data/spec/lib/ruby_smb/dcerpc/svcctl/close_service_handle_response_spec.rb +38 -0
  76. data/spec/lib/ruby_smb/dcerpc/svcctl/control_service_request_spec.rb +39 -0
  77. data/spec/lib/ruby_smb/dcerpc/svcctl/control_service_response_spec.rb +38 -0
  78. data/spec/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_request_spec.rb +78 -0
  79. data/spec/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_response_spec.rb +38 -0
  80. data/spec/lib/ruby_smb/dcerpc/svcctl/open_service_w_request_spec.rb +59 -0
  81. data/spec/lib/ruby_smb/dcerpc/svcctl/open_service_w_response_spec.rb +38 -0
  82. data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_request_spec.rb +38 -0
  83. data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_response_spec.rb +152 -0
  84. data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_status_request_spec.rb +30 -0
  85. data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_status_response_spec.rb +38 -0
  86. data/spec/lib/ruby_smb/dcerpc/svcctl/service_status_spec.rb +72 -0
  87. data/spec/lib/ruby_smb/dcerpc/svcctl/start_service_w_request_spec.rb +46 -0
  88. data/spec/lib/ruby_smb/dcerpc/svcctl/start_service_w_response_spec.rb +30 -0
  89. data/spec/lib/ruby_smb/dcerpc/svcctl_spec.rb +512 -0
  90. data/spec/lib/ruby_smb/dcerpc/winreg/create_key_request_spec.rb +110 -0
  91. data/spec/lib/ruby_smb/dcerpc/winreg/create_key_response_spec.rb +44 -0
  92. data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_request_spec.rb +0 -4
  93. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_request_spec.rb +2 -2
  94. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_response_spec.rb +2 -2
  95. data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_request_spec.rb +9 -4
  96. data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_request_spec.rb +0 -4
  97. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_request_spec.rb +17 -17
  98. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_response_spec.rb +11 -23
  99. data/spec/lib/ruby_smb/dcerpc/winreg/save_key_request_spec.rb +57 -0
  100. data/spec/lib/ruby_smb/dcerpc/winreg/save_key_response_spec.rb +22 -0
  101. data/spec/lib/ruby_smb/dcerpc/winreg_spec.rb +215 -41
  102. data/spec/lib/ruby_smb/dispatcher/socket_spec.rb +10 -10
  103. data/spec/lib/ruby_smb/field/stringz16_spec.rb +12 -0
  104. data/spec/lib/ruby_smb/nbss/session_header_spec.rb +4 -11
  105. data/spec/lib/ruby_smb/smb1/pipe_spec.rb +7 -0
  106. data/spec/lib/ruby_smb/smb2/file_spec.rb +60 -6
  107. data/spec/lib/ruby_smb/smb2/pipe_spec.rb +7 -0
  108. data/spec/lib/ruby_smb/smb2/tree_spec.rb +35 -1
  109. metadata +72 -2
  110. metadata.gz.sig +0 -0
@@ -13,9 +13,10 @@ module RubySMB
13
13
 
14
14
  rpc_hkey :hkey
15
15
  rrp_unicode_string :lp_value_name
16
- string :pad, length: -> { pad_length }
16
+ string :pad1, length: -> { pad_length(self.lp_value_name) }
17
17
  ndr_lp_dword :lp_type
18
- ndr_lp_byte :lp_data
18
+ ndr_lp_byte_array :lp_data
19
+ string :pad2, length: -> { pad_length(self.lp_data) }
19
20
  ndr_lp_dword :lpcb_data
20
21
  ndr_lp_dword :lpcb_len
21
22
 
@@ -24,10 +25,10 @@ module RubySMB
24
25
  @opnum = REG_QUERY_VALUE
25
26
  end
26
27
 
27
- # Determines the correct length for the padding in front of
28
- # #lp_type. It should always force a 4-byte alignment.
29
- def pad_length
30
- offset = (lp_value_name.abs_offset + lp_value_name.to_binary_s.length) % 4
28
+ # Determines the correct length for the padding, so that the next
29
+ # field is 4-byte aligned.
30
+ def pad_length(prev_element)
31
+ offset = (prev_element.abs_offset + prev_element.to_binary_s.length) % 4
31
32
  (4 - offset) % 4
32
33
  end
33
34
  end
@@ -9,22 +9,22 @@ module RubySMB
9
9
 
10
10
  endian :little
11
11
 
12
- ndr_lp_dword :lp_type
13
- ndr_lp_byte :lp_data
14
- string :pad, length: -> { pad_length }
15
- ndr_lp_dword :lpcb_data
16
- ndr_lp_dword :lpcb_len
17
- uint32 :error_status
12
+ ndr_lp_dword :lp_type
13
+ ndr_lp_byte_array :lp_data
14
+ string :pad, length: -> { pad_length(self.lp_data) }
15
+ ndr_lp_dword :lpcb_data
16
+ ndr_lp_dword :lpcb_len
17
+ uint32 :error_status
18
18
 
19
19
  def initialize_instance
20
20
  super
21
21
  @opnum = REG_QUERY_VALUE
22
22
  end
23
23
 
24
- # Determines the correct length for the padding in front of
25
- # #lpcb_data. It should always force a 4-byte alignment.
26
- def pad_length
27
- offset = (lp_data.abs_offset + lp_data.to_binary_s.length) % 4
24
+ # Determines the correct length for the padding, so that the next
25
+ # field is 4-byte aligned.
26
+ def pad_length(prev_element)
27
+ offset = (prev_element.abs_offset + prev_element.to_binary_s.length) % 4
28
28
  (4 - offset) % 4
29
29
  end
30
30
 
@@ -0,0 +1,37 @@
1
+ module RubySMB
2
+ module Dcerpc
3
+ module Winreg
4
+
5
+ class RpcHkey < Ndr::NdrContextHandle; end
6
+
7
+ # This class represents a BaseRegSaveKey Request Packet as defined in
8
+ # [3.1.5.20 BaseRegSaveKey (Opnum 20)](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rrp/f022247d-6ef1-4f46-b195-7f60654f4a0d)
9
+ class SaveKeyRequest < BinData::Record
10
+ attr_reader :opnum
11
+
12
+ endian :little
13
+
14
+ rpc_hkey :hkey
15
+ rrp_unicode_string :lp_file
16
+ string :pad, length: -> { pad_length(self.lp_file) }
17
+ prpc_security_attributes :lp_security_attributes
18
+
19
+ def initialize_instance
20
+ super
21
+ @opnum = REG_SAVE_KEY
22
+ end
23
+
24
+ # Determines the correct length for the padding, so that the next
25
+ # field is 4-byte aligned.
26
+ def pad_length(prev_element)
27
+ offset = (prev_element.abs_offset + prev_element.to_binary_s.length) % 4
28
+ (4 - offset) % 4
29
+ end
30
+ end
31
+
32
+ end
33
+ end
34
+ end
35
+
36
+
37
+
@@ -0,0 +1,23 @@
1
+ module RubySMB
2
+ module Dcerpc
3
+ module Winreg
4
+
5
+ # This class represents a BaseRegSaveKey Response Packet as defined in
6
+ # [3.1.5.20 BaseRegSaveKey (Opnum 20)](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rrp/f022247d-6ef1-4f46-b195-7f60654f4a0d)
7
+ class SaveKeyResponse < BinData::Record
8
+ attr_reader :opnum
9
+
10
+ endian :little
11
+
12
+ uint32 :error_status
13
+
14
+ def initialize_instance
15
+ super
16
+ @opnum = REG_CREATE_KEY
17
+ end
18
+ end
19
+
20
+ end
21
+ end
22
+ end
23
+
@@ -9,7 +9,7 @@ module RubySMB
9
9
  def nbss(packet)
10
10
  nbss = RubySMB::Nbss::SessionHeader.new
11
11
  nbss.session_packet_type = RubySMB::Nbss::SESSION_MESSAGE
12
- nbss.packet_length = packet.do_num_bytes
12
+ nbss.stream_protocol_length = packet.do_num_bytes
13
13
  nbss.to_binary_s
14
14
  end
15
15
 
@@ -74,7 +74,7 @@ module RubySMB
74
74
  raise ::RubySMB::Error::NetBiosSessionService, 'NBSS Header is missing'
75
75
  end
76
76
 
77
- length = nbss_header.packet_length
77
+ length = nbss_header.stream_protocol_length
78
78
  data = full_response ? nbss_header.to_binary_s : ''
79
79
  if length > 0
80
80
  if IO.select([@tcp_socket], nil, nil, @read_timeout).nil?
@@ -25,12 +25,17 @@ module RubySMB
25
25
  # @see BinData::Stringz
26
26
  def read_and_return_value(io)
27
27
  max_length = eval_parameter(:max_length)
28
+ if max_length && max_length % 2 != 0
29
+ raise ArgumentError, "[Stringz16] #max_length should be a multiple of "\
30
+ "two, since it is Unicode (got #{max_length})"
31
+ end
28
32
  str = ''
29
33
  i = 0
30
34
  ch = nil
31
35
 
32
36
  # read until double NULL-byte or we have read in the max number of bytes
33
- while (ch != "\0\0") && (i != max_length)
37
+ loop do
38
+ break if ch == "\0\0" || (max_length && i == max_length)
34
39
  ch = io.readbytes(2)
35
40
  str << ch
36
41
  i += 2
@@ -46,6 +51,17 @@ module RubySMB
46
51
  def truncate_after_first_zero_byte!(str)
47
52
  str.sub!(/([^\0]*\0\0\0).*/, '\1')
48
53
  end
54
+
55
+ def trim_to!(str, max_length = nil)
56
+ if max_length
57
+ max_length = 2 if max_length < 2
58
+ str.slice!(max_length..-1)
59
+ if str.length == max_length && str[-2, 2] != "\0\0"
60
+ str[-2, 2] = "\0\0"
61
+ end
62
+ end
63
+ end
64
+
49
65
  end
50
66
  end
51
67
  end
@@ -1,13 +1,13 @@
1
1
  module RubySMB
2
2
  module Nbss
3
3
  # Representation of the NetBIOS Session Service Header as defined in
4
- # [4.3.1 GENERAL FORMAT OF SESSION PACKETS](https://tools.ietf.org/html/rfc1002)
4
+ # SMB: [2.1 Transport](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb/f906c680-330c-43ae-9a71-f854e24aeee6)
5
+ # SMB2: [2.1 Transport](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/1dfacde4-b5c7-4494-8a14-a09d3ab4cc83)
5
6
  class SessionHeader < BinData::Record
6
7
  endian :big
7
8
 
8
- uint8 :session_packet_type, label: 'Session Packet Type'
9
- bit7 :flags, label: 'Flags', initial_value: 0
10
- bit17 :packet_length, label: 'Packet Length'
9
+ uint8 :session_packet_type, label: 'Session Packet Type', initial_value: 0
10
+ uint24 :stream_protocol_length, label: 'Stream Protocol Length'
11
11
  end
12
12
  end
13
13
  end
@@ -106,11 +106,7 @@ module RubySMB
106
106
  # @raise [RubySMB::Error::InvalidPacket] if the response packet is not valid
107
107
  # @raise [RubySMB::Error::UnexpectedStatusCode] if the response NTStatus is not STATUS_SUCCESS
108
108
  def read(bytes: @size, offset: 0)
109
- atomic_read_size = if bytes > @tree.client.max_buffer_size
110
- @tree.client.max_buffer_size
111
- else
112
- bytes
113
- end
109
+ atomic_read_size = [bytes, @tree.client.max_buffer_size].min
114
110
  remaining_bytes = bytes
115
111
  data = ''
116
112
 
@@ -227,11 +223,7 @@ module RubySMB
227
223
  total_bytes_written = 0
228
224
 
229
225
  loop do
230
- atomic_write_size = if bytes > @tree.client.max_buffer_size
231
- @tree.client.max_buffer_size
232
- else
233
- bytes
234
- end
226
+ atomic_write_size = [bytes, @tree.client.max_buffer_size].min
235
227
  write_request = write_packet(data: buffer.slice!(0, atomic_write_size), offset: offset)
236
228
  raw_response = @tree.client.send_recv(write_request)
237
229
  response = RubySMB::SMB1::Packet::WriteAndxResponse.read(raw_response)
@@ -20,6 +20,8 @@ module RubySMB
20
20
  extend RubySMB::Dcerpc::Srvsvc
21
21
  when 'winreg'
22
22
  extend RubySMB::Dcerpc::Winreg
23
+ when 'svcctl'
24
+ extend RubySMB::Dcerpc::Svcctl
23
25
  end
24
26
  super(tree: tree, response: response, name: name)
25
27
  end
@@ -115,13 +115,15 @@ module RubySMB
115
115
  # @raise [RubySMB::Error::InvalidPacket] if the response is not a ReadResponse packet
116
116
  # @raise [RubySMB::Error::UnexpectedStatusCode] if the response NTStatus is not STATUS_SUCCESS
117
117
  def read(bytes: size, offset: 0)
118
- atomic_read_size = if bytes > tree.client.server_max_read_size
119
- tree.client.server_max_read_size
120
- else
121
- bytes
122
- end
118
+ max_read = tree.client.server_max_read_size
119
+ max_read = 65536 unless tree.client.server_supports_multi_credit
120
+ atomic_read_size = [bytes, max_read].min
121
+ credit_charge = 0
122
+ if tree.client.server_supports_multi_credit
123
+ credit_charge = (atomic_read_size - 1) / 65536 + 1
124
+ end
123
125
 
124
- read_request = read_packet(read_length: atomic_read_size, offset: offset)
126
+ read_request = read_packet(read_length: atomic_read_size, offset: offset, credit_charge: credit_charge)
125
127
  raw_response = tree.client.send_recv(read_request, encrypt: @tree_connect_encrypt_data)
126
128
  response = RubySMB::SMB2::Packet::ReadResponse.read(raw_response)
127
129
  unless response.valid?
@@ -142,9 +144,9 @@ module RubySMB
142
144
 
143
145
  while remaining_bytes > 0
144
146
  offset += atomic_read_size
145
- atomic_read_size = remaining_bytes if remaining_bytes < tree.client.server_max_read_size
147
+ atomic_read_size = remaining_bytes if remaining_bytes < max_read
146
148
 
147
- read_request = read_packet(read_length: atomic_read_size, offset: offset)
149
+ read_request = read_packet(read_length: atomic_read_size, offset: offset, credit_charge: credit_charge)
148
150
  raw_response = tree.client.send_recv(read_request, encrypt: @tree_connect_encrypt_data)
149
151
  response = RubySMB::SMB2::Packet::ReadResponse.read(raw_response)
150
152
  unless response.valid?
@@ -169,11 +171,13 @@ module RubySMB
169
171
  #
170
172
  # @param bytes [Integer] the number of bytes to read
171
173
  # @param offset [Integer] the byte offset in the file to start reading from
174
+ # @param credit_charge [Integer] the number of credits that this request consumes
172
175
  # @return [RubySMB::SMB2::Packet::ReadRequest] the data read from the file
173
- def read_packet(read_length: 0, offset: 0)
176
+ def read_packet(read_length: 0, offset: 0, credit_charge: 1)
174
177
  read_request = set_header_fields(RubySMB::SMB2::Packet::ReadRequest.new)
175
178
  read_request.read_length = read_length
176
179
  read_request.offset = offset
180
+ read_request.smb2_header.credit_charge = credit_charge
177
181
  read_request
178
182
  end
179
183
 
@@ -240,16 +244,18 @@ module RubySMB
240
244
  # @return [WindowsError::ErrorCode] the NTStatus code returned from the operation
241
245
  # @raise [RubySMB::Error::InvalidPacket] if the response is not a WriteResponse packet
242
246
  def write(data:'', offset: 0)
247
+ max_write = tree.client.server_max_write_size
248
+ max_write = 65536 unless tree.client.server_supports_multi_credit
243
249
  buffer = data.dup
244
250
  bytes = data.length
245
- atomic_write_size = if bytes > tree.client.server_max_write_size
246
- tree.client.server_max_write_size
247
- else
248
- bytes
249
- end
251
+ atomic_write_size = [bytes, max_write].min
252
+ credit_charge = 0
253
+ if tree.client.server_supports_multi_credit
254
+ credit_charge = (atomic_write_size - 1) / 65536 + 1
255
+ end
250
256
 
251
257
  while buffer.length > 0 do
252
- write_request = write_packet(data: buffer.slice!(0,atomic_write_size), offset: offset)
258
+ write_request = write_packet(data: buffer.slice!(0, atomic_write_size), offset: offset, credit_charge: credit_charge)
253
259
  raw_response = tree.client.send_recv(write_request, encrypt: @tree_connect_encrypt_data)
254
260
  response = RubySMB::SMB2::Packet::WriteResponse.read(raw_response)
255
261
  unless response.valid?
@@ -262,7 +268,7 @@ module RubySMB
262
268
  end
263
269
  status = response.smb2_header.nt_status.to_nt_status
264
270
 
265
- offset+= atomic_write_size
271
+ offset += atomic_write_size
266
272
  return status unless status == WindowsError::NTStatus::STATUS_SUCCESS
267
273
  end
268
274
 
@@ -273,11 +279,13 @@ module RubySMB
273
279
  #
274
280
  # @param data [String] the data to write to the file
275
281
  # @param offset [Integer] the offset in the file to start writing from
282
+ # @param credit_charge [Integer] the number of credits that this request consumes
276
283
  # @return []RubySMB::SMB2::Packet::WriteRequest] the request packet
277
- def write_packet(data:'', offset: 0)
284
+ def write_packet(data:'', offset: 0, credit_charge: 1)
278
285
  write_request = set_header_fields(RubySMB::SMB2::Packet::WriteRequest.new)
279
286
  write_request.write_offset = offset
280
287
  write_request.buffer = data
288
+ write_request.smb2_header.credit_charge = credit_charge
281
289
  write_request
282
290
  end
283
291
 
@@ -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
@@ -89,6 +91,7 @@ module RubySMB
89
91
  request = set_header_fields(RubySMB::SMB2::Packet::IoctlRequest.new(options))
90
92
  request.ctl_code = 0x0011C017
91
93
  request.flags.is_fsctl = 0x00000001
94
+ # TODO: handle fragmentation when the request size > MAX_XMIT_FRAG
92
95
  request.buffer = action.to_binary_s
93
96
 
94
97
  ioctl_raw_response = @tree.client.send_recv(request)
@@ -147,12 +147,19 @@ module RubySMB
147
147
  directory_request.file_information_class = type::CLASS_LEVEL
148
148
  directory_request.file_id = file_id
149
149
  directory_request.name = pattern
150
- directory_request.output_length = 65_535
150
+
151
+ max_read = client.server_max_read_size
152
+ max_read = 65536 unless client.server_supports_multi_credit
153
+ credit_charge = 0
154
+ if client.server_supports_multi_credit
155
+ credit_charge = (max_read - 1) / 65536 + 1
156
+ end
157
+ directory_request.output_length = max_read
158
+ directory_request.smb2_header.credit_charge = credit_charge
151
159
 
152
160
  directory_request = set_header_fields(directory_request)
153
161
 
154
162
  files = []
155
-
156
163
  loop do
157
164
  response = client.send_recv(directory_request, encrypt: @tree_connect_encrypt_data)
158
165
  directory_response = RubySMB::SMB2::Packet::QueryDirectoryResponse.read(response)
@@ -256,7 +263,6 @@ module RubySMB
256
263
  # @return [RubySMB::SMB2::Packet] the modified packet.
257
264
  def set_header_fields(request)
258
265
  request.smb2_header.tree_id = id
259
- request.smb2_header.credit_charge = 1
260
266
  request.smb2_header.credits = 256
261
267
  request
262
268
  end
@@ -1,3 +1,3 @@
1
1
  module RubySMB
2
- VERSION = '2.0.2'.freeze
2
+ VERSION = '2.0.3'.freeze
3
3
  end
@@ -65,6 +65,8 @@ RSpec.describe RubySMB::Client do
65
65
  it { is_expected.to respond_to :verify_signature }
66
66
  it { is_expected.to respond_to :auth_user }
67
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 }
68
70
 
69
71
  describe '#initialize' do
70
72
  it 'should raise an ArgumentError without a valid dispatcher' do
@@ -141,6 +143,18 @@ RSpec.describe RubySMB::Client do
141
143
  it 'sets the max_buffer_size to MAX_BUFFER_SIZE' do
142
144
  expect(client.max_buffer_size).to eq RubySMB::Client::MAX_BUFFER_SIZE
143
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
144
158
  end
145
159
 
146
160
  describe '#echo' do
@@ -184,11 +198,13 @@ RSpec.describe RubySMB::Client do
184
198
  describe '#send_recv' do
185
199
  let(:smb1_request) { RubySMB::SMB1::Packet::TreeConnectRequest.new }
186
200
  let(:smb2_request) { RubySMB::SMB2::Packet::TreeConnectRequest.new }
201
+ let(:smb2_header) { RubySMB::SMB2::SMB2Header.new }
187
202
 
188
203
  before(:each) do
189
204
  allow(client).to receive(:is_status_pending?).and_return(false)
190
205
  allow(dispatcher).to receive(:send_packet).and_return(nil)
191
206
  allow(dispatcher).to receive(:recv_packet).and_return('A')
207
+ allow(RubySMB::SMB2::SMB2Header).to receive(:read).and_return(smb2_header)
192
208
  end
193
209
 
194
210
  context 'when signing' do
@@ -199,9 +215,10 @@ RSpec.describe RubySMB::Client do
199
215
 
200
216
  context 'with an SMB2 packet' do
201
217
  it 'does not sign a SessionSetupRequest packet' do
218
+ allow(smb2_client).to receive(:is_status_pending?).and_return(false)
202
219
  expect(smb2_client).to_not receive(:smb2_sign)
203
220
  expect(smb2_client).to_not receive(:smb3_sign)
204
- client.send_recv(RubySMB::SMB2::Packet::SessionSetupRequest.new)
221
+ smb2_client.send_recv(RubySMB::SMB2::Packet::SessionSetupRequest.new)
205
222
  end
206
223
 
207
224
  it 'calls #smb2_sign if it is an SMB2 client' do
@@ -229,6 +246,29 @@ RSpec.describe RubySMB::Client do
229
246
  expect(smb1_client).to_not receive(:is_status_pending?)
230
247
  smb1_client.send_recv(smb1_request)
231
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
232
272
  end
233
273
 
234
274
  context 'with SMB2' do
@@ -251,10 +291,8 @@ RSpec.describe RubySMB::Client do
251
291
  context 'with a SessionSetupRequest' do
252
292
  it 'does not encrypt/decrypt' do
253
293
  request = RubySMB::SMB2::Packet::SessionSetupRequest.new
254
- expect(smb3_client).to_not receive(:send_encrypt).with(request)
255
- expect(smb3_client).to_not receive(:recv_encrypt)
256
- expect(dispatcher).to receive(:send_packet).with(request)
257
- expect(dispatcher).to receive(:recv_packet)
294
+ expect(smb3_client).to receive(:send_packet).with(request, encrypt: false)
295
+ expect(smb3_client).to receive(:recv_packet).with(encrypt: false)
258
296
  smb3_client.send_recv(request)
259
297
  end
260
298
  end
@@ -262,17 +300,15 @@ RSpec.describe RubySMB::Client do
262
300
  context 'with a NegotiateRequest' do
263
301
  it 'does not encrypt/decrypt' do
264
302
  request = RubySMB::SMB2::Packet::NegotiateRequest.new
265
- expect(smb3_client).to_not receive(:send_encrypt).with(request)
266
- expect(smb3_client).to_not receive(:recv_encrypt)
267
- expect(dispatcher).to receive(:send_packet).with(request)
268
- expect(dispatcher).to receive(:recv_packet)
303
+ expect(smb3_client).to receive(:send_packet).with(request, encrypt: false)
304
+ expect(smb3_client).to receive(:recv_packet).with(encrypt: false)
269
305
  smb3_client.send_recv(request)
270
306
  end
271
307
  end
272
308
 
273
309
  it 'encrypts and decrypts' do
274
- expect(smb3_client).to receive(:send_encrypt).with(smb2_request)
275
- expect(smb3_client).to receive(:recv_encrypt)
310
+ expect(smb3_client).to receive(:send_packet).with(smb2_request, encrypt: true)
311
+ expect(smb3_client).to receive(:recv_packet).with(encrypt: true)
276
312
  smb3_client.send_recv(smb2_request)
277
313
  end
278
314
 
@@ -280,12 +316,39 @@ RSpec.describe RubySMB::Client do
280
316
  it 'waits 1 second and reads/decrypts again' do
281
317
  allow(smb3_client).to receive(:is_status_pending?).and_return(true, false)
282
318
  expect(smb3_client).to receive(:sleep).with(1)
283
- expect(smb3_client).to receive(:send_encrypt).with(smb2_request)
284
- expect(smb3_client).to receive(:recv_encrypt).twice
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
285
321
  smb3_client.send_recv(smb2_request)
286
322
  end
287
323
  end
288
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 #msb2_message_id with SMB2 header #credit_charge if the dialect is not 0x0202' do
336
+ allow(smb2_client).to receive(:is_status_pending?).and_return(false)
337
+ smb2_client.smb2_message_id = 0
338
+ smb2_client.dialect = '0x0210'
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 dialect is 0x0202' do
345
+ allow(smb2_client).to receive(:is_status_pending?).and_return(false)
346
+ smb2_client.smb2_message_id = 0
347
+ smb2_client.dialect = '0x0202'
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
289
352
  end
290
353
 
291
354
  describe '#is_status_pending?' do
@@ -297,17 +360,17 @@ RSpec.describe RubySMB::Client do
297
360
  }
298
361
 
299
362
  it 'returns true when the response has a STATUS_PENDING status code and the async_command flag set' do
300
- expect(client.is_status_pending?(response.to_binary_s)).to be true
363
+ expect(client.is_status_pending?(response.smb2_header)).to be true
301
364
  end
302
365
 
303
366
  it 'returns false when the response has a STATUS_PENDING status code and the async_command flag not set' do
304
367
  response.smb2_header.flags.async_command = 0
305
- expect(client.is_status_pending?(response.to_binary_s)).to be false
368
+ expect(client.is_status_pending?(response.smb2_header)).to be false
306
369
  end
307
370
 
308
371
  it 'returns false when the response has no STATUS_PENDING status code but the async_command flag set' do
309
372
  response.smb2_header.nt_status= WindowsError::NTStatus::STATUS_SUCCESS.value
310
- expect(client.is_status_pending?(response.to_binary_s)).to be false
373
+ expect(client.is_status_pending?(response.smb2_header)).to be false
311
374
  end
312
375
  end
313
376
 
@@ -344,35 +407,47 @@ RSpec.describe RubySMB::Client do
344
407
  end
345
408
  end
346
409
 
347
- describe '#send_encrypt' do
410
+ describe '#send_packet' do
348
411
  let(:packet) { RubySMB::SMB2::Packet::SessionSetupRequest.new }
349
412
  before :example do
350
413
  allow(dispatcher).to receive(:send_packet)
351
414
  client.dialect = '0x0300'
352
415
  end
353
416
 
354
- it 'creates a Transform request' do
355
- expect(client).to receive(:smb3_encrypt).with(packet.to_binary_s)
356
- client.send_encrypt(packet)
417
+ it 'does not encrypt the packet' do
418
+ expect(client).to_not receive(:smb3_encrypt)
419
+ client.send_packet(packet)
357
420
  end
358
421
 
359
- it 'raises an EncryptionError exception if an error occurs while encrypting' do
360
- allow(client).to receive(:smb3_encrypt).and_raise(RubySMB::Error::RubySMBError.new('Error'))
361
- expect { client.send_encrypt(packet) }.to raise_error(
362
- RubySMB::Error::EncryptionError,
363
- "Error while encrypting #{packet.class.name} packet (SMB 0x0300): Error"
364
- )
422
+ it 'sends the packet through the dispatcher' do
423
+ client.send_packet(packet)
424
+ expect(dispatcher).to have_received(:send_packet).with(packet)
365
425
  end
366
426
 
367
- it 'sends the encrypted packet' do
368
- encrypted_packet = double('Encrypted packet')
369
- allow(client).to receive(:smb3_encrypt).and_return(encrypted_packet)
370
- client.send_encrypt(packet)
371
- expect(dispatcher).to have_received(:send_packet).with(encrypted_packet)
427
+ context 'with encryption' do
428
+ it 'creates a Transform request' do
429
+ expect(client).to receive(:smb3_encrypt).with(packet.to_binary_s)
430
+ client.send_packet(packet, encrypt: true)
431
+ end
432
+
433
+ it 'raises an EncryptionError exception if an error occurs while encrypting' do
434
+ allow(client).to receive(:smb3_encrypt).and_raise(RubySMB::Error::RubySMBError.new('Error'))
435
+ expect { client.send_packet(packet, encrypt: true) }.to raise_error(
436
+ RubySMB::Error::EncryptionError,
437
+ "Error while encrypting #{packet.class.name} packet (SMB 0x0300): Error"
438
+ )
439
+ end
440
+
441
+ it 'sends the encrypted packet' do
442
+ encrypted_packet = double('Encrypted packet')
443
+ allow(client).to receive(:smb3_encrypt).and_return(encrypted_packet)
444
+ client.send_packet(packet, encrypt: true)
445
+ expect(dispatcher).to have_received(:send_packet).with(encrypted_packet)
446
+ end
372
447
  end
373
448
  end
374
449
 
375
- describe '#recv_encrypt' do
450
+ describe '#recv_packet' do
376
451
  let(:packet) { RubySMB::SMB2::Packet::SessionSetupRequest.new }
377
452
  before :example do
378
453
  allow(dispatcher).to receive(:recv_packet).and_return(packet.to_binary_s)
@@ -381,42 +456,49 @@ RSpec.describe RubySMB::Client do
381
456
  end
382
457
 
383
458
  it 'reads the response packet' do
384
- client.recv_encrypt
459
+ client.recv_packet
385
460
  expect(dispatcher).to have_received(:recv_packet)
386
461
  end
387
462
 
388
- it 'raises an EncryptionError exception if an error occurs while receiving the response' do
463
+ it 'raises an CommunicationError exception if an error occurs while receiving the response' do
389
464
  allow(dispatcher).to receive(:recv_packet).and_raise(RubySMB::Error::CommunicationError)
390
- expect { client.recv_encrypt }.to raise_error(
391
- RubySMB::Error::EncryptionError,
392
- 'Communication error with the remote host: RubySMB::Error::CommunicationError. '\
393
- 'The server supports encryption but was not able to handle the encrypted request.'
394
- )
465
+ expect { client.recv_packet }.to raise_error(RubySMB::Error::CommunicationError)
395
466
  end
396
467
 
397
- it 'parses the response as a Transform response packet' do
398
- expect(RubySMB::SMB2::Packet::TransformHeader).to receive(:read).with(packet.to_binary_s)
399
- client.recv_encrypt
400
- end
468
+ context 'with encryption' do
469
+ it 'raises an EncryptionError exception if an error occurs while receiving the response' do
470
+ allow(dispatcher).to receive(:recv_packet).and_raise(RubySMB::Error::CommunicationError)
471
+ expect { client.recv_packet(encrypt: true) }.to raise_error(
472
+ RubySMB::Error::EncryptionError,
473
+ 'Communication error with the remote host: RubySMB::Error::CommunicationError. '\
474
+ 'The server supports encryption but was not able to handle the encrypted request.'
475
+ )
476
+ end
401
477
 
402
- it 'raises an InvalidPacket exception if an error occurs while parsing the response' do
403
- allow(RubySMB::SMB2::Packet::TransformHeader).to receive(:read).and_raise(IOError)
404
- expect { client.recv_encrypt }.to raise_error(RubySMB::Error::InvalidPacket, 'Not a SMB2 TransformHeader packet')
405
- end
478
+ it 'parses the response as a Transform response packet' do
479
+ expect(RubySMB::SMB2::Packet::TransformHeader).to receive(:read).with(packet.to_binary_s)
480
+ client.recv_packet(encrypt: true)
481
+ end
406
482
 
407
- it 'decrypts the Transform response packet' do
408
- transform = double('Transform header packet')
409
- allow(RubySMB::SMB2::Packet::TransformHeader).to receive(:read).and_return(transform)
410
- client.recv_encrypt
411
- expect(client).to have_received(:smb3_decrypt).with(transform)
412
- end
483
+ it 'raises an InvalidPacket exception if an error occurs while parsing the response' do
484
+ allow(RubySMB::SMB2::Packet::TransformHeader).to receive(:read).and_raise(IOError)
485
+ expect { client.recv_packet(encrypt: true) }.to raise_error(RubySMB::Error::InvalidPacket, 'Not a SMB2 TransformHeader packet')
486
+ end
413
487
 
414
- it 'raises an EncryptionError exception if an error occurs while decrypting' do
415
- allow(client).to receive(:smb3_decrypt).and_raise(RubySMB::Error::RubySMBError)
416
- expect { client.recv_encrypt }.to raise_error(
417
- RubySMB::Error::EncryptionError,
418
- 'Error while decrypting RubySMB::SMB2::Packet::TransformHeader packet (SMB 0x0300}): RubySMB::Error::RubySMBError'
419
- )
488
+ it 'decrypts the Transform response packet' do
489
+ transform = double('Transform header packet')
490
+ allow(RubySMB::SMB2::Packet::TransformHeader).to receive(:read).and_return(transform)
491
+ client.recv_packet(encrypt: true)
492
+ expect(client).to have_received(:smb3_decrypt).with(transform)
493
+ end
494
+
495
+ it 'raises an EncryptionError exception if an error occurs while decrypting' do
496
+ allow(client).to receive(:smb3_decrypt).and_raise(RubySMB::Error::RubySMBError)
497
+ expect { client.recv_packet(encrypt: true) }.to raise_error(
498
+ RubySMB::Error::EncryptionError,
499
+ 'Error while decrypting RubySMB::SMB2::Packet::TransformHeader packet (SMB 0x0300}): RubySMB::Error::RubySMBError'
500
+ )
501
+ end
420
502
  end
421
503
  end
422
504
 
@@ -667,7 +749,7 @@ RSpec.describe RubySMB::Client do
667
749
  expect(session_packet.session_header.session_packet_type).to eq RubySMB::Nbss::SESSION_REQUEST
668
750
  expect(session_packet.called_name).to eq called_name
669
751
  expect(session_packet.calling_name).to eq calling_name
670
- expect(session_packet.session_header.packet_length).to eq(
752
+ expect(session_packet.session_header.stream_protocol_length).to eq(
671
753
  session_packet.called_name.to_binary_s.size + session_packet.calling_name.to_binary_s.size
672
754
  )
673
755
  end
@@ -1036,6 +1118,19 @@ RSpec.describe RubySMB::Client do
1036
1118
  it 'returns the string \'SMB2\'' do
1037
1119
  expect(client.parse_negotiate_response(smb2_response)).to eq ('SMB2')
1038
1120
  end
1121
+
1122
+ it 'sets #server_supports_multi_credit to true when the response has #large_mtu capability set' do
1123
+ smb2_response.capabilities.large_mtu = 1
1124
+ client.parse_negotiate_response(smb2_response)
1125
+ expect(client.server_supports_multi_credit).to be true
1126
+ end
1127
+
1128
+ it 'sets #server_supports_multi_credit to false when the dialect is 0x0202' do
1129
+ smb2_response.dialect_revision = 0x0202
1130
+ smb2_response.capabilities.large_mtu = 1 # just to make sure it won't affect the result
1131
+ client.parse_negotiate_response(smb2_response)
1132
+ expect(client.server_supports_multi_credit).to be false
1133
+ end
1039
1134
  end
1040
1135
 
1041
1136
  context 'when SMB3 was negotiated' do
@@ -1060,6 +1155,12 @@ RSpec.describe RubySMB::Client do
1060
1155
  expect(client.parse_negotiate_response(smb3_response)).to eq ('SMB3')
1061
1156
  end
1062
1157
 
1158
+ it 'sets #server_supports_multi_credit to true when the response has #large_mtu capability set' do
1159
+ smb3_response.capabilities.large_mtu = 1
1160
+ client.parse_negotiate_response(smb3_response)
1161
+ expect(client.server_supports_multi_credit).to be true
1162
+ end
1163
+
1063
1164
  context 'when the server supports encryption' do
1064
1165
  before :example do
1065
1166
  smb3_response.capabilities.encryption = 1