CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/rex/post/meterpreter/extensions/stdapi/sys/process.rb
Views: 1904
1
# -*- coding: binary -*-
2
3
require 'rex/post/process'
4
require 'rex/post/meterpreter/packet'
5
require 'rex/post/meterpreter/client'
6
require 'rex/post/meterpreter/channels/pools/stream_pool'
7
require 'rex/post/meterpreter/extensions/stdapi/stdapi'
8
9
require 'rex/post/meterpreter/extensions/stdapi/sys/process_subsystem/image'
10
require 'rex/post/meterpreter/extensions/stdapi/sys/process_subsystem/io'
11
require 'rex/post/meterpreter/extensions/stdapi/sys/process_subsystem/memory'
12
require 'rex/post/meterpreter/extensions/stdapi/sys/process_subsystem/thread'
13
14
module Rex
15
module Post
16
module Meterpreter
17
module Extensions
18
module Stdapi
19
module Sys
20
21
##
22
#
23
# This class implements the Rex::Post::Process interface.
24
#
25
##
26
class Process < Rex::Post::Process
27
28
include Rex::Post::Meterpreter::ObjectAliasesContainer
29
30
##
31
#
32
# Class methods
33
#
34
##
35
36
class << self
37
attr_accessor :client
38
end
39
40
#
41
# Returns the process identifier of the process supplied in key if it's
42
# valid.
43
#
44
def Process.[](key)
45
return if key.nil?
46
47
each_process { |p|
48
if (p['name'].downcase == key.downcase)
49
return p['pid']
50
end
51
}
52
53
return nil
54
end
55
56
#
57
# Attaches to the supplied process with a given set of permissions.
58
#
59
def Process.open(pid = nil, perms = nil)
60
real_perms = 0
61
62
if (perms == nil)
63
perms = PROCESS_ALL
64
end
65
66
if (perms & PROCESS_READ) > 0
67
real_perms |= PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_QUERY_INFORMATION
68
end
69
70
if (perms & PROCESS_WRITE) > 0
71
real_perms |= PROCESS_SET_SESSIONID | PROCESS_VM_WRITE | PROCESS_DUP_HANDLE | PROCESS_SET_QUOTA | PROCESS_SET_INFORMATION
72
end
73
74
if (perms & PROCESS_EXECUTE) > 0
75
real_perms |= PROCESS_TERMINATE | PROCESS_CREATE_THREAD | PROCESS_CREATE_PROCESS | PROCESS_SUSPEND_RESUME
76
end
77
78
return _open(pid, real_perms)
79
end
80
81
#
82
# Low-level process open.
83
#
84
def Process._open(pid, perms, inherit = false)
85
request = Packet.create_request(COMMAND_ID_STDAPI_SYS_PROCESS_ATTACH)
86
87
if (pid == nil)
88
pid = 0
89
end
90
91
# Populate the request
92
request.add_tlv(TLV_TYPE_PID, pid)
93
request.add_tlv(TLV_TYPE_PROCESS_PERMS, perms)
94
request.add_tlv(TLV_TYPE_INHERIT, inherit)
95
96
# Transmit the request
97
response = self.client.send_request(request)
98
handle = response.get_tlv_value(TLV_TYPE_HANDLE)
99
100
# If the handle is valid, allocate a process instance and return it
101
if (handle != nil)
102
return self.new(pid, handle)
103
end
104
105
return nil
106
end
107
108
#
109
# Executes an application using the arguments provided
110
#
111
# Hash arguments supported:
112
#
113
# Hidden => true/false
114
# Channelized => true/false
115
# Suspended => true/false
116
# InMemory => true/false
117
#
118
def Process.execute(path, arguments = nil, opts = nil)
119
request = Packet.create_request(COMMAND_ID_STDAPI_SYS_PROCESS_EXECUTE)
120
flags = 0
121
122
# If we were supplied optional arguments...
123
if (opts != nil)
124
if (opts['Hidden'])
125
flags |= PROCESS_EXECUTE_FLAG_HIDDEN
126
end
127
if (opts['Channelized'])
128
flags |= PROCESS_EXECUTE_FLAG_CHANNELIZED
129
end
130
if (opts['Suspended'])
131
flags |= PROCESS_EXECUTE_FLAG_SUSPENDED
132
end
133
if (opts['UseThreadToken'])
134
flags |= PROCESS_EXECUTE_FLAG_USE_THREAD_TOKEN
135
end
136
if (opts['Desktop'])
137
flags |= PROCESS_EXECUTE_FLAG_DESKTOP
138
end
139
if (opts['Session'])
140
flags |= PROCESS_EXECUTE_FLAG_SESSION
141
request.add_tlv( TLV_TYPE_PROCESS_SESSION, opts['Session'] )
142
end
143
if (opts['Subshell'])
144
flags |= PROCESS_EXECUTE_FLAG_SUBSHELL
145
end
146
if (opts['Pty'])
147
flags |= PROCESS_EXECUTE_FLAG_PTY
148
end
149
if (opts['ParentPid'])
150
request.add_tlv(TLV_TYPE_PARENT_PID, opts['ParentPid']);
151
request.add_tlv(TLV_TYPE_PROCESS_PERMS, PROCESS_ALL_ACCESS)
152
request.add_tlv(TLV_TYPE_INHERIT, false)
153
end
154
inmem = opts['InMemory']
155
if inmem
156
157
# add the file contents into the tlv
158
f = ::File.new(path, 'rb')
159
request.add_tlv(TLV_TYPE_VALUE_DATA, f.read(f.stat.size))
160
f.close
161
162
# replace the path with the "dummy"
163
path = inmem.kind_of?(String) ? inmem : 'cmd'
164
end
165
end
166
167
request.add_tlv(TLV_TYPE_PROCESS_PATH, client.unicode_filter_decode( path ));
168
169
# If process arguments were supplied
170
if (arguments != nil)
171
request.add_tlv(TLV_TYPE_PROCESS_ARGUMENTS, arguments);
172
end
173
174
request.add_tlv(TLV_TYPE_PROCESS_FLAGS, flags);
175
176
response = client.send_request(request)
177
178
# Get the response parameters
179
pid = response.get_tlv_value(TLV_TYPE_PID)
180
handle = response.get_tlv_value(TLV_TYPE_PROCESS_HANDLE)
181
channel_id = response.get_tlv_value(TLV_TYPE_CHANNEL_ID)
182
channel = nil
183
184
# If we were creating a channel out of this
185
if (channel_id != nil)
186
channel = Rex::Post::Meterpreter::Channels::Pools::StreamPool.new(client,
187
channel_id, "stdapi_process", CHANNEL_FLAG_SYNCHRONOUS, response)
188
end
189
190
# Return a process instance
191
return self.new(pid, handle, channel)
192
end
193
194
#
195
# Execute an application and capture the output
196
#
197
def Process.capture_output(path, arguments = nil, opts = nil, time_out = 15)
198
start = Time.now.to_i
199
process = execute(path, arguments, opts)
200
data = ""
201
202
# Wait up to time_out seconds for the first bytes to arrive
203
while (d = process.channel.read)
204
data << d
205
if d == ""
206
if Time.now.to_i - start < time_out
207
sleep 0.1
208
else
209
break
210
end
211
end
212
end
213
data.chomp! if data
214
215
begin
216
process.channel.close
217
rescue IOError => e
218
# Channel was already closed, but we got the cmd output, so let's soldier on.
219
end
220
process.close
221
222
return data
223
end
224
225
#
226
# Kills one or more processes.
227
#
228
def Process.kill(*args)
229
request = Packet.create_request(COMMAND_ID_STDAPI_SYS_PROCESS_KILL)
230
231
args.each { |id|
232
request.add_tlv(TLV_TYPE_PID, id)
233
}
234
235
client.send_request(request)
236
237
return true
238
end
239
240
#
241
# Gets the process id that the remote side is executing under.
242
#
243
def Process.getpid
244
request = Packet.create_request(COMMAND_ID_STDAPI_SYS_PROCESS_GETPID)
245
246
response = client.send_request(request)
247
248
return response.get_tlv_value(TLV_TYPE_PID)
249
end
250
251
#
252
# Enumerates all of the elements in the array returned by get_processes.
253
#
254
def Process.each_process(&block)
255
self.get_processes.each(&block)
256
end
257
258
#
259
# Returns a ProcessList of processes as Hash objects with keys for 'pid',
260
# 'ppid', 'name', 'path', 'user', 'session' and 'arch'.
261
#
262
def Process.get_processes
263
request = Packet.create_request(COMMAND_ID_STDAPI_SYS_PROCESS_GET_PROCESSES)
264
processes = ProcessList.new
265
266
response = client.send_request(request)
267
268
response.each(TLV_TYPE_PROCESS_GROUP) { |p|
269
arch = ""
270
271
pa = p.get_tlv_value(TLV_TYPE_PROCESS_ARCH)
272
if !pa.nil?
273
if pa == 1 # PROCESS_ARCH_X86
274
arch = ARCH_X86
275
elsif pa == 2 # PROCESS_ARCH_X64
276
arch = ARCH_X64
277
end
278
else
279
arch = p.get_tlv_value(TLV_TYPE_PROCESS_ARCH_NAME)
280
end
281
282
processes <<
283
{
284
'pid' => p.get_tlv_value(TLV_TYPE_PID),
285
'ppid' => p.get_tlv_value(TLV_TYPE_PARENT_PID),
286
'name' => client.unicode_filter_encode( p.get_tlv_value(TLV_TYPE_PROCESS_NAME) ),
287
'path' => client.unicode_filter_encode( p.get_tlv_value(TLV_TYPE_PROCESS_PATH) ),
288
'session' => p.get_tlv_value(TLV_TYPE_PROCESS_SESSION),
289
'user' => client.unicode_filter_encode( p.get_tlv_value(TLV_TYPE_USER_NAME) ),
290
'arch' => arch
291
}
292
}
293
294
return processes
295
end
296
297
#
298
# An alias for get_processes.
299
#
300
def Process.processes
301
self.get_processes
302
end
303
304
#
305
# Search memory for supplied regexes and return matches
306
#
307
def Process.memory_search(pid: 0, needles: [''], min_match_length: 5, max_match_length: 127)
308
request = Packet.create_request(COMMAND_ID_STDAPI_SYS_PROCESS_MEMORY_SEARCH)
309
310
request.add_tlv(TLV_TYPE_PID, pid)
311
needles.each { |needle| request.add_tlv(TLV_TYPE_MEMORY_SEARCH_NEEDLE, needle) }
312
request.add_tlv(TLV_TYPE_MEMORY_SEARCH_MATCH_LEN, max_match_length)
313
request.add_tlv(TLV_TYPE_UINT, min_match_length)
314
315
self.client.send_request(request)
316
end
317
318
##
319
#
320
# Instance methods
321
#
322
##
323
324
#
325
# Initializes the process instance and its aliases.
326
#
327
def initialize(pid, handle, channel = nil)
328
self.client = self.class.client
329
self.handle = handle
330
self.channel = channel
331
332
# If the process identifier is zero, then we must lookup the current
333
# process identifier
334
if (pid == 0)
335
self.pid = client.sys.process.getpid
336
else
337
self.pid = pid
338
end
339
340
initialize_aliases(
341
{
342
'image' => Rex::Post::Meterpreter::Extensions::Stdapi::Sys::ProcessSubsystem::Image.new(self),
343
'io' => Rex::Post::Meterpreter::Extensions::Stdapi::Sys::ProcessSubsystem::IO.new(self),
344
'memory' => Rex::Post::Meterpreter::Extensions::Stdapi::Sys::ProcessSubsystem::Memory.new(self),
345
'thread' => Rex::Post::Meterpreter::Extensions::Stdapi::Sys::ProcessSubsystem::Thread.new(self),
346
})
347
348
# Ensure the remote object is closed when all references are removed
349
ObjectSpace.define_finalizer(self, self.class.finalize(client, handle))
350
end
351
352
def self.finalize(client, handle)
353
proc do
354
deferred_close_proc = proc do
355
begin
356
self.close(client, handle)
357
rescue => e
358
elog("finalize method for Process failed", error: e)
359
end
360
end
361
362
# Schedule the finalizing logic out-of-band; as this logic might be called in the context of a Signal.trap, which can't synchronize mutexes
363
client.framework.sessions.schedule(deferred_close_proc)
364
end
365
end
366
367
#
368
# Returns the executable name of the process.
369
#
370
def name
371
return get_info()['name']
372
end
373
374
#
375
# Returns the path to the process' executable.
376
#
377
def path
378
return get_info()['path']
379
end
380
381
#
382
# Closes the handle to the process that was opened.
383
#
384
def self.close(client, handle)
385
request = Packet.create_request(COMMAND_ID_STDAPI_SYS_PROCESS_CLOSE)
386
request.add_tlv(TLV_TYPE_HANDLE, handle)
387
client.send_request(request, nil)
388
handle = nil
389
return true
390
end
391
392
#
393
# Instance method
394
#
395
def close(handle = self.handle)
396
unless self.pid.nil?
397
ObjectSpace.undefine_finalizer(self)
398
self.class.close(self.client, handle)
399
self.pid = nil
400
end
401
end
402
403
#
404
# Block until this process terminates on the remote side.
405
# By default we choose not to allow a packet response timeout to
406
# occur as we may be waiting indefinatly for the process to terminate.
407
#
408
def wait( timeout = -1 )
409
request = Packet.create_request(COMMAND_ID_STDAPI_SYS_PROCESS_WAIT)
410
411
request.add_tlv(TLV_TYPE_HANDLE, self.handle)
412
413
self.client.send_request(request, timeout)
414
415
self.handle = nil
416
417
return true
418
end
419
420
attr_reader :client, :handle, :channel, :pid # :nodoc:
421
protected
422
attr_writer :client, :handle, :channel, :pid # :nodoc:
423
424
#
425
# Gathers information about the process and returns a hash.
426
#
427
def get_info
428
request = Packet.create_request(COMMAND_ID_STDAPI_SYS_PROCESS_GET_INFO)
429
info = {}
430
431
request.add_tlv(TLV_TYPE_HANDLE, handle)
432
433
# Send the request
434
response = client.send_request(request)
435
436
# Populate the hash
437
info['name'] = client.unicode_filter_encode( response.get_tlv_value(TLV_TYPE_PROCESS_NAME) )
438
info['path'] = client.unicode_filter_encode( response.get_tlv_value(TLV_TYPE_PROCESS_PATH) )
439
440
return info
441
end
442
443
end
444
445
#
446
# Simple wrapper class for storing processes
447
#
448
class ProcessList < Array
449
450
#
451
# Create a Rex::Text::Table out of the processes stored in this list
452
#
453
# +opts+ is passed on to Rex::Text::Table.new, mostly unmolested
454
#
455
# Note that this output is affected by Rex::Post::Meterpreter::Client#unicode_filter_encode
456
#
457
def to_table(opts={})
458
if empty?
459
return Rex::Text::Table.new(opts)
460
end
461
462
column_headers = [ "PID", "PPID", "Name", "Arch", "Session", "User", "Path" ]
463
column_headers.delete_if do |h|
464
none? { |process| process.has_key?(h.downcase) } ||
465
all? { |process| process[h.downcase].nil? }
466
end
467
468
opts = {
469
'Header' => 'Process List',
470
'Indent' => 1,
471
'Columns' => column_headers
472
}.merge(opts)
473
474
tbl = Rex::Text::Table.new(opts)
475
each do |process|
476
tbl << column_headers.map do |header|
477
col = header.downcase
478
next unless process.keys.any? { |process_header| process_header == col }
479
val = process[col]
480
if col == 'session'
481
val == 0xFFFFFFFF ? '' : val.to_s
482
else
483
val
484
end
485
end
486
end
487
488
tbl
489
end
490
end
491
492
end; end; end; end; end; end
493
494