ruby_smb 2.0.2 → 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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