ruby_smb 1.0.5 → 1.1.0

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 (77) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.travis.yml +4 -1
  5. data/README.md +35 -47
  6. data/examples/enum_registry_key.rb +28 -0
  7. data/examples/enum_registry_values.rb +30 -0
  8. data/examples/pipes.rb +2 -1
  9. data/examples/read_registry_key_value.rb +32 -0
  10. data/lib/ruby_smb.rb +0 -1
  11. data/lib/ruby_smb/client.rb +2 -0
  12. data/lib/ruby_smb/client/winreg.rb +46 -0
  13. data/lib/ruby_smb/dcerpc.rb +38 -0
  14. data/lib/ruby_smb/dcerpc/bind.rb +2 -2
  15. data/lib/ruby_smb/dcerpc/bind_ack.rb +2 -2
  16. data/lib/ruby_smb/dcerpc/error.rb +3 -0
  17. data/lib/ruby_smb/dcerpc/ndr.rb +95 -16
  18. data/lib/ruby_smb/dcerpc/pdu_header.rb +1 -1
  19. data/lib/ruby_smb/dcerpc/request.rb +28 -9
  20. data/lib/ruby_smb/dcerpc/rrp_unicode_string.rb +35 -0
  21. data/lib/ruby_smb/dcerpc/srvsvc.rb +10 -0
  22. data/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all.rb +9 -0
  23. data/lib/ruby_smb/dcerpc/winreg.rb +340 -0
  24. data/lib/ruby_smb/dcerpc/winreg/close_key_request.rb +24 -0
  25. data/lib/ruby_smb/dcerpc/winreg/close_key_response.rb +27 -0
  26. data/lib/ruby_smb/dcerpc/winreg/enum_key_request.rb +45 -0
  27. data/lib/ruby_smb/dcerpc/winreg/enum_key_response.rb +42 -0
  28. data/lib/ruby_smb/dcerpc/winreg/enum_value_request.rb +39 -0
  29. data/lib/ruby_smb/dcerpc/winreg/enum_value_response.rb +36 -0
  30. data/lib/ruby_smb/dcerpc/winreg/open_key_request.rb +34 -0
  31. data/lib/ruby_smb/dcerpc/winreg/open_key_response.rb +25 -0
  32. data/lib/ruby_smb/dcerpc/winreg/open_root_key_request.rb +43 -0
  33. data/lib/ruby_smb/dcerpc/winreg/open_root_key_response.rb +35 -0
  34. data/lib/ruby_smb/dcerpc/winreg/query_info_key_request.rb +27 -0
  35. data/lib/ruby_smb/dcerpc/winreg/query_info_key_response.rb +40 -0
  36. data/lib/ruby_smb/dcerpc/winreg/query_value_request.rb +39 -0
  37. data/lib/ruby_smb/dcerpc/winreg/query_value_response.rb +57 -0
  38. data/lib/ruby_smb/dcerpc/winreg/regsam.rb +40 -0
  39. data/lib/ruby_smb/smb1/file.rb +2 -0
  40. data/lib/ruby_smb/smb1/pipe.rb +78 -2
  41. data/lib/ruby_smb/smb2/packet/error_packet.rb +2 -4
  42. data/lib/ruby_smb/smb2/pipe.rb +89 -2
  43. data/lib/ruby_smb/version.rb +1 -1
  44. data/ruby_smb.gemspec +3 -3
  45. data/spec/lib/ruby_smb/client_spec.rb +148 -0
  46. data/spec/lib/ruby_smb/dcerpc/bind_ack_spec.rb +2 -2
  47. data/spec/lib/ruby_smb/dcerpc/bind_spec.rb +2 -2
  48. data/spec/lib/ruby_smb/dcerpc/ndr_spec.rb +410 -0
  49. data/spec/lib/ruby_smb/dcerpc/request_spec.rb +50 -7
  50. data/spec/lib/ruby_smb/dcerpc/rrp_unicode_string_spec.rb +98 -0
  51. data/spec/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all_spec.rb +13 -0
  52. data/spec/lib/ruby_smb/dcerpc/srvsvc_spec.rb +60 -0
  53. data/spec/lib/ruby_smb/dcerpc/winreg/close_key_request_spec.rb +28 -0
  54. data/spec/lib/ruby_smb/dcerpc/winreg/close_key_response_spec.rb +36 -0
  55. data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_request_spec.rb +108 -0
  56. data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_response_spec.rb +97 -0
  57. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_request_spec.rb +94 -0
  58. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_response_spec.rb +82 -0
  59. data/spec/lib/ruby_smb/dcerpc/winreg/open_key_request_spec.rb +74 -0
  60. data/spec/lib/ruby_smb/dcerpc/winreg/open_key_response_spec.rb +35 -0
  61. data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_request_spec.rb +90 -0
  62. data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_response_spec.rb +38 -0
  63. data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_request_spec.rb +39 -0
  64. data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_response_spec.rb +113 -0
  65. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_request_spec.rb +88 -0
  66. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_response_spec.rb +150 -0
  67. data/spec/lib/ruby_smb/dcerpc/winreg/regsam_spec.rb +32 -0
  68. data/spec/lib/ruby_smb/dcerpc/winreg_spec.rb +710 -0
  69. data/spec/lib/ruby_smb/dcerpc_spec.rb +81 -0
  70. data/spec/lib/ruby_smb/smb1/file_spec.rb +9 -1
  71. data/spec/lib/ruby_smb/smb1/pipe_spec.rb +210 -148
  72. data/spec/lib/ruby_smb/smb2/packet/error_packet_spec.rb +3 -24
  73. data/spec/lib/ruby_smb/smb2/pipe_spec.rb +256 -145
  74. metadata +66 -9
  75. metadata.gz.sig +0 -0
  76. data/lib/ruby_smb/smb1/dcerpc.rb +0 -72
  77. data/lib/ruby_smb/smb2/dcerpc.rb +0 -75
@@ -35,17 +35,6 @@ RSpec.describe RubySMB::SMB2::Packet::ErrorPacket do
35
35
  it 'should be a 32-bit unsigned integer' do
36
36
  expect(packet.byte_count).to be_a BinData::Uint32le
37
37
  end
38
-
39
- it 'should be the number of bytes in #error_data' do
40
- str = 'testing'
41
- packet.error_data = str
42
- expect(packet.byte_count).to eq str.size
43
- end
44
-
45
- it 'should be 0 when #error_data is 1-byte long' do
46
- packet.error_data = "\x00"
47
- expect(packet.byte_count).to eq 0
48
- end
49
38
  end
50
39
 
51
40
  describe '#error_data' do
@@ -53,20 +42,10 @@ RSpec.describe RubySMB::SMB2::Packet::ErrorPacket do
53
42
  expect(packet.error_data).to be_a BinData::String
54
43
  end
55
44
 
56
- it 'should be \x00 by default' do
57
- expect(packet.error_data).to eq "\x00"
58
- end
59
-
60
- it 'should read 1 byte when #byte_count is 0' do
61
- packet.error_data = 'test'
62
- packet.byte_count = 0
63
- expect(described_class.read(packet.to_binary_s).error_data.size).to eq 1
64
- end
65
-
66
- it 'should read #byte_count bytes when #byte_count is not 0' do
45
+ it 'should read #byte_count bytes' do
67
46
  packet.error_data = 'test'
68
- packet.byte_count = 4
69
- expect(described_class.read(packet.to_binary_s).error_data.size).to eq 4
47
+ packet.byte_count = 3
48
+ expect(described_class.read(packet.to_binary_s).error_data.size).to eq 3
70
49
  end
71
50
  end
72
51
 
@@ -27,14 +27,14 @@ RSpec.describe RubySMB::SMB2::Pipe do
27
27
  last_write: time
28
28
  )
29
29
  }
30
-
31
30
  let(:ioctl_response) {
32
31
  packet = RubySMB::SMB2::Packet::IoctlResponse.new
33
32
  packet.buffer = "\x03\x00\x00\x00" + "\x10\x20\x30\x40" + "\x00\x00\x00\x00" + "\x00\x00\x00\x00"
34
33
  packet
35
34
  }
35
+ let(:filename) { 'msf-pipe' }
36
36
 
37
- subject(:pipe) { described_class.new(name: 'msf-pipe', response: create_response, tree: tree) }
37
+ subject(:pipe) { described_class.new(name: filename, response: create_response, tree: tree) }
38
38
 
39
39
  describe '#peek' do
40
40
  let(:request) { RubySMB::SMB2::Packet::IoctlRequest.new }
@@ -127,206 +127,317 @@ RSpec.describe RubySMB::SMB2::Pipe do
127
127
  end
128
128
  end
129
129
 
130
- context 'with DCERPC' do
131
- describe '#net_share_enum_all' do
132
- let(:host) { '1.2.3.4' }
133
- let(:dcerpc_response) { RubySMB::Dcerpc::Response.new }
134
-
135
- before :example do
136
- allow(pipe).to receive(:bind)
137
- allow(pipe).to receive(:request).and_return(dcerpc_response)
138
- allow(RubySMB::Dcerpc::Srvsvc::NetShareEnumAll).to receive(:parse_response).and_return([])
130
+ describe '#initialize' do
131
+ context 'when name is not provided' do
132
+ it 'raises an ArgumentError' do
133
+ expect {
134
+ described_class.new(tree: tree, response: create_response, name: nil)
135
+ }.to raise_error(ArgumentError)
139
136
  end
137
+ end
140
138
 
141
- it 'calls #bind with the expected arguments' do
142
- expect(pipe).to receive(:bind).with(endpoint: RubySMB::Dcerpc::Srvsvc)
143
- pipe.net_share_enum_all(host)
144
- end
139
+ it 'calls the superclass with the expected arguments' do
140
+ expect(pipe.tree).to eq(tree)
141
+ expect(pipe.name).to eq(filename)
142
+ expect(pipe.attributes).to eq(create_response.file_attributes)
143
+ expect(pipe.guid).to eq(create_response.file_id)
144
+ expect(pipe.last_access).to eq(create_response.last_access.to_datetime)
145
+ expect(pipe.last_change).to eq(create_response.last_change.to_datetime)
146
+ expect(pipe.last_write).to eq(create_response.last_write.to_datetime)
147
+ expect(pipe.size).to eq(create_response.end_of_file)
148
+ expect(pipe.size_on_disk).to eq(create_response.allocation_size)
149
+ end
145
150
 
146
- it 'calls #request with the expected arguments' do
147
- expect(pipe).to receive(:request).with(RubySMB::Dcerpc::Srvsvc::NET_SHARE_ENUM_ALL, host: host)
148
- pipe.net_share_enum_all(host)
151
+ context 'with \'srvsvc\' filename' do
152
+ it 'extends Srvsvc class' do
153
+ pipe = described_class.new(tree: tree, response: create_response, name: 'srvsvc')
154
+ expect(pipe.respond_to?(:net_share_enum_all)).to be true
149
155
  end
156
+ end
150
157
 
151
- it 'parse the response with NetShareEnumAll #parse_response method' do
152
- stub = 'ABCD'
153
- dcerpc_response.alloc_hint = stub.size
154
- dcerpc_response.stub = stub
155
- expect(RubySMB::Dcerpc::Srvsvc::NetShareEnumAll).to receive(:parse_response).with(stub)
156
- pipe.net_share_enum_all(host)
158
+ context 'with \'winreg\' filename' do
159
+ it 'extends Winreg class' do
160
+ pipe = described_class.new(tree: tree, response: create_response, name: 'winreg')
161
+ expect(pipe.respond_to?(:has_registry_key?)).to be true
157
162
  end
163
+ end
164
+ end
158
165
 
159
- it 'returns the remote shares' do
160
- shares = [
161
- ["C$", "DISK", "Default share"],
162
- ["Shared", "DISK", ""],
163
- ["IPC$", "IPC", "Remote IPC"],
164
- ["ADMIN$", "DISK", "Remote Admin"]
165
- ]
166
- output = [
167
- {:name=>"C$", :type=>"DISK", :comment=>"Default share"},
168
- {:name=>"Shared", :type=>"DISK", :comment=>""},
169
- {:name=>"IPC$", :type=>"IPC", :comment=>"Remote IPC"},
170
- {:name=>"ADMIN$", :type=>"DISK", :comment=>"Remote Admin"},
171
- ]
172
- allow(RubySMB::Dcerpc::Srvsvc::NetShareEnumAll).to receive(:parse_response).and_return(shares)
173
- expect(pipe.net_share_enum_all(host)).to eq(output)
174
- end
166
+ describe '#dcerpc_request' do
167
+ let(:options) { { host: '1.2.3.4' } }
168
+ let(:stub_packet ) { RubySMB::Dcerpc::Winreg::OpenKeyRequest.new }
169
+ let(:dcerpc_request) { double('DCERPC Request') }
170
+ let(:request_stub) { double('Request stub') }
171
+ before :example do
172
+ allow(RubySMB::Dcerpc::Request).to receive(:new).and_return(dcerpc_request)
173
+ allow(dcerpc_request).to receive(:stub).and_return(request_stub)
174
+ allow(request_stub).to receive(:read)
175
+ allow(pipe).to receive(:ioctl_send_recv)
176
+ end
177
+
178
+ it 'creates a Request packet with the expected arguments' do
179
+ pipe.dcerpc_request(stub_packet, options)
180
+ expect(options).to eq( { host: '1.2.3.4', endpoint: 'Winreg' })
181
+ expect(RubySMB::Dcerpc::Request).to have_received(:new).with({ opnum: stub_packet.opnum }, options)
182
+ end
183
+
184
+ it 'sets DCERPC request stub to the stub packet passed as argument' do
185
+ pipe.dcerpc_request(stub_packet, options)
186
+ expect(request_stub).to have_received(:read).with(stub_packet.to_binary_s)
187
+ end
188
+
189
+ it 'calls #ioctl_send_recv with the expected arguments' do
190
+ pipe.dcerpc_request(stub_packet, options)
191
+ expect(pipe).to have_received(:ioctl_send_recv).with(dcerpc_request, options)
192
+ end
193
+ end
194
+
195
+ describe '#ioctl_send_recv' do
196
+ let(:ioctl_request_packet) { double('IoctlRequest') }
197
+ let(:flags) { double('Flags') }
198
+ let(:dcerpc_request) { double('DCERPC Request') }
199
+ let(:binary_dcerpc_request) { double('Binary DCERPC Request') }
200
+ let(:options) { { host: '1.2.3.4' } }
201
+ let(:ioctl_raw_response) { double('IOCTL raw response') }
202
+ let(:ioctl_response) { double('IOCTL response') }
203
+ let(:raw_data) { double('Raw data') }
204
+ let(:dcerpc_response) { double('DCERPC Response') }
205
+ let(:result) { 'Result' }
206
+ before :example do
207
+ allow(RubySMB::SMB2::Packet::IoctlRequest).to receive(:new).and_return(ioctl_request_packet)
208
+ allow(pipe).to receive(:set_header_fields).and_return(ioctl_request_packet)
209
+ allow(ioctl_request_packet).to receive_messages(
210
+ :ctl_code= => nil,
211
+ :flags => flags,
212
+ :buffer= => nil
213
+ )
214
+ allow(flags).to receive(:is_fsctl=)
215
+ allow(dcerpc_request).to receive(:to_binary_s).and_return(binary_dcerpc_request)
216
+ allow(client).to receive(:send_recv).and_return(ioctl_raw_response)
217
+ allow(RubySMB::SMB2::Packet::IoctlResponse).to receive(:read).and_return(ioctl_response)
218
+ allow(ioctl_response).to receive_messages(
219
+ :valid? => true,
220
+ :status_code => WindowsError::NTStatus::STATUS_SUCCESS,
221
+ :output_data => raw_data
222
+ )
223
+ allow(RubySMB::Dcerpc::Response).to receive(:read).and_return(dcerpc_response)
224
+ allow(dcerpc_response).to receive_message_chain(:pdu_header, :ptype => RubySMB::Dcerpc::PTypes::RESPONSE)
225
+ allow(dcerpc_response).to receive(:stub).and_return(result)
226
+ end
227
+
228
+ it 'creates an IoctlRequest packet' do
229
+ pipe.ioctl_send_recv(dcerpc_request, options)
230
+ expect(RubySMB::SMB2::Packet::IoctlRequest).to have_received(:new).with(options)
231
+ end
232
+
233
+ it 'calls #set_header_fields' do
234
+ pipe.ioctl_send_recv(dcerpc_request, options)
235
+ expect(pipe).to have_received(:set_header_fields).with(ioctl_request_packet)
236
+ end
237
+
238
+ it 'sets the expected properties on the request packet' do
239
+ pipe.ioctl_send_recv(dcerpc_request, options)
240
+ expect(ioctl_request_packet).to have_received(:ctl_code=).with(0x11C017)
241
+ expect(flags).to have_received(:is_fsctl=).with(0x1)
242
+ expect(ioctl_request_packet).to have_received(:buffer=).with(binary_dcerpc_request)
175
243
  end
176
244
 
177
- describe '#bind' do
178
- let(:options) { { endpoint: RubySMB::Dcerpc::Srvsvc } }
179
- let(:bind_packet) { RubySMB::Dcerpc::Bind.new(options) }
180
- let(:bind_ack_packet) { RubySMB::Dcerpc::BindAck.new }
245
+ it 'sends the expected request' do
246
+ pipe.ioctl_send_recv(dcerpc_request, options)
247
+ expect(client).to have_received(:send_recv).with(ioctl_request_packet)
248
+ end
249
+
250
+ it 'creates an IoctlResponse packet from the response' do
251
+ pipe.ioctl_send_recv(dcerpc_request, options)
252
+ expect(RubySMB::SMB2::Packet::IoctlResponse).to have_received(:read).with(ioctl_raw_response)
253
+ end
181
254
 
255
+ context 'when the response is not an IoctlResponse packet' do
256
+ it 'raises an InvalidPacket exception' do
257
+ allow(ioctl_response).to receive_message_chain(:smb2_header, :protocol)
258
+ allow(ioctl_response).to receive_message_chain(:smb2_header, :command)
259
+ allow(ioctl_response).to receive(:valid?).and_return(false)
260
+ expect { pipe.ioctl_send_recv(dcerpc_request, options) }.to raise_error(RubySMB::Error::InvalidPacket)
261
+ end
262
+ end
263
+
264
+ context 'when the response status code is STATUS_PENDING' do
265
+ let(:ioctl_raw_response2) { double('IOCTL raw response #2') }
266
+ let(:ioctl_response2) { double('IOCTL response #2') }
182
267
  before :example do
183
- allow(RubySMB::Dcerpc::Bind).to receive(:new).and_return(bind_packet)
184
- allow(pipe).to receive(:write)
185
- allow(pipe).to receive(:read)
186
- bind_ack_packet.p_result_list.n_results = 1
187
- bind_ack_packet.p_result_list.p_results[0].result = RubySMB::Dcerpc::BindAck::ACCEPTANCE
188
- allow(RubySMB::Dcerpc::BindAck).to receive(:read).and_return(bind_ack_packet)
268
+ allow(ioctl_response).to receive(:status_code).and_return(WindowsError::NTStatus::STATUS_PENDING)
269
+ allow(pipe).to receive(:sleep)
270
+ allow(dispatcher).to receive(:recv_packet).and_return(ioctl_raw_response2)
271
+ allow(RubySMB::SMB2::Packet::IoctlResponse).to receive(:read).with(ioctl_raw_response2).and_return(ioctl_response2)
272
+ allow(ioctl_response2).to receive_messages(
273
+ :valid? => true,
274
+ :output_data => raw_data,
275
+ :status_code => WindowsError::NTStatus::STATUS_SUCCESS
276
+ )
277
+ end
278
+
279
+ it 'waits 1 second' do
280
+ pipe.ioctl_send_recv(dcerpc_request, options)
281
+ expect(pipe).to have_received(:sleep).with(1)
189
282
  end
190
283
 
191
- it 'creates a Bind packet' do
192
- expect(RubySMB::Dcerpc::Bind).to receive(:new).with(options).and_return(bind_packet)
193
- pipe.bind(options)
284
+ it 'calls dispatcher #recv_packet' do
285
+ pipe.ioctl_send_recv(dcerpc_request, options)
286
+ expect(dispatcher).to have_received(:recv_packet)
194
287
  end
195
288
 
196
- it 'writes to the named pipe' do
197
- expect(pipe).to receive(:write).with(data: bind_packet.to_binary_s)
198
- pipe.bind(options)
289
+ it 'creates an IoctlResponse packet from the response' do
290
+ pipe.ioctl_send_recv(dcerpc_request, options)
291
+ expect(RubySMB::SMB2::Packet::IoctlResponse).to have_received(:read).with(ioctl_raw_response2)
199
292
  end
200
293
 
201
- it 'reads the socket' do
202
- expect(pipe).to receive(:read)
203
- pipe.bind(options)
294
+ context 'when the response is not an IoctlResponse packet' do
295
+ it 'raises an InvalidPacket exception' do
296
+ allow(ioctl_response2).to receive_message_chain(:smb2_header, :protocol)
297
+ allow(ioctl_response2).to receive_message_chain(:smb2_header, :command)
298
+ allow(ioctl_response2).to receive(:valid?).and_return(false)
299
+ expect { pipe.ioctl_send_recv(dcerpc_request, options) }.to raise_error(RubySMB::Error::InvalidPacket)
300
+ end
204
301
  end
302
+ end
205
303
 
206
- it 'creates a BindAck packet from the response' do
207
- raw_response = RubySMB::Dcerpc::BindAck.new.to_binary_s
208
- allow(pipe).to receive(:read).and_return(raw_response)
209
- expect(RubySMB::Dcerpc::BindAck).to receive(:read).with(raw_response).and_return(bind_ack_packet)
210
- pipe.bind(options)
304
+ context 'when the response status code is not STATUS_SUCCESS or STATUS_BUFFER_OVERFLOW' do
305
+ it 'raises an UnexpectedStatusCode exception' do
306
+ allow(ioctl_response).to receive(:status_code).and_return(WindowsError::NTStatus::STATUS_INVALID_HANDLE)
307
+ expect { pipe.ioctl_send_recv(dcerpc_request, options) }.to raise_error(RubySMB::Error::UnexpectedStatusCode)
211
308
  end
309
+ end
212
310
 
213
- it 'raises the expected exception when an invalid packet is received' do
214
- allow(RubySMB::Dcerpc::BindAck).to receive(:read).and_raise(IOError)
215
- expect { pipe.bind(options) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
311
+ context 'when the response status code is STATUS_SUCCESS' do
312
+ it 'does not raise any exception' do
313
+ expect { pipe.ioctl_send_recv(dcerpc_request, options)}.not_to raise_error
216
314
  end
217
315
 
218
- it 'raises the expected exception when it is not a BindAck packet' do
219
- response = RubySMB::Dcerpc::Bind.new
220
- allow(RubySMB::Dcerpc::BindAck).to receive(:read).and_return(response)
221
- expect { pipe.bind(options) }.to raise_error(RubySMB::Dcerpc::Error::BindError)
316
+ it 'creates a DCERPC Response packet from the response' do
317
+ pipe.ioctl_send_recv(dcerpc_request, options)
318
+ expect(RubySMB::Dcerpc::Response).to have_received(:read).with(raw_data)
222
319
  end
223
320
 
224
- it 'raises an exception when no result is returned' do
225
- bind_ack_packet.p_result_list.n_results = 0
226
- expect { pipe.bind(options) }.to raise_error(RubySMB::Dcerpc::Error::BindError)
321
+ context 'when an IOError occurs while parsing the DCERPC response' do
322
+ it 'raises an InvalidPacket exception' do
323
+ allow(RubySMB::Dcerpc::Response).to receive(:read).and_raise(IOError)
324
+ expect { pipe.ioctl_send_recv(dcerpc_request, options) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
325
+ end
227
326
  end
228
327
 
229
- it 'raises an exception when result is not ACCEPTANCE' do
230
- bind_ack_packet.p_result_list.p_results[0].result = RubySMB::Dcerpc::BindAck::USER_REJECTION
231
- expect { pipe.bind(options) }.to raise_error(RubySMB::Dcerpc::Error::BindError)
328
+ context 'when the response is not a DCERPC Response packet' do
329
+ it 'raises an InvalidPacket exception' do
330
+ allow(dcerpc_response).to receive_message_chain(:pdu_header, :ptype => RubySMB::Dcerpc::PTypes::FAULT)
331
+ expect { pipe.ioctl_send_recv(dcerpc_request, options) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
332
+ end
232
333
  end
233
334
 
234
- it 'returns the expected BindAck packet' do
235
- expect(pipe.bind(options)).to eq(bind_ack_packet)
335
+ it 'returns the expected stub data' do
336
+ expect(pipe.ioctl_send_recv(dcerpc_request, options)).to eq(result)
236
337
  end
237
338
  end
238
339
 
239
- describe '#request' do
240
- let(:options) { { host: '1.2.3.4' } }
241
- let(:opnum) { RubySMB::Dcerpc::Srvsvc::NET_SHARE_ENUM_ALL }
242
- let(:req_packet) { RubySMB::Dcerpc::Request.new({ :opnum => opnum }, options) }
243
- let(:ioctl_response) { RubySMB::SMB2::Packet::IoctlResponse.new }
244
- let(:res_packet) { RubySMB::Dcerpc::Response.new }
245
-
340
+ context 'when the response status code is STATUS_BUFFER_OVERFLOW' do
341
+ let(:data_count) { 100 }
342
+ let(:added_raw_data) { double('Added raw data') }
246
343
  before :example do
247
- allow(RubySMB::Dcerpc::Request).to receive(:new).and_return(req_packet)
248
- allow(pipe).to receive(:ioctl_send_recv).and_return(ioctl_response)
249
- allow(RubySMB::Dcerpc::Response).to receive(:read).and_return(res_packet)
344
+ allow(ioctl_response).to receive(:status_code).and_return(WindowsError::NTStatus::STATUS_BUFFER_OVERFLOW)
345
+ allow(ioctl_response).to receive(:output_count).and_return(data_count)
346
+ allow(pipe).to receive(:read).and_return(added_raw_data)
347
+ allow(raw_data).to receive(:<<)
348
+ allow(dcerpc_response).to receive_message_chain(:pdu_header, :pfc_flags, :first_frag => 1)
349
+ allow(dcerpc_response).to receive_message_chain(:pdu_header, :pfc_flags, :last_frag => 1)
250
350
  end
251
351
 
252
- it 'creates a Request packet' do
253
- expect(RubySMB::Dcerpc::Request).to receive(:new).and_return(req_packet)
254
- pipe.request(opnum, options)
352
+ it 'does not raise any exception' do
353
+ expect { pipe.ioctl_send_recv(dcerpc_request, options) }.not_to raise_error
255
354
  end
256
355
 
257
- it 'calls #ioctl_send_recv' do
258
- expect(pipe).to receive(:ioctl_send_recv).with(req_packet, options)
259
- pipe.request(opnum, options)
356
+ it 'reads the expected number of bytes and concatenate it the first response raw data' do
357
+ pipe.ioctl_send_recv(dcerpc_request, options)
358
+ expect(pipe).to have_received(:read).with(bytes: tree.client.max_buffer_size - data_count)
359
+ expect(raw_data).to have_received(:<<).with(added_raw_data)
260
360
  end
261
361
 
262
- it 'creates a DCERPC Response packet from the response' do
263
- expect(RubySMB::Dcerpc::Response).to receive(:read).with(ioctl_response.output_data)
264
- pipe.request(opnum, options)
362
+ it 'creates a DCERPC Response packet from the updated raw data' do
363
+ pipe.ioctl_send_recv(dcerpc_request, options)
364
+ expect(RubySMB::Dcerpc::Response).to have_received(:read).with(raw_data)
265
365
  end
266
366
 
267
- it 'raises the expected exception when an invalid packet is received' do
268
- allow(RubySMB::Dcerpc::Response).to receive(:read).and_raise(IOError)
269
- expect { pipe.request(opnum, options) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
367
+ context 'when an IOError occurs while parsing the DCERPC response' do
368
+ it 'raises an InvalidPacket exception' do
369
+ allow(RubySMB::Dcerpc::Response).to receive(:read).and_raise(IOError)
370
+ expect { pipe.ioctl_send_recv(dcerpc_request, options) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
371
+ end
270
372
  end
271
373
 
272
- it 'raises the expected exception when it is not a BindAck packet' do
273
- response = RubySMB::Dcerpc::Request.new
274
- allow(RubySMB::Dcerpc::Response).to receive(:read).and_return(response)
275
- expect { pipe.request(opnum, options) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
374
+ context 'when the response is not a DCERPC Response packet' do
375
+ it 'raises an InvalidPacket exception' do
376
+ allow(dcerpc_response).to receive_message_chain(:pdu_header, :ptype => RubySMB::Dcerpc::PTypes::FAULT)
377
+ expect { pipe.ioctl_send_recv(dcerpc_request, options) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
378
+ end
276
379
  end
277
380
 
278
- it 'returns the expected DCERPC Response' do
279
- expect(pipe.request(opnum, options)).to eq(res_packet)
381
+ context 'when the response is not the first fragment' do
382
+ it 'raises an InvalidPacket exception' do
383
+ allow(dcerpc_response).to receive_message_chain(:pdu_header, :pfc_flags, :first_frag => 0)
384
+ expect { pipe.ioctl_send_recv(dcerpc_request, options) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
385
+ end
280
386
  end
281
- end
282
387
 
283
- describe '#ioctl_send_recv' do
284
- let(:action) { RubySMB::Dcerpc::Request.new({ :opnum => RubySMB::Dcerpc::Srvsvc::NET_SHARE_ENUM_ALL }, host: '1.2.3.4') }
285
- let(:options) { {} }
286
- let(:ioctl_request) { RubySMB::SMB2::Packet::IoctlRequest.new(options) }
287
- let(:ioctl_response) { RubySMB::SMB2::Packet::IoctlResponse.new }
388
+ context 'when the response is the last fragment' do
389
+ it 'only reads the pipe once' do
390
+ pipe.ioctl_send_recv(dcerpc_request, options)
391
+ expect(RubySMB::Dcerpc::Response).to have_received(:read).once
392
+ end
288
393
 
289
- before :example do
290
- allow(client).to receive(:send_recv).and_return(ioctl_response.to_binary_s)
394
+ it 'returns the expected stub data' do
395
+ expect(pipe.ioctl_send_recv(dcerpc_request, options)).to eq(result)
396
+ end
291
397
  end
292
398
 
293
- it 'calls #set_header_fields' do
294
- expect(pipe).to receive(:set_header_fields).with(ioctl_request).and_call_original
295
- pipe.ioctl_send_recv(action, options)
296
- end
399
+ context 'when the response is not the last fragment' do
400
+ let(:raw_data2) { double('Raw data #2') }
401
+ let(:dcerpc_response2) { double('DCERPC Response #2') }
402
+ let(:result2) { 'Result #2' }
403
+ before :example do
404
+ allow(dcerpc_response).to receive_message_chain(:pdu_header, :pfc_flags, :last_frag => 0)
405
+ allow(pipe).to receive(:read).with(bytes: tree.client.max_buffer_size).and_return(raw_data2)
406
+ allow(RubySMB::Dcerpc::Response).to receive(:read).with(raw_data2).and_return(dcerpc_response2)
407
+ allow(dcerpc_response2).to receive_message_chain(:pdu_header, :ptype => RubySMB::Dcerpc::PTypes::RESPONSE)
408
+ allow(dcerpc_response2).to receive_message_chain(:pdu_header, :pfc_flags, :last_frag => 1)
409
+ allow(dcerpc_response2).to receive(:stub).and_return(result2)
410
+ end
297
411
 
298
- it 'calls Client #send_recv with the expected request' do
299
- expect(client).to receive(:send_recv) do |req|
300
- expect(req.ctl_code).to eq(0x0011C017)
301
- expect(req.flags.is_fsctl).to eq(0x00000001)
302
- expect(req.buffer).to eq(action.to_binary_s)
303
- ioctl_response.to_binary_s
412
+ it 'reads the expected number of bytes' do
413
+ pipe.ioctl_send_recv(dcerpc_request, options)
414
+ expect(pipe).to have_received(:read).with(bytes: tree.client.max_buffer_size)
304
415
  end
305
- pipe.ioctl_send_recv(action, options)
306
- end
307
416
 
308
- it 'creates a IoctlResponse packet from the response' do
309
- expect(RubySMB::SMB2::Packet::IoctlResponse).to receive(:read).with(ioctl_response.to_binary_s).and_call_original
310
- pipe.ioctl_send_recv(action, options)
311
- end
417
+ it 'creates a DCERPC Response packet from the new raw data' do
418
+ pipe.ioctl_send_recv(dcerpc_request, options)
419
+ expect(RubySMB::Dcerpc::Response).to have_received(:read).with(raw_data2)
420
+ end
312
421
 
313
- it 'raises the expected exception when it is not a valid packet' do
314
- ioctl_response.smb2_header.command = RubySMB::SMB2::Commands::LOGOFF
315
- allow(RubySMB::SMB2::Packet::IoctlResponse).to receive(:read).and_return(ioctl_response)
316
- expect { pipe.ioctl_send_recv(action, options) }.to raise_error(RubySMB::Error::InvalidPacket)
317
- end
422
+ context 'when an IOError occurs while parsing the new DCERPC response' do
423
+ it 'raises an InvalidPacket exception' do
424
+ allow(RubySMB::Dcerpc::Response).to receive(:read).with(raw_data2).and_raise(IOError)
425
+ expect { pipe.ioctl_send_recv(dcerpc_request, options) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
426
+ end
427
+ end
318
428
 
319
- it 'raises the expected exception when the status code is not STATUS_SUCCESS' do
320
- ioctl_response_packet = RubySMB::SMB2::Packet::IoctlResponse.new
321
- ioctl_response_packet.smb2_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_HANDLE.value
322
- allow(RubySMB::SMB2::Packet::IoctlResponse).to receive(:read).with(ioctl_response.to_binary_s).and_return(ioctl_response_packet)
323
- expect { pipe.ioctl_send_recv(action, options) }.to raise_error(RubySMB::Error::UnexpectedStatusCode)
324
- end
429
+ context 'when the new response is not a DCERPC Response packet' do
430
+ it 'raises an InvalidPacket exception' do
431
+ allow(dcerpc_response2).to receive_message_chain(:pdu_header, :ptype => RubySMB::Dcerpc::PTypes::FAULT)
432
+ expect { pipe.ioctl_send_recv(dcerpc_request, options) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
433
+ end
434
+ end
325
435
 
326
- it 'returns the expected DCERPC Response' do
327
- expect(pipe.ioctl_send_recv(action, options)).to eq(ioctl_response)
436
+ it 'returns the expected stub data' do
437
+ expect(pipe.ioctl_send_recv(dcerpc_request, options)).to eq(result)
438
+ end
328
439
  end
329
440
  end
330
441
  end
331
- end
332
442
 
443
+ end