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/post/meterpreter/extensions/stdapi/sys/process.rb
Views: 11793
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
# @param path [String] Path on the remote system to the executable to run
111
# @param arguments [String,Array<String>] Arguments to the process. When passed as a String (rather than an array of Strings),
112
# this is treated as a string containing all arguments.
113
# @param opts [Hash] Optional settings to parameterise the process launch
114
# @option Hidden [Boolean] Is the process launched without creating a visible window
115
# @option Channelized [Boolean] The process is launched with pipes connected to a channel, e.g. for sending input/receiving output
116
# @option Suspended [Boolean] Start the process suspended
117
# @option UseThreadToken [Boolean] Use the thread token (as opposed to the process token) to launch the process
118
# @option Desktop [Boolean] Run on meterpreter's current desktopt
119
# @option Session [Integer] Execute process in a given session as the session user
120
# @option Subshell [Boolean] Execute process in a subshell
121
# @option Pty [Boolean] Execute process in a pty (if available)
122
# @option ParentId [Integer] Spoof the parent PID (if possible)
123
# @option InMemory [Boolean,String] Execute from memory (`path` is treated as a local file to upload, and the actual path passed
124
# to meterpreter is this parameter's value, if provided as a String)
125
# @option :legacy_args [String] When arguments is an array, this is the command to execute if the receiving Meterpreter does not support arguments as an array
126
#
127
def Process.execute(path, arguments = '', opts = nil)
128
request = Packet.create_request(COMMAND_ID_STDAPI_SYS_PROCESS_EXECUTE)
129
flags = 0
130
131
# If we were supplied optional arguments...
132
if (opts != nil)
133
if (opts['Hidden'])
134
flags |= PROCESS_EXECUTE_FLAG_HIDDEN
135
end
136
if (opts['Channelized'])
137
flags |= PROCESS_EXECUTE_FLAG_CHANNELIZED
138
end
139
if (opts['Suspended'])
140
flags |= PROCESS_EXECUTE_FLAG_SUSPENDED
141
end
142
if (opts['UseThreadToken'])
143
flags |= PROCESS_EXECUTE_FLAG_USE_THREAD_TOKEN
144
end
145
if (opts['Desktop'])
146
flags |= PROCESS_EXECUTE_FLAG_DESKTOP
147
end
148
if (opts['Session'])
149
flags |= PROCESS_EXECUTE_FLAG_SESSION
150
request.add_tlv( TLV_TYPE_PROCESS_SESSION, opts['Session'] )
151
end
152
if (opts['Subshell'])
153
flags |= PROCESS_EXECUTE_FLAG_SUBSHELL
154
end
155
if (opts['Pty'])
156
flags |= PROCESS_EXECUTE_FLAG_PTY
157
end
158
if (opts['ParentPid'])
159
request.add_tlv(TLV_TYPE_PARENT_PID, opts['ParentPid']);
160
request.add_tlv(TLV_TYPE_PROCESS_PERMS, PROCESS_ALL_ACCESS)
161
request.add_tlv(TLV_TYPE_INHERIT, false)
162
end
163
inmem = opts['InMemory']
164
if inmem
165
166
# add the file contents into the tlv
167
f = ::File.new(path, 'rb')
168
request.add_tlv(TLV_TYPE_VALUE_DATA, f.read(f.stat.size))
169
f.close
170
171
# replace the path with the "dummy"
172
path = inmem.kind_of?(String) ? inmem : 'cmd'
173
end
174
end
175
176
# Add arguments
177
# If process arguments were supplied
178
if arguments.kind_of?(Array)
179
request.add_tlv(TLV_TYPE_PROCESS_UNESCAPED_PATH, client.unicode_filter_decode( path ))
180
# This flag is needed to disambiguate how to handle escaping special characters in the path when no arguments are provided
181
flags |= PROCESS_EXECUTE_FLAG_ARG_ARRAY
182
arguments.each do |arg|
183
request.add_tlv(TLV_TYPE_PROCESS_ARGUMENT, arg);
184
end
185
if opts[:legacy_path]
186
request.add_tlv(TLV_TYPE_PROCESS_PATH, opts[:legacy_path])
187
end
188
if opts[:legacy_args]
189
request.add_tlv(TLV_TYPE_PROCESS_ARGUMENTS, opts[:legacy_args])
190
end
191
elsif arguments.nil? || arguments.kind_of?(String)
192
request.add_tlv(TLV_TYPE_PROCESS_PATH, client.unicode_filter_decode( path ))
193
request.add_tlv(TLV_TYPE_PROCESS_ARGUMENTS, arguments)
194
else
195
raise ArgumentError.new('Unknown type for arguments')
196
end
197
198
request.add_tlv(TLV_TYPE_PROCESS_FLAGS, flags);
199
200
response = client.send_request(request)
201
202
# Get the response parameters
203
pid = response.get_tlv_value(TLV_TYPE_PID)
204
handle = response.get_tlv_value(TLV_TYPE_PROCESS_HANDLE)
205
channel_id = response.get_tlv_value(TLV_TYPE_CHANNEL_ID)
206
channel = nil
207
208
# If we were creating a channel out of this
209
if (channel_id != nil)
210
channel = Rex::Post::Meterpreter::Channels::Pools::StreamPool.new(client,
211
channel_id, "stdapi_process", CHANNEL_FLAG_SYNCHRONOUS, response)
212
end
213
214
# Return a process instance
215
return self.new(pid, handle, channel)
216
end
217
218
#
219
# Execute an application and capture the output
220
#
221
def Process.capture_output(path, arguments = '', opts = nil, time_out = 15)
222
start = Time.now.to_i
223
process = execute(path, arguments, opts)
224
data = ""
225
226
# Wait up to time_out seconds for the first bytes to arrive
227
while (d = process.channel.read)
228
data << d
229
if d == ""
230
if Time.now.to_i - start < time_out
231
sleep 0.1
232
else
233
break
234
end
235
end
236
end
237
data.chomp! if data
238
239
begin
240
process.channel.close
241
rescue IOError => e
242
# Channel was already closed, but we got the cmd output, so let's soldier on.
243
end
244
process.close
245
246
return data
247
end
248
249
#
250
# Kills one or more processes.
251
#
252
def Process.kill(*args)
253
request = Packet.create_request(COMMAND_ID_STDAPI_SYS_PROCESS_KILL)
254
255
args.each { |id|
256
request.add_tlv(TLV_TYPE_PID, id)
257
}
258
259
client.send_request(request)
260
261
return true
262
end
263
264
#
265
# Gets the process id that the remote side is executing under.
266
#
267
def Process.getpid
268
request = Packet.create_request(COMMAND_ID_STDAPI_SYS_PROCESS_GETPID)
269
270
response = client.send_request(request)
271
272
return response.get_tlv_value(TLV_TYPE_PID)
273
end
274
275
#
276
# Enumerates all of the elements in the array returned by get_processes.
277
#
278
def Process.each_process(&block)
279
self.get_processes.each(&block)
280
end
281
282
#
283
# Returns a ProcessList of processes as Hash objects with keys for 'pid',
284
# 'ppid', 'name', 'path', 'user', 'session' and 'arch'.
285
#
286
def Process.get_processes
287
request = Packet.create_request(COMMAND_ID_STDAPI_SYS_PROCESS_GET_PROCESSES)
288
processes = ProcessList.new
289
290
response = client.send_request(request)
291
292
response.each(TLV_TYPE_PROCESS_GROUP) { |p|
293
arch = ""
294
295
pa = p.get_tlv_value(TLV_TYPE_PROCESS_ARCH)
296
if !pa.nil?
297
if pa == 1 # PROCESS_ARCH_X86
298
arch = ARCH_X86
299
elsif pa == 2 # PROCESS_ARCH_X64
300
arch = ARCH_X64
301
end
302
else
303
arch = p.get_tlv_value(TLV_TYPE_PROCESS_ARCH_NAME)
304
end
305
306
processes <<
307
{
308
'pid' => p.get_tlv_value(TLV_TYPE_PID),
309
'ppid' => p.get_tlv_value(TLV_TYPE_PARENT_PID),
310
'name' => client.unicode_filter_encode( p.get_tlv_value(TLV_TYPE_PROCESS_NAME) ),
311
'path' => client.unicode_filter_encode( p.get_tlv_value(TLV_TYPE_PROCESS_PATH) ),
312
'session' => p.get_tlv_value(TLV_TYPE_PROCESS_SESSION),
313
'user' => client.unicode_filter_encode( p.get_tlv_value(TLV_TYPE_USER_NAME) ),
314
'arch' => arch
315
}
316
}
317
318
return processes
319
end
320
321
#
322
# An alias for get_processes.
323
#
324
def Process.processes
325
self.get_processes
326
end
327
328
#
329
# Search memory for supplied regexes and return matches
330
#
331
def Process.memory_search(pid: 0, needles: [''], min_match_length: 5, max_match_length: 127)
332
request = Packet.create_request(COMMAND_ID_STDAPI_SYS_PROCESS_MEMORY_SEARCH)
333
334
request.add_tlv(TLV_TYPE_PID, pid)
335
needles.each { |needle| request.add_tlv(TLV_TYPE_MEMORY_SEARCH_NEEDLE, needle) }
336
request.add_tlv(TLV_TYPE_MEMORY_SEARCH_MATCH_LEN, max_match_length)
337
request.add_tlv(TLV_TYPE_UINT, min_match_length)
338
339
self.client.send_request(request)
340
end
341
342
##
343
#
344
# Instance methods
345
#
346
##
347
348
#
349
# Initializes the process instance and its aliases.
350
#
351
def initialize(pid, handle, channel = nil)
352
self.client = self.class.client
353
self.handle = handle
354
self.channel = channel
355
356
# If the process identifier is zero, then we must lookup the current
357
# process identifier
358
if (pid == 0)
359
self.pid = client.sys.process.getpid
360
else
361
self.pid = pid
362
end
363
364
initialize_aliases(
365
{
366
'image' => Rex::Post::Meterpreter::Extensions::Stdapi::Sys::ProcessSubsystem::Image.new(self),
367
'io' => Rex::Post::Meterpreter::Extensions::Stdapi::Sys::ProcessSubsystem::IO.new(self),
368
'memory' => Rex::Post::Meterpreter::Extensions::Stdapi::Sys::ProcessSubsystem::Memory.new(self),
369
'thread' => Rex::Post::Meterpreter::Extensions::Stdapi::Sys::ProcessSubsystem::Thread.new(self),
370
})
371
372
# Ensure the remote object is closed when all references are removed
373
ObjectSpace.define_finalizer(self, self.class.finalize(client, handle))
374
end
375
376
def self.finalize(client, handle)
377
proc do
378
deferred_close_proc = proc do
379
begin
380
self.close(client, handle)
381
rescue => e
382
elog("finalize method for Process failed", error: e)
383
end
384
end
385
386
# 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
387
client.framework.sessions.schedule(deferred_close_proc)
388
end
389
end
390
391
#
392
# Returns the executable name of the process.
393
#
394
def name
395
return get_info()['name']
396
end
397
398
#
399
# Returns the path to the process' executable.
400
#
401
def path
402
return get_info()['path']
403
end
404
405
#
406
# Closes the handle to the process that was opened.
407
#
408
def self.close(client, handle)
409
request = Packet.create_request(COMMAND_ID_STDAPI_SYS_PROCESS_CLOSE)
410
request.add_tlv(TLV_TYPE_HANDLE, handle)
411
client.send_request(request, nil)
412
handle = nil
413
return true
414
end
415
416
#
417
# Instance method
418
#
419
def close(handle = self.handle)
420
unless self.pid.nil?
421
ObjectSpace.undefine_finalizer(self)
422
self.class.close(self.client, handle)
423
self.pid = nil
424
end
425
end
426
427
#
428
# Block until this process terminates on the remote side.
429
# By default we choose not to allow a packet response timeout to
430
# occur as we may be waiting indefinatly for the process to terminate.
431
#
432
def wait( timeout = -1 )
433
request = Packet.create_request(COMMAND_ID_STDAPI_SYS_PROCESS_WAIT)
434
435
request.add_tlv(TLV_TYPE_HANDLE, self.handle)
436
437
self.client.send_request(request, timeout)
438
439
self.handle = nil
440
441
return true
442
end
443
444
attr_reader :client, :handle, :channel, :pid # :nodoc:
445
protected
446
attr_writer :client, :handle, :channel, :pid # :nodoc:
447
448
#
449
# Gathers information about the process and returns a hash.
450
#
451
def get_info
452
request = Packet.create_request(COMMAND_ID_STDAPI_SYS_PROCESS_GET_INFO)
453
info = {}
454
455
request.add_tlv(TLV_TYPE_HANDLE, handle)
456
457
# Send the request
458
response = client.send_request(request)
459
460
# Populate the hash
461
info['name'] = client.unicode_filter_encode( response.get_tlv_value(TLV_TYPE_PROCESS_NAME) )
462
info['path'] = client.unicode_filter_encode( response.get_tlv_value(TLV_TYPE_PROCESS_PATH) )
463
464
return info
465
end
466
467
end
468
469
#
470
# Simple wrapper class for storing processes
471
#
472
class ProcessList < Array
473
474
#
475
# Create a Rex::Text::Table out of the processes stored in this list
476
#
477
# +opts+ is passed on to Rex::Text::Table.new, mostly unmolested
478
#
479
# Note that this output is affected by Rex::Post::Meterpreter::Client#unicode_filter_encode
480
#
481
def to_table(opts={})
482
if empty?
483
return Rex::Text::Table.new(opts)
484
end
485
486
column_headers = [ "PID", "PPID", "Name", "Arch", "Session", "User", "Path" ]
487
column_headers.delete_if do |h|
488
none? { |process| process.has_key?(h.downcase) } ||
489
all? { |process| process[h.downcase].nil? }
490
end
491
492
opts = {
493
'Header' => 'Process List',
494
'Indent' => 1,
495
'Columns' => column_headers
496
}.merge(opts)
497
498
tbl = Rex::Text::Table.new(opts)
499
each do |process|
500
tbl << column_headers.map do |header|
501
col = header.downcase
502
next unless process.keys.any? { |process_header| process_header == col }
503
val = process[col]
504
if col == 'session'
505
val == 0xFFFFFFFF ? '' : val.to_s
506
else
507
val
508
end
509
end
510
end
511
512
tbl
513
end
514
end
515
516
end; end; end; end; end; end
517
518