ruby_smb 1.0.4 → 2.0.2

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 (130) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.travis.yml +3 -2
  5. data/Gemfile +6 -2
  6. data/README.md +35 -47
  7. data/examples/enum_registry_key.rb +28 -0
  8. data/examples/enum_registry_values.rb +30 -0
  9. data/examples/negotiate.rb +51 -8
  10. data/examples/pipes.rb +2 -1
  11. data/examples/read_file_encryption.rb +56 -0
  12. data/examples/read_registry_key_value.rb +32 -0
  13. data/lib/ruby_smb.rb +4 -1
  14. data/lib/ruby_smb/client.rb +207 -18
  15. data/lib/ruby_smb/client/authentication.rb +27 -8
  16. data/lib/ruby_smb/client/encryption.rb +62 -0
  17. data/lib/ruby_smb/client/negotiation.rb +153 -12
  18. data/lib/ruby_smb/client/signing.rb +19 -0
  19. data/lib/ruby_smb/client/tree_connect.rb +4 -4
  20. data/lib/ruby_smb/client/utils.rb +8 -7
  21. data/lib/ruby_smb/client/winreg.rb +46 -0
  22. data/lib/ruby_smb/crypto.rb +30 -0
  23. data/lib/ruby_smb/dcerpc.rb +38 -0
  24. data/lib/ruby_smb/dcerpc/bind.rb +2 -2
  25. data/lib/ruby_smb/dcerpc/bind_ack.rb +2 -2
  26. data/lib/ruby_smb/dcerpc/error.rb +3 -0
  27. data/lib/ruby_smb/dcerpc/ndr.rb +95 -16
  28. data/lib/ruby_smb/dcerpc/pdu_header.rb +1 -1
  29. data/lib/ruby_smb/dcerpc/request.rb +28 -9
  30. data/lib/ruby_smb/dcerpc/rrp_unicode_string.rb +35 -0
  31. data/lib/ruby_smb/dcerpc/srvsvc.rb +10 -0
  32. data/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all.rb +9 -0
  33. data/lib/ruby_smb/dcerpc/winreg.rb +340 -0
  34. data/lib/ruby_smb/dcerpc/winreg/close_key_request.rb +24 -0
  35. data/lib/ruby_smb/dcerpc/winreg/close_key_response.rb +27 -0
  36. data/lib/ruby_smb/dcerpc/winreg/enum_key_request.rb +45 -0
  37. data/lib/ruby_smb/dcerpc/winreg/enum_key_response.rb +42 -0
  38. data/lib/ruby_smb/dcerpc/winreg/enum_value_request.rb +39 -0
  39. data/lib/ruby_smb/dcerpc/winreg/enum_value_response.rb +36 -0
  40. data/lib/ruby_smb/dcerpc/winreg/open_key_request.rb +34 -0
  41. data/lib/ruby_smb/dcerpc/winreg/open_key_response.rb +25 -0
  42. data/lib/ruby_smb/dcerpc/winreg/open_root_key_request.rb +43 -0
  43. data/lib/ruby_smb/dcerpc/winreg/open_root_key_response.rb +35 -0
  44. data/lib/ruby_smb/dcerpc/winreg/query_info_key_request.rb +27 -0
  45. data/lib/ruby_smb/dcerpc/winreg/query_info_key_response.rb +40 -0
  46. data/lib/ruby_smb/dcerpc/winreg/query_value_request.rb +39 -0
  47. data/lib/ruby_smb/dcerpc/winreg/query_value_response.rb +57 -0
  48. data/lib/ruby_smb/dcerpc/winreg/regsam.rb +40 -0
  49. data/lib/ruby_smb/dispatcher/socket.rb +4 -3
  50. data/lib/ruby_smb/error.rb +28 -1
  51. data/lib/ruby_smb/smb1/commands.rb +1 -1
  52. data/lib/ruby_smb/smb1/file.rb +6 -4
  53. data/lib/ruby_smb/smb1/packet/empty_packet.rb +4 -2
  54. data/lib/ruby_smb/smb1/packet/session_setup_legacy_request.rb +1 -1
  55. data/lib/ruby_smb/smb1/packet/session_setup_legacy_response.rb +2 -2
  56. data/lib/ruby_smb/smb1/packet/session_setup_request.rb +1 -1
  57. data/lib/ruby_smb/smb1/packet/session_setup_response.rb +2 -2
  58. data/lib/ruby_smb/smb1/packet/write_andx_request.rb +1 -1
  59. data/lib/ruby_smb/smb1/pipe.rb +79 -3
  60. data/lib/ruby_smb/smb1/tree.rb +12 -3
  61. data/lib/ruby_smb/smb2/bit_field/session_flags.rb +2 -1
  62. data/lib/ruby_smb/smb2/bit_field/share_flags.rb +6 -4
  63. data/lib/ruby_smb/smb2/file.rb +25 -43
  64. data/lib/ruby_smb/smb2/negotiate_context.rb +108 -0
  65. data/lib/ruby_smb/smb2/packet.rb +2 -0
  66. data/lib/ruby_smb/smb2/packet/compression_transform_header.rb +41 -0
  67. data/lib/ruby_smb/smb2/packet/error_packet.rb +9 -4
  68. data/lib/ruby_smb/smb2/packet/negotiate_request.rb +51 -14
  69. data/lib/ruby_smb/smb2/packet/negotiate_response.rb +50 -4
  70. data/lib/ruby_smb/smb2/packet/transform_header.rb +84 -0
  71. data/lib/ruby_smb/smb2/packet/tree_connect_request.rb +92 -6
  72. data/lib/ruby_smb/smb2/packet/tree_connect_response.rb +8 -26
  73. data/lib/ruby_smb/smb2/pipe.rb +77 -3
  74. data/lib/ruby_smb/smb2/smb2_header.rb +1 -1
  75. data/lib/ruby_smb/smb2/tree.rb +23 -17
  76. data/lib/ruby_smb/version.rb +1 -1
  77. data/ruby_smb.gemspec +5 -3
  78. data/spec/lib/ruby_smb/client_spec.rb +1441 -61
  79. data/spec/lib/ruby_smb/crypto_spec.rb +25 -0
  80. data/spec/lib/ruby_smb/dcerpc/bind_ack_spec.rb +2 -2
  81. data/spec/lib/ruby_smb/dcerpc/bind_spec.rb +2 -2
  82. data/spec/lib/ruby_smb/dcerpc/ndr_spec.rb +410 -0
  83. data/spec/lib/ruby_smb/dcerpc/request_spec.rb +50 -7
  84. data/spec/lib/ruby_smb/dcerpc/rrp_unicode_string_spec.rb +98 -0
  85. data/spec/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all_spec.rb +13 -0
  86. data/spec/lib/ruby_smb/dcerpc/srvsvc_spec.rb +60 -0
  87. data/spec/lib/ruby_smb/dcerpc/winreg/close_key_request_spec.rb +28 -0
  88. data/spec/lib/ruby_smb/dcerpc/winreg/close_key_response_spec.rb +36 -0
  89. data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_request_spec.rb +108 -0
  90. data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_response_spec.rb +97 -0
  91. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_request_spec.rb +94 -0
  92. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_response_spec.rb +82 -0
  93. data/spec/lib/ruby_smb/dcerpc/winreg/open_key_request_spec.rb +74 -0
  94. data/spec/lib/ruby_smb/dcerpc/winreg/open_key_response_spec.rb +35 -0
  95. data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_request_spec.rb +90 -0
  96. data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_response_spec.rb +38 -0
  97. data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_request_spec.rb +39 -0
  98. data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_response_spec.rb +113 -0
  99. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_request_spec.rb +88 -0
  100. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_response_spec.rb +150 -0
  101. data/spec/lib/ruby_smb/dcerpc/winreg/regsam_spec.rb +32 -0
  102. data/spec/lib/ruby_smb/dcerpc/winreg_spec.rb +710 -0
  103. data/spec/lib/ruby_smb/dcerpc_spec.rb +81 -0
  104. data/spec/lib/ruby_smb/dispatcher/socket_spec.rb +2 -2
  105. data/spec/lib/ruby_smb/error_spec.rb +59 -0
  106. data/spec/lib/ruby_smb/smb1/file_spec.rb +9 -1
  107. data/spec/lib/ruby_smb/smb1/packet/empty_packet_spec.rb +10 -0
  108. data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_request_spec.rb +2 -2
  109. data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_response_spec.rb +2 -2
  110. data/spec/lib/ruby_smb/smb1/packet/session_setup_request_spec.rb +2 -2
  111. data/spec/lib/ruby_smb/smb1/packet/session_setup_response_spec.rb +1 -1
  112. data/spec/lib/ruby_smb/smb1/pipe_spec.rb +210 -148
  113. data/spec/lib/ruby_smb/smb2/bit_field/session_flags_spec.rb +9 -0
  114. data/spec/lib/ruby_smb/smb2/bit_field/share_flags_spec.rb +27 -0
  115. data/spec/lib/ruby_smb/smb2/file_spec.rb +86 -62
  116. data/spec/lib/ruby_smb/smb2/negotiate_context_spec.rb +332 -0
  117. data/spec/lib/ruby_smb/smb2/packet/compression_transform_header_spec.rb +108 -0
  118. data/spec/lib/ruby_smb/smb2/packet/error_packet_spec.rb +29 -2
  119. data/spec/lib/ruby_smb/smb2/packet/negotiate_request_spec.rb +138 -3
  120. data/spec/lib/ruby_smb/smb2/packet/negotiate_response_spec.rb +120 -2
  121. data/spec/lib/ruby_smb/smb2/packet/transform_header_spec.rb +220 -0
  122. data/spec/lib/ruby_smb/smb2/packet/tree_connect_request_spec.rb +339 -9
  123. data/spec/lib/ruby_smb/smb2/packet/tree_connect_response_spec.rb +3 -30
  124. data/spec/lib/ruby_smb/smb2/pipe_spec.rb +220 -149
  125. data/spec/lib/ruby_smb/smb2/smb2_header_spec.rb +2 -2
  126. data/spec/lib/ruby_smb/smb2/tree_spec.rb +53 -8
  127. metadata +187 -81
  128. metadata.gz.sig +0 -0
  129. data/lib/ruby_smb/smb1/dcerpc.rb +0 -72
  130. data/lib/ruby_smb/smb2/dcerpc.rb +0 -75
@@ -30,35 +30,8 @@ RSpec.describe RubySMB::SMB2::Packet::TreeConnectResponse do
30
30
  end
31
31
  end
32
32
 
33
- describe '#is_directory?' do
34
- it 'returns true if #share_type is 0x01' do
35
- packet.share_type = 0x01
36
- expect(packet.is_directory?).to be true
37
- end
38
-
39
- it 'returns false if #share_type is not 0x01' do
40
- packet.share_type = 0x02
41
- expect(packet.is_directory?).to be false
42
- end
43
- end
44
-
45
- describe '#access_rights' do
46
- it 'is a DirectoryAccessMask if the Tree is a directory' do
47
- allow(packet).to receive(:is_directory?).and_return(true)
48
- expect(packet.access_rights).to be_a RubySMB::SMB2::BitField::DirectoryAccessMask
49
- end
50
-
51
- it 'is a FileAccessMask if the Tree is not a directory' do
52
- allow(packet).to receive(:is_directory?).and_return(false)
53
- expect(packet.access_rights).to be_a RubySMB::SMB2::BitField::FileAccessMask
54
- end
55
-
56
- context 'when it is not a valid FileAccessMask' do
57
- it 'raises an InvalidBitField exception' do
58
- allow(packet).to receive(:is_directory?).and_return(false)
59
- allow(RubySMB::SMB2::BitField::FileAccessMask).to receive(:read).and_raise(IOError)
60
- expect { packet.access_rights }.to raise_error(RubySMB::Error::InvalidBitField)
61
- end
62
- end
33
+ it 'reads binary data as expected' do
34
+ data = described_class.new
35
+ expect(described_class.read(data.to_binary_s)).to eq(data)
63
36
  end
64
37
  end
@@ -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,277 @@ 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)
175
176
  end
176
177
 
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 }
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
181
183
 
182
- 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)
189
- end
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
190
188
 
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)
194
- end
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
195
194
 
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)
199
- end
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)
243
+ end
244
+
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
200
254
 
201
- it 'reads the socket' do
202
- expect(pipe).to receive(:read)
203
- pipe.bind(options)
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)
204
261
  end
262
+ end
205
263
 
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)
264
+ context 'when the response status code is not STATUS_SUCCESS or STATUS_BUFFER_OVERFLOW' do
265
+ it 'raises an UnexpectedStatusCode exception' do
266
+ allow(ioctl_response).to receive(:status_code).and_return(WindowsError::NTStatus::STATUS_INVALID_HANDLE)
267
+ expect { pipe.ioctl_send_recv(dcerpc_request, options) }.to raise_error(RubySMB::Error::UnexpectedStatusCode)
211
268
  end
269
+ end
212
270
 
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)
271
+ context 'when the response status code is STATUS_SUCCESS' do
272
+ it 'does not raise any exception' do
273
+ expect { pipe.ioctl_send_recv(dcerpc_request, options)}.not_to raise_error
216
274
  end
217
275
 
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)
276
+ it 'creates a DCERPC Response packet from the response' do
277
+ pipe.ioctl_send_recv(dcerpc_request, options)
278
+ expect(RubySMB::Dcerpc::Response).to have_received(:read).with(raw_data)
222
279
  end
223
280
 
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)
281
+ context 'when an IOError occurs while parsing the DCERPC response' do
282
+ it 'raises an InvalidPacket exception' do
283
+ allow(RubySMB::Dcerpc::Response).to receive(:read).and_raise(IOError)
284
+ expect { pipe.ioctl_send_recv(dcerpc_request, options) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
285
+ end
227
286
  end
228
287
 
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)
288
+ context 'when the response is not a DCERPC Response packet' do
289
+ it 'raises an InvalidPacket exception' do
290
+ allow(dcerpc_response).to receive_message_chain(:pdu_header, :ptype => RubySMB::Dcerpc::PTypes::FAULT)
291
+ expect { pipe.ioctl_send_recv(dcerpc_request, options) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
292
+ end
232
293
  end
233
294
 
234
- it 'returns the expected BindAck packet' do
235
- expect(pipe.bind(options)).to eq(bind_ack_packet)
295
+ it 'returns the expected stub data' do
296
+ expect(pipe.ioctl_send_recv(dcerpc_request, options)).to eq(result)
236
297
  end
237
298
  end
238
299
 
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
-
300
+ context 'when the response status code is STATUS_BUFFER_OVERFLOW' do
301
+ let(:data_count) { 100 }
302
+ let(:added_raw_data) { double('Added raw data') }
246
303
  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)
304
+ allow(ioctl_response).to receive(:status_code).and_return(WindowsError::NTStatus::STATUS_BUFFER_OVERFLOW)
305
+ allow(ioctl_response).to receive(:output_count).and_return(data_count)
306
+ allow(pipe).to receive(:read).and_return(added_raw_data)
307
+ allow(raw_data).to receive(:<<)
308
+ allow(dcerpc_response).to receive_message_chain(:pdu_header, :pfc_flags, :first_frag => 1)
309
+ allow(dcerpc_response).to receive_message_chain(:pdu_header, :pfc_flags, :last_frag => 1)
250
310
  end
251
311
 
252
- it 'creates a Request packet' do
253
- expect(RubySMB::Dcerpc::Request).to receive(:new).and_return(req_packet)
254
- pipe.request(opnum, options)
312
+ it 'does not raise any exception' do
313
+ expect { pipe.ioctl_send_recv(dcerpc_request, options) }.not_to raise_error
255
314
  end
256
315
 
257
- it 'calls #ioctl_send_recv' do
258
- expect(pipe).to receive(:ioctl_send_recv).with(req_packet, options)
259
- pipe.request(opnum, options)
316
+ it 'reads the expected number of bytes and concatenate it the first response raw data' do
317
+ pipe.ioctl_send_recv(dcerpc_request, options)
318
+ expect(pipe).to have_received(:read).with(bytes: tree.client.max_buffer_size - data_count)
319
+ expect(raw_data).to have_received(:<<).with(added_raw_data)
260
320
  end
261
321
 
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)
322
+ it 'creates a DCERPC Response packet from the updated raw data' do
323
+ pipe.ioctl_send_recv(dcerpc_request, options)
324
+ expect(RubySMB::Dcerpc::Response).to have_received(:read).with(raw_data)
265
325
  end
266
326
 
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)
327
+ context 'when an IOError occurs while parsing the DCERPC response' do
328
+ it 'raises an InvalidPacket exception' do
329
+ allow(RubySMB::Dcerpc::Response).to receive(:read).and_raise(IOError)
330
+ expect { pipe.ioctl_send_recv(dcerpc_request, options) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
331
+ end
270
332
  end
271
333
 
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)
334
+ context 'when the response is not a DCERPC Response packet' do
335
+ it 'raises an InvalidPacket exception' do
336
+ allow(dcerpc_response).to receive_message_chain(:pdu_header, :ptype => RubySMB::Dcerpc::PTypes::FAULT)
337
+ expect { pipe.ioctl_send_recv(dcerpc_request, options) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
338
+ end
276
339
  end
277
340
 
278
- it 'returns the expected DCERPC Response' do
279
- expect(pipe.request(opnum, options)).to eq(res_packet)
341
+ context 'when the response is not the first fragment' do
342
+ it 'raises an InvalidPacket exception' do
343
+ allow(dcerpc_response).to receive_message_chain(:pdu_header, :pfc_flags, :first_frag => 0)
344
+ expect { pipe.ioctl_send_recv(dcerpc_request, options) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
345
+ end
280
346
  end
281
- end
282
347
 
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 }
348
+ context 'when the response is the last fragment' do
349
+ it 'only reads the pipe once' do
350
+ pipe.ioctl_send_recv(dcerpc_request, options)
351
+ expect(RubySMB::Dcerpc::Response).to have_received(:read).once
352
+ end
288
353
 
289
- before :example do
290
- allow(client).to receive(:send_recv).and_return(ioctl_response.to_binary_s)
354
+ it 'returns the expected stub data' do
355
+ expect(pipe.ioctl_send_recv(dcerpc_request, options)).to eq(result)
356
+ end
291
357
  end
292
358
 
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
359
+ context 'when the response is not the last fragment' do
360
+ let(:raw_data2) { double('Raw data #2') }
361
+ let(:dcerpc_response2) { double('DCERPC Response #2') }
362
+ let(:result2) { 'Result #2' }
363
+ before :example do
364
+ allow(dcerpc_response).to receive_message_chain(:pdu_header, :pfc_flags, :last_frag => 0)
365
+ allow(pipe).to receive(:read).with(bytes: tree.client.max_buffer_size).and_return(raw_data2)
366
+ allow(RubySMB::Dcerpc::Response).to receive(:read).with(raw_data2).and_return(dcerpc_response2)
367
+ allow(dcerpc_response2).to receive_message_chain(:pdu_header, :ptype => RubySMB::Dcerpc::PTypes::RESPONSE)
368
+ allow(dcerpc_response2).to receive_message_chain(:pdu_header, :pfc_flags, :last_frag => 1)
369
+ allow(dcerpc_response2).to receive(:stub).and_return(result2)
370
+ end
297
371
 
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
372
+ it 'reads the expected number of bytes' do
373
+ pipe.ioctl_send_recv(dcerpc_request, options)
374
+ expect(pipe).to have_received(:read).with(bytes: tree.client.max_buffer_size)
304
375
  end
305
- pipe.ioctl_send_recv(action, options)
306
- end
307
376
 
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
377
+ it 'creates a DCERPC Response packet from the new raw data' do
378
+ pipe.ioctl_send_recv(dcerpc_request, options)
379
+ expect(RubySMB::Dcerpc::Response).to have_received(:read).with(raw_data2)
380
+ end
312
381
 
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
382
+ context 'when an IOError occurs while parsing the new DCERPC response' do
383
+ it 'raises an InvalidPacket exception' do
384
+ allow(RubySMB::Dcerpc::Response).to receive(:read).with(raw_data2).and_raise(IOError)
385
+ expect { pipe.ioctl_send_recv(dcerpc_request, options) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
386
+ end
387
+ end
318
388
 
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
389
+ context 'when the new response is not a DCERPC Response packet' do
390
+ it 'raises an InvalidPacket exception' do
391
+ allow(dcerpc_response2).to receive_message_chain(:pdu_header, :ptype => RubySMB::Dcerpc::PTypes::FAULT)
392
+ expect { pipe.ioctl_send_recv(dcerpc_request, options) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
393
+ end
394
+ end
325
395
 
326
- it 'returns the expected DCERPC Response' do
327
- expect(pipe.ioctl_send_recv(action, options)).to eq(ioctl_response)
396
+ it 'returns the expected stub data' do
397
+ expect(pipe.ioctl_send_recv(dcerpc_request, options)).to eq(result)
398
+ end
328
399
  end
329
400
  end
330
401
  end
331
- end
332
402
 
403
+ end