ruby_smb 1.0.0 → 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 (171) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.travis.yml +4 -1
  4. data/README.md +35 -47
  5. data/examples/enum_registry_key.rb +28 -0
  6. data/examples/enum_registry_values.rb +30 -0
  7. data/examples/pipes.rb +2 -1
  8. data/examples/read_registry_key_value.rb +32 -0
  9. data/lib/ruby_smb/client/authentication.rb +43 -25
  10. data/lib/ruby_smb/client/echo.rb +20 -2
  11. data/lib/ruby_smb/client/negotiation.rb +27 -12
  12. data/lib/ruby_smb/client/tree_connect.rb +20 -14
  13. data/lib/ruby_smb/client/utils.rb +2 -2
  14. data/lib/ruby_smb/client/winreg.rb +46 -0
  15. data/lib/ruby_smb/client.rb +28 -4
  16. data/lib/ruby_smb/dcerpc/bind.rb +2 -2
  17. data/lib/ruby_smb/dcerpc/bind_ack.rb +2 -2
  18. data/lib/ruby_smb/dcerpc/error.rb +3 -0
  19. data/lib/ruby_smb/dcerpc/ndr.rb +95 -16
  20. data/lib/ruby_smb/dcerpc/pdu_header.rb +1 -1
  21. data/lib/ruby_smb/dcerpc/request.rb +28 -9
  22. data/lib/ruby_smb/dcerpc/rrp_unicode_string.rb +35 -0
  23. data/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all.rb +9 -0
  24. data/lib/ruby_smb/dcerpc/srvsvc.rb +10 -0
  25. data/lib/ruby_smb/dcerpc/winreg/close_key_request.rb +24 -0
  26. data/lib/ruby_smb/dcerpc/winreg/close_key_response.rb +27 -0
  27. data/lib/ruby_smb/dcerpc/winreg/enum_key_request.rb +45 -0
  28. data/lib/ruby_smb/dcerpc/winreg/enum_key_response.rb +42 -0
  29. data/lib/ruby_smb/dcerpc/winreg/enum_value_request.rb +39 -0
  30. data/lib/ruby_smb/dcerpc/winreg/enum_value_response.rb +36 -0
  31. data/lib/ruby_smb/dcerpc/winreg/open_key_request.rb +34 -0
  32. data/lib/ruby_smb/dcerpc/winreg/open_key_response.rb +25 -0
  33. data/lib/ruby_smb/dcerpc/winreg/open_root_key_request.rb +43 -0
  34. data/lib/ruby_smb/dcerpc/winreg/open_root_key_response.rb +35 -0
  35. data/lib/ruby_smb/dcerpc/winreg/query_info_key_request.rb +27 -0
  36. data/lib/ruby_smb/dcerpc/winreg/query_info_key_response.rb +40 -0
  37. data/lib/ruby_smb/dcerpc/winreg/query_value_request.rb +39 -0
  38. data/lib/ruby_smb/dcerpc/winreg/query_value_response.rb +57 -0
  39. data/lib/ruby_smb/dcerpc/winreg/regsam.rb +40 -0
  40. data/lib/ruby_smb/dcerpc/winreg.rb +340 -0
  41. data/lib/ruby_smb/dcerpc.rb +38 -0
  42. data/lib/ruby_smb/dispatcher/socket.rb +3 -1
  43. data/lib/ruby_smb/error.rb +40 -1
  44. data/lib/ruby_smb/generic_packet.rb +33 -4
  45. data/lib/ruby_smb/smb1/file.rb +67 -11
  46. data/lib/ruby_smb/smb1/packet/close_request.rb +2 -5
  47. data/lib/ruby_smb/smb1/packet/close_response.rb +2 -1
  48. data/lib/ruby_smb/smb1/packet/echo_request.rb +2 -4
  49. data/lib/ruby_smb/smb1/packet/echo_response.rb +2 -1
  50. data/lib/ruby_smb/smb1/packet/empty_packet.rb +10 -1
  51. data/lib/ruby_smb/smb1/packet/logoff_request.rb +2 -4
  52. data/lib/ruby_smb/smb1/packet/logoff_response.rb +2 -1
  53. data/lib/ruby_smb/smb1/packet/negotiate_request.rb +2 -5
  54. data/lib/ruby_smb/smb1/packet/negotiate_response.rb +3 -7
  55. data/lib/ruby_smb/smb1/packet/negotiate_response_extended.rb +4 -4
  56. data/lib/ruby_smb/smb1/packet/nt_create_andx_request.rb +2 -4
  57. data/lib/ruby_smb/smb1/packet/nt_create_andx_response.rb +2 -1
  58. data/lib/ruby_smb/smb1/packet/nt_trans/create_request.rb +2 -1
  59. data/lib/ruby_smb/smb1/packet/nt_trans/create_response.rb +2 -1
  60. data/lib/ruby_smb/smb1/packet/nt_trans/request.rb +2 -4
  61. data/lib/ruby_smb/smb1/packet/nt_trans/response.rb +2 -1
  62. data/lib/ruby_smb/smb1/packet/read_andx_request.rb +2 -5
  63. data/lib/ruby_smb/smb1/packet/read_andx_response.rb +2 -1
  64. data/lib/ruby_smb/smb1/packet/session_setup_legacy_request.rb +2 -1
  65. data/lib/ruby_smb/smb1/packet/session_setup_legacy_response.rb +2 -1
  66. data/lib/ruby_smb/smb1/packet/session_setup_request.rb +2 -5
  67. data/lib/ruby_smb/smb1/packet/session_setup_response.rb +2 -1
  68. data/lib/ruby_smb/smb1/packet/trans/peek_nmpipe_request.rb +0 -1
  69. data/lib/ruby_smb/smb1/packet/trans/peek_nmpipe_response.rb +3 -2
  70. data/lib/ruby_smb/smb1/packet/trans/request.rb +2 -5
  71. data/lib/ruby_smb/smb1/packet/trans/response.rb +2 -1
  72. data/lib/ruby_smb/smb1/packet/trans/transact_nmpipe_request.rb +1 -1
  73. data/lib/ruby_smb/smb1/packet/trans/transact_nmpipe_response.rb +1 -1
  74. data/lib/ruby_smb/smb1/packet/trans2/find_first2_request.rb +2 -1
  75. data/lib/ruby_smb/smb1/packet/trans2/find_first2_response.rb +8 -2
  76. data/lib/ruby_smb/smb1/packet/trans2/find_next2_request.rb +2 -1
  77. data/lib/ruby_smb/smb1/packet/trans2/find_next2_response.rb +8 -2
  78. data/lib/ruby_smb/smb1/packet/trans2/open2_request.rb +2 -1
  79. data/lib/ruby_smb/smb1/packet/trans2/open2_response.rb +2 -1
  80. data/lib/ruby_smb/smb1/packet/trans2/request.rb +2 -4
  81. data/lib/ruby_smb/smb1/packet/trans2/request_secondary.rb +2 -4
  82. data/lib/ruby_smb/smb1/packet/trans2/response.rb +2 -1
  83. data/lib/ruby_smb/smb1/packet/trans2/set_file_information_request.rb +2 -1
  84. data/lib/ruby_smb/smb1/packet/trans2/set_file_information_response.rb +2 -1
  85. data/lib/ruby_smb/smb1/packet/tree_connect_request.rb +2 -4
  86. data/lib/ruby_smb/smb1/packet/tree_connect_response.rb +13 -3
  87. data/lib/ruby_smb/smb1/packet/tree_disconnect_request.rb +2 -4
  88. data/lib/ruby_smb/smb1/packet/tree_disconnect_response.rb +2 -1
  89. data/lib/ruby_smb/smb1/packet/write_andx_request.rb +2 -5
  90. data/lib/ruby_smb/smb1/packet/write_andx_response.rb +2 -1
  91. data/lib/ruby_smb/smb1/pipe.rb +86 -5
  92. data/lib/ruby_smb/smb1/tree.rb +40 -2
  93. data/lib/ruby_smb/smb2/file.rb +97 -1
  94. data/lib/ruby_smb/smb2/packet/close_request.rb +2 -4
  95. data/lib/ruby_smb/smb2/packet/close_response.rb +2 -1
  96. data/lib/ruby_smb/smb2/packet/create_request.rb +2 -4
  97. data/lib/ruby_smb/smb2/packet/create_response.rb +2 -1
  98. data/lib/ruby_smb/smb2/packet/echo_request.rb +2 -4
  99. data/lib/ruby_smb/smb2/packet/echo_response.rb +2 -1
  100. data/lib/ruby_smb/smb2/packet/error_packet.rb +15 -3
  101. data/lib/ruby_smb/smb2/packet/ioctl_request.rb +2 -5
  102. data/lib/ruby_smb/smb2/packet/ioctl_response.rb +2 -1
  103. data/lib/ruby_smb/smb2/packet/logoff_request.rb +2 -4
  104. data/lib/ruby_smb/smb2/packet/logoff_response.rb +2 -1
  105. data/lib/ruby_smb/smb2/packet/negotiate_request.rb +2 -5
  106. data/lib/ruby_smb/smb2/packet/negotiate_response.rb +2 -1
  107. data/lib/ruby_smb/smb2/packet/query_directory_request.rb +2 -4
  108. data/lib/ruby_smb/smb2/packet/query_directory_response.rb +8 -2
  109. data/lib/ruby_smb/smb2/packet/read_request.rb +2 -4
  110. data/lib/ruby_smb/smb2/packet/read_response.rb +2 -1
  111. data/lib/ruby_smb/smb2/packet/session_setup_request.rb +2 -5
  112. data/lib/ruby_smb/smb2/packet/session_setup_response.rb +2 -1
  113. data/lib/ruby_smb/smb2/packet/set_info_request.rb +2 -4
  114. data/lib/ruby_smb/smb2/packet/set_info_response.rb +2 -1
  115. data/lib/ruby_smb/smb2/packet/tree_connect_request.rb +2 -5
  116. data/lib/ruby_smb/smb2/packet/tree_connect_response.rb +8 -2
  117. data/lib/ruby_smb/smb2/packet/tree_disconnect_request.rb +2 -4
  118. data/lib/ruby_smb/smb2/packet/tree_disconnect_response.rb +2 -1
  119. data/lib/ruby_smb/smb2/packet/write_request.rb +2 -4
  120. data/lib/ruby_smb/smb2/packet/write_response.rb +2 -1
  121. data/lib/ruby_smb/smb2/pipe.rb +98 -11
  122. data/lib/ruby_smb/smb2/tree.rb +44 -6
  123. data/lib/ruby_smb/version.rb +1 -1
  124. data/lib/ruby_smb.rb +0 -1
  125. data/ruby_smb.gemspec +3 -3
  126. data/spec/lib/ruby_smb/client_spec.rb +271 -11
  127. data/spec/lib/ruby_smb/dcerpc/bind_ack_spec.rb +2 -2
  128. data/spec/lib/ruby_smb/dcerpc/bind_spec.rb +2 -2
  129. data/spec/lib/ruby_smb/dcerpc/ndr_spec.rb +410 -0
  130. data/spec/lib/ruby_smb/dcerpc/request_spec.rb +50 -7
  131. data/spec/lib/ruby_smb/dcerpc/rrp_unicode_string_spec.rb +98 -0
  132. data/spec/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all_spec.rb +13 -0
  133. data/spec/lib/ruby_smb/dcerpc/srvsvc_spec.rb +60 -0
  134. data/spec/lib/ruby_smb/dcerpc/winreg/close_key_request_spec.rb +28 -0
  135. data/spec/lib/ruby_smb/dcerpc/winreg/close_key_response_spec.rb +36 -0
  136. data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_request_spec.rb +108 -0
  137. data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_response_spec.rb +97 -0
  138. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_request_spec.rb +94 -0
  139. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_response_spec.rb +82 -0
  140. data/spec/lib/ruby_smb/dcerpc/winreg/open_key_request_spec.rb +74 -0
  141. data/spec/lib/ruby_smb/dcerpc/winreg/open_key_response_spec.rb +35 -0
  142. data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_request_spec.rb +90 -0
  143. data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_response_spec.rb +38 -0
  144. data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_request_spec.rb +39 -0
  145. data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_response_spec.rb +113 -0
  146. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_request_spec.rb +88 -0
  147. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_response_spec.rb +150 -0
  148. data/spec/lib/ruby_smb/dcerpc/winreg/regsam_spec.rb +32 -0
  149. data/spec/lib/ruby_smb/dcerpc/winreg_spec.rb +710 -0
  150. data/spec/lib/ruby_smb/dcerpc_spec.rb +81 -0
  151. data/spec/lib/ruby_smb/dispatcher/socket_spec.rb +3 -1
  152. data/spec/lib/ruby_smb/generic_packet_spec.rb +52 -4
  153. data/spec/lib/ruby_smb/smb1/file_spec.rb +191 -2
  154. data/spec/lib/ruby_smb/smb1/packet/empty_packet_spec.rb +68 -0
  155. data/spec/lib/ruby_smb/smb1/packet/trans2/find_first2_response_spec.rb +11 -2
  156. data/spec/lib/ruby_smb/smb1/packet/trans2/find_next2_response_spec.rb +11 -2
  157. data/spec/lib/ruby_smb/smb1/packet/tree_connect_response_spec.rb +40 -0
  158. data/spec/lib/ruby_smb/smb1/pipe_spec.rb +272 -149
  159. data/spec/lib/ruby_smb/smb1/tree_spec.rb +44 -7
  160. data/spec/lib/ruby_smb/smb2/file_spec.rb +295 -2
  161. data/spec/lib/ruby_smb/smb2/packet/error_packet_spec.rb +78 -0
  162. data/spec/lib/ruby_smb/smb2/packet/query_directory_response_spec.rb +8 -0
  163. data/spec/lib/ruby_smb/smb2/packet/tree_connect_response_spec.rb +8 -0
  164. data/spec/lib/ruby_smb/smb2/pipe_spec.rb +322 -145
  165. data/spec/lib/ruby_smb/smb2/tree_spec.rb +214 -0
  166. data.tar.gz.sig +0 -0
  167. metadata +70 -11
  168. metadata.gz.sig +0 -0
  169. data/lib/ruby_smb/smb1/dcerpc.rb +0 -67
  170. data/lib/ruby_smb/smb2/dcerpc.rb +0 -70
  171. data/spec/lib/ruby_smb/smb1/packet/error_packet_spec.rb +0 -37
@@ -27,14 +27,80 @@ 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
+
37
+ subject(:pipe) { described_class.new(name: filename, response: create_response, tree: tree) }
38
+
39
+ describe '#peek' do
40
+ let(:request) { RubySMB::SMB2::Packet::IoctlRequest.new }
41
+ let(:raw_response) { double('Raw response') }
42
+ let(:response) { double('Response') }
43
+
44
+ before :example do
45
+ allow(RubySMB::SMB2::Packet::IoctlRequest).to receive(:new).and_return(request)
46
+ allow(client).to receive(:send_recv).and_return(raw_response)
47
+ allow(RubySMB::SMB2::Packet::IoctlResponse).to receive(:read).and_return(response)
48
+ allow(response).to receive(:valid?).and_return(true)
49
+ allow(response).to receive(:status_code).and_return(WindowsError::NTStatus::STATUS_SUCCESS)
50
+ end
51
+
52
+ it 'creates a IoctlRequest'do
53
+ expect(RubySMB::SMB2::Packet::IoctlRequest).to receive(:new)
54
+ pipe.peek
55
+ end
56
+
57
+ it 'sets the request #ctl_code field' do
58
+ expect(request).to receive(:ctl_code=).with(RubySMB::Fscc::ControlCodes::FSCTL_PIPE_PEEK)
59
+ pipe.peek
60
+ end
61
+
62
+ it 'sets the request #is_fsctl flag to true' do
63
+ pipe.peek
64
+ expect(request.flags.is_fsctl).to eq 1
65
+ end
66
+
67
+ it 'sets the request #max_output_response field to the expected value' do
68
+ pipe.peek(peek_size: 10)
69
+ expect(request.max_output_response).to eq(16 + 10)
70
+ end
71
+
72
+ it 'calls #set_header_fields' do
73
+ expect(pipe).to receive(:set_header_fields).with(request)
74
+ pipe.peek
75
+ end
76
+
77
+ it 'calls Client #send_recv' do
78
+ expect(client).to receive(:send_recv).with(request)
79
+ pipe.peek
80
+ end
81
+
82
+ it 'parses the response as a SMB2 IoctlResponse packet' do
83
+ expect(RubySMB::SMB2::Packet::IoctlResponse).to receive(:read).with(raw_response)
84
+ pipe.peek
85
+ end
86
+
87
+ it 'raises an InvalidPacket exception if the response is not valid' do
88
+ allow(response).to receive(:valid?).and_return(false)
89
+ smb2_header = double('SMB2 Header')
90
+ allow(response).to receive(:smb2_header).and_return(smb2_header)
91
+ allow(smb2_header).to receive_messages(:protocol => nil, :command => nil)
92
+ expect { pipe.peek }.to raise_error(RubySMB::Error::InvalidPacket)
93
+ end
36
94
 
37
- subject(:pipe) { described_class.new(name: 'msf-pipe', response: create_response, tree: tree) }
95
+ it 'raises an UnexpectedStatusCode exception if the response status code is not STATUS_SUCCESS or STATUS_BUFFER_OVERFLOW' do
96
+ allow(response).to receive(:status_code).and_return(WindowsError::NTStatus::STATUS_OBJECT_NAME_NOT_FOUND)
97
+ expect { pipe.peek }.to raise_error(RubySMB::Error::UnexpectedStatusCode)
98
+ end
99
+
100
+ it 'returns the expected response' do
101
+ expect(pipe.peek).to eq(response)
102
+ end
103
+ end
38
104
 
39
105
  describe '#peek_available' do
40
106
  it 'reads the correct number of bytes available' do
@@ -61,206 +127,317 @@ RSpec.describe RubySMB::SMB2::Pipe do
61
127
  end
62
128
  end
63
129
 
64
- context 'with DCERPC' do
65
- describe '#net_share_enum_all' do
66
- let(:host) { '1.2.3.4' }
67
- let(:dcerpc_response) { RubySMB::Dcerpc::Response.new }
68
-
69
- before :example do
70
- allow(pipe).to receive(:bind)
71
- allow(pipe).to receive(:request).and_return(dcerpc_response)
72
- 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)
73
136
  end
137
+ end
74
138
 
75
- it 'calls #bind with the expected arguments' do
76
- expect(pipe).to receive(:bind).with(endpoint: RubySMB::Dcerpc::Srvsvc)
77
- pipe.net_share_enum_all(host)
78
- 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
79
150
 
80
- it 'calls #request with the expected arguments' do
81
- expect(pipe).to receive(:request).with(RubySMB::Dcerpc::Srvsvc::NET_SHARE_ENUM_ALL, host: host)
82
- 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
83
155
  end
156
+ end
84
157
 
85
- it 'parse the response with NetShareEnumAll #parse_response method' do
86
- stub = 'ABCD'
87
- dcerpc_response.alloc_hint = stub.size
88
- dcerpc_response.stub = stub
89
- expect(RubySMB::Dcerpc::Srvsvc::NetShareEnumAll).to receive(:parse_response).with(stub)
90
- 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
91
162
  end
163
+ end
164
+ end
92
165
 
93
- it 'returns the remote shares' do
94
- shares = [
95
- ["C$", "DISK", "Default share"],
96
- ["Shared", "DISK", ""],
97
- ["IPC$", "IPC", "Remote IPC"],
98
- ["ADMIN$", "DISK", "Remote Admin"]
99
- ]
100
- output = [
101
- {:name=>"C$", :type=>"DISK", :comment=>"Default share"},
102
- {:name=>"Shared", :type=>"DISK", :comment=>""},
103
- {:name=>"IPC$", :type=>"IPC", :comment=>"Remote IPC"},
104
- {:name=>"ADMIN$", :type=>"DISK", :comment=>"Remote Admin"},
105
- ]
106
- allow(RubySMB::Dcerpc::Srvsvc::NetShareEnumAll).to receive(:parse_response).and_return(shares)
107
- expect(pipe.net_share_enum_all(host)).to eq(output)
108
- 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)
109
231
  end
110
232
 
111
- describe '#bind' do
112
- let(:options) { { endpoint: RubySMB::Dcerpc::Srvsvc } }
113
- let(:bind_packet) { RubySMB::Dcerpc::Bind.new(options) }
114
- let(:bind_ack_packet) { RubySMB::Dcerpc::BindAck.new }
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
115
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
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') }
116
267
  before :example do
117
- allow(RubySMB::Dcerpc::Bind).to receive(:new).and_return(bind_packet)
118
- allow(pipe).to receive(:write)
119
- allow(pipe).to receive(:read)
120
- bind_ack_packet.p_result_list.n_results = 1
121
- bind_ack_packet.p_result_list.p_results[0].result = RubySMB::Dcerpc::BindAck::ACCEPTANCE
122
- 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
+ )
123
277
  end
124
278
 
125
- it 'creates a Bind packet' do
126
- expect(RubySMB::Dcerpc::Bind).to receive(:new).with(options).and_return(bind_packet)
127
- pipe.bind(options)
279
+ it 'waits 1 second' do
280
+ pipe.ioctl_send_recv(dcerpc_request, options)
281
+ expect(pipe).to have_received(:sleep).with(1)
128
282
  end
129
283
 
130
- it 'writes to the named pipe' do
131
- expect(pipe).to receive(:write).with(data: bind_packet.to_binary_s)
132
- 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)
133
287
  end
134
288
 
135
- it 'reads the socket' do
136
- expect(pipe).to receive(:read)
137
- 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)
138
292
  end
139
293
 
140
- it 'creates a BindAck packet from the response' do
141
- raw_response = RubySMB::Dcerpc::BindAck.new.to_binary_s
142
- allow(pipe).to receive(:read).and_return(raw_response)
143
- expect(RubySMB::Dcerpc::BindAck).to receive(:read).with(raw_response).and_return(bind_ack_packet)
144
- 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
145
301
  end
302
+ end
146
303
 
147
- it 'raises the expected exception when an invalid packet is received' do
148
- allow(RubySMB::Dcerpc::BindAck).to receive(:read).and_raise(IOError)
149
- expect { pipe.bind(options) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
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)
150
308
  end
309
+ end
151
310
 
152
- it 'raises the expected exception when it is not a BindAck packet' do
153
- response = RubySMB::Dcerpc::Bind.new
154
- allow(RubySMB::Dcerpc::BindAck).to receive(:read).and_return(response)
155
- expect { pipe.bind(options) }.to raise_error(RubySMB::Dcerpc::Error::BindError)
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
156
314
  end
157
315
 
158
- it 'raises an exception when no result is returned' do
159
- bind_ack_packet.p_result_list.n_results = 0
160
- 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)
161
319
  end
162
320
 
163
- it 'raises an exception when result is not ACCEPTANCE' do
164
- bind_ack_packet.p_result_list.p_results[0].result = RubySMB::Dcerpc::BindAck::USER_REJECTION
165
- 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
166
326
  end
167
327
 
168
- it 'returns the expected BindAck packet' do
169
- expect(pipe.bind(options)).to eq(bind_ack_packet)
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
170
333
  end
171
- end
172
334
 
173
- describe '#request' do
174
- let(:options) { { host: '1.2.3.4' } }
175
- let(:opnum) { RubySMB::Dcerpc::Srvsvc::NET_SHARE_ENUM_ALL }
176
- let(:req_packet) { RubySMB::Dcerpc::Request.new({ :opnum => opnum }, options) }
177
- let(:ioctl_response) { RubySMB::SMB2::Packet::IoctlResponse.new }
178
- let(:res_packet) { RubySMB::Dcerpc::Response.new }
335
+ it 'returns the expected stub data' do
336
+ expect(pipe.ioctl_send_recv(dcerpc_request, options)).to eq(result)
337
+ end
338
+ end
179
339
 
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') }
180
343
  before :example do
181
- allow(RubySMB::Dcerpc::Request).to receive(:new).and_return(req_packet)
182
- allow(pipe).to receive(:ioctl_send_recv).and_return(ioctl_response)
183
- 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)
184
350
  end
185
351
 
186
- it 'creates a Request packet' do
187
- expect(RubySMB::Dcerpc::Request).to receive(:new).and_return(req_packet)
188
- 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
189
354
  end
190
355
 
191
- it 'calls #ioctl_send_recv' do
192
- expect(pipe).to receive(:ioctl_send_recv).with(req_packet, options)
193
- 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)
194
360
  end
195
361
 
196
- it 'creates a DCERPC Response packet from the response' do
197
- expect(RubySMB::Dcerpc::Response).to receive(:read).with(ioctl_response.output_data)
198
- 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)
199
365
  end
200
366
 
201
- it 'raises the expected exception when an invalid packet is received' do
202
- allow(RubySMB::Dcerpc::Response).to receive(:read).and_raise(IOError)
203
- 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
204
372
  end
205
373
 
206
- it 'raises the expected exception when it is not a BindAck packet' do
207
- response = RubySMB::Dcerpc::Request.new
208
- allow(RubySMB::Dcerpc::Response).to receive(:read).and_return(response)
209
- 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
210
379
  end
211
380
 
212
- it 'returns the expected DCERPC Response' do
213
- 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
214
386
  end
215
- end
216
387
 
217
- describe '#ioctl_send_recv' do
218
- let(:action) { RubySMB::Dcerpc::Request.new({ :opnum => RubySMB::Dcerpc::Srvsvc::NET_SHARE_ENUM_ALL }, host: '1.2.3.4') }
219
- let(:options) { {} }
220
- let(:ioctl_request) { RubySMB::SMB2::Packet::IoctlRequest.new(options) }
221
- 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
222
393
 
223
- before :example do
224
- 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
225
397
  end
226
398
 
227
- it 'calls #set_header_fields' do
228
- expect(pipe).to receive(:set_header_fields).with(ioctl_request).and_call_original
229
- pipe.ioctl_send_recv(action, options)
230
- 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
231
411
 
232
- it 'calls Client #send_recv with the expected request' do
233
- expect(client).to receive(:send_recv) do |req|
234
- expect(req.ctl_code).to eq(0x0011C017)
235
- expect(req.flags.is_fsctl).to eq(0x00000001)
236
- expect(req.buffer).to eq(action.to_binary_s)
237
- 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)
238
415
  end
239
- pipe.ioctl_send_recv(action, options)
240
- end
241
416
 
242
- it 'creates a IoctlResponse packet from the response' do
243
- expect(RubySMB::SMB2::Packet::IoctlResponse).to receive(:read).with(ioctl_response.to_binary_s).and_call_original
244
- pipe.ioctl_send_recv(action, options)
245
- 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
246
421
 
247
- it 'raises the expected exception when it is not a IoctlResponse packet' do
248
- response = RubySMB::SMB2::Packet::LogoffResponse.new
249
- allow(RubySMB::SMB2::Packet::IoctlResponse).to receive(:read).and_return(response)
250
- expect { pipe.ioctl_send_recv(action, options) }.to raise_error(RubySMB::Error::InvalidPacket)
251
- 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
252
428
 
253
- it 'raises the expected exception when the status code is not STATUS_SUCCESS' do
254
- ioctl_response_packet = RubySMB::SMB2::Packet::IoctlResponse.new
255
- ioctl_response_packet.smb2_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_HANDLE.value
256
- allow(RubySMB::SMB2::Packet::IoctlResponse).to receive(:read).with(ioctl_response.to_binary_s).and_return(ioctl_response_packet)
257
- expect { pipe.ioctl_send_recv(action, options) }.to raise_error(RubySMB::Error::UnexpectedStatusCode)
258
- 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
259
435
 
260
- it 'returns the expected DCERPC Response' do
261
- 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
262
439
  end
263
440
  end
264
441
  end
265
- end
266
442
 
443
+ end