CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/rex/proto/dcerpc/client.rb
Views: 11704
1
# -*- coding: binary -*-
2
module Rex
3
module Proto
4
module DCERPC
5
class Client
6
7
require 'rex/text'
8
9
attr_accessor :handle, :socket, :options, :last_response, :context, :no_bind, :ispipe, :smb
10
11
# initialize a DCE/RPC Function Call
12
def initialize(handle, socket, useroptions = Hash.new)
13
self.handle = handle
14
self.socket = socket
15
self.options = {
16
'smb_user' => '',
17
'smb_pass' => '',
18
'smb_pipeio' => 'rw',
19
'smb_name' => nil,
20
'read_timeout' => 10,
21
'connect_timeout' => 5
22
}
23
24
self.options.merge!(useroptions)
25
26
# If the caller passed us a smb_client object, use it and
27
# and skip the connect/login/ipc$ stages of the setup
28
if (self.options['smb_client'])
29
self.smb = self.options['smb_client']
30
end
31
32
# we must have a valid handle, regardless of everything else
33
raise ArgumentError, 'handle is not a Rex::Proto::DCERPC::Handle' if !self.handle.is_a?(Rex::Proto::DCERPC::Handle)
34
35
# we do this in case socket needs setup first, ie, socket = nil
36
if !self.options['no_socketsetup']
37
self.socket_check()
38
end
39
40
raise ArgumentError, 'socket can not read' if !self.socket.respond_to?(:read)
41
raise ArgumentError, 'socket can not write' if !self.socket.respond_to?(:write)
42
43
if !self.options['no_autobind']
44
self.bind()
45
end
46
end
47
48
def socket_check()
49
if self.socket == nil
50
self.socket_setup()
51
end
52
53
case self.handle.protocol
54
when 'ncacn_ip_tcp'
55
if self.socket.type? != 'tcp'
56
raise ::Rex::Proto::DCERPC::Exceptions::InvalidSocket, "ack, #{self.handle.protocol} requires socket type tcp, not #{self.socket.type?}!"
57
end
58
when 'ncacn_np'
59
if self.socket.class == Rex::Proto::SMB::SimpleClient::OpenPipe
60
self.ispipe = 1
61
elsif self.socket.type? == 'tcp'
62
self.smb_connect()
63
else
64
raise ::Rex::Proto::DCERPC::Exceptions::InvalidSocket, "ack, #{self.handle.protocol} requires socket type tcp, not #{self.socket.type?}!"
65
end
66
# No support ncacn_ip_udp (is it needed now that its ripped from Vista?)
67
else
68
raise ::Rex::Proto::DCERPC::Exceptions::InvalidSocket, "Unsupported protocol : #{self.handle.protocol}"
69
end
70
end
71
72
# Create the appropriate socket based on protocol
73
def socket_setup()
74
ctx = { 'Msf' => self.options['Msf'], 'MsfExploit' => self.options['MsfExploit'] }
75
self.socket = case self.handle.protocol
76
77
when 'ncacn_ip_tcp'
78
Rex::Socket.create_tcp(
79
'PeerHost' => self.handle.address,
80
'PeerPort' => self.handle.options[0],
81
'Context' => ctx,
82
'Timeout' => self.options['connect_timeout']
83
)
84
85
when 'ncacn_np'
86
begin
87
socket = Rex::Socket.create_tcp(
88
'PeerHost' => self.handle.address,
89
'PeerPort' => 445,
90
'Context' => ctx,
91
'Timeout' => self.options['connect_timeout']
92
)
93
rescue ::Timeout::Error, Rex::ConnectionRefused
94
socket = Rex::Socket.create_tcp(
95
'PeerHost' => self.handle.address,
96
'PeerPort' => 139,
97
'Context' => ctx,
98
'Timeout' => self.options['connect_timeout']
99
)
100
end
101
socket
102
else nil
103
end
104
105
# Add this socket to the exploit's list of open sockets
106
options['MsfExploit'].add_socket(self.socket) if (options['MsfExploit'])
107
end
108
109
def smb_connect()
110
111
if(not self.smb)
112
if self.socket.peerport == 139
113
smb = Rex::Proto::SMB::SimpleClient.new(self.socket)
114
else
115
smb = Rex::Proto::SMB::SimpleClient.new(self.socket, true)
116
end
117
118
smb.login('*SMBSERVER', self.options['smb_user'], self.options['smb_pass'])
119
smb.connect("\\\\#{self.handle.address}\\IPC$")
120
self.smb = smb
121
self.smb.client.read_timeout = self.options['read_timeout']
122
end
123
124
f = self.smb.create_pipe(self.handle.options[0])
125
f.mode = self.options['smb_pipeio']
126
self.socket = f
127
end
128
129
def read()
130
131
max_read = self.options['pipe_read_max_size'] || 1024*1024
132
min_read = self.options['pipe_read_min_size'] || max_read
133
134
raw_response = ''
135
136
# Are we reading from a remote pipe over SMB?
137
if (self.socket.class == Rex::Proto::SMB::SimpleClient::OpenPipe)
138
begin
139
raw_response = self.socket.read(65535, 0)
140
rescue Rex::Proto::SMB::Exceptions::NoReply
141
# I don't care if I didn't get a reply...
142
rescue Rex::Proto::SMB::Exceptions::ErrorCode => exception
143
if exception.error_code != 0xC000014B
144
raise exception
145
end
146
end
147
# This must be a regular TCP or UDP socket
148
else
149
if (self.socket.type? == 'tcp')
150
if (false and max_read)
151
while (true)
152
data = self.socket.get_once((rand(max_read-min_read)+min_read), self.options['read_timeout'])
153
break if not data
154
break if not data.length
155
raw_response << data
156
end
157
else
158
# Just read the entire response in one go
159
raw_response = self.socket.get_once(-1, self.options['read_timeout'])
160
end
161
else
162
# No segmented read support for non-TCP sockets
163
raw_response = self.socket.read(0xFFFFFFFF / 2 - 1) # read max data
164
end
165
end
166
167
raw_response
168
end
169
170
# Write data to the underlying socket, limiting the sizes of the writes based on
171
# the pipe_write_min / pipe_write_max options.
172
def write(data)
173
174
max_write = self.options['pipe_write_max_size'] || data.length
175
min_write = self.options['pipe_write_min_size'] || max_write
176
177
if(min_write > max_write)
178
max_write = min_write
179
end
180
181
idx = 0
182
183
if (self.socket.class == Rex::Proto::SMB::SimpleClient::OpenPipe)
184
while(idx < data.length)
185
bsize = (rand(max_write-min_write)+min_write).to_i
186
len = self.socket.write(data[idx, bsize])
187
idx += bsize
188
end
189
else
190
self.socket.write(data)
191
end
192
193
data.length
194
end
195
196
def bind()
197
bind = ''
198
context = ''
199
if self.options['fake_multi_bind']
200
201
args = [ self.handle.uuid[0], self.handle.uuid[1] ]
202
203
if (self.options['fake_multi_bind_prepend'])
204
args << self.options['fake_multi_bind_prepend']
205
end
206
207
if (self.options['fake_multi_bind_append'])
208
args << self.options['fake_multi_bind_append']
209
end
210
211
bind, context = Rex::Proto::DCERPC::Packet.make_bind_fake_multi(*args)
212
else
213
bind, context = Rex::Proto::DCERPC::Packet.make_bind(*self.handle.uuid)
214
end
215
216
raise ::Rex::Proto::DCERPC::Exceptions::BindError, 'make_bind failed' if !bind
217
218
self.write(bind)
219
raw_response = self.read()
220
221
response = Rex::Proto::DCERPC::Response.new(raw_response)
222
self.last_response = response
223
if response.type == 12 or response.type == 15
224
if self.last_response.ack_result[context] == 2
225
raise ::Rex::Proto::DCERPC::Exceptions::BindError, "Could not bind to #{self.handle}"
226
end
227
self.context = context
228
else
229
raise ::Rex::Proto::DCERPC::Exceptions::BindError, "Could not bind to #{self.handle}"
230
end
231
end
232
233
# Perform a DCE/RPC Function Call
234
def call(function, data, do_recv = true)
235
236
frag_size = data.length
237
if options['frag_size']
238
frag_size = options['frag_size']
239
end
240
object_id = ''
241
if options['object_call']
242
object_id = self.handle.uuid[0]
243
end
244
if options['random_object_id']
245
object_id = Rex::Proto::DCERPC::UUID.uuid_unpack(Rex::Text.rand_text(16))
246
end
247
248
call_packets = Rex::Proto::DCERPC::Packet.make_request(function, data, frag_size, self.context, object_id)
249
call_packets.each { |packet|
250
self.write(packet)
251
}
252
253
return true if not do_recv
254
255
raw_response = ''
256
data = ''
257
last_frag = false
258
259
until last_frag do
260
begin
261
raw_response = self.read()
262
rescue ::EOFError
263
raise Rex::Proto::DCERPC::Exceptions::NoResponse
264
end
265
266
if (raw_response == nil or raw_response.length == 0)
267
raise Rex::Proto::DCERPC::Exceptions::NoResponse
268
end
269
270
self.last_response = Rex::Proto::DCERPC::Response.new(raw_response)
271
272
if self.last_response.type == 3
273
e = Rex::Proto::DCERPC::Exceptions::Fault.new
274
e.fault = self.last_response.status
275
raise e
276
end
277
278
data << self.last_response.stub_data
279
last_frag = (self.last_response.flags & Rex::Proto::DCERPC::Response::FLAG_LAST_FRAG) == Rex::Proto::DCERPC::Response::FLAG_LAST_FRAG
280
end
281
282
data
283
end
284
285
# Process a DCERPC response packet from a socket
286
def self.read_response(socket, timeout=self.options['read_timeout'])
287
288
data = socket.get_once(-1, timeout)
289
290
# We need at least 10 bytes to find the FragLen
291
if (! data or data.length() < 10)
292
return
293
end
294
295
# Pass the first 10 bytes to the constructor
296
resp = Rex::Proto::DCERPC::Response.new(data.slice!(0, 10))
297
298
# Something went wrong in the parser...
299
if (! resp.frag_len)
300
return resp
301
end
302
303
# Do we need to read more data?
304
if (resp.frag_len > (data.length + 10))
305
begin
306
data << socket.timed_read(resp.frag_len - data.length - 10, timeout)
307
rescue Timeout::Error
308
end
309
end
310
311
# Still missing some data...
312
if (data.length() != resp.frag_len - 10)
313
# TODO: Bubble this up somehow
314
# $stderr.puts "Truncated DCERPC response :-("
315
return resp
316
end
317
318
resp.parse(data)
319
return resp
320
end
321
322
end
323
end
324
end
325
end
326
327
328